diff --git a/src/Microsoft.PowerShell.PSReadLine/ReadLine.cs b/src/Microsoft.PowerShell.PSReadLine/ReadLine.cs
index e7ec6fb018..f3805676ad 100644
--- a/src/Microsoft.PowerShell.PSReadLine/ReadLine.cs
+++ b/src/Microsoft.PowerShell.PSReadLine/ReadLine.cs
@@ -881,6 +881,9 @@ namespace Microsoft.PowerShell
_singleton.Render();
}
+ private const string PromptCommand = "prompt";
+ private const string DefaultPrompt = "PS>";
+
///
/// Gets the current prompt as possibly defined by the user through the
/// prompt function, and returns a default prompt if no other is
@@ -888,23 +891,45 @@ namespace Microsoft.PowerShell
///
public static string GetPrompt()
{
+ string newPrompt;
var runspaceIsRemote = _singleton._mockableMethods.RunspaceIsRemote(_singleton._runspace);
- System.Management.Automation.PowerShell ps;
- if (!runspaceIsRemote)
+
+ if ((_singleton._runspace.Debugger != null) && _singleton._runspace.Debugger.InBreakpoint)
{
- ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace);
+ // Run prompt command in debugger API to ensure it is run correctly on the runspace.
+ // This handles remote runspace debugging and nested debugger scenarios.
+ PSDataCollection results = new PSDataCollection();
+ var command = new PSCommand();
+ command.AddCommand(PromptCommand);
+ _singleton._runspace.Debugger.ProcessCommand(
+ command,
+ results);
+
+ newPrompt = (results.Count == 1) ? (results[0].BaseObject as string) : DefaultPrompt;
}
else
{
- ps = System.Management.Automation.PowerShell.Create();
- ps.Runspace = _singleton._runspace;
+ System.Management.Automation.PowerShell ps;
+ if (!runspaceIsRemote)
+ {
+ ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace);
+ }
+ else
+ {
+ ps = System.Management.Automation.PowerShell.Create();
+ ps.Runspace = _singleton._runspace;
+ }
+ using (ps)
+ {
+ ps.AddCommand(PromptCommand);
+ var result = ps.Invoke();
+ newPrompt = result.Count == 1 ? result[0] : DefaultPrompt;
+ }
}
- string newPrompt;
- using (ps)
+
+ if (string.IsNullOrEmpty(newPrompt))
{
- ps.AddCommand("prompt");
- var result = ps.Invoke();
- newPrompt = result.Count == 1 ? result[0] : "PS>";
+ newPrompt = DefaultPrompt;
}
if (runspaceIsRemote)
@@ -915,6 +940,7 @@ namespace Microsoft.PowerShell
newPrompt = string.Format(CultureInfo.InvariantCulture, "[{0}]: {1}", connectionInfo.ComputerName, newPrompt);
}
}
+
return newPrompt;
}
diff --git a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs
index 31fb225eff..35fdd420ee 100644
--- a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs
+++ b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs
@@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.PowerShell.Commands;
using System.Management.Automation.Host;
@@ -863,6 +864,65 @@ namespace System.Management.Automation
#region Public Access
+ #region Runspace Invoke
+
+ ///
+ /// Helper method to invoke a PSCommand on a given runspace. This method correctly invokes the command for
+ /// these runspace cases:
+ /// 1. Local runspace. If the local runspace is busy it will invoke as a nested command.
+ /// 2. Remote runspace.
+ /// 3. Runspace that is stopped in the debugger at a breakpoint.
+ ///
+ /// Error and information streams are ignored and only the command result output is returned.
+ ///
+ /// This method is NOT thread safe. It does not support running commands from different threads on the
+ /// provided runspace. It assumes the thread invoking this method is the same that runs all other
+ /// commands on the provided runspace.
+ ///
+ /// Runspace to invoke the command on
+ /// Command to invoke
+ /// Collection of command output result objects
+ public static Collection InvokeOnRunspace(PSCommand command, Runspace runspace)
+ {
+ if (command == null)
+ {
+ throw new PSArgumentNullException("command");
+ }
+
+ if (runspace == null)
+ {
+ throw new PSArgumentNullException("runspace");
+ }
+
+ if ((runspace.Debugger != null) && runspace.Debugger.InBreakpoint)
+ {
+ // Use the Debugger API to run the command when a runspace is stopped in the debugger.
+ PSDataCollection output = new PSDataCollection();
+ runspace.Debugger.ProcessCommand(
+ command,
+ output);
+
+ return new Collection(output);
+ }
+
+ // Otherwise run command directly in runspace.
+ PowerShell ps = PowerShell.Create();
+ ps.Runspace = runspace;
+ ps.IsRunspaceOwner = false;
+ if (runspace.ConnectionInfo == null)
+ {
+ // Local runspace. Make a nested PowerShell object as needed.
+ ps.SetIsNested(runspace.GetCurrentlyRunningPipeline() != null);
+ }
+ using (ps)
+ {
+ ps.Commands = command;
+ return ps.Invoke();
+ }
+ }
+
+ #endregion
+
#region PSEdit Support
///
@@ -878,10 +938,10 @@ namespace System.Management.Automation
dir $file -File | foreach {
$filePathName = $_.FullName
-# Get file contents
+ # Get file contents
$contentBytes = Get-Content -Path $filePathName -Raw -Encoding Byte
-# Notify client for file open.
+ # Notify client for file open.
New-Event -SourceIdentifier PSISERemoteSessionOpenFile -EventArguments @($filePathName, $contentBytes) > $null
}
}
diff --git a/test/powershell/Host/HostUtilities.Tests.ps1 b/test/powershell/Host/HostUtilities.Tests.ps1
new file mode 100644
index 0000000000..8e4dd1995c
--- /dev/null
+++ b/test/powershell/Host/HostUtilities.Tests.ps1
@@ -0,0 +1,70 @@
+Describe "InvokeOnRunspace method argument error handling" -tags "Feature" {
+
+ BeforeAll {
+ $command = [System.Management.Automation.PSCommand]::new()
+ $localRunspace = $host.Runspace
+ }
+
+ It "Null argument exception should be thrown for null PSCommand argument" {
+
+ try
+ {
+ [System.Management.Automation.HostUtilities]::InvokeOnRunspace($null, $localRunspace)
+ throw "InvokeOnRunspace method did not throw expected PSArgumentNullException exception"
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should Be "PSArgumentNullException"
+ }
+ }
+
+ It "Null argument exception should be thrown for null Runspace argument" {
+
+ try
+ {
+ [System.Management.Automation.HostUtilities]::InvokeOnRunspace($command, $null)
+ throw "InvokeOnRunspace method did not throw expected PSArgumentNullException exception"
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should Be "PSArgumentNullException"
+ }
+ }
+}
+
+Describe "InvokeOnRunspace method as nested command" -tags "Feature" {
+
+ It "Method should successfully invoke command as nested on busy runspace" {
+
+ $command = [System.Management.Automation.PSCommand]::new()
+ $command.AddScript('"Hello!"')
+ $currentRunspace = $host.Runspace
+
+ $results = [System.Management.Automation.HostUtilities]::InvokeOnRunspace($command, $currentRunspace)
+
+ $results[0] | Should Be "Hello!"
+ }
+}
+
+Describe "InvokeOnRunspace method on remote runspace" -tags "Feature" {
+
+ BeforeAll {
+ $wc = [System.Management.Automation.Runspaces.WSManConnectionInfo]::new()
+ $remoteRunspace = [runspacefactory]::CreateRunspace($host, $wc)
+ $remoteRunspace.Open()
+ }
+
+ AfterAll {
+ $remoteRunspace.Dispose();
+ }
+
+ It "Method should successfully invoke command on remote runspace" {
+
+ $command = [System.Management.Automation.PSCommand]::new()
+ $command.AddScript('"Hello!"')
+
+ $results = [System.Management.Automation.HostUtilities]::InvokeOnRunspace($command, $remoteRunspace)
+
+ $results[0] | Should Be "Hello!"
+ }
+}