@samitouri / QOSAMI-WSL / commits / 3e879d65

Add a new API to query the earliest supported client version (#40067)

* Add a new API to query the earliest supported client version * Apply PR feedback * Add test coverage * Apply PR feedback * Apply PR feedback * Update logic * Format

Blue committed May 7, 2026 at 15:57 UTC 3e879d65cd51fe9a87a313f9a9400cce777ef692
7 files changed +148 -28
src/windows/WslcSDK/wslcsdk.cpp
+38 -18
@@ -309,6 +309,25 @@ std::pair<wil::com_ptr<IWSLCSessionManager>, HRESULT> CreateSessionManagerRaw()
309 {
310 wil::com_ptr<IWSLCSessionManager> result;
311 HRESULT hr = CoCreateInstance(__uuidof(WSLCSessionManager), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&result));
312 + if (SUCCEEDED(hr))
313 + {
314 + const WSLCVersion clientVersion{WSL_PACKAGE_VERSION_MAJOR, WSL_PACKAGE_VERSION_MINOR, WSL_PACKAGE_VERSION_REVISION};
315 + BOOL isSupported = FALSE;
316 + THROW_IF_FAILED(result->IsClientVersionSupported(&clientVersion, &isSupported));
317 +
318 + if (!isSupported)
319 + {
320 + LOG_HR_MSG(
321 + WSLC_E_SDK_UPDATE_NEEDED,
322 + "WSLC SDK update required. Current SDK version: %lu.%lu.%lu",
323 + WSL_PACKAGE_VERSION_MAJOR,
324 + WSL_PACKAGE_VERSION_MINOR,
325 + WSL_PACKAGE_VERSION_REVISION);
326 +
327 + return {result, WSLC_E_SDK_UPDATE_NEEDED};
328 + }
329 + }
330 +
331 return {result, hr};
332 }
333
@@ -336,20 +355,6 @@ wil::com_ptr<IWSLCSessionManager> CreateSessionManager()
355 return result;
356 }
357
339 -bool NeedsWslRuntimeInstalled()
340 -{
341 - auto hr = CreateSessionManagerRaw().second;
342 -
343 - if (SUCCEEDED(hr))
344 - {
345 - return false;
346 - }
347 - else if (hr == REGDB_E_CLASSNOTREG)
348 - {
349 - return true;
350 - }
351 - THROW_HR(hr);
352 -}
358 } // namespace
359
360 // SESSION DEFINITIONS
@@ -1468,7 +1473,20 @@ try
1473 WslcComponentFlags componentCheck = WSLC_COMPONENT_FLAG_NONE;
1474
1475 WI_SetFlagIf(componentCheck, WSLC_COMPONENT_FLAG_VIRTUAL_MACHINE_PLATFORM, NeedsVirtualMachineServicesInstalled());
1471 - WI_SetFlagIf(componentCheck, WSLC_COMPONENT_FLAG_WSL_PACKAGE, NeedsWslRuntimeInstalled());
1476 +
1477 + auto hr = CreateSessionManagerRaw().second;
1478 + if (hr == REGDB_E_CLASSNOTREG)
1479 + {
1480 + WI_SetFlag(componentCheck, WSLC_COMPONENT_FLAG_WSL_PACKAGE);
1481 + }
1482 + else if (hr == WSLC_E_SDK_UPDATE_NEEDED)
1483 + {
1484 + WI_SetFlag(componentCheck, WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE);
1485 + }
1486 + else if (FAILED(hr))
1487 + {
1488 + THROW_HR(hr);
1489 + }
1490
1491 *missingComponents = componentCheck;
1492
@@ -1502,13 +1520,15 @@ try
1520 {
1521 HRESULT result = S_OK;
1522 bool needsVirtualMachine = NeedsVirtualMachineServicesInstalled();
1505 - bool needsRuntime = NeedsWslRuntimeInstalled();
1523 + auto runtimeResult = CreateSessionManagerRaw().second;
1524
1507 - if (!needsVirtualMachine && !needsRuntime)
1525 + if (!needsVirtualMachine && SUCCEEDED(runtimeResult))
1526 {
1527 return result;
1528 }
1529
1530 + THROW_HR_IF(runtimeResult, runtimeResult != REGDB_E_CLASSNOTREG && runtimeResult != WSLC_E_SDK_UPDATE_NEEDED);
1531 +
1532 // Installing these components requires elevation.
1533 auto token = wil::open_current_access_token();
1534 RETURN_HR_IF(
@@ -1540,7 +1560,7 @@ try
1560 }
1561 }
1562
1543 - if (needsRuntime)
1563 + if (!SUCCEEDED(runtimeResult))
1564 {
1565 std::function<void(uint32_t)> callback;
1566 if (progressCallback)
src/windows/WslcSDK/wslcsdk.h
+16
@@ -25,6 +25,20 @@ Abstract:
25
26 EXTERN_C_START
27
28 +// WSLC specific error codes
29 +#define WSLC_E_BASE (0x0600)
30 +#define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */
31 +#define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */
32 +#define WSLC_E_CONTAINER_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 3) /* 0x80040603 */
33 +#define WSLC_E_VOLUME_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 4) /* 0x80040604 */
34 +#define WSLC_E_CONTAINER_NOT_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */
35 +#define WSLC_E_CONTAINER_IS_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 6) /* 0x80040606 */
36 +#define WSLC_E_SESSION_RESERVED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 7) /* 0x80040607 */
37 +#define WSLC_E_INVALID_SESSION_NAME MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 8) /* 0x80040608 */
38 +#define WSLC_E_NETWORK_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 9) /* 0x80040609 */
39 +#define WSLC_E_WU_SEARCH_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 10) /* 0x8004060A */
40 +#define WSLC_E_SDK_UPDATE_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 11) /* 0x8004060B */
41 +
42 // Session values
43 #define WSLC_SESSION_OPTIONS_SIZE 80
44 #define WSLC_SESSION_OPTIONS_ALIGNMENT 8
@@ -546,6 +560,8 @@ typedef enum WslcComponentFlags
560 WSLC_COMPONENT_FLAG_VIRTUAL_MACHINE_PLATFORM = 1,
561 // The WSL runtime package, at an appropriate version to provide support for WSLC.
562 WSLC_COMPONENT_FLAG_WSL_PACKAGE = 2,
563 + // Set if the WSLC SDK itself needs to be updated.
564 + WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE = 4,
565 } WslcComponentFlags;
566
567 DEFINE_ENUM_FLAG_OPERATORS(WslcComponentFlags);
src/windows/service/exe/WSLCSessionManager.cpp
+31 -8
@@ -321,13 +321,6 @@ void WSLCSessionManagerImpl::ListSessions(_Out_ WSLCSessionInformation** Session
321 *SessionsCount = static_cast<ULONG>(sessionInfo.size());
322 }
323
324 -void WSLCSessionManagerImpl::GetVersion(_Out_ WSLCVersion* Version)
325 -{
326 - Version->Major = WSL_PACKAGE_VERSION_MAJOR;
327 - Version->Minor = WSL_PACKAGE_VERSION_MINOR;
328 - Version->Revision = WSL_PACKAGE_VERSION_REVISION;
329 -}
330 -
324 void WSLCSessionManagerImpl::EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession)
325 {
326 THROW_HR_IF(E_POINTER, DisplayName == nullptr || StoragePath == nullptr);
@@ -450,9 +443,39 @@ WSLCSessionManager::WSLCSessionManager(WSLCSessionManagerImpl* Impl) : COMImplCl
443 }
444
445 HRESULT WSLCSessionManager::GetVersion(_Out_ WSLCVersion* Version)
446 +try
447 {
454 - return CallImpl(&WSLCSessionManagerImpl::GetVersion, Version);
448 + RETURN_HR_IF(E_POINTER, Version == nullptr);
449 +
450 + Version->Major = WSL_PACKAGE_VERSION_MAJOR;
451 + Version->Minor = WSL_PACKAGE_VERSION_MINOR;
452 + Version->Revision = WSL_PACKAGE_VERSION_REVISION;
453 +
454 + return S_OK;
455 +}
456 +CATCH_RETURN();
457 +
458 +HRESULT WSLCSessionManager::IsClientVersionSupported(_In_ const WSLCVersion* ClientVersion, _Out_ BOOL* IsSupported)
459 +try
460 +{
461 + RETURN_HR_IF(E_POINTER, ClientVersion == nullptr || IsSupported == nullptr);
462 +
463 + WSL_LOG(
464 + "ClientVersionCheck",
465 + TraceLoggingValue(ClientVersion->Major, "Major"),
466 + TraceLoggingValue(ClientVersion->Minor, "Minor"),
467 + TraceLoggingValue(ClientVersion->Revision, "Revision"));
468 +
469 + constexpr std::tuple<uint32_t, uint32_t, uint32_t> c_minClientVersion{2, 9, 0};
470 +
471 + const std::tuple<uint32_t, uint32_t, uint32_t> clientVersion{ClientVersion->Major, ClientVersion->Minor, ClientVersion->Revision};
472 +
473 + // For now set 2.9.0 as the floor version. Also support if the client version exactly matches ours to cover builds before 2.9.0.
474 + *IsSupported = (clientVersion >= c_minClientVersion || wsl::shared::PackageVersion == clientVersion);
475 +
476 + return S_OK;
477 }
478 +CATCH_RETURN();
479
480 HRESULT WSLCSessionManager::CreateSession(const WSLCSessionSettings* WslcSessionSettings, WSLCSessionFlags Flags, IWSLCSession** WslcSession)
481 {
src/windows/service/exe/WSLCSessionManager.h
+1 -1
@@ -71,7 +71,6 @@ public:
71 WSLCSessionManagerImpl() = default;
72 ~WSLCSessionManagerImpl();
73
74 - void GetVersion(_Out_ WSLCVersion* Version);
74 void CreateSession(const WSLCSessionSettings* WslcSessionSettings, WSLCSessionFlags Flags, IWSLCSession** WslcSession);
75 void EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession);
76 void ListSessions(_Out_ WSLCSessionInformation** Sessions, _Out_ ULONG* SessionsCount);
@@ -181,6 +180,7 @@ public:
180 WSLCSessionManager(wsl::windows::service::wslc::WSLCSessionManagerImpl* Impl);
181
182 IFACEMETHOD(GetVersion)(_Out_ WSLCVersion* Version) override;
183 + IFACEMETHOD(IsClientVersionSupported)(_In_ const WSLCVersion* ClientVersion, _Out_ BOOL* IsSupported) override;
184 IFACEMETHOD(CreateSession)(const WSLCSessionSettings* WslcSessionSettings, WSLCSessionFlags Flags, IWSLCSession** WslcSession) override;
185 IFACEMETHOD(EnterSession)(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession) override;
186 IFACEMETHOD(ListSessions)(_Out_ WSLCSessionInformation** Sessions, _Out_ ULONG* SessionsCount) override;
src/windows/service/inc/wslc.idl
+2
@@ -835,6 +835,7 @@ cpp_quote("DEFINE_ENUM_FLAG_OPERATORS(WSLCSessionFlags);")
835 interface IWSLCSessionManager : IUnknown
836 {
837 HRESULT GetVersion([out] WSLCVersion* Version);
838 + HRESULT IsClientVersionSupported([in] const WSLCVersion* ClientVersion, [out] BOOL* IsSupported);
839
840 // Session management.
841 HRESULT CreateSession([in, unique] const WSLCSessionSettings* Settings, WSLCSessionFlags Flags, [out] IWSLCSession** Session);
@@ -855,3 +856,4 @@ cpp_quote("#define WSLC_E_SESSION_RESERVED MAKE_HRESULT(SEVERITY_ERROR, FACILITY
856 cpp_quote("#define WSLC_E_INVALID_SESSION_NAME MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 8) /* 0x80040608 */")
857 cpp_quote("#define WSLC_E_NETWORK_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 9) /* 0x80040609 */")
858 cpp_quote("#define WSLC_E_WU_SEARCH_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 10) /* 0x8004060A */")
859 +cpp_quote("#define WSLC_E_SDK_UPDATE_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 11) /* 0x8004060B */")
test/windows/InstallerTests.cpp
+37 -1
@@ -18,6 +18,7 @@ Abstract:
18 #include "Common.h"
19 #include "registry.hpp"
20 #include "PluginTests.h"
21 +#include "wslcsdk.h"
22
23 using namespace wsl::windows::common::registry;
24
@@ -420,6 +421,41 @@ class InstallerTests
421 CallWslUpdateViaMsi();
422 }
423
424 + WSLC_TEST_METHOD(WslcSdkVersionDetection)
425 + {
426 + auto restore = wil::scope_exit([this]() { InstallMsi(); });
427 +
428 + UninstallMsi();
429 +
430 + // Validate that the SDK detects that the WSL package is not installed.
431 + WslcComponentFlags flags{};
432 + VERIFY_SUCCEEDED(WslcGetMissingComponents(&flags));
433 + VERIFY_ARE_EQUAL(flags, WSLC_COMPONENT_FLAG_WSL_PACKAGE);
434 +
435 + // Validate that the SDK detects that the installed version of WSL is too old.
436 + InstallGitHubRelease(L"2.0.2");
437 +
438 + VERIFY_SUCCEEDED(WslcGetMissingComponents(&flags));
439 + VERIFY_ARE_EQUAL(flags, WSLC_COMPONENT_FLAG_WSL_PACKAGE);
440 +
441 + restore.reset();
442 +
443 + // Validate that the SDK supports the current package.
444 + VERIFY_SUCCEEDED(WslcGetMissingComponents(&flags));
445 + VERIFY_ARE_EQUAL(flags, 0);
446 +
447 + // TODO: Add test coverage for a more recent version of the package that doesn't support the SDK, if ever needed.
448 + // In the meantime, the below block can be commented to manual test this scenario with a manual code change.
449 + /*VERIFY_SUCCEEDED(WslcGetMissingComponents(&flags));
450 + VERIFY_ARE_EQUAL(flags, WSLC_COMPONENT_FLAG_SDK_NEEDS_UPDATE);
451 +
452 + WslcSessionSettings sessionSettings{};
453 + VERIFY_SUCCEEDED(WslcInitSessionSettings(L"should-fail", L"C:\\", &sessionSettings));
454 +
455 + WslcSession session{};
456 + VERIFY_ARE_EQUAL(WslcCreateSession(&sessionSettings, &session, nullptr), WSLC_E_SDK_UPDATE_NEEDED);*/
457 + }
458 +
459 TEST_METHOD(MsrdcPluginKey)
460 {
461 // Remove the MSI package.
@@ -1089,4 +1125,4 @@ class InstallerTests
1125 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
1126 VerifyWslSettingsProtocolAssociationExistsWithRetry();
1127 }
1092 -};
\ No newline at end of file
1128 +};
test/windows/WSLCTests.cpp
+23
@@ -242,6 +242,29 @@ class WSLCTests
242 VERIFY_ARE_EQUAL(version.Revision, WSL_PACKAGE_VERSION_REVISION);
243 }
244
245 + WSLC_TEST_METHOD(IsClientVersionSupported)
246 + {
247 + wil::com_ptr<IWSLCSessionManager> sessionManager;
248 + VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(WSLCSessionManager), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&sessionManager)));
249 +
250 + BOOL isSupported = FALSE;
251 +
252 + // The current version should always be supported.
253 + const WSLCVersion currentVersion{WSL_PACKAGE_VERSION_MAJOR, WSL_PACKAGE_VERSION_MINOR, WSL_PACKAGE_VERSION_REVISION};
254 + VERIFY_SUCCEEDED(sessionManager->IsClientVersionSupported(&currentVersion, &isSupported));
255 + VERIFY_IS_TRUE(isSupported);
256 +
257 + // A very old version should not be supported.
258 + const WSLCVersion oldVersion{1, 0, 0};
259 + VERIFY_SUCCEEDED(sessionManager->IsClientVersionSupported(&oldVersion, &isSupported));
260 + VERIFY_IS_FALSE(isSupported);
261 +
262 + // A very high version should be supported.
263 + const WSLCVersion futureVersion{99, 0, 0};
264 + VERIFY_SUCCEEDED(sessionManager->IsClientVersionSupported(&futureVersion, &isSupported));
265 + VERIFY_IS_TRUE(isSupported);
266 + }
267 +
268 static RunningWSLCProcess::ProcessResult RunCommand(IWSLCSession* session, const std::vector<std::string>& command, int timeout = 600000)
269 {
270 WSLCProcessLauncher process(command[0], command);