diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs
index 9282a39df02..6ad1f929c31 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs
@@ -100,6 +100,21 @@ public Guid InstanceId
set;
}
+ ///
+ /// The optional breakpoint objects to use for debugging.
+ ///
+ [Experimental("Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints", ExperimentAction.Show)]
+ [Parameter(Position = 1,
+ ParameterSetName = DebugRunspaceCommand.InstanceIdParameterSet)]
+ [Parameter(ParameterSetName = DebugRunspaceCommand.RunspaceParameterSet)]
+ [Parameter(ParameterSetName = DebugRunspaceCommand.IdParameterSet)]
+ [Parameter(ParameterSetName = DebugRunspaceCommand.NameParameterSet)]
+ public Breakpoint[] Breakpoint
+ {
+ get;
+ set;
+ }
+
#endregion
#region Overrides
@@ -260,7 +275,7 @@ private void WaitAndReceiveRunspaceOutput()
_debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript);
// Set up host script debugger to debug the runspace.
- _debugger.DebugRunspace(_runspace);
+ _debugger.DebugRunspace(_runspace, disableBreakAll: Breakpoint?.Length > 0);
while (_debugging)
{
@@ -517,6 +532,10 @@ private void PrepareRunspace(Runspace runspace)
{
SetLocalMode(runspace.Debugger, true);
EnableHostDebugger(runspace, false);
+ if (Breakpoint?.Length > 0)
+ {
+ runspace.Debugger?.SetBreakpoints(Breakpoint);
+ }
}
private void RestoreRunspace(Runspace runspace)
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs
index 92dcae2d056..5c1b61f3601 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs
@@ -350,6 +350,22 @@ public SwitchParameter BreakAll
set;
}
+ ///
+ /// The optional breakpoint objects to use for debugging.
+ ///
+ [Experimental("Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints", ExperimentAction.Show)]
+ [Parameter(Position = 1,
+ ParameterSetName = CommonRunspaceCommandBase.RunspaceParameterSet)]
+ [Parameter(Position = 1,
+ ParameterSetName = CommonRunspaceCommandBase.RunspaceNameParameterSet)]
+ [Parameter(Position = 1,
+ ParameterSetName = CommonRunspaceCommandBase.RunspaceIdParameterSet)]
+ public Breakpoint[] Breakpoint
+ {
+ get;
+ set;
+ }
+
#endregion
#region Overrides
@@ -362,58 +378,61 @@ protected override void ProcessRecord()
if (this.ParameterSetName.Equals(CommonRunspaceCommandBase.ProcessNameParameterSet))
{
SetDebugPreferenceHelper(ProcessName, AppDomainName, true, "EnableRunspaceDebugCommandPersistDebugPreferenceFailure");
+ return;
}
- else
- {
- IReadOnlyList results = GetRunspaces();
- foreach (var runspace in results)
+ IReadOnlyList results = GetRunspaces();
+
+ foreach (var runspace in results)
+ {
+ if (runspace.RunspaceStateInfo.State != RunspaceState.Opened)
{
- if (runspace.RunspaceStateInfo.State != RunspaceState.Opened)
- {
- WriteError(
- new ErrorRecord(new PSInvalidOperationException(string.Format(CultureInfo.InvariantCulture, Debugger.RunspaceOptionInvalidRunspaceState, runspace.Name)),
- "SetRunspaceDebugOptionCommandInvalidRunspaceState",
- ErrorCategory.InvalidOperation,
- this)
- );
+ WriteError(
+ new ErrorRecord(new PSInvalidOperationException(string.Format(CultureInfo.InvariantCulture, Debugger.RunspaceOptionInvalidRunspaceState, runspace.Name)),
+ "SetRunspaceDebugOptionCommandInvalidRunspaceState",
+ ErrorCategory.InvalidOperation,
+ this));
- continue;
- }
+ continue;
+ }
- System.Management.Automation.Debugger debugger = GetDebuggerFromRunspace(runspace);
- if (debugger == null)
- {
- continue;
- }
+ System.Management.Automation.Debugger debugger = GetDebuggerFromRunspace(runspace);
+ if (debugger == null)
+ {
+ continue;
+ }
- // Enable debugging by preserving debug stop events.
- debugger.UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Wait;
+ // Enable debugging by preserving debug stop events.
+ debugger.UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Wait;
- if (this.MyInvocation.BoundParameters.ContainsKey(nameof(BreakAll)))
+ if (this.MyInvocation.BoundParameters.ContainsKey(nameof(BreakAll)))
+ {
+ if (BreakAll)
{
- if (BreakAll)
+ try
{
- try
- {
- debugger.SetDebuggerStepMode(true);
- }
- catch (PSInvalidOperationException e)
- {
- WriteError(
- new ErrorRecord(
- e,
- "SetRunspaceDebugOptionCommandCannotEnableDebuggerStepping",
- ErrorCategory.InvalidOperation,
- this)
- );
- }
+ debugger.SetDebuggerStepMode(true);
}
- else
+ catch (PSInvalidOperationException e)
{
- debugger.SetDebuggerStepMode(false);
+ WriteError(
+ new ErrorRecord(
+ e,
+ "SetRunspaceDebugOptionCommandCannotEnableDebuggerStepping",
+ ErrorCategory.InvalidOperation,
+ this));
}
}
+ else
+ {
+ debugger.SetDebuggerStepMode(false);
+ }
+ }
+
+ // If any breakpoints were provided, set those in the debugger.
+ if (Breakpoint?.Length > 0)
+ {
+ debugger.SetBreakpoints(Breakpoint);
}
}
}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs
new file mode 100644
index 00000000000..b5837cc0da3
--- /dev/null
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-PSBreakpoint.cs
@@ -0,0 +1,101 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.IO;
+using System.Management.Automation;
+using System.Management.Automation.Internal;
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// This class implements New-PSBreakpoint command.
+ ///
+ [Experimental("Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints", ExperimentAction.Show)]
+ [Cmdlet(VerbsCommon.New, "PSBreakpoint", DefaultParameterSetName = LineParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113449")]
+ [OutputType(typeof(VariableBreakpoint), typeof(CommandBreakpoint), typeof(LineBreakpoint))]
+ public class NewPSBreakpointCommand : PSBreakpointCreationBase
+ {
+ ///
+ /// Create a new breakpoint.
+ ///
+ protected override void ProcessRecord()
+ {
+ // If there is a script, resolve its path
+ Collection scripts = ResolveScriptPaths();
+
+ // If it is a command breakpoint...
+ if (ParameterSetName.Equals(CommandParameterSetName, StringComparison.OrdinalIgnoreCase))
+ {
+ for (int i = 0; i < Command.Length; i++)
+ {
+ if (scripts.Count > 0)
+ {
+ foreach (string path in scripts)
+ {
+ WildcardPattern pattern = WildcardPattern.Get(Command[i], WildcardOptions.Compiled | WildcardOptions.IgnoreCase);
+ WriteObject(new CommandBreakpoint(path, pattern, Command[i], Action));
+ }
+ }
+ else
+ {
+ WildcardPattern pattern = WildcardPattern.Get(Command[i], WildcardOptions.Compiled | WildcardOptions.IgnoreCase);
+ WriteObject(new CommandBreakpoint(null, pattern, Command[i], Action));
+ }
+ }
+ }
+ else if (ParameterSetName.Equals(VariableParameterSetName, StringComparison.OrdinalIgnoreCase))
+ {
+ // If it is a variable breakpoint...
+ for (int i = 0; i < Variable.Length; i++)
+ {
+ if (scripts.Count > 0)
+ {
+ foreach (string path in scripts)
+ {
+ WriteObject(new VariableBreakpoint(path, Variable[i], Mode, Action));
+ }
+ }
+ else
+ {
+ WriteObject(new VariableBreakpoint(null, Variable[i], Mode, Action));
+ }
+ }
+ }
+ else
+ {
+ // Else it is the default parameter set (Line breakpoint)...
+ Debug.Assert(ParameterSetName.Equals(LineParameterSetName, StringComparison.OrdinalIgnoreCase));
+
+ for (int i = 0; i < Line.Length; i++)
+ {
+ if (Line[i] < 1)
+ {
+ WriteError(
+ new ErrorRecord(
+ new ArgumentException(Debugger.LineLessThanOne),
+ "NewPSBreakpoint:LineLessThanOne",
+ ErrorCategory.InvalidArgument,
+ null));
+
+ continue;
+ }
+
+ foreach (string path in scripts)
+ {
+ if (Column != 0)
+ {
+ WriteObject(new LineBreakpoint(path, Line[i], Column, Action));
+ }
+ else
+ {
+ WriteObject(new LineBreakpoint(path, Line[i], Action));
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs
new file mode 100644
index 00000000000..43e3e93839d
--- /dev/null
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs
@@ -0,0 +1,122 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Management.Automation;
+using System.Management.Automation.Internal;
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// Base class for Set/New-PSBreakpoint.
+ ///
+ public class PSBreakpointCreationBase : PSCmdlet
+ {
+ internal const string CommandParameterSetName = "Command";
+ internal const string LineParameterSetName = "Line";
+ internal const string VariableParameterSetName = "Variable";
+
+ #region parameters
+
+ ///
+ /// The action to take when hitting this breakpoint.
+ ///
+ [Parameter(ParameterSetName = CommandParameterSetName)]
+ [Parameter(ParameterSetName = LineParameterSetName)]
+ [Parameter(ParameterSetName = VariableParameterSetName)]
+ public ScriptBlock Action { get; set; }
+
+ ///
+ /// The column to set the breakpoint on.
+ ///
+ [Parameter(Position = 2, ParameterSetName = LineParameterSetName)]
+ [ValidateRange(1, int.MaxValue)]
+ public int Column { get; set; }
+
+ ///
+ /// The command(s) to set the breakpoint on.
+ ///
+ [Alias("C")]
+ [Parameter(ParameterSetName = CommandParameterSetName, Mandatory = true)]
+ public string[] Command { get; set; }
+
+ ///
+ /// The line to set the breakpoint on.
+ ///
+ [Parameter(Position = 1, ParameterSetName = LineParameterSetName, Mandatory = true)]
+ public int[] Line { get; set; }
+
+ ///
+ /// The script to set the breakpoint on.
+ ///
+ [Parameter(ParameterSetName = CommandParameterSetName, Position = 0)]
+ [Parameter(ParameterSetName = LineParameterSetName, Mandatory = true, Position = 0)]
+ [Parameter(ParameterSetName = VariableParameterSetName, Position = 0)]
+ [ValidateNotNull]
+ public string[] Script { get; set; }
+
+ ///
+ /// The variables to set the breakpoint(s) on.
+ ///
+ [Alias("V")]
+ [Parameter(ParameterSetName = VariableParameterSetName, Mandatory = true)]
+ public string[] Variable { get; set; }
+
+ ///
+ /// The access type for variable breakpoints to break on.
+ ///
+ [Parameter(ParameterSetName = VariableParameterSetName)]
+ public VariableAccessMode Mode { get; set; } = VariableAccessMode.Write;
+
+ #endregion parameters
+
+ internal Collection ResolveScriptPaths()
+ {
+ Collection scripts = new Collection();
+
+ if (Script != null)
+ {
+ foreach (string script in Script)
+ {
+ Collection scriptPaths = SessionState.Path.GetResolvedPSPathFromPSPath(script);
+
+ for (int i = 0; i < scriptPaths.Count; i++)
+ {
+ string providerPath = scriptPaths[i].ProviderPath;
+
+ if (!File.Exists(providerPath))
+ {
+ WriteError(
+ new ErrorRecord(
+ new ArgumentException(StringUtil.Format(Debugger.FileDoesNotExist, providerPath)),
+ "NewPSBreakpoint:FileDoesNotExist",
+ ErrorCategory.InvalidArgument,
+ null));
+
+ continue;
+ }
+
+ string extension = Path.GetExtension(providerPath);
+
+ if (!extension.Equals(".ps1", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".psm1", StringComparison.OrdinalIgnoreCase))
+ {
+ WriteError(
+ new ErrorRecord(
+ new ArgumentException(StringUtil.Format(Debugger.WrongExtension, providerPath)),
+ "NewPSBreakpoint:WrongExtension",
+ ErrorCategory.InvalidArgument,
+ null));
+ continue;
+ }
+
+ scripts.Add(Path.GetFullPath(providerPath));
+ }
+ }
+ }
+
+ return scripts;
+ }
+ }
+}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs
index 1a68ee4ce31..23773fd4477 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs
@@ -13,79 +13,10 @@ namespace Microsoft.PowerShell.Commands
///
/// This class implements Set-PSBreakpoint command.
///
- [Cmdlet(VerbsCommon.Set, "PSBreakpoint", DefaultParameterSetName = "Line", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113449")]
+ [Cmdlet(VerbsCommon.Set, "PSBreakpoint", DefaultParameterSetName = LineParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113449")]
[OutputType(typeof(VariableBreakpoint), typeof(CommandBreakpoint), typeof(LineBreakpoint))]
- public class SetPSBreakpointCommand : PSCmdlet
+ public class SetPSBreakpointCommand : PSBreakpointCreationBase
{
- #region parameters
-
- ///
- /// The action to take when hitting this breakpoint.
- ///
- [Parameter(ParameterSetName = "Command")]
- [Parameter(ParameterSetName = "Line")]
- [Parameter(ParameterSetName = "Variable")]
- public ScriptBlock Action { get; set; } = null;
-
- ///
- /// The column to set the breakpoint on.
- ///
- [Parameter(Position = 2, ParameterSetName = "Line")]
- [ValidateRange(1, int.MaxValue)]
- public int Column
- {
- get
- {
- return _column ?? 0;
- }
-
- set
- {
- _column = value;
- }
- }
-
- private int? _column = null;
-
- ///
- /// The command(s) to set the breakpoint on.
- ///
- [Alias("C")]
- [Parameter(ParameterSetName = "Command", Mandatory = true)]
- [ValidateNotNull]
- public string[] Command { get; set; } = null;
-
- ///
- /// The line to set the breakpoint on.
- ///
- [Parameter(Position = 1, ParameterSetName = "Line", Mandatory = true)]
- [ValidateNotNull]
- public int[] Line { get; set; } = null;
-
- ///
- /// The script to set the breakpoint on.
- ///
- [Parameter(ParameterSetName = "Command", Position = 0)]
- [Parameter(ParameterSetName = "Line", Mandatory = true, Position = 0)]
- [Parameter(ParameterSetName = "Variable", Position = 0)]
- [ValidateNotNull]
- public string[] Script { get; set; } = null;
-
- ///
- /// The variables to set the breakpoint(s) on.
- ///
- [Alias("V")]
- [Parameter(ParameterSetName = "Variable", Mandatory = true)]
- [ValidateNotNull]
- public string[] Variable { get; set; } = null;
-
- ///
- ///
- [Parameter(ParameterSetName = "Variable")]
- public VariableAccessMode Mode { get; set; } = VariableAccessMode.Write;
-
- #endregion parameters
-
///
/// Verifies that debugging is supported.
///
@@ -130,52 +61,12 @@ protected override void BeginProcessing()
protected override void ProcessRecord()
{
// If there is a script, resolve its path
- Collection scripts = new Collection();
-
- if (Script != null)
- {
- foreach (string script in Script)
- {
- Collection scriptPaths = SessionState.Path.GetResolvedPSPathFromPSPath(script);
-
- for (int i = 0; i < scriptPaths.Count; i++)
- {
- string providerPath = scriptPaths[i].ProviderPath;
-
- if (!File.Exists(providerPath))
- {
- WriteError(
- new ErrorRecord(
- new ArgumentException(StringUtil.Format(Debugger.FileDoesNotExist, providerPath)),
- "SetPSBreakpoint:FileDoesNotExist",
- ErrorCategory.InvalidArgument,
- null));
-
- continue;
- }
-
- string extension = Path.GetExtension(providerPath);
-
- if (!extension.Equals(".ps1", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".psm1", StringComparison.OrdinalIgnoreCase))
- {
- WriteError(
- new ErrorRecord(
- new ArgumentException(StringUtil.Format(Debugger.WrongExtension, providerPath)),
- "SetPSBreakpoint:WrongExtension",
- ErrorCategory.InvalidArgument,
- null));
- continue;
- }
-
- scripts.Add(Path.GetFullPath(providerPath));
- }
- }
- }
+ Collection scripts = ResolveScriptPaths();
//
// If it is a command breakpoint...
//
- if (ParameterSetName.Equals("Command", StringComparison.OrdinalIgnoreCase))
+ if (ParameterSetName.Equals(CommandParameterSetName, StringComparison.OrdinalIgnoreCase))
{
for (int i = 0; i < Command.Length; i++)
{
@@ -197,7 +88,7 @@ protected override void ProcessRecord()
//
// If it is a variable breakpoint...
//
- else if (ParameterSetName.Equals("Variable", StringComparison.OrdinalIgnoreCase))
+ else if (ParameterSetName.Equals(VariableParameterSetName, StringComparison.OrdinalIgnoreCase))
{
for (int i = 0; i < Variable.Length; i++)
{
@@ -221,7 +112,7 @@ protected override void ProcessRecord()
//
else
{
- Debug.Assert(ParameterSetName.Equals("Line", StringComparison.OrdinalIgnoreCase));
+ Debug.Assert(ParameterSetName.Equals(LineParameterSetName, StringComparison.OrdinalIgnoreCase));
for (int i = 0; i < Line.Length; i++)
{
@@ -239,7 +130,7 @@ protected override void ProcessRecord()
foreach (string path in scripts)
{
- if (_column != null)
+ if (Column != 0)
{
WriteObject(
Context.Debugger.NewStatementBreakpoint(path, Line[i], Column, Action));
diff --git a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
index 863b38f7498..71edf2b7031 100644
--- a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
+++ b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
@@ -18,7 +18,7 @@ CmdletsToExport = @(
'Set-MarkdownOption', 'Add-Member', 'Get-Member', 'Compare-Object', 'Group-Object', 'Measure-Object',
'New-Object', 'Select-Object', 'Sort-Object', 'Tee-Object', 'Register-ObjectEvent', 'Write-Output',
'Import-PowerShellDataFile', 'Write-Progress', 'Disable-PSBreakpoint', 'Enable-PSBreakpoint',
- 'Get-PSBreakpoint', 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession',
+ 'Get-PSBreakpoint', 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'New-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession',
'Import-PSSession', 'Get-Random', 'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace',
'Disable-RunspaceDebug', 'Enable-RunspaceDebug', 'Get-RunspaceDebug', 'Start-Sleep', 'Join-String',
'Out-String', 'Select-String', 'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan',
@@ -31,4 +31,14 @@ FunctionsToExport = @()
AliasesToExport = @('fhx')
NestedModules = @("Microsoft.PowerShell.Commands.Utility.dll")
HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855960'
+PrivateData = @{
+ PSData = @{
+ ExperimentalFeatures = @(
+ @{
+ Name = 'Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints'
+ Description = "Enables the New-PSBreakpoint cmdlet and the -Breakpoint parameter on Debug-Runspace to set breakpoints in another Runspace upfront."
+ }
+ )
+ }
+}
}
diff --git a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
index 2db43e68531..152f8832688 100644
--- a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
+++ b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
@@ -17,7 +17,7 @@ CmdletsToExport = @(
'Show-Markdown', 'Get-MarkdownOption', 'Set-MarkdownOption', 'Add-Member', 'Get-Member', 'Compare-Object', 'Group-Object',
'Measure-Object', 'New-Object', 'Select-Object', 'Sort-Object', 'Tee-Object', 'Register-ObjectEvent', 'Write-Output',
'Import-PowerShellDataFile', 'Write-Progress', 'Disable-PSBreakpoint', 'Enable-PSBreakpoint', 'Get-PSBreakpoint',
- 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random',
+ 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'New-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random',
'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace', 'Disable-RunspaceDebug', 'Enable-RunspaceDebug',
'Get-RunspaceDebug', 'ConvertFrom-SddlString', 'Start-Sleep', 'Join-String', 'Out-String', 'Select-String',
'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan', 'Get-TraceSource', 'Set-TraceSource',
diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs
index 68860b90e23..cf2a32295ef 100644
--- a/src/System.Management.Automation/engine/InitialSessionState.cs
+++ b/src/System.Management.Automation/engine/InitialSessionState.cs
@@ -4532,6 +4532,7 @@ internal static SessionStateAliasEntry[] BuiltInAliases
new SessionStateAliasEntry("mi", "Move-Item", string.Empty, ReadOnly),
new SessionStateAliasEntry("mp", "Move-ItemProperty", string.Empty, ReadOnly),
new SessionStateAliasEntry("nal", "New-Alias", string.Empty, ReadOnly),
+ new SessionStateAliasEntry("nbp", "New-PSBreakpoint", string.Empty, ReadOnly),
new SessionStateAliasEntry("ndr", "New-PSDrive", string.Empty, ReadOnly),
new SessionStateAliasEntry("ni", "New-Item", string.Empty, ReadOnly),
new SessionStateAliasEntry("nv", "New-Variable", string.Empty, ReadOnly),
diff --git a/src/System.Management.Automation/engine/debugger/Breakpoint.cs b/src/System.Management.Automation/engine/debugger/Breakpoint.cs
index 07c0f26ec01..f7372a27520 100644
--- a/src/System.Management.Automation/engine/debugger/Breakpoint.cs
+++ b/src/System.Management.Automation/engine/debugger/Breakpoint.cs
@@ -6,6 +6,7 @@
using System.Linq;
using System.Management.Automation.Internal;
using System.Management.Automation.Language;
+using System.Threading;
namespace System.Management.Automation
{
@@ -58,16 +59,36 @@ internal bool IsScriptBreakpoint
#region constructors
- internal Breakpoint(string script, ScriptBlock action)
+ ///
+ /// Creates a new instance of a
+ ///
+ protected Breakpoint(string script)
+ : this(script, null)
+ {}
+
+ ///
+ /// Creates a new instance of a
+ ///
+ protected Breakpoint(string script, ScriptBlock action)
{
Enabled = true;
Script = script;
- Id = s_lastID++;
+ Id = Interlocked.Increment(ref s_lastID);
Action = action;
HitCount = 0;
}
- internal Breakpoint(string script, ScriptBlock action, int id)
+ ///
+ /// Creates a new instance of a
+ ///
+ protected Breakpoint(string script, int id)
+ : this(script, null, id)
+ {}
+
+ ///
+ /// Creates a new instance of a
+ ///
+ protected Breakpoint(string script, ScriptBlock action, int id)
{
Enabled = true;
Script = script;
@@ -135,14 +156,34 @@ internal enum BreakpointAction
///
public class CommandBreakpoint : Breakpoint
{
- internal CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action)
+ ///
+ /// Creates a new instance of a
+ ///
+ public CommandBreakpoint(string script, WildcardPattern command, string commandString)
+ : this(script, command, commandString, null)
+ {}
+
+ ///
+ /// Creates a new instance of a
+ ///
+ public CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action)
: base(script, action)
{
CommandPattern = command;
Command = commandString;
}
- internal CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action, int id)
+ ///
+ /// Creates a new instance of a
+ ///
+ public CommandBreakpoint(string script, WildcardPattern command, string commandString, int id)
+ : this(script, command, commandString, null, id)
+ {}
+
+ ///
+ /// Creates a new instance of a
+ ///
+ public CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action, int id)
: base(script, action, id)
{
CommandPattern = command;
@@ -224,7 +265,7 @@ public enum VariableAccessMode
///
Read,
///
- /// Break on write access only (default)
+ /// Break on write access only (default).
///
Write,
///
@@ -238,14 +279,34 @@ public enum VariableAccessMode
///
public class VariableBreakpoint : Breakpoint
{
- internal VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action)
+ ///
+ /// Creates a new instance of a .
+ ///
+ public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode)
+ : this(script, variable, accessMode, null)
+ {}
+
+ ///
+ /// Creates a new instance of a .
+ ///
+ public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action)
: base(script, action)
{
Variable = variable;
AccessMode = accessMode;
}
- internal VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action, int id)
+ ///
+ /// Creates a new instance of a .
+ ///
+ public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, int id)
+ : this(script, variable, accessMode, null, id)
+ {}
+
+ ///
+ /// Creates a new instance of a .
+ ///
+ public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action, int id)
: base(script, action, id)
{
Variable = variable;
@@ -300,7 +361,17 @@ internal override void RemoveSelf(ScriptDebugger debugger)
///
public class LineBreakpoint : Breakpoint
{
- internal LineBreakpoint(string script, int line, ScriptBlock action)
+ ///
+ /// Creates a new instance of a
+ ///
+ public LineBreakpoint(string script, int line)
+ : this(script, line, null)
+ {}
+
+ ///
+ /// Creates a new instance of a
+ ///
+ public LineBreakpoint(string script, int line, ScriptBlock action)
: base(script, action)
{
Diagnostics.Assert(!string.IsNullOrEmpty(script), "Caller to verify script parameter is not null or empty.");
@@ -309,7 +380,17 @@ internal LineBreakpoint(string script, int line, ScriptBlock action)
SequencePointIndex = -1;
}
- internal LineBreakpoint(string script, int line, int column, ScriptBlock action)
+ ///
+ /// Creates a new instance of a
+ ///
+ public LineBreakpoint(string script, int line, int column)
+ : this(script, line, column, null)
+ {}
+
+ ///
+ /// Creates a new instance of a
+ ///
+ public LineBreakpoint(string script, int line, int column, ScriptBlock action)
: base(script, action)
{
Diagnostics.Assert(!string.IsNullOrEmpty(script), "Caller to verify script parameter is not null or empty.");
@@ -318,7 +399,17 @@ internal LineBreakpoint(string script, int line, int column, ScriptBlock action)
SequencePointIndex = -1;
}
- internal LineBreakpoint(string script, int line, int column, ScriptBlock action, int id)
+ ///
+ /// Creates a new instance of a
+ ///
+ public LineBreakpoint(string script, int line, int column, int id)
+ : this(script, line, column, null, id)
+ {}
+
+ ///
+ /// Creates a new instance of a
+ ///
+ public LineBreakpoint(string script, int line, int column, ScriptBlock action, int id)
: base(script, action, id)
{
Diagnostics.Assert(!string.IsNullOrEmpty(script), "Caller to verify script parameter is not null or empty.");
diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs
index b78c8b66aaf..1d2c1624d0f 100644
--- a/src/System.Management.Automation/engine/debugger/debugger.cs
+++ b/src/System.Management.Automation/engine/debugger/debugger.cs
@@ -626,6 +626,23 @@ public virtual void SetBreakpoints(IEnumerable breakpoints)
throw new PSNotImplementedException();
}
+ ///
+ /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets.
+ ///
+ /// Id of the breakpoint you want.
+ public virtual Breakpoint GetBreakpoint(int id)
+ {
+ throw new PSNotImplementedException();
+ }
+
+ ///
+ /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet.
+ ///
+ public virtual List GetBreakpoints()
+ {
+ throw new PSNotImplementedException();
+ }
+
///
/// Resets the command processor source information so that it is
/// updated with latest information on the next debug stop.
@@ -742,6 +759,16 @@ internal virtual void DebugRunspace(Runspace runspace)
throw new PSNotImplementedException();
}
+ ///
+ /// Sets up debugger to debug provided Runspace in a nested debug session.
+ ///
+ /// Runspace to debug.
+ ///
+ internal virtual void DebugRunspace(Runspace runspace, bool disableBreakAll)
+ {
+ throw new PSNotImplementedException();
+ }
+
///
/// Removes the provided Runspace from the nested "active" debugger state.
///
@@ -822,11 +849,11 @@ internal ScriptDebugger(ExecutionContext context)
{
_context = context;
_inBreakpoint = false;
- _idToBreakpoint = new Dictionary();
- _pendingBreakpoints = new List();
- _boundBreakpoints = new Dictionary>>(StringComparer.OrdinalIgnoreCase);
- _commandBreakpoints = new List();
- _variableBreakpoints = new Dictionary>(StringComparer.OrdinalIgnoreCase);
+ _idToBreakpoint = new ConcurrentDictionary();
+ _pendingBreakpoints = new ConcurrentDictionary();
+ _boundBreakpoints = new ConcurrentDictionary>>(StringComparer.OrdinalIgnoreCase);
+ _commandBreakpoints = new ConcurrentDictionary();
+ _variableBreakpoints = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase);
_steppingMode = SteppingMode.None;
_callStack = new CallStackList { _callStackList = new List() };
@@ -1061,10 +1088,10 @@ internal void RegisterScriptFile(ExternalScriptInfo scriptCommandInfo)
internal void RegisterScriptFile(string path, string scriptContents)
{
- Tuple> boundBreakpoints;
+ Tuple> boundBreakpoints;
if (!_boundBreakpoints.TryGetValue(path, out boundBreakpoints))
{
- _boundBreakpoints.Add(path, Tuple.Create(new WeakReference(scriptContents), new List()));
+ _boundBreakpoints[path] = Tuple.Create(new WeakReference(scriptContents), new ConcurrentDictionary());
}
else
{
@@ -1073,8 +1100,8 @@ internal void RegisterScriptFile(string path, string scriptContents)
boundBreakpoints.Item1.TryGetTarget(out oldScriptContents);
if (oldScriptContents == null || !oldScriptContents.Equals(scriptContents, StringComparison.Ordinal))
{
- UnbindBoundBreakpoints(boundBreakpoints.Item2);
- _boundBreakpoints[path] = Tuple.Create(new WeakReference(scriptContents), new List());
+ UnbindBoundBreakpoints(boundBreakpoints.Item2.Values.ToList());
+ _boundBreakpoints[path] = Tuple.Create(new WeakReference(scriptContents), new ConcurrentDictionary());
}
}
}
@@ -1097,7 +1124,7 @@ internal void AddBreakpointCommon(Breakpoint breakpoint)
private Breakpoint AddCommandBreakpoint(CommandBreakpoint breakpoint)
{
AddBreakpointCommon(breakpoint);
- _commandBreakpoints.Add(breakpoint);
+ _commandBreakpoints[breakpoint.Id] = breakpoint;
return breakpoint;
}
@@ -1116,7 +1143,7 @@ internal Breakpoint NewCommandBreakpoint(string command, ScriptBlock action)
private Breakpoint AddLineBreakpoint(LineBreakpoint breakpoint)
{
AddBreakpointCommon(breakpoint);
- _pendingBreakpoints.Add(breakpoint);
+ _pendingBreakpoints[breakpoint.Id] = breakpoint;
return breakpoint;
}
@@ -1161,14 +1188,13 @@ internal VariableBreakpoint AddVariableBreakpoint(VariableBreakpoint breakpoint)
{
AddBreakpointCommon(breakpoint);
- List breakpoints;
- if (!_variableBreakpoints.TryGetValue(breakpoint.Variable, out breakpoints))
+ if (!_variableBreakpoints.TryGetValue(breakpoint.Variable, out ConcurrentDictionary breakpoints))
{
- breakpoints = new List();
- _variableBreakpoints.Add(breakpoint.Variable, breakpoints);
+ breakpoints = new ConcurrentDictionary();
+ _variableBreakpoints[breakpoint.Variable] = breakpoints;
}
- breakpoints.Add(breakpoint);
+ breakpoints[breakpoint.Id] = breakpoint;
return breakpoint;
}
@@ -1198,7 +1224,7 @@ private void OnBreakpointUpdated(BreakpointUpdatedEventArgs e)
// This is the implementation of the Remove-PSBreakpoint cmdlet.
internal void RemoveBreakpoint(Breakpoint breakpoint)
{
- _idToBreakpoint.Remove(breakpoint.Id);
+ _idToBreakpoint.Remove(breakpoint.Id, out _);
breakpoint.RemoveSelf(this);
@@ -1213,22 +1239,22 @@ internal void RemoveBreakpoint(Breakpoint breakpoint)
internal void RemoveVariableBreakpoint(VariableBreakpoint breakpoint)
{
- _variableBreakpoints[breakpoint.Variable].Remove(breakpoint);
+ _variableBreakpoints[breakpoint.Variable].Remove(breakpoint.Id, out _);
}
internal void RemoveCommandBreakpoint(CommandBreakpoint breakpoint)
{
- _commandBreakpoints.Remove(breakpoint);
+ _commandBreakpoints.Remove(breakpoint.Id, out _);
}
internal void RemoveLineBreakpoint(LineBreakpoint breakpoint)
{
- _pendingBreakpoints.Remove(breakpoint);
+ _pendingBreakpoints.Remove(breakpoint.Id, out _);
- Tuple> value;
+ Tuple> value;
if (_boundBreakpoints.TryGetValue(breakpoint.Script, out value))
{
- value.Item2.Remove(breakpoint);
+ value.Item2.Remove(breakpoint.Id, out _);
}
}
@@ -1255,7 +1281,7 @@ internal bool CheckCommand(InvocationInfo invocationInfo)
}
List breakpoints =
- _commandBreakpoints.Where(bp => bp.Enabled && bp.Trigger(invocationInfo)).ToList();
+ _commandBreakpoints.Values.Where(bp => bp.Enabled && bp.Trigger(invocationInfo)).ToList();
bool checkLineBp = true;
if (breakpoints.Any())
@@ -1308,7 +1334,7 @@ private List GetVariableBreakpointsToTrigger(string variable
{
SetInternalDebugMode(InternalDebugMode.Disabled);
- List breakpoints;
+ ConcurrentDictionary breakpoints;
if (!_variableBreakpoints.TryGetValue(variableName, out breakpoints))
{
// $PSItem is an alias for $_. We don't use PSItem internally, but a user might
@@ -1324,7 +1350,7 @@ private List GetVariableBreakpointsToTrigger(string variable
var callStackInfo = _callStack.Last();
var currentScriptFile = (callStackInfo != null) ? callStackInfo.File : null;
- return breakpoints.Where(bp => bp.Trigger(currentScriptFile, read: read)).ToList();
+ return breakpoints.Values.Where(bp => bp.Trigger(currentScriptFile, read: read)).ToList();
}
finally
{
@@ -1342,17 +1368,17 @@ internal void TriggerVariableBreakpoints(List breakpoints)
///
/// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets.
///
- internal Breakpoint GetBreakpoint(int id)
+ /// Id of the breakpoint you want.
+ public override Breakpoint GetBreakpoint(int id)
{
- Breakpoint breakpoint;
- _idToBreakpoint.TryGetValue(id, out breakpoint);
+ _idToBreakpoint.TryGetValue(id, out Breakpoint breakpoint);
return breakpoint;
}
///
/// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet.
///
- internal List GetBreakpoints()
+ public override List GetBreakpoints()
{
return (from bp in _idToBreakpoint.Values orderby bp.Id select bp).ToList();
}
@@ -1499,7 +1525,7 @@ private void UpdateBreakpoints(FunctionContext functionContext)
if (string.IsNullOrEmpty(functionContext._file)) { return; }
bool havePendingBreakpoint = false;
- foreach (var item in _pendingBreakpoints)
+ foreach ((int breakpointId, LineBreakpoint item) in _pendingBreakpoints)
{
if (item.IsScriptBreakpoint && item.Script.Equals(functionContext._file, StringComparison.OrdinalIgnoreCase))
{
@@ -1618,11 +1644,11 @@ internal void Clear()
}
private readonly ExecutionContext _context;
- private List _pendingBreakpoints;
- private readonly Dictionary>> _boundBreakpoints;
- private readonly List _commandBreakpoints;
- private readonly Dictionary> _variableBreakpoints;
- private readonly Dictionary _idToBreakpoint;
+ private ConcurrentDictionary _pendingBreakpoints;
+ private readonly ConcurrentDictionary>> _boundBreakpoints;
+ private readonly ConcurrentDictionary _commandBreakpoints;
+ private readonly ConcurrentDictionary> _variableBreakpoints;
+ private readonly ConcurrentDictionary _idToBreakpoint;
private SteppingMode _steppingMode;
private CallStackInfo _overOrOutFrame;
private CallStackList _callStack;
@@ -1935,7 +1961,7 @@ private void UnbindBoundBreakpoints(List boundBreakpoints)
breakpoint.SequencePoints = null;
breakpoint.SequencePointIndex = -1;
breakpoint.BreakpointBitArray = null;
- _pendingBreakpoints.Add(breakpoint);
+ _pendingBreakpoints[breakpoint.Id] = breakpoint;
}
boundBreakpoints.Clear();
@@ -1946,7 +1972,7 @@ private void SetPendingBreakpoints(FunctionContext functionContext)
if (!_pendingBreakpoints.Any())
return;
- var newPendingBreakpoints = new List();
+ var newPendingBreakpoints = new Dictionary();
var currentScriptFile = functionContext._file;
// If we're not in a file, we can't have any line breakpoints.
@@ -1967,7 +1993,7 @@ private void SetPendingBreakpoints(FunctionContext functionContext)
Diagnostics.Assert(tuple.Item1 == functionContext._boundBreakpoints, "What's up?");
- foreach (var breakpoint in _pendingBreakpoints)
+ foreach ((int breakpointId, LineBreakpoint breakpoint) in _pendingBreakpoints)
{
bool bound = false;
if (breakpoint.TrySetBreakpoint(currentScriptFile, functionContext))
@@ -1983,17 +2009,16 @@ private void SetPendingBreakpoints(FunctionContext functionContext)
// We need to keep track of any breakpoints that are bound in each script because they may
// need to be rebound if the script changes.
var boundBreakpoints = _boundBreakpoints[currentScriptFile].Item2;
- Diagnostics.Assert(boundBreakpoints.IndexOf(breakpoint) < 0, "Don't add more than once.");
- boundBreakpoints.Add(breakpoint);
+ boundBreakpoints[breakpoint.Id] = breakpoint;
}
if (!bound)
{
- newPendingBreakpoints.Add(breakpoint);
+ newPendingBreakpoints.Add(breakpoint.Id, breakpoint);
}
}
- _pendingBreakpoints = newPendingBreakpoints;
+ _pendingBreakpoints = new ConcurrentDictionary(newPendingBreakpoints);
}
private void StopOnSequencePoint(FunctionContext functionContext, List breakpoints)
@@ -2355,24 +2380,20 @@ public override void SetBreakpoints(IEnumerable breakpoints)
{
if (_idToBreakpoint.ContainsKey(breakpoint.Id)) { continue; }
- LineBreakpoint lineBp = breakpoint as LineBreakpoint;
- if (lineBp != null)
- {
- AddLineBreakpoint(lineBp);
- continue;
- }
-
- CommandBreakpoint cmdBp = breakpoint as CommandBreakpoint;
- if (cmdBp != null)
+ switch (breakpoint)
{
- AddCommandBreakpoint(cmdBp);
- continue;
- }
-
- VariableBreakpoint variableBp = breakpoint as VariableBreakpoint;
- if (variableBp != null)
- {
- AddVariableBreakpoint(variableBp);
+ case LineBreakpoint lineBp:
+ AddLineBreakpoint(lineBp);
+ continue;
+ case CommandBreakpoint cmdBp:
+ AddCommandBreakpoint(cmdBp);
+ continue;
+ case VariableBreakpoint variableBp:
+ AddVariableBreakpoint(variableBp);
+ continue;
+ default:
+ // Unreachable default block
+ break;
}
}
}
@@ -2657,11 +2678,17 @@ internal static void SetDebugJobAsync(IJobDebugger debuggableJob, bool isAsync)
#region Runspace Debugging
+ internal override void DebugRunspace(Runspace runspace)
+ {
+ DebugRunspace(runspace, disableBreakAll:false);
+ }
+
///
/// Sets up debugger to debug provided Runspace in a nested debug session.
///
/// Runspace to debug.
- internal override void DebugRunspace(Runspace runspace)
+ /// When specified, it will not turn on BreakAll.
+ internal override void DebugRunspace(Runspace runspace, bool disableBreakAll)
{
if (runspace == null)
{
@@ -2696,7 +2723,7 @@ internal override void DebugRunspace(Runspace runspace)
AddToRunningRunspaceList(new PSStandaloneMonitorRunspaceInfo(runspace));
- if (!runspace.Debugger.InBreakpoint)
+ if (!runspace.Debugger.InBreakpoint && !disableBreakAll)
{
EnableDebuggerStepping(EnableNestedType.NestedRunspace);
}
@@ -3988,6 +4015,28 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC
return _wrappedDebugger.ProcessCommand(command, output);
}
+ ///
+ /// Adds the provided set of breakpoints to the debugger.
+ ///
+ /// Breakpoints.
+ public override void SetBreakpoints(IEnumerable breakpoints)
+ {
+ _wrappedDebugger.SetBreakpoints(breakpoints);
+ }
+
+ ///
+ /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets.
+ ///
+ /// Id of the breakpoint you want.
+ public override Breakpoint GetBreakpoint(int id) =>
+ _wrappedDebugger.GetBreakpoint(id);
+
+ ///
+ /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet.
+ ///
+ public override List GetBreakpoints() =>
+ _wrappedDebugger.GetBreakpoints();
+
///
/// SetDebuggerAction.
///
diff --git a/src/System.Management.Automation/engine/remoting/client/Job.cs b/src/System.Management.Automation/engine/remoting/client/Job.cs
index 39a5e35ff74..78397a2d906 100644
--- a/src/System.Management.Automation/engine/remoting/client/Job.cs
+++ b/src/System.Management.Automation/engine/remoting/client/Job.cs
@@ -3907,6 +3907,28 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC
return _wrappedDebugger.ProcessCommand(command, output);
}
+ ///
+ /// Adds the provided set of breakpoints to the debugger.
+ ///
+ /// Breakpoints.
+ public override void SetBreakpoints(IEnumerable breakpoints)
+ {
+ _wrappedDebugger.SetBreakpoints(breakpoints);
+ }
+
+ ///
+ /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets.
+ ///
+ /// Id of the breakpoint you want.
+ public override Breakpoint GetBreakpoint(int id) =>
+ _wrappedDebugger.GetBreakpoint(id);
+
+ ///
+ /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet.
+ ///
+ public override List GetBreakpoints() =>
+ _wrappedDebugger.GetBreakpoints();
+
///
/// Sets the debugger resume action.
///
diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs
index b390e3f9b69..9d673b5a24c 100644
--- a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs
+++ b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs
@@ -2007,6 +2007,28 @@ public override void StopProcessCommand()
}
}
+ ///
+ /// Adds the provided set of breakpoints to the debugger.
+ ///
+ /// Breakpoints.
+ public override void SetBreakpoints(IEnumerable breakpoints)
+ {
+ _runspace.Debugger?.SetBreakpoints(breakpoints);
+ }
+
+ ///
+ /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets.
+ ///
+ /// Id of the breakpoint you want.
+ public override Breakpoint GetBreakpoint(int id) =>
+ _runspace.Debugger?.GetBreakpoint(id);
+
+ ///
+ /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet.
+ ///
+ public override List GetBreakpoints() =>
+ _runspace.Debugger?.GetBreakpoints();
+
///
/// SetDebuggerAction.
///
diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs
index 5ed66313f72..29ecb409455 100644
--- a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs
+++ b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs
@@ -1767,6 +1767,28 @@ public override bool InBreakpoint
get { return _inDebugMode; }
}
+ ///
+ /// Adds the provided set of breakpoints to the debugger.
+ ///
+ /// Breakpoints.
+ public override void SetBreakpoints(IEnumerable breakpoints)
+ {
+ _wrappedDebugger.Value.SetBreakpoints(breakpoints);
+ }
+
+ ///
+ /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets.
+ ///
+ /// Id of the breakpoint you want.
+ public override Breakpoint GetBreakpoint(int id) =>
+ _wrappedDebugger.Value.GetBreakpoint(id);
+
+ ///
+ /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet.
+ ///
+ public override List GetBreakpoints() =>
+ _wrappedDebugger.Value.GetBreakpoints();
+
///
/// Exits debugger mode with the provided resume action.
///
@@ -1921,6 +1943,16 @@ internal override void DebugRunspace(Runspace runspace)
_wrappedDebugger.Value.DebugRunspace(runspace);
}
+ ///
+ /// Sets up debugger to debug provided Runspace in a nested debug session.
+ ///
+ /// Runspace to debug.
+ ///
+ internal override void DebugRunspace(Runspace runspace, bool disableBreakAll)
+ {
+ _wrappedDebugger.Value.DebugRunspace(runspace, disableBreakAll);
+ }
+
///
/// Removes the provided Runspace from the nested "active" debugger state.
///
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1
new file mode 100644
index 00000000000..176d980f551
--- /dev/null
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-RunspaceDebug.Tests.ps1
@@ -0,0 +1,77 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+$FeatureEnabled = $EnabledExperimentalFeatures.Contains('Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints')
+
+Describe "`Enable-RunspaceDebug -Breakpoint` Unit Tests - Feature-Enabled" -Tags "CI" {
+
+ BeforeAll {
+ if (!$FeatureEnabled) {
+ Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints' to be enabled." -Verbose
+ $originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
+ $PSDefaultParameterValues["it:skip"] = $true
+ return
+ }
+
+ #Set up script file 1
+ $scriptFileName1 = Join-Path $TestDrive -ChildPath breakpointTestScript.ps1
+
+ $contents = @"
+function Hello
+{
+ `$greeting = 'Hello, world!'
+ write-host `$greeting
+}
+
+function Goodbye
+{
+ `$message = 'Good bye, cruel world!'
+ write-host `$message
+}
+
+Hello
+Goodbye
+"@
+
+ $contents > $scriptFileName1
+
+ # The breakpoints are created here because when the tests are run with the experimental feature off,
+ # this command does not exist and the Pester tests fail to work
+ $breakpointArr = @(
+ New-PSBreakpoint -Line 12 $scriptFileName1
+ New-PSBreakpoint -Line 13 $scriptFileName1
+ )
+
+ $iss = [initialsessionstate]::CreateDefault2();
+ $testRunspace1 = [runspacefactory]::CreateRunspace($iss)
+ $testRunspace1.Name = "TestRunspaceDebuggerReset"
+ $testRunspace1.Open()
+ }
+
+ AfterAll {
+ if (!$FeatureEnabled) {
+ $global:PSDefaultParameterValues = $originalDefaultParameterValues
+ return
+ }
+
+ # Clean up
+ $testRunspace1.Dispose()
+ }
+
+ It "Can set breakpoints in the runspace - " -TestCases @(
+ @{
+ Name = "Current runspace"
+ Runspace = [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace
+ Breakpoints = $breakpointArr
+ },
+ @{
+ Name = $testRunspace1.Name
+ Runspace = $testRunspace1
+ Breakpoints = $breakpointArr
+ }
+ ) {
+ param($Runspace, $Breakpoints)
+ Enable-RunspaceDebug -Breakpoint $Breakpoints -Runspace $Runspace
+ $Runspace.Debugger.GetBreakpoints() | Should -Be @($Breakpoints)
+ }
+}
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1
new file mode 100644
index 00000000000..544cdff2902
--- /dev/null
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1
@@ -0,0 +1,195 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+$FeatureEnabled = $EnabledExperimentalFeatures.Contains('Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints')
+
+Describe "New-PSBreakpoint Unit Tests - Feature-Enabled" -Tags "CI" {
+
+ BeforeAll {
+ if (!$FeatureEnabled) {
+ Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints' to be enabled." -Verbose
+ $originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
+ $PSDefaultParameterValues["it:skip"] = $true
+ return
+ }
+
+ #Set up script file 1
+ $scriptFileName1 = Join-Path $TestDrive -ChildPath breakpointTestScript.ps1
+
+ $contents = @"
+function Hello
+{
+ `$greeting = 'Hello, world!'
+ write-host `$greeting
+}
+
+function Goodbye
+{
+ `$message = 'Good bye, cruel world!'
+ write-host `$message
+}
+
+Hello
+Goodbye
+
+# The following 2 statements produce null tokens (needed to verify 105473)
+#
+`$table = @{}
+
+return
+"@
+
+ $contents > $scriptFileName1
+
+ # Set up script file 2
+ $scriptFileName2 = Join-Path -Path $TestDrive -ChildPath psbreakpointtestscript.ps1
+
+ "`$var = 1 " > $scriptFileName2
+ }
+
+ AfterAll {
+ if (!$FeatureEnabled) {
+ $global:PSDefaultParameterValues = $originalDefaultParameterValues
+ return
+ }
+ }
+
+ It "Should be able to set psbreakpoints for -Line" {
+ $brk = New-PSBreakpoint -Line 13 -Script $scriptFileName1
+ $brk.Line | Should -Be 13
+ }
+
+ It "Should be able to set psbreakpoints for -Line and -column" {
+ $brk = New-PSBreakpoint -line 13 -column 1 -script $scriptFileName1
+ $brk.Line | Should -Be 13
+ $brk.Column | Should -Be 1
+ }
+
+ It "Should be able to set psbreakpoints for -Line and -action" {
+ $brk = New-PSBreakpoint -line 13 -action {{ break; }} -script $scriptFileName1
+ $brk.Line | Should -Be 13
+ $brk.Action | Should -Match "break"
+ }
+
+ It "Should be able to set psbreakpoints for -Line, -column and -action" {
+ $brk = New-PSBreakpoint -line 13 -column 1 -action {{ break; }} -script $scriptFileName1
+ $brk.Line | Should -Be 13
+ $brk.Column | Should -Be 1
+ $brk.Action | Should -Match "break"
+ }
+
+ It "-script and -line can take multiple items" {
+ $brk = New-PSBreakpoint -line 11,12,13 -column 1 -script $scriptFileName1,$scriptFileName1
+ $brk.Line | Should -BeIn 11,12,13
+ $brk.Column | Should -BeIn 1
+ }
+
+ It "-script and -line are positional" {
+ $brk = New-PSBreakpoint $scriptFileName1 13
+ $brk.Line | Should -Be 13
+ }
+
+ It "-script, -line and -column are positional" {
+ $brk = New-PSBreakpoint $scriptFileName1 13 1
+ $brk.Line | Should -Be 13
+ $brk.Column | Should -Be 1
+ }
+
+ It "Should throw Exception when missing mandatory parameter -line" -Pending {
+ $output = pwsh -noninteractive -command "nbp -column 1 -script $scriptFileName1"
+ [system.string]::Join(" ", $output) | Should -Match "MissingMandatoryParameter,Microsoft.PowerShell.Commands.NewPSBreakpointCommand"
+ }
+
+ It "Should throw Exception when missing mandatory parameter" -Pending {
+ $output = pwsh -noprofile -noninteractive -command "nbp -line 1"
+ [system.string]::Join(" ", $output) | Should -Match "MissingMandatoryParameter,Microsoft.PowerShell.Commands.NewPSBreakpointCommand"
+ }
+
+ It "Should be able to set psbreakpoints for -command" {
+ $brk = New-PSBreakpoint -command "write-host"
+ $brk.Command | Should -BeExactly "write-host"
+ }
+
+ It "Should be able to set psbreakpoints for -command, -script" {
+ $brk = New-PSBreakpoint -command "write-host" -script $scriptFileName1
+ $brk.Command | Should -BeExactly "write-host"
+ }
+
+ It "Should be able to set psbreakpoints for -command, -action and -script" {
+ $brk = New-PSBreakpoint -command "write-host" -action {{ break; }} -script $scriptFileName1
+ $brk.Action | Should -Match "break"
+ }
+
+ It "-Command can take multiple items" {
+ $brk = New-PSBreakpoint -command write-host,Hello
+ $brk.Command | Should -Be write-host,Hello
+ }
+
+ It "-Script is positional" {
+ $brk = New-PSBreakpoint -command "Hello" $scriptFileName1
+ $brk.Command | Should -BeExactly "Hello"
+
+ $brk = New-PSBreakpoint $scriptFileName1 -command "Hello"
+ $brk.Command | Should -BeExactly "Hello"
+ }
+
+ It "Should be able to set breakpoints on functions" {
+ $brk = New-PSBreakpoint -command Hello,Goodbye -script $scriptFileName1
+ $brk.Command | Should -Be Hello,Goodbye
+ }
+
+ It "Should be throw Exception when Column number less than 1" {
+ { New-PSBreakpoint -line 1 -column -1 -script $scriptFileName1 } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.NewPSBreakpointCommand"
+ }
+
+ It "Should be throw Exception when Line number less than 1" {
+ $ErrorActionPreference = "Stop"
+ { New-PSBreakpoint -line -1 -script $scriptFileName1 } | Should -Throw -ErrorId "NewPSBreakpoint:LineLessThanOne,Microsoft.PowerShell.Commands.NewPSBreakpointCommand"
+ $ErrorActionPreference = "SilentlyContinue"
+ }
+
+ It "Fail to set psbreakpoints when script is a file of wrong type" {
+ $tempFile = [System.IO.Path]::GetTempFileName()
+ $ErrorActionPreference = "Stop"
+ {
+ New-PSBreakpoint -Script $tempFile -Line 1
+ } | Should -Throw
+ $ErrorActionPreference = "SilentlyContinue"
+ Remove-Item $tempFile -Force
+ }
+
+ It "Fail to set psbreakpoints when script file does not exist" {
+ $ErrorActionPreference = "Stop"
+ ${script.ps1} = 10
+ {
+ New-PSBreakpoint -Script variable:\script.ps1 -Line 1
+ } | Should -Throw
+ $ErrorActionPreference = "SilentlyContinue"
+ }
+
+ It "Should be able to set a psbreakpoint on a line" {
+ $lineNumber = 1
+ $brk = New-PSBreakpoint -Line $lineNumber -Script $scriptFileName2
+ $brk.Line | Should -Be $lineNumber
+ }
+
+ It "Should throw when a string is entered for a line number" {
+ {
+ $lineNumber = "one"
+ New-PSBreakpoint -Line $lineNumber -Script $scriptFileName2
+
+ } | Should -Throw
+ }
+
+ It "Should be able to set a psbreakpoint on a Command" {
+ $command = "theCommand"
+ $brk = New-PSBreakpoint -Command $command -Script $scriptFileName2
+ $brk.Command | Should -Be $command
+ }
+
+ It "Should be able to set a psbreakpoint on a variable" {
+ $var = "theVariable"
+ $brk = New-PSBreakpoint -Command $var -Script $scriptFileName2
+ $brk.Command | Should -Be $var
+ }
+}
diff --git a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1
index b33e243606d..750ecfbd518 100644
--- a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1
+++ b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1
@@ -105,6 +105,7 @@ Describe "Verify approved aliases list" -Tags "CI" {
"Alias", "move", "Move-Item", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "AllScope", ""
"Alias", "mp", "Move-ItemProperty", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", ""
"Alias", "mv", "Move-Item", $($FullCLR -or $CoreWindows ), "", "", ""
+"Alias", "nbp", "New-PSBreakpoint", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", ""
"Alias", "nal", "New-Alias", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", ""
"Alias", "ndr", "New-PSDrive", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", ""
"Alias", "ni", "New-Item", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", ""
diff --git a/test/tools/TestMetadata.json b/test/tools/TestMetadata.json
index c49ab1758c6..bc172948978 100644
--- a/test/tools/TestMetadata.json
+++ b/test/tools/TestMetadata.json
@@ -1,5 +1,6 @@
{
"ExperimentalFeatures": {
+ "Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints": ["test/powershell/Modules/Microsoft.PowerShell.Utility/New-PSBreakpoint.Tests.ps1"],
"ExpTest.FeatureOne": [ "test/powershell/engine/ExperimentalFeature/ExperimentalFeature.Basic.Tests.ps1" ]
}
}
diff --git a/test/xUnit/csharp/test_Runspace.cs b/test/xUnit/csharp/test_Runspace.cs
index 7bf0bc3f85f..38a6dfd1bf3 100644
--- a/test/xUnit/csharp/test_Runspace.cs
+++ b/test/xUnit/csharp/test_Runspace.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.Collections.Generic;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using Xunit;
@@ -100,5 +101,31 @@ public void TestRunspaceWithPowerShellAndInitialSessionState()
runspace.Close();
}
}
+
+ [Fact]
+ public void TestRunspaceSetBreakpoints()
+ {
+ using (var runspace = RunspaceFactory.CreateRunspace())
+ {
+ var expectedBreakpoints = new Breakpoint[] {
+ new LineBreakpoint(@"./path/to/some/file.ps1", 1),
+ new CommandBreakpoint(@"./path/to/some/file.ps1", new WildcardPattern("Write-Host"), "Write-Host"),
+ };
+
+ runspace.Open();
+
+ try
+ {
+ runspace.Debugger.SetBreakpoints(expectedBreakpoints);
+ List actualBreakpoints = runspace.Debugger.GetBreakpoints();
+ Assert.Equal(expectedBreakpoints.Length, actualBreakpoints.Count);
+ Assert.Equal(expectedBreakpoints, actualBreakpoints);
+ }
+ finally
+ {
+ runspace.Close();
+ }
+ }
+ }
}
}