Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions localization/strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,16 @@ Falling back to NAT networking.</value>
<data name="MessageUpdate" xml:space="preserve">
<value>Update</value>
</data>
<data name="MessageUpgradeWhileActiveTitle" xml:space="preserve">
<value>WSL Update</value>
<comment>{Locked="WSL"}Product names should not be translated</comment>
</data>
<data name="MessageUpgradeWhileActivePrompt" xml:space="preserve">
<value>An update for WSL is ready to install, but it is currently in use.

Select Yes to shutdown WSL and install now. Select No to install on the next reboot.</value>
Comment thread
chemwolf6922 marked this conversation as resolved.
<comment>{Locked="WSL"}Product names should not be translated</comment>
</data>
<data name="MessageViewReleaseNotes" xml:space="preserve">
<value>See Docs</value>
</data>
Expand Down
4 changes: 3 additions & 1 deletion src/windows/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ set(SOURCES
wslutil.cpp
install.cpp
WSLCUserSettings.cpp
notifications.cpp)
notifications.cpp
WslActivityMarker.cpp)

set(HEADERS
../../../generated/Localization.h
Expand Down Expand Up @@ -140,6 +141,7 @@ set(HEADERS
EnumVariantMap.h
WSLCUserSettings.h
WSLCSessionDefaults.h
WslActivityMarker.h
)

add_library(common STATIC ${SOURCES} ${HEADERS})
Expand Down
60 changes: 60 additions & 0 deletions src/windows/common/WslActivityMarker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

WslActivityMarker.cpp

Abstract:

This file contains the implementation for tracking whether WSL is in use.

--*/

#include "precomp.h"
#include "WslActivityMarker.h"

namespace {

constexpr auto c_activityObjectName = L"Global\\WslActive";

wil::srwlock g_activityLock;
_Guarded_by_(g_activityLock) size_t g_activityCount = 0;
_Guarded_by_(g_activityLock) wil::unique_handle g_activityEvent;

} // namespace

namespace wsl::windows::common {

WslActivityMarker::WslActivityMarker() noexcept
{
auto lock = g_activityLock.lock_exclusive();

g_activityCount++;

if (!g_activityEvent)
{
g_activityEvent.reset(CreateEventW(nullptr, TRUE, FALSE, c_activityObjectName));
LOG_LAST_ERROR_IF_MSG(!g_activityEvent, "Failed to create WSL activity object");
}
}

WslActivityMarker::~WslActivityMarker() noexcept
{
auto lock = g_activityLock.lock_exclusive();

g_activityCount--;
if (g_activityCount == 0)
{
g_activityEvent.reset();
}
}

bool WslActivityMarker::IsWslActive() noexcept
{
wil::unique_handle event{OpenEventW(SYNCHRONIZE, FALSE, c_activityObjectName)};
return event != nullptr;
}

} // namespace wsl::windows::common
31 changes: 31 additions & 0 deletions src/windows/common/WslActivityMarker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

WslActivityMarker.h

Abstract:

This file contains declarations for tracking whether WSL is in use.

--*/

#pragma once

namespace wsl::windows::common {

class WslActivityMarker
{
public:
WslActivityMarker() noexcept;
~WslActivityMarker() noexcept;

NON_COPYABLE(WslActivityMarker);
NON_MOVABLE(WslActivityMarker);

static bool IsWslActive() noexcept;
};

} // namespace wsl::windows::common
3 changes: 3 additions & 0 deletions src/windows/service/exe/LxssCreateProcess.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Module Name:

#include "SocketChannel.h"
#include "WslPluginApi.h"
#include "WslActivityMarker.h"

// Macro to test if Windows interop is enabled.
#define LXSS_INTEROP_FLAGS (LXSS_DISTRO_FLAGS_ENABLE_DRIVE_MOUNTING | LXSS_DISTRO_FLAGS_ENABLE_INTEROP)
Expand Down Expand Up @@ -163,4 +164,6 @@ class LxssRunningInstance

private:
int m_idleTimeout;

wsl::windows::common::WslActivityMarker m_activityMarker;
};
12 changes: 11 additions & 1 deletion src/windows/service/exe/WSLCSessionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,17 @@ void WSLCSessionManagerImpl::CreateSession(

// Track the session via its service ref, along with metadata and security info.
m_sessions.push_back(SessionEntry{
std::move(serviceRef), sessionId, creatorPid, resolvedDisplayName, std::move(tokenInfo), notifier, false, sharedToken, std::move(storedSid), std::move(sessionJob)});
std::move(serviceRef),
sessionId,
creatorPid,
resolvedDisplayName,
std::move(tokenInfo),
notifier,
false,
sharedToken,
std::move(storedSid),
std::move(sessionJob),
std::make_unique<wsl::windows::common::WslActivityMarker>()});

