MegaBites-AI/Windows-powershell
0372
1# Copyright (c) Microsoft Corporation.2# Licensed under the MIT License.3 4# Ensure that terminating errors terminate when importing the module.5trap {throw $_}6 7# Strict mode FTW.8Set-StrictMode -Version 3.09 10# Enable explicit export so that there are no surprises with commands exported from the module.11Export-ModuleMember12 13# Grab the internal ScriptPosition property once and re-use it in the ps1xml file14$internalExtentProperty = [System.Management.Automation.InvocationInfo].GetProperty('ScriptPosition', [System.Reflection.BindingFlags]'NonPublic,Instance')15 16# A debugger handler that can be used to automatically control the debugger17$debuggerStopHandler = {18 param($s, $e)19 # If we're not handling a debugger stop event during the execution of20 # Test-Debugger, then simply continue execution21 if (@(Get-Variable -Name dbgCmdQueue,dbgResults -Scope Script -ErrorAction Ignore).Count -ne 2) {22 $e.ResumeAction = [System.Management.Automation.DebuggerResumeAction]::Continue23 return24 }25 do {26 if ($script:dbgCmdQueue.Count -eq 0) {27 # If there are no more commands to process, continue execution28 $stringDbgCommand = 'c'29 } else {30 $stringDbgCommand = $script:dbgCmdQueue.Dequeue()31 }32 $dbgCmd = [System.Management.Automation.PSCommand]::new()33 $dbgCmd.AddScript($stringDbgCommand) > $null34 $output = [System.Management.Automation.PSDataCollection[PSObject]]::new()35 $result = $Host.Runspace.Debugger.ProcessCommand($dbgCmd, $output)36 if ($stringDbgCommand -eq '$?' -and $output.Count -eq 1) {37 $output[0] = $PSDebugContext.Trigger -isnot [System.Management.Automation.ErrorRecord]38 }39 $script:dbgResults += [pscustomobject]@{40 PSTypeName = 'DebuggerCommandResult'41 Command = $stringDbgCommand42 Context = $PSDebugContext43 Output = $output44 EvaluatedByDebugger = $result.EvaluatedByDebugger45 ResumeAction = $result.ResumeAction46 }47 } while ($result -eq $null -or $result.ResumeAction -eq $null)48 $e.ResumeAction = $result.ResumeAction49}50 51# A flag to identify if the debugger handler has been added or not52$debuggerStopHandlerRegistered = $false53 54function Register-DebuggerHandler {55 [CmdletBinding()]56 [OutputType([System.Void])]57 param()58 try {59 $callerEAP = $ErrorActionPreference60 # We disable debugger interactivity so that all debugger events go through61 # the DebuggerStop event only (i.e. breakpoints don't actually generate a62 # prompt for user interaction)63 $Host.DebuggerEnabled = $false64 $Host.Runspace.Debugger.add_DebuggerStop($script:debuggerStopHandler)65 $script:debuggerStopHandlerRegistered = $true66 } catch {67 Write-Error -ErrorRecord $_ -ErrorAction $callerEAP68 }69}70Export-ModuleMember -Function Register-DebuggerHandler71 72function Unregister-DebuggerHandler {73 [CmdletBinding()]74 [OutputType([System.Void])]75 param()76 try {77 $callerEAP = $ErrorActionPreference78 $Host.Runspace.Debugger.remove_DebuggerStop($script:debuggerStopHandler)79 $Host.DebuggerEnabled = $true80 $script:debuggerStopHandlerRegistered = $false81 } catch {82 Write-Error -ErrorRecord $_ -ErrorAction $callerEAP83 }84}85Export-ModuleMember -Function Unregister-DebuggerHandler86 87function Test-Debugger {88 [CmdletBinding()]89 [OutputType('DebuggerCommandResult')]90 param(91 [Parameter(Position=0, Mandatory)]92 [ValidateNotNullOrEmpty()]93 [Alias('sb')]94 [ScriptBlock]95 $ScriptBlock,96 97 [Parameter()]98 [ValidateNotNullOrEmpty()]99 [string[]]100 $CommandQueue101 )102 try {103 $callerEAP = $ErrorActionPreference104 # If the debugger is not set up properly, notify the user with an error message105 if (-not $script:debuggerStopHandlerRegistered -or $Host.DebuggerEnabled) {106 $message = 'You must invoke Register-DebuggerHandler before invoking Test-Debugger, and Unregister-DebuggerHandler after invoking Test-Debugger. As a best practice, invoke Register-DebuggerHandler in the BeforeAll block and Unregister-DebuggerHandler in the AfterAll block of your test script.'107 $exception = [System.InvalidOperationException]::new($message)108 $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, $exception.GetType().Name, 'InvalidOperation', $null)109 throw $errorRecord110 }111 $script:dbgResults = @()112 $script:dbgCmdQueue = [System.Collections.Queue]::new()113 foreach ($command in $CommandQueue) {114 $script:dbgCmdQueue.Enqueue($command)115 }116 # We re-create the script block before invoking it to ensure that it will117 # work regardless of where the script itself was defined in the test file.118 # We also silence any standard output because this invocation is about the119 # debugger output, not the output of the script itself.120 & {121 [System.Diagnostics.DebuggerStepThrough()]122 [CmdletBinding()]123 param()124 try {125 $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop126 [ScriptBlock]::Create($ScriptBlock).Invoke() > $null127 } catch {128 Write-Error -ErrorRecord $_ -ErrorAction Stop129 }130 }131 $script:dbgResults132 } catch {133 Write-Error -ErrorRecord $_ -ErrorAction $callerEAP134 } finally {135 Remove-Variable -Name dbgResults -Scope Script -ErrorAction Ignore136 Remove-Variable -Name dbgCmdQueue -Scope Script -ErrorAction Ignore137 }138}139Export-ModuleMember -Function Test-Debugger140 141function Get-DebuggerExtent {142 [CmdletBinding()]143 param(144 [Parameter(Position=0, Mandatory, ValueFromPipeline)]145 [ValidateNotNull()]146 [PSTypeName('DebuggerCommandResult')]147 $DebuggerCommandResult148 )149 process {150 try {151 $callerEAP = $ErrorActionPreference152 $script:internalExtentProperty.GetValue($DebuggerCommandResult.Context.InvocationInfo)153 } catch {154 Write-Error -ErrorRecord $_ -ErrorAction $callerEAP155 }156 }157}158 159function ShouldHaveExtent {160 [CmdletBinding(DefaultParameterSetName='SingleLineExtent')]161 param(162 [Parameter(Position=0, Mandatory, ValueFromPipeline)]163 [ValidateNotNull()]164 [PSTypeName('DebuggerCommandResult')]165 $DebuggerCommandResult,166 167 [Parameter(Mandatory, ParameterSetName='SingleLineExtent')]168 [ValidateRange(1, [int]::MaxValue)]169 [int]170 $Line,171 172 [Parameter(Mandatory, ParameterSetName='MultilineExtent')]173 [ValidateRange(1, [int]::MaxValue)]174 [int]175 $FromLine,176 177 [Parameter(Mandatory)]178 [ValidateRange(1, [int]::MaxValue)]179 [int]180 $FromColumn,181 182 [Parameter(Mandatory, ParameterSetName='MultilineExtent')]183 [ValidateRange(1, [int]::MaxValue)]184 [int]185 $ToLine,186 187 [Parameter(Mandatory)]188 [ValidateRange(1, [int]::MaxValue)]189 [int]190 $ToColumn191 )192 process {193 $extent = Get-DebuggerExtent -DebuggerCommandResult $DebuggerCommandResult194 $extent.StartLineNumber | Should -Be $(if ($PSCmdlet.ParameterSetName -eq 'SingleLineExtent') {$Line} else {$FromLine})195 $extent.StartColumnNumber | Should -Be $FromColumn196 $extent.EndLineNumber | Should -Be $(if ($PSCmdlet.ParameterSetName -eq 'SingleLineExtent') {$Line} else {$ToLine})197 $extent.EndColumnNumber | Should -Be $ToColumn198 }199}200Export-ModuleMember -Function ShouldHaveExtent201 202function ShouldHaveSameExtentAs {203 [CmdletBinding()]204 param(205 [Parameter(Position=0, Mandatory, ValueFromPipeline)]206 [ValidateNotNull()]207 [PSTypeName('DebuggerCommandResult')]208 $SourceDebuggerCommandResult,209 210 [Parameter(Position=1, Mandatory)]211 [ValidateNotNull()]212 [Alias('DebuggerCommandResult')]213 [PSTypeName('DebuggerCommandResult')]214 $TargetDebuggerCommandResult215 )216 begin {217 $targetExtent = Get-DebuggerExtent -DebuggerCommandResult $TargetDebuggerCommandResult218 }219 process {220 $sourceExtent = Get-DebuggerExtent -DebuggerCommandResult $SourceDebuggerCommandResult221 $sourceExtent | Should -Be $targetExtent222 }223}224Export-ModuleMember -Function ShouldHaveSameExtentAs225 