8000 Implementation for Invoke-Command step-in remote debugging by PaulHigin · Pull Request #3015 · PowerShell/PowerShell · GitHub
[go: up one dir, main page]

Skip to content

Implementation for Invoke-Command step-in remote debugging #3015

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 1, 2017
Merged

Implementation for Invoke-Command step-in remote debugging #3015

merged 8 commits into from
Mar 1, 2017

Conversation

PaulHigin
Copy link
Contributor

These changes provide the ability to debug remote running scripts started with the Invoke-Command cmdlet. The design is event based and provides new public events that allow subscribers to be notified when an Invoke-Command remote session is ready for debugging. Since Invoke-Command allows running scripts on multiple targets at once (fan-out) the notification event is raised for each remote session as it becomes ready for debugging. The subscriber to these events will be a script debugger implementation (such as PowerShell console, ISE, or VSCode) and will handle all debugging details such as simultaneously debugging multiple remote sessions at once in separate windows.

But these changes also include an internal implementation which is used by default if host debuggers don't want to handle the debugging details. This internal implementation is what PowerShell console, ISE uses so they can have this new behavior without having to modify their debugger implementations. The internal implementation serializes each remote session of Invoke-Command so that they can be debugged one at a time. The remote session debugger is "pushed" onto the internal debugger stack so that debugging transitions to the remote session. Existing debugging commands work so that the "quit" debugging command will stop the current remote session script from running and allow the next remote session to be debugged. Similarly the "continue" debugging command allows the script to continue running outside step mode and again go to the next remote session for debugging. The "stepout" debugging command steps out of all Invoke-Command remote sessions and lets the script continue to run for each remote session in parallel as they are normally run.

The purpose of Invoke-Command step-in remote debugging is allow seamless debugging of a local script that calls Invoke-Command on remote targets. But there is also a new Invoke-Command "-RemoteDebug" parameter that lets you Invoke-Command on the command line and have it drop directly into the debugger.

An example from the PowerShell command line looks like this:

PS C:\> C:\TestICM.ps1
Entering debug mode. Use h or ? for help.

Hit Command breakpoint on 'Invoke-Command'

At C:\TestICM.ps1:2 char:1
+ Invoke-Command -cn $computerName,paulhig-3 -File c:\LinuxScript.ps1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[DBG]: PS C:\>> list

    1:  $computerName = "localhost"
    2:* Invoke-Command -cn $computerName,paulhig-3 -File c:\LinuxScript.ps1
    3:  "Test Complete!"

[DBG]: PS C:\>> stepin
At line:1 char:1
+ Write-Output "Running script on Linux!"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[paulhig-3]:[DBG]: [Process:14072]: [Runspace5]: PS C:\Users\paulhi\Documents>

Notice that the debugger "stepin" command transitioned from local script debugging to debugging the remote session on computer "paulhig-3", as can be seen by the change in the debugger prompt.

You can also do this from the command line to drop directly into the debugger

Invoke-Command -cn localhost -Script  $scriptblock -RemoteDebug

These changes also remove an old behavior that was incompatible with this new step-in feature. Previously if a remote session running script hit a break point it would stop in the debugger and go to the "disconnected session" state. This was to allow the user to reconnect using Enter-PSSession and then interactively debug the remote session script. This behavior has been removed and now the user needs to attach a debugger using the newer Debug-Runspace cmdlet.

Minor updates to test.

Added It:Skip for Windows only test.

More CR changes

Fix for SSH remoting Ctrl+C operation

Fixed comment

Clean up PSObject base

Implementation for Invoke-Command step-in remote debugging

Remove GP cache changes

Added test

Fixed potential null reference error

Fixed ad hoc bug

Added debugger ready wait in debugger process loop

Stability improvements

Improve step out behavior

Added event raised when internal runsapce debug processing ends

Added consistent runspace clean up

Some clean up
@PowerShellTeam PowerShellTeam added the Review - Needed The PR is being reviewed label Jan 19, 2017
return
}

