From 4574c704fe1c7ec4e3d37f7d4e7b9a6dda5ab09c Mon Sep 17 00:00:00 2001 From: tznind Date: Wed, 4 Jan 2023 13:43:10 +0000 Subject: [PATCH 01/22] Add Show-Object --- .../Microsoft.PowerShell.ConsoleGuiTools.psd1 | 2 +- .../ShowObjectCmdletCommand.cs | 114 ++++++++++++++ .../ShowObjectView.cs | 142 ++++++++++++++++++ 3 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectCmdletCommand.cs create mode 100644 src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 index 2addab4..3e17e5d 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 @@ -69,7 +69,7 @@ NestedModules = @() FunctionsToExport = @() # 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. -CmdletsToExport = @( 'Out-ConsoleGridView' ) +CmdletsToExport = @( 'Out-ConsoleGridView', 'Out-ShowObject' ) # Variables to export from this module VariablesToExport = '*' diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectCmdletCommand.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectCmdletCommand.cs new file mode 100644 index 0000000..8334119 --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectCmdletCommand.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Internal; +using OutGridView.Models; + +namespace OutGridView.Cmdlet +{ + [Cmdlet(VerbsData.Out, "ShowObject")] + public class ShowObjectCmdletCommand : PSCmdlet, IDisposable + { + #region Properties + + private const string DataNotQualifiedForGridView = nameof(DataNotQualifiedForGridView); + private const string EnvironmentNotSupportedForGridView = nameof(EnvironmentNotSupportedForGridView); + + private List _psObjects = new List(); + + #endregion Properties + + #region Input Parameters + + /// + /// This parameter specifies the current pipeline object. + /// + [Parameter(ValueFromPipeline = true, HelpMessage = "Specifies the input pipeline object")] + public PSObject InputObject { get; set; } = AutomationNull.Value; + + #endregion Input Parameters + + // This method gets called once for each cmdlet in the pipeline when the pipeline starts executing + protected override void BeginProcessing() + { + if (Console.IsInputRedirected) + { + ErrorRecord error = new ErrorRecord( + new PSNotSupportedException("Not supported in this environment (when input is redirected)."), + EnvironmentNotSupportedForGridView, + ErrorCategory.NotImplemented, + null); + + ThrowTerminatingError(error); + } + } + + // 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 + protected override void ProcessRecord() + { + if (InputObject == null || InputObject == AutomationNull.Value) + { + return; + } + + if (InputObject.BaseObject is IDictionary dictionary) + { + // Dictionaries should be enumerated through because the pipeline does not enumerate through them. + foreach (DictionaryEntry entry in dictionary) + { + ProcessObject(PSObject.AsPSObject(entry)); + } + } + else + { + ProcessObject(InputObject); + } + } + + private void ProcessObject(PSObject input) + { + + object baseObject = input.BaseObject; + + // Throw a terminating error for types that are not supported. + if (baseObject is ScriptBlock || + baseObject is SwitchParameter || + baseObject is PSReference || + baseObject is PSObject) + { + ErrorRecord error = new ErrorRecord( + new FormatException("Invalid data type for Out-GridView"), + DataNotQualifiedForGridView, + ErrorCategory.InvalidType, + null); + + ThrowTerminatingError(error); + } + + _psObjects.Add(input); + } + + // This method will be called once at the end of pipeline execution; if no input is received, this method is not called + protected override void EndProcessing() + { + base.EndProcessing(); + + //Return if no objects + if (_psObjects.Count == 0) + { + return; + } + + ShowObjectView.Run(_psObjects); + } + + public void Dispose() + { + + } + } +} diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs new file mode 100644 index 0000000..842a23f --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Reflection; +using Terminal.Gui; +using Terminal.Gui.Trees; +using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Linq; + +namespace OutGridView.Cmdlet +{ + internal class ShowObjectView : Window, ITreeBuilder + { + private readonly TreeView tree; + + public bool SupportsCanExpand => true; + + public ShowObjectView(List rootObjects) + { + Width = Dim.Fill(); + Height = Dim.Fill(); + + tree = new TreeView + { + Width = Dim.Fill(), + Height = Dim.Fill(), + }; + tree.TreeBuilder = this; + + if (rootObjects.Count > 0) + { + tree.AddObjects(rootObjects); + } + else + { + tree.AddObject("No Objects"); + } + + Add(tree); + } + + + + public bool CanExpand(object toExpand) + { + if (toExpand is CachedMemberResult p) + { + return IsBasicType(p?.Value); + } + + // Any complex object type can be expanded to reveal properties + return IsBasicType(toExpand); + } + + private bool IsBasicType(object? value) + { + return value != null && value is not string && !value.GetType().IsValueType; + } + + public IEnumerable GetChildren(object forObject) + { + if(forObject is CachedMemberResult p) + { + return GetChildren(p.Value); + } + + List children = new List(); + + // Vanilla object + foreach (var prop in forObject.GetType().GetProperties()) + { + children.Add(new CachedMemberResult(forObject, prop)); + } + foreach (var field in forObject.GetType().GetFields()) + { + children.Add(new CachedMemberResult(forObject, field)); + } + + return children; + } + + internal static void Run(List objects) + { + + Application.Init(); + Window window = null; + + try + { + window = new ShowObjectView(objects.Select(p=>p.BaseObject).ToList()); + Application.Run(window); + } + finally{ + Application.Shutdown(); + window?.Dispose(); + } + } + + class CachedMemberResult + { + public MemberInfo Member; + public object Value; + public object Parent; + private string representation; + + public CachedMemberResult(object parent, MemberInfo mem) + { + Parent = parent; + Member = mem; + + try + { + if (mem is PropertyInfo p) + { + Value = p.GetValue(parent); + } + else if (mem is FieldInfo f) + { + Value = f.GetValue(parent); + } + else + throw new NotSupportedException($"Unknown {nameof(MemberInfo)} Type"); + + representation = Value?.ToString() ?? "Null"; + + } + catch (Exception) + { + Value = representation = "Unavailable"; + } + } + + public override string ToString() + { + return Member.Name + ":" + representation; + } + } + } +} From f5e6407f0f8cb6571fdfe7069172ea6f8728fcd9 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 16 Jan 2023 20:12:13 +0000 Subject: [PATCH 02/22] Rename Show-ObjectTree --- .../Microsoft.PowerShell.ConsoleGuiTools.psd1 | 2 +- ...wObjectCmdletCommand.cs => ShowObjectTreeCmdletCommand.cs} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/Microsoft.PowerShell.ConsoleGuiTools/{ShowObjectCmdletCommand.cs => ShowObjectTreeCmdletCommand.cs} (97%) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 index 3e17e5d..e45376a 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 @@ -69,7 +69,7 @@ NestedModules = @() FunctionsToExport = @() # 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. -CmdletsToExport = @( 'Out-ConsoleGridView', 'Out-ShowObject' ) +CmdletsToExport = @( 'Out-ConsoleGridView', 'Show-ObjectTree' ) # Variables to export from this module VariablesToExport = '*' diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectCmdletCommand.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs similarity index 97% rename from src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectCmdletCommand.cs rename to src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs index 8334119..e535fef 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectCmdletCommand.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs @@ -10,8 +10,8 @@ namespace OutGridView.Cmdlet { - [Cmdlet(VerbsData.Out, "ShowObject")] - public class ShowObjectCmdletCommand : PSCmdlet, IDisposable + [Cmdlet("Show", "ObjectTree")] + public class ShowObjectTreeCmdletCommand : PSCmdlet, IDisposable { #region Properties From 3562e47a44675989b324a8f5ea2ee1a132521dc3 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 16 Jan 2023 20:39:50 +0000 Subject: [PATCH 03/22] Sort members alphabetically and add statusbar --- .../ShowObjectView.cs | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index 842a23f..458451f 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -9,6 +9,7 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Linq; +using System.Diagnostics; namespace OutGridView.Cmdlet { @@ -17,6 +18,8 @@ internal class ShowObjectView : Window, ITreeBuilder private readonly TreeView tree; public bool SupportsCanExpand => true; + private StatusItem selectedStatusBarItem; + private StatusBar statusBar; public ShowObjectView(List rootObjects) { @@ -26,9 +29,11 @@ public ShowObjectView(List rootObjects) tree = new TreeView { Width = Dim.Fill(), - Height = Dim.Fill(), + Height = Dim.Fill(1), }; tree.TreeBuilder = this; + tree.AspectGetter = this.AspectGetter; + tree.SelectionChanged += this.SelectionChanged; if (rootObjects.Count > 0) { @@ -38,11 +43,58 @@ public ShowObjectView(List rootObjects) { tree.AddObject("No Objects"); } + statusBar = new StatusBar(); + + string elementDescription = "objects"; + + var types = rootObjects.Select(o=>o.GetType()).Distinct().ToArray(); + if(types.Length == 1) + { + elementDescription = types[0].Name; + } + var siCount = new StatusItem(Key.Null, $"{rootObjects.Count} {elementDescription}",null); + selectedStatusBarItem = new StatusItem(Key.Null, string.Empty,null); + statusBar.AddItemAt(0,siCount); + statusBar.AddItemAt(1,selectedStatusBarItem); + Add(statusBar); Add(tree); } - + private void SelectionChanged(object sender, SelectionChangedEventArgs e) + { + var selectedValue = e.NewValue; + + if( selectedValue is CachedMemberResult cmr) + { + selectedValue = cmr.Value; + } + + if(selectedValue != null && selectedStatusBarItem != null) + { + selectedStatusBarItem.Title = selectedValue.GetType().Name; + } + else + { + selectedStatusBarItem.Title = string.Empty; + } + + statusBar.SetNeedsDisplay(); + } + + private string AspectGetter(object toRender) + { + if(toRender is Process p) + { + return p.ProcessName; + } + if(toRender is null) + { + return "Null"; + } + + return toRender.ToString(); + } public bool CanExpand(object toExpand) { @@ -69,14 +121,16 @@ public IEnumerable GetChildren(object forObject) List children = new List(); - // Vanilla object - foreach (var prop in forObject.GetType().GetProperties()) - { - children.Add(new CachedMemberResult(forObject, prop)); - } - foreach (var field in forObject.GetType().GetFields()) + foreach(var member in forObject.GetType().GetMembers().OrderBy(m=>m.Name)) { - children.Add(new CachedMemberResult(forObject, field)); + if(member is PropertyInfo prop) + { + children.Add(new CachedMemberResult(forObject, prop)); + } + if(member is FieldInfo field) + { + children.Add(new CachedMemberResult(forObject, field)); + } } return children; From cc19635247842cecde7d98d98a42d5fe012504ba Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 16 Jan 2023 21:18:23 +0000 Subject: [PATCH 04/22] Support for expanding IEnumerables --- .../ShowObjectView.cs | 98 ++++++++++++++++++- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index 458451f..d710745 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -10,6 +10,7 @@ using System.Management.Automation.Internal; using System.Linq; using System.Diagnostics; +using System.Collections; namespace OutGridView.Cmdlet { @@ -116,9 +117,19 @@ public IEnumerable GetChildren(object forObject) { if(forObject is CachedMemberResult p) { + if(p.IsCollection) + { + return p.Elements; + } + return GetChildren(p.Value); } + if(forObject is CachedMemberResultElement e) + { + return GetChildren(e.Value); + } + List children = new List(); foreach(var member in forObject.GetType().GetMembers().OrderBy(m=>m.Name)) @@ -153,12 +164,43 @@ internal static void Run(List objects) } } + class CachedMemberResultElement + { + public int Index; + public object Value; + + private string representation; + + public CachedMemberResultElement(object value, int index) + { + Index = index; + Value = value; + + try{ + representation = Value?.ToString() ?? "Null"; + } + catch (Exception) + { + Value = representation = "Unavailable"; + } + } + public override string ToString() + { + return $"[{Index}]: {representation}]"; + } + } + class CachedMemberResult { public MemberInfo Member; public object Value; public object Parent; - private string representation; + private string representation; + private List valueAsList; + + + public bool IsCollection => valueAsList != null; + public IReadOnlyCollection Elements => valueAsList?.AsReadOnly(); public CachedMemberResult(object parent, MemberInfo mem) { @@ -178,7 +220,7 @@ public CachedMemberResult(object parent, MemberInfo mem) else throw new NotSupportedException($"Unknown {nameof(MemberInfo)} Type"); - representation = Value?.ToString() ?? "Null"; + representation = ValueToString(); } catch (Exception) @@ -187,9 +229,59 @@ public CachedMemberResult(object parent, MemberInfo mem) } } + private string ValueToString() + { + if(Value == null) + { + return "Null"; + } + try{ + if(IsCollectionOfKnownTypeAndSize(out Type elementType, out int size)) + { + return $"{elementType.Name}[{size}]"; + } + }catch(Exception) + { + return Value?.ToString(); + } + + + return Value?.ToString(); + } + + private bool IsCollectionOfKnownTypeAndSize(out Type elementType, out int size) + { + elementType = null; + size = 0; + + if(Value == null || Value is string) + { + + return false; + } + + if(Value is IEnumerable ienumerable) + { + var list = ienumerable.Cast().ToList(); + + var types = list.Where(v=>v!=null).Select(v=>v.GetType()).Distinct().ToArray(); + + if(types.Length == 1) + { + elementType = types[0]; + size = list.Count; + + valueAsList = list.Select((e,i)=>new CachedMemberResultElement(e,i)).ToList(); + return true; + } + } + + return false; + } + public override string ToString() { - return Member.Name + ":" + representation; + return Member.Name + ": " + representation; } } } From 49ae67a8366cf78d44be7464bebee346e9d28e49 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 5 May 2023 09:00:26 +0100 Subject: [PATCH 05/22] Disable ExpandAll This prevents Ctrl+Right causing infinite loop where a property references a parent. --- src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index d710745..1c0a7e9 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -36,6 +36,8 @@ public ShowObjectView(List rootObjects) tree.AspectGetter = this.AspectGetter; tree.SelectionChanged += this.SelectionChanged; + tree.ClearKeybinding(Command.ExpandAll); + if (rootObjects.Count > 0) { tree.AddObjects(rootObjects); From 62e14e44b915df2ea2d6d8a59e75d99bfde99d2b Mon Sep 17 00:00:00 2001 From: tznind Date: Wed, 10 May 2023 07:52:47 +0100 Subject: [PATCH 06/22] Add window title with quit shortcut hint and fix NRT warning --- src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index 1c0a7e9..20b99d3 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -24,6 +24,7 @@ internal class ShowObjectView : Window, ITreeBuilder public ShowObjectView(List rootObjects) { + Title = "Show-ObjectTree (Ctrl+Q to quit)"; Width = Dim.Fill(); Height = Dim.Fill(); @@ -110,7 +111,7 @@ public bool CanExpand(object toExpand) return IsBasicType(toExpand); } - private bool IsBasicType(object? value) + private bool IsBasicType(object value) { return value != null && value is not string && !value.GetType().IsValueType; } From db96d5537a65baee56263ed5453b0802a52bf4a8 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 19 May 2023 20:13:05 +0100 Subject: [PATCH 07/22] Add search to Show-ObjectTree --- ...icrosoft.PowerShell.ConsoleGuiTools.csproj | 2 +- .../ShowObjectView.cs | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.csproj b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.csproj index 577e6fe..d8b7857 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.csproj +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index 20b99d3..ec11223 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -17,6 +17,7 @@ namespace OutGridView.Cmdlet internal class ShowObjectView : Window, ITreeBuilder { private readonly TreeView tree; + private readonly TreeViewTextFilter filter; public bool SupportsCanExpand => true; private StatusItem selectedStatusBarItem; @@ -30,6 +31,7 @@ public ShowObjectView(List rootObjects) tree = new TreeView { + Y = 2, Width = Dim.Fill(), Height = Dim.Fill(1), }; @@ -39,6 +41,9 @@ public ShowObjectView(List rootObjects) tree.ClearKeybinding(Command.ExpandAll); + this.filter = new TreeViewTextFilter(tree); + tree.Filter = this.filter; + if (rootObjects.Count > 0) { tree.AddObjects(rootObjects); @@ -57,6 +62,22 @@ public ShowObjectView(List rootObjects) elementDescription = types[0].Name; } + var lblFilter = new Label(){ + Text = "Filter:" + }; + var tbFilter = new TextField(){ + X = Pos.Right(lblFilter), + Width = Dim.Fill() + }; + tbFilter.TextChanged += (_)=>{ + filter.Text = tbFilter.Text.ToString(); + }; + + Add(lblFilter); + Add(tbFilter); + + tbFilter.FocusFirst(); + var siCount = new StatusItem(Key.Null, $"{rootObjects.Count} {elementDescription}",null); selectedStatusBarItem = new StatusItem(Key.Null, string.Empty,null); statusBar.AddItemAt(0,siCount); From 37a207f5246cd42db11741c9ecea770001d473f8 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 22 May 2023 20:39:40 +0100 Subject: [PATCH 08/22] Fix format/environment error strings Now correctly refers to ObjectTree not GridView --- .../ShowObjectTreeCmdletCommand.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs index e535fef..1c6e081 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs @@ -15,8 +15,8 @@ public class ShowObjectTreeCmdletCommand : PSCmdlet, IDisposable { #region Properties - private const string DataNotQualifiedForGridView = nameof(DataNotQualifiedForGridView); - private const string EnvironmentNotSupportedForGridView = nameof(EnvironmentNotSupportedForGridView); + private const string DataNotQualifiedForShowObjectTree = nameof(DataNotQualifiedForShowObjectTree); + private const string EnvironmentNotSupportedForShowObjectTree = nameof(EnvironmentNotSupportedForShowObjectTree); private List _psObjects = new List(); @@ -39,7 +39,7 @@ protected override void BeginProcessing() { ErrorRecord error = new ErrorRecord( new PSNotSupportedException("Not supported in this environment (when input is redirected)."), - EnvironmentNotSupportedForGridView, + EnvironmentNotSupportedForShowObjectTree, ErrorCategory.NotImplemented, null); @@ -81,8 +81,8 @@ baseObject is PSReference || baseObject is PSObject) { ErrorRecord error = new ErrorRecord( - new FormatException("Invalid data type for Out-GridView"), - DataNotQualifiedForGridView, + new FormatException("Invalid data type for Show-ObjectTree"), + DataNotQualifiedForShowObjectTree, ErrorCategory.InvalidType, null); From 5c6238c5eacb4ecfa55885dd244be18af3c8f290 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 22 May 2023 20:40:23 +0100 Subject: [PATCH 09/22] Add bracers to else block --- src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index ec11223..3d553a6 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -242,8 +242,10 @@ public CachedMemberResult(object parent, MemberInfo mem) Value = f.GetValue(parent); } else + { throw new NotSupportedException($"Unknown {nameof(MemberInfo)} Type"); - + } + representation = ValueToString(); } From fe7da7ea16ad01d3fbd926105596d88368d525d0 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 22 May 2023 20:41:44 +0100 Subject: [PATCH 10/22] Add command name as tag --- .../Microsoft.PowerShell.ConsoleGuiTools.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 index e45376a..35dfb4b 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 @@ -92,7 +92,7 @@ PrivateData = @{ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('Console', 'Gui', 'Out-GridView', 'Out-ConsoleGridView', 'Terminal.Gui', 'gui.cs', 'MacOS', 'Windows', 'Linux', 'PSEdition_Core') + Tags = @('Console', 'Gui', 'Out-GridView', 'Out-ConsoleGridView', 'Show-ObjectTree', 'Terminal.Gui', 'gui.cs', 'MacOS', 'Windows', 'Linux', 'PSEdition_Core') # A URL to the license for this module. LicenseUri = 'https://github.com/PowerShell/GraphicalTools/blob/master/LICENSE.txt' From b377d1590828fa44788b99c1ebd2dbb0c5d8b175 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 22 May 2023 20:51:56 +0100 Subject: [PATCH 11/22] Remove Ctrl+Q prompt and use Esc for exit --- src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index 3d553a6..300c1cd 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -25,7 +25,6 @@ internal class ShowObjectView : Window, ITreeBuilder public ShowObjectView(List rootObjects) { - Title = "Show-ObjectTree (Ctrl+Q to quit)"; Width = Dim.Fill(); Height = Dim.Fill(); @@ -78,10 +77,12 @@ public ShowObjectView(List rootObjects) tbFilter.FocusFirst(); + statusBar.AddItemAt(0, new StatusItem(Key.Esc, "~ESC~ Close", () => Application.RequestStop())); + var siCount = new StatusItem(Key.Null, $"{rootObjects.Count} {elementDescription}",null); selectedStatusBarItem = new StatusItem(Key.Null, string.Empty,null); - statusBar.AddItemAt(0,siCount); - statusBar.AddItemAt(1,selectedStatusBarItem); + statusBar.AddItemAt(1,siCount); + statusBar.AddItemAt(2,selectedStatusBarItem); Add(statusBar); Add(tree); } From 1805fa6aac42a58558c2997c9f82a25300747777 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 22 May 2023 21:11:06 +0100 Subject: [PATCH 12/22] Adjust look and feel to match grid view --- .../ShowObjectView.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index 300c1cd..af98fc9 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -25,14 +25,16 @@ internal class ShowObjectView : Window, ITreeBuilder public ShowObjectView(List rootObjects) { + Title = "Show-ObjectTree"; Width = Dim.Fill(); - Height = Dim.Fill(); + Height = Dim.Fill(1); + Modal = false; tree = new TreeView { Y = 2, Width = Dim.Fill(), - Height = Dim.Fill(1), + Height = Dim.Fill(), }; tree.TreeBuilder = this; tree.AspectGetter = this.AspectGetter; @@ -62,11 +64,12 @@ public ShowObjectView(List rootObjects) } var lblFilter = new Label(){ - Text = "Filter:" + Text = "Filter:", + X = 1, }; var tbFilter = new TextField(){ X = Pos.Right(lblFilter), - Width = Dim.Fill() + Width = Dim.Fill(1) }; tbFilter.TextChanged += (_)=>{ filter.Text = tbFilter.Text.ToString(); @@ -83,7 +86,8 @@ public ShowObjectView(List rootObjects) selectedStatusBarItem = new StatusItem(Key.Null, string.Empty,null); statusBar.AddItemAt(1,siCount); statusBar.AddItemAt(2,selectedStatusBarItem); - Add(statusBar); + + Application.Top.Add(statusBar); Add(tree); } @@ -181,7 +185,8 @@ internal static void Run(List objects) try { window = new ShowObjectView(objects.Select(p=>p.BaseObject).ToList()); - Application.Run(window); + Application.Top.Add(window); + Application.Run(); } finally{ Application.Shutdown(); From 72e165f35506b5f96f07646d7b48190942b5493d Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 22 Jul 2023 08:00:36 +0100 Subject: [PATCH 13/22] Add Filter, MinUI and Title options and started docs --- .../Show-ObjectTree.md | 258 ++++++++++++++++++ .../ShowObjectTreeCmdletCommand.cs | 28 +- .../ShowObjectView.cs | 36 ++- 3 files changed, 311 insertions(+), 11 deletions(-) create mode 100644 docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md diff --git a/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md b/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md new file mode 100644 index 0000000..2026ace --- /dev/null +++ b/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md @@ -0,0 +1,258 @@ +--- +external help file: ConsoleGuiToolsModule.dll-Help.xml +keywords: powershell,cmdlet +locale: en-us +Module Name: Microsoft.PowerShell.ConsoleGuiTools +ms.date: 07/20/2023 +schema: 2.0.0 +title: Show-ObjectTree +--- + +# Show-ObjectTree + +## SYNOPSIS + +Sends output to an interactive tree in the same console window. + +## SYNTAX + +```PowerShell + Show-ObjectTree [-InputObject ] [-Title ] [-OutputMode {None | Single | + Multiple}] [-Filter ] [-MinUi] [] +``` + +## DESCRIPTION + +The **Show-ObjectTree** cmdlet sends the output from a command to a tree view window where the output is displayed in an interactive tree. + +You can use the following features of the tree to examine your data: + +- Quick Filter. Use the Filter box at the top of the window to search the text in the tree. 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. + +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. + +## EXAMPLES + +### Example 1: Output processes to a tree view + +```PowerShell +PS C:\> Get-Process | Show-ObjectTree +``` + +This command gets the processes running on the local computer and sends them to a tree view window. + +### Example 2: Use a variable to output processes to a tree view + +```PowerShell +PS C:\> $P = Get-Process +PS C:\> $P | Show-ObjectTree -OutputMode Single +``` + +This command also gets the processes running on the local computer and sends them to a tree view window. + +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. + +The second command uses a pipeline operator to send the $P variable to **Show-ObjectTree**. + +By specifying `-OutputMode Single` the tree view window will be restricted to a single selection, ensuring now more than a single object is returned. + +### Example 3: Display a formatted tree in a tree view + +```PowerShell +PS C:\> Get-Process | Select-Object -Property Name, WorkingSet, PeakWorkingSet | Sort-Object -Property WorkingSet -Descending | Show-ObjectTree +``` + +This command displays a formatted tree in a tree view window. + +It uses the Get-Process cmdlet to get the processes on the computer. + +Then, it uses a pipeline operator (|) to send the process objects to the Select-Object cmdlet. +The command uses the **Property** parameter of **Select-Object** to select the Name, WorkingSet, and PeakWorkingSet properties to be displayed in the tree. + +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. + +The final part of the command uses a pipeline operator (|) to send the formatted tree to **Show-ObjectTree**. + +You can now use the features of the tree view to search, sort, and filter the data. + +### Example 4: Save output to a variable, and then output a tree view + +```PowerShell +PS C:\> ($A = Get-ChildItem -Path $pshome -Recurse) | Show-ObjectTree +``` + +This command saves its output in a variable and sends it to **Show-ObjectTree**. + +The command uses the Get-ChildItem cmdlet to get the files in the Windows PowerShell installation directory and its subdirectories. +The path to the installation directory is saved in the $pshome automatic variable. + +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**. + +The parentheses in the command establish the order of operations. +As a result, the output from the Get-ChildItem command is saved in the $A variable before it is sent to **Show-ObjectTree**. + +### Example 5: Output processes for a specified computer to a tree view + +```PowerShell +PS C:\> Get-Process -ComputerName "Server01" | ocgv -Title "Processes - Server01" +``` + +This command displays the processes that are running on the Server01 computer in a tree view window. + +The command uses `ocgv`, which is the built-in alias for the **Show-ObjectTree** cmdlet, it uses the *Title* parameter to specify the window title. + +### Example 6: Define a function to kill processes using a graphical chooser + +```PowerShell +PS C:\> function killp { Get-Process | Show-ObjectTree -OutputMode Single -Filter $args[0] | Stop-Process -Id {$_.Id} } +PS C:\> killp note +``` +This example shows defining a function named `killp` that shows a tree view of all running processes and allows the user to select one to kill it. + +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 tree view and pressing `ENTER` will kill that process. + +### Example 7: Pass multiple items through Show-ObjectTree + +```PowerShell +PS C:\> Get-Process | Show-ObjectTree -PassThru | Export-Csv -Path .\ProcessLog.csv +``` + +This command lets you select multiple processes from the **Show-ObjectTree** window. +The processes that you select are passed to the **Export-Csv** command and written to the ProcessLog.csv file. + +The command uses the *PassThru* parameter of **Show-ObjectTree**, which lets you send multiple items down the pipeline. +The *PassThru* parameter is equivalent to using the Multiple value of the *OutputMode* parameter. + +### Example 8: Use F7 as "Show Command History" + +Save See [this gist](https://gist.github.com/tig/cbbeab7f53efd73e329afd6d4b838191) as `F7History.ps1` and run `F7History.ps1` in your `$profile`. + +Press `F7` to see the history for the current PowerShell instance + +Press `Shift-F7` to see the history for all PowerShell instances. + +Whatever you select within `Show-ObjectTree` will be inserted on your command line. + +Whatever was typed on the command line prior to hitting `F7` or `Shift-F7` will be used as a filter. + +## PARAMETERS + +### -Filter +Pre-populates the Filter edit box, allowing filtering to be specified on the command line. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InputObject +Specifies that the cmdlet accepts input for **Show-ObjectTree**. + +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. + +To display the each object in the collection, use a pipeline operator (|) to send objects to **Show-ObjectTree**. + +```yaml +Type: PSObject +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -OutputMode +Specifies the items that the interactive window sends down the pipeline as input to other commands. +By default, this cmdlet generates zero, one, or many items. + +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. + +The values of this parameter determine how many items you can send down the pipeline. + +- None. No items. +- Single. Zero items or one item. Use this value when the next command can take only one input object. +- Multiple. Zero, one, or many items. Use this value when the next command can take multiple input objects. This is the default value. + +```yaml +Type: OutputModeOption +Parameter Sets: OutputMode +Aliases: +Accepted values: None, Single, Multiple + +Required: False +Position: Named +Default value: Multiple +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Title +Specifies the text that appears in the title bar of the **Show-ObjectTree** window. + +By default, the title bar displays the command that invokes **Show-ObjectTree**. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -MinUi +If specified no window frame, filter box, or status bar will be displayed in the **Show-ObjectTree** window. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +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). + +## INPUTS + +### System.Management.Automation.PSObject + +You can send any object to this cmdlet. + +## OUTPUTS + +### System.Object + +By default `Show-ObjectTree` returns objects representing the selected rows to the pipeline. Use `-OutputMode` to change this behavior. + +## NOTES + +* 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. + +* Deserialized output from remote commands might not be formatted correctly in the tree view window. + +## RELATED LINKS + +[Out-File](Out-File.md) + +[Out-Printer](Out-Printer.md) + +[Out-String](Out-String.md) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs index 1c6e081..d13e2de 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs @@ -30,6 +30,25 @@ public class ShowObjectTreeCmdletCommand : PSCmdlet, IDisposable [Parameter(ValueFromPipeline = true, HelpMessage = "Specifies the input pipeline object")] public PSObject InputObject { get; set; } = AutomationNull.Value; + /// + /// Gets/sets the title of the Out-GridView window. + /// + [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.")] + [ValidateNotNullOrEmpty] + public string Title { get; set; } + + /// + /// gets or sets the initial value for the filter in the GUI + /// + [Parameter(HelpMessage = "Pre-populates the Filter edit box, allowing filtering to be specified on the command line. The filter uses regular expressions." )] + public string Filter { set; get; } + + /// + /// gets or sets the whether "minimum UI" mode will be enabled + /// + [Parameter(HelpMessage = "If specified no window frame, filter box, or status bar will be displayed in the GUI.")] + public SwitchParameter MinUI { set; get; } + #endregion Input Parameters // This method gets called once for each cmdlet in the pipeline when the pipeline starts executing @@ -102,8 +121,15 @@ protected override void EndProcessing() { return; } + + var applicationData = new ApplicationData + { + Title = Title ?? "Show-ObjectTree", + Filter = Filter, + MinUI = MinUI + }; - ShowObjectView.Run(_psObjects); + ShowObjectView.Run(_psObjects, applicationData); } public void Dispose() diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index af98fc9..b247d42 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Diagnostics; using System.Collections; +using OutGridView.Models; namespace OutGridView.Cmdlet { @@ -23,16 +24,24 @@ internal class ShowObjectView : Window, ITreeBuilder private StatusItem selectedStatusBarItem; private StatusBar statusBar; - public ShowObjectView(List rootObjects) + public ShowObjectView(List rootObjects, ApplicationData applicationData) { - Title = "Show-ObjectTree"; + Title = applicationData.Title; Width = Dim.Fill(); Height = Dim.Fill(1); Modal = false; + if(applicationData.MinUI) + { + Border.BorderStyle = BorderStyle.None; + Title = string.Empty; + X = -1; + Height = Dim.Fill(); + } + tree = new TreeView { - Y = 2, + Y = applicationData.MinUI ? 0 : 1, Width = Dim.Fill(), Height = Dim.Fill(), }; @@ -43,6 +52,7 @@ public ShowObjectView(List rootObjects) tree.ClearKeybinding(Command.ExpandAll); this.filter = new TreeViewTextFilter(tree); + this.filter.Text = applicationData.Filter ?? string.Empty; tree.Filter = this.filter; if (rootObjects.Count > 0) @@ -69,16 +79,20 @@ public ShowObjectView(List rootObjects) }; var tbFilter = new TextField(){ X = Pos.Right(lblFilter), - Width = Dim.Fill(1) + Width = Dim.Fill(1), + Text = applicationData.Filter ?? string.Empty }; + tbFilter.CursorPosition = tbFilter.Text.Length; + tbFilter.TextChanged += (_)=>{ filter.Text = tbFilter.Text.ToString(); }; - Add(lblFilter); - Add(tbFilter); - - tbFilter.FocusFirst(); + if(!applicationData.MinUI) + { + Add(lblFilter); + Add(tbFilter); + } statusBar.AddItemAt(0, new StatusItem(Key.Esc, "~ESC~ Close", () => Application.RequestStop())); @@ -87,7 +101,9 @@ public ShowObjectView(List rootObjects) statusBar.AddItemAt(1,siCount); statusBar.AddItemAt(2,selectedStatusBarItem); + statusBar.Visible = !applicationData.MinUI; Application.Top.Add(statusBar); + Add(tree); } @@ -176,7 +192,7 @@ public IEnumerable GetChildren(object forObject) return children; } - internal static void Run(List objects) + internal static void Run(List objects, ApplicationData applicationData) { Application.Init(); @@ -184,7 +200,7 @@ internal static void Run(List objects) try { - window = new ShowObjectView(objects.Select(p=>p.BaseObject).ToList()); + window = new ShowObjectView(objects.Select(p=>p.BaseObject).ToList(), applicationData); Application.Top.Add(window); Application.Run(); } From 3501e4b5c17a1a864de924899188db71213fcf48 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 22 Jul 2023 08:30:07 +0100 Subject: [PATCH 14/22] Change Filter to match ocgv (regex support with error box) --- .../Show-ObjectTree.md | 2 +- .../ShowObjectView.cs | 74 ++++++++++++++++++- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md b/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md index 2026ace..11180a6 100644 --- a/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md +++ b/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md @@ -27,7 +27,7 @@ The **Show-ObjectTree** cmdlet sends the output from a command to a tree view wi You can use the following features of the tree to examine your data: -- Quick Filter. Use the Filter box at the top of the window to search the text in the tree. 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. +- 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. 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. diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index b247d42..46cf3d1 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -12,13 +12,15 @@ using System.Diagnostics; using System.Collections; using OutGridView.Models; +using System.Text.RegularExpressions; namespace OutGridView.Cmdlet { internal class ShowObjectView : Window, ITreeBuilder { private readonly TreeView tree; - private readonly TreeViewTextFilter filter; + private readonly SplitRegexTreeViewTextFilter filter; + private readonly Label filterErrorLabel; public bool SupportsCanExpand => true; private StatusItem selectedStatusBarItem; @@ -41,7 +43,7 @@ public ShowObjectView(List rootObjects, ApplicationData applicationData) tree = new TreeView { - Y = applicationData.MinUI ? 0 : 1, + Y = applicationData.MinUI ? 0 : 2, Width = Dim.Fill(), Height = Dim.Fill(), }; @@ -51,7 +53,7 @@ public ShowObjectView(List rootObjects, ApplicationData applicationData) tree.ClearKeybinding(Command.ExpandAll); - this.filter = new TreeViewTextFilter(tree); + this.filter = new SplitRegexTreeViewTextFilter(this, tree); this.filter.Text = applicationData.Filter ?? string.Empty; tree.Filter = this.filter; @@ -88,10 +90,20 @@ public ShowObjectView(List rootObjects, ApplicationData applicationData) filter.Text = tbFilter.Text.ToString(); }; + + filterErrorLabel = new Label(string.Empty) + { + X = Pos.Right(lblFilter) + 1, + Y = Pos.Top(lblFilter) + 1, + ColorScheme = Colors.Base, + Width = Dim.Fill() - lblFilter.Text.Length + }; + if(!applicationData.MinUI) { Add(lblFilter); Add(tbFilter); + Add(filterErrorLabel); } statusBar.AddItemAt(0, new StatusItem(Key.Esc, "~ESC~ Close", () => Application.RequestStop())); @@ -106,6 +118,16 @@ public ShowObjectView(List rootObjects, ApplicationData applicationData) Add(tree); } + private void SetRegexError(string error) + { + if(string.Equals(error, filterErrorLabel.Text.ToString())) + { + return; + } + filterErrorLabel.Text = error; + filterErrorLabel.ColorScheme = Colors.Error; + filterErrorLabel.Redraw(filterErrorLabel.Bounds); + } private void SelectionChanged(object sender, SelectionChangedEventArgs e) { @@ -332,5 +354,51 @@ public override string ToString() return Member.Name + ": " + representation; } } + private class SplitRegexTreeViewTextFilter : ITreeViewFilter + { + private readonly ShowObjectView parent; + readonly TreeView _forTree; + + public SplitRegexTreeViewTextFilter (ShowObjectView parent, TreeView forTree) + { + this.parent = parent; + _forTree = forTree ?? throw new ArgumentNullException (nameof (forTree)); + } + + private string text; + + public string Text { + get { return text; } + set { + text = value; + RefreshTreeView (); + } + } + + private void RefreshTreeView () + { + _forTree.InvalidateLineMap (); + _forTree.SetNeedsDisplay (); + } + + public bool IsMatch (object model) + { + if (string.IsNullOrWhiteSpace (Text)) { + return true; + } + + parent.SetRegexError(string.Empty); + + var modelText = _forTree.AspectGetter (model); + try{ + return Regex.IsMatch(modelText, text, RegexOptions.IgnoreCase); + } + catch(RegexParseException e) + { + parent.SetRegexError(e.Message); + return true; + } + } + } } } From 6e253644cce37a102837a02339a05c1440c0928b Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 22 Jul 2023 08:34:34 +0100 Subject: [PATCH 15/22] Remove invalid examples and docs on output types to show tree --- .../Show-ObjectTree.md | 109 +----------------- 1 file changed, 3 insertions(+), 106 deletions(-) diff --git a/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md b/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md index 11180a6..77176c8 100644 --- a/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md +++ b/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md @@ -41,41 +41,7 @@ PS C:\> Get-Process | Show-ObjectTree This command gets the processes running on the local computer and sends them to a tree view window. -### Example 2: Use a variable to output processes to a tree view - -```PowerShell -PS C:\> $P = Get-Process -PS C:\> $P | Show-ObjectTree -OutputMode Single -``` - -This command also gets the processes running on the local computer and sends them to a tree view window. - -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. - -The second command uses a pipeline operator to send the $P variable to **Show-ObjectTree**. - -By specifying `-OutputMode Single` the tree view window will be restricted to a single selection, ensuring now more than a single object is returned. - -### Example 3: Display a formatted tree in a tree view - -```PowerShell -PS C:\> Get-Process | Select-Object -Property Name, WorkingSet, PeakWorkingSet | Sort-Object -Property WorkingSet -Descending | Show-ObjectTree -``` - -This command displays a formatted tree in a tree view window. - -It uses the Get-Process cmdlet to get the processes on the computer. - -Then, it uses a pipeline operator (|) to send the process objects to the Select-Object cmdlet. -The command uses the **Property** parameter of **Select-Object** to select the Name, WorkingSet, and PeakWorkingSet properties to be displayed in the tree. - -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. - -The final part of the command uses a pipeline operator (|) to send the formatted tree to **Show-ObjectTree**. - -You can now use the features of the tree view to search, sort, and filter the data. - -### Example 4: Save output to a variable, and then output a tree view +### Example 2: Save output to a variable, and then output a tree view ```PowerShell PS C:\> ($A = Get-ChildItem -Path $pshome -Recurse) | Show-ObjectTree @@ -91,50 +57,6 @@ The command uses the assignment operator (=) to save the output in the $A variab The parentheses in the command establish the order of operations. As a result, the output from the Get-ChildItem command is saved in the $A variable before it is sent to **Show-ObjectTree**. -### Example 5: Output processes for a specified computer to a tree view - -```PowerShell -PS C:\> Get-Process -ComputerName "Server01" | ocgv -Title "Processes - Server01" -``` - -This command displays the processes that are running on the Server01 computer in a tree view window. - -The command uses `ocgv`, which is the built-in alias for the **Show-ObjectTree** cmdlet, it uses the *Title* parameter to specify the window title. - -### Example 6: Define a function to kill processes using a graphical chooser - -```PowerShell -PS C:\> function killp { Get-Process | Show-ObjectTree -OutputMode Single -Filter $args[0] | Stop-Process -Id {$_.Id} } -PS C:\> killp note -``` -This example shows defining a function named `killp` that shows a tree view of all running processes and allows the user to select one to kill it. - -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 tree view and pressing `ENTER` will kill that process. - -### Example 7: Pass multiple items through Show-ObjectTree - -```PowerShell -PS C:\> Get-Process | Show-ObjectTree -PassThru | Export-Csv -Path .\ProcessLog.csv -``` - -This command lets you select multiple processes from the **Show-ObjectTree** window. -The processes that you select are passed to the **Export-Csv** command and written to the ProcessLog.csv file. - -The command uses the *PassThru* parameter of **Show-ObjectTree**, which lets you send multiple items down the pipeline. -The *PassThru* parameter is equivalent to using the Multiple value of the *OutputMode* parameter. - -### Example 8: Use F7 as "Show Command History" - -Save See [this gist](https://gist.github.com/tig/cbbeab7f53efd73e329afd6d4b838191) as `F7History.ps1` and run `F7History.ps1` in your `$profile`. - -Press `F7` to see the history for the current PowerShell instance - -Press `Shift-F7` to see the history for all PowerShell instances. - -Whatever you select within `Show-ObjectTree` will be inserted on your command line. - -Whatever was typed on the command line prior to hitting `F7` or `Shift-F7` will be used as a filter. - ## PARAMETERS ### -Filter @@ -171,31 +93,6 @@ Accept pipeline input: True (ByValue) Accept wildcard characters: False ``` -### -OutputMode -Specifies the items that the interactive window sends down the pipeline as input to other commands. -By default, this cmdlet generates zero, one, or many items. - -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. - -The values of this parameter determine how many items you can send down the pipeline. - -- None. No items. -- Single. Zero items or one item. Use this value when the next command can take only one input object. -- Multiple. Zero, one, or many items. Use this value when the next command can take multiple input objects. This is the default value. - -```yaml -Type: OutputModeOption -Parameter Sets: OutputMode -Aliases: -Accepted values: None, Single, Multiple - -Required: False -Position: Named -Default value: Multiple -Accept pipeline input: False -Accept wildcard characters: False -``` - ### -Title Specifies the text that appears in the title bar of the **Show-ObjectTree** window. @@ -239,9 +136,9 @@ You can send any object to this cmdlet. ## OUTPUTS -### System.Object +### None -By default `Show-ObjectTree` returns objects representing the selected rows to the pipeline. Use `-OutputMode` to change this behavior. +`Show-ObjectTree` does not output any objects. ## NOTES From 90f6797782abf537a5b1f87dc2c45ba5597f8ce4 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 22 Jul 2023 08:41:59 +0100 Subject: [PATCH 16/22] Add prototype implementation of directory contents listing --- .../ShowObjectView.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index 46cf3d1..839f3d8 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -13,6 +13,7 @@ using System.Collections; using OutGridView.Models; using System.Text.RegularExpressions; +using System.IO; namespace OutGridView.Cmdlet { @@ -211,9 +212,28 @@ public IEnumerable GetChildren(object forObject) } } + try{ + children.AddRange(GetExtraChildren(forObject)); + } + catch(Exception) + { + // Extra children unavailable, possibly security or IO exceptions enumerating children etc + } + return children; } + private IEnumerable GetExtraChildren(object forObject) + { + if(forObject is DirectoryInfo dir) + { + foreach(var c in dir.EnumerateFileSystemInfos()) + { + yield return c; + } + } + } + internal static void Run(List objects, ApplicationData applicationData) { From ef309adc2662e842efaeec17811cfe03e8e41ec4 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 22 Jul 2023 08:50:10 +0100 Subject: [PATCH 17/22] Don't output full path names of child objects --- .../ShowObjectView.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index 839f3d8..820644a 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -162,9 +162,19 @@ private string AspectGetter(object toRender) return "Null"; } + if(toRender is FileSystemInfo fsi) + { + return IsRootObject(fsi) ? fsi.ToString() : fsi.Name; + } + return toRender.ToString(); } + private bool IsRootObject(object o) + { + return tree.Objects.Contains(o); + } + public bool CanExpand(object toExpand) { if (toExpand is CachedMemberResult p) From ca6baf135c85f9b5caeeccc77662d2e126d33b9f Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 22 Jul 2023 09:10:53 +0100 Subject: [PATCH 18/22] Adjust logic to be more readable --- src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index 820644a..0c2c200 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -161,10 +161,9 @@ private string AspectGetter(object toRender) { return "Null"; } - - if(toRender is FileSystemInfo fsi) + if(toRender is FileSystemInfo fsi && !IsRootObject(fsi)) { - return IsRootObject(fsi) ? fsi.ToString() : fsi.Name; + return fsi.Name; } return toRender.ToString(); From 0070117b54e172dbce6ef5d2ffa287e68e507769 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 22 Jul 2023 09:15:44 +0100 Subject: [PATCH 19/22] Fix class name --- .../ShowObjectView.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index 0c2c200..9dcea6b 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -20,7 +20,7 @@ namespace OutGridView.Cmdlet internal class ShowObjectView : Window, ITreeBuilder { private readonly TreeView tree; - private readonly SplitRegexTreeViewTextFilter filter; + private readonly RegexTreeViewTextFilter filter; private readonly Label filterErrorLabel; public bool SupportsCanExpand => true; @@ -54,7 +54,7 @@ public ShowObjectView(List rootObjects, ApplicationData applicationData) tree.ClearKeybinding(Command.ExpandAll); - this.filter = new SplitRegexTreeViewTextFilter(this, tree); + this.filter = new RegexTreeViewTextFilter(this, tree); this.filter.Text = applicationData.Filter ?? string.Empty; tree.Filter = this.filter; @@ -383,12 +383,12 @@ public override string ToString() return Member.Name + ": " + representation; } } - private class SplitRegexTreeViewTextFilter : ITreeViewFilter + private class RegexTreeViewTextFilter : ITreeViewFilter { private readonly ShowObjectView parent; readonly TreeView _forTree; - public SplitRegexTreeViewTextFilter (ShowObjectView parent, TreeView forTree) + public RegexTreeViewTextFilter (ShowObjectView parent, TreeView forTree) { this.parent = parent; _forTree = forTree ?? throw new ArgumentNullException (nameof (forTree)); From b05aac7f420d6b2de2841100c73dca2d7f3ed434 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 22 Jul 2023 09:26:44 +0100 Subject: [PATCH 20/22] Add alias 'sot' for Show-ObjectTree --- docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md | 2 +- .../ShowObjectTreeCmdletCommand.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md b/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md index 77176c8..d485a63 100644 --- a/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md +++ b/docs/Microsoft.PowerShell.ConsoleGuiTools/Show-ObjectTree.md @@ -44,7 +44,7 @@ This command gets the processes running on the local computer and sends them to ### Example 2: Save output to a variable, and then output a tree view ```PowerShell -PS C:\> ($A = Get-ChildItem -Path $pshome -Recurse) | Show-ObjectTree +PS C:\> ($A = Get-ChildItem -Path $pshome -Recurse) | sot ``` This command saves its output in a variable and sends it to **Show-ObjectTree**. diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs index d13e2de..7fa8ee5 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs @@ -11,6 +11,7 @@ namespace OutGridView.Cmdlet { [Cmdlet("Show", "ObjectTree")] + [Alias("sot")] public class ShowObjectTreeCmdletCommand : PSCmdlet, IDisposable { #region Properties From ac6d2d322e1c0db6e3d6ac8ad8330a9304b65764 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 22 Jul 2023 09:39:36 +0100 Subject: [PATCH 21/22] Add sot to `AliasesToExport` --- .../Microsoft.PowerShell.ConsoleGuiTools.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 index 5b30c75..b41d0dc 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 @@ -75,7 +75,7 @@ CmdletsToExport = @( 'Out-ConsoleGridView', 'Show-ObjectTree' ) VariablesToExport = '*' # 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. -AliasesToExport = @( 'ocgv' ) +AliasesToExport = @( 'ocgv', 'sot' ) # DSC resources to export from this module # DscResourcesToExport = @() From 3c654e9b8de6d5a78803ad74c6bbf51a63f92eb9 Mon Sep 17 00:00:00 2001 From: tznind Date: Wed, 18 Oct 2023 21:14:28 +0100 Subject: [PATCH 22/22] Add UseNetDriver and Debug flags to Show-ObjectTree --- .../ShowObjectTreeCmdletCommand.cs | 16 +++++++++++++- .../ShowObjectView.cs | 21 ++++++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs index 7fa8ee5..1d66c65 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectTreeCmdletCommand.cs @@ -49,6 +49,17 @@ public class ShowObjectTreeCmdletCommand : PSCmdlet, IDisposable /// [Parameter(HelpMessage = "If specified no window frame, filter box, or status bar will be displayed in the GUI.")] public SwitchParameter MinUI { set; get; } + /// + /// gets or sets the whether the Terminal.Gui System.Net.Console-based ConsoleDriver will be used instead of the + /// default platform-specific (Windows or Curses) ConsoleDriver. + /// + [Parameter(HelpMessage = "If specified the Terminal.Gui System.Net.Console-based ConsoleDriver (NetDriver) will be used.")] + public SwitchParameter UseNetDriver { set; get; } + + /// + /// For the -Debug switch + /// + public bool Debug => MyInvocation.BoundParameters.TryGetValue("Debug", out var o); #endregion Input Parameters @@ -127,7 +138,10 @@ protected override void EndProcessing() { Title = Title ?? "Show-ObjectTree", Filter = Filter, - MinUI = MinUI + MinUI = MinUI, + UseNetDriver = UseNetDriver, + Debug = Debug, + ModuleVersion = MyInvocation.MyCommand.Version.ToString() }; ShowObjectView.Run(_psObjects, applicationData); diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs index 9dcea6b..43a94ee 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs @@ -33,6 +33,7 @@ public ShowObjectView(List rootObjects, ApplicationData applicationData) Width = Dim.Fill(); Height = Dim.Fill(1); Modal = false; + if(applicationData.MinUI) { @@ -107,13 +108,21 @@ public ShowObjectView(List rootObjects, ApplicationData applicationData) Add(filterErrorLabel); } - statusBar.AddItemAt(0, new StatusItem(Key.Esc, "~ESC~ Close", () => Application.RequestStop())); + int pos = 0; + statusBar.AddItemAt(pos++, new StatusItem(Key.Esc, "~ESC~ Close", () => Application.RequestStop())); var siCount = new StatusItem(Key.Null, $"{rootObjects.Count} {elementDescription}",null); selectedStatusBarItem = new StatusItem(Key.Null, string.Empty,null); - statusBar.AddItemAt(1,siCount); - statusBar.AddItemAt(2,selectedStatusBarItem); - + statusBar.AddItemAt(pos++,siCount); + statusBar.AddItemAt(pos++,selectedStatusBarItem); + + if ( applicationData.Debug) + { + statusBar.AddItemAt(pos++,new StatusItem(Key.Null, $" v{applicationData.ModuleVersion}", null)); + statusBar.AddItemAt(pos++,new StatusItem(Key.Null, + $"{Application.Driver} v{FileVersionInfo.GetVersionInfo(Assembly.GetAssembly(typeof(Application)).Location).ProductVersion}", null)); + } + statusBar.Visible = !applicationData.MinUI; Application.Top.Add(statusBar); @@ -245,7 +254,9 @@ private IEnumerable GetExtraChildren(object forObject) internal static void Run(List objects, ApplicationData applicationData) { - + // Note, in Terminal.Gui v2, this property is renamed to Application.UseNetDriver, hence + // using that terminology here. + Application.UseSystemConsole = applicationData.UseNetDriver; Application.Init(); Window window = null;