diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs
index c54c9499761..1bdd5a5aea8 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs
@@ -8,41 +8,36 @@ namespace Microsoft.PowerShell.Commands
///
/// This class implements Disable-PSBreakpoint.
///
- [Cmdlet(VerbsLifecycle.Disable, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = "Breakpoint", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096498")]
+ [Cmdlet(VerbsLifecycle.Disable, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = BreakpointParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096498")]
[OutputType(typeof(Breakpoint))]
- public class DisablePSBreakpointCommand : PSBreakpointCommandBase
+ public class DisablePSBreakpointCommand : PSBreakpointUpdaterCommandBase
{
+ #region parameters
+
///
/// Gets or sets the parameter -passThru which states whether the
/// command should place the breakpoints it processes in the pipeline.
///
[Parameter]
- public SwitchParameter PassThru
- {
- get
- {
- return _passThru;
- }
+ public SwitchParameter PassThru { get; set; }
- set
- {
- _passThru = value;
- }
- }
+ #endregion parameters
- private bool _passThru;
+ #region overrides
///
/// Disables the given breakpoint.
///
protected override void ProcessBreakpoint(Breakpoint breakpoint)
{
- this.Context.Debugger.DisableBreakpoint(breakpoint);
+ breakpoint = Runspace.Debugger.DisableBreakpoint(breakpoint);
- if (_passThru)
+ if (PassThru)
{
- WriteObject(breakpoint);
+ base.ProcessBreakpoint(breakpoint);
}
}
+
+ #endregion overrides
}
}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs
index d06073c4214..0822e146ae6 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs
@@ -1,158 +1,43 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System;
-using System.Diagnostics;
using System.Management.Automation;
-using System.Management.Automation.Internal;
namespace Microsoft.PowerShell.Commands
{
- ///
- /// Base class for Enable/Disable/Remove-PSBreakpoint.
- ///
- public abstract class PSBreakpointCommandBase : PSCmdlet
- {
- ///
- /// The breakpoint to enable.
- ///
- [Parameter(ParameterSetName = "Breakpoint", ValueFromPipeline = true, Position = 0, Mandatory = true)]
- [ValidateNotNull]
- public Breakpoint[] Breakpoint
- {
- get
- {
- return _breakpoints;
- }
-
- set
- {
- _breakpoints = value;
- }
- }
-
- private Breakpoint[] _breakpoints;
-
- ///
- /// The Id of the breakpoint to enable.
- ///
- [Parameter(ParameterSetName = "Id", ValueFromPipelineByPropertyName = true, Position = 0, Mandatory = true)]
- [ValidateNotNull]
- public int[] Id
- {
- get
- {
- return _ids;
- }
-
- set
- {
- _ids = value;
- }
- }
-
- private int[] _ids;
-
- ///
- /// Gathers the list of breakpoints to process and calls ProcessBreakpoints.
- ///
- protected override void ProcessRecord()
- {
- if (ParameterSetName.Equals("Breakpoint", StringComparison.OrdinalIgnoreCase))
- {
- foreach (Breakpoint breakpoint in _breakpoints)
- {
- if (ShouldProcessInternal(breakpoint.ToString()))
- {
- ProcessBreakpoint(breakpoint);
- }
- }
- }
- else
- {
- Debug.Assert(ParameterSetName.Equals("Id", StringComparison.OrdinalIgnoreCase));
-
- foreach (int i in _ids)
- {
- Breakpoint breakpoint = this.Context.Debugger.GetBreakpoint(i);
-
- if (breakpoint == null)
- {
- WriteError(
- new ErrorRecord(
- new ArgumentException(StringUtil.Format(Debugger.BreakpointIdNotFound, i)),
- "PSBreakpoint:BreakpointIdNotFound",
- ErrorCategory.InvalidArgument,
- null));
- continue;
- }
-
- if (ShouldProcessInternal(breakpoint.ToString()))
- {
- ProcessBreakpoint(breakpoint);
- }
- }
- }
- }
-
- ///
- /// Process the given breakpoint.
- ///
- protected abstract void ProcessBreakpoint(Breakpoint breakpoint);
-
- private bool ShouldProcessInternal(string target)
- {
- // ShouldProcess should be called only if the WhatIf or Confirm parameters are passed in explicitly.
- // It should *not* be called if we are in a nested debug prompt and the current running command was
- // run with -WhatIf or -Confirm, because this prevents the user from adding/removing breakpoints inside
- // a debugger stop.
- if (this.MyInvocation.BoundParameters.ContainsKey("WhatIf") || this.MyInvocation.BoundParameters.ContainsKey("Confirm"))
- {
- return ShouldProcess(target);
- }
-
- return true;
- }
- }
-
///
/// This class implements Enable-PSBreakpoint.
///
- [Cmdlet(VerbsLifecycle.Enable, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = "Id", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096700")]
+ [Cmdlet(VerbsLifecycle.Enable, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = BreakpointParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096700")]
[OutputType(typeof(Breakpoint))]
- public class EnablePSBreakpointCommand : PSBreakpointCommandBase
+ public class EnablePSBreakpointCommand : PSBreakpointUpdaterCommandBase
{
+ #region parameters
+
///
/// Gets or sets the parameter -passThru which states whether the
/// command should place the breakpoints it processes in the pipeline.
///
[Parameter]
- public SwitchParameter PassThru
- {
- get
- {
- return _passThru;
- }
+ public SwitchParameter PassThru { get; set; }
- set
- {
- _passThru = value;
- }
- }
+ #endregion parameters
- private bool _passThru;
+ #region overrides
///
/// Enables the given breakpoint.
///
protected override void ProcessBreakpoint(Breakpoint breakpoint)
{
- this.Context.Debugger.EnableBreakpoint(breakpoint);
+ breakpoint = Runspace.Debugger.EnableBreakpoint(breakpoint);
- if (_passThru)
+ if (PassThru)
{
- WriteObject(breakpoint);
+ base.ProcessBreakpoint(breakpoint);
}
}
+
+ #endregion overrides
}
}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs
index b836fafc9f9..4f8903facfd 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs
@@ -27,152 +27,97 @@ public enum BreakpointType
///
/// This class implements Get-PSBreakpoint.
///
- [Cmdlet(VerbsCommon.Get, "PSBreakpoint", DefaultParameterSetName = "Script", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097108")]
- [OutputType(typeof(Breakpoint))]
- public class GetPSBreakpointCommand : PSCmdlet
+ [Cmdlet(VerbsCommon.Get, "PSBreakpoint", DefaultParameterSetName = LineParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097108")]
+ [OutputType(typeof(CommandBreakpoint), ParameterSetName = new[] { CommandParameterSetName })]
+ [OutputType(typeof(LineBreakpoint), ParameterSetName = new[] { LineParameterSetName })]
+ [OutputType(typeof(VariableBreakpoint), ParameterSetName = new[] { VariableParameterSetName })]
+ [OutputType(typeof(Breakpoint), ParameterSetName = new[] { TypeParameterSetName, IdParameterSetName })]
+ public class GetPSBreakpointCommand : PSBreakpointAccessorCommandBase
{
+ #region strings
+
+ internal const string TypeParameterSetName = "Type";
+ internal const string IdParameterSetName = "Id";
+
+ #endregion strings
+
#region parameters
///
/// Scripts of the breakpoints to output.
///
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's OK to use arrays for cmdlet parameters")]
- [Parameter(ParameterSetName = "Script", Position = 0, ValueFromPipeline = true)]
- [Parameter(ParameterSetName = "Variable")]
- [Parameter(ParameterSetName = "Command")]
- [Parameter(ParameterSetName = "Type")]
+ [Parameter(ParameterSetName = LineParameterSetName, Position = 0, ValueFromPipeline = true)]
+ [Parameter(ParameterSetName = CommandParameterSetName)]
+ [Parameter(ParameterSetName = VariableParameterSetName)]
+ [Parameter(ParameterSetName = TypeParameterSetName)]
[ValidateNotNullOrEmpty()]
- public string[] Script
- {
- get
- {
- return _script;
- }
-
- set
- {
- _script = value;
- }
- }
-
- private string[] _script;
+ public string[] Script { get; set; }
///
/// IDs of the breakpoints to output.
///
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's OK to use arrays for cmdlet parameters")]
- [Parameter(ParameterSetName = "Id", Mandatory = true, Position = 0, ValueFromPipeline = true)]
+ [Parameter(ParameterSetName = IdParameterSetName, Mandatory = true, Position = 0, ValueFromPipeline = true)]
[ValidateNotNull]
- public int[] Id
- {
- get
- {
- return _id;
- }
-
- set
- {
- _id = value;
- }
- }
-
- private int[] _id;
+ public int[] Id { get; set; }
///
/// Variables of the breakpoints to output.
///
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's OK to use arrays for cmdlet parameters")]
- [Parameter(ParameterSetName = "Variable", Mandatory = true)]
+ [Parameter(ParameterSetName = VariableParameterSetName, Mandatory = true)]
[ValidateNotNull]
- public string[] Variable
- {
- get
- {
- return _variable;
- }
-
- set
- {
- _variable = value;
- }
- }
-
- private string[] _variable;
+ public string[] Variable { get; set; }
///
/// Commands of the breakpoints to output.
///
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's OK to use arrays for cmdlet parameters")]
- [Parameter(ParameterSetName = "Command", Mandatory = true)]
+ [Parameter(ParameterSetName = CommandParameterSetName, Mandatory = true)]
[ValidateNotNull]
- public string[] Command
- {
- get
- {
- return _command;
- }
-
- set
- {
- _command = value;
- }
- }
-
- private string[] _command;
+ public string[] Command { get; set; }
///
/// Commands of the breakpoints to output.
///
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's OK to use arrays for cmdlet parameters")]
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "Type is OK for a cmdlet parameter")]
- [Parameter(ParameterSetName = "Type", Mandatory = true, Position = 0, ValueFromPipeline = true)]
+ [Parameter(ParameterSetName = TypeParameterSetName, Mandatory = true, Position = 0, ValueFromPipeline = true)]
[ValidateNotNull]
- public BreakpointType[] Type
- {
- get
- {
- return _type;
- }
-
- set
- {
- _type = value;
- }
- }
-
- private BreakpointType[] _type;
+ public BreakpointType[] Type { get; set; }
#endregion parameters
+ #region overrides
+
///
/// Remove breakpoints.
///
protected override void ProcessRecord()
{
- List breakpoints = Context.Debugger.GetBreakpoints();
+ List breakpoints = Runspace.Debugger.GetBreakpoints();
- //
// Filter by parameter set
- //
- if (this.ParameterSetName.Equals("Script", StringComparison.OrdinalIgnoreCase))
+ if (ParameterSetName.Equals(LineParameterSetName, StringComparison.OrdinalIgnoreCase))
{
// no filter
}
- else if (this.ParameterSetName.Equals("Id", StringComparison.OrdinalIgnoreCase))
+ else if (ParameterSetName.Equals(IdParameterSetName, StringComparison.OrdinalIgnoreCase))
{
breakpoints = Filter(
breakpoints,
- _id,
+ Id,
delegate (Breakpoint breakpoint, int id)
{
return breakpoint.Id == id;
}
);
}
- else if (this.ParameterSetName.Equals("Command", StringComparison.OrdinalIgnoreCase))
+ else if (ParameterSetName.Equals(CommandParameterSetName, StringComparison.OrdinalIgnoreCase))
{
breakpoints = Filter(
breakpoints,
- _command,
+ Command,
delegate (Breakpoint breakpoint, string command)
{
CommandBreakpoint commandBreakpoint = breakpoint as CommandBreakpoint;
@@ -185,11 +130,11 @@ protected override void ProcessRecord()
return commandBreakpoint.Command.Equals(command, StringComparison.OrdinalIgnoreCase);
});
}
- else if (this.ParameterSetName.Equals("Variable", StringComparison.OrdinalIgnoreCase))
+ else if (ParameterSetName.Equals(VariableParameterSetName, StringComparison.OrdinalIgnoreCase))
{
breakpoints = Filter(
breakpoints,
- _variable,
+ Variable,
delegate (Breakpoint breakpoint, string variable)
{
VariableBreakpoint variableBreakpoint = breakpoint as VariableBreakpoint;
@@ -202,11 +147,11 @@ protected override void ProcessRecord()
return variableBreakpoint.Variable.Equals(variable, StringComparison.OrdinalIgnoreCase);
});
}
- else if (this.ParameterSetName.Equals("Type", StringComparison.OrdinalIgnoreCase))
+ else if (ParameterSetName.Equals(TypeParameterSetName, StringComparison.OrdinalIgnoreCase))
{
breakpoints = Filter(
breakpoints,
- _type,
+ Type,
delegate (Breakpoint breakpoint, BreakpointType type)
{
switch (type)
@@ -244,14 +189,12 @@ protected override void ProcessRecord()
Diagnostics.Assert(false, "Invalid parameter set: {0}", this.ParameterSetName);
}
- //
// Filter by script
- //
- if (_script != null)
+ if (Script != null)
{
breakpoints = Filter(
breakpoints,
- _script,
+ Script,
delegate (Breakpoint breakpoint, string script)
{
if (breakpoint.Script == null)
@@ -267,15 +210,17 @@ protected override void ProcessRecord()
});
}
- //
// Output results
- //
foreach (Breakpoint b in breakpoints)
{
- WriteObject(b);
+ ProcessBreakpoint(b);
}
}
+ #endregion overrides
+
+ #region private methods
+
///
/// Gives the criteria to filter breakpoints.
///
@@ -303,5 +248,7 @@ private List Filter(List input, T[] filter, FilterSel
return output;
}
+
+ #endregion private methods
}
}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointAccessorCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointAccessorCommandBase.cs
new file mode 100644
index 00000000000..88b3cde9acf
--- /dev/null
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointAccessorCommandBase.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Management.Automation;
+using System.Management.Automation.Runspaces;
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// Base class for Get/Set-PSBreakpoint.
+ ///
+ public abstract class PSBreakpointAccessorCommandBase : PSBreakpointCommandBase
+ {
+ #region strings
+
+ internal const string CommandParameterSetName = "Command";
+ internal const string LineParameterSetName = "Line";
+ internal const string VariableParameterSetName = "Variable";
+
+ #endregion strings
+ }
+}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCommandBase.cs
new file mode 100644
index 00000000000..bc01f341861
--- /dev/null
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCommandBase.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Management.Automation;
+using System.Management.Automation.Runspaces;
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// Base class for PSBreakpoint cmdlets.
+ ///
+ public abstract class PSBreakpointCommandBase : PSCmdlet
+ {
+ #region parameters
+
+ ///
+ /// Gets or sets the runspace where the breakpoints will be used.
+ ///
+ [Experimental("Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace", ExperimentAction.Show)]
+ [Parameter]
+ [ValidateNotNull]
+ [Runspace]
+ public virtual Runspace Runspace { get; set; }
+
+ #endregion parameters
+
+ #region overrides
+
+ ///
+ /// Identifies the default runspace.
+ ///
+ protected override void BeginProcessing()
+ {
+ if (Runspace == null)
+ {
+ Runspace = Context.CurrentRunspace;
+ }
+ }
+
+ #endregion overrides
+
+ #region protected methods
+
+ ///
+ /// Write the given breakpoint out to the pipeline, decorated with the runspace instance id if appropriate.
+ ///
+ /// The breakpoint to write to the pipeline.
+ protected virtual void ProcessBreakpoint(Breakpoint breakpoint)
+ {
+ if (Runspace != Context.CurrentRunspace)
+ {
+ var pso = new PSObject(breakpoint);
+ pso.Properties.Add(new PSNoteProperty(RemotingConstants.RunspaceIdNoteProperty, Runspace.InstanceId));
+ WriteObject(pso);
+ }
+ else
+ {
+ WriteObject(breakpoint);
+ }
+ }
+
+ #endregion protected methods
+ }
+}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs
deleted file mode 100644
index cb0e9ea2435..00000000000
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// 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/PSBreakpointUpdaterCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointUpdaterCommandBase.cs
new file mode 100644
index 00000000000..0b1452b0709
--- /dev/null
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointUpdaterCommandBase.cs
@@ -0,0 +1,168 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Management.Automation;
+using System.Management.Automation.Internal;
+using System.Management.Automation.Runspaces;
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// Base class for Enable/Disable/Remove-PSBreakpoint.
+ ///
+ public abstract class PSBreakpointUpdaterCommandBase : PSBreakpointCommandBase
+ {
+ #region strings
+
+ internal const string BreakpointParameterSetName = "Breakpoint";
+ internal const string IdParameterSetName = "Id";
+
+ #endregion strings
+
+ #region parameters
+
+ ///
+ /// Gets or sets the breakpoint to enable.
+ ///
+ [Parameter(ParameterSetName = BreakpointParameterSetName, ValueFromPipeline = true, Position = 0, Mandatory = true)]
+ [ValidateNotNull]
+ public Breakpoint[] Breakpoint { get; set; }
+
+ ///
+ /// Gets or sets the Id of the breakpoint to enable.
+ ///
+ [Parameter(ParameterSetName = IdParameterSetName, ValueFromPipelineByPropertyName = true, Position = 0, Mandatory = true)]
+ [ValidateNotNull]
+ public int[] Id { get; set; }
+
+ ///
+ /// Gets or sets the runspace where the breakpoints will be used.
+ ///
+ [Parameter(ParameterSetName = IdParameterSetName, ValueFromPipelineByPropertyName = true)]
+ [Alias("RunspaceId")]
+ [Runspace]
+ public override Runspace Runspace { get; set; }
+
+ #endregion parameters
+
+ #region overrides
+
+ ///
+ /// Gathers the list of breakpoints to process and calls ProcessBreakpoints.
+ ///
+ protected override void ProcessRecord()
+ {
+ if (ParameterSetName.Equals(BreakpointParameterSetName, StringComparison.OrdinalIgnoreCase))
+ {
+ foreach (Breakpoint breakpoint in Breakpoint)
+ {
+ if (ShouldProcessInternal(breakpoint.ToString()) &&
+ TryGetRunspace(breakpoint))
+ {
+ ProcessBreakpoint(breakpoint);
+ }
+ }
+ }
+ else
+ {
+ Debug.Assert(
+ ParameterSetName.Equals(IdParameterSetName, StringComparison.OrdinalIgnoreCase),
+ $"There should be no other parameter sets besides '{BreakpointParameterSetName}' and '{IdParameterSetName}'.");
+
+ foreach (int id in Id)
+ {
+ Breakpoint breakpoint;
+ if (TryGetBreakpoint(id, out breakpoint) &&
+ ShouldProcessInternal(breakpoint.ToString()))
+ {
+ ProcessBreakpoint(breakpoint);
+ }
+ }
+ }
+ }
+
+ #endregion overrides
+
+ #region private data
+
+ private readonly Dictionary runspaces = new Dictionary();
+
+ #endregion private data
+
+ #region private methods
+
+ private bool TryGetRunspace(Breakpoint breakpoint)
+ {
+ // Breakpoints retrieved from another runspace will have a RunspaceId note property of type Guid on them.
+ var pso = new PSObject(breakpoint);
+ var runspaceInstanceIdProperty = pso.Properties[RemotingConstants.RunspaceIdNoteProperty];
+ if (runspaceInstanceIdProperty == null)
+ {
+ Runspace = Context.CurrentRunspace;
+ return true;
+ }
+
+ Debug.Assert(runspaceInstanceIdProperty.TypeNameOfValue.Equals("System.Guid", StringComparison.OrdinalIgnoreCase), "Instance ids must be GUIDs.");
+
+ var runspaceInstanceId = (Guid)runspaceInstanceIdProperty.Value;
+ if (runspaces.ContainsKey(runspaceInstanceId))
+ {
+ Runspace = runspaces[runspaceInstanceId];
+ return true;
+ }
+
+ var matchingRunspaces = GetRunspaceUtils.GetRunspacesByInstanceId(new[] { runspaceInstanceId });
+ if (matchingRunspaces.Count != 1)
+ {
+ WriteError(
+ new ErrorRecord(
+ new ArgumentException(StringUtil.Format(Debugger.RunspaceInstanceIdNotFound, runspaceInstanceId)),
+ "PSBreakpoint:RunspaceInstanceIdNotFound",
+ ErrorCategory.InvalidArgument,
+ null));
+ return false;
+ }
+
+ Runspace = runspaces[runspaceInstanceId] = matchingRunspaces[0];
+ return true;
+ }
+
+ private bool TryGetBreakpoint(int id, out Breakpoint breakpoint)
+ {
+ breakpoint = Runspace.Debugger.GetBreakpoint(id);
+
+ if (breakpoint == null)
+ {
+ WriteError(
+ new ErrorRecord(
+ new ArgumentException(StringUtil.Format(Debugger.BreakpointIdNotFound, id)),
+ "PSBreakpoint:BreakpointIdNotFound",
+ ErrorCategory.InvalidArgument,
+ null));
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool ShouldProcessInternal(string target)
+ {
+ // ShouldProcess should be called only if the WhatIf or Confirm parameters are passed in explicitly.
+ // It should *not* be called if we are in a nested debug prompt and the current running command was
+ // run with -WhatIf or -Confirm, because this prevents the user from adding/removing breakpoints inside
+ // a debugger stop.
+ if (MyInvocation.BoundParameters.ContainsKey("WhatIf") ||
+ MyInvocation.BoundParameters.ContainsKey("Confirm"))
+ {
+ return ShouldProcess(target);
+ }
+
+ return true;
+ }
+
+ #endregion private methods
+ }
+}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs
index 458b471b322..de324b33fb8 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs
@@ -8,16 +8,20 @@ namespace Microsoft.PowerShell.Commands
///
/// This class implements Remove-PSBreakpoint.
///
- [Cmdlet(VerbsCommon.Remove, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = "Breakpoint",
+ [Cmdlet(VerbsCommon.Remove, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = BreakpointParameterSetName,
HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097134")]
- public class RemovePSBreakpointCommand : PSBreakpointCommandBase
+ public class RemovePSBreakpointCommand : PSBreakpointUpdaterCommandBase
{
+ #region overrides
+
///
/// Removes the given breakpoint.
///
protected override void ProcessBreakpoint(Breakpoint breakpoint)
{
- this.Context.Debugger.RemoveBreakpoint(breakpoint);
+ Runspace.Debugger.RemoveBreakpoint(breakpoint);
}
+
+ #endregion overrides
}
}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RunspaceAttribute.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RunspaceAttribute.cs
new file mode 100644
index 00000000000..0967c9a4985
--- /dev/null
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RunspaceAttribute.cs
@@ -0,0 +1,97 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#pragma warning disable 1634, 1691
+#pragma warning disable 56506
+
+using Microsoft.PowerShell.Commands;
+
+namespace System.Management.Automation.Runspaces
+{
+ ///
+ /// Defines the attribute used to designate a cmdlet parameter as one that
+ /// should accept runspaces.
+ ///
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
+ public sealed class RunspaceAttribute : ArgumentTransformationAttribute
+ {
+ ///
+ /// Transforms the input data to a Runspace.
+ ///
+ ///
+ /// The engine APIs for the context under which the transformation is being
+ /// made.
+ ///
+ ///
+ /// If a string, the transformation uses the input as the runspace name.
+ /// If an int, the transformation uses the input as the runspace ID.
+ /// If a guid, the transformation uses the input as the runspace GUID.
+ /// If already a Runspace, the transform does nothing.
+ ///
+ /// A runspace object representing the inputData.
+ public override object Transform(EngineIntrinsics engineIntrinsics, object inputData)
+ {
+ if (engineIntrinsics?.Host?.UI == null)
+ {
+ throw PSTraceSource.NewArgumentNullException("engineIntrinsics");
+ }
+
+ if (inputData == null)
+ {
+ return null;
+ }
+
+ // Try to coerce the input as a runspace
+ Runspace runspace = LanguagePrimitives.FromObjectAs(inputData);
+ if (runspace != null)
+ {
+ return runspace;
+ }
+
+ // Try to coerce the runspace if the user provided a string, int, or guid
+ switch (inputData)
+ {
+ case string name:
+ var runspacesByName = GetRunspaceUtils.GetRunspacesByName(new[] { name });
+ if (runspacesByName.Count == 1)
+ {
+ return runspacesByName[0];
+ }
+
+ break;
+
+ case int id:
+ var runspacesById = GetRunspaceUtils.GetRunspacesById(new[] { id });
+ if (runspacesById.Count == 1)
+ {
+ return runspacesById[0];
+ }
+
+ break;
+
+ case Guid guid:
+ var runspacesByGuid = GetRunspaceUtils.GetRunspacesByInstanceId(new[] { guid });
+ if (runspacesByGuid.Count == 1)
+ {
+ return runspacesByGuid[0];
+ }
+
+ break;
+
+ default:
+ // Non-convertible type
+ break;
+ }
+
+ // If we couldn't get a single runspace, return the inputData
+ return inputData;
+ }
+
+ ///
+ /// Gets a flag indicating whether or not null optional parameters are transformed.
+ ///
+ public override bool TransformNullOptionalParameters { get { return false; } }
+ }
+}
+
+#pragma warning restore 56506
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 cb4b14a6619..e628da48244 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs
@@ -7,6 +7,7 @@
using System.IO;
using System.Management.Automation;
using System.Management.Automation.Internal;
+using System.Management.Automation.Runspaces;
namespace Microsoft.PowerShell.Commands
{
@@ -14,14 +15,75 @@ namespace Microsoft.PowerShell.Commands
/// This class implements Set-PSBreakpoint command.
///
[Cmdlet(VerbsCommon.Set, "PSBreakpoint", DefaultParameterSetName = LineParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096623")]
- [OutputType(typeof(VariableBreakpoint), typeof(CommandBreakpoint), typeof(LineBreakpoint))]
- public class SetPSBreakpointCommand : PSBreakpointCreationBase
+ [OutputType(typeof(CommandBreakpoint), ParameterSetName = new string[] { CommandParameterSetName })]
+ [OutputType(typeof(LineBreakpoint), ParameterSetName = new string[] { LineParameterSetName })]
+ [OutputType(typeof(VariableBreakpoint), ParameterSetName = new string[] { VariableParameterSetName })]
+ public class SetPSBreakpointCommand : PSBreakpointAccessorCommandBase
{
+ #region parameters
+
+ ///
+ /// Gets or sets the action to take when hitting this breakpoint.
+ ///
+ [Parameter(ParameterSetName = CommandParameterSetName)]
+ [Parameter(ParameterSetName = LineParameterSetName)]
+ [Parameter(ParameterSetName = VariableParameterSetName)]
+ public ScriptBlock Action { get; set; }
+
+ ///
+ /// Gets or sets the column to set the breakpoint on.
+ ///
+ [Parameter(Position = 2, ParameterSetName = LineParameterSetName)]
+ [ValidateRange(1, int.MaxValue)]
+ public int Column { get; set; }
+
+ ///
+ /// Gets or sets the command(s) to set the breakpoint on.
+ ///
+ [Alias("C")]
+ [Parameter(ParameterSetName = CommandParameterSetName, Mandatory = true)]
+ public string[] Command { get; set; }
+
+ ///
+ /// Gets or sets the line to set the breakpoint on.
+ ///
+ [Parameter(Position = 1, ParameterSetName = LineParameterSetName, Mandatory = true)]
+ public int[] Line { get; set; }
+
+ ///
+ /// Gets or sets 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; }
+
+ ///
+ /// Gets or sets the variables to set the breakpoint(s) on.
+ ///
+ [Alias("V")]
+ [Parameter(ParameterSetName = VariableParameterSetName, Mandatory = true)]
+ public string[] Variable { get; set; }
+
+ ///
+ /// Gets or sets the access type for variable breakpoints to break on.
+ ///
+ [Parameter(ParameterSetName = VariableParameterSetName)]
+ public VariableAccessMode Mode { get; set; } = VariableAccessMode.Write;
+
+ #endregion parameters
+
+ #region overrides
+
///
/// Verifies that debugging is supported.
///
protected override void BeginProcessing()
{
+ // Call the base method to ensure Runspace is initialized properly.
+ base.BeginProcessing();
+
// Check whether we are executing on a remote session and if so
// whether the RemoteScript debug option is selected.
if (this.Context.InternalHost.ExternalHost is System.Management.Automation.Remoting.ServerRemoteHost &&
@@ -61,11 +123,49 @@ protected override void BeginProcessing()
protected override void ProcessRecord()
{
// If there is a script, resolve its path
- Collection scripts = 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));
+ }
+ }
+ }
- //
// If it is a command breakpoint...
- //
if (ParameterSetName.Equals(CommandParameterSetName, StringComparison.OrdinalIgnoreCase))
{
for (int i = 0; i < Command.Length; i++)
@@ -74,20 +174,18 @@ protected override void ProcessRecord()
{
foreach (string path in scripts)
{
- WriteObject(
- Context.Debugger.SetCommandBreakpoint(Command[i], Action, path));
+ ProcessBreakpoint(
+ Runspace.Debugger.SetCommandBreakpoint(Command[i], Action, path));
}
}
else
{
- WriteObject(
- Context.Debugger.SetCommandBreakpoint(Command[i], Action, path: null));
+ ProcessBreakpoint(
+ Runspace.Debugger.SetCommandBreakpoint(Command[i], Action, path: null));
}
}
}
- //
// If it is a variable breakpoint...
- //
else if (ParameterSetName.Equals(VariableParameterSetName, StringComparison.OrdinalIgnoreCase))
{
for (int i = 0; i < Variable.Length; i++)
@@ -96,20 +194,18 @@ protected override void ProcessRecord()
{
foreach (string path in scripts)
{
- WriteObject(
- Context.Debugger.SetVariableBreakpoint(Variable[i], Mode, Action, path));
+ ProcessBreakpoint(
+ Runspace.Debugger.SetVariableBreakpoint(Variable[i], Mode, Action, path));
}
}
else
{
- WriteObject(
- Context.Debugger.SetVariableBreakpoint(Variable[i], Mode, Action, path: null));
+ ProcessBreakpoint(
+ Runspace.Debugger.SetVariableBreakpoint(Variable[i], Mode, Action, path: null));
}
}
}
- //
// Else it is the default parameter set (Line breakpoint)...
- //
else
{
Debug.Assert(ParameterSetName.Equals(LineParameterSetName, StringComparison.OrdinalIgnoreCase));
@@ -130,11 +226,13 @@ protected override void ProcessRecord()
foreach (string path in scripts)
{
- WriteObject(
- Context.Debugger.SetLineBreakpoint(path, Line[i], Column, Action));
+ ProcessBreakpoint(
+ Runspace.Debugger.SetLineBreakpoint(path, Line[i], Column, Action));
}
}
}
}
+
+ #endregion overrides
}
}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/Debugger.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/Debugger.resx
index 1c36e4c8af0..043fa4a9320 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/resources/Debugger.resx
+++ b/src/Microsoft.PowerShell.Commands.Utility/resources/Debugger.resx
@@ -174,4 +174,7 @@
Wait-Debugger called on line {0} in {1}.
+
+ A breakpoint associated with another runspace cannot be updated because there is no runspace with instance ID '{0}'.
+
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 33823700f7f..1ef5f3328e0 100644
--- a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
+++ b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
@@ -36,7 +36,7 @@ PrivateData = @{
ExperimentalFeatures = @(
@{
Name = 'Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace'
- Description = 'Enables -BreakAll parameter on Debug-Runspace and Debug-Job cmdlets to allow users to decide if they want PowerShell to break immediately in the current location when they attach a debugger.'
+ Description = 'Enables -BreakAll parameter on Debug-Runspace and Debug-Job cmdlets to allow users to decide if they want PowerShell to break immediately in the current location when they attach a debugger. Enables -Runspace parameter on *-PSBreakpoint cmdlets to support management of breakpoints in another runspace.'
}
)
}
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 cd12b64d034..3b401f039e9 100644
--- a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
+++ b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
@@ -35,7 +35,7 @@ PrivateData = @{
ExperimentalFeatures = @(
@{
Name = 'Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace'
- Description = 'Enables -BreakAll parameter on Debug-Runspace and Debug-Job cmdlets to allow users to decide if they want PowerShell to break immediately in the current location when they attach a debugger.'
+ Description = 'Enables -BreakAll parameter on Debug-Runspace and Debug-Job cmdlets to allow users to decide if they want PowerShell to break immediately in the current location when they attach a debugger. Enables -Runspace parameter on *-PSBreakpoint cmdlets to support management of breakpoints in another runspace.'
}
)
}
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Disable-PSBreakpoint.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Disable-PSBreakpoint.Tests.ps1
new file mode 100644
index 00000000000..ab47ac094f6
--- /dev/null
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Disable-PSBreakpoint.Tests.ps1
@@ -0,0 +1,31 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+Describe 'Disable-PSBreakpoint' -Tags 'CI' {
+
+ BeforeEach {
+ # Set some breakpoints
+ $lineBp = Set-PSBreakpoint -Line ([int]::MaxValue) -Script $PSCommandPath
+ $cmdBp = Set-PSBreakpoint -Command Test-ThisIsNotReallyACommand
+ $varBp = Set-PSBreakpoint -Variable thisIsNotReallyAVariable
+ }
+
+ AfterEach {
+ # Clean up after ourselves
+ Get-PSBreakpoint | Remove-PSBreakpoint
+ }
+
+ It 'Should disable breakpoints using pipeline input by value' {
+ foreach ($bp in $lineBp, $cmdBp, $varBp) {
+ $bp = $bp | Disable-PSBreakpoint -PassThru
+ $bp.Enabled | Should -Not -BeTrue
+ }
+ }
+
+ It 'Should disable breakpoints using pipeline input by property name' {
+ foreach ($bp in $lineBp, $cmdBp, $varBp) {
+ $bp = [pscustomobject]@{ Id = $bp.Id } | Disable-PSBreakpoint -PassThru
+ $bp.Enabled | Should -Not -BeTrue
+ }
+ }
+}
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-PSBreakpoint.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-PSBreakpoint.Tests.ps1
new file mode 100644
index 00000000000..b0550cd0773
--- /dev/null
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Enable-PSBreakpoint.Tests.ps1
@@ -0,0 +1,31 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+Describe 'Enable-PSBreakpoint' -Tags 'CI' {
+
+ BeforeEach {
+ # Set some breakpoints
+ $lineBp = Set-PSBreakpoint -Line ([int]::MaxValue) -Script $PSCommandPath | Disable-PSBreakpoint -PassThru
+ $cmdBp = Set-PSBreakpoint -Command Test-ThisIsNotReallyACommand | Disable-PSBreakpoint -PassThru
+ $varBp = Set-PSBreakpoint -Variable thisIsNotReallyAVariable | Disable-PSBreakpoint -PassThru
+ }
+
+ AfterEach {
+ # Clean up after ourselves
+ Get-PSBreakpoint | Remove-PSBreakpoint
+ }
+
+ It 'Should enable breakpoints using pipeline input by value' {
+ foreach ($bp in $lineBp, $cmdBp, $varBp) {
+ $bp = $bp | Enable-PSBreakpoint -PassThru
+ $bp.Enabled | Should -BeTrue
+ }
+ }
+
+ It 'Should enable breakpoints using pipeline input by property name' {
+ foreach ($bp in $lineBp, $cmdBp, $varBp) {
+ $bp = [pscustomobject]@{ Id = $bp.Id } | Enable-PSBreakpoint -PassThru
+ $bp.Enabled | Should -BeTrue
+ }
+ }
+}
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Remove-PSBreakpoint.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Remove-PSBreakpoint.Tests.ps1
index 549bb97851d..d1b04339167 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Remove-PSBreakpoint.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Remove-PSBreakpoint.Tests.ps1
@@ -1,65 +1,87 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
+
Describe "Remove-PSBreakpoint" -Tags "CI" {
- # Set up test script
- $testScript = Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath assets) -ChildPath psbreakpointtestscript.ps1
+
+ BeforeAll {
+ # Set up test script
+ $testScript = Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath assets) -ChildPath psbreakpointtestscript.ps1
- $script = "`$var = 1
+ $script = "`$var = 1
`$var2 = Get-Command
# this is a comment
Get-Date
"
- $script > $testScript
+ $script > $testScript
+ }
BeforeEach {
- # set some breakpoints
- $line = Set-PSBreakpoint -Line 1,2,3 -Script $testScript
- $command = Set-PSBreakpoint -Command "Get-Date" -Script $testScript
- $variable = Set-PSBreakpoint -Variable var2 -Script $testScript
+ # set some breakpoints
+ $line = Set-PSBreakpoint -Line 1, 2, 3 -Script $testScript
+ $command = Set-PSBreakpoint -Command "Get-Date" -Script $testScript
+ $variable = Set-PSBreakpoint -Variable var2 -Script $testScript
}
Context "Basic Removal Methods Tests" {
- It "Should be able to remove a breakpoint by breakpoint Id" {
- $NumberOfBreakpoints = $(Get-PSBreakpoint).Id.length
- $BreakID = $(Get-PSBreakpoint).Id[0]
- Remove-PSBreakpoint -Id $BreakID
+ It "Should be able to remove a breakpoint by breakpoint Id" {
+ $NumberOfBreakpoints = $(Get-PSBreakpoint).Id.length
+ $BreakID = $(Get-PSBreakpoint).Id[0]
+ Remove-PSBreakpoint -Id $BreakID
- $(Get-PSBreakpoint).Id.length | Should -Be ($NumberOfBreakpoints -1)
- }
+ $(Get-PSBreakpoint).Id.length | Should -Be ($NumberOfBreakpoints - 1)
+ }
- It "Should be able to remove a breakpoint by variable" {
- $NumberOfBreakpoints = $(Get-PSBreakpoint).Id.length
- Remove-PSBreakpoint -Breakpoint $variable
+ It "Should be able to remove a breakpoint by variable" {
+ $NumberOfBreakpoints = $(Get-PSBreakpoint).Id.length
+ Remove-PSBreakpoint -Breakpoint $variable
- $(Get-PSBreakpoint).Id.length | Should -Be ($NumberOfBreakpoints -1)
- }
+ $(Get-PSBreakpoint).Id.length | Should -Be ($NumberOfBreakpoints - 1)
+ }
- It "Should be able to remove a breakpoint by command" {
- $NumberOfBreakpoints = $(Get-PSBreakpoint).Id.length
- Remove-PSBreakpoint -Breakpoint $command
+ It "Should be able to remove a breakpoint by command" {
+ $NumberOfBreakpoints = $(Get-PSBreakpoint).Id.length
+ Remove-PSBreakpoint -Breakpoint $command
- $(Get-PSBreakpoint).Id.length | Should -Be ($NumberOfBreakpoints -1)
- }
+ $(Get-PSBreakpoint).Id.length | Should -Be ($NumberOfBreakpoints - 1)
+ }
- It "Should be able to pipe breakpoint objects to Remove-PSBreakpoint" {
- $NumberOfBreakpoints = $(Get-PSBreakpoint).Id.length
- $variable | Remove-PSBreakpoint
+ It "Should be able to pipe breakpoint objects to Remove-PSBreakpoint" {
+ $NumberOfBreakpoints = $(Get-PSBreakpoint).Id.length
+ $variable | Remove-PSBreakpoint
- $(Get-PSBreakpoint).Id.length | Should -Be ($NumberOfBreakpoints -1)
- }
+ $(Get-PSBreakpoint).Id.length | Should -Be ($NumberOfBreakpoints - 1)
+ }
}
It "Should Remove all breakpoints" {
- $(Get-PSBreakpoint).Id.Length | Should -Not -BeNullOrEmpty
+ $(Get-PSBreakpoint).Id.Length | Should -Not -BeNullOrEmpty
- Get-PSBreakpoint | Remove-PSBreakpoint
+ Get-PSBreakpoint | Remove-PSBreakpoint
- $(Get-PSBreakpoint).Id.Length | Should -Be 0
+ $(Get-PSBreakpoint).Id.Length | Should -Be 0
}
- #Clean up after ourselves
+ It 'Should remove breakpoints using pipeline input by value' {
+ foreach ($bp in $line, $command, $variable) {
+ $bp | Remove-PSBreakpoint
+ }
- Remove-Item $testScript
+ Get-PSBreakpoint | Should -HaveCount 0
+ }
+
+ It 'Should remove breakpoints using pipeline input by property name' {
+ foreach ($bp in $line, $command, $variable) {
+ [pscustomobject]@{ Id = $bp.Id } | Remove-PSBreakpoint
+ }
+
+ Get-PSBreakpoint | Should -HaveCount 0
+ }
+
+ AfterAll {
+ #Clean up after ourselves
+
+ Remove-Item $testScript
+ }
}
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/RunspaceBreakpointManagement.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/RunspaceBreakpointManagement.Tests.ps1
new file mode 100644
index 00000000000..fe131f99117
--- /dev/null
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/RunspaceBreakpointManagement.Tests.ps1
@@ -0,0 +1,207 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+$FeatureEnabled = $EnabledExperimentalFeatures.Contains('Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace')
+
+Describe 'Runspace Breakpoint Unit Tests - Feature-Enabled' -Tags 'CI' {
+
+ BeforeAll {
+ if (!$FeatureEnabled) {
+ Write-Verbose 'Test series skipped. This series of tests requires the experimental feature ''Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace'' to be enabled.' -Verbose
+ $originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
+ $PSDefaultParameterValues['it:skip'] = $true
+ return
+ }
+
+ # Start a job; this will create a runspace in which we can manage breakpoints
+ $job = Start-Job -ScriptBlock {
+ Set-PSBreakpoint -Command Start-Sleep
+ 1..240 | ForEach-Object {
+ Start-Sleep -Milliseconds 250
+ $_
+ Write-Error 'boo'
+ Write-Verbose 'Verbose' -Verbose
+ $DebugPreference = 'Continue'
+ Write-Debug 'Debug'
+ Write-Warning 'Warning'
+ }
+ }
+
+ # Wait for the child job that gets created to hit the breakpoint. This is the
+ # only safe way to know that the job has actually entered a running state and
+ # that the remote runspace is listening for requests.
+ Wait-UntilTrue { $job.ChildJobs.Count -gt 0 -and $job.ChildJobs[0].State -eq 'AtBreakpoint' } -TimeoutInMilliseconds 10000 -IntervalInMilliseconds 250
+
+ # Get the runspace for the running job
+ $jobRunspace = $job.ChildJobs[0].Runspace
+ }
+
+ AfterAll {
+ if (!$FeatureEnabled) {
+ $global:PSDefaultParameterValues = $originalDefaultParameterValues
+ return
+ }
+
+ # Remove the running job forcibly (whether it has finished or not)
+ Remove-Job -Job $job -Force
+ }
+
+ # Test the transformation attribute independently of PSBreakpoint cmdlet tests so that
+ # those tests can focus on scenarios expected to work.
+ Context 'Can transform a runspace name, id, or instanceid into a runspace' {
+
+ function Test-RunspaceTransform {
+ param(
+ [ValidateNotNull()]
+ [Runspace]
+ [System.Management.Automation.Runspaces.Runspace()]
+ $Runspace
+ )
+ $Runspace
+ }
+
+ It 'Transforms a valid runspace name into a runspace' {
+ Test-RunspaceTransform -Runspace $host.Runspace.Name | Should -Be $host.Runspace
+ }
+
+ It 'Transforms a valid runspace ID into a runspace' {
+ Test-RunspaceTransform -Runspace $host.Runspace.Id | Should -Be $host.Runspace
+ }
+
+ It 'Transforms a valid runspace instance ID into a runspace' {
+ Test-RunspaceTransform -Runspace $host.Runspace.InstanceId | Should -Be $host.Runspace
+ }
+
+ It 'Reports an argument transformation error when given invalid input' {
+ $e = { Test-RunspaceTransform -Runspace 'This is not a runspace name' } | Should -Throw -PassThru
+ $e.Exception.GetType().Name | Should -Be 'ParameterBindingArgumentTransformationException'
+ }
+
+ It 'Passes through $null without transforming it' {
+ $e = { Test-RunspaceTransform -Runspace $null } | Should -Throw -PassThru
+ $e.Exception.GetType().Name | Should -Be 'ParameterBindingValidationException'
+ }
+ }
+
+ Context 'Managing breakpoints in the host runspace' {
+
+ It 'Can set breakpoints' {
+ Set-PSBreakpoint -Command Test-ThisCommandDoesNotExist -Runspace $host.Runspace | Should -BeOfType [System.Management.Automation.CommandBreakpoint]
+ }
+
+ It 'Can get breakpoints, and the result breakpoints do not show the runspace id because they are local' {
+ foreach ($bp in Get-PSBreakpoint -Runspace $host.Runspace) {
+ Get-Member -InputObject $bp -Name RunspaceId -ErrorAction Ignore | Should -Be $null
+ }
+ }
+
+ It 'Can disable breakpoints in a pipeline' {
+ foreach ($bp in Get-PSBreakpoint -Runspace $host.Runspace | Disable-PSBreakpoint -PassThru) {
+ $bp.Enabled | Should -BeFalse
+ # This ensures we're working in the right runspace
+ Get-Member -InputObject $bp -Name RunspaceId -ErrorAction Ignore | Should -Be $null
+ }
+ }
+
+ It 'Can enable breakpoints in a pipeline' {
+ foreach ($bp in Get-PSBreakpoint -Runspace $host.Runspace | Enable-PSBreakpoint -PassThru) {
+ $bp.Enabled | Should -BeTrue
+ # This ensures we're working in the right runspace
+ Get-Member -InputObject $bp -Name RunspaceId -ErrorAction Ignore | Should -Be $null
+ }
+ }
+
+ It 'Can disable breakpoints by id' {
+ foreach ($bp in Get-PSBreakpoint -Runspace $host.Runspace) {
+ $bp = Disable-PSBreakpoint -Id $bp.Id -Runspace $host.Runspace -PassThru
+ $bp.Enabled | Should -BeFalse
+ # This ensures we're working in the right runspace
+ Get-Member -InputObject $bp -Name RunspaceId -ErrorAction Ignore | Should -Be $null
+ }
+ }
+
+ It 'Can enable breakpoints by id' {
+ foreach ($bp in Get-PSBreakpoint -Runspace $host.Runspace) {
+ $bp = Enable-PSBreakpoint -Id $bp.Id -Runspace $host.Runspace -PassThru
+ $bp.Enabled | Should -BeTrue
+ # This ensures we're working in the right runspace
+ Get-Member -InputObject $bp -Name RunspaceId -ErrorAction Ignore | Should -Be $null
+ }
+ }
+
+ It 'Can remove breakpoints' {
+ Get-PSBreakpoint -Runspace $host.Runspace | Remove-PSBreakpoint
+ Get-PSBreakpoint -Runspace $host.Runspace | Should -BeNull
+ }
+ }
+
+ Context 'Managing breakpoints in a remote runspace' {
+
+ AfterAll {
+ # Get rid of any breakpoints that were created in the default runspace.
+ # This is necessary due to a known bug that causes breakpoints with the
+ # same id to be created or updated in the default runspace.
+ Get-PSBreakpoint | Remove-PSBreakpoint
+ }
+
+ It 'Can set breakpoints' {
+ Set-PSBreakpoint -Command Write-Verbose -Action { break } -Runspace $jobRunspace | Should -BeOfType [System.Management.Automation.CommandBreakpoint]
+ Set-PSBreakpoint -Variable DebugPreference -Mode ReadWrite -Action { break } -Runspace $jobRunspace | Should -BeOfType [System.Management.Automation.VariableBreakpoint]
+ Set-PSBreakpoint -Script $PSCommandPath -Line 1 -Column 1 -Action { break } -Runspace $jobRunspace | Should -BeOfType [System.Management.Automation.LineBreakpoint]
+ }
+
+ It 'Can get breakpoints, and the result breakpoints show the remote runspace id' {
+ foreach ($bp in Get-PSBreakpoint -Runspace $jobRunspace) {
+ $bp.RunspaceId | Should -Be $jobRunspace.InstanceId
+ }
+ }
+
+ It 'Breakpoints are triggered by the remote debugger' {
+ $startTime = [DateTime]::UtcNow
+ $maxTimeToWait = [TimeSpan]'00:00:20'
+ while ($job.State -ne 'AtBreakpoint' -and ([DateTime]::UtcNow - $startTime) -lt $maxTimeToWait) {
+ Start-Sleep -Milliseconds 100 # Give the job a bit of time to hit a breakpoint
+ }
+ $job.State | Should -Be 'AtBreakpoint'
+ }
+
+ It 'Can disable breakpoints in a pipeline' {
+ foreach ($bp in Get-PSBreakpoint -Runspace $jobRunspace | Disable-PSBreakpoint -PassThru) {
+ $bp.Enabled | Should -BeFalse
+ # This ensures we're working in the right runspace
+ $bp.RunspaceId | Should -Be $jobRunspace.InstanceId
+ }
+ }
+
+ It 'Can enable breakpoints in a pipeline' {
+ foreach ($bp in Get-PSBreakpoint -Runspace $jobRunspace | Enable-PSBreakpoint -PassThru) {
+ $bp.Enabled | Should -BeTrue
+ # This ensures we're working in the right runspace
+ $bp.RunspaceId | Should -Be $jobRunspace.InstanceId
+ }
+ }
+
+ It 'Can disable breakpoints by id' {
+ foreach ($bp in Get-PSBreakpoint -Runspace $jobRunspace) {
+ $bp = Disable-PSBreakpoint -Id $bp.Id -Runspace $jobRunspace -PassThru
+ $bp.Enabled | Should -BeFalse
+ # This ensures we're working in the right runspace
+ $bp.RunspaceId | Should -Be $jobRunspace.InstanceId
+ }
+ }
+
+ It 'Can enable breakpoints in a pipeline' {
+ foreach ($bp in Get-PSBreakpoint -Runspace $jobRunspace) {
+ $bp = Enable-PSBreakpoint -Id $bp.Id -Runspace $jobRunspace -PassThru
+ $bp.Enabled | Should -BeTrue
+ # This ensures we're working in the right runspace
+ $bp.RunspaceId | Should -Be $jobRunspace.InstanceId
+ }
+ }
+
+ It 'Can remove breakpoints' {
+ Get-PSBreakpoint -Runspace $jobRunspace | Remove-PSBreakpoint
+ Get-PSBreakpoint -Runspace $jobRunspace | Should -BeNull
+ }
+ }
+}
diff --git a/test/tools/TestMetadata.json b/test/tools/TestMetadata.json
index fede31d68ed..db65e6ec212 100644
--- a/test/tools/TestMetadata.json
+++ b/test/tools/TestMetadata.json
@@ -5,6 +5,7 @@
"test/powershell/Language/Operators/NullConditional.Tests.ps1",
"test/powershell/Language/Parser/Parsing.Tests.ps1",
"test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1" ],
- "PSCultureInvariantReplaceOperator": [ "test/powershell/Language/Operators/ReplaceOperator.Tests.ps1" ]
+ "PSCultureInvariantReplaceOperator": [ "test/powershell/Language/Operators/ReplaceOperator.Tests.ps1" ],
+ "Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace": [ "test/powershell/Modules/Microsoft.PowerShell.Utility/RunspaceBreakpointManagement.Tests.ps1" ]
}
}