Fix 'Debug-Runspace' for Unix platforms and properly enable Windows identity impersonation code (#8451)

This commit is contained in:
Paul Higinbotham
2018-12-19 10:14:53 -08:00
committed by Dongbo Wang
parent c857392c34
commit bc29337460
7 changed files with 229 additions and 229 deletions
@@ -1,34 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Security;
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.ComponentModel;
using System.Management.Automation.Configuration;
using System.Management.Automation.Internal;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Security;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
#if !UNIX
using System.Security.Principal;
#endif
using System.Text;
using System.Threading;
using Microsoft.PowerShell.Commands;
using Microsoft.Win32;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Text;
using Microsoft.Win32.SafeHandles;
using TypeTable = System.Management.Automation.Runspaces.TypeTable;
using System.Diagnostics;
using Microsoft.Win32.SafeHandles;
namespace System.Management.Automation
{
/// <summary>
@@ -870,6 +872,40 @@ namespace System.Management.Automation
return result;
}
#if !UNIX
private static bool TryGetWindowsCurrentIdentity(out WindowsIdentity currentIdentity)
{
try
{
currentIdentity = WindowsIdentity.GetCurrent();
}
catch (SecurityException)
{
currentIdentity = null;
}
return (currentIdentity != null);
}
/// <summary>
/// Gets the current impersonating Windows identity, if any
/// </summary>
/// <param name="impersonatedIdentity">Current impersonated Windows identity or null</param>
/// <returns>True if current identity is impersonated</returns>
internal static bool TryGetWindowsImpersonatedIdentity(out WindowsIdentity impersonatedIdentity)
{
WindowsIdentity currentIdentity;
if (TryGetWindowsCurrentIdentity(out currentIdentity) && (currentIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation))
{
impersonatedIdentity = currentIdentity;
return true;
}
impersonatedIdentity = null;
return false;
}
#endif
internal static bool IsAdministrator()
{
// Porting note: only Windows supports the SecurityPrincipal API of .NET. Due to
@@ -881,10 +917,13 @@ namespace System.Management.Automation
#if UNIX
return true;
#else
System.Security.Principal.WindowsIdentity currentIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();
System.Security.Principal.WindowsPrincipal principal = new System.Security.Principal.WindowsPrincipal(currentIdentity);
return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
WindowsIdentity currentIdentity;
if (TryGetWindowsCurrentIdentity(out currentIdentity))
{
var principal = new WindowsPrincipal(currentIdentity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
return false;
#endif
}
@@ -1171,7 +1210,7 @@ namespace System.Management.Automation
internal static readonly UTF8Encoding utf8NoBom =
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
#if !CORECLR // TODO:CORECLR - WindowsIdentity.Impersonate() is not available. Use WindowsIdentity.RunImpersonated to replace it.
#if !UNIX
/// <summary>
/// Queues a CLR worker thread with impersonation of provided Windows identity.
/// </summary>
@@ -1197,28 +1236,14 @@ namespace System.Management.Automation
WaitCallback callback = args[1] as WaitCallback;
object state = args[2];
WindowsImpersonationContext impersonationContext = null;
if ((identityToImpersonate != null) &&
(identityToImpersonate.ImpersonationLevel == TokenImpersonationLevel.Impersonation))
if (identityToImpersonate != null)
{
impersonationContext = identityToImpersonate.Impersonate();
}
try
{
callback(state);
}
finally
{
if (impersonationContext != null)
{
try
{
impersonationContext.Undo();
impersonationContext.Dispose();
}
catch (System.Security.SecurityException) { }
}
WindowsIdentity.RunImpersonated(
identityToImpersonate.AccessToken,
() => callback(state));
return;
}
callback(state);
}
#endif
@@ -1,15 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Diagnostics;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using Microsoft.Win32;
using System.Management.Automation.Internal;
using System.Management.Automation.Internal.Host;
using System.Management.Automation.Tracing;
#if !UNIX
using System.Security.Principal;
#endif
using System.Threading;
using Microsoft.PowerShell.Commands;
using Microsoft.Win32;
using Dbg = System.Management.Automation.Diagnostics;
@@ -152,13 +155,29 @@ namespace System.Management.Automation.Runspaces
PSThreadOptions memberOptions = this.IsNested ? PSThreadOptions.UseCurrentThread : this.LocalRunspace.ThreadOptions;
#if !UNIX
// Use thread proc that supports impersonation flow for new thread start.
ThreadStart invokeThreadProcDelegate = InvokeThreadProcImpersonate;
_identityToImpersonate = null;
// If impersonation identity flow is requested, then get current thread impersonation, if any.
if ((InvocationSettings != null) && InvocationSettings.FlowImpersonationPolicy)
{
Utils.TryGetWindowsImpersonatedIdentity(out _identityToImpersonate);
}
#else
// UNIX does not support thread impersonation flow.
ThreadStart invokeThreadProcDelegate = InvokeThreadProc;
#endif
switch (memberOptions)
{
case PSThreadOptions.Default:
case PSThreadOptions.UseNewThread:
{
// Start execution of pipeline in another thread
Thread invokeThread = new Thread(new ThreadStart(this.InvokeThreadProc), DefaultPipelineStackSize);
// Start execution of pipeline in another thread,
// and support impersonation flow as needed (Windows only).
Thread invokeThread = new Thread(new ThreadStart(invokeThreadProcDelegate), DefaultPipelineStackSize);
SetupInvokeThread(invokeThread, true);
#if !CORECLR
// No ApartmentState in CoreCLR
@@ -187,16 +206,18 @@ namespace System.Management.Automation.Runspaces
{
if (this.IsNested)
{
// if this a nested pipeline we are already in the appropriate thread so we just execute the pipeline here
// If this a nested pipeline we are already in the appropriate thread so we just execute the pipeline here.
// Impersonation flow (Windows only) is not needed when using existing thread.
SetupInvokeThread(Thread.CurrentThread, true);
this.InvokeThreadProc();
InvokeThreadProc();
}
else
{
// otherwise we execute the pipeline in the Runspace's thread
// Otherwise we execute the pipeline in the Runspace's thread,
// and support information flow on new thread as needed (Windows only).
PipelineThread invokeThread = this.LocalRunspace.GetPipelineThread();
SetupInvokeThread(invokeThread.Worker, true);
invokeThread.Start(this.InvokeThreadProc);
invokeThread.Start(invokeThreadProcDelegate);
}
break;
}
@@ -210,9 +231,10 @@ namespace System.Management.Automation.Runspaces
try
{
// prepare invoke thread
// Prepare invoke thread.
// Impersonation flow (Windows only) is not needed when using existing thread.
SetupInvokeThread(Thread.CurrentThread, false);
this.InvokeThreadProc();
InvokeThreadProc();
}
finally
{
@@ -508,8 +530,26 @@ namespace System.Management.Automation.Runspaces
return flowControlException;
}
// NTRAID#Windows Out Of Band Releases-915506-2005/09/09
// Removed HandleUnexpectedExceptions infrastructure
#if !UNIX
/// <summary>
/// Invokes the InvokeThreadProc() method on new thread, and flows calling thread
/// impersonation as needed.
/// </summary>
private void InvokeThreadProcImpersonate()
{
if (_identityToImpersonate != null)
{
WindowsIdentity.RunImpersonated(
_identityToImpersonate.AccessToken,
() => InvokeThreadProc());
return;
}
InvokeThreadProc();
}
#endif
/// <summary>
/// Start thread method for asynchronous pipeline execution.
/// </summary>
@@ -520,19 +560,6 @@ namespace System.Management.Automation.Runspaces
try
{
#if !CORECLR // Impersonation is not supported in CoreCLR.
// Used to store old impersonation context if we impersonate.
System.Security.Principal.WindowsImpersonationContext oldImpersonationCtxt = null;
try
{
if ((InvocationSettings != null) && (InvocationSettings.FlowImpersonationPolicy))
{
// we have a valid identity to impersonate.
System.Security.Principal.WindowsIdentity identityToImPersonate =
new System.Security.Principal.WindowsIdentity(InvocationSettings.WindowsIdentityToImpersonate.Token);
oldImpersonationCtxt = identityToImPersonate.Impersonate();
}
#endif
// Set up pipeline internal host if it is available.
if (InvocationSettings != null && InvocationSettings.Host != null)
{
@@ -575,29 +602,6 @@ namespace System.Management.Automation.Runspaces
// Invoke finished successfully. Set state to Completed.
SetPipelineState(PipelineState.Completed);
}
#if !CORECLR
}
finally
{
// Impersonation is not supported in CoreCLR.
// This finally block is needed to handle fxcop CA2124
// If sensitive operations such as impersonation occur in the try block, and an
// exception is thrown, the filter can execute before the finally block. For the
// impersonation example, this means that the filter would execute as the impersonated user.
if (oldImpersonationCtxt != null)
{
try
{
oldImpersonationCtxt.Undo();
oldImpersonationCtxt.Dispose();
oldImpersonationCtxt = null;
}
catch (System.Security.SecurityException)
{
}
}
}
#endif
}
catch (PipelineStoppedException ex)
{
@@ -1101,12 +1105,15 @@ namespace System.Management.Automation.Runspaces
}
}
// NTRAID#Windows Out Of Band Releases-925566-2005/12/09-JonN
private bool _useExternalInput;
private PipelineWriter _oldExternalErrorOutput;
private PipelineWriter _oldExternalSuccessOutput;
#if !UNIX
private WindowsIdentity _identityToImpersonate;
#endif
#endregion private_fields
#region invoke_loop_detection
@@ -1,21 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Management.Automation.Runspaces;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.Management.Automation.Tracing;
using Dbg = System.Management.Automation.Diagnostics;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Management.Automation.Remoting;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Microsoft.PowerShell.Commands;
using System.Security.Principal;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.Management.Automation.Remoting;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Runspaces.Internal;
using System.Management.Automation.Tracing;
#if !UNIX
using System.Security.Principal;
#endif
using System.Threading;
using Microsoft.PowerShell.Commands;
using Dbg = System.Management.Automation.Diagnostics;
#pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings
@@ -1806,9 +1809,11 @@ namespace System.Management.Automation
private UnhandledBreakpointProcessingMode _unhandledBreakpointMode;
private bool _detachCommand;
// Impersonation flow
#if !UNIX
// Windows impersonation flow
private WindowsIdentity _identityToPersonate;
private bool _identityPersonationChecked;
#endif
/// <summary>
/// RemoteDebuggerStopEvent
@@ -2197,11 +2202,13 @@ namespace System.Management.Automation
_runspace.RemoteDebuggerStop -= HandleForwardedDebuggerStopEvent;
_runspace.RemoteDebuggerBreakpointUpdated -= HandleForwardedDebuggerBreakpointUpdatedEvent;
#if !UNIX
if (_identityToPersonate != null)
{
_identityToPersonate.Dispose();
_identityToPersonate = null;
}
#endif
}
#endregion
@@ -2378,16 +2385,17 @@ namespace System.Management.Automation
if (!invokedOnBlockedThread)
{
#if CORECLR
Threading.ThreadPool.QueueUserWorkItem(
ProcessDebuggerStopEventProc,
args);
#else
// Otherwise run on worker thread.
#if !UNIX
Utils.QueueWorkItemWithImpersonation(
_identityToPersonate,
ProcessDebuggerStopEventProc,
args);
#else
Threading.ThreadPool.QueueUserWorkItem(
ProcessDebuggerStopEventProc,
args);
#endif
}
}
@@ -2503,19 +2511,15 @@ namespace System.Management.Automation
throw new InvalidRunspaceStateException();
}
#if !UNIX
if (!_identityPersonationChecked)
{
_identityPersonationChecked = true;
// Save identity to impersonate.
WindowsIdentity currentIdentity = null;
try
{
currentIdentity = WindowsIdentity.GetCurrent();
}
catch (System.Security.SecurityException) { }
_identityToPersonate = ((currentIdentity != null) && (currentIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation)) ? currentIdentity : null;
Utils.TryGetWindowsImpersonatedIdentity(out _identityToPersonate);
}
#endif
}
private void SetRemoteDebug(bool remoteDebug, RunspaceAvailability? availability)
@@ -2574,12 +2578,12 @@ namespace System.Management.Automation
}
}
#endregion
#endregion
}
#endregion
#endregion
#region RemoteSessionStateProxy
#region RemoteSessionStateProxy
internal class RemoteSessionStateProxy : SessionStateProxy
{
@@ -2981,5 +2985,5 @@ namespace System.Management.Automation
}
}
#endregion
#endregion
}
@@ -15,7 +15,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Management.Automation.Internal;
#if !CORECLR
#if !UNIX
using System.Security.Principal;
#endif
@@ -735,11 +735,9 @@ namespace System.Management.Automation.Remoting.Client
_isServicingCallbacks = true;
// Start a thread pool thread to process callbacks.
#if !CORECLR
// Flow thread impersonation as needed.
WindowsIdentity identityToImpersonate = WindowsIdentity.GetCurrent();
identityToImpersonate = (identityToImpersonate.ImpersonationLevel == TokenImpersonationLevel.Impersonation) ?
identityToImpersonate : null;
#if !UNIX
WindowsIdentity identityToImpersonate;
Utils.TryGetWindowsImpersonatedIdentity(out identityToImpersonate);
Utils.QueueWorkItemWithImpersonation(
identityToImpersonate,
@@ -7,24 +7,20 @@
*
*/
using System.Management.Automation.Tracing;
using System.IO;
using System.Xml;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Runtime.InteropServices;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Management.Automation.Internal;
#if !CORECLR
using System.Management.Automation.Remoting.Server;
using System.Management.Automation.Runspaces.Internal;
using System.Management.Automation.Tracing;
using System.Runtime.InteropServices;
#if !UNIX
using System.Security.Principal;
#endif
// Don't expose the System.Management.Automation namespace here. This is transport layer
// and it shouldn't know anything about the engine.
using System.Management.Automation.Remoting.Server;
// TODO: this seems ugly...Remoting datatypes should be in remoting namespace
using System.Management.Automation.Runspaces.Internal;
using System.Xml;
using System.Threading;
using PSRemotingCryptoHelper = System.Management.Automation.Internal.PSRemotingCryptoHelper;
using WSManConnectionInfo = System.Management.Automation.Runspaces.WSManConnectionInfo;
@@ -2591,7 +2587,7 @@ namespace System.Management.Automation.Remoting.Client
// Dispose
private bool _isDisposed;
private object _syncObject = new object();
#if !CORECLR
#if !UNIX
private WindowsIdentity _identityToImpersonate;
#endif
@@ -2600,11 +2596,9 @@ namespace System.Management.Automation.Remoting.Client
/// </summary>
internal WSManAPIDataCommon()
{
#if !CORECLR
#if !UNIX
// Check for thread impersonation and save identity for later de-initialization.
_identityToImpersonate = WindowsIdentity.GetCurrent();
_identityToImpersonate = (_identityToImpersonate.ImpersonationLevel == TokenImpersonationLevel.Impersonation) ?
_identityToImpersonate : null;
Utils.TryGetWindowsImpersonatedIdentity(out _identityToImpersonate);
#endif
_handle = IntPtr.Zero;
@@ -2673,42 +2667,25 @@ namespace System.Management.Automation.Remoting.Client
if (IntPtr.Zero != _handle)
{
#if !CORECLR
// If we initialized with thread impersonation make sure de-initialize is run with same.
WindowsImpersonationContext impersonationContext = null;
int result = 0;
#if !UNIX
// If we initialized with thread impersonation, make sure de-initialize is run with the same.
if (_identityToImpersonate != null)
{
try
{
_identityToImpersonate.Impersonate();
}
catch (ObjectDisposedException)
{
_handle = IntPtr.Zero;
return;
}
result = WindowsIdentity.RunImpersonated(
_identityToImpersonate.AccessToken,
() => WSManNativeApi.WSManDeinitialize(_handle, 0));
}
else
{
#endif
result = WSManNativeApi.WSManDeinitialize(_handle, 0);
#if !UNIX
}
#endif
try
{
#endif
int result = WSManNativeApi.WSManDeinitialize(_handle, 0);
Dbg.Assert(result == 0, "WSManDeinitialize returned non-zero value");
#if !CORECLR
}
finally
{
if (impersonationContext != null)
{
try
{
impersonationContext.Undo();
impersonationContext.Dispose();
}
catch (System.Security.SecurityException) { }
}
}
#endif
_handle = IntPtr.Zero;
}
}
@@ -1,12 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Management.Automation.Tracing;
using System.IO;
using System.Threading;
using System.Security.Principal;
using System.Management.Automation.Internal;
using System.Management.Automation.Tracing;
using System.Threading;
#if !UNIX
using System.Security.Principal;
#endif
using Microsoft.Win32.SafeHandles;
using Dbg = System.Management.Automation.Diagnostics;
namespace System.Management.Automation.Remoting.Server
@@ -26,8 +29,10 @@ namespace System.Management.Automation.Remoting.Server
protected string _initialCommand;
protected ManualResetEvent allcmdsClosedEvent;
#if !UNIX
// Thread impersonation.
protected WindowsIdentity _windowsIdentityToImpersonate;
#endif
/// <summary>
/// Count of commands in progress
@@ -350,13 +355,13 @@ namespace System.Management.Automation.Remoting.Server
// process data in a thread pool thread..this way Runspace, Command
// data can be processed concurrently.
#if CORECLR
ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessingThreadStart), data);
#else
#if !UNIX
Utils.QueueWorkItemWithImpersonation(
_windowsIdentityToImpersonate,
new WaitCallback(ProcessingThreadStart),
data);
_windowsIdentityToImpersonate,
new WaitCallback(ProcessingThreadStart),
data);
#else
ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessingThreadStart), data);
#endif
} while (true);
}
@@ -591,15 +596,8 @@ namespace System.Management.Automation.Remoting.Server
originalStdErr = new NamedPipeErrorTextWriter(namedPipeServer.TextWriter);
#if !UNIX
// Flow impersonation if requested.
WindowsIdentity currentIdentity = null;
try
{
currentIdentity = WindowsIdentity.GetCurrent();
}
catch (System.Security.SecurityException) { }
_windowsIdentityToImpersonate = ((currentIdentity != null) && (currentIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation)) ?
currentIdentity : null;
// Flow impersonation as needed.
Utils.TryGetWindowsImpersonatedIdentity(out _windowsIdentityToImpersonate);
#endif
}
@@ -4,18 +4,21 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.Management.Automation.Remoting;
using System.Management.Automation.Remoting.Server;
using Dbg = System.Management.Automation.Diagnostics;
using System.Threading;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Security;
using System.Diagnostics;
#if !UNIX
using System.Security.Principal;
#endif
using System.Threading;
using Dbg = System.Management.Automation.Diagnostics;
namespace System.Management.Automation
{
@@ -2037,7 +2040,9 @@ namespace System.Management.Automation
private PSDataCollection<PSObject> _output;
private DebuggerCommandResults _results;
private Exception _exception;
#if !UNIX
private WindowsIdentity _identityToImpersonate;
#endif
// Constructors
private ThreadCommandProcessing() { }
@@ -2057,14 +2062,10 @@ namespace System.Management.Automation
// Methods
public DebuggerCommandResults Invoke(ManualResetEventSlim startInvokeEvent)
{
#if !UNIX
// Get impersonation information to flow if any.
WindowsIdentity currentIdentity = null;
try
{
currentIdentity = WindowsIdentity.GetCurrent();
}
catch (System.Security.SecurityException) { }
_identityToImpersonate = ((currentIdentity != null) && (currentIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation)) ? currentIdentity : null;
Utils.TryGetWindowsImpersonatedIdentity(out _identityToImpersonate);
#endif
// Signal thread to process command.
Dbg.Assert(!_commandCompleteEvent.IsSet, "Command complete event shoulds always be non-signaled here.");
@@ -2074,8 +2075,13 @@ namespace System.Management.Automation
// Wait for completion.
_commandCompleteEvent.Wait();
_commandCompleteEvent.Reset();
_identityToImpersonate = null;
#if !UNIX
if (_identityToImpersonate != null)
{
_identityToImpersonate.Dispose();
_identityToImpersonate = null;
}
#endif
// Propagate exception.
if (_exception != null)
@@ -2098,18 +2104,17 @@ namespace System.Management.Automation
internal void DoInvoke()
{
#if !CORECLR // TODO:CORECLR - WindowsIdentity.Impersonate() is not available. Use WindowsIdentity.RunImplemented to replace it.
// Flow impersonation onto thread if needed.
WindowsImpersonationContext impersonationContext = null;
if ((_identityToImpersonate != null) &&
(_identityToImpersonate.ImpersonationLevel == TokenImpersonationLevel.Impersonation))
{
impersonationContext = _identityToImpersonate.Impersonate();
}
#endif
try
{
#if !UNIX
if (_identityToImpersonate != null)
{
_results = WindowsIdentity.RunImpersonated(
_identityToImpersonate.AccessToken,
() =>_wrappedDebugger.ProcessCommand(_command, _output));
return;
}
#endif
_results = _wrappedDebugger.ProcessCommand(_command, _output);
}
catch (Exception e)
@@ -2119,19 +2124,6 @@ namespace System.Management.Automation
finally
{
_commandCompleteEvent.Set();
#if !CORECLR // TODO:CORECLR - WindowsIdentity.Impersonate() is not available. Use WindowsIdentity.RunImplemented to replace it.
// Restore previous context to thread.
if (impersonationContext != null)
{
try
{
impersonationContext.Undo();
impersonationContext.Dispose();
}
catch (System.Security.SecurityException) { }
}
#endif
}
}
}
@@ -2317,7 +2309,7 @@ namespace System.Management.Automation
if (isNestedStop)
{
// Blocking call for nested debugger execution (Workflow) stop events.
// Blocking call for nested debugger execution (Debug-Runspace) stop events.
// The root debugger never makes two EnterDebugMode calls without an ExitDebugMode.
if (_nestedDebugStopCompleteEvent == null)
{
@@ -2494,8 +2486,7 @@ namespace System.Management.Automation
bool addToHistory)
{
// For nested debugger command processing, invoke command on new local runspace since
// the root script debugger runspace is unavailable (it is running a PS script or a
// workflow function script).
// the root script debugger runspace is unavailable (it is running a PS script).
Runspace runspace = (remoteHost != null) ?
RunspaceFactory.CreateRunspace(remoteHost) : RunspaceFactory.CreateRunspace();