2025-11-28 00:35:46 +09:00

316 lines
13 KiB
C#

// <copyright file="DebuggerSample.cs" company="Microsoft Corporation">
// Copyright (c) 2013 Microsoft Corporation. All rights reserved.
// </copyright>
// DISCLAIMER OF WARRANTY: The software is licensed “as-is.” You
// bear the risk of using it. Microsoft gives no express warranties,
// guarantees or conditions. You may have additional consumer rights
// under your local laws which this agreement cannot change. To the extent
// permitted under your local laws, Microsoft excludes the implied warranties
// of merchantability, fitness for a particular purpose and non-infringement.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Threading.Tasks;
namespace PSDebuggerSample
{
public class DebuggerSample
{
private static void Main(string[] args)
{
Console.Title = "PowerShell Debugger Sample Application";
Console.WriteLine(" Windows PowerShell Debugger Application Sample");
Console.WriteLine(" ==============================================");
Console.WriteLine();
Console.WriteLine("This is a simple example of using the PowerShell Debugger API to set");
Console.WriteLine("breakpoints in a script file, run the script and handle debugger events.");
Console.WriteLine();
// Create DebuggerSample class instance and begin debugging sample script.
var debuggerSample = new DebuggerSample();
debuggerSample.Run();
}
#region Private members
/// <summary>
/// Simple sample script that writes output, sets variables, and
/// calls a workflow function.
/// </summary>
private string _script = @"
workflow WorkflowF1
{
[CmdletBinding()]
param(
[String]
$Title
)
Write-Output -InputObject $Title
$PSProcess = Get-Process -Name PowerShell*
$CurrentDate = [DateTime]::Now
Write-Output $CurrentDate
foreach ($item in $PSProcess)
{
Write-Output -InputObject $item
}
}
Write-Output ""Beginning Script""
$WorkflowTitle = ""MyWorkflowTitle""
WorkflowF1 -Title $WorkflowTitle
Write-Output ""Script Completed""
";
private bool _showHelpMessage;
/// <summary>
/// Collection of all breakpoints on the runspace debugger.
/// </summary>
private Dictionary<int, Breakpoint> _breakPoints = new Dictionary<int, Breakpoint>();
#endregion
#region Public Methods
/// <summary>
/// Method to run the sample script and handle debugger events.
/// </summary>
public void Run()
{
Console.WriteLine("Starting PowerShell Debugger Sample");
Console.WriteLine();
// Create sample script file to debug.
string fileName = "PowerShellSDKDebuggerSample.ps1";
string filePath = System.IO.Path.Combine(Environment.CurrentDirectory, fileName);
System.IO.File.WriteAllText(filePath, _script);
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
// Open runspace and set debug mode to debug PowerShell scripts and
// Workflow scripts. PowerShell script debugging is enabled by default,
// Workflow debugging is opt-in.
runspace.Open();
runspace.Debugger.SetDebugMode(DebugModes.LocalScript);
using (PowerShell powerShell = PowerShell.Create())
{
powerShell.Runspace = runspace;
// Set breakpoint update event handler. The breakpoint update event is
// raised whenever a break point is added, removed, enabled, or disabled.
// This event is generally used to display current breakpoint information.
runspace.Debugger.BreakpointUpdated += HandlerBreakpointUpdatedEvent;
// Set debugger stop event handler. The debugger stop event is raised
// whenever a breakpoint is hit, or for each script execution sequence point
// when the debugger is in step mode. The debugger remains stopped at the
// current execution location until the event handler returns. When the
// event handler returns it should set the DebuggerStopEventArgs.ResumeAction
// to indicate how the debugger should proceed:
// - Continue Continue execution until next breakpoint is hit.
// - StepInto Step into function.
// - StepOut Step out of function.
// - StepOver Step over function.
// - Stop Stop debugging.
runspace.Debugger.DebuggerStop += HandleDebuggerStopEvent;
// Set initial breakpoint on line 10 of script. This breakpoint
// will be in the script workflow function.
powerShell.AddCommand("Set-PSBreakpoint").AddParameter("Script", filePath).AddParameter("Line", 10);
powerShell.Invoke();
Console.WriteLine("Starting script file: " + filePath);
Console.WriteLine();
// Run script file.
powerShell.Commands.Clear();
powerShell.AddScript(filePath).AddCommand("Out-String").AddParameter("Stream", true);
var scriptOutput = new PSDataCollection<PSObject>();
scriptOutput.DataAdded += (sender, args) =>
{
// Stream script output to console.
foreach (var item in scriptOutput.ReadAll())
{
Console.WriteLine(item);
}
};
powerShell.Invoke<PSObject>(null, scriptOutput);
}
}
// Delete the sample script file.
if (System.IO.File.Exists(filePath))
{
System.IO.File.Delete(filePath);
}
Console.WriteLine("PowerShell Debugger Sample Complete");
Console.WriteLine();
Console.WriteLine("Press any key to exit.");
Console.ReadKey(true);
}
#endregion
#region Private Methods
// Method to handle the Debugger DebuggerStop event.
// The debugger will remain in debugger stop mode until this event
// handler returns, at which time DebuggerStopEventArgs.ResumeAction should
// be set to indicate how the debugger should proceed (Continue, StepInto,
// StepOut, StepOver, Stop).
// This handler should run a REPL (Read Evaluate Print Loop) to allow user
// to investigate the state of script execution, by processing user commands
// with the Debugger.ProcessCommand method. If a user command releases the
// debugger then the DebuggerStopEventArgs.ResumeAction is set and this
// handler returns.
private void HandleDebuggerStopEvent(object sender, DebuggerStopEventArgs args)
{
Debugger debugger = sender as Debugger;
DebuggerResumeAction? resumeAction = null;
// Display messages pertaining to this debugger stop.
WriteDebuggerStopMessages(args);
// Simple REPL (Read Evaluate Print Loop) to process
// Debugger commands.
while (resumeAction == null)
{
// Write debug prompt.
Console.Write("[DBG] PS >> ");
string command = Console.ReadLine();
Console.WriteLine();
// Stream output from command processing to console.
var output = new PSDataCollection<PSObject>();
output.DataAdded += (dSender, dArgs) =>
{
foreach (var item in output.ReadAll())
{
Console.WriteLine(item);
}
};
// Process command.
// The Debugger.ProcesCommand method will parse and handle debugger specific
// commands such as 'h' (help), 'list', 'stepover', etc. If the command is
// not specific to the debugger then it will be evaluated as a PowerShell
// command or script. The returned DebuggerCommandResults object will indicate
// whether the command was evaluated by the debugger and if the debugger should
// be released with a specific resume action.
PSCommand psCommand = new PSCommand();
psCommand.AddScript(command).AddCommand("Out-String").AddParameter("Stream", true);
DebuggerCommandResults results = debugger.ProcessCommand(psCommand, output);
if (results.ResumeAction != null)
{
resumeAction = results.ResumeAction;
}
}
// Return from event handler with user resume action.
args.ResumeAction = resumeAction.Value;
}
// Method to handle the Debugger BreakpointUpdated event.
// This method will display the current breakpoint change and maintain a
// collection of all current breakpoints.
private void HandlerBreakpointUpdatedEvent(object sender, BreakpointUpdatedEventArgs args)
{
// Write message to console.
ConsoleColor saveFGColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine();
switch (args.UpdateType)
{
case BreakpointUpdateType.Set:
if (!_breakPoints.ContainsKey(args.Breakpoint.Id))
{
_breakPoints.Add(args.Breakpoint.Id, args.Breakpoint);
}
Console.WriteLine("Breakpoint created:");
break;
case BreakpointUpdateType.Removed:
_breakPoints.Remove(args.Breakpoint.Id);
Console.WriteLine("Breakpoint removed:");
break;
case BreakpointUpdateType.Enabled:
Console.WriteLine("Breakpoint enabled:");
break;
case BreakpointUpdateType.Disabled:
Console.WriteLine("Breakpoint disabled:");
break;
}
Console.WriteLine(args.Breakpoint.ToString());
Console.WriteLine();
Console.ForegroundColor = saveFGColor;
}
/// <summary>
/// Helper method to write debugger stop messages.
/// </summary>
/// <param name="args">DebuggerStopEventArgs for current debugger stop</param>
private void WriteDebuggerStopMessages(DebuggerStopEventArgs args)
{
// Write debugger stop information in yellow.
ConsoleColor saveFGColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
// Show help message only once.
if (!_showHelpMessage)
{
Console.WriteLine("Entering debug mode. Type 'h' to get help.");
Console.WriteLine();
_showHelpMessage = true;
}
// Break point summary message.
string breakPointMsg = String.Format(System.Globalization.CultureInfo.InvariantCulture,
"Breakpoints: Enabled {0}, Disabled {1}",
(_breakPoints.Values.Where<Breakpoint>((bp) => { return bp.Enabled; })).Count(),
(_breakPoints.Values.Where<Breakpoint>((bp) => { return !bp.Enabled; })).Count());
Console.WriteLine(breakPointMsg);
Console.WriteLine();
// Breakpoint stop information. Writes all breakpoints that
// pertain to this debugger execution stop point.
if (args.Breakpoints.Count > 0)
{
Console.WriteLine("Debugger hit breakpoint on:");
foreach (var breakPoint in args.Breakpoints)
{
Console.WriteLine(breakPoint.ToString());
}
Console.WriteLine();
}
// Script position stop information.
// This writes the InvocationInfo position message if
// there is one.
if (args.InvocationInfo != null)
{
Console.WriteLine(args.InvocationInfo.PositionMessage);
Console.WriteLine();
}
Console.ForegroundColor = saveFGColor;
}
#endregion
}
}