Skip to content
This repository was archived by the owner on Jun 5, 2025. It is now read-only.
Merged
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
472 changes: 236 additions & 236 deletions HyperVExtension/src/DevSetupAgent/HostRegistryChannel.cs

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions HyperVExtension/src/DevSetupAgent/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ CLSCTX
WIN32_ERROR
S_OK
E_FAIL
LsaEnumerateLogonSessions
LsaGetLogonSessionData
Windows.Win32.Security.Authentication.Identity.LsaFreeReturnBuffer
SECURITY_LOGON_TYPE
STATUS_SUCCESS
56 changes: 28 additions & 28 deletions HyperVExtension/src/DevSetupAgent/Requests/ErrorRequest.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Text.Json.Nodes;

namespace HyperVExtension.DevSetupAgent;

/// <summary>
/// Class used to handle invalid requests (for example an exception while parsing request JSON).
/// It creates an error response to send back to the client.
/// </summary>
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Text.Json.Nodes;
namespace HyperVExtension.DevSetupAgent;
/// <summary>
/// Class used to handle invalid requests (for example an exception while parsing request JSON).
/// It creates an error response to send back to the client.
/// </summary>
internal class ErrorRequest : IHostRequest
{
public ErrorRequest(IRequestMessage requestMessage)
{
Timestamp = DateTime.UtcNow;
RequestId = requestMessage.RequestId!;
}

public virtual uint Version { get; set; } = 1;

public virtual bool IsStatusRequest => true;

public virtual string RequestId { get; }

public virtual string RequestType => "ErrorNoData";

public DateTime Timestamp { get; }

public virtual IHostResponse Execute(ProgressHandler progressHandler, CancellationToken stoppingToken)
{
return new ErrorResponse(RequestId);
}
}
}
public virtual uint Version { get; set; } = 1;
public virtual bool IsStatusRequest => true;
public virtual string RequestId { get; }
public virtual string RequestType => "ErrorNoData";
public DateTime Timestamp { get; }
public virtual IHostResponse Execute(ProgressHandler progressHandler, CancellationToken stoppingToken)
{
return new ErrorResponse(RequestId);
}
}
120 changes: 120 additions & 0 deletions HyperVExtension/src/DevSetupAgent/Requests/IsUserLoggedInRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Diagnostics;
using System.Security.Principal;
using System.Text.Json.Nodes;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Security.Authentication.Identity;

namespace HyperVExtension.DevSetupAgent;

/// <summary>
/// Class used to handle request for service version (RequestType = GetVersion).
/// </summary>
internal sealed class IsUserLoggedInRequest : RequestBase
{
public IsUserLoggedInRequest(IRequestContext requestContext)
: base(requestContext)
{
}

public override bool IsStatusRequest => true;

public override IHostResponse Execute(ProgressHandler progressHandler, CancellationToken stoppingToken)
{
var loggedInUsers = EnumerateLogonSessions();
return new IsUserLoggedInResponse(RequestMessage.RequestId!, loggedInUsers);
}

private static List<string> EnumerateLogonSessions()
{
// We'll take interactive sessions where explorer is running to filter out stale sessions
var interactiveSessions = new List<string>();

var explorers = Process.GetProcessesByName("explorer");
if (explorers.Length == 0)
{
return interactiveSessions;
}

unsafe
{
var sessions = new List<(uint, string)>(); // (SessionId, UserName)
LUID* luidPtr = default;
SECURITY_LOGON_SESSION_DATA* sessionData = default;
try
{
uint count;
var status = PInvoke.LsaEnumerateLogonSessions(out count, out luidPtr);
if (status != NTSTATUS.STATUS_SUCCESS)
{
throw new NtStatusException("LsaEnumerateLogonSessions failed.", status);
}

Logging.Logger()?.ReportDebug($"Number of logon sessions: {count}");
for (var i = 0; i < count; i++)
{
var luid = luidPtr[i];
status = PInvoke.LsaGetLogonSessionData(luid, out sessionData);
if (status != NTSTATUS.STATUS_SUCCESS)
{
throw new NtStatusException("LsaGetLogonSessionData failed.", status);
}

if (sessionData->Session > 0)
{
switch (sessionData->LogonType)
{
case (uint)SECURITY_LOGON_TYPE.Interactive:
case (uint)SECURITY_LOGON_TYPE.RemoteInteractive:
case (uint)SECURITY_LOGON_TYPE.CachedRemoteInteractive:
var sid = new SecurityIdentifier((IntPtr)sessionData->Sid.Value);
Logging.Logger()?.ReportDebug(
$"Logged on user: {sessionData->UserName.Buffer}, " +
$"Domain: {sessionData->LogonDomain.Buffer}, " +
$"Session: {sessionData->Session}, " +
$"Logon type: {sessionData->LogonType}, " +
$"SID: {sid}, " +
$"UserFlags: {sessionData->UserFlags}");

sessions.Add((sessionData->Session, new string(sessionData->UserName.Buffer)));
break;

default:
break;
}
}

PInvoke.LsaFreeReturnBuffer(sessionData);
sessionData = default;
}

// We'll take interactive sessions where explorer is running to filter out stale sessions
var interactiveSessionsWithExplorer = sessions.Where(s => explorers.Any(e => (uint)e.SessionId == s.Item1));
foreach (var session in interactiveSessionsWithExplorer)
{
// TODO: We get OS user names like "DWM-2" or "UMFD-2" into this list. We need to filter them out.
Logging.Logger()?.ReportDebug($"Logged on user: {session.Item2}, Session: {session.Item1}");
interactiveSessions.Add(session.Item2);
}
}
finally
{
if (sessionData != default)
{
PInvoke.LsaFreeReturnBuffer(sessionData);
}

if (luidPtr != default)
{
PInvoke.LsaFreeReturnBuffer(luidPtr);
}
}
}

return interactiveSessions;
}
}
31 changes: 31 additions & 0 deletions HyperVExtension/src/DevSetupAgent/Requests/NtStatusException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Runtime.Serialization;

