public static void Patch(int version, EngineIntrinsics context, PSHostUserInterface ui) { Clear(); var uiFieldName = version >= 7 ? "_externalUI" : "externalUI"; // we get InternalHostUserInterface, grab external ui from that and replace it with ours var externalUIField = ui.GetType().GetField(uiFieldName, BindingFlags.Instance | BindingFlags.NonPublic); var externalUI = (PSHostUserInterface)externalUIField.GetValue(ui); // replace it with out patched up UI that writes to profiler on debug externalUIField.SetValue(ui, new ProfilerUI(externalUI)); ResetUI = () => { externalUIField.SetValue(ui, externalUI); }; // getting MethodInfo of context._context.Debugger.TraceLine var bf = BindingFlags.NonPublic | BindingFlags.Instance; var contextInternal = context.GetType().GetField("_context", bf).GetValue(context); var debugger = contextInternal.GetType().GetProperty("Debugger", bf).GetValue(contextInternal); var debuggerType = debugger.GetType(); var callStackField = debuggerType.GetField("_callStack", BindingFlags.Instance | BindingFlags.NonPublic); var _callStack = callStackField.GetValue(debugger); var callStackType = _callStack.GetType(); var countProperty = callStackType.GetProperty("Count", BindingFlags.Instance | BindingFlags.NonPublic); var getCount = countProperty.GetMethod; var empty = new object[0]; var stack = callStackField.GetValue(debugger); var initialLevel = (int)getCount.Invoke(stack, empty); var lastFunctionContextMethod = callStackType.GetMethod("LastFunctionContext", BindingFlags.Instance | BindingFlags.NonPublic); object functionContext1 = lastFunctionContextMethod.Invoke(callStackField.GetValue(debugger), empty); var functionContextType = functionContext1.GetType(); var scriptBlockField = functionContextType.GetField("_scriptBlock", BindingFlags.Instance | BindingFlags.NonPublic); var currentPositionProperty = functionContextType.GetProperty("CurrentPosition", BindingFlags.Instance | BindingFlags.NonPublic); var scriptBlock1 = (ScriptBlock)scriptBlockField.GetValue(functionContext1); var extent1 = (IScriptExtent)currentPositionProperty.GetValue(functionContext1); TraceLine = () => { var callStack = callStackField.GetValue(debugger); var level = (int)getCount.Invoke(callStack, empty) - initialLevel; object functionContext = lastFunctionContextMethod.Invoke(callStack, empty); var scriptBlock = (ScriptBlock)scriptBlockField.GetValue(functionContext); var extent = (IScriptExtent)currentPositionProperty.GetValue(functionContext); Trace(extent, scriptBlock, level); }; // Add another event to the top apart from the scriptblock invocation // in Trace-ScriptInternal, this makes it more consistently work on first // run. Without this, the triggering line sometimes does not show up as 99.9% TraceLine(); }
/// <summary> /// Enable tracing by patch the current session by replacing the UI host with another that triggers tracing on every statement. Set-PSDebug -Trace 1 needs to be called before this, and Set-PSDebug -Off needs to be called after Unpatch. /// </summary> /// <param name="powerShellVersion">Major PSVersion that is used `$PSVersionTable.PSVersion.Major`</param> /// <param name="context">ExecutionContext to be used `$ExecutionContext`</param> /// <param name="ui">UIHost to be replaced. `$host.UI`</param> /// <param name="tracer">The tracer to be used. For example ProfilerTracer.</param> public static void Patch(int powerShellVersion, EngineIntrinsics context, PSHostUserInterface ui, ITracer tracer) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (ui is null) { throw new ArgumentNullException(nameof(ui)); } Tracer1 = tracer ?? throw new ArgumentNullException(nameof(tracer)); var uiFieldName = powerShellVersion >= 6 ? "_externalUI" : "externalUI"; // we get InternalHostUserInterface, grab external ui from that and replace it with ours var externalUIField = ui.GetType().GetField(uiFieldName, BindingFlags.Instance | BindingFlags.NonPublic); var externalUI = (PSHostUserInterface)externalUIField.GetValue(ui); // replace it with out patched up UI that writes to profiler on debug externalUIField.SetValue(ui, new TracerHostUI(externalUI, (message) => TraceLine(message, false))); ResetUI = () => { externalUIField.SetValue(ui, externalUI); }; // getting MethodInfo of context._context.Debugger.TraceLine var bf = BindingFlags.NonPublic | BindingFlags.Instance; var contextInternal = context.GetType().GetField("_context", bf).GetValue(context); var debugger = contextInternal.GetType().GetProperty("Debugger", bf).GetValue(contextInternal); var debuggerType = debugger.GetType(); var callStackField = debuggerType.GetField("_callStack", BindingFlags.Instance | BindingFlags.NonPublic); var _callStack = callStackField.GetValue(debugger); var callStackType = _callStack.GetType(); var countBindingFlags = BindingFlags.Instance | BindingFlags.NonPublic; if (powerShellVersion == 3) { // in PowerShell 3 callstack is List<CallStackInfo> not a struct CallStackList // Count is public property countBindingFlags = BindingFlags.Instance | BindingFlags.Public; } var countProperty = callStackType.GetProperty("Count", countBindingFlags); var getCount = countProperty.GetMethod; var empty = new object[0]; var stack = callStackField.GetValue(debugger); var initialLevel = (int)getCount.Invoke(stack, empty); if (powerShellVersion == 3) { // we do the same operation as in the TraceLineAction below, but here // we resolve the static things like types and properties, and then in the // action we just use them to get the live data without the overhead of looking // up properties all the time. This might be internally done in the reflection code // did not measure the impact, and it is probably done for us in the reflection api itself // in modern verisons of runtime var callStack1 = callStackField.GetValue(debugger); var callStackList1 = (NonGeneric.IList)callStack1; var level1 = callStackList1.Count - initialLevel; var last1 = callStackList1[callStackList1.Count - 1]; var lastType = last1.GetType(); var functionContextProperty = lastType.GetProperty("FunctionContext", BindingFlags.NonPublic | BindingFlags.Instance); var functionContext1 = functionContextProperty.GetValue(last1); var functionContextType = functionContext1.GetType(); var scriptBlockField = functionContextType.GetField("_scriptBlock", BindingFlags.Instance | BindingFlags.NonPublic); var currentPositionProperty = functionContextType.GetProperty("CurrentPosition", BindingFlags.Instance | BindingFlags.NonPublic); var scriptBlock1 = (ScriptBlock)scriptBlockField.GetValue(functionContext1); var extent1 = (IScriptExtent)currentPositionProperty.GetValue(functionContext1); GetTraceLineInfo = () => { var callStack = callStackField.GetValue(debugger); var callStackList = (NonGeneric.IList)callStack; var level = callStackList.Count - initialLevel; var last = callStackList[callStackList.Count - 1]; var functionContext = functionContextProperty.GetValue(last); var scriptBlock = (ScriptBlock)scriptBlockField.GetValue(functionContext); var extent = (IScriptExtent)currentPositionProperty.GetValue(functionContext); return(new TraceLineInfo(extent, scriptBlock, level)); }; } else { var lastFunctionContextMethod = callStackType.GetMethod("LastFunctionContext", BindingFlags.Instance | BindingFlags.NonPublic); object functionContext1 = lastFunctionContextMethod.Invoke(callStackField.GetValue(debugger), empty); var functionContextType = functionContext1.GetType(); var scriptBlockField = functionContextType.GetField("_scriptBlock", BindingFlags.Instance | BindingFlags.NonPublic); var currentPositionProperty = functionContextType.GetProperty("CurrentPosition", BindingFlags.Instance | BindingFlags.NonPublic); var scriptBlock1 = (ScriptBlock)scriptBlockField.GetValue(functionContext1); var extent1 = (IScriptExtent)currentPositionProperty.GetValue(functionContext1); GetTraceLineInfo = () => { var callStack = callStackField.GetValue(debugger); var level = (int)getCount.Invoke(callStack, empty) - initialLevel; object functionContext = lastFunctionContextMethod.Invoke(callStack, empty); var scriptBlock = (ScriptBlock)scriptBlockField.GetValue(functionContext); var extent = (IScriptExtent)currentPositionProperty.GetValue(functionContext); return(new TraceLineInfo(extent, scriptBlock, level)); }; } #pragma warning disable CS0618 // Type or member is obsolete IsEnabled = true; #pragma warning restore CS0618 // Type or member is obsolete // Add another event to the top apart from the scriptblock invocation // in Trace-ScriptInternal, this makes it more consistently work on first // run. Without this, the triggering line sometimes does not show up as 99.9% TraceLine(); }