WSLC SDK install API update (#40949)

Updates the WSLC SDK install API to allow more flexibility in the calling pattern, specifically enabling caller-initiated updates/reinstalls to WSL even when WSLC is already present.

JohnMcPMS committed Jul 28, 2026 at 09:12 UTC b25c0820a9e9211d8c3212b45ee948120eb02919
17 files changed +354 -69
src/windows/WslcSDK/winrt/CMakeLists.txt
+2
@@ -9,6 +9,7 @@ set(SOURCES
9 ContainerVolume.cpp
10 ImageInfo.cpp
11 ImageProgress.cpp
12 + InstallOptions.cpp
13 InstallProgress.cpp
14 Process.cpp
15 ProcessCrashInformation.cpp
@@ -33,6 +34,7 @@ set(HEADERS
34 Helpers.h
35 ImageInfo.h
36 ImageProgress.h
37 + InstallOptions.h
38 InstallProgress.h
39 Process.h
40 ProcessCrashInformation.h
src/windows/WslcSDK/winrt/InstallOptions.cpp new
+41
@@ -0,0 +1,41 @@
1 +/*++
2 +
3 +Copyright (c) Microsoft. All rights reserved.
4 +
5 +Module Name:
6 +
7 + InstallOptions.cpp
8 +
9 +Abstract:
10 +
11 + This file contains the implementation of the WinRT wrapper for the WSLC SDK InstallOptions class.
12 +
13 +--*/
14 +
15 +#include "precomp.h"
16 +#include "InstallOptions.h"
17 +#include "Microsoft.WSL.Containers.InstallOptions.g.cpp"
18 +
19 +namespace winrt::Microsoft::WSL::Containers::implementation {
20 +
21 +winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::WSL::Containers::Component> InstallOptions::Components()
22 +{
23 + return m_components;
24 +}
25 +
26 +void InstallOptions::Components(winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::WSL::Containers::Component> value)
27 +{
28 + m_components = std::move(value);
29 +}
30 +
31 +bool InstallOptions::Repair()
32 +{
33 + return m_repair;
34 +}
35 +
36 +void InstallOptions::Repair(bool value)
37 +{
38 + m_repair = value;
39 +}
40 +
41 +} // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/InstallOptions.h new
+41
@@ -0,0 +1,41 @@
1 +/*++
2 +
3 +Copyright (c) Microsoft. All rights reserved.
4 +
5 +Module Name:
6 +
7 + InstallOptions.h
8 +
9 +Abstract:
10 +
11 + This file contains the definition of the WinRT wrapper for the WSLC SDK InstallOptions class.
12 +
13 +--*/
14 +
15 +#pragma once
16 +#include "Microsoft.WSL.Containers.InstallOptions.g.h"
17 +#include "Helpers.h"
18 +
19 +namespace winrt::Microsoft::WSL::Containers::implementation {
20 +struct InstallOptions : InstallOptionsT<InstallOptions>
21 +{
22 + InstallOptions() = default;
23 +
24 + winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::WSL::Containers::Component> Components();
25 + void Components(winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::WSL::Containers::Component> value);
26 + bool Repair();
27 + void Repair(bool value);
28 +
29 +private:
30 + winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::WSL::Containers::Component> m_components = nullptr;
31 + bool m_repair = false;
32 +};
33 +} // namespace winrt::Microsoft::WSL::Containers::implementation
34 +
35 +namespace winrt::Microsoft::WSL::Containers::factory_implementation {
36 +struct InstallOptions : InstallOptionsT<InstallOptions, implementation::InstallOptions>
37 +{
38 +};
39 +} // namespace winrt::Microsoft::WSL::Containers::factory_implementation
40 +
41 +DEFINE_TYPE_HELPERS(InstallOptions);
src/windows/WslcSDK/winrt/WslcService.cpp
+64 -4
@@ -24,6 +24,60 @@ using namespace winrt::Windows::Foundation::Collections;
24 namespace winrt::Microsoft::WSL::Containers::implementation {
25
26 namespace {
27 + WslcComponentFlags GetComponentsForInstall(const InstallOptions& options)
28 + {
29 + WslcComponentFlags result = WslcComponentFlags::WSLC_COMPONENT_FLAG_NONE;
30 + bool shouldCheckMissingComponents = true;
31 +
32 + if (options)
33 + {
34 + auto components = options.Components();
35 + if (components)
36 + {
37 + shouldCheckMissingComponents = false;
38 +
39 + for (const auto& component : components)
40 + {
41 + switch (component)
42 + {
43 + case Component::VirtualMachinePlatform:
44 + result |= WslcComponentFlags::WSLC_COMPONENT_FLAG_VIRTUAL_MACHINE_PLATFORM;
45 + break;
46 + case Component::WslPackage:
47 + result |= WslcComponentFlags::WSLC_COMPONENT_FLAG_WSL_PACKAGE;
48 + break;
49 + case Component::SdkNeedsUpdate:
50 + THROW_HR(WSLC_E_SDK_UPDATE_NEEDED);
51 + default:
52 + THROW_HR(E_INVALIDARG);
53 + }
54 + }
55 + }
56 + }
57 +
58 + if (shouldCheckMissingComponents)
59 + {
60 + winrt::check_hresult(WslcGetMissingComponents(&result));
61 + }
62 +
63 + return result;
64 + }
65 +
66 + WslcInstallOptions GetOptionsForInstall(const InstallOptions& options)
67 + {
68 + WslcInstallOptions result = WslcInstallOptions::WSLC_INSTALL_OPTION_NONE;
69 +
70 + if (options)
71 + {
72 + if (options.Repair())
73 + {
74 + result |= WslcInstallOptions::WSLC_INSTALL_OPTION_REPAIR;
75 + }
76 + }
77 +
78 + return result;
79 + }
80 +
81 void CALLBACK InstallProgressCallback(WslcComponentFlags component, uint32_t progressSteps, uint32_t totalSteps, PVOID context) noexcept
82 {
83 try
@@ -64,16 +118,22 @@ winrt::Microsoft::WSL::Containers::ServiceVersion WslcService::GetVersion()
118 return winrt::make<implementation::ServiceVersion>(version.major, version.minor, version.revision);
119 }
120
67 -IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::InstallProgress> WslcService::InstallWithDependenciesAsync()
121 +IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::InstallProgress> WslcService::InstallWithDependenciesAsync(
122 + winrt::Microsoft::WSL::Containers::InstallOptions options)
123 {
124 + auto components = GetComponentsForInstall(options);
125 + auto wslcOptions = GetOptionsForInstall(options);
126 +
127 co_await winrt::resume_background();
128
129 auto context = ProgressCallbackHelper<winrt::Microsoft::WSL::Containers::InstallProgress>{co_await winrt::get_progress_token()};
72 - winrt::check_hresult(WslcInstallWithDependencies(InstallProgressCallback, &context));
130 + winrt::check_hresult(WslcInstallWithDependencies(components, wslcOptions, InstallProgressCallback, &context));
131 }
132
75 -void WslcService::InstallWithDependencies()
133 +void WslcService::InstallWithDependencies(winrt::Microsoft::WSL::Containers::InstallOptions options)
134 {
77 - winrt::check_hresult(WslcInstallWithDependencies(nullptr, nullptr));
135 + auto components = GetComponentsForInstall(options);
136 + auto wslcOptions = GetOptionsForInstall(options);
137 + winrt::check_hresult(WslcInstallWithDependencies(components, wslcOptions, nullptr, nullptr));
138 }
139 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/WslcService.h
+3 -2
@@ -23,8 +23,9 @@ struct WslcService
23
24 static winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::WSL::Containers::Component> GetMissingComponents();
25 static winrt::Microsoft::WSL::Containers::ServiceVersion GetVersion();
26 - static void InstallWithDependencies();
27 - static winrt::Windows::Foundation::IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::InstallProgress> InstallWithDependenciesAsync();
26 + static void InstallWithDependencies(winrt::Microsoft::WSL::Containers::InstallOptions options);
27 + static winrt::Windows::Foundation::IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::InstallProgress> InstallWithDependenciesAsync(
28 + winrt::Microsoft::WSL::Containers::InstallOptions options);
29 };
30 } // namespace winrt::Microsoft::WSL::Containers::implementation
31 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
src/windows/WslcSDK/winrt/wslcsdk.idl
+10 -2
@@ -249,6 +249,14 @@ namespace Microsoft.WSL.Containers
249 UInt32 Revision { get; };
250 };
251
252 + runtimeclass InstallOptions
253 + {
254 + InstallOptions();
255 +
256 + IVectorView<Component> Components;
257 + Boolean Repair;
258 + };
259 +
260 runtimeclass InstallProgress
261 {
262 Component Component { get; };
@@ -260,8 +268,8 @@ namespace Microsoft.WSL.Containers
268 {
269 static IVectorView<Component> GetMissingComponents();
270 static ServiceVersion GetVersion();
263 - static void InstallWithDependencies();
264 - static Windows.Foundation.IAsyncActionWithProgress<InstallProgress> InstallWithDependenciesAsync();
271 + static void InstallWithDependencies(InstallOptions options);
272 + static Windows.Foundation.IAsyncActionWithProgress<InstallProgress> InstallWithDependenciesAsync(InstallOptions options);
273 };
274
275 enum VhdType
src/windows/WslcSDK/wslcsdk.cpp
+25 -13
@@ -1664,33 +1664,43 @@ try
1664 }
1665 CATCH_RETURN();
1666
1667 -STDAPI WslcInstallWithDependencies(_In_opt_ WslcInstallCallback progressCallback, _In_opt_ PVOID context)
1667 +STDAPI WslcInstallWithDependencies(
1668 + _In_ WslcComponentFlags components, _In_ WslcInstallOptions options, _In_opt_ WslcInstallCallback progressCallback, _In_opt_ PVOID context)
1669 try
1670 {
1671 + // Reject unknown flag bits.
1672 + constexpr WslcComponentFlags c_knownComponents =
1673 + WSLC_COMPONENT_FLAG_VIRTUAL_MACHINE_PLATFORM | WSLC_COMPONENT_FLAG_WSL_PACKAGE | WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE;
1674 + RETURN_HR_IF(E_INVALIDARG, (components & ~c_knownComponents) != WSLC_COMPONENT_FLAG_NONE);
1675 + constexpr WslcInstallOptions c_knownOptions = WSLC_INSTALL_OPTION_REPAIR;
1676 + RETURN_HR_IF(E_INVALIDARG, (options & ~c_knownOptions) != WSLC_INSTALL_OPTION_NONE);
1677 +
1678 + // This API cannot update the SDK that the client is using.
1679 + RETURN_HR_IF(WSLC_E_SDK_UPDATE_NEEDED, WI_IsFlagSet(components, WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE));
1680 +
1681 HRESULT result = S_OK;
1671 - bool needsVirtualMachine = NeedsVirtualMachineServicesInstalled();
1672 - auto runtimeResult = CreateSessionManagerRaw().second;
1682
1674 - if (!needsVirtualMachine && SUCCEEDED(runtimeResult))
1683 + if (components == WSLC_COMPONENT_FLAG_NONE)
1684 {
1685 return result;
1686 }
1687
1679 - THROW_HR_IF(runtimeResult, runtimeResult != REGDB_E_CLASSNOTREG && runtimeResult != WSLC_E_SDK_UPDATE_NEEDED);
1680 -
1681 - // Installing these components requires elevation.
1688 + // Installing components requires elevation.
1689 RETURN_HR_IF(
1690 HRESULT_FROM_WIN32(ERROR_ELEVATION_REQUIRED),
1691 !wsl::windows::common::security::IsTokenElevated(GetCurrentThreadEffectiveToken()) &&
1692 !wsl::windows::common::security::IsTokenLocalSystem(nullptr));
1693
1687 - if (needsVirtualMachine)
1694 + bool isRepair = WI_IsFlagSet(options, WSLC_INSTALL_OPTION_REPAIR);
1695 +
1696 + if (WI_IsFlagSet(components, WSLC_COMPONENT_FLAG_VIRTUAL_MACHINE_PLATFORM))
1697 {
1698 if (progressCallback)
1699 {
1700 progressCallback(WSLC_COMPONENT_FLAG_VIRTUAL_MACHINE_PLATFORM, 0, 1, context);
1701 }
1702
1703 + // No difference between install and repair, just let DISM attempt to enable the feature.
1704 auto exitCode = WslInstall::InstallOptionalComponent(WslInstall::c_optionalFeatureNameVmp, false);
1705 if (exitCode == ERROR_SUCCESS_REBOOT_REQUIRED)
1706 {
@@ -1709,7 +1719,7 @@ try
1719 }
1720 }
1721
1712 - if (!SUCCEEDED(runtimeResult))
1722 + if (WI_IsFlagSet(components, WSLC_COMPONENT_FLAG_WSL_PACKAGE))
1723 {
1724 std::function<void(uint32_t)> callback;
1725 if (progressCallback)
@@ -1719,14 +1729,16 @@ try
1729 };
1730 }
1731
1722 - wsl::windows::common::WindowsUpdateContext wuContext;
1723 - wuContext.RunUpdateFlow(true, callback);
1732 + using WindowsUpdateContext = wsl::windows::common::WindowsUpdateContext;
1733 + WindowsUpdateContext wuContext;
1734 + wuContext.RunUpdateFlow(
1735 + isRepair ? WindowsUpdateContext::UpdateOptions::ResetProductRegistration : WindowsUpdateContext::UpdateOptions::EnsureProductRegistration,
1736 + callback);
1737
1725 - // Because we do a forced install here, we expect an update.
1738 if (wuContext.GetUpdateCount() == 0)
1739 {
1740 // During the preview period, the package may not be published yet, so fall back to getting it from GH.
1729 - // When moving to GA, change this to a hard error to indicate a service configuration issue.
1741 + // When moving to GA, change this to an error like WSL_E_NO_UPDATE_AVAILABLE or similar.
1742 if (callback)
1743 {
1744 callback(0);
src/windows/WslcSDK/wslcsdk.h
+12 -2
@@ -624,8 +624,18 @@ STDAPI WslcGetVersion(_Out_writes_(1) WslcVersion* version);
624 typedef __callback void(CALLBACK* WslcInstallCallback)(
625 _In_ WslcComponentFlags component, _In_ uint32_t progressSteps, _In_ uint32_t totalSteps, _In_opt_ PVOID context);
626
627 +typedef enum WslcInstallOptions
628 +{
629 + WSLC_INSTALL_OPTION_NONE = 0,
630 + // Allows components to be reinstalled.
631 + WSLC_INSTALL_OPTION_REPAIR = 1,
632 +} WslcInstallOptions;
633 +
634 +DEFINE_ENUM_FLAG_OPERATORS(WslcInstallOptions);
635 +
636 // Callbacks will only be made for components that are actively installed by this call.
628 -// That list can be acquired prior to this call with `WslcCanRun`.
629 -STDAPI WslcInstallWithDependencies(_In_opt_ WslcInstallCallback progressCallback, _In_opt_ PVOID context);
637 +// The list of required components can be acquired prior to this call with `WslcGetMissingComponents`.
638 +STDAPI WslcInstallWithDependencies(
639 + _In_ WslcComponentFlags components, _In_ WslcInstallOptions options, _In_opt_ WslcInstallCallback progressCallback, _In_opt_ PVOID context);
640
641 EXTERN_C_END
src/windows/common/WindowsUpdateIntegration.cpp
+12 -15
@@ -126,18 +126,12 @@ namespace anon {
126 };
127 } // namespace anon
128
129 -WindowsUpdateContext::WindowsUpdateContext() :
130 - WindowsUpdateContext(std::make_unique<anon::DefaultWindowsUpdateClassFactory>(), WslProductIdentifier())
129 +WindowsUpdateContext::WindowsUpdateContext() : WindowsUpdateContext(std::make_unique<anon::DefaultWindowsUpdateClassFactory>())
130 {
131 }
132
134 -WindowsUpdateContext::WindowsUpdateContext(std::wstring product) :
135 - WindowsUpdateContext(std::make_unique<anon::DefaultWindowsUpdateClassFactory>(), std::move(product))
136 -{
137 -}
138 -
139 -WindowsUpdateContext::WindowsUpdateContext(std::unique_ptr<WindowsUpdateClassFactory> factory, std::wstring product) :
140 - m_factory(std::move(factory)), m_product(std::move(product))
133 +WindowsUpdateContext::WindowsUpdateContext(std::unique_ptr<WindowsUpdateClassFactory> factory) :
134 + m_factory(std::move(factory)), m_product(WslProductIdentifier())
135 {
136 m_session = m_factory->CreateUpdateSession();
137
@@ -158,9 +152,12 @@ std::wstring WindowsUpdateContext::WslProductIdentifier()
152 return STRING_TO_WIDE_STRING(DCAT_PRODUCT_NAME);
153 }
154
161 -void WindowsUpdateContext::EnsureProductRegistryEntry() const
155 +void WindowsUpdateContext::EnsureProductRegistryEntry(bool reset) const
156 {
163 - wsl::windows::common::helpers::RegisterWithDcat(false);
157 + if (reset || !wsl::windows::common::helpers::VersionRegisteredWithDcat())
158 + {
159 + wsl::windows::common::helpers::RegisterWithDcat(false);
160 + }
161 }
162
163 size_t WindowsUpdateContext::SearchForUpdates()
@@ -336,14 +333,14 @@ void WindowsUpdateContext::InstallUpdates(const std::function<void(uint32_t)>& p
333 THROW_IF_FAILED(installationHResult);
334 }
335
339 -void WindowsUpdateContext::RunUpdateFlow(bool forceInstall, const std::function<void(uint32_t)>& progress)
336 +void WindowsUpdateContext::RunUpdateFlow(UpdateOptions options, const std::function<void(uint32_t)>& progress)
337 {
338 TraceLoggingWriteTagged(
339 *m_activity,
340 "RunUpdateFlow",
341 TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
342 TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage),
346 - TraceLoggingBool(forceInstall, "forceInstall"));
343 + TraceLoggingUInt32(static_cast<std::underlying_type_t<UpdateOptions>>(options), "options"));
344
345 static_assert(
346 DownloadProgressPercent + InstallProgressPercent == 100, "Download and Install progress values must add up to 100.");
@@ -353,9 +350,9 @@ void WindowsUpdateContext::RunUpdateFlow(bool forceInstall, const std::function<
350 progress(0);
351 }
352
356 - if (forceInstall)
353 + if (options != UpdateOptions::None)
354 {
358 - EnsureProductRegistryEntry();
355 + EnsureProductRegistryEntry(options == UpdateOptions::ResetProductRegistration);
356 }
357
358 size_t updateCount = SearchForUpdates();
src/windows/common/WindowsUpdateIntegration.h
+13 -9
@@ -31,11 +31,8 @@ struct WindowsUpdateContext
31 // Create a context using the default class factory and WSL product.
32 WindowsUpdateContext();
33
34 - // Create a context using the default class factory.
35 - WindowsUpdateContext(std::wstring product);
36 -
34 // Create a context using the provided class factory.
38 - WindowsUpdateContext(std::unique_ptr<WindowsUpdateClassFactory> factory, std::wstring product);
35 + WindowsUpdateContext(std::unique_ptr<WindowsUpdateClassFactory> factory);
36
37 NON_COPYABLE(WindowsUpdateContext);
38 DEFAULT_MOVABLE(WindowsUpdateContext);
@@ -45,7 +42,8 @@ struct WindowsUpdateContext
42
43 // Ensures that the product is registered in with the Windows Update system.
44 // This is required to use the system for initial installs.
48 - void EnsureProductRegistryEntry() const;
45 + // When `reset` is true, always sets the entry to a value that will result in an install.
46 + void EnsureProductRegistryEntry(bool reset = false) const;
47
48 // Searches for updates for the product.
49 // Returns the number of updates found.
@@ -65,11 +63,17 @@ struct WindowsUpdateContext
63 static constexpr uint32_t DownloadProgressPercent = 70;
64 static constexpr uint32_t InstallProgressPercent = 30;
65
68 - // Performs a complete update flow. This is a convenience method to remove the need to call and coordinate the individual actions.
69 - // When `forceInstall` is true, `EnsureProductRegistryEntry` is called.
70 - // Calls the progress callback, if provided, with the overall update progress estimate.
66 + enum class UpdateOptions
67 + {
68 + None,
69 + EnsureProductRegistration,
70 + ResetProductRegistration,
71 + };
72 +
73 + // Performs a complete update flow. This is a convenience method to remove the need to call and coordinate the individual
74 + // actions. Calls the progress callback, if provided, with the overall update progress estimate.
75 // Download and install phases are split according to the values defined above.
72 - void RunUpdateFlow(bool forceInstall = false, const std::function<void(uint32_t)>& progress = {});
76 + void RunUpdateFlow(UpdateOptions options = UpdateOptions::EnsureProductRegistration, const std::function<void(uint32_t)>& progress = {});
77
78 private:
79 using ActivityType = TraceLoggingActivity<g_hTraceLoggingProvider, MICROSOFT_KEYWORD_MEASURES>;
src/windows/common/helpers.cpp
+17 -1
@@ -38,6 +38,7 @@ using wsl::windows::common::helpers::LaunchWslRelayFlags;
38
39 constexpr auto c_WslSupportInterfaceKey = L"Software\\Classes\\Interface\\{46f3c96d-ffa3-42f0-b052-52f5e7ecbb08}";
40 constexpr auto c_WslSupportInterfaceName = L"IWslSupport";
41 +constexpr auto c_DcatRegistryVersionValueName = L"Version";
42
43 constexpr ULONG c_MaxStorageHwQueues = 4;
44
@@ -746,6 +747,21 @@ bool wsl::windows::common::helpers::TryAttachConsole()
747 return ReopenStdHandles();
748 }
749
750 +std::optional<std::wstring> wsl::windows::common::helpers::VersionRegisteredWithDcat()
751 +{
752 + try
753 + {
754 + auto [dcatKey, result] = wsl::windows::common::registry::OpenKeyNoThrow(HKEY_LOCAL_MACHINE, TEXT(DCAT_REGISTRATION_KEY), KEY_READ);
755 + if (SUCCEEDED(result))
756 + {
757 + return wsl::windows::common::registry::ReadOptionalString(dcatKey.get(), nullptr, c_DcatRegistryVersionValueName);
758 + }
759 + }
760 + CATCH_LOG()
761 +
762 + return {};
763 +}
764 +
765 void wsl::windows::common::helpers::RegisterWithDcat(_In_ bool IncludeVersionNumber)
766 try
767 {
@@ -760,7 +776,7 @@ try
776 }
777
778 wil::unique_hkey dcatKey = wsl::windows::common::registry::CreateKey(HKEY_LOCAL_MACHINE, TEXT(DCAT_REGISTRATION_KEY), KEY_SET_VALUE);
763 - wsl::windows::common::registry::WriteString(dcatKey.get(), nullptr, L"Version", registeredVersion.c_str());
779 + wsl::windows::common::registry::WriteString(dcatKey.get(), nullptr, c_DcatRegistryVersionValueName, registeredVersion.c_str());
780 }
781 CATCH_LOG()
782
src/windows/common/helpers.hpp
+2
@@ -203,6 +203,8 @@ void SetHandleInheritable(_In_ HANDLE Handle, _In_ bool Inheritable = true);
203
204 bool TryAttachConsole();
205
206 +std::optional<std::wstring> VersionRegisteredWithDcat();
207 +
208 void RegisterWithDcat(_In_ bool IncludeVersionNumber = true);
209
210 void AppendCommonKernelCommandLine(_Inout_ std::wstring& kernelCmdLine, _In_ int pageReportingOrder, _In_ ULONG64 swiotlbSizeBytes, _In_ ULONG cpuCount);
test/windows/Common.cpp
+3 -2
@@ -2568,7 +2568,7 @@ void ScopedEnvVariable::Clear()
2568 VERIFY_IS_TRUE(SetEnvironmentVariableW(m_name.c_str(), nullptr));
2569 }
2570
2571 -UniqueWebServer::UniqueWebServer(LPCWSTR Endpoint, LPCWSTR Content)
2571 +UniqueWebServer::UniqueWebServer(LPCWSTR Endpoint, LPCWSTR Content, UINT StatusCode)
2572 {
2573 auto cmd = std::format(
2574 LR"(Powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "
@@ -2579,12 +2579,13 @@ $server.Start()
2579 while ($true)
2580 {{
2581 $context = $server.GetContext()
2582 - $context.Response.StatusCode
2582 + $context.Response.StatusCode = {}
2583 $content = [Text.Encoding]::UTF8.GetBytes('{}')
2584 $context.Response.OutputStream.Write($content , 0, $content.length)
2585 $context.Response.close()
2586 }}")",
2587 Endpoint,
2588 + StatusCode,
2589 Content);
2590
2591 m_process = LxsstuStartProcess(cmd.data());
test/windows/Common.h
+1 -1
@@ -356,7 +356,7 @@ private:
356 class UniqueWebServer
357 {
358 public:
359 - UniqueWebServer(LPCWSTR Endpoint, LPCWSTR ResponseContent);
359 + UniqueWebServer(LPCWSTR Endpoint, LPCWSTR ResponseContent, UINT StatusCode = 200);
360 UniqueWebServer(LPCWSTR Endpoint, const std::filesystem::path& path);
361 ~UniqueWebServer();
362 UniqueWebServer(const UniqueWebServer&) = delete;
test/windows/WindowsUpdateTests.cpp
+17 -17
@@ -830,7 +830,7 @@ class WindowsUpdateTests
830 TEST_METHOD(SearchForUpdates_NoUpdates)
831 {
832 auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
833 - WindowsUpdateContext ctx(std::move(factory), L"TestProduct");
833 + WindowsUpdateContext ctx(std::move(factory));
834
835 VERIFY_ARE_EQUAL(0u, ctx.SearchForUpdates());
836 VERIFY_ARE_EQUAL(0u, ctx.GetUpdateCount());
@@ -845,7 +845,7 @@ class WindowsUpdateTests
845 AddMockUpdate(col, VARIANT_FALSE);
846 AddMockUpdate(col, VARIANT_FALSE);
847
848 - WindowsUpdateContext ctx(std::move(factory), L"TestProduct");
848 + WindowsUpdateContext ctx(std::move(factory));
849
850 VERIFY_ARE_EQUAL(3u, ctx.SearchForUpdates());
851 VERIFY_ARE_EQUAL(3u, ctx.GetUpdateCount());
@@ -858,7 +858,7 @@ class WindowsUpdateTests
858 fp->session->searcher->searchResult->resultCode = OperationResultCode::orcSucceededWithErrors;
859 AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_FALSE);
860
861 - WindowsUpdateContext ctx(std::move(factory), L"TestProduct");
861 + WindowsUpdateContext ctx(std::move(factory));
862
863 // orcSucceededWithErrors must succeed — the update count is still returned.
864 VERIFY_ARE_EQUAL(1u, ctx.SearchForUpdates());
@@ -869,7 +869,7 @@ class WindowsUpdateTests
869 auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
870 factory->session->searcher->searchResult->resultCode = OperationResultCode::orcFailed;
871
872 - WindowsUpdateContext ctx(std::move(factory), L"TestProduct");
872 + WindowsUpdateContext ctx(std::move(factory));
873
874 VERIFY_ARE_EQUAL(WSLC_E_WU_SEARCH_FAILED, CaptureHResult([&] { ctx.SearchForUpdates(); }));
875 }
@@ -886,7 +886,7 @@ class WindowsUpdateTests
886 AddMockUpdate(col, VARIANT_TRUE);
887 AddMockUpdate(col, VARIANT_TRUE);
888
889 - WindowsUpdateContext ctx(std::move(factory), L"TestProduct");
889 + WindowsUpdateContext ctx(std::move(factory));
890 ctx.SearchForUpdates();
891
892 std::vector<uint32_t> progressCalls;
@@ -909,7 +909,7 @@ class WindowsUpdateTests
909 AddMockUpdate(col, VARIANT_FALSE); // needs download
910 AddMockUpdate(col, VARIANT_FALSE); // needs download
911
912 - WindowsUpdateContext ctx(std::move(factory), L"TestProduct");
912 + WindowsUpdateContext ctx(std::move(factory));
913 ctx.SearchForUpdates();
914 ctx.DownloadUpdates();
915
@@ -924,7 +924,7 @@ class WindowsUpdateTests
924 AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_FALSE);
925 fp->session->downloader->downloadResult->downloadHResult = E_FAIL;
926
927 - WindowsUpdateContext ctx(std::move(factory), L"TestProduct");
927 + WindowsUpdateContext ctx(std::move(factory));
928 ctx.SearchForUpdates();
929
930 VERIFY_ARE_EQUAL(E_FAIL, CaptureHResult([&] { ctx.DownloadUpdates(); }));
@@ -940,7 +940,7 @@ class WindowsUpdateTests
940 auto* fp = factory.get();
941 AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_TRUE);
942
943 - WindowsUpdateContext ctx(std::move(factory), L"TestProduct");
943 + WindowsUpdateContext ctx(std::move(factory));
944 ctx.SearchForUpdates();
945
946 // Should not throw.
@@ -957,7 +957,7 @@ class WindowsUpdateTests
957 AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_TRUE);
958 fp->session->installer->installResult->installHResult = E_ACCESSDENIED;
959
960 - WindowsUpdateContext ctx(std::move(factory), L"TestProduct");
960 + WindowsUpdateContext ctx(std::move(factory));
961 ctx.SearchForUpdates();
962
963 VERIFY_ARE_EQUAL(E_ACCESSDENIED, CaptureHResult([&] { ctx.InstallUpdates(); }));
@@ -972,10 +972,10 @@ class WindowsUpdateTests
972 auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
973 auto* fp = factory.get();
974
975 - WindowsUpdateContext ctx(std::move(factory), L"TestProduct");
975 + WindowsUpdateContext ctx(std::move(factory));
976
977 std::vector<uint32_t> progressCalls;
978 - ctx.RunUpdateFlow(false, [&](uint32_t p) { progressCalls.push_back(p); });
978 + ctx.RunUpdateFlow(WindowsUpdateContext::UpdateOptions::None, [&](uint32_t p) { progressCalls.push_back(p); });
979
980 // progress(0) at the start, progress(100) because there are no updates.
981 VERIFY_ARE_EQUAL(2u, progressCalls.size());
@@ -993,10 +993,10 @@ class WindowsUpdateTests
993 auto* fp = factory.get();
994 AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_FALSE);
995
996 - WindowsUpdateContext ctx(std::move(factory), L"TestProduct");
996 + WindowsUpdateContext ctx(std::move(factory));
997
998 std::vector<uint32_t> progressCalls;
999 - ctx.RunUpdateFlow(false, [&](uint32_t p) { progressCalls.push_back(p); });
999 + ctx.RunUpdateFlow(WindowsUpdateContext::UpdateOptions::None, [&](uint32_t p) { progressCalls.push_back(p); });
1000
1001 // progress(0) is emitted at the start.
1002 VERIFY_IS_FALSE(progressCalls.empty());
@@ -1027,9 +1027,9 @@ class WindowsUpdateTests
1027 AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_FALSE);
1028 fp->session->downloader->downloadResult->downloadHResult = E_FAIL;
1029
1030 - WindowsUpdateContext ctx(std::move(factory), L"TestProduct");
1030 + WindowsUpdateContext ctx(std::move(factory));
1031
1032 - VERIFY_ARE_EQUAL(E_FAIL, CaptureHResult([&] { ctx.RunUpdateFlow(); }));
1032 + VERIFY_ARE_EQUAL(E_FAIL, CaptureHResult([&] { ctx.RunUpdateFlow(WindowsUpdateContext::UpdateOptions::None); }));
1033
1034 // Install should not have been called after download failure.
1035 VERIFY_IS_FALSE(fp->session->installer->beginInstallCalled);
@@ -1041,9 +1041,9 @@ class WindowsUpdateTests
1041 auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
1042 AddMockUpdate(factory->session->searcher->searchResult->updates.get(), VARIANT_TRUE);
1043
1044 - WindowsUpdateContext ctx(std::move(factory), L"TestProduct");
1044 + WindowsUpdateContext ctx(std::move(factory));
1045
1046 // Should complete without crashing even with no progress callback.
1047 - ctx.RunUpdateFlow();
1047 + ctx.RunUpdateFlow(WindowsUpdateContext::UpdateOptions::None);
1048 }
1049 };
test/windows/WslcSdkTests.cpp
+54
@@ -1513,6 +1513,60 @@ class WslcSdkTests
1513 VERIFY_ARE_EQUAL(missing, WSLC_COMPONENT_FLAG_NONE);
1514 }
1515
1516 + WSLC_TEST_METHOD(InstallWithDependencies_NoComponents_Succeeds)
1517 + {
1518 + // Passing WSLC_COMPONENT_FLAG_NONE must return S_OK immediately without requiring elevation.
1519 + VERIFY_SUCCEEDED(WslcInstallWithDependencies(WSLC_COMPONENT_FLAG_NONE, WSLC_INSTALL_OPTION_NONE, nullptr, nullptr));
1520 + }
1521 +
1522 + WSLC_TEST_METHOD(InstallWithDependencies_SdkNeedsUpdate_ReturnsError)
1523 + {
1524 + // Passing SDK_NEEDS_UPDATE must always return WSLC_E_SDK_UPDATE_NEEDED — the caller must update their SDK.
1525 + VERIFY_ARE_EQUAL(
1526 + WSLC_E_SDK_UPDATE_NEEDED,
1527 + WslcInstallWithDependencies(WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE, WSLC_INSTALL_OPTION_NONE, nullptr, nullptr));
1528 + }
1529 +
1530 + WSLC_TEST_METHOD(InstallWithDependencies_WslPackage_GhFallback404)
1531 + {
1532 + // Without repair semantics, DCAT uses EnsureProductRegistration so it should find no update
1533 + // (the product is already registered at the current version). The code then falls back to the
1534 + // GitHub release endpoint. This test intercepts that fallback: the fake API server returns a
1535 + // release whose asset URL points at a second local server that always responds with HTTP 404,
1536 + // so the download fails and WslcInstallWithDependencies surfaces an error HRESULT.
1537 + constexpr auto apiEndpoint = L"http://127.0.0.1:12345/";
1538 + constexpr auto assetEndpoint = L"http://127.0.0.1:12346/";
1539 +
1540 + RegistryKeyChange<std::wstring> urlOverride(
1541 + HKEY_LOCAL_MACHINE,
1542 + L"Software\\Microsoft\\Windows\\CurrentVersion\\Lxss",
1543 + wsl::windows::common::wslutil::c_githubUrlOverrideRegistryValue,
1544 + apiEndpoint);
1545 +
1546 + // Version 1.0.0 is below the current package version, so without repair=true the version
1547 + // check in UpdatePackageImpl would short-circuit and return early. Using a sub-2.0 version
1548 + // here confirms that the repair flag (always set in the GH fallback) is what drives the
1549 + // download attempt rather than the version being newer than the installed one.
1550 + constexpr auto GitHubApiResponse =
1551 + LR"([{
1552 + \"name\": \"1.0.0\",
1553 + \"created_at\": \"2023-06-14T16:56:30Z\",
1554 + \"assets\": [
1555 + {
1556 + \"url\": \"http://127.0.0.1:12346/fake.msixbundle\",
1557 + \"id\": 1,
1558 + \"name\": \"Microsoft.WSL_1.0.0.0_x64_ARM64.msixbundle\"
1559 + }
1560 + ]
1561 + }])";
1562 +
1563 + UniqueWebServer apiServer(apiEndpoint, GitHubApiResponse);
1564 + UniqueWebServer assetServer(assetEndpoint, L"", 404u);
1565 +
1566 + VERIFY_ARE_EQUAL(
1567 + HTTP_E_STATUS_NOT_FOUND, WslcInstallWithDependencies(WSLC_COMPONENT_FLAG_WSL_PACKAGE, WSLC_INSTALL_OPTION_NONE, nullptr, nullptr));
1568 + }
1569 +
1570 // -----------------------------------------------------------------------
1571 // WslcSetProcessSettingsCallbacks tests
1572 // -----------------------------------------------------------------------
test/windows/WslcSdkWinRTTests.cpp
+37 -1
@@ -1164,10 +1164,46 @@ class WslcSdkWinRtTests
1164
1165 WSLC_TEST_METHOD(InstallWithDependencies)
1166 {
1167 - WSLCSDK::WslcService::InstallWithDependenciesAsync().get();
1167 + // Pass null options to auto-detect and install any missing components (same behavior as the old no-arg call).
1168 + WSLCSDK::WslcService::InstallWithDependenciesAsync(nullptr).get();
1169 VERIFY_ARE_EQUAL(WSLCSDK::WslcService::GetMissingComponents().Size(), 0u);
1170 }
1171
1172 + WSLC_TEST_METHOD(InstallOptions_DefaultValues)
1173 + {
1174 + // Default-constructed InstallOptions must have null Components and Repair=false.
1175 + auto options = WSLCSDK::InstallOptions();
1176 + VERIFY_IS_NULL(options.Components());
1177 + VERIFY_IS_FALSE(options.Repair());
1178 + }
1179 +
1180 + WSLC_TEST_METHOD(InstallOptions_SetRepair)
1181 + {
1182 + auto options = WSLCSDK::InstallOptions();
1183 + options.Repair(true);
1184 + VERIFY_IS_TRUE(options.Repair());
1185 + options.Repair(false);
1186 + VERIFY_IS_FALSE(options.Repair());
1187 + }
1188 +
1189 + WSLC_TEST_METHOD(InstallOptions_SetComponents)
1190 + {
1191 + auto options = WSLCSDK::InstallOptions();
1192 + auto components = winrt::single_threaded_vector<WSLCSDK::Component>({WSLCSDK::Component::WslPackage});
1193 + options.Components(components.GetView());
1194 + VERIFY_ARE_EQUAL(1u, options.Components().Size());
1195 + VERIFY_ARE_EQUAL(WSLCSDK::Component::WslPackage, options.Components().GetAt(0));
1196 + }
1197 +
1198 + WSLC_TEST_METHOD(InstallWithDependencies_SdkNeedsUpdate_Throws)
1199 + {
1200 + // Passing SdkNeedsUpdate in the component list must throw WSLC_E_SDK_UPDATE_NEEDED.
1201 + auto options = WSLCSDK::InstallOptions();
1202 + auto components = winrt::single_threaded_vector<WSLCSDK::Component>({WSLCSDK::Component::SdkNeedsUpdate});
1203 + options.Components(components.GetView());
1204 + VERIFY_THROWS_HR(WSLCSDK::WslcService::InstallWithDependenciesAsync(options).get(), WSLC_E_SDK_UPDATE_NEEDED);
1205 + }
1206 +
1207 // -----------------------------------------------------------------------
1208 // Process IO event tests
1209 // -----------------------------------------------------------------------