$sb = [scriptblock]::Create(@'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The below code should be moved to else

if (!$IsWindows)  
        {  
            $originalDefaultParameterValues = $PSDefaultParameterValues.Clone()  
            $PSDefaultParameterValues["it:skip"] = $true  
} else {
    $sb = [scriptblock]::Create(@'
        "Hello!" 
'@)

Add-Type -TypeDefinition $typeDef -ReferencedAssemblies "System.Globalization","System.Management.Automation" 

...
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#Closed

if (!$IsWindows)
{
$global:PSDefaultParameterValues = $originalDefaultParameterValues
return
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Th same - 'return' should be removed and below code should be moved to else .

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#Closed

if ($remoteSession -ne $null) { Remove-PSSession $remoteSession -ErrorAction SilentlyContinue }
}

It "Verifies that asynchronous Invoke-Command -RemoteDebug is ignored" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better:

It "Verifies that asynchronous 'Invoke-Command -RemoteDebug' is ignored" { 

It would be good to fix headers below similarly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#Closed

public override void StopProcessCommand() { }
}
}
'@
Copy link
Collaborator
@iSazonov iSazonov Jan 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we skip all test for non-Windows the above code should be moved in

if ($IsWindows) {
   # here
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#Closed

##
## PowerShell Invoke-Command -RemoteDebug Tests
##

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know whether this work on IoT and Windows Core Server? If not, then we need to change the 'skipping'.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test works on Windows Core Server. I don't know about IoT

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can point you to an IoT VM (internally) so you can at least run a smoke test there. IoT and Nano Server are very similar environments, so I would expect it to work there as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe tests are currently run on IoT or Nano Server. So I feel this shouldn't block the PR merge.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment doesn't block the PR merge.
But I do think for features like this one, we should at least verify if it works on NanoServer, because it adds great value to customers and thus is very possible to be cherry-picked to the NanoServer in-box powershell.

##
## PowerShell Invoke-Command -RemoteDebug Tests
##

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know whether this work on IoT and Windows Core Server? If not, then we need to change the 'skipping'.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test works on Windows Core Server. I don't know about IoT

/// <summary>
/// The runspace to process
/// </summary>
public Runspace Runspace
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 lines for a trivial property is too verbose - please use one line.

/// When set to true this will cause PowerShell to handle this runspace debug session internally through its
/// internal script debugger
/// </summary>
public bool HandleInternally
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this was an internal api, I wouldn't care about the name as much, but it's public, so can you think of a better term than Internally to prevent any ambiguity as to what Internally actually refers to?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to "UseDefaultProcessing"

/// <summary>
/// Event raised when a runspace debugger needs breakpoint processing
/// </summary>
public event EventHandler<ProcessRunspaceDebugEventArgs> ProcessRunspaceDebug;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm finding Process confusing here - it's not the noun, but it seems like maybe RunspaceDebugProcessingStarted, RunspaceDebugProcessingCompleted, and RunspaceDebugProcessingCancelled might be clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit confusing. I'll change "ProcessRunspaceDebugEnd" to "RunspaceDebugProcessingCompleted" since that is much better.

The other two should probably be "StartRunspaceDebugProcessing" and "CancelRunspaceDebugProcessing".

The reason these two events exist is to give a host the chance to debug these remote sessions directly , rather than let PowerShell provide a default debug experience. For example VSCode may want to show multiple windows to debug multiple remote sessions simultaneously instead of using PowerShell default processing which debugs each session serially.

/// <summary>
/// Event raised when debugging session is over and runspace debuggers queued for processing should be released
/// </summary>
public event EventHandler<EventArgs> CancelProcessRunspaceDebug;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment suggests this event is raised under normal processing but Cancel suggests termination that might not be normal.

/// <param name="runspace">Runspace to debug</param>
internal override void QueueRunspaceForDebug(Runspace runspace)
{
if (runspace == null) { throw new PSArgumentNullException("runspace"); }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need argument validation here? The method is internal, so it seems unnecessary - PSArgumentNullException or NullReferenceException are equally informative.

public void PushRunspace(Runspace runspace) { }
public void PopRunspace() { }
public bool IsRunspacePushed { get { return false; } }
public Runspace Runspace { get { return _runspace; } private set { _runspace = value; } }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just test code - but won't this return the wrong runspace if/when PushRunspace is used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test host Push/Pop methods are not needed for the test. Only the Runspace property is needed to provide access to the host runspace.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid incorrect code where possible - it has a nasty habit of getting copied.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not incorrect code. The methods are simply unnecessary for the test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are trying to apply the same standard between test code and product code.

If this code were copied or reused beyond this test (and people definitely do copy our test code - maybe treating it like a sample) - what change would you suggest to avoid problems?

Maybe throw from push/pop - knowing that Runspace does not work as intended with push/pop?
At a minimum, I'd say you would add a comment pointing out the flaw when the code is used elsewhere.

if (!$IsWindows)
{
$originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
$PSDefaultParameterValues["it:skip"] = $true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature only works on Windows? Is that a temporary situation (I hope), then shouldn't this be Pending, not Skip?


It "Verifies that asynchronous 'Invoke-Command -RemoteDebug' is ignored" {

$ps.AddCommand("Invoke-Command").AddParameter("Session", $remoteSession).AddParameter("ScriptBlock", $sb).AddParameter("RemoteDebug", $true).AddParameter("AsJob", $true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines like this are hard to read if on one line. You can move method calls to the next line like this:

    $ps.AddCommand("Invoke-Command").
        AddParameter("Session", $remoteSession).
        AddParameter ...

#region Runspace Debug Processing

/// <summary>
/// RaiseProcessRunspaceDebugEvent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use helpful xml doc comments or use a simple:

/// <summary/>

to silence the compiler warnings.

@@ -2938,6 +3189,10 @@ private Debugger PopActiveDebugger()
_steppingMode = SteppingMode.StepIn;
_overOrOutFrame = _nestedRunningFrame;
_nestedRunningFrame = null;
if (_lastActiveDebuggerAction == DebuggerResumeAction.StepOut)
{
CancelDebuggerProcessingAsNeeded(poppedDebugger);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I'm finding the verb used confusing. Here stepping out is normal - so do we really mean to "cancel" debugger processing?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To elaborate, I expect potentially different cleanup code if something completes normally versus terminated (cancelled) for some other reason. Maybe they aren't different in this code, but that's how I'm thinking about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CancelDebuggerProcessingAsNeeded() method cancels debugger processing only when stepping out of the current call stack, which means stepping out of the currently debugged remote session. This behavior was debated but we finally decided to step out of all remote sessions from the Invoke-Command, and stop in the local script that called Invoke-Command.

We decided to do this because stepping out of one remote session and into another results in confusion because you are debugging one remote session while one or more are running in parallel spewing output into your debug session console. So "step-out" at the top of the stack means to step out of the Invoke-Command remote debugging and back to the local script.

I'll add a comment to try to make this clear.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this some more I have changed my mind. Right now "step-out" aborts the Invoke-Command debugging and continues with local script debugging, which I thought was the right behavior for some reason. But this is different behavior from how "continue" and "quit" debugger commands work. I think "step-out" should be consistent and I'll remove this cancel code.

if (-not (Get-Module TestRemoting -ErrorAction SilentlyContinue))
{
$remotingModule = Join-Path $PSScriptRoot "../Common/TestRemoting.psm1"
Import-Module $remotingModule
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to check Get-Module TestRemoting
This work well:

Import-Module $remotingModule  -ErrorAction SilentlyContinue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.


It "Verifies that s 179B ynchronous 'Invoke-Command -RemoteDebug' invokes debugger" {

$ps.Commands.Clear()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If somebody add a new test with AddCommand this may break the existing tests.
It would be safer to move $ps.Commands.Clear() in AfterEach {}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Thanks.

@iSazonov
Copy link
Collaborator

@PaulHigin Whether to increase the number of files with similar tests?
If so, then perhaps we should move TestDebugger to TestRemoting.psm1 and merge DummyHost with TestHostCS in TestHostCS.psm1.

@PaulHigin
Copy link
Contributor Author

@iSazonov I feel it is not necessary at this point. If in the future more tests rely on these classes then the code can be refactored.

@iSazonov
Copy link
Collaborator

@PaulHigin Thanks for clarification! Clear.

@mirichmo
Copy link
Member

@lzybkr and @iSazonov - Do you have any additional comments, outstanding questions, or discussion points? Is this PR ready to go?

@iSazonov
Copy link
Collaborator

@mirichmo With regard to the tests the minor question remains open about IoT #3015 (review)

PaulHigin The test works on Windows Core Server. I don't know about IoT

@PaulHigin PaulHigin assigned daxian-dbw and unassigned mirichmo Mar 1, 2017
@PaulHigin
Copy link
Contributor Author

@daxian-dbw This PR has been languishing for weeks. Is there any reason why it cannot be merged? It seems to me that any minor issues can be addressed as they come up. Otherwise I am Ok abandoning this PR since this a debugging feature requested internally.
Thanks!

@daviwil
Copy link
Contributor
daviwil commented Mar 1, 2017

I think this should be merged, it adds value for all our users, not just the internal ones. This functionality will light up for users in VS Code once it's released in PowerShell Core.

@lzybkr
Copy link
Contributor
lzybkr commented Mar 1, 2017

Most of my feedback was addressed.

I won't block merging, but do think my feedback on the host test code should be addressed - I expanded on my thinking inline.

@daxian-dbw
Copy link
Member

I opened #3236 to capture 2 open comments in the review. Given that, I'm merging this PR.

@daxian-dbw daxian-dbw merged commit b4cb5e9 into PowerShell:master Mar 1, 2017
@PaulHigin PaulHigin deleted the ICMRemoteDebug branch March 6, 2017 19:04
@iSazonov iSazonov removed the Review - Needed The PR is being reviewed label Mar 27, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants
0