├── .config └── tsaoptions.json ├── .github ├── CODE_OF_CONDUCT.md ├── SECURITY.md ├── dependabot.yml └── workflows │ └── ci-test.yml ├── .gitignore ├── .pipelines └── ConsoleGuiTools-Official.yml ├── ConsoleGuiTools.Common.props ├── ConsoleGuiTools.build.ps1 ├── Directory.Build.props ├── Directory.Packages.props ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── demo.ps1 ├── docs └── Microsoft.PowerShell.ConsoleGuiTools │ ├── Out-ConsoleGridView.md │ ├── Show-ObjectTree.md │ └── ocgv.gif ├── global.json ├── nuget.config ├── src ├── Microsoft.PowerShell.ConsoleGuiTools │ ├── ConsoleGui.cs │ ├── GridViewDataSource.cs │ ├── GridViewDetails.cs │ ├── GridViewHelpers.cs │ ├── GridViewRow.cs │ ├── Microsoft.PowerShell.ConsoleGuiTools.csproj │ ├── Microsoft.PowerShell.ConsoleGuiTools.psd1 │ ├── OutConsoleGridviewCmdletCommand.cs │ ├── ShowObjectTreeCmdletCommand.cs │ ├── ShowObjectView.cs │ └── TypeGetter.cs └── Microsoft.PowerShell.OutGridView.Models │ ├── ApplicationData.cs │ ├── DataTable.cs │ ├── DataTableColumn.cs │ ├── DataTableRow.cs │ ├── Microsoft.PowerShell.OutGridView.Models.csproj │ ├── OutputModeOptions.cs │ └── Serializers.cs └── tools ├── installPSResources.ps1 └── updateVersion.ps1 /.config/tsaoptions.json: -------------------------------------------------------------------------------- 1 | { 2 | "instanceUrl": "https://msazure.visualstudio.com", 3 | "projectName": "One", 4 | "areaPath": "One\\MGMT\\Compute\\Powershell\\Powershell", 5 | "notificationAliases": [ "andschwa@microsoft.com", "slee@microsoft.com" ], 6 | "codebaseName": "PowerShell_ConsoleGuiTools_20240404", 7 | "tools": [ "CredScan", "PoliCheck", "BinSkim" ] 8 | } 9 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | - Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support) 11 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin) and [PowerShell](https://github.com/PowerShell). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/src/Microsoft.PowerShell.ConsoleGuiTools/" 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: nuget 8 | directory: "/src/Microsoft.PowerShell.OutGridView.Models/" 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/workflows/ci-test.yml: -------------------------------------------------------------------------------- 1 | name: CI Tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | merge_group: 9 | types: [ checks_requested ] 10 | 11 | jobs: 12 | ci: 13 | name: dotnet 14 | strategy: 15 | matrix: 16 | os: [ windows-latest, macos-latest, ubuntu-latest ] 17 | runs-on: ${{ matrix.os }} 18 | env: 19 | DOTNET_NOLOGO: true 20 | DOTNET_GENERATE_ASPNET_CERTIFICATE: false 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v4 24 | 25 | - name: Install dotnet 26 | uses: actions/setup-dotnet@v4 27 | with: 28 | cache: true 29 | cache-dependency-path: '**/*.csproj' 30 | 31 | - name: Install PSResources 32 | shell: pwsh 33 | run: ./tools/installPSResources.ps1 34 | 35 | - name: Build and test 36 | shell: pwsh 37 | run: Invoke-Build -Configuration Release Build, Package 38 | 39 | - name: Upload module 40 | if: always() 41 | uses: actions/upload-artifact@v4 42 | with: 43 | name: ConsoleGuiTools-module-${{ matrix.os }} 44 | path: module 45 | 46 | - name: Upload NuGet package 47 | uses: actions/upload-artifact@v4 48 | with: 49 | name: ConsoleGuiTools-nupkg-${{ matrix.os }} 50 | path: out/*.nupkg 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | module/ 2 | out/ 3 | bin/ 4 | obj/ 5 | publish/ 6 | *.sln 7 | -------------------------------------------------------------------------------- /.pipelines/ConsoleGuiTools-Official.yml: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | # OneBranch Pipelines # 3 | # This pipeline was created by EasyStart from a sample located at: # 4 | # https://aka.ms/obpipelines/easystart/samples # 5 | # Documentation: https://aka.ms/obpipelines # 6 | # Yaml Schema: https://aka.ms/obpipelines/yaml/schema # 7 | # Retail Tasks: https://aka.ms/obpipelines/tasks # 8 | # Support: https://aka.ms/onebranchsup # 9 | ################################################################################# 10 | 11 | trigger: 12 | - main 13 | 14 | schedules: 15 | - cron: '50 19 * * 3' 16 | displayName: Weekly CodeQL 17 | branches: 18 | include: 19 | - main 20 | always: true 21 | 22 | parameters: 23 | - name: debug 24 | displayName: Enable debug output 25 | type: boolean 26 | default: false 27 | 28 | variables: 29 | system.debug: ${{ parameters.debug }} 30 | BuildConfiguration: Release 31 | WindowsContainerImage: onebranch.azurecr.io/windows/ltsc2022/vse2022:latest 32 | DOTNET_NOLOGO: true 33 | DOTNET_GENERATE_ASPNET_CERTIFICATE: false 34 | 35 | resources: 36 | repositories: 37 | - repository: templates 38 | type: git 39 | name: OneBranch.Pipelines/GovernedTemplates 40 | ref: refs/heads/main 41 | 42 | extends: 43 | # https://aka.ms/obpipelines/templates 44 | template: v2/OneBranch.Official.CrossPlat.yml@templates 45 | parameters: 46 | globalSdl: # https://aka.ms/obpipelines/sdl 47 | asyncSdl: 48 | enabled: true 49 | forStages: [build] 50 | featureFlags: 51 | EnableCDPxPAT: false 52 | WindowsHostVersion: 53 | Version: 2022 54 | Network: Netlock 55 | stages: 56 | - stage: build 57 | jobs: 58 | - job: main 59 | displayName: Build package 60 | pool: 61 | type: windows 62 | variables: 63 | ob_outputDirectory: $(Build.SourcesDirectory)/out 64 | steps: 65 | - pwsh: | 66 | $data = Import-PowerShellDataFile -Path src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 67 | Write-Output "##vso[task.setvariable variable=version;isOutput=true]$($data.ModuleVersion)" 68 | name: package 69 | displayName: Get version from project properties 70 | - task: onebranch.pipeline.version@1 71 | displayName: Set OneBranch version 72 | inputs: 73 | system: Custom 74 | customVersion: $(package.version) 75 | - task: UseDotNet@2 76 | displayName: Use .NET SDK 77 | inputs: 78 | packageType: sdk 79 | useGlobalJson: true 80 | - pwsh: ./tools/installPSResources.ps1 -PSRepository CFS 81 | displayName: Install PSResources 82 | - pwsh: Invoke-Build -Configuration $(BuildConfiguration) 83 | displayName: Build 84 | - task: onebranch.pipeline.signing@1 85 | displayName: Sign 1st-party files 86 | inputs: 87 | command: sign 88 | signing_profile: external_distribution 89 | search_root: $(Build.SourcesDirectory)/module 90 | files_to_sign: | 91 | *.psd1; 92 | Microsoft.PowerShell.*.dll; 93 | - task: onebranch.pipeline.signing@1 94 | displayName: Sign 3rd-party files 95 | inputs: 96 | command: sign 97 | signing_profile: 135020002 98 | search_root: $(Build.SourcesDirectory)/module 99 | files_to_sign: | 100 | NStack.dll; 101 | Terminal.Gui.dll; 102 | - task: ArchiveFiles@2 103 | displayName: Zip module 104 | inputs: 105 | rootFolderOrFile: $(Build.SourcesDirectory)/module 106 | includeRootFolder: false 107 | archiveType: zip 108 | archiveFile: out/ConsoleGuiTools-v$(package.version).zip 109 | - pwsh: Invoke-Build -Configuration $(BuildConfiguration) Package 110 | displayName: Package module 111 | - task: onebranch.pipeline.signing@1 112 | displayName: Sign NuGet package 113 | inputs: 114 | command: sign 115 | signing_profile: external_distribution 116 | search_root: $(Build.SourcesDirectory)/out 117 | files_to_sign: "*.nupkg" 118 | - stage: release 119 | dependsOn: build 120 | condition: eq(variables['Build.Reason'], 'Manual') 121 | variables: 122 | version: $[ stageDependencies.build.main.outputs['package.version'] ] 123 | drop: $(Pipeline.Workspace)/drop_build_main 124 | jobs: 125 | - job: github 126 | displayName: Publish draft to GitHub 127 | pool: 128 | type: windows 129 | variables: 130 | ob_outputDirectory: $(Build.SourcesDirectory)/out 131 | steps: 132 | - download: current 133 | displayName: Download artifacts 134 | - task: GitHubRelease@1 135 | displayName: Create GitHub release 136 | inputs: 137 | gitHubConnection: GitHub 138 | repositoryName: PowerShell/ConsoleGuiTools 139 | assets: | 140 | $(drop)/Microsoft.PowerShell.ConsoleGuiTools.$(version).nupkg 141 | $(drop)/ConsoleGuiTools-v$(version).zip 142 | tagSource: userSpecifiedTag 143 | tag: v$(version) 144 | isDraft: true 145 | addChangeLog: false 146 | releaseNotesSource: inline 147 | releaseNotesInline: "" 148 | - job: validation 149 | displayName: Manual validation 150 | pool: 151 | type: agentless 152 | timeoutInMinutes: 1440 153 | steps: 154 | - task: ManualValidation@0 155 | displayName: Wait 24 hours for validation 156 | inputs: 157 | notifyUsers: $(Build.RequestedForEmail) 158 | instructions: Please validate the release and then publish it! 159 | timeoutInMinutes: 1440 160 | - job: publish 161 | dependsOn: validation 162 | displayName: Publish to PowerShell Gallery 163 | pool: 164 | type: windows 165 | variables: 166 | ob_outputDirectory: $(Build.SourcesDirectory)/out 167 | steps: 168 | - download: current 169 | displayName: Download artifacts 170 | - task: NuGetCommand@2 171 | displayName: Publish ConsoleGuiTools to PowerShell Gallery 172 | inputs: 173 | command: push 174 | packagesToPush: $(drop)/Microsoft.PowerShell.ConsoleGuiTools.$(version).nupkg 175 | nuGetFeedType: external 176 | publishFeedCredentials: PowerShellGallery 177 | -------------------------------------------------------------------------------- /ConsoleGuiTools.Common.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0.7.7 4 | 5 | Microsoft 6 | © Microsoft Corporation. 7 | latest 8 | https://github.com/PowerShell/ConsoleGuiTools/blob/main/LICENSE.txt 9 | git 10 | https://github.com/PowerShell/ConsoleGuiTools 11 | 12 | 13 | -------------------------------------------------------------------------------- /ConsoleGuiTools.build.ps1: -------------------------------------------------------------------------------- 1 | 2 | param( 3 | [ValidateSet("Debug", "Release")] 4 | [string]$Configuration = "Debug" 5 | ) 6 | 7 | task FindDotNet -Before Clean, Build { 8 | Assert (Get-Command dotnet -ErrorAction SilentlyContinue) "The dotnet CLI was not found, please install it: https://aka.ms/dotnet-cli" 9 | $DotnetVersion = dotnet --version 10 | Assert ($?) "The required .NET SDK was not found, please install it: https://aka.ms/dotnet-cli" 11 | Write-Host "Using dotnet $DotnetVersion at path $((Get-Command dotnet).Source)" -ForegroundColor Green 12 | } 13 | 14 | task Clean { 15 | Remove-BuildItem ./module, ./out 16 | Push-Location src/Microsoft.PowerShell.ConsoleGuiTools 17 | Invoke-BuildExec { & dotnet clean } 18 | Pop-Location 19 | } 20 | 21 | task Build { 22 | New-Item -ItemType Directory -Force ./module | Out-Null 23 | 24 | Push-Location src/Microsoft.PowerShell.ConsoleGuiTools 25 | Invoke-BuildExec { & dotnet publish --configuration $Configuration --output publish } 26 | $Assets = $( 27 | "./publish/Microsoft.PowerShell.ConsoleGuiTools.dll", 28 | "./publish/Microsoft.PowerShell.ConsoleGuiTools.psd1", 29 | "./publish/Microsoft.PowerShell.OutGridView.Models.dll", 30 | "./publish/Terminal.Gui.dll", 31 | "./publish/NStack.dll") 32 | $Assets | ForEach-Object { 33 | Copy-Item -Force -Path $_ -Destination ../../module 34 | } 35 | Pop-Location 36 | 37 | $Assets = $( 38 | "./README.md", 39 | "./LICENSE.txt", 40 | "./NOTICE.txt") 41 | $Assets | ForEach-Object { 42 | Copy-Item -Force -Path $_ -Destination ./module 43 | } 44 | 45 | New-ExternalHelp -Path docs/Microsoft.PowerShell.ConsoleGuiTools -OutputPath module/en-US -Force 46 | } 47 | 48 | task Package { 49 | New-Item -ItemType Directory -Force ./out | Out-Null 50 | if (-Not (Get-PSResourceRepository -Name ConsoleGuiTools -ErrorAction SilentlyContinue)) { 51 | Register-PSResourceRepository -Name ConsoleGuiTools -Uri ./out 52 | } 53 | Publish-PSResource -Path ./module -Repository ConsoleGuiTools -Verbose 54 | } 55 | 56 | task . Clean, Build 57 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 PowerShell Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | NOTICES AND INFORMATION 2 | Do Not Translate or Localize 3 | 4 | This software incorporates material from third parties. 5 | Microsoft makes certain open source code available at https://3rdpartysource.microsoft.com, 6 | or you may send a check or money order for US $5.00, including the product name, 7 | the open source component name, platform, and version number, to: 8 | 9 | Source Code Compliance Team 10 | Microsoft Corporation 11 | One Microsoft Way 12 | Redmond, WA 98052 13 | USA 14 | 15 | Notwithstanding any other terms, you may reverse engineer this software to the extent 16 | required to debug changes to any libraries licensed under the GNU Lesser General Public License. 17 | 18 | --------------------------------------------------------- 19 | 20 | NStack.Core 1.1.1 - MIT 21 | 22 | 23 | 24 | MIT License 25 | 26 | Copyright (c) 2009 The Go Authors. All rights reserved. 27 | Copyright (c) 2017 Microsoft. 28 | 29 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 30 | 31 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 32 | 33 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 34 | 35 | --------------------------------------------------------- 36 | 37 | --------------------------------------------------------- 38 | 39 | System.CodeDom 8.0.0 - MIT 40 | 41 | 42 | Copyright (c) Six Labors 43 | (c) Microsoft Corporation 44 | Copyright (c) Andrew Arnott 45 | Copyright 2019 LLVM Project 46 | Copyright 2018 Daniel Lemire 47 | Copyright (c) .NET Foundation 48 | Copyright (c) 2011, Google Inc. 49 | Copyright (c) 2020 Dan Shechter 50 | (c) 1997-2005 Sean Eron Anderson 51 | Copyright (c) 1998 Microsoft. To 52 | Copyright (c) 2022, Wojciech Mula 53 | Copyright (c) 2017 Yoshifumi Kawai 54 | Copyright (c) 2022, Geoff Langdale 55 | Copyright (c) 2005-2020 Rich Felker 56 | Copyright (c) 2012-2021 Yann Collet 57 | Copyright (c) Microsoft Corporation 58 | Copyright (c) 2007 James Newton-King 59 | Copyright (c) 1991-2022 Unicode, Inc. 60 | Copyright (c) 2013-2017, Alfred Klomp 61 | Copyright 2012 the V8 project authors 62 | Copyright (c) 1999 Lucent Technologies 63 | Copyright (c) 2008-2016, Wojciech Mula 64 | Copyright (c) 2011-2020 Microsoft Corp 65 | Copyright (c) 2015-2017, Wojciech Mula 66 | Copyright (c) 2021 csFastFloat authors 67 | Copyright (c) 2005-2007, Nick Galbreath 68 | Copyright (c) 2015 The Chromium Authors 69 | Copyright (c) 2018 Alexander Chermyanin 70 | Copyright (c) The Internet Society 1997 71 | Portions (c) International Organization 72 | Copyright (c) 2004-2006 Intel Corporation 73 | Copyright (c) 2011-2015 Intel Corporation 74 | Copyright (c) 2013-2017, Milosz Krajewski 75 | Copyright (c) 2016-2017, Matthieu Darbois 76 | Copyright (c) The Internet Society (2003) 77 | Copyright (c) .NET Foundation Contributors 78 | Copyright (c) 2020 Mara Bos 79 | Copyright (c) .NET Foundation and Contributors 80 | Copyright (c) 2012 - present, Victor Zverovich 81 | Copyright (c) 2006 Jb Evain (jbevain@gmail.com) 82 | Copyright (c) 2008-2020 Advanced Micro Devices, Inc. 83 | Copyright (c) 2019 Microsoft Corporation, Daan Leijen 84 | Copyright (c) 2011 Novell, Inc (http://www.novell.com) 85 | Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler 86 | Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) 87 | Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors 88 | Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com 89 | Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. 90 | Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 91 | Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip 92 | Copyright (c) 1980, 1986, 1993 The Regents of the University of California 93 | Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California 94 | Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass 95 | 96 | The MIT License (MIT) 97 | 98 | Copyright (c) .NET Foundation and Contributors 99 | 100 | All rights reserved. 101 | 102 | Permission is hereby granted, free of charge, to any person obtaining a copy 103 | of this software and associated documentation files (the "Software"), to deal 104 | in the Software without restriction, including without limitation the rights 105 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 106 | copies of the Software, and to permit persons to whom the Software is 107 | furnished to do so, subject to the following conditions: 108 | 109 | The above copyright notice and this permission notice shall be included in all 110 | copies or substantial portions of the Software. 111 | 112 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 113 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 114 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 115 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 116 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 117 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 118 | SOFTWARE. 119 | 120 | 121 | --------------------------------------------------------- 122 | 123 | --------------------------------------------------------- 124 | 125 | System.Management 8.0.0 - MIT 126 | 127 | 128 | Copyright (c) Six Labors 129 | (c) Microsoft Corporation 130 | Copyright (c) Andrew Arnott 131 | Copyright 2019 LLVM Project 132 | Copyright 2018 Daniel Lemire 133 | Copyright (c) .NET Foundation 134 | Copyright (c) 2011, Google Inc. 135 | Copyright (c) 2020 Dan Shechter 136 | (c) 1997-2005 Sean Eron Anderson 137 | Copyright (c) 1998 Microsoft. To 138 | Copyright (c) 2022, Wojciech Mula 139 | Copyright (c) 2017 Yoshifumi Kawai 140 | Copyright (c) 2022, Geoff Langdale 141 | Copyright (c) 2005-2020 Rich Felker 142 | Copyright (c) 2012-2021 Yann Collet 143 | Copyright (c) Microsoft Corporation 144 | Copyright (c) 2007 James Newton-King 145 | Copyright (c) 1991-2022 Unicode, Inc. 146 | Copyright (c) 2013-2017, Alfred Klomp 147 | Copyright 2012 the V8 project authors 148 | Copyright (c) 1999 Lucent Technologies 149 | Copyright (c) 2008-2016, Wojciech Mula 150 | Copyright (c) 2011-2020 Microsoft Corp 151 | Copyright (c) 2015-2017, Wojciech Mula 152 | Copyright (c) 2021 csFastFloat authors 153 | Copyright (c) 2005-2007, Nick Galbreath 154 | Copyright (c) 2015 The Chromium Authors 155 | Copyright (c) 2018 Alexander Chermyanin 156 | Copyright (c) The Internet Society 1997 157 | Portions (c) International Organization 158 | Copyright (c) 2004-2006 Intel Corporation 159 | Copyright (c) 2011-2015 Intel Corporation 160 | Copyright (c) 2013-2017, Milosz Krajewski 161 | Copyright (c) 2016-2017, Matthieu Darbois 162 | Copyright (c) The Internet Society (2003) 163 | Copyright (c) .NET Foundation Contributors 164 | Copyright (c) 2020 Mara Bos 165 | Copyright (c) .NET Foundation and Contributors 166 | Copyright (c) 2012 - present, Victor Zverovich 167 | Copyright (c) 2006 Jb Evain (jbevain@gmail.com) 168 | Copyright (c) 2008-2020 Advanced Micro Devices, Inc. 169 | Copyright (c) 2019 Microsoft Corporation, Daan Leijen 170 | Copyright (c) 2011 Novell, Inc (http://www.novell.com) 171 | Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler 172 | Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) 173 | Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors 174 | Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com 175 | Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. 176 | Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 177 | Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip 178 | Copyright (c) 1980, 1986, 1993 The Regents of the University of California 179 | Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California 180 | Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass 181 | 182 | The MIT License (MIT) 183 | 184 | Copyright (c) .NET Foundation and Contributors 185 | 186 | All rights reserved. 187 | 188 | Permission is hereby granted, free of charge, to any person obtaining a copy 189 | of this software and associated documentation files (the "Software"), to deal 190 | in the Software without restriction, including without limitation the rights 191 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 192 | copies of the Software, and to permit persons to whom the Software is 193 | furnished to do so, subject to the following conditions: 194 | 195 | The above copyright notice and this permission notice shall be included in all 196 | copies or substantial portions of the Software. 197 | 198 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 199 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 200 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 201 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 202 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 203 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 204 | SOFTWARE. 205 | 206 | 207 | --------------------------------------------------------- 208 | 209 | --------------------------------------------------------- 210 | 211 | System.ValueTuple 4.5.0 - MIT 212 | 213 | 214 | (c) 2023 GitHub, Inc. 215 | (c) Microsoft Corporation 216 | Copyright (c) 2011, Google Inc. 217 | (c) 1997-2005 Sean Eron Anderson 218 | Copyright (c) 1991-2017 Unicode, Inc. 219 | Copyright (c) 2015 The Chromium Authors 220 | Portions (c) International Organization 221 | Copyright (c) 2004-2006 Intel Corporation 222 | Copyright (c) .NET Foundation Contributors 223 | Copyright (c) .NET Foundation and Contributors 224 | Copyright (c) 2011 Novell, Inc (http://www.novell.com) 225 | Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler 226 | Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) 227 | Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors 228 | Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 229 | 230 | The MIT License (MIT) 231 | 232 | Copyright (c) .NET Foundation and Contributors 233 | 234 | All rights reserved. 235 | 236 | Permission is hereby granted, free of charge, to any person obtaining a copy 237 | of this software and associated documentation files (the "Software"), to deal 238 | in the Software without restriction, including without limitation the rights 239 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 240 | copies of the Software, and to permit persons to whom the Software is 241 | furnished to do so, subject to the following conditions: 242 | 243 | The above copyright notice and this permission notice shall be included in all 244 | copies or substantial portions of the Software. 245 | 246 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 247 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 248 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 249 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 250 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 251 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 252 | SOFTWARE. 253 | 254 | 255 | --------------------------------------------------------- 256 | 257 | --------------------------------------------------------- 258 | 259 | Terminal.Gui 1.16.0 - MIT 260 | 261 | 262 | 263 | MIT License 264 | 265 | Copyright (c) 2007-2011 Novell Inc 266 | Copyright (c) 2017 Microsoft Corp 267 | 268 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 269 | 270 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 271 | 272 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 273 | 274 | --------------------------------------------------------- 275 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ConsoleGuiTools - `Out-ConsoleGridView` and `Show-ObjectTree` 2 | 3 | This repo contains the `Out-ConsoleGridView` 4 | PowerShell Cmdlet providing console-based GUI experiences based on 5 | [Terminal.Gui (gui.cs)](https://github.com/gui-cs/Terminal.Gui). 6 | 7 | _Note:_ A module named `Microsoft.PowerShell.GraphicalTools` used to be built and published out of this repo, but per [#101](https://github.com/PowerShell/ConsoleGuiTools/issues/101) it is deprecated and unmaintained until such time that it can be rewritten on top of [.NET MAUI](https://devblogs.microsoft.com/dotnet/introducing-net-multi-platform-app-ui/). 8 | 9 | ## Installation 10 | 11 | ```powershell 12 | Install-Module Microsoft.PowerShell.ConsoleGuiTools 13 | ``` 14 | 15 | ## Features 16 | 17 | * [`Out-ConsoleGridview`](docs/Microsoft.PowerShell.ConsoleGuiTools/Out-ConsoleGridView.md) - Send objects to a grid view window for interactive filtering and sorting. 18 | * [`Show-ObjectTree`](docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md) - Send objects to a tree view window for interactive filtering and sorting. 19 | 20 | * Cross-platform - Works on any platform that supports PowerShell 7.2+. 21 | * Interactive - Use the mouse and keyboard to interact with the grid or tree view. 22 | * Filtering - Filter the data using the built-in filter box. 23 | * Sorting - Sort the data by clicking on the column headers. 24 | * Multiple Selection - Select multiple items and send them down the pipeline. 25 | * Customizable - Customize the grid view window with the built-in parameters. 26 | 27 | ![Demo GIF](docs/Microsoft.PowerShell.ConsoleGuiTools/ocgv.gif) 28 | 29 | ## Examples 30 | 31 | ### Example 1: Output processes to a grid view 32 | 33 | ```PowerShell 34 | Get-Process | Out-ConsoleGridView 35 | ``` 36 | 37 | This command gets the processes running on the local computer and sends them to a grid view window. 38 | 39 | ### Example 2: Use a variable to output processes to a grid view 40 | 41 | ```PowerShell 42 | $P = Get-Process 43 | $P | Out-ConsoleGridView -OutputMode Single 44 | ``` 45 | 46 | This command also gets the processes running on the local computer and sends them to a grid view window. 47 | 48 | The first command uses the Get-Process cmdlet to get the processes on the computer and then saves the process objects in the $P variable. 49 | 50 | The second command uses a pipeline operator to send the $P variable to **Out-ConsoleGridView**. 51 | 52 | By specifying `-OutputMode Single` the grid view window will be restricted to a single selection, ensuring no more than a single object is returned. 53 | 54 | ### Example 3: Display a formatted table in a grid view 55 | 56 | ```PowerShell 57 | Get-Process | Select-Object -Property Name, WorkingSet, PeakWorkingSet | Sort-Object -Property WorkingSet -Descending | Out-ConsoleGridView 58 | ``` 59 | 60 | This command displays a formatted table in a grid view window. 61 | 62 | It uses the Get-Process cmdlet to get the processes on the computer. 63 | 64 | Then, it uses a pipeline operator (|) to send the process objects to the Select-Object cmdlet. 65 | The command uses the **Property** parameter of **Select-Object** to select the Name, WorkingSet, and PeakWorkingSet properties to be displayed in the table. 66 | 67 | Another pipeline operator sends the filtered objects to the Sort-Object cmdlet, which sorts them in descending order by the value of the **WorkingSet** property. 68 | 69 | The final part of the command uses a pipeline operator (|) to send the formatted table to **Out-ConsoleGridView**. 70 | 71 | You can now use the features of the grid view to search, sort, and filter the data. 72 | 73 | ### Example 4: Save output to a variable, and then output a grid view 74 | 75 | ```PowerShell 76 | ($A = Get-ChildItem -Path $pshome -Recurse) | Out-ConsoleGridView 77 | ``` 78 | 79 | This command saves its output in a variable and sends it to **Out-ConsoleGridView**. 80 | 81 | The command uses the Get-ChildItem cmdlet to get the files in the Windows PowerShell installation directory and its subdirectories. 82 | The path to the installation directory is saved in the $pshome automatic variable. 83 | 84 | The command uses the assignment operator (=) to save the output in the $A variable and the pipeline operator (|) to send the output to **Out-ConsoleGridView**. 85 | 86 | The parentheses in the command establish the order of operations. 87 | As a result, the output from the Get-ChildItem command is saved in the $A variable before it is sent to **Out-ConsoleGridView**. 88 | 89 | ### Example 5: Output processes for a specified computer to a grid view 90 | 91 | ```PowerShell 92 | Get-Process -ComputerName "Server01" | ocgv -Title "Processes - Server01" 93 | ``` 94 | 95 | This command displays the processes that are running on the Server01 computer in a grid view window. 96 | 97 | The command uses `ocgv`, which is the built-in alias for the **Out-ConsoleGridView** cmdlet, it uses the _Title_ parameter to specify the window title. 98 | 99 | ### Example 6: Define a function to kill processes using a graphical chooser 100 | 101 | ```PowerShell 102 | function killp { Get-Process | Out-ConsoleGridView -OutputMode Single -Filter $args[0] | Stop-Process -Id {$_.Id} } 103 | killp note 104 | ``` 105 | 106 | This example shows defining a function named `killp` that shows a grid view of all running processes and allows the user to select one to kill it. 107 | 108 | The example uses the `-Filter` paramter to filter for all proceses with a name that includes `note` (thus highlighting `Notepad` if it were running. Selecting an item in the grid view and pressing `ENTER` will kill that process. 109 | 110 | ### Example 7: Use F7 as "Show Command History" 111 | 112 | Add [gui-cs/F7History](https://github.com/gui-cs/F7History) to your Powershell profile. 113 | 114 | Press `F7` to see the history for the current PowerShell instance 115 | 116 | Press `Shift-F7` to see the history for all PowerShell instances. 117 | 118 | Whatever you select within `Out-ConsoleGridView` will be inserted on your command line. 119 | 120 | Whatever was typed on the command line prior to hitting `F7` or `Shift-F7` will be used as a filter. 121 | 122 | ### Example 8: Output processes to a tree view 123 | 124 | ```PowerShell 125 | PS C:\> Get-Process | Show-ObjectTree 126 | ``` 127 | 128 | This command gets the processes running on the local computer and sends them to a tree view window. 129 | 130 | Use right arrow when a row has a `+` symbol to expand the tree. Left arrow will collapse the tree. 131 | 132 | ## Development 133 | 134 | ### 1. Install PowerShell 7.2+ 135 | 136 | Install PowerShell 7.2+ with [these instructions](https://github.com/PowerShell/PowerShell#get-powershell). 137 | 138 | ### 2. Clone the GitHub repository 139 | 140 | ```powershell 141 | git clone https://github.com/PowerShell/ConsoleGuiTools.git 142 | ``` 143 | 144 | ### 3. Install [Invoke-Build](https://github.com/nightroman/Invoke-Build) 145 | 146 | ```powershell 147 | Install-Module InvokeBuild -Scope CurrentUser 148 | ``` 149 | 150 | Now you're ready to build the code. You can do so in one of two ways: 151 | 152 | ### 4. Building the code from PowerShell 153 | 154 | ```powershell 155 | pushd ./ConsoleGuiTools 156 | Invoke-Build Build -ModuleName Microsoft.PowerShell.ConsoleGuiTools 157 | popd 158 | ``` 159 | 160 | From there you can import the module that you just built for example (start a fresh `pwsh` instance first so you can unload the module with an `exit`; otherwise building again may fail because the `.dll` will be held open): 161 | 162 | ```powershell 163 | pwsh 164 | Import-Module ./module/Microsoft.PowerShell.ConsoleGuiTools 165 | ``` 166 | 167 | And then run the cmdlet you want to test, for example: 168 | 169 | ```powershell 170 | Get-Process | Out-ConsoleGridView 171 | exit 172 | ``` 173 | 174 | > NOTE: If you change the code and rebuild the project, you'll need to launch a 175 | > _new_ PowerShell process since the DLL is already loaded and can't be unloaded. 176 | 177 | ### 5. Debugging in Visual Studio Code 178 | 179 | ```powershell 180 | code ./ConsoleGuiTools 181 | ``` 182 | 183 | Build by hitting `Ctrl-Shift-B` in VS Code. 184 | 185 | Set a breakpoint and hit `F5` to start the debugger. 186 | 187 | Click on the VS Code "TERMINAL" tab and type your command that starts `Out-ConsoleGridView`, e.g. 188 | 189 | ```powershell 190 | ls | ocgv 191 | ``` 192 | 193 | Your breakpoint should be hit. 194 | 195 | ## Contributions Welcome 196 | 197 | We would love to incorporate community contributions into this project. If 198 | you would like to contribute code, documentation, tests, or bug reports, 199 | please read the [development section above](https://github.com/PowerShell/ConsoleGuiTools#development) 200 | to learn more. 201 | 202 | ## Microsoft.PowerShell.ConsoleGuiTools Architecture 203 | 204 | `ConsoleGuiTools` consists of 2 .NET Projects: 205 | 206 | * ConsoleGuiTools - Cmdlet implementation for Out-ConsoleGridView 207 | * OutGridView.Models - Contains data contracts between the GUI & Cmdlet 208 | 209 | _Note:_ Previously, this repo included `Microsoft.PowerShell.GraphicalTools` which included the Avalonia-based `Out-GridView` (implemented in `.\Microsoft.PowerShell.GraphicalTools` and `.\OutGridView.Gui`). These components have been deprecated (see note above). 210 | 211 | ## Maintainers 212 | 213 | * [Andy Jordan](https://andyleejordan.com) - [@andyleejordan](https://github.com/andyleejordan) 214 | * [Tig Kindel](https://www.kindel.com) - [@tig](https://github.com/tig) 215 | 216 | Originally authored by [Tyler Leonhardt](http://twitter.com/tylerleonhardt). 217 | 218 | ## License 219 | 220 | This project is [licensed under the MIT License](LICENSE.txt). 221 | 222 | ## Code of Conduct 223 | 224 | Please see our [Code of Conduct](.github/CODE_OF_CONDUCT.md) before participating in this project. 225 | 226 | ## Security Policy 227 | 228 | For any security issues, please see our [Security Policy](.github/SECURITY.md). 229 | -------------------------------------------------------------------------------- /demo.ps1: -------------------------------------------------------------------------------- 1 | # .Silent cls 2 | # Out-ConsoleGridView (ocgv) from Microsoft.PowerShell.ConsoleGuiTools Demo - Press enter to move to next step in demo 3 | # Example 1: Output processes to a grid view 4 | Get-Process | Out-ConsoleGridView 5 | # .Silent cls 6 | # Example 2: Display a formatted table in a grid view 7 | Get-Process | Select-Object -Property Name, WorkingSet, PeakWorkingSet | Sort-Object -Property WorkingSet -Descending | Out-ConsoleGridView 8 | # .Silent cls 9 | # Example 3: Define the function 'killp' to kill a process 10 | function killp { Get-Process | Out-ConsoleGridView -OutputMode Single -Filter $args[0] | Stop-Process -Id {$_.Id} } 11 | killp 12 | # .Silent cls 13 | # Example 3b: 'killp note' fitlers for "note" (e.g. notepad.exe) 14 | killp note 15 | # .Silent cls 16 | # Example 4: Navigate PowerShell command history (Map this to F7 with https://github.com/gui-cs/F7History) 17 | Get-History | Sort-Object -Descending -Property Id -Unique | Select-Object CommandLine -ExpandProperty CommandLine | Out-ConsoleGridView -OutputMode Single -Filter $line -Title "Command Line History" 18 | # .Silent cls 19 | # Example 4: Use Show-ObjectTree to output processes to a tree view 20 | Get-Process | Show-ObjectTree 21 | -------------------------------------------------------------------------------- /docs/Microsoft.PowerShell.ConsoleGuiTools/Out-ConsoleGridView.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: ConsoleGuiToolsModule.dll-Help.xml 3 | keywords: powershell,cmdlet 4 | locale: en-us 5 | Module Name: Microsoft.PowerShell.ConsoleGuiTools 6 | ms.date: 08/24/2022 7 | schema: 2.0.0 8 | title: Out-ConsoleGridView 9 | --- 10 | 11 | # Out-ConsoleGridView 12 | 13 | ## SYNOPSIS 14 | 15 | Sends output to an interactive table in the same console window. 16 | 17 | ## SYNTAX 18 | 19 | ```PowerShell 20 | Out-ConsoleGridView [-InputObject ] [-Title ] [-OutputMode {None | Single | 21 | Multiple}] [-Filter ] [-MinUi] [] 22 | ``` 23 | 24 | ## DESCRIPTION 25 | 26 | The **Out-ConsoleGridView** cmdlet sends the output from a command to a grid view window where the output is displayed in an interactive table. 27 | 28 | You can use the following features of the table to examine your data: 29 | 30 | - Quick Filter. Use the Filter box at the top of the window to search the text in the table. You can search for text in a particular column, search for literals, and search for multiple words. You can use the `-Filter` command to pre-populate the Filter box. The filter uses regular expressions. 31 | 32 | For instructions for using these features, type `Get-Help Out-ConsoleGridView -Full` and see How to Use the Grid View Window Features in the Notes section. 33 | 34 | To send items from the interactive window down the pipeline, click to select the items (either the the mouse in terminals that support mouse or the `SPACE` key) and then press `ENTER`. `ESC` cancels. 35 | 36 | ## EXAMPLES 37 | 38 | ### Example 1: Output processes to a grid view 39 | 40 | ```PowerShell 41 | PS C:\> Get-Process | Out-ConsoleGridView 42 | ``` 43 | 44 | This command gets the processes running on the local computer and sends them to a grid view window. 45 | 46 | ### Example 2: Use a variable to output processes to a grid view 47 | 48 | ```PowerShell 49 | PS C:\> $P = Get-Process 50 | PS C:\> $P | Out-ConsoleGridView -OutputMode Single 51 | ``` 52 | 53 | This command also gets the processes running on the local computer and sends them to a grid view window. 54 | 55 | The first command uses the Get-Process cmdlet to get the processes on the computer and then saves the process objects in the $P variable. 56 | 57 | The second command uses a pipeline operator to send the $P variable to **Out-ConsoleGridView**. 58 | 59 | By specifying `-OutputMode Single` the grid view window will be restricted to a single selection, ensuring now more than a single object is returned. 60 | 61 | ### Example 3: Display a formatted table in a grid view 62 | 63 | ```PowerShell 64 | PS C:\> Get-Process | Select-Object -Property Name, WorkingSet, PeakWorkingSet | Sort-Object -Property WorkingSet -Descending | Out-ConsoleGridView 65 | ``` 66 | 67 | This command displays a formatted table in a grid view window. 68 | 69 | It uses the Get-Process cmdlet to get the processes on the computer. 70 | 71 | Then, it uses a pipeline operator (|) to send the process objects to the Select-Object cmdlet. 72 | The command uses the **Property** parameter of **Select-Object** to select the Name, WorkingSet, and PeakWorkingSet properties to be displayed in the table. 73 | 74 | Another pipeline operator sends the filtered objects to the Sort-Object cmdlet, which sorts them in descending order by the value of the **WorkingSet** property. 75 | 76 | The final part of the command uses a pipeline operator (|) to send the formatted table to **Out-ConsoleGridView**. 77 | 78 | You can now use the features of the grid view to search, sort, and filter the data. 79 | 80 | ### Example 4: Save output to a variable, and then output a grid view 81 | 82 | ```PowerShell 83 | PS C:\> ($A = Get-ChildItem -Path $pshome -Recurse) | Out-ConsoleGridView 84 | ``` 85 | 86 | This command saves its output in a variable and sends it to **Out-ConsoleGridView**. 87 | 88 | The command uses the Get-ChildItem cmdlet to get the files in the Windows PowerShell installation directory and its subdirectories. 89 | The path to the installation directory is saved in the $pshome automatic variable. 90 | 91 | The command uses the assignment operator (=) to save the output in the $A variable and the pipeline operator (|) to send the output to **Out-ConsoleGridView**. 92 | 93 | The parentheses in the command establish the order of operations. 94 | As a result, the output from the Get-ChildItem command is saved in the $A variable before it is sent to **Out-ConsoleGridView**. 95 | 96 | ### Example 5: Output processes for a specified computer to a grid view 97 | 98 | ```PowerShell 99 | PS C:\> Get-Process -ComputerName "Server01" | ocgv -Title "Processes - Server01" 100 | ``` 101 | 102 | This command displays the processes that are running on the Server01 computer in a grid view window. 103 | 104 | The command uses `ocgv`, which is the built-in alias for the **Out-ConsoleGridView** cmdlet, it uses the *Title* parameter to specify the window title. 105 | 106 | ### Example 6: Define a function to kill processes using a graphical chooser 107 | 108 | ```PowerShell 109 | PS C:\> function killp { Get-Process | Out-ConsoleGridView -OutputMode Single -Filter $args[0] | Stop-Process -Id {$_.Id} } 110 | PS C:\> killp note 111 | ``` 112 | This example shows defining a function named `killp` that shows a grid view of all running processes and allows the user to select one to kill it. 113 | 114 | The example uses the `-Filter` paramter to filter for all proceses with a name that includes `note` (thus highlighting `Notepad` if it were running. Selecting an item in the grid view and pressing `ENTER` will kill that process. 115 | 116 | ### Example 7: Pass multiple items through Out-ConsoleGridView 117 | 118 | ```PowerShell 119 | PS C:\> Get-Process | Out-ConsoleGridView -PassThru | Export-Csv -Path .\ProcessLog.csv 120 | ``` 121 | 122 | This command lets you select multiple processes from the **Out-ConsoleGridView** window. 123 | The processes that you select are passed to the **Export-Csv** command and written to the ProcessLog.csv file. 124 | 125 | The command uses the *PassThru* parameter of **Out-ConsoleGridView**, which lets you send multiple items down the pipeline. 126 | The *PassThru* parameter is equivalent to using the Multiple value of the *OutputMode* parameter. 127 | 128 | ### Example 8: Use F7 as "Show Command History" 129 | 130 | Save See [this gist](https://gist.github.com/tig/cbbeab7f53efd73e329afd6d4b838191) as `F7History.ps1` and run `F7History.ps1` in your `$profile`. 131 | 132 | Press `F7` to see the history for the current PowerShell instance 133 | 134 | Press `Shift-F7` to see the history for all PowerShell instances. 135 | 136 | Whatever you select within `Out-ConsoleGridView` will be inserted on your command line. 137 | 138 | Whatever was typed on the command line prior to hitting `F7` or `Shift-F7` will be used as a filter. 139 | 140 | ## PARAMETERS 141 | 142 | ### -Filter 143 | Pre-populates the Filter edit box, allowing filtering to be specified on the command line. 144 | 145 | ```yaml 146 | Type: String 147 | Parameter Sets: (All) 148 | Aliases: 149 | 150 | Required: False 151 | Position: Named 152 | Default value: None 153 | Accept pipeline input: False 154 | Accept wildcard characters: False 155 | ``` 156 | 157 | ### -InputObject 158 | Specifies that the cmdlet accepts input for **Out-ConsoleGridView**. 159 | 160 | When you use the **InputObject** parameter to send a collection of objects to **Out-ConsoleGridView**, **Out-ConsoleGridView** treats the collection as one collection object, and it displays one row that represents the collection. 161 | 162 | To display the each object in the collection, use a pipeline operator (|) to send objects to **Out-ConsoleGridView**. 163 | 164 | ```yaml 165 | Type: PSObject 166 | Parameter Sets: (All) 167 | Aliases: 168 | 169 | Required: False 170 | Position: Named 171 | Default value: None 172 | Accept pipeline input: True (ByValue) 173 | Accept wildcard characters: False 174 | ``` 175 | 176 | ### -OutputMode 177 | Specifies the items that the interactive window sends down the pipeline as input to other commands. 178 | By default, this cmdlet generates zero, one, or many items. 179 | 180 | To send items from the interactive window down the pipeline, click to select the items (either the the mouse in terminals that support mouse or the `SPACE` key) and then press `ENTER`. `ESC` cancels. 181 | 182 | The values of this parameter determine how many items you can send down the pipeline. 183 | 184 | - None. No items. 185 | - Single. Zero items or one item. Use this value when the next command can take only one input object. 186 | - Multiple. Zero, one, or many items. Use this value when the next command can take multiple input objects. This is the default value. 187 | 188 | ```yaml 189 | Type: OutputModeOption 190 | Parameter Sets: OutputMode 191 | Aliases: 192 | Accepted values: None, Single, Multiple 193 | 194 | Required: False 195 | Position: Named 196 | Default value: Multiple 197 | Accept pipeline input: False 198 | Accept wildcard characters: False 199 | ``` 200 | 201 | ### -Title 202 | Specifies the text that appears in the title bar of the **Out-ConsoleGridView** window. 203 | 204 | By default, the title bar displays the command that invokes **Out-ConsoleGridView**. 205 | 206 | ```yaml 207 | Type: String 208 | Parameter Sets: (All) 209 | Aliases: 210 | 211 | Required: False 212 | Position: Named 213 | Default value: None 214 | Accept pipeline input: False 215 | Accept wildcard characters: False 216 | ``` 217 | 218 | ### -MinUi 219 | If specified no window frame, filter box, or status bar will be displayed in the **Out-ConsoleGridView** window. 220 | 221 | ```yaml 222 | Type: SwitchParameter 223 | Parameter Sets: (All) 224 | Aliases: 225 | 226 | Required: False 227 | Position: Named 228 | Default value: None 229 | Accept pipeline input: False 230 | Accept wildcard characters: False 231 | ``` 232 | 233 | ### CommonParameters 234 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). 235 | 236 | ## INPUTS 237 | 238 | ### System.Management.Automation.PSObject 239 | 240 | You can send any object to this cmdlet. 241 | 242 | ## OUTPUTS 243 | 244 | ### System.Object 245 | 246 | By default `Out-ConsoleGridView` returns objects representing the selected rows to the pipeline. Use `-OutputMode` to change this behavior. 247 | 248 | ## NOTES 249 | 250 | * The command output that you send to **Out-ConsoleGridView** should not be formatted, such as by using the Format-Table or Format-Wide cmdlets. To select properties, use the Select-Object cmdlet. 251 | 252 | * Deserialized output from remote commands might not be formatted correctly in the grid view window. 253 | 254 | ## RELATED LINKS 255 | 256 | [Out-File](Out-File.md) 257 | 258 | [Out-Printer](Out-Printer.md) 259 | 260 | [Out-String](Out-String.md) 261 | -------------------------------------------------------------------------------- /docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: ConsoleGuiToolsModule.dll-Help.xml 3 | keywords: powershell,cmdlet 4 | locale: en-us 5 | Module Name: Microsoft.PowerShell.ConsoleGuiTools 6 | ms.date: 07/20/2023 7 | schema: 2.0.0 8 | title: Show-ObjectTree 9 | --- 10 | 11 | # Show-ObjectTree 12 | 13 | ## SYNOPSIS 14 | 15 | Sends output to an interactive tree in the same console window. 16 | 17 | ## SYNTAX 18 | 19 | ```PowerShell 20 | Show-ObjectTree [-InputObject ] [-Title ] [-Filter ] [-MinUi] [-UseNetDriver] [] 21 | ``` 22 | 23 | ## DESCRIPTION 24 | 25 | The **Show-ObjectTree** cmdlet sends the output from a command to a tree view window where the output is displayed in an interactive tree. 26 | 27 | You can use the following features of the tree to examine your data: 28 | 29 | - Quick Filter. Use the Filter box at the top of the window to search the text in the tree. You can search for literals or multiple words. You can use the `-Filter` command to pre-populate the Filter box. The filter uses regular expressions. 30 | 31 | For instructions for using these features, type `Get-Help Show-ObjectTree -Full` and see How to Use the Tree View Window Features in the Notes section. 32 | 33 | ## EXAMPLES 34 | 35 | ### Example 1: Output processes to a tree view 36 | 37 | ```PowerShell 38 | PS C:\> Get-Process | Show-ObjectTree 39 | ``` 40 | 41 | This command gets the processes running on the local computer and sends them to a tree view window. 42 | 43 | ### Example 2: Save output to a variable, and then output a tree view 44 | 45 | ```PowerShell 46 | PS C:\> ($A = Get-ChildItem -Path $pshome -Recurse) | shot 47 | ``` 48 | 49 | This command saves its output in a variable and sends it to **Show-ObjectTree**. 50 | 51 | The command uses the Get-ChildItem cmdlet to get the files in the Windows PowerShell installation directory and its subdirectories. 52 | The path to the installation directory is saved in the $pshome automatic variable. 53 | 54 | The command uses the assignment operator (=) to save the output in the $A variable and the pipeline operator (|) to send the output to **Show-ObjectTree**. 55 | 56 | The parentheses in the command establish the order of operations. 57 | As a result, the output from the Get-ChildItem command is saved in the $A variable before it is sent to **Show-ObjectTree**. 58 | 59 | ## PARAMETERS 60 | 61 | ### -Filter 62 | Pre-populates the Filter edit box, allowing filtering to be specified on the command line. 63 | 64 | ```yaml 65 | Type: String 66 | Parameter Sets: (All) 67 | Aliases: 68 | 69 | Required: False 70 | Position: Named 71 | Default value: None 72 | Accept pipeline input: False 73 | Accept wildcard characters: False 74 | ``` 75 | 76 | ### -InputObject 77 | Specifies that the cmdlet accepts input for **Show-ObjectTree**. 78 | 79 | When you use the **InputObject** parameter to send a collection of objects to **Show-ObjectTree**, **Show-ObjectTree** treats the collection as one collection object, and it displays one row that represents the collection. 80 | 81 | To display the each object in the collection, use a pipeline operator (|) to send objects to **Show-ObjectTree**. 82 | 83 | ```yaml 84 | Type: PSObject 85 | Parameter Sets: (All) 86 | Aliases: 87 | 88 | Required: False 89 | Position: Named 90 | Default value: None 91 | Accept pipeline input: True (ByValue) 92 | Accept wildcard characters: False 93 | ``` 94 | 95 | ### -Title 96 | Specifies the text that appears in the title bar of the **Show-ObjectTree** window. 97 | 98 | By default, the title bar displays the command that invokes **Show-ObjectTree**. 99 | 100 | ```yaml 101 | Type: String 102 | Parameter Sets: (All) 103 | Aliases: 104 | 105 | Required: False 106 | Position: Named 107 | Default value: None 108 | Accept pipeline input: False 109 | Accept wildcard characters: False 110 | ``` 111 | 112 | ### -MinUi 113 | If specified no window frame, filter box, or status bar will be displayed in the **Show-ObjectTree** window. 114 | 115 | ```yaml 116 | Type: SwitchParameter 117 | Parameter Sets: (All) 118 | Aliases: 119 | 120 | Required: False 121 | Position: Named 122 | Default value: None 123 | Accept pipeline input: False 124 | Accept wildcard characters: False 125 | ``` 126 | 127 | ### CommonParameters 128 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). 129 | 130 | ## INPUTS 131 | 132 | ### System.Management.Automation.PSObject 133 | 134 | You can send any object to this cmdlet. 135 | 136 | ## OUTPUTS 137 | 138 | ### None 139 | 140 | `Show-ObjectTree` does not output any objects. 141 | 142 | ## NOTES 143 | 144 | * The command output that you send to **Show-ObjectTree** should not be formatted, such as by using the Format-Table or Format-Wide cmdlets. To select properties, use the Select-Object cmdlet. 145 | 146 | * Deserialized output from remote commands might not be formatted correctly in the tree view window. 147 | 148 | ## RELATED LINKS 149 | 150 | [Out-File](Out-File.md) 151 | 152 | [Out-Printer](Out-Printer.md) 153 | 154 | [Out-String](Out-String.md) 155 | -------------------------------------------------------------------------------- /docs/Microsoft.PowerShell.ConsoleGuiTools/ocgv.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/ConsoleGuiTools/bb9e46800c1efbb6a617141016e8e89206bc0fd7/docs/Microsoft.PowerShell.ConsoleGuiTools/ocgv.gif -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.405", 4 | "rollForward": "latestFeature", 5 | "allowPrerelease": false 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.ConsoleGuiTools/ConsoleGui.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Text; 10 | 11 | using OutGridView.Models; 12 | 13 | using Terminal.Gui; 14 | 15 | namespace OutGridView.Cmdlet 16 | { 17 | internal sealed class ConsoleGui : IDisposable 18 | { 19 | private const string FILTER_LABEL = "Filter"; 20 | // This adjusts the left margin of all controls 21 | private const int MARGIN_LEFT = 1; 22 | // Width of Terminal.Gui ListView selection/check UI elements (old == 4, new == 2) 23 | private const int CHECK_WIDTH = 2; 24 | private bool _cancelled; 25 | private Label _filterLabel; 26 | private TextField _filterField; 27 | private ListView _listView; 28 | // _inputSource contains the full set of Input data and tracks any items the user 29 | // marks. When the cmdlet exits, any marked items are returned. When a filter is 30 | // active, the list view shows a copy of _inputSource that includes both the items 31 | // matching the filter AND any items previously marked. 32 | private GridViewDataSource _inputSource; 33 | 34 | // _listViewSource is a filtered copy of _inputSource that ListView.Source is set to. 35 | // Changes to IsMarked are propogated back to _inputSource. 36 | private GridViewDataSource _listViewSource; 37 | private ApplicationData _applicationData; 38 | private GridViewDetails _gridViewDetails; 39 | 40 | public HashSet Start(ApplicationData applicationData) 41 | { 42 | _applicationData = applicationData; 43 | // Note, in Terminal.Gui v2, this property is renamed to Application.UseNetDriver, hence 44 | // using that terminology here. 45 | Application.UseSystemConsole = _applicationData.UseNetDriver; 46 | Application.Init(); 47 | _gridViewDetails = new GridViewDetails 48 | { 49 | // If OutputMode is Single or Multiple, then we make items selectable. If we make them selectable, 50 | // 2 columns are required for the check/selection indicator and space. 51 | ListViewOffset = _applicationData.OutputMode != OutputModeOption.None ? MARGIN_LEFT + CHECK_WIDTH : MARGIN_LEFT 52 | }; 53 | 54 | Window win = CreateTopLevelWindow(); 55 | 56 | // Create the headers and calculate column widths based on the DataTable 57 | List gridHeaders = _applicationData.DataTable.DataColumns.Select((c) => c.Label).ToList(); 58 | CalculateColumnWidths(gridHeaders); 59 | 60 | // Copy the input DataTable into our master ListView source list; upon exit any items 61 | // that are IsMarked are returned (if Outputmode is set) 62 | _inputSource = LoadData(); 63 | 64 | if (!_applicationData.MinUI) 65 | { 66 | // Add Filter UI 67 | AddFilter(win); 68 | // Add Header UI 69 | AddHeaders(win, gridHeaders); 70 | } 71 | 72 | // Add ListView 73 | AddListView(win); 74 | 75 | // Status bar is where our key-bindings are handled 76 | AddStatusBar(!_applicationData.MinUI); 77 | 78 | // We *always* apply a filter, even if the -Filter parameter is not set or Filtering is not 79 | // available. The ListView always shows a fitlered version of _inputSource even if there is no 80 | // actual fitler. 81 | ApplyFilter(); 82 | 83 | _listView.SetFocus(); 84 | 85 | // Run the GUI. 86 | Application.Run(); 87 | Application.Shutdown(); 88 | 89 | // Return results of selection if required. 90 | HashSet selectedIndexes = new HashSet(); 91 | if (_cancelled) 92 | { 93 | return selectedIndexes; 94 | } 95 | 96 | // Return any items that were selected. 97 | foreach (GridViewRow gvr in _inputSource.GridViewRowList) 98 | { 99 | if (gvr.IsMarked) 100 | { 101 | selectedIndexes.Add(gvr.OriginalIndex); 102 | } 103 | } 104 | 105 | return selectedIndexes; 106 | } 107 | 108 | private GridViewDataSource LoadData() 109 | { 110 | var items = new List(); 111 | int newIndex = 0; 112 | for (int i = 0; i < _applicationData.DataTable.Data.Count; i++) 113 | { 114 | var dataTableRow = _applicationData.DataTable.Data[i]; 115 | var valueList = new List(); 116 | foreach (var dataTableColumn in _applicationData.DataTable.DataColumns) 117 | { 118 | string dataValue = dataTableRow.Values[dataTableColumn.ToString()].DisplayValue; 119 | valueList.Add(dataValue); 120 | } 121 | 122 | string displayString = GridViewHelpers.GetPaddedString(valueList, 0, _gridViewDetails.ListViewColumnWidths); 123 | 124 | items.Add(new GridViewRow 125 | { 126 | DisplayString = displayString, 127 | // We use this to keep _inputSource up to date when a filter is applied 128 | OriginalIndex = i 129 | }); 130 | 131 | newIndex++; 132 | } 133 | 134 | return new GridViewDataSource(items); 135 | } 136 | 137 | private void ApplyFilter() 138 | { 139 | // The ListView is always filled with a (filtered) copy of _inputSource. 140 | // We listen for `MarkChanged` events on this filtered list and apply those changes up to _inputSource. 141 | 142 | if (_listViewSource != null) 143 | { 144 | _listViewSource.MarkChanged -= ListViewSource_MarkChanged; 145 | _listViewSource = null; 146 | } 147 | 148 | _listViewSource = new GridViewDataSource(GridViewHelpers.FilterData(_inputSource.GridViewRowList, _applicationData.Filter ?? string.Empty)); 149 | _listViewSource.MarkChanged += ListViewSource_MarkChanged; 150 | _listView.Source = _listViewSource; 151 | } 152 | 153 | private void ListViewSource_MarkChanged(object s, GridViewDataSource.RowMarkedEventArgs a) 154 | { 155 | _inputSource.GridViewRowList[a.Row.OriginalIndex].IsMarked = a.Row.IsMarked; 156 | } 157 | 158 | private static void Accept() 159 | { 160 | Application.RequestStop(); 161 | } 162 | 163 | private void Close() 164 | { 165 | _cancelled = true; 166 | Application.RequestStop(); 167 | } 168 | 169 | private Window CreateTopLevelWindow() 170 | { 171 | // Creates the top-level window to show 172 | var win = new Window(_applicationData.Title) 173 | { 174 | X = _applicationData.MinUI ? -1 : 0, 175 | Y = _applicationData.MinUI ? -1 : 0, 176 | 177 | // By using Dim.Fill(), it will automatically resize without manual intervention 178 | Width = Dim.Fill(_applicationData.MinUI ? -1 : 0), 179 | Height = Dim.Fill(_applicationData.MinUI ? -1 : 1) 180 | }; 181 | 182 | if (_applicationData.MinUI) 183 | { 184 | win.Border.BorderStyle = BorderStyle.None; 185 | } 186 | 187 | Application.Top.Add(win); 188 | return win; 189 | } 190 | 191 | private void AddStatusBar(bool visible) 192 | { 193 | var statusItems = new List(); 194 | if (_applicationData.OutputMode != OutputModeOption.None) 195 | { 196 | // Use Key.Unknown for SPACE with no delegate because ListView already 197 | // handles SPACE 198 | statusItems.Add(new StatusItem(Key.Unknown, "~SPACE~ Select Item", null)); 199 | } 200 | 201 | if (_applicationData.OutputMode == OutputModeOption.Multiple) 202 | { 203 | statusItems.Add(new StatusItem(Key.A | Key.CtrlMask, "~CTRL-A~ Select All", () => 204 | { 205 | // This selects only the items that match the Filter 206 | var gvds = _listView.Source as GridViewDataSource; 207 | gvds.GridViewRowList.ForEach(i => i.IsMarked = true); 208 | _listView.SetNeedsDisplay(); 209 | })); 210 | 211 | // Ctrl-D is commonly used in GUIs for select-none 212 | statusItems.Add(new StatusItem(Key.D | Key.CtrlMask, "~CTRL-D~ Select None", () => 213 | { 214 | // This un-selects only the items that match the Filter 215 | var gvds = _listView.Source as GridViewDataSource; 216 | gvds.GridViewRowList.ForEach(i => i.IsMarked = false); 217 | _listView.SetNeedsDisplay(); 218 | })); 219 | } 220 | 221 | if (_applicationData.OutputMode != OutputModeOption.None) 222 | { 223 | statusItems.Add(new StatusItem(Key.Enter, "~ENTER~ Accept", () => 224 | { 225 | if (Application.Top.MostFocused == _listView) 226 | { 227 | // If nothing was explicitly marked, we return the item that was selected 228 | // when ENTER is pressed in Single mode. If something was previously selected 229 | // (using SPACE) then honor that as the single item to return 230 | if (_applicationData.OutputMode == OutputModeOption.Single && 231 | _inputSource.GridViewRowList.Find(i => i.IsMarked) == null) 232 | { 233 | _listView.MarkUnmarkRow(); 234 | } 235 | Accept(); 236 | } 237 | else if (Application.Top.MostFocused == _filterField) 238 | { 239 | _listView.SetFocus(); 240 | } 241 | })); 242 | } 243 | 244 | statusItems.Add(new StatusItem(Key.Esc, "~ESC~ Close", () => Close())); 245 | if (_applicationData.Verbose || _applicationData.Debug) 246 | { 247 | statusItems.Add(new StatusItem(Key.Null, $" v{_applicationData.ModuleVersion}", null)); 248 | statusItems.Add(new StatusItem(Key.Null, 249 | $"{Application.Driver} v{FileVersionInfo.GetVersionInfo(Assembly.GetAssembly(typeof(Application)).Location).ProductVersion}", null)); 250 | } 251 | 252 | var statusBar = new StatusBar(statusItems.ToArray()); 253 | statusBar.Visible = visible; 254 | Application.Top.Add(statusBar); 255 | } 256 | 257 | private void CalculateColumnWidths(List gridHeaders) 258 | { 259 | _gridViewDetails.ListViewColumnWidths = new int[gridHeaders.Count]; 260 | var listViewColumnWidths = _gridViewDetails.ListViewColumnWidths; 261 | 262 | for (int i = 0; i < gridHeaders.Count; i++) 263 | { 264 | listViewColumnWidths[i] = gridHeaders[i].Length; 265 | } 266 | 267 | // calculate the width of each column based on longest string in each column for each row 268 | foreach (var row in _applicationData.DataTable.Data) 269 | { 270 | int index = 0; 271 | 272 | // use half of the visible buffer height for the number of objects to inspect to calculate widths 273 | foreach (var col in row.Values.Take(Application.Top.Frame.Height / 2)) 274 | { 275 | var len = col.Value.DisplayValue.Length; 276 | if (len > listViewColumnWidths[index]) 277 | { 278 | listViewColumnWidths[index] = len; 279 | } 280 | index++; 281 | } 282 | } 283 | 284 | // if the total width is wider than the usable width, remove 1 from widest column until it fits 285 | _gridViewDetails.UsableWidth = Application.Top.Frame.Width - MARGIN_LEFT - listViewColumnWidths.Length - _gridViewDetails.ListViewOffset; 286 | int columnWidthsSum = listViewColumnWidths.Sum(); 287 | while (columnWidthsSum >= _gridViewDetails.UsableWidth) 288 | { 289 | int maxWidth = 0; 290 | int maxIndex = 0; 291 | for (int i = 0; i < listViewColumnWidths.Length; i++) 292 | { 293 | if (listViewColumnWidths[i] > maxWidth) 294 | { 295 | maxWidth = listViewColumnWidths[i]; 296 | maxIndex = i; 297 | } 298 | } 299 | 300 | listViewColumnWidths[maxIndex]--; 301 | columnWidthsSum--; 302 | } 303 | } 304 | 305 | private void AddFilter(Window win) 306 | { 307 | _filterLabel = new Label(FILTER_LABEL) 308 | { 309 | X = MARGIN_LEFT, 310 | Y = 0 311 | }; 312 | 313 | _filterField = new TextField(_applicationData.Filter ?? string.Empty) 314 | { 315 | X = Pos.Right(_filterLabel) + 1, 316 | Y = Pos.Top(_filterLabel), 317 | CanFocus = true, 318 | Width = Dim.Fill() - 1 319 | }; 320 | 321 | // TextField captures Ctrl-A (select all text) and Ctrl-D (delete backwards) 322 | // In OCGV these are used for select-all/none of items. Selecting items is more 323 | // common than editing the filter field so we turn them off in the filter textview. 324 | // BACKSPACE still works for delete backwards 325 | _filterField.ClearKeybinding(Key.A | Key.CtrlMask); 326 | _filterField.ClearKeybinding(Key.D | Key.CtrlMask); 327 | 328 | var filterErrorLabel = new Label(string.Empty) 329 | { 330 | X = Pos.Right(_filterLabel) + 1, 331 | Y = Pos.Top(_filterLabel) + 1, 332 | ColorScheme = Colors.Base, 333 | Width = Dim.Fill() - _filterLabel.Text.Length 334 | }; 335 | 336 | _filterField.TextChanged += (str) => 337 | { 338 | // str is the OLD value 339 | string filterText = _filterField.Text?.ToString(); 340 | try 341 | { 342 | filterErrorLabel.Text = " "; 343 | filterErrorLabel.ColorScheme = Colors.Base; 344 | filterErrorLabel.Redraw(filterErrorLabel.Bounds); 345 | _applicationData.Filter = filterText; 346 | ApplyFilter(); 347 | 348 | } 349 | catch (Exception ex) 350 | { 351 | filterErrorLabel.Text = ex.Message; 352 | filterErrorLabel.ColorScheme = Colors.Error; 353 | filterErrorLabel.Redraw(filterErrorLabel.Bounds); 354 | } 355 | }; 356 | 357 | win.Add(_filterLabel, _filterField, filterErrorLabel); 358 | 359 | _filterField.Text = _applicationData.Filter ?? string.Empty; 360 | _filterField.CursorPosition = _filterField.Text.Length; 361 | } 362 | 363 | private void AddHeaders(Window win, List gridHeaders) 364 | { 365 | var header = new Label(GridViewHelpers.GetPaddedString( 366 | gridHeaders, 367 | _gridViewDetails.ListViewOffset, 368 | _gridViewDetails.ListViewColumnWidths)); 369 | header.X = 0; 370 | if (_applicationData.MinUI) 371 | { 372 | header.Y = 0; 373 | } 374 | else 375 | { 376 | header.Y = 2; 377 | } 378 | win.Add(header); 379 | 380 | // This renders dashes under the header to make it more clear what is header and what is data 381 | var headerLineText = new StringBuilder(); 382 | foreach (char c in header.Text) 383 | { 384 | if (c.Equals(' ')) 385 | { 386 | headerLineText.Append(' '); 387 | } 388 | else 389 | { 390 | // When gui.cs supports text decorations, should replace this with just underlining the header 391 | headerLineText.Append('-'); 392 | } 393 | } 394 | 395 | if (!_applicationData.MinUI) 396 | { 397 | var headerLine = new Label(headerLineText.ToString()) 398 | { 399 | X = 0, 400 | Y = Pos.Bottom(header) 401 | }; 402 | win.Add(headerLine); 403 | } 404 | } 405 | 406 | private void AddListView(Window win) 407 | { 408 | _listView = new ListView(_inputSource); 409 | _listView.X = MARGIN_LEFT; 410 | if (!_applicationData.MinUI) 411 | { 412 | _listView.Y = Pos.Bottom(_filterLabel) + 3; // 1 for space, 1 for header, 1 for header underline 413 | } 414 | else 415 | { 416 | _listView.Y = 1; // 1 for space, 1 for header, 1 for header underline 417 | } 418 | _listView.Width = Dim.Fill(1); 419 | _listView.Height = Dim.Fill(); 420 | _listView.AllowsMarking = _applicationData.OutputMode != OutputModeOption.None; 421 | _listView.AllowsMultipleSelection = _applicationData.OutputMode == OutputModeOption.Multiple; 422 | _listView.AddKeyBinding(Key.Space, Command.ToggleChecked, Command.LineDown); 423 | 424 | win.Add(_listView); 425 | } 426 | 427 | public void Dispose() 428 | { 429 | if (!Console.IsInputRedirected) 430 | { 431 | // By emitting this, we fix two issues: 432 | // 1. An issue where arrow keys don't work in the console because .NET 433 | // requires application mode to support Arrow key escape sequences. 434 | // Esc[?1h sets the cursor key to application mode 435 | // See http://ascii-table.com/ansi-escape-sequences-vt-100.php 436 | // 2. An issue where moving the mouse causes characters to show up because 437 | // mouse tracking is still on. Esc[?1003l turns it off. 438 | // See https://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking 439 | Console.Write("\u001b[?1h\u001b[?1003l"); 440 | } 441 | } 442 | } 443 | } 444 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDataSource.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | using NStack; 9 | 10 | using Terminal.Gui; 11 | 12 | namespace OutGridView.Cmdlet 13 | { 14 | internal sealed class GridViewDataSource : IListDataSource 15 | { 16 | public List GridViewRowList { get; set; } 17 | 18 | public int Count => GridViewRowList.Count; 19 | 20 | public GridViewDataSource(List itemList) 21 | { 22 | GridViewRowList = itemList; 23 | } 24 | 25 | public int Length { get; } 26 | 27 | public void Render(ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start) 28 | { 29 | container.Move(col, line); 30 | RenderUstr(driver, GridViewRowList[item].DisplayString, col, line, width); 31 | } 32 | 33 | public bool IsMarked(int item) => GridViewRowList[item].IsMarked; 34 | 35 | public void SetMark(int item, bool value) 36 | { 37 | var oldValue = GridViewRowList[item].IsMarked; 38 | GridViewRowList[item].IsMarked = value; 39 | var args = new RowMarkedEventArgs() 40 | { 41 | Row = GridViewRowList[item], 42 | OldValue = oldValue 43 | }; 44 | MarkChanged?.Invoke(this, args); 45 | } 46 | 47 | public sealed class RowMarkedEventArgs : EventArgs 48 | { 49 | public GridViewRow Row { get; set; } 50 | public bool OldValue { get; set; } 51 | 52 | } 53 | 54 | public event EventHandler MarkChanged; 55 | 56 | public IList ToList() 57 | { 58 | return GridViewRowList; 59 | } 60 | 61 | // A slightly adapted method from gui.cs: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461 62 | private static void RenderUstr(ConsoleDriver driver, ustring ustr, int col, int line, int width) 63 | { 64 | int used = 0; 65 | int index = 0; 66 | while (index < ustr.Length) 67 | { 68 | (var rune, var size) = Utf8.DecodeRune(ustr, index, index - ustr.Length); 69 | var count = Rune.ColumnWidth(rune); 70 | if (used + count > width) break; 71 | driver.AddRune(rune); 72 | used += count; 73 | index += size; 74 | } 75 | 76 | while (used < width) 77 | { 78 | driver.AddRune(' '); 79 | used++; 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDetails.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace OutGridView.Cmdlet 5 | { 6 | internal sealed class GridViewDetails 7 | { 8 | // Contains the width of each column in the grid view. 9 | public int[] ListViewColumnWidths { get; set; } 10 | 11 | // Dictates where the header should actually start considering 12 | // some offset is needed to factor in the checkboxes 13 | public int ListViewOffset { get; set; } 14 | 15 | // The width that is actually useable on the screen after 16 | // subtracting space needed for a clean UI (spaces between columns, etc). 17 | public int UsableWidth { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.ConsoleGuiTools/GridViewHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace OutGridView.Cmdlet 10 | { 11 | internal sealed class GridViewHelpers 12 | { 13 | // Add all items already selected plus any that match the filter 14 | // The selected items should be at the top of the list, in their original order 15 | public static List FilterData(List listToFilter, string filter) 16 | { 17 | var filteredList = new List(); 18 | if (string.IsNullOrEmpty(filter)) 19 | { 20 | return listToFilter; 21 | } 22 | 23 | filteredList.AddRange(listToFilter.Where(gvr => gvr.IsMarked)); 24 | filteredList.AddRange(listToFilter.Where(gvr => !gvr.IsMarked && Regex.IsMatch(gvr.DisplayString, filter, RegexOptions.IgnoreCase))); 25 | 26 | return filteredList; 27 | } 28 | 29 | public static string GetPaddedString(List strings, int offset, int[] listViewColumnWidths) 30 | { 31 | var builder = new StringBuilder(); 32 | if (offset > 0) 33 | { 34 | builder.Append(string.Empty.PadRight(offset)); 35 | } 36 | 37 | for (int i = 0; i < strings.Count; i++) 38 | { 39 | if (i > 0) 40 | { 41 | // Add a space between columns 42 | builder.Append(' '); 43 | } 44 | 45 | // Replace any newlines with encoded newline/linefeed (`n or `r) 46 | // Note we can't use Environment.Newline because we don't know that the 47 | // Command honors that. 48 | strings[i] = strings[i].Replace("\r", "`r"); 49 | strings[i] = strings[i].Replace("\n", "`n"); 50 | 51 | // If the string won't fit in the column, append an ellipsis. 52 | if (strings[i].Length > listViewColumnWidths[i]) 53 | { 54 | builder.Append(strings[i], 0, listViewColumnWidths[i] - 3); 55 | builder.Append("..."); 56 | } 57 | else 58 | { 59 | builder.Append(strings[i].PadRight(listViewColumnWidths[i])); 60 | } 61 | } 62 | 63 | return builder.ToString(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.ConsoleGuiTools/GridViewRow.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | namespace OutGridView.Cmdlet 5 | { 6 | public class GridViewRow 7 | { 8 | public string DisplayString { get; set; } 9 | public bool IsMarked { get; set; } 10 | public int OriginalIndex { get; set; } 11 | public override string ToString() => DisplayString; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | true 33 | true 34 | Recommended 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) Microsoft. 3 | # Licensed under the MIT license. 4 | # 5 | 6 | @{ 7 | 8 | # Script module or binary module file associated with this manifest. 9 | RootModule = 'Microsoft.PowerShell.ConsoleGuiTools.dll' 10 | 11 | # Version number of this module. 12 | ModuleVersion = '0.7.7' 13 | 14 | # Supported PSEditions 15 | CompatiblePSEditions = @( 'Core' ) 16 | 17 | # ID used to uniquely identify this module 18 | GUID = '06028f35-8304-4460-ae73-306741982afe' 19 | 20 | # Author of this module 21 | Author = 'PowerShell Team' 22 | 23 | # Company or vendor of this module 24 | CompanyName = 'Microsoft' 25 | 26 | # Copyright statement for this module 27 | Copyright = '(c) Microsoft Corporation.' 28 | 29 | # Description of the functionality provided by this module 30 | Description = 'Cross-platform Console GUI Tools for PowerShell' 31 | 32 | # Minimum version of the PowerShell engine required by this module 33 | PowerShellVersion = '7.2' 34 | 35 | # Name of the PowerShell host required by this module 36 | # PowerShellHostName = '' 37 | 38 | # Minimum version of the PowerShell host required by this module 39 | # PowerShellHostVersion = '' 40 | 41 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 42 | # DotNetFrameworkVersion = '' 43 | 44 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # CLRVersion = '' 46 | 47 | # Processor architecture (None, X86, Amd64) required by this module 48 | # ProcessorArchitecture = '' 49 | 50 | # Modules that must be imported into the global environment prior to importing this module 51 | # RequiredModules = @() 52 | 53 | # Assemblies that must be loaded prior to importing this module 54 | # RequiredAssemblies = @() 55 | 56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 57 | # ScriptsToProcess = @() 58 | 59 | # Type files (.ps1xml) to be loaded when importing this module 60 | # TypesToProcess = @() 61 | 62 | # Format files (.ps1xml) to be loaded when importing this module 63 | # FormatsToProcess = @() 64 | 65 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 66 | NestedModules = @() 67 | 68 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 69 | FunctionsToExport = @() 70 | 71 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 72 | CmdletsToExport = @( 'Out-ConsoleGridView', 'Show-ObjectTree' ) 73 | 74 | # Variables to export from this module 75 | VariablesToExport = '*' 76 | 77 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 78 | AliasesToExport = @( 'ocgv', 'shot' ) 79 | 80 | # DSC resources to export from this module 81 | # DscResourcesToExport = @() 82 | 83 | # List of all modules packaged with this module 84 | # ModuleList = @() 85 | 86 | # List of all files packaged with this module 87 | # FileList = @() 88 | 89 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 90 | PrivateData = @{ 91 | 92 | PSData = @{ 93 | 94 | # Tags applied to this module. These help with module discovery in online galleries. 95 | Tags = @('Console', 'Gui', 'Out-GridView', 'Out-ConsoleGridView', 'Show-ObjectTree', 'Terminal.Gui', 'gui.cs', 'MacOS', 'Windows', 'Linux', 'PSEdition_Core') 96 | 97 | # A URL to the license for this module. 98 | LicenseUri = 'https://github.com/PowerShell/ConsoleGuiTools/blob/master/LICENSE.txt' 99 | 100 | # A URL to the main website for this project. 101 | ProjectUri = 'https://github.com/PowerShell/ConsoleGuiTools/' 102 | 103 | # A URL to an icon representing this module. 104 | # IconUri = '' 105 | 106 | # ReleaseNotes of this module 107 | ReleaseNotes = 'Please see release notes at: https://github.com/PowerShell/ConsoleGuiTools/releases' 108 | 109 | # Prerelease string of this module 110 | # Prerelease = '' 111 | 112 | # Flag to indicate whether the module requires explicit user acceptance for install/update/save 113 | RequireLicenseAcceptance = 'false' 114 | 115 | # External dependent modules of this module 116 | # ExternalModuleDependencies = @() 117 | 118 | } # End of PSData hashtable 119 | 120 | } # End of PrivateData hashtable 121 | 122 | # HelpInfo URI of this module 123 | # HelpInfoURI = '' 124 | 125 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 126 | # DefaultCommandPrefix = '' 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.ConsoleGuiTools/OutConsoleGridviewCmdletCommand.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Management.Automation; 8 | using System.Management.Automation.Internal; 9 | 10 | using OutGridView.Models; 11 | 12 | namespace OutGridView.Cmdlet 13 | { 14 | [Cmdlet(VerbsData.Out, "ConsoleGridView")] 15 | [Alias("ocgv")] 16 | public class OutConsoleGridViewCmdletCommand : PSCmdlet, IDisposable 17 | { 18 | #region Properties 19 | 20 | private const string DataNotQualifiedForGridView = nameof(DataNotQualifiedForGridView); 21 | private const string EnvironmentNotSupportedForGridView = nameof(EnvironmentNotSupportedForGridView); 22 | 23 | private List _psObjects = new List(); 24 | private ConsoleGui _consoleGui = new ConsoleGui(); 25 | 26 | #endregion Properties 27 | 28 | #region Input Parameters 29 | 30 | /// 31 | /// This parameter specifies the current pipeline object. 32 | /// 33 | [Parameter(ValueFromPipeline = true, HelpMessage = "Specifies the input pipeline object")] 34 | public PSObject InputObject { get; set; } = AutomationNull.Value; 35 | 36 | /// 37 | /// Gets/sets the title of the Out-GridView window. 38 | /// 39 | [Parameter(HelpMessage = "Specifies the text that appears in the title bar of the Out-ConsoleGridView window. y default, the title bar displays the command that invokes Out-ConsoleGridView.")] 40 | [ValidateNotNullOrEmpty] 41 | public string Title { get; set; } 42 | 43 | /// 44 | /// Get or sets a value indicating whether the selected items should be written to the pipeline 45 | /// and if it should be possible to select multiple or single list items. 46 | /// 47 | [Parameter(HelpMessage = "Determines whether a single item (Single), multiple items (Multiple; default), or no items (None) will be written to the pipeline. Also determines selection behavior in the GUI.")] 48 | public OutputModeOption OutputMode { set; get; } = OutputModeOption.Multiple; 49 | 50 | /// 51 | /// gets or sets the initial value for the filter in the GUI 52 | /// 53 | [Parameter(HelpMessage = "Pre-populates the Filter edit box, allowing filtering to be specified on the command line. The filter uses regular expressions.")] 54 | public string Filter { set; get; } 55 | 56 | /// 57 | /// gets or sets the whether "minimum UI" mode will be enabled 58 | /// 59 | [Parameter(HelpMessage = "If specified no window frame, filter box, or status bar will be displayed in the GUI.")] 60 | public SwitchParameter MinUI { set; get; } 61 | 62 | /// 63 | /// gets or sets the whether the Terminal.Gui System.Net.Console-based ConsoleDriver will be used instead of the 64 | /// default platform-specific (Windows or Curses) ConsoleDriver. 65 | /// 66 | [Parameter(HelpMessage = "If specified the Terminal.Gui System.Net.Console-based ConsoleDriver (NetDriver) will be used.")] 67 | public SwitchParameter UseNetDriver { set; get; } 68 | 69 | /// 70 | /// For the -Verbose switch 71 | /// 72 | public bool Verbose => MyInvocation.BoundParameters.TryGetValue("Verbose", out var o); 73 | 74 | /// 75 | /// For the -Debug switch 76 | /// 77 | public bool Debug => MyInvocation.BoundParameters.TryGetValue("Debug", out var o); 78 | 79 | #endregion Input Parameters 80 | 81 | // This method gets called once for each cmdlet in the pipeline when the pipeline starts executing 82 | protected override void BeginProcessing() 83 | { 84 | if (Console.IsInputRedirected) 85 | { 86 | ErrorRecord error = new ErrorRecord( 87 | new PSNotSupportedException("Not supported in this environment (when input is redirected)."), 88 | EnvironmentNotSupportedForGridView, 89 | ErrorCategory.NotImplemented, 90 | null); 91 | 92 | ThrowTerminatingError(error); 93 | } 94 | } 95 | 96 | // This method will be called for each input received from the pipeline to this cmdlet; if no input is received, this method is not called 97 | protected override void ProcessRecord() 98 | { 99 | if (InputObject == null || InputObject == AutomationNull.Value) 100 | { 101 | return; 102 | } 103 | 104 | if (InputObject.BaseObject is IDictionary dictionary) 105 | { 106 | // Dictionaries should be enumerated through because the pipeline does not enumerate through them. 107 | foreach (DictionaryEntry entry in dictionary) 108 | { 109 | ProcessObject(PSObject.AsPSObject(entry)); 110 | } 111 | } 112 | else 113 | { 114 | ProcessObject(InputObject); 115 | } 116 | } 117 | 118 | private void ProcessObject(PSObject input) 119 | { 120 | 121 | object baseObject = input.BaseObject; 122 | 123 | // Throw a terminating error for types that are not supported. 124 | if (baseObject is ScriptBlock || 125 | baseObject is SwitchParameter || 126 | baseObject is PSReference || 127 | baseObject is PSObject) 128 | { 129 | ErrorRecord error = new ErrorRecord( 130 | new FormatException("Invalid data type for Out-ConsoleGridView"), 131 | DataNotQualifiedForGridView, 132 | ErrorCategory.InvalidType, 133 | null); 134 | 135 | ThrowTerminatingError(error); 136 | } 137 | 138 | _psObjects.Add(input); 139 | } 140 | 141 | // This method will be called once at the end of pipeline execution; if no input is received, this method is not called 142 | protected override void EndProcessing() 143 | { 144 | base.EndProcessing(); 145 | 146 | //Return if no objects 147 | if (_psObjects.Count == 0) 148 | { 149 | return; 150 | } 151 | 152 | var TG = new TypeGetter(this); 153 | 154 | var dataTable = TG.CastObjectsToTableView(_psObjects); 155 | var applicationData = new ApplicationData 156 | { 157 | Title = Title ?? "Out-ConsoleGridView", 158 | OutputMode = OutputMode, 159 | Filter = Filter, 160 | MinUI = MinUI, 161 | DataTable = dataTable, 162 | UseNetDriver = UseNetDriver, 163 | Verbose = Verbose, 164 | Debug = Debug, 165 | ModuleVersion = MyInvocation.MyCommand.Version.ToString() 166 | }; 167 | 168 | 169 | var selectedIndexes = _consoleGui.Start(applicationData); 170 | foreach (int idx in selectedIndexes) 171 | { 172 | var selectedObject = _psObjects[idx]; 173 | if (selectedObject == null) 174 | { 175 | continue; 176 | } 177 | WriteObject(selectedObject, false); 178 | } 179 | } 180 | 181 | public void Dispose() 182 | { 183 | _consoleGui.Dispose(); 184 | GC.SuppressFinalize(this); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Management.Automation; 8 | using System.Management.Automation.Internal; 9 | 10 | using OutGridView.Models; 11 | 12 | namespace OutGridView.Cmdlet 13 | { 14 | [Cmdlet("Show", "ObjectTree")] 15 | [Alias("shot")] 16 | public class ShowObjectTreeCmdletCommand : PSCmdlet, IDisposable 17 | { 18 | #region Properties 19 | 20 | private const string DataNotQualifiedForShowObjectTree = nameof(DataNotQualifiedForShowObjectTree); 21 | private const string EnvironmentNotSupportedForShowObjectTree = nameof(EnvironmentNotSupportedForShowObjectTree); 22 | 23 | private List _psObjects = new List(); 24 | 25 | #endregion Properties 26 | 27 | #region Input Parameters 28 | 29 | /// 30 | /// This parameter specifies the current pipeline object. 31 | /// 32 | [Parameter(ValueFromPipeline = true, HelpMessage = "Specifies the input pipeline object")] 33 | public PSObject InputObject { get; set; } = AutomationNull.Value; 34 | 35 | /// 36 | /// Gets/sets the title of the Out-GridView window. 37 | /// 38 | [Parameter(HelpMessage = "Specifies the text that appears in the title bar of the Out-ConsoleGridView window. y default, the title bar displays the command that invokes Out-ConsoleGridView.")] 39 | [ValidateNotNullOrEmpty] 40 | public string Title { get; set; } 41 | 42 | /// 43 | /// gets or sets the initial value for the filter in the GUI 44 | /// 45 | [Parameter(HelpMessage = "Pre-populates the Filter edit box, allowing filtering to be specified on the command line. The filter uses regular expressions.")] 46 | public string Filter { set; get; } 47 | 48 | /// 49 | /// gets or sets the whether "minimum UI" mode will be enabled 50 | /// 51 | [Parameter(HelpMessage = "If specified no window frame, filter box, or status bar will be displayed in the GUI.")] 52 | public SwitchParameter MinUI { set; get; } 53 | /// 54 | /// gets or sets the whether the Terminal.Gui System.Net.Console-based ConsoleDriver will be used instead of the 55 | /// default platform-specific (Windows or Curses) ConsoleDriver. 56 | /// 57 | [Parameter(HelpMessage = "If specified the Terminal.Gui System.Net.Console-based ConsoleDriver (NetDriver) will be used.")] 58 | public SwitchParameter UseNetDriver { set; get; } 59 | 60 | /// 61 | /// For the -Debug switch 62 | /// 63 | public bool Debug => MyInvocation.BoundParameters.TryGetValue("Debug", out var o); 64 | 65 | #endregion Input Parameters 66 | 67 | // This method gets called once for each cmdlet in the pipeline when the pipeline starts executing 68 | protected override void BeginProcessing() 69 | { 70 | if (Console.IsInputRedirected) 71 | { 72 | ErrorRecord error = new ErrorRecord( 73 | new PSNotSupportedException("Not supported in this environment (when input is redirected)."), 74 | EnvironmentNotSupportedForShowObjectTree, 75 | ErrorCategory.NotImplemented, 76 | null); 77 | 78 | ThrowTerminatingError(error); 79 | } 80 | } 81 | 82 | // This method will be called for each input received from the pipeline to this cmdlet; if no input is received, this method is not called 83 | protected override void ProcessRecord() 84 | { 85 | if (InputObject == null || InputObject == AutomationNull.Value) 86 | { 87 | return; 88 | } 89 | 90 | if (InputObject.BaseObject is IDictionary dictionary) 91 | { 92 | // Dictionaries should be enumerated through because the pipeline does not enumerate through them. 93 | foreach (DictionaryEntry entry in dictionary) 94 | { 95 | ProcessObject(PSObject.AsPSObject(entry)); 96 | } 97 | } 98 | else 99 | { 100 | ProcessObject(InputObject); 101 | } 102 | } 103 | 104 | private void ProcessObject(PSObject input) 105 | { 106 | 107 | object baseObject = input.BaseObject; 108 | 109 | // Throw a terminating error for types that are not supported. 110 | if (baseObject is ScriptBlock || 111 | baseObject is SwitchParameter || 112 | baseObject is PSReference || 113 | baseObject is PSObject) 114 | { 115 | ErrorRecord error = new ErrorRecord( 116 | new FormatException("Invalid data type for Show-ObjectTree"), 117 | DataNotQualifiedForShowObjectTree, 118 | ErrorCategory.InvalidType, 119 | null); 120 | 121 | ThrowTerminatingError(error); 122 | } 123 | 124 | _psObjects.Add(input); 125 | } 126 | 127 | // This method will be called once at the end of pipeline execution; if no input is received, this method is not called 128 | protected override void EndProcessing() 129 | { 130 | base.EndProcessing(); 131 | 132 | //Return if no objects 133 | if (_psObjects.Count == 0) 134 | { 135 | return; 136 | } 137 | 138 | var applicationData = new ApplicationData 139 | { 140 | Title = Title ?? "Show-ObjectTree", 141 | Filter = Filter, 142 | MinUI = MinUI, 143 | UseNetDriver = UseNetDriver, 144 | Debug = Debug, 145 | ModuleVersion = MyInvocation.MyCommand.Version.ToString() 146 | }; 147 | 148 | ShowObjectView.Run(_psObjects, applicationData); 149 | } 150 | 151 | public void Dispose() 152 | { 153 | GC.SuppressFinalize(this); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Management.Automation; 11 | using System.Reflection; 12 | using System.Text.RegularExpressions; 13 | 14 | using OutGridView.Models; 15 | 16 | using Terminal.Gui; 17 | using Terminal.Gui.Trees; 18 | 19 | namespace OutGridView.Cmdlet 20 | { 21 | internal sealed class ShowObjectView : Window, ITreeBuilder 22 | { 23 | private readonly TreeView tree; 24 | private readonly RegexTreeViewTextFilter filter; 25 | private readonly Label filterErrorLabel; 26 | 27 | public bool SupportsCanExpand => true; 28 | private StatusItem selectedStatusBarItem; 29 | private StatusBar statusBar; 30 | 31 | public ShowObjectView(List rootObjects, ApplicationData applicationData) 32 | { 33 | Title = applicationData.Title; 34 | Width = Dim.Fill(); 35 | Height = Dim.Fill(1); 36 | Modal = false; 37 | 38 | 39 | if (applicationData.MinUI) 40 | { 41 | Border.BorderStyle = BorderStyle.None; 42 | Title = string.Empty; 43 | X = -1; 44 | Height = Dim.Fill(); 45 | } 46 | 47 | tree = new TreeView 48 | { 49 | Y = applicationData.MinUI ? 0 : 2, 50 | Width = Dim.Fill(), 51 | Height = Dim.Fill(), 52 | }; 53 | tree.TreeBuilder = this; 54 | tree.AspectGetter = this.AspectGetter; 55 | tree.SelectionChanged += this.SelectionChanged; 56 | 57 | tree.ClearKeybinding(Command.ExpandAll); 58 | 59 | this.filter = new RegexTreeViewTextFilter(this, tree); 60 | this.filter.Text = applicationData.Filter ?? string.Empty; 61 | tree.Filter = this.filter; 62 | 63 | if (rootObjects.Count > 0) 64 | { 65 | tree.AddObjects(rootObjects); 66 | } 67 | else 68 | { 69 | tree.AddObject("No Objects"); 70 | } 71 | statusBar = new StatusBar(); 72 | 73 | string elementDescription = "objects"; 74 | 75 | var types = rootObjects.Select(o => o.GetType()).Distinct().ToArray(); 76 | if (types.Length == 1) 77 | { 78 | elementDescription = types[0].Name; 79 | } 80 | 81 | var lblFilter = new Label() 82 | { 83 | Text = "Filter:", 84 | X = 1, 85 | }; 86 | var tbFilter = new TextField() 87 | { 88 | X = Pos.Right(lblFilter), 89 | Width = Dim.Fill(1), 90 | Text = applicationData.Filter ?? string.Empty 91 | }; 92 | tbFilter.CursorPosition = tbFilter.Text.Length; 93 | 94 | tbFilter.TextChanged += (_) => 95 | { 96 | filter.Text = tbFilter.Text.ToString(); 97 | }; 98 | 99 | 100 | filterErrorLabel = new Label(string.Empty) 101 | { 102 | X = Pos.Right(lblFilter) + 1, 103 | Y = Pos.Top(lblFilter) + 1, 104 | ColorScheme = Colors.Base, 105 | Width = Dim.Fill() - lblFilter.Text.Length 106 | }; 107 | 108 | if (!applicationData.MinUI) 109 | { 110 | Add(lblFilter); 111 | Add(tbFilter); 112 | Add(filterErrorLabel); 113 | } 114 | 115 | int pos = 0; 116 | statusBar.AddItemAt(pos++, new StatusItem(Key.Esc, "~ESC~ Close", () => Application.RequestStop())); 117 | 118 | var siCount = new StatusItem(Key.Null, $"{rootObjects.Count} {elementDescription}", null); 119 | selectedStatusBarItem = new StatusItem(Key.Null, string.Empty, null); 120 | statusBar.AddItemAt(pos++, siCount); 121 | statusBar.AddItemAt(pos++, selectedStatusBarItem); 122 | 123 | if (applicationData.Debug) 124 | { 125 | statusBar.AddItemAt(pos++, new StatusItem(Key.Null, $" v{applicationData.ModuleVersion}", null)); 126 | statusBar.AddItemAt(pos++, new StatusItem(Key.Null, 127 | $"{Application.Driver} v{FileVersionInfo.GetVersionInfo(Assembly.GetAssembly(typeof(Application)).Location).ProductVersion}", null)); 128 | } 129 | 130 | statusBar.Visible = !applicationData.MinUI; 131 | Application.Top.Add(statusBar); 132 | 133 | Add(tree); 134 | } 135 | private void SetRegexError(string error) 136 | { 137 | if (string.Equals(error, filterErrorLabel.Text.ToString(), StringComparison.Ordinal)) 138 | { 139 | return; 140 | } 141 | filterErrorLabel.Text = error; 142 | filterErrorLabel.ColorScheme = Colors.Error; 143 | filterErrorLabel.Redraw(filterErrorLabel.Bounds); 144 | } 145 | 146 | private void SelectionChanged(object sender, SelectionChangedEventArgs e) 147 | { 148 | var selectedValue = e.NewValue; 149 | 150 | if (selectedValue is CachedMemberResult cmr) 151 | { 152 | selectedValue = cmr.Value; 153 | } 154 | 155 | if (selectedValue != null && selectedStatusBarItem != null) 156 | { 157 | selectedStatusBarItem.Title = selectedValue.GetType().Name; 158 | } 159 | else 160 | { 161 | selectedStatusBarItem.Title = string.Empty; 162 | } 163 | 164 | statusBar.SetNeedsDisplay(); 165 | } 166 | 167 | private string AspectGetter(object toRender) 168 | { 169 | if (toRender is Process p) 170 | { 171 | return p.ProcessName; 172 | } 173 | if (toRender is null) 174 | { 175 | return "Null"; 176 | } 177 | if (toRender is FileSystemInfo fsi && !IsRootObject(fsi)) 178 | { 179 | return fsi.Name; 180 | } 181 | 182 | return toRender.ToString(); 183 | } 184 | 185 | private bool IsRootObject(object o) 186 | { 187 | return tree.Objects.Contains(o); 188 | } 189 | 190 | public bool CanExpand(object toExpand) 191 | { 192 | if (toExpand is CachedMemberResult p) 193 | { 194 | return IsBasicType(p?.Value); 195 | } 196 | 197 | // Any complex object type can be expanded to reveal properties 198 | return IsBasicType(toExpand); 199 | } 200 | 201 | private static bool IsBasicType(object value) 202 | { 203 | return value != null && value is not string && !value.GetType().IsValueType; 204 | } 205 | 206 | public IEnumerable GetChildren(object forObject) 207 | { 208 | if (forObject == null || !this.CanExpand(forObject)) 209 | { 210 | return Enumerable.Empty(); 211 | } 212 | 213 | if (forObject is CachedMemberResult p) 214 | { 215 | if (p.IsCollection) 216 | { 217 | return p.Elements; 218 | } 219 | 220 | return GetChildren(p.Value); 221 | } 222 | 223 | if (forObject is CachedMemberResultElement e) 224 | { 225 | return GetChildren(e.Value); 226 | } 227 | 228 | List children = new List(); 229 | 230 | foreach (var member in forObject.GetType().GetMembers(BindingFlags.Instance | BindingFlags.Public).OrderBy(m => m.Name)) 231 | { 232 | if (member is PropertyInfo prop) 233 | { 234 | children.Add(new CachedMemberResult(forObject, prop)); 235 | } 236 | if (member is FieldInfo field) 237 | { 238 | children.Add(new CachedMemberResult(forObject, field)); 239 | } 240 | } 241 | 242 | try 243 | { 244 | children.AddRange(GetExtraChildren(forObject)); 245 | } 246 | catch (Exception) 247 | { 248 | // Extra children unavailable, possibly security or IO exceptions enumerating children etc 249 | } 250 | 251 | return children; 252 | } 253 | 254 | private static IEnumerable GetExtraChildren(object forObject) 255 | { 256 | if (forObject is DirectoryInfo dir) 257 | { 258 | foreach (var c in dir.EnumerateFileSystemInfos()) 259 | { 260 | yield return c; 261 | } 262 | } 263 | } 264 | 265 | internal static void Run(List objects, ApplicationData applicationData) 266 | { 267 | // Note, in Terminal.Gui v2, this property is renamed to Application.UseNetDriver, hence 268 | // using that terminology here. 269 | Application.UseSystemConsole = applicationData.UseNetDriver; 270 | Application.Init(); 271 | Window window = null; 272 | 273 | try 274 | { 275 | window = new ShowObjectView(objects.Select(p => p.BaseObject).ToList(), applicationData); 276 | Application.Top.Add(window); 277 | Application.Run(); 278 | } 279 | finally 280 | { 281 | Application.Shutdown(); 282 | window?.Dispose(); 283 | } 284 | } 285 | 286 | sealed class CachedMemberResultElement 287 | { 288 | public int Index; 289 | public object Value; 290 | 291 | private string representation; 292 | 293 | public CachedMemberResultElement(object value, int index) 294 | { 295 | Index = index; 296 | Value = value; 297 | 298 | try 299 | { 300 | representation = Value?.ToString() ?? "Null"; 301 | } 302 | catch (Exception) 303 | { 304 | Value = representation = "Unavailable"; 305 | } 306 | } 307 | public override string ToString() 308 | { 309 | return $"[{Index}]: {representation}]"; 310 | } 311 | } 312 | 313 | sealed class CachedMemberResult 314 | { 315 | public MemberInfo Member; 316 | public object Value; 317 | public object Parent; 318 | private string representation; 319 | private List valueAsList; 320 | 321 | 322 | public bool IsCollection => valueAsList != null; 323 | public IReadOnlyCollection Elements => valueAsList?.AsReadOnly(); 324 | 325 | public CachedMemberResult(object parent, MemberInfo mem) 326 | { 327 | Parent = parent; 328 | Member = mem; 329 | 330 | try 331 | { 332 | if (mem is PropertyInfo p) 333 | { 334 | Value = p.GetValue(parent); 335 | } 336 | else if (mem is FieldInfo f) 337 | { 338 | Value = f.GetValue(parent); 339 | } 340 | else 341 | { 342 | throw new NotSupportedException($"Unknown {nameof(MemberInfo)} Type"); 343 | } 344 | 345 | representation = ValueToString(); 346 | 347 | } 348 | catch (Exception) 349 | { 350 | Value = representation = "Unavailable"; 351 | } 352 | } 353 | 354 | private string ValueToString() 355 | { 356 | if (Value == null) 357 | { 358 | return "Null"; 359 | } 360 | try 361 | { 362 | if (IsCollectionOfKnownTypeAndSize(out Type elementType, out int size)) 363 | { 364 | return $"{elementType.Name}[{size}]"; 365 | } 366 | } 367 | catch (Exception) 368 | { 369 | return Value?.ToString(); 370 | } 371 | 372 | 373 | return Value?.ToString(); 374 | } 375 | 376 | private bool IsCollectionOfKnownTypeAndSize(out Type elementType, out int size) 377 | { 378 | elementType = null; 379 | size = 0; 380 | 381 | if (Value == null || Value is string) 382 | { 383 | 384 | return false; 385 | } 386 | 387 | if (Value is IEnumerable ienumerable) 388 | { 389 | var list = ienumerable.Cast().ToList(); 390 | 391 | var types = list.Where(v => v != null).Select(v => v.GetType()).Distinct().ToArray(); 392 | 393 | if (types.Length == 1) 394 | { 395 | elementType = types[0]; 396 | size = list.Count; 397 | 398 | valueAsList = list.Select((e, i) => new CachedMemberResultElement(e, i)).ToList(); 399 | return true; 400 | } 401 | } 402 | 403 | return false; 404 | } 405 | 406 | public override string ToString() 407 | { 408 | return Member.Name + ": " + representation; 409 | } 410 | } 411 | private sealed class RegexTreeViewTextFilter : ITreeViewFilter 412 | { 413 | private readonly ShowObjectView parent; 414 | readonly TreeView _forTree; 415 | 416 | public RegexTreeViewTextFilter(ShowObjectView parent, TreeView forTree) 417 | { 418 | this.parent = parent; 419 | _forTree = forTree ?? throw new ArgumentNullException(nameof(forTree)); 420 | } 421 | 422 | private string text; 423 | 424 | public string Text 425 | { 426 | get { return text; } 427 | set 428 | { 429 | text = value; 430 | RefreshTreeView(); 431 | } 432 | } 433 | 434 | private void RefreshTreeView() 435 | { 436 | _forTree.InvalidateLineMap(); 437 | _forTree.SetNeedsDisplay(); 438 | } 439 | 440 | public bool IsMatch(object model) 441 | { 442 | if (string.IsNullOrWhiteSpace(Text)) 443 | { 444 | return true; 445 | } 446 | 447 | parent.SetRegexError(string.Empty); 448 | 449 | var modelText = _forTree.AspectGetter(model); 450 | try 451 | { 452 | return Regex.IsMatch(modelText, text, RegexOptions.IgnoreCase); 453 | } 454 | catch (RegexParseException e) 455 | { 456 | parent.SetRegexError(e.Message); 457 | return true; 458 | } 459 | } 460 | } 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.ConsoleGuiTools/TypeGetter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Globalization; 7 | using System.Linq; 8 | using System.Management.Automation; 9 | using System.Management.Automation.Internal; 10 | 11 | using Microsoft.PowerShell.Commands; 12 | 13 | using OutGridView.Models; 14 | 15 | namespace OutGridView.Cmdlet 16 | { 17 | public class TypeGetter 18 | { 19 | private PSCmdlet _cmdlet; 20 | 21 | public TypeGetter(PSCmdlet cmdlet) 22 | { 23 | _cmdlet = cmdlet; 24 | } 25 | public FormatViewDefinition GetFormatViewDefinitionForObject(PSObject obj) 26 | { 27 | var typeName = obj.BaseObject.GetType().FullName; 28 | 29 | var types = _cmdlet.InvokeCommand.InvokeScript(@"Microsoft.PowerShell.Utility\Get-FormatData " + typeName).ToList(); 30 | 31 | //No custom type definitions found - try the PowerShell specific format data 32 | if (types == null || types.Count == 0) 33 | { 34 | types = _cmdlet.InvokeCommand 35 | .InvokeScript(@"Microsoft.PowerShell.Utility\Get-FormatData -PowerShellVersion $PSVersionTable.PSVersion " + typeName).ToList(); 36 | 37 | if (types == null || types.Count == 0) 38 | { 39 | return null; 40 | } 41 | } 42 | 43 | var extendedTypeDefinition = types[0].BaseObject as ExtendedTypeDefinition; 44 | 45 | return extendedTypeDefinition.FormatViewDefinition[0]; 46 | } 47 | 48 | public static DataTableRow CastObjectToDataTableRow(PSObject ps, List dataColumns, int objectIndex) 49 | { 50 | Dictionary valuePairs = new Dictionary(); 51 | 52 | foreach (var dataColumn in dataColumns) 53 | { 54 | var expression = new PSPropertyExpression(ScriptBlock.Create(dataColumn.PropertyScriptAccessor)); 55 | 56 | var result = expression.GetValues(ps).FirstOrDefault().Result; 57 | 58 | var stringValue = result?.ToString() ?? String.Empty; 59 | 60 | var isDecimal = decimal.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat, out var decimalValue); 61 | 62 | if (isDecimal) 63 | { 64 | valuePairs[dataColumn.ToString()] = new DecimalValue { DisplayValue = stringValue, SortValue = decimalValue }; 65 | } 66 | else 67 | { 68 | var stringDecorated = new StringDecorated(stringValue); 69 | valuePairs[dataColumn.ToString()] = new StringValue { DisplayValue = stringDecorated.ToString(OutputRendering.PlainText) }; 70 | } 71 | } 72 | 73 | return new DataTableRow(valuePairs, objectIndex); 74 | } 75 | 76 | private static void SetTypesOnDataColumns(List dataTableRows, List dataTableColumns) 77 | { 78 | var dataRows = dataTableRows.Select(x => x.Values); 79 | 80 | foreach (var dataColumn in dataTableColumns) 81 | { 82 | dataColumn.StringType = typeof(decimal).FullName; 83 | } 84 | 85 | //If every value in a column could be a decimal, assume that it is supposed to be a decimal 86 | foreach (var dataRow in dataRows) 87 | { 88 | foreach (var dataColumn in dataTableColumns) 89 | { 90 | if (!(dataRow[dataColumn.ToString()] is DecimalValue)) 91 | { 92 | dataColumn.StringType = typeof(string).FullName; 93 | } 94 | } 95 | } 96 | } 97 | private List GetDataColumnsForObject(List psObjects) 98 | { 99 | var dataColumns = new List(); 100 | 101 | 102 | 103 | foreach (PSObject obj in psObjects) 104 | { 105 | var labels = new List(); 106 | 107 | FormatViewDefinition fvd = GetFormatViewDefinitionForObject(obj); 108 | 109 | var propertyAccessors = new List(); 110 | 111 | if (fvd == null) 112 | { 113 | if (PSObjectIsPrimitive(obj)) 114 | { 115 | labels = new List { obj.BaseObject.GetType().Name }; 116 | propertyAccessors = new List { "$_" }; 117 | } 118 | else 119 | { 120 | labels = obj.Properties.Select(x => x.Name).ToList(); 121 | propertyAccessors = obj.Properties.Select(x => $"$_.\"{x.Name}\"").ToList(); 122 | } 123 | } 124 | else 125 | { 126 | var tableControl = fvd.Control as TableControl; 127 | 128 | var definedColumnLabels = tableControl.Headers.Select(x => x.Label); 129 | 130 | var displayEntries = tableControl.Rows[0].Columns.Select(x => x.DisplayEntry); 131 | 132 | var propertyLabels = displayEntries.Select(x => x.Value); 133 | 134 | //Use the TypeDefinition Label if availble otherwise just use the property name as a label 135 | labels = definedColumnLabels.Zip(propertyLabels, (definedColumnLabel, propertyLabel) => 136 | { 137 | if (String.IsNullOrEmpty(definedColumnLabel)) 138 | { 139 | return propertyLabel; 140 | } 141 | return definedColumnLabel; 142 | }).ToList(); 143 | 144 | 145 | propertyAccessors = displayEntries.Select(x => 146 | { 147 | //If it's a propety access directly 148 | if (x.ValueType == DisplayEntryValueType.Property) 149 | { 150 | return $"$_.\"{x.Value}\""; 151 | } 152 | //Otherwise return access script 153 | return x.Value; 154 | }).ToList(); 155 | } 156 | 157 | for (var i = 0; i < labels.Count; i++) 158 | { 159 | dataColumns.Add(new DataTableColumn(labels[i], propertyAccessors[i])); 160 | } 161 | } 162 | return dataColumns.Distinct().ToList(); 163 | } 164 | 165 | public DataTable CastObjectsToTableView(List psObjects) 166 | { 167 | List objectFormats = psObjects.Select(GetFormatViewDefinitionForObject).ToList(); 168 | 169 | var dataTableColumns = GetDataColumnsForObject(psObjects); 170 | 171 | List dataTableRows = new List(); 172 | for (var i = 0; i < objectFormats.Count; i++) 173 | { 174 | var dataTableRow = CastObjectToDataTableRow(psObjects[i], dataTableColumns, i); 175 | dataTableRows.Add(dataTableRow); 176 | } 177 | 178 | SetTypesOnDataColumns(dataTableRows, dataTableColumns); 179 | 180 | return new DataTable(dataTableColumns, dataTableRows); 181 | } 182 | 183 | 184 | //Types that are condisidered primitives to PowerShell but not C# 185 | private readonly static List additionalPrimitiveTypes = new List { "System.String", 186 | "System.Decimal", 187 | "System.IntPtr", 188 | "System.Security.SecureString", 189 | "System.Numerics.BigInteger" 190 | }; 191 | private static bool PSObjectIsPrimitive(PSObject ps) 192 | { 193 | var psBaseType = ps.BaseObject.GetType(); 194 | 195 | return psBaseType.IsPrimitive || psBaseType.IsEnum || additionalPrimitiveTypes.Contains(psBaseType.FullName); 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.OutGridView.Models/ApplicationData.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace OutGridView.Models 8 | { 9 | public class ApplicationData 10 | { 11 | public string Title { get; set; } 12 | public OutputModeOption OutputMode { get; set; } 13 | public bool PassThru { get; set; } 14 | public string Filter { get; set; } 15 | public bool MinUI { get; set; } 16 | public DataTable DataTable { get; set; } 17 | 18 | public bool UseNetDriver { get; set; } 19 | public bool Verbose { get; set; } 20 | public bool Debug { get; set; } 21 | 22 | public string ModuleVersion { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.OutGridView.Models/DataTable.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | namespace OutGridView.Models 7 | { 8 | public class DataTable 9 | { 10 | public List Data { get; set; } 11 | public List DataColumns { get; set; } 12 | public DataTable(List columns, List data) 13 | { 14 | DataColumns = columns; 15 | 16 | Data = data; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.OutGridView.Models/DataTableColumn.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using Newtonsoft.Json; 6 | using System.Text; 7 | 8 | namespace OutGridView.Models 9 | { 10 | public class DataTableColumn 11 | { 12 | [JsonIgnore] 13 | public Type Type => Type.GetType(StringType); 14 | public string Label { get; set; } 15 | //Serializable Version of Type 16 | public string StringType { get; set; } 17 | public string PropertyScriptAccessor { get; set; } 18 | public DataTableColumn(string label, string propertyScriptAccessor) 19 | { 20 | Label = label; 21 | PropertyScriptAccessor = propertyScriptAccessor; 22 | } 23 | 24 | //Distinct column defined by Label, Prop Accessor 25 | public override bool Equals(object obj) 26 | { 27 | DataTableColumn b = obj as DataTableColumn; 28 | return b.Label == Label && b.PropertyScriptAccessor == PropertyScriptAccessor; 29 | } 30 | public override int GetHashCode() 31 | { 32 | return Label.GetHashCode() + PropertyScriptAccessor.GetHashCode(); 33 | } 34 | public override string ToString() 35 | { 36 | //Needs to be encoded to embed safely in xaml 37 | return Convert.ToBase64String(Encoding.UTF8.GetBytes(Label + PropertyScriptAccessor)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.OutGridView.Models/DataTableRow.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace OutGridView.Models 8 | { 9 | public interface IValue : IComparable 10 | { 11 | string DisplayValue { get; set; } 12 | } 13 | public class DecimalValue : IValue 14 | { 15 | public string DisplayValue { get; set; } 16 | public decimal SortValue { get; set; } 17 | 18 | public int CompareTo(object obj) 19 | { 20 | DecimalValue otherDecimalValue = obj as DecimalValue; 21 | if (otherDecimalValue == null) return 1; 22 | return Decimal.Compare(SortValue, otherDecimalValue.SortValue); 23 | } 24 | } 25 | public class StringValue : IValue 26 | { 27 | public string DisplayValue { get; set; } 28 | public int CompareTo(object obj) 29 | { 30 | StringValue otherStringValue = obj as StringValue; 31 | if (otherStringValue == null) return 1; 32 | return DisplayValue.CompareTo(otherStringValue.DisplayValue); 33 | } 34 | } 35 | public class DataTableRow 36 | { 37 | //key is datacolumn hash code 38 | //have to do it this way because JSON can't serialize objects as keys 39 | public Dictionary Values { get; set; } 40 | public int OriginalObjectIndex { get; set; } 41 | public DataTableRow(Dictionary data, int originalObjectIndex) 42 | { 43 | Values = data; 44 | OriginalObjectIndex = originalObjectIndex; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.OutGridView.Models/Microsoft.PowerShell.OutGridView.Models.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.OutGridView.Models/OutputModeOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace OutGridView.Models 5 | { 6 | public enum OutputModeOption 7 | { 8 | /// 9 | /// None is the default and it means OK and Cancel will not be present 10 | /// and no objects will be written to the pipeline. 11 | /// The selectionMode of the actual list will still be multiple. 12 | /// 13 | None, 14 | /// 15 | /// Allow selection of one single item to be written to the pipeline. 16 | /// 17 | Single, 18 | /// 19 | ///Allow select of multiple items to be written to the pipeline. 20 | /// 21 | Multiple 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Microsoft.PowerShell.OutGridView.Models/Serializers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Newtonsoft.Json; 5 | using System; 6 | using System.Text; 7 | using System.Collections.Generic; 8 | //TODO: swich to JSON.NET 9 | 10 | namespace OutGridView.Models 11 | { 12 | public class Serializers 13 | { 14 | private readonly static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings() 15 | { 16 | TypeNameHandling = TypeNameHandling.All 17 | }; 18 | public static string ObjectToJson(T obj) 19 | { 20 | var jsonString = JsonConvert.SerializeObject(obj, jsonSerializerSettings); 21 | 22 | return ToBase64String(jsonString); 23 | } 24 | 25 | public static T ObjectFromJson(string base64Json) 26 | { 27 | var jsonString = FromBase64String(base64Json); 28 | 29 | return JsonConvert.DeserializeObject(jsonString, jsonSerializerSettings); 30 | } 31 | 32 | 33 | private static string FromBase64String(string base64string) 34 | { 35 | return Encoding.UTF8.GetString(Convert.FromBase64String(base64string)); 36 | } 37 | 38 | private static string ToBase64String(string str) 39 | { 40 | return Convert.ToBase64String(Encoding.UTF8.GetBytes(str)); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tools/installPSResources.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | param( 4 | [ValidateSet("PSGallery", "CFS")] 5 | [string]$PSRepository = "PSGallery" 6 | ) 7 | 8 | if ($PSRepository -eq "CFS" -and -not (Get-PSResourceRepository -Name CFS -ErrorAction SilentlyContinue)) { 9 | Register-PSResourceRepository -Name CFS -Uri "https://pkgs.dev.azure.com/powershell/PowerShell/_packaging/PowerShellGalleryMirror/nuget/v3/index.json" 10 | } 11 | 12 | # NOTE: Due to a bug in Install-PSResource with upstream feeds, we have to 13 | # request an exact version. Otherwise, if a newer version is available in the 14 | # upstream feed, it will fail to install any version at all. 15 | Install-PSResource -Verbose -TrustRepository -RequiredResource @{ 16 | InvokeBuild = @{ 17 | version = "5.12.1" 18 | repository = $PSRepository 19 | } 20 | platyPS = @{ 21 | version = "0.14.2" 22 | repository = $PSRepository 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tools/updateVersion.ps1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | param( 5 | [Parameter(Mandatory)] 6 | [semver]$Version, 7 | 8 | [Parameter(Mandatory)] 9 | [string]$Changes 10 | ) 11 | 12 | git diff --staged --quiet --exit-code 13 | if ($LASTEXITCODE -ne 0) { 14 | throw "There are staged changes in the repository. Please commit or reset them before running this script." 15 | } 16 | 17 | $v = "$($Version.Major).$($Version.Minor).$($Version.Patch)" 18 | 19 | $Path = "ConsoleGuiTools.Common.props" 20 | $f = Get-Content -Path $Path 21 | $f = $f -replace '^(?\s+)(.+)(?)$', "`${prefix}${v}`${suffix}" 22 | $f = $f -replace '^(?\s+)(.*)(?)$', "`${prefix}$($Version.PreReleaseLabel)`${suffix}" 23 | $f | Set-Content -Path $Path 24 | git add $Path 25 | 26 | $Path = "src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1" 27 | $f = Get-Content -Path $Path 28 | $f = $f -replace "^(?ModuleVersion\s+=\s+')(.+)(?')`$", "`${prefix}${v}`${suffix}" 29 | $f | Set-Content -Path $Path 30 | git add $Path 31 | 32 | git commit --edit --message "v$($Version): $Changes" 33 | --------------------------------------------------------------------------------