SDK updates (#40840)

JohnMcPMS committed Jun 22, 2026 at 13:50 UTC c8888dc66d17189ca8e66f362de2d2b93a186fc7
15 files changed +381 -43
src/windows/WslcSDK/CMakeLists.txt
+3 -2
@@ -28,5 +28,6 @@ if (WSL_INCLUDE_SDK_CSHARP)
28 endif()
29
30 add_dependencies(wslcsdk wslserviceidl wslcsdkwinrt)
31 -target_link_libraries(wslcsdk ${COMMON_LINK_LIBRARIES} legacy_stdio_definitions common wslcsdkwinrt)
32 -target_precompile_headers(wslcsdk REUSE_FROM common)
\ No newline at end of file
31 +target_link_libraries(wslcsdk ${COMMON_LINK_LIBRARIES} ${MSI_LINK_LIBRARIES} legacy_stdio_definitions common wslcsdkwinrt delayimp.lib)
32 +set_target_properties(wslcsdk PROPERTIES LINK_FLAGS "/DELAYLOAD:msi.dll /DELAYLOAD:WINTRUST.dll")
33 +target_precompile_headers(wslcsdk REUSE_FROM common)
src/windows/WslcSDK/ProgressCallback.cpp
+23 -9
@@ -25,16 +25,30 @@ WslcImageProgressStatus ConvertStatus(LPCSTR Status)
25 return _status_; \
26 }
27
28 - // TODO: Mapping engine strings to status values seems fragile.
29 - // WSLC is intentionally avoiding this kind of thing for localization of engine strings, which amounts to the same
30 - // thing. If we keep this, a test should be added to explicitly validate that each status is returned properly.
31 - WSLC_STRING_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_PULLING, "Pulling fs layer");
32 - WSLC_STRING_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_WAITING, "Waiting");
33 - WSLC_STRING_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_DOWNLOADING, "Downloading");
34 - WSLC_STRING_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_VERIFYING, "Verifying Checksum");
35 - WSLC_STRING_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_EXTRACTING, "Extracting");
36 - WSLC_STRING_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_COMPLETE, "Pull complete");
28 +#define WSLC_PREFIX_TO_STATUS_MAPPING(_status_, _prefix_) \
29 + if (std::string_view{Status}.starts_with(_prefix_##sv)) \
30 + { \
31 + return _status_; \
32 + }
33 +
34 + if (Status)
35 + {
36 + // TODO: Mapping engine strings to status values seems fragile.
37 + // WSLC is intentionally avoiding this kind of thing for localization of engine strings, which amounts to the same
38 + // thing. If we keep this, a test should be added to explicitly validate that each status is returned properly.
39 + WSLC_PREFIX_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_PULLING, "Pulling from ");
40 + WSLC_STRING_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_PULLING, "Pulling fs layer");
41 + WSLC_STRING_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_WAITING, "Waiting");
42 + WSLC_STRING_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_DOWNLOADING, "Downloading");
43 + WSLC_STRING_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_COMPLETE, "Download complete");
44 + WSLC_STRING_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_VERIFYING, "Verifying Checksum");
45 + WSLC_PREFIX_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_VERIFYING, "Digest: ");
46 + WSLC_STRING_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_EXTRACTING, "Extracting");
47 + WSLC_STRING_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_COMPLETE, "Pull complete");
48 + WSLC_PREFIX_TO_STATUS_MAPPING(WSLC_IMAGE_PROGRESS_STATUS_COMPLETE, "Status: ");
49 + }
50
51 + WSL_LOG_DEBUG("UnknownImageProgressStatus", TraceLoggingString(Status, "status"));
52 return WSLC_IMAGE_PROGRESS_STATUS_UNKNOWN;
53 }
54 } // namespace
src/windows/WslcSDK/winrt/CMakeLists.txt
+4 -2
@@ -10,7 +10,8 @@ set(SOURCES
10 ImageInfo.cpp
11 ImageProgress.cpp
12 InstallProgress.cpp
13 - Process.cpp
13 + Process.cpp
14 + ProcessCrashInformation.cpp
15 ProcessSettings.cpp
16 PullImageOptions.cpp
17 PushImageOptions.cpp
@@ -33,7 +34,8 @@ set(HEADERS
34 ImageInfo.h
35 ImageProgress.h
36 InstallProgress.h
36 - Process.h
37 + Process.h
38 + ProcessCrashInformation.h
39 ProcessSettings.h
40 PullImageOptions.h
41 PushImageOptions.h
src/windows/WslcSDK/winrt/ProcessCrashInformation.cpp new
+53
@@ -0,0 +1,53 @@
1 +/*++
2 +
3 +Copyright (c) Microsoft. All rights reserved.
4 +
5 +Module Name:
6 +
7 + ProcessCrashInformation.cpp
8 +
9 +Abstract:
10 +
11 + This file contains the implementation of the WinRT wrapper for the WSLC SDK ProcessCrashInformation class.
12 +
13 +--*/
14 +
15 +#include "precomp.h"
16 +#include "ProcessCrashInformation.h"
17 +#include "Microsoft.WSL.Containers.ProcessCrashInformation.g.cpp"
18 +
19 +namespace winrt::Microsoft::WSL::Containers::implementation {
20 +ProcessCrashInformation::ProcessCrashInformation(const WslcSessionCrashDumpInfo* info)
21 +{
22 + m_dumpPath = info->dumpPath;
23 + m_processName = winrt::to_hstring(info->processName);
24 + m_pid = info->pid;
25 + m_signal = info->signal;
26 + m_timestamp = winrt::clock::from_time_t(static_cast<time_t>(info->timestamp));
27 +}
28 +
29 +hstring ProcessCrashInformation::DumpPath() const
30 +{
31 + return m_dumpPath;
32 +}
33 +
34 +hstring ProcessCrashInformation::ProcessName() const
35 +{
36 + return m_processName;
37 +}
38 +
39 +uint32_t ProcessCrashInformation::Pid() const
40 +{
41 + return m_pid;
42 +}
43 +
44 +uint32_t ProcessCrashInformation::Signal() const
45 +{
46 + return m_signal;
47 +}
48 +
49 +winrt::Windows::Foundation::DateTime ProcessCrashInformation::Timestamp() const
50 +{
51 + return m_timestamp;
52 +}
53 +} // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/ProcessCrashInformation.h new
+39
@@ -0,0 +1,39 @@
1 +/*++
2 +
3 +Copyright (c) Microsoft. All rights reserved.
4 +
5 +Module Name:
6 +
7 + ProcessCrashInformation.h
8 +
9 +Abstract:
10 +
11 + This file contains the definition of the WinRT wrapper for the WSLC SDK ProcessCrashInformation class.
12 +
13 +--*/
14 +
15 +#pragma once
16 +#include "Microsoft.WSL.Containers.ProcessCrashInformation.g.h"
17 +#include "Helpers.h"
18 +
19 +namespace winrt::Microsoft::WSL::Containers::implementation {
20 +struct ProcessCrashInformation : ProcessCrashInformationT<ProcessCrashInformation>
21 +{
22 + ProcessCrashInformation(const WslcSessionCrashDumpInfo* info);
23 +
24 + hstring DumpPath() const;
25 + hstring ProcessName() const;
26 + uint32_t Pid() const;
27 + uint32_t Signal() const;
28 + winrt::Windows::Foundation::DateTime Timestamp() const;
29 +
30 +private:
31 + hstring m_dumpPath;
32 + hstring m_processName;
33 + uint32_t m_pid{};
34 + uint32_t m_signal{};
35 + winrt::Windows::Foundation::DateTime m_timestamp{};
36 +};
37 +} // namespace winrt::Microsoft::WSL::Containers::implementation
38 +
39 +DEFINE_TYPE_HELPERS(ProcessCrashInformation);
src/windows/WslcSDK/winrt/Session.cpp
+27
@@ -14,6 +14,7 @@ Abstract:
14
15 #include "precomp.h"
16 #include "Session.h"
17 +#include "ProcessCrashInformation.h"
18 #include "SessionSettings.h"
19 #include "Microsoft.WSL.Containers.Session.g.cpp"
20
@@ -62,6 +63,9 @@ void Session::Start()
63 m_terminationWait.reset(CreateThreadpoolWait(&Session::OnTerminated, this, nullptr));
64 THROW_LAST_ERROR_IF_NULL(m_terminationWait);
65 SetThreadpoolWait(m_terminationWait.get(), m_terminationEvent.get(), nullptr);
66 +
67 + hr = WslcRegisterSessionCrashDumpCallback(m_session.get(), &Session::OnCrashDump, this, &m_crashDumpSubscription, errorMessage.put());
68 + THROW_MSG_IF_FAILED(hr, errorMessage);
69 }
70
71 void Session::EnsureStarted() const
@@ -281,6 +285,16 @@ void Session::Terminated(winrt::event_token const& token) noexcept
285 m_terminatedEvent.remove(token);
286 }
287
288 +winrt::event_token Session::ProcessCrashed(winrt::Microsoft::WSL::Containers::ProcessCrashHandler const& handler)
289 +{
290 + return m_crashDumpEvent.add(handler);
291 +}
292 +
293 +void Session::ProcessCrashed(winrt::event_token const& token) noexcept
294 +{
295 + m_crashDumpEvent.remove(token);
296 +}
297 +
298 IVectorView<winrt::Microsoft::WSL::Containers::ImageInfo> Session::Images()
299 {
300 EnsureStarted();
@@ -318,4 +332,17 @@ void CALLBACK Session::OnTerminated(PTP_CALLBACK_INSTANCE /* instance */, PVOID
332 CATCH_LOG();
333 }
334
335 +void CALLBACK Session::OnCrashDump(const WslcSessionCrashDumpInfo* info, PVOID context) noexcept
336 +{
337 + try
338 + {
339 + auto session = static_cast<Session*>(context);
340 +
341 + auto information = winrt::make_self<implementation::ProcessCrashInformation>(info);
342 +
343 + session->m_crashDumpEvent(*information);
344 + }
345 + CATCH_LOG();
346 +}
347 +
348 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/Session.h
+6
@@ -38,6 +38,8 @@ struct Session : SessionT<Session>
38 winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::WSL::Containers::ImageInfo> Images();
39 winrt::event_token Terminated(winrt::Microsoft::WSL::Containers::SessionTerminationHandler const& handler);
40 void Terminated(winrt::event_token const& token) noexcept;
41 + winrt::event_token ProcessCrashed(winrt::Microsoft::WSL::Containers::ProcessCrashHandler const& handler);
42 + void ProcessCrashed(winrt::event_token const& token) noexcept;
43
44 WslcSession ToHandle();
45
@@ -47,13 +49,17 @@ private:
49
50 // Threadpool callback that raises the Terminated event once the session's termination handle is signaled.
51 static void CALLBACK OnTerminated(PTP_CALLBACK_INSTANCE instance, PVOID context, PTP_WAIT wait, TP_WAIT_RESULT waitResult) noexcept;
52 + static void CALLBACK OnCrashDump(const WslcSessionCrashDumpInfo* info, PVOID context) noexcept;
53
54 winrt::event<winrt::Microsoft::WSL::Containers::SessionTerminationHandler> m_terminatedEvent;
55 + winrt::event<winrt::Microsoft::WSL::Containers::ProcessCrashHandler> m_crashDumpEvent;
56 wil::unique_any<WslcSession, decltype(&WslcReleaseSession), &WslcReleaseSession> m_session{nullptr};
57
58 // Bridges the one-off termination event surfaced by the SDK to the WinRT Terminated event.
59 wil::unique_handle m_terminationEvent;
60 wil::unique_threadpool_wait m_terminationWait;
61 +
62 + wil::unique_any<WslcCrashDumpSubscription, decltype(&WslcReleaseCrashDumpSubscription), &WslcReleaseCrashDumpSubscription> m_crashDumpSubscription;
63 };
64 } // namespace winrt::Microsoft::WSL::Containers::implementation
65 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
src/windows/WslcSDK/winrt/wslcsdk.idl
+33 -1
@@ -28,7 +28,18 @@ namespace Microsoft.WSL.Containers
28 Crashed = 2,
29 };
30
31 - delegate void SessionTerminationHandler(SessionTerminationReason reason);
31 + delegate void SessionTerminationHandler(SessionTerminationReason reason);
32 +
33 + runtimeclass ProcessCrashInformation
34 + {
35 + String DumpPath { get; };
36 + String ProcessName { get; };
37 + UInt32 Pid { get; };
38 + UInt32 Signal { get; };
39 + Windows.Foundation.DateTime Timestamp { get; };
40 + };
41 +
42 + delegate void ProcessCrashHandler(ProcessCrashInformation information);
43
44 runtimeclass SessionSettings
45 {
@@ -70,6 +81,7 @@ namespace Microsoft.WSL.Containers
81 IVectorView<ImageInfo> Images { get; };
82
83 event SessionTerminationHandler Terminated;
84 + event ProcessCrashHandler ProcessCrashed;
85 };
86
87
@@ -238,6 +250,7 @@ namespace Microsoft.WSL.Containers
250 None = 0,
251 VirtualMachinePlatform = 1,
252 WslPackage = 2,
253 + SdkNeedsUpdate = 4,
254 };
255
256 runtimeclass ServiceVersion
@@ -331,5 +344,24 @@ namespace Microsoft.WSL.Containers
344 Windows.Storage.Streams.IBuffer Sha256 { get; };
345 UInt64 SizeBytes { get; };
346 Windows.Foundation.DateTime CreatedTimestamp { get; };
347 + };
348 +
349 + // Ensure wslcsdk.h and wslc.idl are also updated.
350 + enum Error
351 + {
352 + ImageNotFound = 0x80040601,
353 + ContainerPrefixAmbiguous = 0x80040602,
354 + ContainerNotFound = 0x80040603,
355 + VolumeNotFound = 0x80040604,
356 + ContainerNotRunning = 0x80040605,
357 + ContainerIsRunning = 0x80040606,
358 + SessionReserved = 0x80040607,
359 + InvalidSessionName = 0x80040608,
360 + NetworkNotFound = 0x80040609,
361 + WindowsUpdateSearchFailed = 0x8004060A,
362 + SdkUpdateNeeded = 0x8004060B,
363 + ContainerDisabled = 0x8004060C,
364 + RegistryBlockedByPolicy = 0x8004060D,
365 + VolumeNotAvailable = 0x8004060E,
366 };
367 }
src/windows/WslcSDK/wslcsdk.cpp
+19 -2
@@ -18,6 +18,7 @@ Abstract:
18 #include "Defaults.h"
19 #include "ProgressCallback.h"
20 #include "CrashDumpCallback.h"
21 +#include "install.h"
22 #include "Localization.h"
23 #include "WslInstall.h"
24 #include "wslutil.h"
@@ -1676,10 +1677,10 @@ try
1677 THROW_HR_IF(runtimeResult, runtimeResult != REGDB_E_CLASSNOTREG && runtimeResult != WSLC_E_SDK_UPDATE_NEEDED);
1678
1679 // Installing these components requires elevation.
1679 - auto token = wil::open_current_access_token();
1680 RETURN_HR_IF(
1681 HRESULT_FROM_WIN32(ERROR_ELEVATION_REQUIRED),
1682 - !wsl::windows::common::security::IsTokenElevated(token.get()) && !wsl::windows::common::security::IsTokenLocalSystem(token.get()));
1682 + !wsl::windows::common::security::IsTokenElevated(GetCurrentThreadEffectiveToken()) &&
1683 + !wsl::windows::common::security::IsTokenLocalSystem(nullptr));
1684
1685 if (needsVirtualMachine)
1686 {
@@ -1718,6 +1719,22 @@ try
1719
1720 wsl::windows::common::WindowsUpdateContext wuContext;
1721 wuContext.RunUpdateFlow(true, callback);
1722 +
1723 + // Because we do a forced install here, we expect an update.
1724 + if (wuContext.GetUpdateCount() == 0)
1725 + {
1726 + // During the preview period, the package may not be published yet, so fall back to getting it from GH.
1727 + // When moving to GA, change this to a hard error to indicate a service configuration issue.
1728 + if (callback)
1729 + {
1730 + callback(0);
1731 + }
1732 + wsl::windows::common::install::UpdatePackage(true, false, false);
1733 + if (callback)
1734 + {
1735 + callback(100);
1736 + }
1737 + }
1738 }
1739
1740 return result;
src/windows/WslcSDK/wslcsdk.h
+1
@@ -26,6 +26,7 @@ Abstract:
26 EXTERN_C_START
27
28 // WSLC specific error codes
29 +// Ensure wslc.idl and wslcsdk.idl are also updated.
30 #define WSLC_E_BASE (0x0600)
31 #define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */
32 #define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */
src/windows/common/WindowsUpdateIntegration.cpp
+30 -5
@@ -166,7 +166,12 @@ void WindowsUpdateContext::EnsureProductRegistryEntry() const
166 size_t WindowsUpdateContext::SearchForUpdates()
167 {
168 TraceLoggingWriteTagged(
169 - *m_activity, "SearchForUpdates", TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
169 + *m_activity,
170 + "SearchForUpdates",
171 + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
172 + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage),
173 + TraceLoggingWideString(m_product.c_str(), "product"));
174 +
175 THROW_IF_FAILED(m_session->CreateUpdateSearcher(&m_searcher));
176
177 std::wstring queryString = std::format(L"Product='{}'", m_product);
@@ -217,7 +222,17 @@ size_t WindowsUpdateContext::SearchForUpdates()
222 }
223
224 THROW_IF_FAILED(searchResult->get_Updates(&m_updates));
220 - return GetUpdateCount();
225 + size_t result = GetUpdateCount();
226 +
227 + TraceLoggingWriteTagged(
228 + *m_activity,
229 + "SearchForUpdatesResult",
230 + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
231 + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage),
232 + TraceLoggingInt32(resultCode, "OperationResultCode"),
233 + TraceLoggingLong(static_cast<LONG>(result), "updateCount"));
234 +
235 + return result;
236 }
237
238 size_t WindowsUpdateContext::GetUpdateCount() const
@@ -232,8 +247,6 @@ size_t WindowsUpdateContext::GetUpdateCount() const
247
248 void WindowsUpdateContext::DownloadUpdates(const std::function<void(uint32_t)>& progress) const
249 {
235 - TraceLoggingWriteTagged(
236 - *m_activity, "DownloadUpdates", TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
250 // Collect all of the updates that are not currently downloaded
251 wil::com_ptr<IUpdateCollection> toDownload = m_factory->CreateUpdateCollection();
252
@@ -252,6 +265,14 @@ void WindowsUpdateContext::DownloadUpdates(const std::function<void(uint32_t)>&
265 // All updates are already downloaded — nothing to do.
266 LONG toDownloadCount{};
267 THROW_IF_FAILED(toDownload->get_Count(&toDownloadCount));
268 +
269 + TraceLoggingWriteTagged(
270 + *m_activity,
271 + "DownloadUpdates",
272 + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
273 + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage),
274 + TraceLoggingLong(toDownloadCount, "downloadCount"));
275 +
276 if (toDownloadCount == 0)
277 {
278 if (progress)
@@ -318,7 +339,11 @@ void WindowsUpdateContext::InstallUpdates(const std::function<void(uint32_t)>& p
339 void WindowsUpdateContext::RunUpdateFlow(bool forceInstall, const std::function<void(uint32_t)>& progress)
340 {
341 TraceLoggingWriteTagged(
321 - *m_activity, "RunUpdateFlow", TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
342 + *m_activity,
343 + "RunUpdateFlow",
344 + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
345 + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage),
346 + TraceLoggingBool(forceInstall, "forceInstall"));
347
348 static_assert(
349 DownloadProgressPercent + InstallProgressPercent == 100, "Download and Install progress values must add up to 100.");
src/windows/common/install.cpp
+39 -20
@@ -77,9 +77,9 @@ bool PromptForKeyPressWithTimeout()
77 return waitResult == std::future_status::ready && future.get();
78 }
79
80 -int UpdatePackageImpl(bool preRelease, bool repair)
80 +int UpdatePackageImpl(bool preRelease, bool repair, bool callerOwnsProcess)
81 {
82 - if (!repair)
82 + if (!repair && callerOwnsProcess)
83 {
84 PrintMessage(Localization::MessageCheckingForUpdates());
85 }
@@ -88,11 +88,17 @@ int UpdatePackageImpl(bool preRelease, bool repair)
88
89 if (!repair && ParseWslPackageVersion(version) <= wsl::shared::PackageVersion)
90 {
91 - PrintMessage(Localization::MessageUpdateNotNeeded());
91 + if (callerOwnsProcess)
92 + {
93 + PrintMessage(Localization::MessageUpdateNotNeeded());
94 + }
95 return 0;
96 }
97
95 - PrintMessage(Localization::MessageUpdatingToVersion(version.c_str()));
98 + if (callerOwnsProcess)
99 + {
100 + PrintMessage(Localization::MessageUpdatingToVersion(version.c_str()));
101 + }
102
103 const bool msiInstall = wsl::shared::string::EndsWith<wchar_t>(release.name, L".msi");
104 const auto downloadPath = DownloadFile(release.url, release.name);
@@ -102,11 +108,14 @@ int UpdatePackageImpl(bool preRelease, bool repair)
108 auto clearLogs =
109 wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&logFile]() { LOG_IF_WIN32_BOOL_FALSE(DeleteFile(logFile.c_str())); });
110
105 - const auto exitCode = UpgradeViaMsi(downloadPath.c_str(), L"", logFile.c_str(), &MsiMessageCallback);
111 + const auto exitCode = UpgradeViaMsi(downloadPath.c_str(), L"", logFile.c_str(), callerOwnsProcess ? &MsiMessageCallback : nullptr);
112
113 if (exitCode == ERROR_SUCCESS_REBOOT_REQUIRED)
114 {
109 - PrintSystemError(ERROR_SUCCESS_REBOOT_REQUIRED);
115 + if (callerOwnsProcess)
116 + {
117 + PrintSystemError(ERROR_SUCCESS_REBOOT_REQUIRED);
118 + }
119 }
120 else if (exitCode != 0)
121 {
@@ -131,7 +140,7 @@ int UpdatePackageImpl(bool preRelease, bool repair)
140
141 THROW_IF_FAILED(result.get().ExtendedErrorCode());
142
134 - // Note: If the installation is successful, this process is expected to receive and Ctrl-C and exit
143 + // Note: If the installation is successful, this process is expected to receive a Ctrl-C and exit
144 }
145
146 return 0;
@@ -333,24 +342,34 @@ void wsl::windows::common::install::MsiMessageCallback(INSTALLMESSAGE type, LPCW
342 }
343 }
344
336 -int wsl::windows::common::install::UpdatePackage(bool PreRelease, bool Repair)
345 +int wsl::windows::common::install::UpdatePackage(bool PreRelease, bool Repair, bool CallerOwnsProcess)
346 {
338 - // Register a console control handler so "^C" is not printed when the app platform terminates the process.
339 - THROW_IF_WIN32_BOOL_FALSE(SetConsoleCtrlHandler(
340 - [](DWORD ctrlType) {
341 - if (ctrlType == CTRL_C_EVENT)
342 - {
343 - ExitProcess(0);
344 - }
345 - return FALSE;
346 - },
347 - TRUE));
347 + bool clearHandler = false;
348 + auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
349 + if (clearHandler)
350 + {
351 + SetConsoleCtrlHandler(nullptr, FALSE);
352 + }
353 + });
354
349 - auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [] { SetConsoleCtrlHandler(nullptr, FALSE); });
355 + if (CallerOwnsProcess)
356 + {
357 + // Register a console control handler so "^C" is not printed when the app platform terminates the process.
358 + THROW_IF_WIN32_BOOL_FALSE(SetConsoleCtrlHandler(
359 + [](DWORD ctrlType) {
360 + if (ctrlType == CTRL_C_EVENT)
361 + {
362 + ExitProcess(0);
363 + }
364 + return FALSE;
365 + },
366 + TRUE));
367 + clearHandler = true;
368 + }
369
370 try
371 {
353 - return UpdatePackageImpl(PreRelease, Repair);
372 + return UpdatePackageImpl(PreRelease, Repair, CallerOwnsProcess);
373 }
374 catch (...)
375 {
src/windows/common/install.h
+2 -1
@@ -23,7 +23,8 @@ void MsiMessageCallback(INSTALLMESSAGE type, LPCWSTR message);
23
24 wil::unique_hfile ValidateFileSignature(LPCWSTR Path);
25
26 -int UpdatePackage(bool PreRelease, bool Repair);
26 +// Setting CallerOwnsProcess to false will prevent this function from making process wide changes or printing output.
27 +int UpdatePackage(bool PreRelease, bool Repair, bool CallerOwnsProcess = true);
28
29 UINT UpgradeViaMsi(_In_ LPCWSTR PackageLocation, _In_opt_ LPCWSTR ExtraArgs, _In_opt_ LPCWSTR LogFile, _In_ const std::function<void(INSTALLMESSAGE, LPCWSTR)>& callback);
30
src/windows/service/inc/wslc.idl
+2 -1
@@ -710,7 +710,8 @@ interface IWSLCSessionManager : IUnknown
710 HRESULT OpenSession([in] ULONG Id, [out] IWSLCSession** Session);
711 HRESULT OpenSessionByName([in, unique] LPCWSTR DisplayName, [out] IWSLCSession** Session);
712 }
713 -
713 +
714 +// Ensure wslcsdk.h and wslcsdk.idl are also updated.
715 cpp_quote("#define WSLC_E_BASE (0x0600)")
716 cpp_quote("#define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */")
717 cpp_quote("#define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */")
test/windows/WslcSdkWinRTTests.cpp
+100
@@ -22,6 +22,7 @@ Abstract:
22
23 #include "winrt/Session.h"
24 #include "winrt/Helpers.h"
25 +#include "winrt/ProcessCrashInformation.h"
26
27 #include <winrt/Microsoft.WSL.Containers.h>
28 #include <winrt/Windows.Foundation.h>
@@ -307,6 +308,105 @@ class WslcSdkWinRtTests
308 VERIFY_ARE_EQUAL(future.get(), WSLCSDK::SessionTerminationReason::Shutdown);
309 }
310
311 + WSLC_TEST_METHOD(ProcessCrashedEvent)
312 + {
313 + // Start a long-running container so we can exec non-PID-1 processes into it.
314 + // The crashing process must NOT be the container's init process: Linux silently
315 + // drops kill()-sent signals with default disposition when targeting PID 1 in a
316 + // PID namespace, so no core dump would be generated if we crash the init process.
317 + auto initProcSettings = WSLCSDK::ProcessSettings();
318 + initProcSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"99"}));
319 +
320 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
321 + containerSettings.InitProcess(initProcSettings);
322 +
323 + auto container = m_defaultSession.CreateContainer(containerSettings);
324 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
325 + container.Start();
326 +
327 + // Positive: A crashing exec process must fire the ProcessCrashed event with correctly populated info.
328 + {
329 + std::promise<WSLCSDK::ProcessCrashInformation> promise;
330 +
331 + auto revoker = m_defaultSession.ProcessCrashed(winrt::auto_revoke, [&](WSLCSDK::ProcessCrashInformation info) {
332 + // Guard against multiple firings from concurrently running tests.
333 + try
334 + {
335 + promise.set_value(info);
336 + }
337 + catch (...)
338 + {
339 + }
340 + });
341 +
342 + auto execSettings = WSLCSDK::ProcessSettings();
343 + execSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"kill -SEGV $$"}));
344 +
345 + const auto beforeCrash = winrt::clock::now();
346 + StartProcessAndWaitForExit(container.CreateProcess(execSettings), 30s);
347 +
348 + auto future = promise.get_future();
349 + VERIFY_ARE_EQUAL(future.wait_for(60s), std::future_status::ready);
350 + const auto afterCrash = winrt::clock::now();
351 +
352 + auto info = future.get();
353 +
354 + VERIFY_IS_FALSE(info.DumpPath().empty());
355 + VERIFY_IS_TRUE(std::filesystem::exists(info.DumpPath().c_str()));
356 + VERIFY_IS_TRUE(std::wstring_view(info.ProcessName()).find(L"sh") != std::wstring_view::npos);
357 + VERIFY_IS_GREATER_THAN(info.Pid(), 0u);
358 + VERIFY_ARE_EQUAL(info.Signal(), 11u); // SIGSEGV = 11
359 +
360 + // Crash timestamps are second-granularity; allow some slack around the measured window.
361 + VERIFY_IS_TRUE(info.Timestamp() >= beforeCrash - 1s);
362 + VERIFY_IS_TRUE(info.Timestamp() <= afterCrash + 1s);
363 + }
364 +
365 + // Negative: After revoking the subscription token, the handler must no longer fire.
366 + {
367 + std::atomic<int> callCount{0};
368 + {
369 + auto revoker =
370 + m_defaultSession.ProcessCrashed(winrt::auto_revoke, [&](WSLCSDK::ProcessCrashInformation) { ++callCount; });
371 + // revoker goes out of scope here — handler is unsubscribed before any crash is triggered.
372 + }
373 +
374 + auto execSettings = WSLCSDK::ProcessSettings();
375 + execSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"kill -SEGV $$"}));
376 +
377 + StartProcessAndWaitForExit(container.CreateProcess(execSettings), 60s);
378 +
379 + // Allow the event system time to dispatch any pending callbacks.
380 + Sleep(1000);
381 + VERIFY_ARE_EQUAL(callCount.load(), 0);
382 + }
383 + }
384 +
385 + WSLC_TEST_METHOD(ProcessCrashInformationProperties)
386 + {
387 + // Verify that ProcessCrashInformation correctly maps all fields from WslcSessionCrashDumpInfo.
388 + constexpr PCWSTR c_dumpPath = L"C:\\test\\dump.dmp";
389 + constexpr PCSTR c_processName = "test-process";
390 + constexpr uint32_t c_pid = 42;
391 + constexpr uint32_t c_signal = 11; // SIGSEGV
392 + constexpr uint64_t c_timestamp = 1700000000; // 2023-11-14 22:13:20 UTC
393 +
394 + WslcSessionCrashDumpInfo info{};
395 + info.dumpPath = c_dumpPath;
396 + info.processName = c_processName;
397 + info.pid = c_pid;
398 + info.signal = c_signal;
399 + info.timestamp = c_timestamp;
400 +
401 + auto impl = winrt::make_self<WSLCSDK::implementation::ProcessCrashInformation>(&info);
402 +
403 + VERIFY_ARE_EQUAL(impl->DumpPath(), winrt::hstring(c_dumpPath));
404 + VERIFY_ARE_EQUAL(impl->ProcessName(), winrt::to_hstring(c_processName));
405 + VERIFY_ARE_EQUAL(impl->Pid(), c_pid);
406 + VERIFY_ARE_EQUAL(impl->Signal(), c_signal);
407 + VERIFY_ARE_EQUAL(winrt::clock::to_time_t(impl->Timestamp()), static_cast<time_t>(c_timestamp));
408 + }
409 +
410 // -----------------------------------------------------------------------
411 // Image tests
412 // -----------------------------------------------------------------------