namespace HyperVExtension.DevSetupAgent;

internal sealed class NtStatusException : Exception
{
public NtStatusException()
{
}

public NtStatusException(string? message)
: base(message)
{
}

public NtStatusException(string? message, Exception? innerException)
: base(message, innerException)
{
}

public NtStatusException(string? message, int ntStatus)
: base(message)
{
// NTStatus is not an HRESULT, but we will uonly use it to pass error back to the caller
// for diagnostic. Conversion to HRESULT can be done in more that one way and can be not 1 to 1 mapping anyway
HResult = ntStatus;
}
}
3 changes: 2 additions & 1 deletion HyperVExtension/src/DevSetupAgent/Requests/RequestFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public class RequestFactory : IRequestFactory
// TODO: Define request type constants in one place
{ "GetVersion", (requestContext) => new GetVersionRequest(requestContext) },
{ "Configure", (requestContext) => new ConfigureRequest(requestContext) },
{ "Ack", (requestContext) => new AckRequest(requestContext) },
{ "Ack", (requestContext) => new AckRequest(requestContext) },
{ "IsUserLoggedIn", (requestContext) => new IsUserLoggedInRequest(requestContext) },
};

public RequestFactory()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Text.Json;

namespace HyperVExtension.DevSetupAgent;

internal sealed class IsUserLoggedInResponse : ResponseBase
{
public IsUserLoggedInResponse(string requestId, List<string> loggedInUsers)
: base(requestId)
{
RequestType = "IsUserLoggedIn";

// Return empty list for now. Reserved for the future use to deal with multiple logged in users.
LoggedInUsers = new List<string>();
IsUserLoggedIn = loggedInUsers.Count > 0;
GenerateJsonData();
}

public List<string> LoggedInUsers { get; }

public bool IsUserLoggedIn { get; }

protected override void GenerateJsonData()
{
base.GenerateJsonData();

JsonData![nameof(IsUserLoggedIn)] = IsUserLoggedIn;
JsonData![nameof(LoggedInUsers)] = JsonSerializer.Serialize(LoggedInUsers);
}
}
4 changes: 2 additions & 2 deletions HyperVExtension/src/DevSetupEngine/DevSetupEngine.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)ToolingVersions.props" />
<!-- Debug builds produce a console app; otherwise a Windows app -->
<Import Project="$(SolutionDir)ToolingVersions.props" />
<!-- Debug builds produce a console app; otherwise a Windows app -->
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<OutputType>Exe</OutputType>
</PropertyGroup>
Expand Down
Loading