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(¤tVersion, &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);