diff --git a/.gitignore b/.gitignore
index 80046ed..0537311 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,49 @@
-module/
-out/
-bin/
-obj/
+*.swp
+*.*~
+project.lock.json
+.DS_Store
+*.pyc
+nupkg/
+
+# Rider
+.idea
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+msbuild.log
+msbuild.err
+msbuild.wrn
+
publish/
-*.sln
+
+#Module build
+module/
+
+# Visual Studio 2015
+.vs/
+
+# ingore downloaded .NET
+.dotnet
+
+# Ignore package
+Microsoft.PowerShell.GraphicalTools.zip
+Microsoft.PowerShell.ConsoleGuiTools.zip
+
+# git artifacts
+*.orig
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..3b66410
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "git.ignoreLimitWarning": true
+}
\ No newline at end of file
diff --git a/ConsoleGuiTools.build.ps1 b/ConsoleGuiTools.build.ps1
index 3fa225b..5ba0fa6 100644
--- a/ConsoleGuiTools.build.ps1
+++ b/ConsoleGuiTools.build.ps1
@@ -23,15 +23,18 @@ task Build {
Push-Location src/Microsoft.PowerShell.ConsoleGuiTools
Invoke-BuildExec { & dotnet publish --configuration $Configuration --output publish }
- $Assets = $(
- "./publish/Microsoft.PowerShell.ConsoleGuiTools.dll",
- "./publish/Microsoft.PowerShell.ConsoleGuiTools.psd1",
- "./publish/Microsoft.PowerShell.OutGridView.Models.dll",
- "./publish/Terminal.Gui.dll",
- "./publish/NStack.dll")
- $Assets | ForEach-Object {
- Copy-Item -Force -Path $_ -Destination ../../module
+
+ # Copy all DLLs except PowerShell SDK dependencies (those are provided by PowerShell itself)
+ Get-ChildItem "./publish/*.dll" | Where-Object {
+ $_.Name -notlike "System.Management.Automation.dll" -and
+ $_.Name -notlike "Microsoft.PowerShell.Commands.Diagnostics.dll" -and
+ $_.Name -notlike "Microsoft.Management.Infrastructure.CimCmdlets.dll"
+ } | ForEach-Object {
+ Copy-Item -Force -Path $_.FullName -Destination ../../module
}
+
+ # Copy the module manifest
+ Copy-Item -Force -Path "./publish/Microsoft.PowerShell.ConsoleGuiTools.psd1" -Destination ../../module
Pop-Location
$Assets = $(
diff --git a/Directory.Packages.props b/Directory.Packages.props
index be0590b..fc10969 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -2,7 +2,7 @@
-
-
+
+
-
+
\ No newline at end of file
diff --git a/GraphicalTools.sln b/GraphicalTools.sln
new file mode 100644
index 0000000..0789aa8
--- /dev/null
+++ b/GraphicalTools.sln
@@ -0,0 +1,36 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 18
+VisualStudioVersion = 18.3.11206.111 d18.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.ConsoleGuiTools", "src\Microsoft.PowerShell.ConsoleGuiTools\Microsoft.PowerShell.ConsoleGuiTools.csproj", "{C0749375-3F76-9F36-9A4D-6857B5504C9A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.OutGridView.Models", "src\Microsoft.PowerShell.OutGridView.Models\Microsoft.PowerShell.OutGridView.Models.csproj", "{D5EDF10B-A646-FC2C-FF3C-B7F2C1814863}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C0749375-3F76-9F36-9A4D-6857B5504C9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C0749375-3F76-9F36-9A4D-6857B5504C9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C0749375-3F76-9F36-9A4D-6857B5504C9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C0749375-3F76-9F36-9A4D-6857B5504C9A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D5EDF10B-A646-FC2C-FF3C-B7F2C1814863}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D5EDF10B-A646-FC2C-FF3C-B7F2C1814863}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D5EDF10B-A646-FC2C-FF3C-B7F2C1814863}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D5EDF10B-A646-FC2C-FF3C-B7F2C1814863}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {C0749375-3F76-9F36-9A4D-6857B5504C9A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
+ {D5EDF10B-A646-FC2C-FF3C-B7F2C1814863} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {7EA28E35-5572-47D2-B03E-5DB79D82BEBC}
+ EndGlobalSection
+EndGlobal
diff --git a/GraphicalTools.sln.DotSettings b/GraphicalTools.sln.DotSettings
new file mode 100644
index 0000000..40a04f5
--- /dev/null
+++ b/GraphicalTools.sln.DotSettings
@@ -0,0 +1,7 @@
+
+ PS
+ UI
+ <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB" /></Policy>
+ True
+ True
\ No newline at end of file
diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/CachedMemberResult.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/CachedMemberResult.cs
new file mode 100644
index 0000000..14534c5
--- /dev/null
+++ b/src/Microsoft.PowerShell.ConsoleGuiTools/CachedMemberResult.cs
@@ -0,0 +1,152 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Microsoft.PowerShell.ConsoleGuiTools;
+
+///
+/// Represents a cached reflection result for a property or field member, including its value and collection details.
+///
+internal sealed class CachedMemberResult
+{
+ #region Fields
+
+ private readonly string? _representation;
+ private List? _valueAsList;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the member information (property or field) that was accessed.
+ ///
+ public MemberInfo Member { get; set; }
+
+ ///
+ /// Gets or sets the value retrieved from the member.
+ ///
+ public object? Value { get; set; }
+
+ ///
+ /// Gets or sets the parent object that contains this member.
+ ///
+ public object Parent { get; set; }
+
+ ///
+ /// Gets a value indicating whether this member's value is a collection.
+ ///
+ public bool IsCollection => _valueAsList != null;
+
+ ///
+ /// Gets the collection elements if this member's value is a collection; otherwise, .
+ ///
+ public IReadOnlyCollection? Elements => _valueAsList?.AsReadOnly();
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class by reflecting on the specified member.
+ ///
+ /// The parent object containing the member.
+ /// The member information to retrieve the value from.
+ 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 = ValueToString();
+ }
+ catch (Exception)
+ {
+ Value = _representation = "Unavailable";
+ }
+ }
+
+ #endregion
+
+ #region Overrides
+
+ ///
+ /// Returns a string representation of this member in the format "MemberName: value".
+ ///
+ /// A formatted string showing the member name and value.
+ public override string ToString() => Member.Name + ": " + _representation;
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Converts the member's value to a string representation, detecting collections and formatting them appropriately.
+ ///
+ /// A string representation of the value.
+ private string? ValueToString()
+ {
+ if (Value == null)
+ return "Null";
+
+ try
+ {
+ if (IsCollectionOfKnownTypeAndSize(out var elementType, out var size))
+ return $"{elementType!.Name}[{size}]";
+ }
+ catch (Exception)
+ {
+ return Value?.ToString();
+ }
+
+ return Value?.ToString();
+ }
+
+ ///
+ /// Determines whether the value is a collection of a known type and caches the collection elements.
+ ///
+ /// When this method returns, contains the element type if the value is a homogeneous collection; otherwise, .
+ /// When this method returns, contains the size of the collection if applicable; otherwise, 0.
+ /// if the value is a collection of a single known type; otherwise, .
+ private bool IsCollectionOfKnownTypeAndSize(out Type? elementType, out int size)
+ {
+ elementType = null;
+ size = 0;
+
+ if (Value is null or string)
+ return false;
+
+ if (Value is IEnumerable enumerable)
+ {
+ var list = enumerable.Cast