// For persistent sessions, also hold a strong reference to keep them alive.
const bool persistent = WI_IsFlagSet(Flags, WSLCSessionFlagsPersistent);
Expand Down
3 changes: 3 additions & 0 deletions src/windows/service/exe/WSLCSessionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Module Name:
#include "wslc.h"
#include "COMImplClass.h"
#include "wslutil.h"
#include "WslActivityMarker.h"
#include <atomic>
#include <algorithm>
#include <string>
Expand Down Expand Up @@ -70,6 +71,8 @@ struct SessionEntry
std::vector<BYTE> UserSid;

wil::unique_handle JobObject;

std::unique_ptr<wsl::windows::common::WslActivityMarker> ActivityMarker;
};

class WSLCSessionManagerImpl
Expand Down
1 change: 1 addition & 0 deletions src/windows/wslinstaller/exe/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ set_target_properties(wslinstaller PROPERTIES LINK_FLAGS "/merge:minATL=.rdata /
target_link_libraries(wslinstaller
${COMMON_LINK_LIBRARIES}
${MSI_LINK_LIBRARIES}
Wtsapi32.lib
common
legacy_stdio_definitions)

Expand Down
76 changes: 76 additions & 0 deletions src/windows/wslinstaller/exe/WslInstaller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Module Name:
#include "precomp.h"
#include "install.h"
#include "WslInstaller.h"
#include "WslActivityMarker.h"
#include <wtsapi32.h>

extern wil::unique_event g_stopEvent;

Expand Down Expand Up @@ -164,6 +166,74 @@ std::pair<bool, std::wstring> IsUpdateNeeded()
}
}

static bool DeferUpdatePromptEnabled()
try
{
const auto key = wsl::windows::common::registry::OpenLxssMachineKey(KEY_READ);

auto value = wsl::windows::common::registry::ReadDword(key.get(), L"MSI", L"EnableMsixDeferUpdatePrompt", 1);

return value == 1;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return true;
}

static bool PromptUserToUpgradeWhileActive()
try
{
// For test automation purpose
if (!DeferUpdatePromptEnabled())
{
wsl::windows::common::install::WriteInstallLog("DeferUpdatePrompt is disabled");
return false;
}

constexpr DWORD c_upgradePromptTimeoutSeconds = 60;

const DWORD sessionId = WTSGetActiveConsoleSessionId();
if (sessionId == 0xFFFFFFFF)
{
wsl::windows::common::install::WriteInstallLog("No active console session");
return false;
}
Comment thread
chemwolf6922 marked this conversation as resolved.

auto title = wsl::shared::Localization::MessageUpgradeWhileActiveTitle();
auto message = wsl::shared::Localization::MessageUpgradeWhileActivePrompt();

DWORD response = 0;
if (!WTSSendMessageW(
WTS_CURRENT_SERVER_HANDLE,
sessionId,
title.data(),
static_cast<DWORD>(title.size() * sizeof(wchar_t)),
message.data(),
static_cast<DWORD>(message.size() * sizeof(wchar_t)),
MB_YESNO | MB_ICONQUESTION | MB_TOPMOST,
c_upgradePromptTimeoutSeconds,
&response,
TRUE))
{
LOG_LAST_ERROR_MSG("WTSSendMessageW failed");
return false;
}

if (response != IDYES)
{
wsl::windows::common::install::WriteInstallLog(std::format("User declined upgrade prompt (response {})", response));
return false;
}

return true;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return false;
}

