Fix race condition in RemoteHyperVSocket

This commit is contained in:
Travis Plunk (HE/HIM)
2025-09-09 17:17:29 -07:00
parent 500096bc37
commit 6a3d53da00
14 changed files with 1575 additions and 203 deletions
@@ -203,35 +203,35 @@ namespace Microsoft.PowerShell
[Flags]
internal enum ParameterBitmap : long
{
Command = 0x00000001, // -Command | -c
ConfigurationName = 0x00000002, // -ConfigurationName | -config
CustomPipeName = 0x00000004, // -CustomPipeName
EncodedCommand = 0x00000008, // -EncodedCommand | -e | -ec
EncodedArgument = 0x00000010, // -EncodedArgument
ExecutionPolicy = 0x00000020, // -ExecutionPolicy | -ex | -ep
File = 0x00000040, // -File | -f
Help = 0x00000080, // -Help, -?, /?
InputFormat = 0x00000100, // -InputFormat | -inp | -if
Interactive = 0x00000200, // -Interactive | -i
Login = 0x00000400, // -Login | -l
MTA = 0x00000800, // -MTA
NoExit = 0x00001000, // -NoExit | -noe
NoLogo = 0x00002000, // -NoLogo | -nol
NonInteractive = 0x00004000, // -NonInteractive | -noni
NoProfile = 0x00008000, // -NoProfile | -nop
OutputFormat = 0x00010000, // -OutputFormat | -o | -of
SettingsFile = 0x00020000, // -SettingsFile | -settings
SSHServerMode = 0x00040000, // -SSHServerMode | -sshs
SocketServerMode = 0x00080000, // -SocketServerMode | -sockets
ServerMode = 0x00100000, // -ServerMode | -server
NamedPipeServerMode = 0x00200000, // -NamedPipeServerMode | -namedpipes
STA = 0x00400000, // -STA
Version = 0x00800000, // -Version | -v
WindowStyle = 0x01000000, // -WindowStyle | -w
WorkingDirectory = 0x02000000, // -WorkingDirectory | -wd
ConfigurationFile = 0x04000000, // -ConfigurationFile
NoProfileLoadTime = 0x08000000, // -NoProfileLoadTime
CommandWithArgs = 0x10000000, // -CommandWithArgs | -cwa
Command = 0x0000000000000001, // -Command | -c
ConfigurationName = 0x0000000000000002, // -ConfigurationName | -config
CustomPipeName = 0x0000000000000004, // -CustomPipeName
EncodedCommand = 0x0000000000000008, // -EncodedCommand | -e | -ec
EncodedArgument = 0x0000000000000010, // -EncodedArgument
ExecutionPolicy = 0x0000000000000020, // -ExecutionPolicy | -ex | -ep
File = 0x0000000000000040, // -File | -f
Help = 0x0000000000000080, // -Help, -?, /?
InputFormat = 0x0000000000000100, // -InputFormat | -inp | -if
Interactive = 0x0000000000000200, // -Interactive | -i
Login = 0x0000000000000400, // -Login | -l
MTA = 0x0000000000000800, // -MTA
NoExit = 0x0000000000001000, // -NoExit | -noe
NoLogo = 0x0000000000002000, // -NoLogo | -nol
NonInteractive = 0x0000000000004000, // -NonInteractive | -noni
NoProfile = 0x0000000000008000, // -NoProfile | -nop
OutputFormat = 0x0000000000010000, // -OutputFormat | -o | -of
SettingsFile = 0x0000000000020000, // -SettingsFile | -settings
SSHServerMode = 0x0000000000040000, // -SSHServerMode | -sshs
SocketServerMode = 0x0000000000080000, // -SocketServerMode | -sockets
ServerMode = 0x0000000000100000, // -ServerMode | -server
NamedPipeServerMode = 0x0000000000200000, // -NamedPipeServerMode | -namedpipes
STA = 0x0000000000400000, // -STA
Version = 0x0000000000800000, // -Version | -v
WindowStyle = 0x0000000001000000, // -WindowStyle | -w
WorkingDirectory = 0x0000000002000000, // -WorkingDirectory | -wd
ConfigurationFile = 0x0000000004000000, // -ConfigurationFile
NoProfileLoadTime = 0x0000000008000000, // -NoProfileLoadTime
CommandWithArgs = 0x0000000010000000, // -CommandWithArgs | -cwa
// Enum values for specified ExecutionPolicy
EPUnrestricted = 0x0000000100000000, // ExecutionPolicy unrestricted
EPRemoteSigned = 0x0000000200000000, // ExecutionPolicy remote signed
@@ -241,6 +241,8 @@ namespace Microsoft.PowerShell
EPBypass = 0x0000002000000000, // ExecutionPolicy bypass
EPUndefined = 0x0000004000000000, // ExecutionPolicy undefined
EPIncorrect = 0x0000008000000000, // ExecutionPolicy incorrect
// V2 Socket Server Mode
V2SocketServerMode = 0x0000100000000000, // -V2SocketServerMode | -v2so
}
internal ParameterBitmap ParametersUsed = 0;
@@ -597,6 +599,33 @@ namespace Microsoft.PowerShell
return _removeWorkingDirectoryTrailingCharacter;
}
}
internal DateTimeOffset? UTCTimestamp
{
get
{
AssertArgumentsParsed();
return _utcTimestamp;
}
}
internal string? Token
{
get
{
AssertArgumentsParsed();
return _token;
}
}
internal bool V2SocketServerMode
{
get
{
AssertArgumentsParsed();
return _v2SocketServerMode;
}
}
#endif
#endregion Internal properties
@@ -916,6 +945,14 @@ namespace Microsoft.PowerShell
_showBanner = false;
ParametersUsed |= ParameterBitmap.SocketServerMode;
}
#if !UNIX
else if (MatchSwitch(switchKey, "v2socketservermode", "v2so"))
{
_v2SocketServerMode = true;
_showBanner = false;
ParametersUsed |= ParameterBitmap.V2SocketServerMode;
}
#endif
else if (MatchSwitch(switchKey, "servermode", "s"))
{
_serverMode = true;
@@ -1167,6 +1204,35 @@ namespace Microsoft.PowerShell
{
_removeWorkingDirectoryTrailingCharacter = true;
}
else if (MatchSwitch(switchKey, "token", "to") )
{
++i;
if (i >= args.Length)
{
SetCommandLineError(
string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.MissingMandatoryArgument, "-Token"));
break;
}
_token = args[i];
// Not adding anything to ParametersUsed, because it is required with V2 socket server mode
// So, we can assume it based on that bit
}
else if (MatchSwitch(switchKey, "utctimestamp", "utc") )
{
++i;
if (i >= args.Length)
{
SetCommandLineError(
string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.MissingMandatoryArgument, "-UTCTimestamp"));
break;
}
// Parse as iso8601UtcString
_utcTimestamp = DateTimeOffset.ParseExact(args[i], "yyyy-MM-dd'T'HH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
// Not adding anything to ParametersUsed, because it is required with V2 socket server mode
// So, we can assume it based on that bit
}
#endif
else
{
@@ -1521,6 +1587,9 @@ namespace Microsoft.PowerShell
}
private bool _socketServerMode;
#if !UNIX
private bool _v2SocketServerMode;
#endif
private bool _serverMode;
private bool _namedPipeServerMode;
private bool _sshServerMode;
@@ -1553,6 +1622,10 @@ namespace Microsoft.PowerShell
private string? _executionPolicy;
private string? _settingsFile;
private string? _workingDirectory;
#if !UNIX
private string? _token;
private DateTimeOffset? _utcTimestamp;
#endif
#if !UNIX
private ProcessWindowStyle? _windowStyle;
@@ -186,7 +186,26 @@ namespace Microsoft.PowerShell
}
// Servermode parameter validation check.
if ((s_cpp.ServerMode && s_cpp.NamedPipeServerMode) || (s_cpp.ServerMode && s_cpp.SocketServerMode) || (s_cpp.NamedPipeServerMode && s_cpp.SocketServerMode))
int serverModeCount = 0;
if (s_cpp.ServerMode)
{
serverModeCount++;
}
if (s_cpp.NamedPipeServerMode)
{
serverModeCount++;
}
if (s_cpp.SocketServerMode)
{
serverModeCount++;
}
#if !UNIX
if (s_cpp.V2SocketServerMode)
{
serverModeCount++;
}
#endif
if (serverModeCount > 1)
{
s_tracer.TraceError("Conflicting server mode parameters, parameters must be used exclusively.");
s_theConsoleHost?.ui.WriteErrorLine(ConsoleHostStrings.ConflictingServerModeParameters);
@@ -230,6 +249,34 @@ namespace Microsoft.PowerShell
configurationName: s_cpp.ConfigurationName);
exitCode = 0;
}
#if !UNIX
else if (s_cpp.V2SocketServerMode)
{
if (s_cpp.Token == null)
{
s_tracer.TraceError("Token is required for V2SocketServerMode.");
s_theConsoleHost?.ui.WriteErrorLine(string.Format(CultureInfo.CurrentCulture, ConsoleHostStrings.MissingMandatoryParameter, "-Token", "-V2SocketServerMode"));
return ExitCodeBadCommandLineParameter;
}
if (s_cpp.UTCTimestamp == null)
{
s_tracer.TraceError("UTCTimestamp is required for V2SocketServerMode.");
s_theConsoleHost?.ui.WriteErrorLine(string.Format(CultureInfo.CurrentCulture, ConsoleHostStrings.MissingMandatoryParameter, "-UTCTimestamp", "-v2socketservermode"));
return ExitCodeBadCommandLineParameter;
}
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("V2SocketServerMode", s_cpp.ParametersUsedAsDouble);
ProfileOptimization.StartProfile("StartupProfileData-V2SocketServerMode");
HyperVSocketMediator.Run(
initialCommand: s_cpp.InitialCommand,
configurationName: s_cpp.ConfigurationName,
token: s_cpp.Token,
tokenCreationTime: s_cpp.UTCTimestamp.Value
);
exitCode = 0;
}
#endif
else if (s_cpp.SocketServerMode)
{
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SocketServerMode", s_cpp.ParametersUsedAsDouble);
@@ -222,4 +222,10 @@ Valid formats are:
<data name="NullElementInArgs" xml:space="preserve">
<value>The specified arguments must not contain null elements.</value>
</data>
<data name="InvalidExecutionPolicyArgument" xml:space="preserve">
<value>Invalid ExecutionPolicy value '{0}'.</value>
</data>
<data name="MissingMandatoryArgument" xml:space="preserve">
<value>An argument is required to be supplied to the '{0}' parameter.</value>
</data>
</root>
@@ -182,4 +182,10 @@ The current session does not support debugging; execution will continue.
<data name="RunAsAdministrator" xml:space="preserve">
<value>Run as Administrator</value>
</data>
<data name="PushRunspaceNotRemote" xml:space="preserve">
<value>PushRunspace can only push a remote runspace.</value>
</data>
<data name="MissingMandatoryParameter" xml:space="preserve">
<value>The '{0}' parameter is mandatory and must be specified when using the '{1}' parameter.</value>
</data>
</root>
@@ -7,8 +7,10 @@ using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Buffers;
using Dbg = System.Diagnostics.Debug;
using SMA = System.Management.Automation;
namespace System.Management.Automation.Remoting
{
@@ -140,6 +142,10 @@ namespace System.Management.Automation.Remoting
private readonly object _syncObject;
private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();
// This is to prevent persistent replay attacks.
// it is not meant to ensure all replay attacks are impossible.
private const int MAX_TOKEN_LIFE_MINUTES = 10;
#endregion
#region Properties
@@ -175,66 +181,17 @@ namespace System.Management.Automation.Remoting
public RemoteSessionHyperVSocketServer(bool LoopbackMode)
{
// TODO: uncomment below code when .NET supports Hyper-V socket duplication
/*
NamedPipeClientStream clientPipeStream;
byte[] buffer = new byte[1000];
int bytesRead;
*/
_syncObject = new object();
Exception ex = null;
try
{
// TODO: uncomment below code when .NET supports Hyper-V socket duplication
/*
if (!LoopbackMode)
{
//
// Create named pipe client.
//
using (clientPipeStream = new NamedPipeClientStream(".",
"PS_VMSession",
PipeDirection.InOut,
PipeOptions.None,
TokenImpersonationLevel.None))
{
//
// Connect to named pipe server.
//
clientPipeStream.Connect(10*1000);
//
// Read LPWSAPROTOCOL_INFO.
//
bytesRead = clientPipeStream.Read(buffer, 0, 1000);
}
}
//
// Create duplicate socket.
//
byte[] protocolInfo = new byte[bytesRead];
Array.Copy(buffer, protocolInfo, bytesRead);
SocketInformation sockInfo = new SocketInformation();
sockInfo.ProtocolInformation = protocolInfo;
sockInfo.Options = SocketInformationOptions.Connected;
socket = new Socket(sockInfo);
if (socket == null)
{
Dbg.Assert(false, "Unexpected error in RemoteSessionHyperVSocketServer.");
tracer.WriteMessage("RemoteSessionHyperVSocketServer", "RemoteSessionHyperVSocketServer", Guid.Empty,
"Unexpected error in constructor: {0}", "socket duplication failure");
}
*/
// TODO: remove below 6 lines of code when .NET supports Hyper-V socket duplication
Guid serviceId = new Guid("a5201c21-2770-4c11-a68e-f182edb29220"); // HV_GUID_VM_SESSION_SERVICE_ID_2
HyperVSocketEndPoint endpoint = new HyperVSocketEndPoint(HyperVSocketEndPoint.AF_HYPERV, Guid.Empty, serviceId);
Guid loopbackId = new Guid("e0e16197-dd56-4a10-9195-5ee7a155a838"); // HV_GUID_LOOPBACK
Guid parentId = new Guid("a42e7cda-d03f-480c-9cc2-a4de20abb878"); // HV_GUID_PARENT
Guid vmId = LoopbackMode ? loopbackId : parentId;
HyperVSocketEndPoint endpoint = new HyperVSocketEndPoint(HyperVSocketEndPoint.AF_HYPERV, vmId, serviceId);
Socket listenSocket = new Socket(endpoint.AddressFamily, SocketType.Stream, (System.Net.Sockets.ProtocolType)1);
listenSocket.Bind(endpoint);
@@ -284,6 +241,98 @@ namespace System.Management.Automation.Remoting
}
}
public RemoteSessionHyperVSocketServer(bool LoopbackMode, string token, DateTimeOffset tokenCreationTime)
{
_syncObject = new object();
Exception ex = null;
try
{
Guid serviceId = new Guid("a5201c21-2770-4c11-a68e-f182edb29220"); // HV_GUID_VM_SESSION_SERVICE_ID_2
HyperVSocketEndPoint endpoint = new HyperVSocketEndPoint(HyperVSocketEndPoint.AF_HYPERV, Guid.Empty, serviceId);
Socket listenSocket = new Socket(endpoint.AddressFamily, SocketType.Stream, (System.Net.Sockets.ProtocolType)1);
listenSocket.Bind(endpoint);
listenSocket.Listen(1);
HyperVSocket = listenSocket.Accept();
TimeSpan timeout = TimeSpan.FromMinutes(MAX_TOKEN_LIFE_MINUTES);
DateTimeOffset timeoutExpiry = tokenCreationTime.Add(timeout);
DateTimeOffset now = DateTimeOffset.UtcNow;
// Calculate remaining time and create cancellation token
TimeSpan remainingTime = timeoutExpiry - now;
// Check if the token has already expired
if (remainingTime <= TimeSpan.Zero)
{
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential, "Token has expired"));
}
// Set socket timeout for receive operations to prevent indefinite blocking
int timeoutMs = (int)remainingTime.TotalMilliseconds;
HyperVSocket.ReceiveTimeout = timeoutMs;
HyperVSocket.SendTimeout = timeoutMs;
// Create a cancellation token that will be cancelled when the timeout expires
using var cancellationTokenSource = new CancellationTokenSource(remainingTime);
CancellationToken cancellationToken = cancellationTokenSource.Token;
ValidateToken(HyperVSocket, token, cancellationToken);
Stream = new NetworkStream(HyperVSocket, true);
// Create reader/writer streams.
TextReader = new StreamReader(Stream);
TextWriter = new StreamWriter(Stream);
TextWriter.AutoFlush = true;
//
// listenSocket is not closed when it goes out of scope here. Sometimes it is
// closed later in this thread, while other times it is not closed at all. This will
// cause problem when we set up a second PowerShell Direct session. Let's
// explicitly close listenSocket here for safe.
//
if (listenSocket != null)
{
try
{
listenSocket.Dispose();
}
catch (ObjectDisposedException)
{
}
}
}
catch (Exception e)
{
ex = e;
}
if (ex != null)
{
Dbg.Fail("Unexpected error in RemoteSessionHyperVSocketServer.");
// Unexpected error.
string errorMessage = !string.IsNullOrEmpty(ex.Message) ? ex.Message : string.Empty;
_tracer.WriteMessage(
"RemoteSessionHyperVSocketServer",
"RemoteSessionHyperVSocketServer",
Guid.Empty,
"Unexpected error in constructor: {0}",
errorMessage);
throw new PSInvalidOperationException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.RemoteSessionHyperVSocketServerConstructorFailure),
ex,
nameof(PSRemotingErrorId.RemoteSessionHyperVSocketServerConstructorFailure),
ErrorCategory.InvalidOperation,
null);
}
}
#endregion
#region IDisposable
@@ -333,6 +382,79 @@ namespace System.Management.Automation.Remoting
}
#endregion
/// <summary>
/// Validates the token received from the client over the HyperVSocket.
/// Throws PSDirectException if the token is invalid or not received in time.
/// </summary>
/// <param name="socket">The connected HyperVSocket.</param>
/// <param name="token">The expected token string.</param>
/// <param name="cancellationToken">Cancellation token for timeout handling.</param>
internal static void ValidateToken(Socket socket, string token, CancellationToken cancellationToken = default)
{
// Check for cancellation before starting validation
cancellationToken.ThrowIfCancellationRequested();
// We should move to this pattern and
// in the tests I found I needed to get a bigger buffer than the token length
// and test length of the received data similar to this pattern.
string responseString = RemoteSessionHyperVSocketClient.ReceiveResponse(socket, RemoteSessionHyperVSocketClient.VERSION_REQUEST.Length + 4);
if (string.IsNullOrEmpty(responseString) || responseString.Length != RemoteSessionHyperVSocketClient.VERSION_REQUEST.Length)
{
socket.Send("FAIL"u8);
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Client", "Version Request: " + responseString));
}
cancellationToken.ThrowIfCancellationRequested();
socket.Send(Encoding.UTF8.GetBytes(RemoteSessionHyperVSocketClient.CLIENT_VERSION));
responseString = RemoteSessionHyperVSocketClient.ReceiveResponse(socket, RemoteSessionHyperVSocketClient.CLIENT_VERSION.Length + 4);
// In the future we may need to handle different versions, differently.
// For now, we are just checking that we exchanged versions correctly.
if (string.IsNullOrEmpty(responseString) || !responseString.StartsWith(RemoteSessionHyperVSocketClient.VERSION_PREFIX, StringComparison.Ordinal))
{
socket.Send("FAIL"u8);
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Client", "Version Response: " + responseString));
}
cancellationToken.ThrowIfCancellationRequested();
socket.Send("PASS"u8);
// The client should send the token in the format TOKEN <token>
// the token should be up to 256 bits, which is less than 50 characters.
// I'll double that to 100 characters to be safe, plus the "TOKEN " prefix.
// So we expect a response of length 6 + 100 = 106 characters.
responseString = RemoteSessionHyperVSocketClient.ReceiveResponse(socket, 110);
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrEmpty(responseString) || !responseString.StartsWith("TOKEN ", StringComparison.Ordinal))
{
socket.Send("FAIL"u8);
// If the response is not in the expected format, we throw an exception.
// This is a failure to authenticate the client.
// don't send this response for risk of information disclosure.
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Client", "Token Response"));
}
// Extract the token from the response.
string responseToken = responseString.Substring(6).Trim();
if (!string.Equals(responseToken, token, StringComparison.Ordinal))
{
socket.Send("FAIL"u8);
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential));
}
// Acknowledge the token is valid with "PASS".
socket.Send("PASS"u8);
}
}
internal sealed class RemoteSessionHyperVSocketClient : IDisposable
@@ -340,7 +462,15 @@ namespace System.Management.Automation.Remoting
#region Members
private readonly object _syncObject;
private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource();
#region tracer
/// <summary>
/// An instance of the PSTraceSource class used for trace output.
/// </summary>
[SMA.TraceSource("RemoteSessionHyperVSocketClient", "Class that has PowerShell Direct Client implementation")]
private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("RemoteSessionHyperVSocketClient", "Class that has PowerShell Direct Client implementation");
#endregion tracer
private static readonly ManualResetEvent s_connectDone =
new ManualResetEvent(false);
@@ -354,6 +484,14 @@ namespace System.Management.Automation.Remoting
#endregion
#region version constants
internal const string VERSION_REQUEST = "VERSION";
internal const string CLIENT_VERSION = "VERSION_2";
internal const string VERSION_PREFIX = "VERSION_";
#endregion
#region Properties
/// <summary>
@@ -364,7 +502,7 @@ namespace System.Management.Automation.Remoting
/// <summary>
/// Returns the Hyper-V socket object.
/// </summary>
public Socket HyperVSocket { get; }
public Socket HyperVSocket { get; private set; }
/// <summary>
/// Returns the network stream object.
@@ -381,6 +519,37 @@ namespace System.Management.Automation.Remoting
/// </summary>
public StreamWriter TextWriter { get; private set; }
/// <summary>
/// True if the client is a Hyper-V container.
/// </summary>
public bool IsContainer { get; }
/// <summary>
/// True if the client is using backwards compatible mode.
/// This is used to determine if the client should use
/// the backwards compatible or not.
/// In modern mode, the vmicvmsession service will
/// hand off the socket to the PowerShell process
/// inside the VM automatically.
/// In backwards compatible mode, the vmicvmsession
/// service create a new socket to the PowerShell process
/// inside the VM.
/// </summary>
public bool UseBackwardsCompatibleMode { get; private set; }
/// <summary>
/// The authentication token used for the session.
/// This token is provided by the broker and provided to the server to authenticate the server session.
/// This protocol uses two connections:
/// 1. The first is to the broker or vmicvmsession service to exchange credentials and configuration.
/// The broker will respond with an authentication token. The broker also launches a PowerShell
/// server process with the authentication token.
/// 2. The second is to the server process, that was launched by the broker,
/// inside the VM, which uses the authentication token to verify that the client is the same client
/// that connected to the broker.
/// </summary>
public string AuthenticationToken { get; private set; }
/// <summary>
/// Returns true if object is currently disposed.
/// </summary>
@@ -393,7 +562,9 @@ namespace System.Management.Automation.Remoting
internal RemoteSessionHyperVSocketClient(
Guid vmId,
bool isFirstConnection,
bool isContainer = false)
bool useBackwardsCompatibleMode = false,
bool isContainer = false,
string authenticationToken = null)
{
Guid serviceId;
@@ -412,28 +583,16 @@ namespace System.Management.Automation.Remoting
EndPoint = new HyperVSocketEndPoint(HyperVSocketEndPoint.AF_HYPERV, vmId, serviceId);
HyperVSocket = new Socket(EndPoint.AddressFamily, SocketType.Stream, (System.Net.Sockets.ProtocolType)1);
IsContainer = isContainer;
//
// We need to call SetSocketOption() in order to set up Hyper-V socket connection between container host and Hyper-V container.
// Here is the scenario: the Hyper-V container is inside a utility vm, which is inside the container host
//
if (isContainer)
UseBackwardsCompatibleMode = useBackwardsCompatibleMode;
if (!isFirstConnection && !useBackwardsCompatibleMode && !string.IsNullOrEmpty(authenticationToken))
{
var value = new byte[sizeof(uint)];
value[0] = 1;
try
{
HyperVSocket.SetSocketOption((System.Net.Sockets.SocketOptionLevel)HV_PROTOCOL_RAW,
(System.Net.Sockets.SocketOptionName)HVSOCKET_CONTAINER_PASSTHRU,
(byte[])value);
}
catch
{
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.RemoteSessionHyperVSocketClientConstructorSetSocketOptionFailure));
}
// If this is not the first connection and we are using backwards compatible mode,
// we should not set the authentication token here.
// The authentication token will be set during the Connect method.
AuthenticationToken = authenticationToken;
}
}
@@ -489,6 +648,81 @@ namespace System.Management.Automation.Remoting
#region Public Methods
private void ShutdownSocket()
{
if (HyperVSocket != null)
{
// Ensure the socket is disposed properly.
try
{
s_tracer.WriteLine("ShutdownSocket: Disposing of the HyperVSocket.");
HyperVSocket.Dispose();
}
catch (Exception ex)
{
s_tracer.WriteLine("ShutdownSocket: Exception while disposing the socket: {0}", ex.Message);
}
}
// Dispose of the existing stream if it exists.
if (Stream != null)
{
try
{
Stream.Dispose();
}
catch (Exception ex)
{
s_tracer.WriteLine("ShutdownSocket: Exception while disposing the stream: {0}", ex.Message);
}
}
}
/// <summary>
/// Recreates the HyperVSocket and connects it to the endpoint, updating the Stream if successful.
/// </summary>
private bool ConnectSocket()
{
HyperVSocket = new Socket(EndPoint.AddressFamily, SocketType.Stream, (System.Net.Sockets.ProtocolType)1);
//
// We need to call SetSocketOption() in order to set up Hyper-V socket connection between container host and Hyper-V container.
// Here is the scenario: the Hyper-V container is inside a utility vm, which is inside the container host
//
if (IsContainer)
{
var value = new byte[sizeof(uint)];
value[0] = 1;
try
{
HyperVSocket.SetSocketOption(
(System.Net.Sockets.SocketOptionLevel)HV_PROTOCOL_RAW,
(System.Net.Sockets.SocketOptionName)HVSOCKET_CONTAINER_PASSTHRU,
value);
}
catch
{
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.RemoteSessionHyperVSocketClientConstructorSetSocketOptionFailure));
}
}
s_tracer.WriteLine("Connect: Client connecting, to {0}; isContainer: {1}.", EndPoint.ServiceId.ToString(), IsContainer);
HyperVSocket.Connect(EndPoint);
// Check if the socket is connected.
// If it is connected, create a NetworkStream.
if (HyperVSocket.Connected)
{
s_tracer.WriteLine("Connect: Client connected, to {0}; isContainer: {1}.", EndPoint.ServiceId.ToString(), IsContainer);
Stream = new NetworkStream(HyperVSocket, true);
return true;
}
return false;
}
/// <summary>
/// Connect to Hyper-V socket server. This is a blocking call until a
/// connection occurs or the timeout time has elapsed.
@@ -516,100 +750,51 @@ namespace System.Management.Automation.Remoting
}
}
HyperVSocket.Connect(EndPoint);
if (HyperVSocket.Connected)
if (ConnectSocket())
{
_tracer.WriteMessage("RemoteSessionHyperVSocketClient", "Connect", Guid.Empty,
"Client connected.");
Stream = new NetworkStream(HyperVSocket, true);
if (isFirstConnection)
{
if (string.IsNullOrEmpty(networkCredential.Domain))
var exchangeResult = ExchangeCredentialsAndConfiguration(networkCredential, configurationName, HyperVSocket, this.UseBackwardsCompatibleMode);
if (!exchangeResult.success)
{
networkCredential.Domain = "localhost";
}
bool emptyPassword = string.IsNullOrEmpty(networkCredential.Password);
bool emptyConfiguration = string.IsNullOrEmpty(configurationName);
byte[] domain = Encoding.Unicode.GetBytes(networkCredential.Domain);
byte[] userName = Encoding.Unicode.GetBytes(networkCredential.UserName);
byte[] password = Encoding.Unicode.GetBytes(networkCredential.Password);
byte[] response = new byte[4]; // either "PASS" or "FAIL"
string responseString;
//
// Send credential to VM so that PowerShell process inside VM can be
// created under the correct security context.
//
HyperVSocket.Send(domain);
HyperVSocket.Receive(response);
HyperVSocket.Send(userName);
HyperVSocket.Receive(response);
//
// We cannot simply send password because if it is empty,
// the vmicvmsession service in VM will block in recv method.
//
if (emptyPassword)
{
HyperVSocket.Send("EMPTYPW"u8);
HyperVSocket.Receive(response);
responseString = Encoding.ASCII.GetString(response);
}
else
{
HyperVSocket.Send("NONEMPTYPW"u8);
HyperVSocket.Receive(response);
HyperVSocket.Send(password);
HyperVSocket.Receive(response);
responseString = Encoding.ASCII.GetString(response);
}
//
// There are 3 cases for the responseString received above.
// - "FAIL": credential is invalid
// - "PASS": credential is valid, but PowerShell Direct in VM does not support configuration (Server 2016 TP4 and before)
// - "CONF": credential is valid, and PowerShell Direct in VM supports configuration (Server 2016 TP5 and later)
//
//
// Credential is invalid.
//
if (string.Equals(responseString, "FAIL", StringComparison.Ordinal))
{
HyperVSocket.Send(response);
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential));
}
//
// If PowerShell Direct in VM supports configuration, send configuration name.
//
if (string.Equals(responseString, "CONF", StringComparison.Ordinal))
{
if (emptyConfiguration)
// We will not block here for a container because a container does not have a broker.
if (IsRequirePsDirectAuthenticationEnabled(@"SOFTWARE\\Microsoft\\PowerShell", Microsoft.Win32.RegistryHive.LocalMachine))
{
HyperVSocket.Send("EMPTYCF"u8);
s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: RequirePsDirectAuthentication is enabled, requiring latest transport version.");
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVNegotiationFailed));
}
else
{
HyperVSocket.Send("NONEMPTYCF"u8);
HyperVSocket.Receive(response);
byte[] configName = Encoding.Unicode.GetBytes(configurationName);
HyperVSocket.Send(configName);
this.UseBackwardsCompatibleMode = true;
s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Using backwards compatible mode.");
// If the first connection fails in modern mode, fall back to backwards compatible mode.
ShutdownSocket(); // will terminate the broker
ConnectSocket(); // restart the broker
exchangeResult = ExchangeCredentialsAndConfiguration(networkCredential, configurationName, HyperVSocket, this.UseBackwardsCompatibleMode);
if (!exchangeResult.success)
{
s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Failed to exchange credentials and configuration in backwards compatible mode.");
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Broker", "Credential"));
}
}
else
{
HyperVSocket.Send(response);
this.AuthenticationToken = exchangeResult.authenticationToken;
}
}
if (!isFirstConnection)
{
if (!this.UseBackwardsCompatibleMode)
{
s_tracer.WriteLine("Connect-Server: Performing transport version and token exchange for Hyper-V socket. isFirstConnection: {0}, UseBackwardsCompatibleMode: {1}", isFirstConnection, this.UseBackwardsCompatibleMode);
RemoteSessionHyperVSocketClient.PerformTransportVersionAndTokenExchange(HyperVSocket, this.AuthenticationToken);
}
else
{
s_tracer.WriteLine("Connect-Server: Skipping transport version and token exchange for backwards compatible mode.");
}
}
@@ -621,8 +806,7 @@ namespace System.Management.Automation.Remoting
}
else
{
_tracer.WriteMessage("RemoteSessionHyperVSocketClient", "Connect", Guid.Empty,
"Client unable to connect.");
s_tracer.WriteLine("Connect: Client unable to connect.");
result = false;
}
@@ -630,12 +814,318 @@ namespace System.Management.Automation.Remoting
return result;
}
/// <summary>
/// Performs the transport version and token exchange sequence for the Hyper-V socket connection.
/// Throws PSDirectException on failure.
/// </summary>
/// <param name="socket">The socket to use for communication.</param>
/// <param name="authenticationToken">The authentication token to send.</param>
public static void PerformTransportVersionAndTokenExchange(Socket socket, string authenticationToken)
{
if (string.IsNullOrEmpty(authenticationToken))
{
s_tracer.WriteLine("PerformTransportVersionAndTokenExchange: Authentication token is null or empty. Aborting transport version and token exchange.");
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential));
}
socket.Send(Encoding.UTF8.GetBytes(VERSION_REQUEST));
string responseStr = ReceiveResponse(socket, 16);
// Check if the response starts with the expected version prefix.
// We will rely on the broker to determine if the two can communicate.
// At least, for now.
if (!responseStr.StartsWith(VERSION_PREFIX, StringComparison.Ordinal))
{
s_tracer.WriteLine("PerformTransportVersionAndTokenExchange: Server responded with an invalid response of {0}. Notifying the transport manager to downgrade if allowed.", responseStr);
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Server", "TransportVersion"));
}
socket.Send(Encoding.UTF8.GetBytes(CLIENT_VERSION));
string response = ReceiveResponse(socket, 4); // either "PASS" or "FAIL"
if (!string.Equals(response, "PASS", StringComparison.Ordinal))
{
s_tracer.WriteLine(
"PerformTransportVersionAndTokenExchange: Transport version negotiation with server failed. Response: {0}", response);
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Server", "TransportVersion"));
}
byte[] tokenBytes = Encoding.UTF8.GetBytes("TOKEN " + authenticationToken);
socket.Send(tokenBytes);
// This is the opportunity for the server to tell the client to go away.
string tokenResponse = ReceiveResponse(socket, 256); // either "PASS" or "FAIL", but get a little more buffer to allow for better error in the future
if (!string.Equals(tokenResponse, "PASS", StringComparison.Ordinal))
{
s_tracer.WriteLine(
"PerformTransportVersionAndTokenExchange: Server Authentication Token exchange failed. Response: {0}", tokenResponse);
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential));
}
}
/// <summary>
/// Checks if the registry key RequirePsDirectAuthentication is set to 1.
/// Returns true if fallback should be aborted.
/// Uses the 64-bit registry view on 64-bit systems to ensure consistent behavior regardless of process architecture.
/// On 32-bit systems, uses the default registry view since there is no WOW64 redirection.
/// </summary>
internal static bool IsRequirePsDirectAuthenticationEnabled(string keyPath, Microsoft.Win32.RegistryHive registryHive)
{
const string regValueName = "RequirePsDirectAuthentication";
try
{
Microsoft.Win32.RegistryView registryView = Environment.Is64BitOperatingSystem
? Microsoft.Win32.RegistryView.Registry64
: Microsoft.Win32.RegistryView.Default;
using (Microsoft.Win32.RegistryKey baseKey = Microsoft.Win32.RegistryKey.OpenBaseKey(
registryHive,
registryView))
{
using (Microsoft.Win32.RegistryKey key = baseKey.OpenSubKey(keyPath))
{
if (key != null)
{
var value = key.GetValue(regValueName);
if (value is int intValue && intValue != 0)
{
return true;
}
}
return false;
}
}
}
catch (Exception regEx)
{
s_tracer.WriteLine("IsRequirePsDirectAuthenticationEnabled: Exception while checking registry key: {0}", regEx.Message);
return false; // If we cannot read the registry, assume the feature is not enabled.
}
}
/// <summary>
/// Handles credential and configuration exchange with the VM for the first connection.
/// </summary>
public static (bool success, string authenticationToken) ExchangeCredentialsAndConfiguration(NetworkCredential networkCredential, string configurationName, Socket HyperVSocket, bool useBackwardsCompatibleMode)
{
// Encoding for the Hyper-V socket communication
// To send the domain, username, password, and configuration name, use UTF-16 (Encoding.Unicode)
// All other sends use UTF-8 (Encoding.UTF8)
// Receiving uses ASCII encoding
// NOT CONFUSING AT ALL
if (!useBackwardsCompatibleMode)
{
HyperVSocket.Send(Encoding.UTF8.GetBytes(VERSION_REQUEST));
// vmicvmsession service in VM will respond with "VERSION_2" or newer
// Version 1 protocol will respond with "PASS" or "FAIL"
// Receive the response and check for VERSION_2 or newer
string responseStr = ReceiveResponse(HyperVSocket, 16);
if (!responseStr.StartsWith(VERSION_PREFIX, StringComparison.Ordinal))
{
s_tracer.WriteLine("When asking for version the server responded with an invalid response of {0}.", responseStr);
s_tracer.WriteLine("Session is invalid, continuing session with a fake user to close the session with the broker for stability.");
// If not the new protocol, finish the conversation
// Send a fake user
// Use ? <> that are illegal in user names so no one can create the user
string probeUserName = "?<PSDirectVMLegacy>"; // must be less than or equal to 20 characters for Windows Server 2016
s_tracer.WriteLine("probeUserName (static): length: {0}", probeUserName.Length);
SendUserData(probeUserName, HyperVSocket);
responseStr = ReceiveResponse(HyperVSocket, 4); // either "PASS" or "FAIL"
s_tracer.WriteLine("When sending user {0}.", responseStr);
// Send that the password is empty
HyperVSocket.Send("EMPTYPW"u8);
responseStr = ReceiveResponse(HyperVSocket, 4); // either "CONF", "PASS" or "FAIL"
s_tracer.WriteLine("When sending EMPTYPW: {0}.", responseStr); // server responds with FAIL so we respond with FAIL and the conversation is done
HyperVSocket.Send("FAIL"u8);
s_tracer.WriteLine("Notifying the transport manager to downgrade if allowed.");
// end new code
return (false, null);
}
HyperVSocket.Send(Encoding.UTF8.GetBytes(CLIENT_VERSION));
ReceiveResponse(HyperVSocket, 4); // either "PASS" or "FAIL"
}
if (string.IsNullOrEmpty(networkCredential.Domain))
{
networkCredential.Domain = "localhost";
}
System.Security.SecureString securePassword = networkCredential.SecurePassword;
int passwordLength = securePassword.Length;
bool emptyPassword = (passwordLength <= 0);
bool emptyConfiguration = string.IsNullOrEmpty(configurationName);
string responseString;
// Send credential to VM so that PowerShell process inside VM can be
// created under the correct security context.
SendUserData(networkCredential.Domain, HyperVSocket);
ReceiveResponse(HyperVSocket, 4); // only "PASS" is expected
SendUserData(networkCredential.UserName, HyperVSocket);
ReceiveResponse(HyperVSocket, 4); // only "PASS" is expected
// We cannot simply send password because if it is empty,
// the vmicvmsession service in VM will block in recv method.
if (emptyPassword)
{
HyperVSocket.Send("EMPTYPW"u8);
responseString = ReceiveResponse(HyperVSocket, 4); // either "CONF", "PASS" or "FAIL" (note, "PASS" is not used in VERSION_2 or newer mode)
}
else
{
HyperVSocket.Send("NONEMPTYPW"u8);
ReceiveResponse(HyperVSocket, 4); // only "PASS" is expected
// Get the password bytes from the SecureString, send them, and then zero out the byte array.
byte[] passwordBytes = Microsoft.PowerShell.SecureStringHelper.GetData(securePassword);
try
{
HyperVSocket.Send(passwordBytes);
}
finally
{
// Zero out the byte array for security
Array.Clear(passwordBytes);
}
responseString = ReceiveResponse(HyperVSocket, 4); // either "CONF", "PASS" or "FAIL" (note, "PASS" is not used in VERSION_2 or newer mode)
}
// Check for invalid response from server
if (!string.Equals(responseString, "FAIL", StringComparison.Ordinal) &&
!string.Equals(responseString, "PASS", StringComparison.Ordinal) &&
!string.Equals(responseString, "CONF", StringComparison.Ordinal))
{
s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Server responded with an invalid response of {0} for credentials.", responseString);
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Broker", "Credential"));
}
// Credential is invalid.
if (string.Equals(responseString, "FAIL", StringComparison.Ordinal))
{
HyperVSocket.Send("FAIL"u8);
// should we be doing this? Disabling the test for now
// HyperVSocket.Shutdown(SocketShutdown.Both);
s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Server responded with FAIL for credentials.");
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential));
}
// If PowerShell Direct in VM supports configuration, send configuration name.
if (string.Equals(responseString, "CONF", StringComparison.Ordinal))
{
if (emptyConfiguration)
{
HyperVSocket.Send("EMPTYCF"u8);
}
else
{
HyperVSocket.Send("NONEMPTYCF"u8);
ReceiveResponse(HyperVSocket, 4); // only "PASS" is expected
SendUserData(configurationName, HyperVSocket);
}
}
else
{
HyperVSocket.Send("PASS"u8);
}
if (!useBackwardsCompatibleMode)
{
// Receive the token from the server
// Getting 1024 bytes because it is well above the expected token size
// The expected size at the time of writing this would be about 50 based64 characters,
// plus the 6 characters for the "TOKEN " prefix.
// The 50 character size is designed to last 10 years of cryptographic changes.
// Since the broker completely controls the cryptographic portion here,
// allowing a significant larger size, allows the broker to make almost arbitrary changes,
// without breaking the client.
string token = ReceiveResponse(HyperVSocket, 1024); // either "PASS" or "FAIL"
if (token == null || !token.StartsWith("TOKEN ", StringComparison.Ordinal))
{
s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Server did not respond with a valid token. Response: {0}", token);
throw new PSDirectException(
PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Broker", "Token " + token));
}
token = token.Substring(6); // remove "TOKEN " prefix
HyperVSocket.Send("PASS"u8); // acknowledge the token
return (true, token);
}
return (true, null);
}
public void Close()
{
Stream.Dispose();
HyperVSocket.Dispose();
}
/// <summary>
/// Receives a response from the socket and decodes it.
/// </summary>
/// <param name="socket">The socket to receive from.</param>
/// <param name="bufferSize">The size of the buffer to use for receiving data.</param>
/// <returns>The decoded response string.</returns>
internal static string ReceiveResponse(Socket socket, int bufferSize)
{
System.Buffers.ArrayPool<byte> pool = System.Buffers.ArrayPool<byte>.Shared;
byte[] responseBuffer = pool.Rent(bufferSize);
int bytesReceived = 0;
try
{
bytesReceived = socket.Receive(responseBuffer);
if (bytesReceived == 0)
{
return null;
}
string response = Encoding.ASCII.GetString(responseBuffer, 0, bytesReceived);
// Handle null terminators and log if found
if (response.EndsWith('\0'))
{
int originalLength = response.Length;
response = response.TrimEnd('\0');
// Cannot log actual response, because we don't know if it is sensitive
s_tracer.WriteLine(
"ReceiveResponse: Removed null terminator(s). Original length: {0}, New length: {1}",
originalLength,
response.Length);
}
return response;
}
finally
{
pool.Return(responseBuffer);
}
}
/// <summary>
/// Sends user data (domain, username, etc.) over the HyperVSocket using Unicode encoding.
/// </summary>
private static void SendUserData(string data, Socket socket)
{
// this encodes the data in UTF-16 (Unicode)
byte[] buffer = Encoding.Unicode.GetBytes(data);
socket.Send(buffer);
}
#endregion
}
}
@@ -1014,7 +1014,7 @@ namespace System.Management.Automation.Remoting.Client
}
#endregion
#region Protected Methods
/// <summary>
@@ -1544,8 +1544,9 @@ namespace System.Management.Automation.Remoting.Client
/// </summary>
public override void CreateAsync()
{
_client = new RemoteSessionHyperVSocketClient(_vmGuid, true);
if (!_client.Connect(_networkCredential, _configurationName, true))
// isFirstConnection: true - specifies to use VM_SESSION_SERVICE_ID socket.
_client = new RemoteSessionHyperVSocketClient(_vmGuid, useBackwardsCompatibleMode: false, isFirstConnection: true);
if (!_client.Connect(_networkCredential, _configurationName, isFirstConnection: true))
{
_client.Dispose();
throw new PSInvalidOperationException(
@@ -1555,11 +1556,14 @@ namespace System.Management.Automation.Remoting.Client
ErrorCategory.InvalidOperation,
null);
}
bool useBackwardsCompatibleMode = _client.UseBackwardsCompatibleMode;
string token = _client.AuthenticationToken;
// TODO: remove below 3 lines when Hyper-V socket duplication is supported in .NET framework.
_client.Dispose();
_client = new RemoteSessionHyperVSocketClient(_vmGuid, false);
if (!_client.Connect(_networkCredential, _configurationName, false))
// isFirstConnection: false - specifies to use the SESSION_SERVICE_ID_2 socket.
_client = new RemoteSessionHyperVSocketClient(_vmGuid, useBackwardsCompatibleMode: useBackwardsCompatibleMode, isFirstConnection: false, authenticationToken: token);
if (!_client.Connect(_networkCredential, _configurationName, isFirstConnection: false))
{
_client.Dispose();
throw new PSInvalidOperationException(
@@ -1617,7 +1621,9 @@ namespace System.Management.Automation.Remoting.Client
/// </summary>
public override void CreateAsync()
{
_client = new RemoteSessionHyperVSocketClient(_targetGuid, false, true);
// Container scenario is not working.
// When we fix it we need to setup the token in ContainerConnectionInfo and use it here.
_client = new RemoteSessionHyperVSocketClient(_targetGuid, isFirstConnection: false, useBackwardsCompatibleMode: false, isContainer: true);
if (!_client.Connect(null, string.Empty, false))
{
_client.Dispose();
@@ -1716,7 +1722,7 @@ namespace System.Management.Automation.Remoting.Client
// Start connection timeout timer if requested.
// Timer callback occurs only once after timeout time.
_connectionTimer = new Timer(
callback: (_) =>
callback: (_) =>
{
if (_connectionEstablished)
{
@@ -2505,7 +2511,7 @@ namespace System.Management.Automation.Remoting.Server
_stdErrWriter = errWriter;
_cmdTransportManagers = new Dictionary<Guid, OutOfProcessServerTransportManager>();
this.WSManTransportErrorOccured += (object sender, TransportErrorOccuredEventArgs e) =>
this.WSManTransportErrorOccured += (object sender, TransportErrorOccuredEventArgs e) =>
{
string msg = e.Exception.TransportMessage ?? e.Exception.InnerException?.Message ?? string.Empty;
_stdErrWriter.WriteLine(StringUtil.Format(RemotingErrorIdStrings.RemoteTransportError, msg));
@@ -635,6 +635,16 @@ namespace System.Management.Automation.Remoting.Server
originalStdErr = new HyperVSocketErrorTextWriter(_hypervSocketServer.TextWriter);
}
private HyperVSocketMediator(string token,
DateTimeOffset tokenCreationTime)
: base(false)
{
_hypervSocketServer = new RemoteSessionHyperVSocketServer(false, token: token, tokenCreationTime: tokenCreationTime);
originalStdIn = _hypervSocketServer.TextReader;
originalStdOut = new OutOfProcessTextWriter(_hypervSocketServer.TextWriter);
originalStdErr = new HyperVSocketErrorTextWriter(_hypervSocketServer.TextWriter);
}
#endregion
#region Static Methods
@@ -656,6 +666,24 @@ namespace System.Management.Automation.Remoting.Server
configurationFile: null);
}
internal static void Run(
string initialCommand,
string configurationName,
string token,
DateTimeOffset tokenCreationTime)
{
lock (SyncObject)
{
s_instance = new HyperVSocketMediator(token, tokenCreationTime);
}
s_instance.Start(
initialCommand: initialCommand,
cryptoHelper: new PSRemotingCryptoHelperServer(),
workingDirectory: null,
configurationName: configurationName,
configurationFile: null);
}
#endregion
}
@@ -1720,4 +1720,13 @@ SSH client process terminated before connection could be established.</value>
<data name="WDACGetPowerShellLogMessage" xml:space="preserve">
<value>Creating a PowerShell object from a script block may require evaluating some expressions within the script block. The expression evaluation will silently fail and return 'null' in Constrained Language mode, unless the expression represents a constant value.</value>
</data>
<data name="HyperVFailedToGetStateUnknownType" xml:space="preserve">
<value>Failed to get Hyper-V VM State. The value was of the type {0} but was expected to be Microsoft.HyperV.PowerShell.VMState or System.String.</value>
</data>
<data name="HyperVInvalidResponse" xml:space="preserve">
<value>Hyper-V {0} sent an invalid {1} response during the connection negotiation.</value>
</data>
<data name="HyperVNegotiationFailed" xml:space="preserve">
<value>Negotiating a secure connection to Hyper-V failed. Make sure the Host and Guest are updated with all relevant Microsoft Updates.</value>
</data>
</root>