std::shared_ptr<InstallContext> LaunchInstall()
{
static wil::srwlock mutex;
Expand All @@ -177,6 +247,12 @@ std::shared_ptr<InstallContext> LaunchInstall()
return {};
}

if (wsl::windows::common::WslActivityMarker::IsWslActive() && !PromptUserToUpgradeWhileActive())
{
wsl::windows::common::install::WriteInstallLog("WSL is active; deferring MSI upgrade until WSL is idle.");
return {};
}

wsl::windows::common::install::WriteInstallLog(std::format("Starting upgrade via WslInstaller. Previous version: {}", existingVersion));

// Return an existing install if any
Expand Down
14 changes: 14 additions & 0 deletions test/windows/Common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2476,6 +2476,20 @@ std::optional<GUID> GetDistributionId(LPCWSTR Name)
return {};
}

LxssDistributionState GetDistributionState(LPCWSTR Name)
{
wsl::windows::common::SvcComm service;
for (const auto& e : service.EnumerateDistributions())
{
if (wsl::shared::string::IsEqual(e.DistroName, Name))
{
return e.State;
}
}

return LxssDistributionStateInvalid;
}

wil::unique_hkey OpenDistributionKey(LPCWSTR Name)
{
const auto id = GetDistributionId(Name);
Expand Down
1 change: 1 addition & 0 deletions test/windows/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@ void StopWslService();

std::optional<GUID> GetDistributionId(LPCWSTR Name);
wil::unique_hkey OpenDistributionKey(LPCWSTR Name);
LxssDistributionState GetDistributionState(LPCWSTR Name = LXSS_DISTRO_NAME_TEST_L);

void ValidateOutput(LPCWSTR CommandLine, const std::wstring& ExpectedOutput, const std::wstring& ExpectedWarnings = L"", int ExitCode = -1);

Expand Down
39 changes: 38 additions & 1 deletion test/windows/InstallerTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class InstallerTests

try
{
wsl::shared::retry::RetryWithTimeout<void>(pred, std::chrono::hours(3), std::chrono::minutes(2));
wsl::shared::retry::RetryWithTimeout<void>(pred, std::chrono::seconds(3), std::chrono::minutes(2));
}
catch (...)
{
Expand Down Expand Up @@ -694,6 +694,43 @@ class InstallerTests
output);
}

TEST_METHOD(MsixUpgradeDefer)
{
InstallMsi();
VERIFY_IS_TRUE(IsMsiPackageInstalled());

auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { InstallMsi(); });

RegistryKeyChange<std::wstring> changeVersion(
HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Lxss\\MSI", L"Version", L"1.0.0");

RegistryKeyChange<DWORD> disablePrompt(
HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Lxss\\MSI", L"EnableMsixDeferUpdatePrompt", 0);

const auto commandLine = LxssGenerateWslCommandLine(L"sleep infinity");
wsl::windows::common::SubProcess process(nullptr, commandLine.c_str());
auto processHandle = process.Start();

Comment thread
chemwolf6922 marked this conversation as resolved.
wsl::shared::retry::RetryWithTimeout<void>(
[]() { THROW_HR_IF(E_ABORT, GetDistributionState() != LxssDistributionStateRunning); },
std::chrono::seconds(1),
std::chrono::seconds(30));

// Cannot redeploy directly even with ForceUpdateFromAnyVersion set.
UninstallMsix();
VERIFY_IS_FALSE(IsMsixInstalled());

InstallMsix();
VERIFY_IS_TRUE(IsMsixInstalled());

WaitForInstallerServiceStop();

const auto key = wsl::windows::common::registry::OpenLxssMachineKey();
VERIFY_ARE_EQUAL(wsl::windows::common::registry::ReadString(key.get(), L"MSI", L"Version"), L"1.0.0");

VERIFY_ARE_EQUAL(WaitForSingleObject(processHandle.get(), 0), static_cast<DWORD>(WAIT_TIMEOUT));
}

TEST_METHOD(WslUpdateNoNewVersion)
{
constexpr auto endpoint = L"http://127.0.0.1:12345/";
Expand Down
Loading
Loading