From 02a2d2ff5e04fb770efc1333063b04bf2aa705f8 Mon Sep 17 00:00:00 2001 From: AJ Raymond <19650958-poshAJ@users.noreply.gitlab.com> Date: Sat, 11 Apr 2026 22:05:32 +0000 Subject: [PATCH 01/17] initial --- module/PSADTree.Format.ps1xml | 46 ---- module/PSADTree.Formats.ps1xml | 211 ++++++++++++++++++ module/PSADTree.Types.ps1xml | 38 ++++ module/PSADTree.psd1 | 6 +- .../Commands/GetADTreeStyleCommand.cs | 12 + .../Extensions/ExceptionExtensions.cs | 4 + src/PSADTree/Extensions/TreeExtensions.cs | 12 +- src/PSADTree/Style/OutputRendering.cs | 8 + src/PSADTree/Style/Palette.cs | 67 ++++++ src/PSADTree/Style/RenderingSet.cs | 24 ++ src/PSADTree/Style/RenderingStyle.cs | 9 + src/PSADTree/Style/TreeStyle.cs | 100 +++++++++ src/PSADTree/TreeGroup.cs | 20 +- 13 files changed, 492 insertions(+), 65 deletions(-) delete mode 100644 module/PSADTree.Format.ps1xml create mode 100644 module/PSADTree.Formats.ps1xml create mode 100644 module/PSADTree.Types.ps1xml create mode 100644 src/PSADTree/Commands/GetADTreeStyleCommand.cs create mode 100644 src/PSADTree/Style/OutputRendering.cs create mode 100644 src/PSADTree/Style/Palette.cs create mode 100644 src/PSADTree/Style/RenderingSet.cs create mode 100644 src/PSADTree/Style/RenderingStyle.cs create mode 100644 src/PSADTree/Style/TreeStyle.cs diff --git a/module/PSADTree.Format.ps1xml b/module/PSADTree.Format.ps1xml deleted file mode 100644 index 1c3a2af..0000000 --- a/module/PSADTree.Format.ps1xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - treeview - - PSADTree.TreeObjectBase - - - [PSADTree.Internal._FormattingInternals]::GetSource($_) - - - - - - - Left - - - - Right - - - - Left - - - - - - - [PSADTree.Internal._FormattingInternals]::GetDomain($_) - - - ObjectClass - - - Hierarchy - - - - - - - - diff --git a/module/PSADTree.Formats.ps1xml b/module/PSADTree.Formats.ps1xml new file mode 100644 index 0000000..87d9318 --- /dev/null +++ b/module/PSADTree.Formats.ps1xml @@ -0,0 +1,211 @@ + + + + + treeviewad + + PSADTree.TreeObjectBase + + + [PSADTree.Internal._FormattingInternals]::GetSource($_) + + + + + + + Left + + + + Right + + + + Left + + + + + + + [PSADTree.Internal._FormattingInternals]::GetDomain($_) + + + ObjectClass + + + Hierarchy + + + + + + + + PSADTree.Style.Palette + + PSADTree.Style.Palette + + + + + + + + Foreground + + + + Background + + + + + + + + PSADTree.Style.Palette.Palettes + + PSADTree.Style.Palette+ForegroundPalette + PSADTree.Style.Palette+BackgroundPalette + + + + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.Black) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightBlack) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.White) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightWhite) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.Red) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightRed) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.Magenta) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightMagenta) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.Blue) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightBlue) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.Cyan) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightCyan) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.Green) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightGreen) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.Yellow) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightYellow) + + + + + + + + + PSADTree.Style.TreeStyle + + PSADTree.Style.TreeStyle + + + + + + + + OutputRendering + + + + RenderingStyle + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.Reset) + + + + + Palette + + + + + + + + diff --git a/module/PSADTree.Types.ps1xml b/module/PSADTree.Types.ps1xml new file mode 100644 index 0000000..613ad77 --- /dev/null +++ b/module/PSADTree.Types.ps1xml @@ -0,0 +1,38 @@ + + + + PSADTree.Style.TreeStyle + + + CombineSequence + + + + ToItalic + + + + ToBold + + + + EscapeSequence + + + + ResetSettings + + + + + diff --git a/module/PSADTree.psd1 b/module/PSADTree.psd1 index 557e3a7..7825a14 100644 --- a/module/PSADTree.psd1 +++ b/module/PSADTree.psd1 @@ -64,10 +64,10 @@ # ScriptsToProcess = @() # Type files (.ps1xml) to be loaded when importing this module - # TypesToProcess = @() + TypesToProcess = @('PSADTree.Types.ps1xml') # Format files (.ps1xml) to be loaded when importing this module - FormatsToProcess = @('PSADTree.Format.ps1xml') + FormatsToProcess = @('PSADTree.Formats.ps1xml') # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess # NestedModules = @() @@ -77,6 +77,7 @@ # 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 = @( + 'Get-ADTreeStyle' 'Get-ADTreeGroupMember' 'Get-ADTreePrincipalGroupMembership' ) @@ -86,6 +87,7 @@ # 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 = @( + 'treestyle' 'treegroupmember' 'treeprincipalmembership' ) diff --git a/src/PSADTree/Commands/GetADTreeStyleCommand.cs b/src/PSADTree/Commands/GetADTreeStyleCommand.cs new file mode 100644 index 0000000..1c157f7 --- /dev/null +++ b/src/PSADTree/Commands/GetADTreeStyleCommand.cs @@ -0,0 +1,12 @@ +using System.Management.Automation; +using PSADTree.Style; + +namespace PSADTree.Commands; + +[Cmdlet(VerbsCommon.Get, "ADTreeStyle")] +[OutputType(typeof(TreeStyle))] +[Alias("treestyle")] +public sealed class GetADTreeStyleCommand : PSCmdlet +{ + protected override void BeginProcessing() => WriteObject(TreeStyle.Instance); +} diff --git a/src/PSADTree/Extensions/ExceptionExtensions.cs b/src/PSADTree/Extensions/ExceptionExtensions.cs index 121dc38..536c32b 100644 --- a/src/PSADTree/Extensions/ExceptionExtensions.cs +++ b/src/PSADTree/Extensions/ExceptionExtensions.cs @@ -24,4 +24,8 @@ internal static ErrorRecord ToEnumerationFailure(this Exception exception, Princ internal static ErrorRecord ToSetPrincipalContext(this Exception exception) => new(exception, "SetPrincipalContext", ErrorCategory.ConnectionError, null); + + internal static void ThrowInvalidSequence(this string vt) => + throw new ArgumentException( + $"The specified string contains printable content when it should only contain ANSI escape sequences: '{vt}'."); } diff --git a/src/PSADTree/Extensions/TreeExtensions.cs b/src/PSADTree/Extensions/TreeExtensions.cs index 5ea2336..fdd31d8 100644 --- a/src/PSADTree/Extensions/TreeExtensions.cs +++ b/src/PSADTree/Extensions/TreeExtensions.cs @@ -9,6 +9,7 @@ using System.Text; #endif using System.Text.RegularExpressions; +using PSADTree.Style; namespace PSADTree.Extensions; @@ -24,7 +25,7 @@ internal static class TreeExtensions #endif internal static string Indent(this string inputString, int indentation) { - const string corner = "└── "; + string corner = TreeStyle.Instance.RenderingSet.Corner; int repeatCount = (4 * indentation) - 4; int capacity = repeatCount + 4 + inputString.Length; @@ -54,11 +55,12 @@ internal static TreeObjectBase[] Format( this TreeObjectBase[] tree) { int index; + RenderingSet set = TreeStyle.Instance.RenderingSet; for (int i = 0; i < tree.Length; i++) { TreeObjectBase current = tree[i]; - if ((index = current.Hierarchy.IndexOf('└')) == -1) + if ((index = current.Hierarchy.IndexOf(set.UpRight)) == -1) { continue; } @@ -70,13 +72,13 @@ internal static TreeObjectBase[] Format( if (char.IsWhiteSpace(hierarchy[index])) { - current.Hierarchy = hierarchy.ReplaceAt(index, '│'); + current.Hierarchy = hierarchy.ReplaceAt(index, set.Vertical); continue; } - if (hierarchy[index] == '└') + if (hierarchy[index] == set.UpRight) { - current.Hierarchy = hierarchy.ReplaceAt(index, '├'); + current.Hierarchy = hierarchy.ReplaceAt(index, set.VerticalRight); } break; diff --git a/src/PSADTree/Style/OutputRendering.cs b/src/PSADTree/Style/OutputRendering.cs new file mode 100644 index 0000000..c8be2e1 --- /dev/null +++ b/src/PSADTree/Style/OutputRendering.cs @@ -0,0 +1,8 @@ +namespace PSADTree.Style; + +public enum OutputRendering +{ + Host = 0, + PlainText = 1, + Ansi = 2 +} diff --git a/src/PSADTree/Style/Palette.cs b/src/PSADTree/Style/Palette.cs new file mode 100644 index 0000000..fd9e46e --- /dev/null +++ b/src/PSADTree/Style/Palette.cs @@ -0,0 +1,67 @@ +using System; + +namespace PSADTree.Style; + +public sealed class Palette +{ + public sealed class ForegroundPalette + { + public string Black { get; } = "\x1B[30m"; + public string BrightBlack { get; } = "\x1B[90m"; + public string White { get; } = "\x1B[37m"; + public string BrightWhite { get; } = "\x1B[97m"; + public string Red { get; } = "\x1B[31m"; + public string BrightRed { get; } = "\x1B[91m"; + public string Magenta { get; } = "\x1B[35m"; + public string BrightMagenta { get; } = "\x1B[95m"; + public string Blue { get; } = "\x1B[34m"; + public string BrightBlue { get; } = "\x1B[94m"; + public string Cyan { get; } = "\x1B[36m"; + public string BrightCyan { get; } = "\x1B[96m"; + public string Green { get; } = "\x1B[32m"; + public string BrightGreen { get; } = "\x1B[92m"; + public string Yellow { get; } = "\x1B[33m"; + public string BrightYellow { get; } = "\x1B[93m"; + + private string? _toString; + + public override string ToString() => + _toString ??= TreeStyle.FormatType(this); + } + + public sealed class BackgroundPalette + { + public string Black { get; } = "\x1B[40m"; + public string BrightBlack { get; } = "\x1B[100m"; + public string White { get; } = "\x1B[47m"; + public string BrightWhite { get; } = "\x1B[107m"; + public string Red { get; } = "\x1B[41m"; + public string BrightRed { get; } = "\x1B[101m"; + public string Magenta { get; } = "\x1B[45m"; + public string BrightMagenta { get; } = "\x1B[105m"; + public string Blue { get; } = "\x1B[44m"; + public string BrightBlue { get; } = "\x1B[104m"; + public string Cyan { get; } = "\x1B[46m"; + public string BrightCyan { get; } = "\x1B[106m"; + public string Green { get; } = "\x1B[42m"; + public string BrightGreen { get; } = "\x1B[102m"; + public string Yellow { get; } = "\x1B[43m"; + public string BrightYellow { get; } = "\x1B[103m"; + + private string? _toString; + + public override string ToString() => + _toString ??= TreeStyle.FormatType(this); + } + + public ForegroundPalette Foreground { get; } = new(); + public BackgroundPalette Background { get; } = new(); + + private string? _toString; + + internal Palette() + { } + + public override string ToString() => + _toString ??= $"{Foreground}{Environment.NewLine}{Environment.NewLine}{Background}"; +} diff --git a/src/PSADTree/Style/RenderingSet.cs b/src/PSADTree/Style/RenderingSet.cs new file mode 100644 index 0000000..7f8ec41 --- /dev/null +++ b/src/PSADTree/Style/RenderingSet.cs @@ -0,0 +1,24 @@ +namespace PSADTree.Style; + +internal readonly struct RenderingSet +{ + internal static readonly RenderingSet Fancy = new("└── ", '└', '│', '├', "↔"); + internal static readonly RenderingSet FancyRounded = new("╰── ", '╰', '│', '├', "↔"); + internal static readonly RenderingSet Classic = new("+-- ", '+', '|', '|', "<>"); + internal static readonly RenderingSet ClassicRounded = new("`-- ", '`', '|', '|', "<>"); + + internal string Corner { get; } + internal char UpRight { get; } + internal char Vertical { get; } + internal char VerticalRight { get; } + internal string Arrows { get; } + + private RenderingSet(string corner, char upRight, char vertical, char verticalRight, string arrows) + { + Corner = corner; + UpRight = upRight; + Vertical = vertical; + VerticalRight = verticalRight; + Arrows = arrows; + } +} diff --git a/src/PSADTree/Style/RenderingStyle.cs b/src/PSADTree/Style/RenderingStyle.cs new file mode 100644 index 0000000..1ecfc2a --- /dev/null +++ b/src/PSADTree/Style/RenderingStyle.cs @@ -0,0 +1,9 @@ +namespace PSADTree.Style; + +public enum RenderingStyle +{ + Fancy = 0, + FancyRounded = 1, + Classic = 2, + ClassicRounded = 3 +} diff --git a/src/PSADTree/Style/TreeStyle.cs b/src/PSADTree/Style/TreeStyle.cs new file mode 100644 index 0000000..c05a8d1 --- /dev/null +++ b/src/PSADTree/Style/TreeStyle.cs @@ -0,0 +1,100 @@ +using System; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using PSADTree.Extensions; + +namespace PSADTree.Style; + +public sealed class TreeStyle +{ + public static TreeStyle Instance { get => s_instance ??= new(); } + public OutputRendering OutputRendering { get; set; } = OutputRendering.Host; + public RenderingStyle RenderingStyle { get; set; } = RenderingStyle.Fancy; + public string Reset { get; } = "\x1B[0m"; + public Palette Palette { get; } = new(); + + internal RenderingSet RenderingSet + { + get => RenderingStyle switch + { + RenderingStyle.Fancy => RenderingSet.Fancy, + RenderingStyle.FancyRounded => RenderingSet.FancyRounded, + RenderingStyle.Classic => RenderingSet.Classic, + RenderingStyle.ClassicRounded => RenderingSet.ClassicRounded, + _ => throw new ArgumentOutOfRangeException(nameof(RenderingStyle)) + }; + } + + private static TreeStyle? s_instance; + private static readonly Regex s_validate = new( + @"^\x1B\[(?:[0-9]+;?){1,}m$", + RegexOptions.Compiled); + + internal TreeStyle() + { } + + public static string CombineSequence(string left, string right) + { + ThrowIfInvalidSequence(left); + ThrowIfInvalidSequence(right); + return $"{left.TrimEnd('m')};{right.Substring(2)}"; + } + + public static string ToItalic(string vt) + { + ThrowIfInvalidSequence(vt); + return $"{vt.TrimEnd('m')};3m"; + } + + public static string ToBold(string vt) + { + ThrowIfInvalidSequence(vt); + return $"{vt.TrimEnd('m')};1m"; + } + + public static string EscapeSequence(string vt) => + $"{vt}{vt.Replace("\x1B", "`e")}\x1B[0m"; + + public static void ResetSettings() => + s_instance = new(); + + internal static string FormatType(object instance) + { + PropertyInfo[] properties = instance.GetType().GetProperties(); + StringBuilder builder = new(properties.Length); + int i = 1; + + foreach (PropertyInfo property in properties) + { + string value = EscapeSequence( + vt: (string)property.GetValue(instance)!, + padding: 10); + + builder.Append(value); + + if (i++ % 4 == 0) + { + builder.AppendLine("\x1B[0m"); + continue; + } + + builder.Append("\x1B[0m"); + } + + return builder.ToString(); + } + + private static string EscapeSequence(string vt, int padding) => + $"{vt}{vt.Replace("\x1B", "`e").PadRight(padding)}\x1B[0m"; + + private static string ThrowIfInvalidSequence(string vt) + { + if (!s_validate.IsMatch(vt)) + { + vt.ThrowInvalidSequence(); + } + + return vt; + } +} diff --git a/src/PSADTree/TreeGroup.cs b/src/PSADTree/TreeGroup.cs index 27cce7a..07c5d75 100644 --- a/src/PSADTree/TreeGroup.cs +++ b/src/PSADTree/TreeGroup.cs @@ -1,21 +1,12 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.DirectoryServices.AccountManagement; +using PSADTree.Style; namespace PSADTree; public sealed class TreeGroup : TreeObjectBase { - private const string Circular = $" ↔ {VTBrightRed}Circular Reference{VTReset}"; - - private const string Processed = $" ↔ {VTBrightYellow}Processed Group{VTReset}"; - - private const string VTBrightRed = "\x1B[91m"; - - private const string VTBrightYellow = "\x1B[93m"; - - private const string VTReset = "\x1B[0m"; - private List _children; public ReadOnlyCollection Children => new(_children); @@ -72,13 +63,18 @@ internal bool SetIfCircularNested() { if (IsCircular = IsCircularNested()) { - Hierarchy = $"{Hierarchy}{Circular}"; + TreeStyle treeStyle = TreeStyle.Instance; + Hierarchy = $"{Hierarchy} {treeStyle.RenderingSet.Arrows} {treeStyle.Palette.Foreground.BrightRed}Circular Reference{treeStyle.Reset}"; } return IsCircular; } - internal void SetProcessed() => Hierarchy = $"{Hierarchy}{Processed}"; + internal void SetProcessed() + { + TreeStyle treeStyle = TreeStyle.Instance; + Hierarchy = $"{Hierarchy} {treeStyle.RenderingSet.Arrows} {treeStyle.Palette.Foreground.BrightYellow}Processed Group{treeStyle.Reset}"; + } internal void LinkCachedChildren(TreeCache cache) => _children = cache[DistinguishedName]._children; From bb33c324c5be9c381d0aa3380cac3f8b42629afb Mon Sep 17 00:00:00 2001 From: AJ Raymond <19650958-poshAJ@users.noreply.gitlab.com> Date: Sun, 12 Apr 2026 00:10:44 +0000 Subject: [PATCH 02/17] fix performance regression --- src/PSADTree/Style/TreeStyle.cs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/PSADTree/Style/TreeStyle.cs b/src/PSADTree/Style/TreeStyle.cs index c05a8d1..e1a54a1 100644 --- a/src/PSADTree/Style/TreeStyle.cs +++ b/src/PSADTree/Style/TreeStyle.cs @@ -10,21 +10,27 @@ public sealed class TreeStyle { public static TreeStyle Instance { get => s_instance ??= new(); } public OutputRendering OutputRendering { get; set; } = OutputRendering.Host; - public RenderingStyle RenderingStyle { get; set; } = RenderingStyle.Fancy; + public RenderingStyle RenderingStyle + { + get; + set + { + RenderingSet = value switch + { + RenderingStyle.Fancy => RenderingSet.Fancy, + RenderingStyle.FancyRounded => RenderingSet.FancyRounded, + RenderingStyle.Classic => RenderingSet.Classic, + RenderingStyle.ClassicRounded => RenderingSet.ClassicRounded, + _ => throw new ArgumentOutOfRangeException(nameof(RenderingStyle)) + }; + + field = value; + } + } = RenderingStyle.Fancy; public string Reset { get; } = "\x1B[0m"; public Palette Palette { get; } = new(); - internal RenderingSet RenderingSet - { - get => RenderingStyle switch - { - RenderingStyle.Fancy => RenderingSet.Fancy, - RenderingStyle.FancyRounded => RenderingSet.FancyRounded, - RenderingStyle.Classic => RenderingSet.Classic, - RenderingStyle.ClassicRounded => RenderingSet.ClassicRounded, - _ => throw new ArgumentOutOfRangeException(nameof(RenderingStyle)) - }; - } + internal RenderingSet RenderingSet { get; private set; } = RenderingSet.Fancy; private static TreeStyle? s_instance; private static readonly Regex s_validate = new( From 4757e1276a6e0fa3d6e6cc86410c4d49ca8545df Mon Sep 17 00:00:00 2001 From: AJ Raymond <19650958-poshAJ@users.noreply.gitlab.com> Date: Sun, 12 Apr 2026 22:09:39 +0000 Subject: [PATCH 03/17] add computer, group, and user style --- module/PSADTree.Formats.ps1xml | 38 +++++++++++++++++ src/PSADTree/Extensions/TreeExtensions.cs | 17 ++++++++ src/PSADTree/Style/ComputerStyle.cs | 19 +++++++++ src/PSADTree/Style/GroupStyle.cs | 52 +++++++++++++++++++++++ src/PSADTree/Style/TreeStyle.cs | 11 +++-- src/PSADTree/Style/UserStyle.cs | 20 +++++++++ src/PSADTree/TreeGroup.cs | 6 +-- src/PSADTree/TreeObjectBase.cs | 6 +-- 8 files changed, 158 insertions(+), 11 deletions(-) create mode 100644 src/PSADTree/Style/ComputerStyle.cs create mode 100644 src/PSADTree/Style/GroupStyle.cs create mode 100644 src/PSADTree/Style/UserStyle.cs diff --git a/module/PSADTree.Formats.ps1xml b/module/PSADTree.Formats.ps1xml index 87d9318..d24e044 100644 --- a/module/PSADTree.Formats.ps1xml +++ b/module/PSADTree.Formats.ps1xml @@ -42,6 +42,32 @@ + + PSADTree.Style.GroupStyle + + PSADTree.Style.GroupStyle + + + + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.Circular) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.Processed) + + + + + + + PSADTree.Style.Palette @@ -198,6 +224,18 @@ [PSADTree.Style.TreeStyle]::EscapeSequence($_.Reset) + + + Computer + + + + Group + + + + User + Palette diff --git a/src/PSADTree/Extensions/TreeExtensions.cs b/src/PSADTree/Extensions/TreeExtensions.cs index fdd31d8..8add7c7 100644 --- a/src/PSADTree/Extensions/TreeExtensions.cs +++ b/src/PSADTree/Extensions/TreeExtensions.cs @@ -23,6 +23,23 @@ internal static class TreeExtensions [ThreadStatic] private static StringBuilder? s_sb; #endif + + internal static string GetColoredName(this TreeObjectBase treeObject) => treeObject switch + { + TreeComputer computer => TreeStyle.Instance.Computer.GetColoredName(computer), + TreeGroup group => TreeStyle.Instance.Group.GetColoredName(group), + TreeUser user => TreeStyle.Instance.User.GetColoredName(user), + _ => throw new NotSupportedException(nameof(TreeObjectBase)) + }; + + internal static string GetColoredName(this Principal principal) => principal switch + { + ComputerPrincipal computer => TreeStyle.Instance.Computer.GetColoredName(computer), + GroupPrincipal group => TreeStyle.Instance.Group.GetColoredName(group), + UserPrincipal user => TreeStyle.Instance.User.GetColoredName(user), + _ => throw new NotSupportedException(nameof(Principal)) + }; + internal static string Indent(this string inputString, int indentation) { string corner = TreeStyle.Instance.RenderingSet.Corner; diff --git a/src/PSADTree/Style/ComputerStyle.cs b/src/PSADTree/Style/ComputerStyle.cs new file mode 100644 index 0000000..835d072 --- /dev/null +++ b/src/PSADTree/Style/ComputerStyle.cs @@ -0,0 +1,19 @@ +using System.DirectoryServices.AccountManagement; + +namespace PSADTree.Style; + +public sealed class ComputerStyle +{ + internal ComputerStyle() + { } + + internal string GetColoredName(TreeObjectBase computer) + { + return computer.SamAccountName; + } + + internal string GetColoredName(ComputerPrincipal computer) + { + return computer.SamAccountName; + } +} diff --git a/src/PSADTree/Style/GroupStyle.cs b/src/PSADTree/Style/GroupStyle.cs new file mode 100644 index 0000000..a152faf --- /dev/null +++ b/src/PSADTree/Style/GroupStyle.cs @@ -0,0 +1,52 @@ +using System.DirectoryServices.AccountManagement; + +namespace PSADTree.Style; + +public sealed class GroupStyle +{ + public string Circular + { + get; + set => field = TreeStyle.ThrowIfInvalidSequence(value); + } = "\x1B[91m"; + public string Processed + { + get; + set => field = TreeStyle.ThrowIfInvalidSequence(value); + } = "\x1B[93m"; + + internal GroupStyle() + { } + + internal string GetColoredName(TreeObjectBase group) + { + return group.SamAccountName; + } + + internal string GetColoredName(GroupPrincipal group) + { + return group.SamAccountName; + } + + internal string GetColoredProcessed() + { + TreeStyle treeStyle = TreeStyle.Instance; + if (treeStyle.OutputRendering == OutputRendering.PlainText) + { + return $"{treeStyle.RenderingSet.Arrows} Circular Reference"; + } + + return $"{treeStyle.RenderingSet.Arrows} {Circular}Circular Reference{treeStyle.Reset}"; + } + + internal string GetColoredCircular() + { + TreeStyle treeStyle = TreeStyle.Instance; + if (treeStyle.OutputRendering == OutputRendering.PlainText) + { + return $"{treeStyle.RenderingSet.Arrows} Processed Group"; + } + + return $"{treeStyle.RenderingSet.Arrows} {Processed}Processed Group{treeStyle.Reset}"; + } +} diff --git a/src/PSADTree/Style/TreeStyle.cs b/src/PSADTree/Style/TreeStyle.cs index e1a54a1..8c491dd 100644 --- a/src/PSADTree/Style/TreeStyle.cs +++ b/src/PSADTree/Style/TreeStyle.cs @@ -28,6 +28,9 @@ public RenderingStyle RenderingStyle } } = RenderingStyle.Fancy; public string Reset { get; } = "\x1B[0m"; + public ComputerStyle Computer { get; } = new(); + public GroupStyle Group { get; } = new(); + public UserStyle User { get; } = new(); public Palette Palette { get; } = new(); internal RenderingSet RenderingSet { get; private set; } = RenderingSet.Fancy; @@ -91,10 +94,7 @@ internal static string FormatType(object instance) return builder.ToString(); } - private static string EscapeSequence(string vt, int padding) => - $"{vt}{vt.Replace("\x1B", "`e").PadRight(padding)}\x1B[0m"; - - private static string ThrowIfInvalidSequence(string vt) + internal static string ThrowIfInvalidSequence(string vt) { if (!s_validate.IsMatch(vt)) { @@ -103,4 +103,7 @@ private static string ThrowIfInvalidSequence(string vt) return vt; } + + private static string EscapeSequence(string vt, int padding) => + $"{vt}{vt.Replace("\x1B", "`e").PadRight(padding)}\x1B[0m"; } diff --git a/src/PSADTree/Style/UserStyle.cs b/src/PSADTree/Style/UserStyle.cs new file mode 100644 index 0000000..72ab8fa --- /dev/null +++ b/src/PSADTree/Style/UserStyle.cs @@ -0,0 +1,20 @@ +using System.DirectoryServices.AccountManagement; + +namespace PSADTree.Style; + +public sealed class UserStyle +{ + internal UserStyle() + { } + + internal string GetColoredName(TreeObjectBase user) + { + return user.SamAccountName; + } + + internal string GetColoredName(UserPrincipal user) + { + return user.SamAccountName; + } + +} diff --git a/src/PSADTree/TreeGroup.cs b/src/PSADTree/TreeGroup.cs index 07c5d75..52558d3 100644 --- a/src/PSADTree/TreeGroup.cs +++ b/src/PSADTree/TreeGroup.cs @@ -63,8 +63,7 @@ internal bool SetIfCircularNested() { if (IsCircular = IsCircularNested()) { - TreeStyle treeStyle = TreeStyle.Instance; - Hierarchy = $"{Hierarchy} {treeStyle.RenderingSet.Arrows} {treeStyle.Palette.Foreground.BrightRed}Circular Reference{treeStyle.Reset}"; + Hierarchy = $"{Hierarchy} {TreeStyle.Instance.Group.GetColoredCircular()}"; } return IsCircular; @@ -72,8 +71,7 @@ internal bool SetIfCircularNested() internal void SetProcessed() { - TreeStyle treeStyle = TreeStyle.Instance; - Hierarchy = $"{Hierarchy} {treeStyle.RenderingSet.Arrows} {treeStyle.Palette.Foreground.BrightYellow}Processed Group{treeStyle.Reset}"; + Hierarchy = $"{Hierarchy} {TreeStyle.Instance.Group.GetColoredProcessed()}"; } internal void LinkCachedChildren(TreeCache cache) diff --git a/src/PSADTree/TreeObjectBase.cs b/src/PSADTree/TreeObjectBase.cs index 1aba91c..2a68d22 100644 --- a/src/PSADTree/TreeObjectBase.cs +++ b/src/PSADTree/TreeObjectBase.cs @@ -50,7 +50,7 @@ protected TreeObjectBase( DistinguishedName = treeObject.DistinguishedName; ObjectGuid = treeObject.ObjectGuid; ObjectSid = treeObject.ObjectSid; - Hierarchy = treeObject.SamAccountName.Indent(depth); + Hierarchy = treeObject.GetColoredName().Indent(depth); Parent = parent; UserPrincipalName = treeObject.UserPrincipalName; Description = treeObject.Description; @@ -67,7 +67,7 @@ protected TreeObjectBase(string source, Principal principal, string[] properties DistinguishedName = principal.DistinguishedName; ObjectGuid = principal.Guid; ObjectSid = principal.Sid; - Hierarchy = SamAccountName; + Hierarchy = principal.GetColoredName(); UserPrincipalName = principal.UserPrincipalName; Description = principal.Description; DisplayName = principal.DisplayName; @@ -83,7 +83,7 @@ protected TreeObjectBase( : this(source, principal, properties) { Depth = depth; - Hierarchy = principal.SamAccountName.Indent(depth); + Hierarchy = principal.GetColoredName().Indent(depth); Parent = parent; } From 59082e4d23ab6069ec369ffe0e72a088221864b7 Mon Sep 17 00:00:00 2001 From: AJ Raymond <19650958-poshAJ@users.noreply.gitlab.com> Date: Mon, 13 Apr 2026 01:33:43 +0000 Subject: [PATCH 04/17] combine styles into leaf --- module/PSADTree.Formats.ps1xml | 4 +- src/PSADTree/Extensions/TreeExtensions.cs | 16 ------ src/PSADTree/Style/ComputerStyle.cs | 19 ------- src/PSADTree/Style/GroupStyle.cs | 52 ------------------- src/PSADTree/Style/LeafStyle.cs | 62 +++++++++++++++++++++++ src/PSADTree/Style/TreeStyle.cs | 4 +- src/PSADTree/Style/UserStyle.cs | 20 -------- src/PSADTree/TreeGroup.cs | 4 +- src/PSADTree/TreeObjectBase.cs | 7 +-- 9 files changed, 71 insertions(+), 117 deletions(-) delete mode 100644 src/PSADTree/Style/ComputerStyle.cs delete mode 100644 src/PSADTree/Style/GroupStyle.cs create mode 100644 src/PSADTree/Style/LeafStyle.cs delete mode 100644 src/PSADTree/Style/UserStyle.cs diff --git a/module/PSADTree.Formats.ps1xml b/module/PSADTree.Formats.ps1xml index d24e044..358e1c6 100644 --- a/module/PSADTree.Formats.ps1xml +++ b/module/PSADTree.Formats.ps1xml @@ -43,9 +43,9 @@ - PSADTree.Style.GroupStyle + PSADTree.Style.LeafStyle - PSADTree.Style.GroupStyle + PSADTree.Style.LeafStyle diff --git a/src/PSADTree/Extensions/TreeExtensions.cs b/src/PSADTree/Extensions/TreeExtensions.cs index 8add7c7..140e58c 100644 --- a/src/PSADTree/Extensions/TreeExtensions.cs +++ b/src/PSADTree/Extensions/TreeExtensions.cs @@ -24,22 +24,6 @@ internal static class TreeExtensions private static StringBuilder? s_sb; #endif - internal static string GetColoredName(this TreeObjectBase treeObject) => treeObject switch - { - TreeComputer computer => TreeStyle.Instance.Computer.GetColoredName(computer), - TreeGroup group => TreeStyle.Instance.Group.GetColoredName(group), - TreeUser user => TreeStyle.Instance.User.GetColoredName(user), - _ => throw new NotSupportedException(nameof(TreeObjectBase)) - }; - - internal static string GetColoredName(this Principal principal) => principal switch - { - ComputerPrincipal computer => TreeStyle.Instance.Computer.GetColoredName(computer), - GroupPrincipal group => TreeStyle.Instance.Group.GetColoredName(group), - UserPrincipal user => TreeStyle.Instance.User.GetColoredName(user), - _ => throw new NotSupportedException(nameof(Principal)) - }; - internal static string Indent(this string inputString, int indentation) { string corner = TreeStyle.Instance.RenderingSet.Corner; diff --git a/src/PSADTree/Style/ComputerStyle.cs b/src/PSADTree/Style/ComputerStyle.cs deleted file mode 100644 index 835d072..0000000 --- a/src/PSADTree/Style/ComputerStyle.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.DirectoryServices.AccountManagement; - -namespace PSADTree.Style; - -public sealed class ComputerStyle -{ - internal ComputerStyle() - { } - - internal string GetColoredName(TreeObjectBase computer) - { - return computer.SamAccountName; - } - - internal string GetColoredName(ComputerPrincipal computer) - { - return computer.SamAccountName; - } -} diff --git a/src/PSADTree/Style/GroupStyle.cs b/src/PSADTree/Style/GroupStyle.cs deleted file mode 100644 index a152faf..0000000 --- a/src/PSADTree/Style/GroupStyle.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.DirectoryServices.AccountManagement; - -namespace PSADTree.Style; - -public sealed class GroupStyle -{ - public string Circular - { - get; - set => field = TreeStyle.ThrowIfInvalidSequence(value); - } = "\x1B[91m"; - public string Processed - { - get; - set => field = TreeStyle.ThrowIfInvalidSequence(value); - } = "\x1B[93m"; - - internal GroupStyle() - { } - - internal string GetColoredName(TreeObjectBase group) - { - return group.SamAccountName; - } - - internal string GetColoredName(GroupPrincipal group) - { - return group.SamAccountName; - } - - internal string GetColoredProcessed() - { - TreeStyle treeStyle = TreeStyle.Instance; - if (treeStyle.OutputRendering == OutputRendering.PlainText) - { - return $"{treeStyle.RenderingSet.Arrows} Circular Reference"; - } - - return $"{treeStyle.RenderingSet.Arrows} {Circular}Circular Reference{treeStyle.Reset}"; - } - - internal string GetColoredCircular() - { - TreeStyle treeStyle = TreeStyle.Instance; - if (treeStyle.OutputRendering == OutputRendering.PlainText) - { - return $"{treeStyle.RenderingSet.Arrows} Processed Group"; - } - - return $"{treeStyle.RenderingSet.Arrows} {Processed}Processed Group{treeStyle.Reset}"; - } -} diff --git a/src/PSADTree/Style/LeafStyle.cs b/src/PSADTree/Style/LeafStyle.cs new file mode 100644 index 0000000..a25c226 --- /dev/null +++ b/src/PSADTree/Style/LeafStyle.cs @@ -0,0 +1,62 @@ +using System.DirectoryServices.AccountManagement; + +namespace PSADTree.Style; + +public sealed class LeafStyle +{ + public string Circular + { + get; + set => field = TreeStyle.ThrowIfInvalidSequence(value); + } = "\x1B[91m"; + public string Processed + { + get; + set => field = TreeStyle.ThrowIfInvalidSequence(value); + } = "\x1B[93m"; + + private TreeStyle TreeStyle { get => TreeStyle.Instance; } + + internal LeafStyle() + { } + + internal string GetColoredName(TreeObjectBase treeObject) + { + if (TreeStyle.OutputRendering == OutputRendering.PlainText) + { + return treeObject.SamAccountName; + } + + return $"{treeObject.SamAccountName}"; + } + + internal string GetColoredName(Principal principal) + { + if (TreeStyle.OutputRendering == OutputRendering.PlainText) + { + return principal.SamAccountName; + } + + return $"{principal.SamAccountName}"; + } + + internal string GetColoredCircular() + { + if (TreeStyle.OutputRendering == OutputRendering.PlainText) + { + return $"{TreeStyle.RenderingSet.Arrows} Circular Reference"; + } + + return $"{TreeStyle.RenderingSet.Arrows} {Circular}Circular Reference{TreeStyle.Reset}"; + } + + internal string GetColoredProcessed() + { + if (TreeStyle.OutputRendering == OutputRendering.PlainText) + { + return $"{TreeStyle.RenderingSet.Arrows} Processed Group"; + } + + return $"{TreeStyle.RenderingSet.Arrows} {Processed}Processed Group{TreeStyle.Reset}"; + } +} diff --git a/src/PSADTree/Style/TreeStyle.cs b/src/PSADTree/Style/TreeStyle.cs index 8c491dd..1c3b236 100644 --- a/src/PSADTree/Style/TreeStyle.cs +++ b/src/PSADTree/Style/TreeStyle.cs @@ -28,9 +28,7 @@ public RenderingStyle RenderingStyle } } = RenderingStyle.Fancy; public string Reset { get; } = "\x1B[0m"; - public ComputerStyle Computer { get; } = new(); - public GroupStyle Group { get; } = new(); - public UserStyle User { get; } = new(); + public LeafStyle Leaf { get; } = new(); public Palette Palette { get; } = new(); internal RenderingSet RenderingSet { get; private set; } = RenderingSet.Fancy; diff --git a/src/PSADTree/Style/UserStyle.cs b/src/PSADTree/Style/UserStyle.cs deleted file mode 100644 index 72ab8fa..0000000 --- a/src/PSADTree/Style/UserStyle.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.DirectoryServices.AccountManagement; - -namespace PSADTree.Style; - -public sealed class UserStyle -{ - internal UserStyle() - { } - - internal string GetColoredName(TreeObjectBase user) - { - return user.SamAccountName; - } - - internal string GetColoredName(UserPrincipal user) - { - return user.SamAccountName; - } - -} diff --git a/src/PSADTree/TreeGroup.cs b/src/PSADTree/TreeGroup.cs index 52558d3..5f9bad0 100644 --- a/src/PSADTree/TreeGroup.cs +++ b/src/PSADTree/TreeGroup.cs @@ -63,7 +63,7 @@ internal bool SetIfCircularNested() { if (IsCircular = IsCircularNested()) { - Hierarchy = $"{Hierarchy} {TreeStyle.Instance.Group.GetColoredCircular()}"; + Hierarchy = $"{Hierarchy} {TreeStyle.Instance.Leaf.GetColoredCircular()}"; } return IsCircular; @@ -71,7 +71,7 @@ internal bool SetIfCircularNested() internal void SetProcessed() { - Hierarchy = $"{Hierarchy} {TreeStyle.Instance.Group.GetColoredProcessed()}"; + Hierarchy = $"{Hierarchy} {TreeStyle.Instance.Leaf.GetColoredProcessed()}"; } internal void LinkCachedChildren(TreeCache cache) diff --git a/src/PSADTree/TreeObjectBase.cs b/src/PSADTree/TreeObjectBase.cs index 2a68d22..388b665 100644 --- a/src/PSADTree/TreeObjectBase.cs +++ b/src/PSADTree/TreeObjectBase.cs @@ -3,6 +3,7 @@ using System.DirectoryServices.AccountManagement; using System.Security.Principal; using PSADTree.Extensions; +using PSADTree.Style; namespace PSADTree; @@ -50,7 +51,7 @@ protected TreeObjectBase( DistinguishedName = treeObject.DistinguishedName; ObjectGuid = treeObject.ObjectGuid; ObjectSid = treeObject.ObjectSid; - Hierarchy = treeObject.GetColoredName().Indent(depth); + Hierarchy = TreeStyle.Instance.Leaf.GetColoredName(treeObject).Indent(depth); Parent = parent; UserPrincipalName = treeObject.UserPrincipalName; Description = treeObject.Description; @@ -67,7 +68,7 @@ protected TreeObjectBase(string source, Principal principal, string[] properties DistinguishedName = principal.DistinguishedName; ObjectGuid = principal.Guid; ObjectSid = principal.Sid; - Hierarchy = principal.GetColoredName(); + Hierarchy = TreeStyle.Instance.Leaf.GetColoredName(principal); UserPrincipalName = principal.UserPrincipalName; Description = principal.Description; DisplayName = principal.DisplayName; @@ -83,7 +84,7 @@ protected TreeObjectBase( : this(source, principal, properties) { Depth = depth; - Hierarchy = principal.GetColoredName().Indent(depth); + Hierarchy = TreeStyle.Instance.Leaf.GetColoredName(principal).Indent(depth); Parent = parent; } From 56cb0aa8451412bdfd344466a121de741ed070f6 Mon Sep 17 00:00:00 2001 From: AJ Raymond <19650958-poshAJ@users.noreply.gitlab.com> Date: Fri, 17 Apr 2026 11:45:33 +0000 Subject: [PATCH 05/17] update names and format --- src/PSADTree/Style/Palette.cs | 43 ++++++++++++++++--- .../Style/{LeafStyle.cs => PrincipalStyle.cs} | 24 ++++++++++- src/PSADTree/Style/RenderingSet.cs | 7 +++ src/PSADTree/Style/TreeStyle.cs | 18 +++++--- src/PSADTree/TreeGroup.cs | 6 ++- src/PSADTree/TreeObjectBase.cs | 12 +++--- 6 files changed, 89 insertions(+), 21 deletions(-) rename src/PSADTree/Style/{LeafStyle.cs => PrincipalStyle.cs} (77%) diff --git a/src/PSADTree/Style/Palette.cs b/src/PSADTree/Style/Palette.cs index fd9e46e..6b096b4 100644 --- a/src/PSADTree/Style/Palette.cs +++ b/src/PSADTree/Style/Palette.cs @@ -6,24 +6,39 @@ public sealed class Palette { public sealed class ForegroundPalette { + private string? _toString; + public string Black { get; } = "\x1B[30m"; + public string BrightBlack { get; } = "\x1B[90m"; + public string White { get; } = "\x1B[37m"; + public string BrightWhite { get; } = "\x1B[97m"; + public string Red { get; } = "\x1B[31m"; + public string BrightRed { get; } = "\x1B[91m"; + public string Magenta { get; } = "\x1B[35m"; + public string BrightMagenta { get; } = "\x1B[95m"; + public string Blue { get; } = "\x1B[34m"; + public string BrightBlue { get; } = "\x1B[94m"; + public string Cyan { get; } = "\x1B[36m"; + public string BrightCyan { get; } = "\x1B[96m"; + public string Green { get; } = "\x1B[32m"; + public string BrightGreen { get; } = "\x1B[92m"; + public string Yellow { get; } = "\x1B[33m"; - public string BrightYellow { get; } = "\x1B[93m"; - private string? _toString; + public string BrightYellow { get; } = "\x1B[93m"; public override string ToString() => _toString ??= TreeStyle.FormatType(this); @@ -31,33 +46,49 @@ public override string ToString() => public sealed class BackgroundPalette { + private string? _toString; + public string Black { get; } = "\x1B[40m"; + public string BrightBlack { get; } = "\x1B[100m"; + public string White { get; } = "\x1B[47m"; + public string BrightWhite { get; } = "\x1B[107m"; + public string Red { get; } = "\x1B[41m"; + public string BrightRed { get; } = "\x1B[101m"; + public string Magenta { get; } = "\x1B[45m"; + public string BrightMagenta { get; } = "\x1B[105m"; + public string Blue { get; } = "\x1B[44m"; + public string BrightBlue { get; } = "\x1B[104m"; + public string Cyan { get; } = "\x1B[46m"; + public string BrightCyan { get; } = "\x1B[106m"; + public string Green { get; } = "\x1B[42m"; + public string BrightGreen { get; } = "\x1B[102m"; + public string Yellow { get; } = "\x1B[43m"; - public string BrightYellow { get; } = "\x1B[103m"; - private string? _toString; + public string BrightYellow { get; } = "\x1B[103m"; public override string ToString() => _toString ??= TreeStyle.FormatType(this); } + private string? _toString; + public ForegroundPalette Foreground { get; } = new(); - public BackgroundPalette Background { get; } = new(); - private string? _toString; + public BackgroundPalette Background { get; } = new(); internal Palette() { } diff --git a/src/PSADTree/Style/LeafStyle.cs b/src/PSADTree/Style/PrincipalStyle.cs similarity index 77% rename from src/PSADTree/Style/LeafStyle.cs rename to src/PSADTree/Style/PrincipalStyle.cs index a25c226..9d9c819 100644 --- a/src/PSADTree/Style/LeafStyle.cs +++ b/src/PSADTree/Style/PrincipalStyle.cs @@ -2,22 +2,42 @@ namespace PSADTree.Style; -public sealed class LeafStyle +public sealed class PrincipalStyle { public string Circular { get; set => field = TreeStyle.ThrowIfInvalidSequence(value); } = "\x1B[91m"; + public string Processed { get; set => field = TreeStyle.ThrowIfInvalidSequence(value); } = "\x1B[93m"; + public string Computer + { + get; + set => TreeStyle.ThrowIfInvalidSequence(value); + } = string.Empty; + + public string Group + { + get; + set => field = TreeStyle.ThrowIfInvalidSequence(value); + } = string.Empty; + + public string User + { + get; + set => field = TreeStyle.ThrowIfInvalidSequence(value); + } = string.Empty; + + private TreeStyle TreeStyle { get => TreeStyle.Instance; } - internal LeafStyle() + internal PrincipalStyle() { } internal string GetColoredName(TreeObjectBase treeObject) diff --git a/src/PSADTree/Style/RenderingSet.cs b/src/PSADTree/Style/RenderingSet.cs index 7f8ec41..d64dc83 100644 --- a/src/PSADTree/Style/RenderingSet.cs +++ b/src/PSADTree/Style/RenderingSet.cs @@ -3,14 +3,21 @@ namespace PSADTree.Style; internal readonly struct RenderingSet { internal static readonly RenderingSet Fancy = new("└── ", '└', '│', '├', "↔"); + internal static readonly RenderingSet FancyRounded = new("╰── ", '╰', '│', '├', "↔"); + internal static readonly RenderingSet Classic = new("+-- ", '+', '|', '|', "<>"); + internal static readonly RenderingSet ClassicRounded = new("`-- ", '`', '|', '|', "<>"); internal string Corner { get; } + internal char UpRight { get; } + internal char Vertical { get; } + internal char VerticalRight { get; } + internal string Arrows { get; } private RenderingSet(string corner, char upRight, char vertical, char verticalRight, string arrows) diff --git a/src/PSADTree/Style/TreeStyle.cs b/src/PSADTree/Style/TreeStyle.cs index 1c3b236..1f00112 100644 --- a/src/PSADTree/Style/TreeStyle.cs +++ b/src/PSADTree/Style/TreeStyle.cs @@ -8,8 +8,16 @@ namespace PSADTree.Style; public sealed class TreeStyle { + private static TreeStyle? s_instance; + + private static readonly Regex s_validate = new( + @"^\x1B\[(?:[0-9]+;?){1,}m$", + RegexOptions.Compiled); + public static TreeStyle Instance { get => s_instance ??= new(); } + public OutputRendering OutputRendering { get; set; } = OutputRendering.Host; + public RenderingStyle RenderingStyle { get; @@ -27,17 +35,15 @@ public RenderingStyle RenderingStyle field = value; } } = RenderingStyle.Fancy; + public string Reset { get; } = "\x1B[0m"; - public LeafStyle Leaf { get; } = new(); + + public PrincipalStyle Principal { get; } = new(); + public Palette Palette { get; } = new(); internal RenderingSet RenderingSet { get; private set; } = RenderingSet.Fancy; - private static TreeStyle? s_instance; - private static readonly Regex s_validate = new( - @"^\x1B\[(?:[0-9]+;?){1,}m$", - RegexOptions.Compiled); - internal TreeStyle() { } diff --git a/src/PSADTree/TreeGroup.cs b/src/PSADTree/TreeGroup.cs index 5f9bad0..0f9ccdf 100644 --- a/src/PSADTree/TreeGroup.cs +++ b/src/PSADTree/TreeGroup.cs @@ -13,6 +13,8 @@ public sealed class TreeGroup : TreeObjectBase public bool IsCircular { get; private set; } + private TreeStyle TreeStyle { get => TreeStyle.Instance; } + private TreeGroup( TreeGroup group, TreeGroup parent, @@ -63,7 +65,7 @@ internal bool SetIfCircularNested() { if (IsCircular = IsCircularNested()) { - Hierarchy = $"{Hierarchy} {TreeStyle.Instance.Leaf.GetColoredCircular()}"; + Hierarchy = $"{Hierarchy} {TreeStyle.Principal.GetColoredCircular()}"; } return IsCircular; @@ -71,7 +73,7 @@ internal bool SetIfCircularNested() internal void SetProcessed() { - Hierarchy = $"{Hierarchy} {TreeStyle.Instance.Leaf.GetColoredProcessed()}"; + Hierarchy = $"{Hierarchy} {TreeStyle.Principal.GetColoredProcessed()}"; } internal void LinkCachedChildren(TreeCache cache) diff --git a/src/PSADTree/TreeObjectBase.cs b/src/PSADTree/TreeObjectBase.cs index 388b665..8a53669 100644 --- a/src/PSADTree/TreeObjectBase.cs +++ b/src/PSADTree/TreeObjectBase.cs @@ -11,8 +11,6 @@ public abstract class TreeObjectBase { public int Depth { get; } - internal string Source { get; } - public TreeGroup? Parent { get; } public string Domain { get; } @@ -37,6 +35,10 @@ public abstract class TreeObjectBase public ReadOnlyDictionary? AdditionalProperties { get; } + internal string Source { get; } + + private TreeStyle TreeStyle { get => TreeStyle.Instance; } + protected TreeObjectBase( TreeObjectBase treeObject, TreeGroup? parent, @@ -51,7 +53,7 @@ protected TreeObjectBase( DistinguishedName = treeObject.DistinguishedName; ObjectGuid = treeObject.ObjectGuid; ObjectSid = treeObject.ObjectSid; - Hierarchy = TreeStyle.Instance.Leaf.GetColoredName(treeObject).Indent(depth); + Hierarchy = TreeStyle.Principal.GetColoredName(treeObject).Indent(depth); Parent = parent; UserPrincipalName = treeObject.UserPrincipalName; Description = treeObject.Description; @@ -68,7 +70,7 @@ protected TreeObjectBase(string source, Principal principal, string[] properties DistinguishedName = principal.DistinguishedName; ObjectGuid = principal.Guid; ObjectSid = principal.Sid; - Hierarchy = TreeStyle.Instance.Leaf.GetColoredName(principal); + Hierarchy = TreeStyle.Principal.GetColoredName(principal); UserPrincipalName = principal.UserPrincipalName; Description = principal.Description; DisplayName = principal.DisplayName; @@ -84,7 +86,7 @@ protected TreeObjectBase( : this(source, principal, properties) { Depth = depth; - Hierarchy = TreeStyle.Instance.Leaf.GetColoredName(principal).Indent(depth); + Hierarchy = TreeStyle.Principal.GetColoredName(principal).Indent(depth); Parent = parent; } From 050263e3196261547405849f54af06c8c940fd0f Mon Sep 17 00:00:00 2001 From: AJ Raymond <19650958-poshAJ@users.noreply.gitlab.com> Date: Fri, 17 Apr 2026 12:08:46 +0000 Subject: [PATCH 06/17] update formats --- module/PSADTree.Formats.ps1xml | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/module/PSADTree.Formats.ps1xml b/module/PSADTree.Formats.ps1xml index 358e1c6..c035941 100644 --- a/module/PSADTree.Formats.ps1xml +++ b/module/PSADTree.Formats.ps1xml @@ -43,9 +43,9 @@ - PSADTree.Style.LeafStyle + PSADTree.Style.PrincipalStyle - PSADTree.Style.LeafStyle + PSADTree.Style.PrincipalStyle @@ -63,6 +63,24 @@ [PSADTree.Style.TreeStyle]::EscapeSequence($_.Processed) + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.Computer) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.Group) + + + + + + [PSADTree.Style.TreeStyle]::EscapeSequence($_.User) + + @@ -225,16 +243,8 @@ - - Computer - - - - Group - - - - User + + Principal From 2550ae5505ef4705f4e2769de8e2447b566e880f Mon Sep 17 00:00:00 2001 From: AJ Raymond <19650958-poshAJ@users.noreply.gitlab.com> Date: Fri, 17 Apr 2026 12:10:04 +0000 Subject: [PATCH 07/17] add colored names --- src/PSADTree/Style/PrincipalStyle.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/PSADTree/Style/PrincipalStyle.cs b/src/PSADTree/Style/PrincipalStyle.cs index 9d9c819..8b2de57 100644 --- a/src/PSADTree/Style/PrincipalStyle.cs +++ b/src/PSADTree/Style/PrincipalStyle.cs @@ -34,7 +34,6 @@ public string User set => field = TreeStyle.ThrowIfInvalidSequence(value); } = string.Empty; - private TreeStyle TreeStyle { get => TreeStyle.Instance; } internal PrincipalStyle() @@ -47,7 +46,13 @@ internal string GetColoredName(TreeObjectBase treeObject) return treeObject.SamAccountName; } - return $"{treeObject.SamAccountName}"; + return treeObject switch + { + TreeComputer treeComputer => $"{Computer}{treeComputer.SamAccountName}{TreeStyle.Reset}", + TreeGroup treeGroup => $"{Group}{treeGroup.SamAccountName}{TreeStyle.Reset}", + TreeUser treeUser => $"{User}{treeUser.SamAccountName}{TreeStyle.Reset}", + _ => treeObject.SamAccountName + }; } internal string GetColoredName(Principal principal) @@ -57,7 +62,13 @@ internal string GetColoredName(Principal principal) return principal.SamAccountName; } - return $"{principal.SamAccountName}"; + return principal switch + { + ComputerPrincipal computerPrincipal => $"{Computer}{computerPrincipal.SamAccountName}{TreeStyle.Reset}", + GroupPrincipal groupPrincipal => $"{Group}{groupPrincipal.SamAccountName}{TreeStyle.Reset}", + UserPrincipal userPrincipal => $"{User}{userPrincipal.SamAccountName}{TreeStyle.Reset}", + _ => principal.SamAccountName + }; } internal string GetColoredCircular() From 813d71ecf5f316d6d13cf06e64a03d3e8d783e65 Mon Sep 17 00:00:00 2001 From: AJ Raymond <19650958-poshAJ@users.noreply.gitlab.com> Date: Fri, 17 Apr 2026 12:33:25 +0000 Subject: [PATCH 08/17] add checks for output rendering --- src/PSADTree/PSADTreeCmdletBase.cs | 3 +++ src/PSADTree/Style/PrincipalStyle.cs | 12 ++++++++---- src/PSADTree/Style/TreeStyle.cs | 2 ++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/PSADTree/PSADTreeCmdletBase.cs b/src/PSADTree/PSADTreeCmdletBase.cs index aed33a0..12c2ed4 100644 --- a/src/PSADTree/PSADTreeCmdletBase.cs +++ b/src/PSADTree/PSADTreeCmdletBase.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Management.Automation; using PSADTree.Extensions; +using PSADTree.Style; namespace PSADTree; @@ -87,6 +88,8 @@ protected override void BeginProcessing() { try { + TreeStyle.Instance.SupportsVirtualTerminal = Host.UI.SupportsVirtualTerminal; + if (Recursive.IsPresent) { Depth = int.MaxValue; diff --git a/src/PSADTree/Style/PrincipalStyle.cs b/src/PSADTree/Style/PrincipalStyle.cs index 8b2de57..c64a316 100644 --- a/src/PSADTree/Style/PrincipalStyle.cs +++ b/src/PSADTree/Style/PrincipalStyle.cs @@ -41,7 +41,8 @@ internal PrincipalStyle() internal string GetColoredName(TreeObjectBase treeObject) { - if (TreeStyle.OutputRendering == OutputRendering.PlainText) + if (TreeStyle.OutputRendering == OutputRendering.PlainText || + (TreeStyle.OutputRendering == OutputRendering.Host && !TreeStyle.SupportsVirtualTerminal)) { return treeObject.SamAccountName; } @@ -57,7 +58,8 @@ internal string GetColoredName(TreeObjectBase treeObject) internal string GetColoredName(Principal principal) { - if (TreeStyle.OutputRendering == OutputRendering.PlainText) + if (TreeStyle.OutputRendering == OutputRendering.PlainText || + (TreeStyle.OutputRendering == OutputRendering.Host && !TreeStyle.SupportsVirtualTerminal)) { return principal.SamAccountName; } @@ -73,7 +75,8 @@ internal string GetColoredName(Principal principal) internal string GetColoredCircular() { - if (TreeStyle.OutputRendering == OutputRendering.PlainText) + if (TreeStyle.OutputRendering == OutputRendering.PlainText || + (TreeStyle.OutputRendering == OutputRendering.Host && !TreeStyle.SupportsVirtualTerminal)) { return $"{TreeStyle.RenderingSet.Arrows} Circular Reference"; } @@ -83,7 +86,8 @@ internal string GetColoredCircular() internal string GetColoredProcessed() { - if (TreeStyle.OutputRendering == OutputRendering.PlainText) + if (TreeStyle.OutputRendering == OutputRendering.PlainText || + (TreeStyle.OutputRendering == OutputRendering.Host && !TreeStyle.SupportsVirtualTerminal)) { return $"{TreeStyle.RenderingSet.Arrows} Processed Group"; } diff --git a/src/PSADTree/Style/TreeStyle.cs b/src/PSADTree/Style/TreeStyle.cs index 1f00112..4c51338 100644 --- a/src/PSADTree/Style/TreeStyle.cs +++ b/src/PSADTree/Style/TreeStyle.cs @@ -44,6 +44,8 @@ public RenderingStyle RenderingStyle internal RenderingSet RenderingSet { get; private set; } = RenderingSet.Fancy; + internal bool SupportsVirtualTerminal { get; set; } = true; + internal TreeStyle() { } From 8b4e47b77b4708023b52b9a233c2705a89959900 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 17 Apr 2026 12:33:15 -0300 Subject: [PATCH 09/17] update extensions to use extension keyword --- .../Extensions/ExceptionExtensions.cs | 40 +-- src/PSADTree/Extensions/MiscExtensions.cs | 107 ++++---- src/PSADTree/Extensions/TreeExtensions.cs | 228 +++++++++--------- 3 files changed, 198 insertions(+), 177 deletions(-) diff --git a/src/PSADTree/Extensions/ExceptionExtensions.cs b/src/PSADTree/Extensions/ExceptionExtensions.cs index 536c32b..de83a7e 100644 --- a/src/PSADTree/Extensions/ExceptionExtensions.cs +++ b/src/PSADTree/Extensions/ExceptionExtensions.cs @@ -6,26 +6,34 @@ namespace PSADTree.Extensions; internal static class ExceptionExtensions { - internal static ErrorRecord ToIdentityNotFound(this string? identity) => - new( - new NoMatchingPrincipalException($"Cannot find an object with identity: '{identity}'."), - "IdentityNotFound", - ErrorCategory.ObjectNotFound, - identity); + extension(string? identity) + { + internal ErrorRecord ToIdentityNotFound() => + new( + new NoMatchingPrincipalException($"Cannot find an object with identity: '{identity}'."), + "IdentityNotFound", + ErrorCategory.ObjectNotFound, + identity); + } - internal static ErrorRecord ToAmbiguousIdentity(this Exception exception, string? identity) => - new(exception, "AmbiguousIdentity", ErrorCategory.InvalidResult, identity); + extension(Exception exception) + { + internal ErrorRecord ToAmbiguousIdentity(string? identity) => + new(exception, "AmbiguousIdentity", ErrorCategory.InvalidResult, identity); - internal static ErrorRecord ToUnspecified(this Exception exception, string? identity) => - new(exception, "Unspecified", ErrorCategory.NotSpecified, identity); + internal ErrorRecord ToUnspecified(string? identity) => + new(exception, "Unspecified", ErrorCategory.NotSpecified, identity); - internal static ErrorRecord ToEnumerationFailure(this Exception exception, Principal? principal) => - new(exception, "EnumerationFailure", ErrorCategory.NotSpecified, principal); + internal ErrorRecord ToEnumerationFailure(Principal? principal) => + new(exception, "EnumerationFailure", ErrorCategory.NotSpecified, principal); - internal static ErrorRecord ToSetPrincipalContext(this Exception exception) => - new(exception, "SetPrincipalContext", ErrorCategory.ConnectionError, null); + internal ErrorRecord ToSetPrincipalContext() => + new(exception, "SetPrincipalContext", ErrorCategory.ConnectionError, null); + } - internal static void ThrowInvalidSequence(this string vt) => - throw new ArgumentException( + extension(string vt) + { + internal void ThrowInvalidSequence() => throw new ArgumentException( $"The specified string contains printable content when it should only contain ANSI escape sequences: '{vt}'."); + } } diff --git a/src/PSADTree/Extensions/MiscExtensions.cs b/src/PSADTree/Extensions/MiscExtensions.cs index 29e0a8c..bfd8333 100644 --- a/src/PSADTree/Extensions/MiscExtensions.cs +++ b/src/PSADTree/Extensions/MiscExtensions.cs @@ -11,58 +11,74 @@ namespace PSADTree.Extensions; internal static class MiscExtensions { - internal static DirectoryEntry GetDirectoryEntry(this Principal principal) - => (DirectoryEntry)principal.GetUnderlyingObject(); - - internal static ReadOnlyDictionary? GetAdditionalProperties( - this Principal principal, - string[] properties) + extension(Principal principal) { - if (properties.Length == 0) - return null; - - DirectoryEntry entry = principal.GetDirectoryEntry(); - - if (properties.Any(e => e == "*")) - return entry.GetAllAttributes(); - - Dictionary additionalProperties = new( - capacity: properties.Length, - StringComparer.OrdinalIgnoreCase); + internal DirectoryEntry GetDirectoryEntry() => (DirectoryEntry)principal.GetUnderlyingObject(); - foreach (string property in properties) + internal ReadOnlyDictionary? GetAdditionalProperties(string[] properties) { - // already processed - if (additionalProperties.ContainsKey(property)) - continue; + if (properties.Length == 0) + return null; - if (!LdapMap.TryGetValue(property, out string? ldapDn)) - { - ldapDn = property; - } + DirectoryEntry entry = principal.GetDirectoryEntry(); - if (IsSecurityDescriptor(property)) - { - additionalProperties[property] = entry.GetSecurityDescriptorAsPSObject(); - continue; - } + if (properties.Any(e => e == "*")) + return GetAllAttributes(entry); - object? value = entry.Properties[ldapDn]?.Value; + Dictionary additionalProperties = new( + capacity: properties.Length, + StringComparer.OrdinalIgnoreCase); - if (value is null) continue; - if (IsIAdsLargeInteger(value, out long? fileTime)) + foreach (string property in properties) { - additionalProperties[property] = fileTime; - continue; + // already processed + if (additionalProperties.ContainsKey(property)) + continue; + + if (!LdapMap.TryGetValue(property, out string? ldapDn)) + { + ldapDn = property; + } + + if (IsSecurityDescriptor(property)) + { + additionalProperties[property] = entry.GetSecurityDescriptorAsPSObject(); + continue; + } + + object? value = entry.Properties[ldapDn]?.Value; + + if (value is null) continue; + if (IsIAdsLargeInteger(value, out long? fileTime)) + { + additionalProperties[property] = fileTime; + continue; + } + + additionalProperties[property] = value; } - additionalProperties[property] = value; + return additionalProperties is { Count: 0 } ? null : new(additionalProperties); } + } - return additionalProperties is { Count: 0 } ? null : new(additionalProperties); + extension(AuthenticablePrincipal principal) + { + internal UserAccountControl? GetUserAccountControl() + { + DirectoryEntry entry = principal.GetDirectoryEntry(); + object? uac = entry.Properties["userAccountControl"]?.Value; + if (uac is null) return null; + return (UserAccountControl)Convert.ToUInt32(uac); + } } - private static ReadOnlyDictionary GetAllAttributes(this DirectoryEntry entry) + extension(UserAccountControl uac) + { + internal bool IsEnabled() => !uac.HasFlag(UserAccountControl.ACCOUNTDISABLE); + } + + private static ReadOnlyDictionary GetAllAttributes(DirectoryEntry entry) { Dictionary additionalProperties = new( capacity: entry.Properties.Count, @@ -79,6 +95,7 @@ internal static DirectoryEntry GetDirectoryEntry(this Principal principal) object? value = entry.Properties[property]?.Value; if (value is null) continue; + if (IsIAdsLargeInteger(value, out long? fileTime)) { additionalProperties[property] = fileTime; @@ -96,8 +113,7 @@ private static bool IsIAdsLargeInteger( [NotNullWhen(true)] out long? fileTime) { fileTime = default; - if (value is not IAdsLargeInteger largeInt) - return false; + if (value is not IAdsLargeInteger largeInt) return false; fileTime = (largeInt.HighPart << 32) + largeInt.LowPart; return true; @@ -105,15 +121,4 @@ private static bool IsIAdsLargeInteger( private static bool IsSecurityDescriptor(string ldapDn) => ldapDn.Equals("nTSecurityDescriptor", StringComparison.OrdinalIgnoreCase); - - internal static UserAccountControl? GetUserAccountControl(this AuthenticablePrincipal principal) - { - DirectoryEntry entry = principal.GetDirectoryEntry(); - object? uac = entry.Properties["userAccountControl"]?.Value; - if (uac is null) return null; - return (UserAccountControl)Convert.ToUInt32(uac); - } - - internal static bool IsEnabled(this UserAccountControl uac) - => !uac.HasFlag(UserAccountControl.ACCOUNTDISABLE); } diff --git a/src/PSADTree/Extensions/TreeExtensions.cs b/src/PSADTree/Extensions/TreeExtensions.cs index 140e58c..f5a0535 100644 --- a/src/PSADTree/Extensions/TreeExtensions.cs +++ b/src/PSADTree/Extensions/TreeExtensions.cs @@ -13,153 +13,161 @@ namespace PSADTree.Extensions; -internal static class TreeExtensions +internal static partial class TreeExtensions { +#if NETCOREAPP + [GeneratedRegex("(?<=,)DC=.+$", RegexOptions.Compiled)] + private static partial Regex GetDefaultNamingContextRegex(); + + private static readonly Regex s_reDefaultNamingContext = GetDefaultNamingContextRegex(); +#else private static readonly Regex s_reDefaultNamingContext = new( "(?<=,)DC=.+$", RegexOptions.Compiled); - -#if !NETCOREAPP - [ThreadStatic] - private static StringBuilder? s_sb; #endif - internal static string Indent(this string inputString, int indentation) + extension(string input) { - string corner = TreeStyle.Instance.RenderingSet.Corner; - int repeatCount = (4 * indentation) - 4; - int capacity = repeatCount + 4 + inputString.Length; + internal string GetDefaultNamingContext() => s_reDefaultNamingContext.Match(input).Value; -#if NETCOREAPP - return string.Create( - capacity, (repeatCount, corner, inputString), - static (buffer, state) => +#if !NETCOREAPP + [ThreadStatic] + private static StringBuilder? s_sb; +#endif + internal string Indent(int indentation) { - int count = state.repeatCount; - buffer[..count].Fill(' '); - state.corner.AsSpan().CopyTo(buffer[count..]); - state.inputString.AsSpan().CopyTo(buffer[(count + 4)..]); - }); + string corner = TreeStyle.Instance.RenderingSet.Corner; + int repeatCount = (4 * indentation) - 4; + int capacity = repeatCount + 4 + input.Length; + +#if NETCOREAPP + return string.Create( + capacity, (repeatCount, corner, input), + static (buffer, state) => + { + int count = state.repeatCount; + buffer[..count].Fill(' '); + state.corner.AsSpan().CopyTo(buffer[count..]); + state.input.AsSpan().CopyTo(buffer[(count + 4)..]); + }); #else - s_sb ??= new StringBuilder(64); - s_sb.Clear().EnsureCapacity(capacity); - - return s_sb - .Append(' ', repeatCount) - .Append(corner) - .Append(inputString) - .ToString(); + s_sb ??= new StringBuilder(64); + s_sb.Clear().EnsureCapacity(capacity); + + return s_sb + .Append(' ', repeatCount) + .Append(corner) + .Append(input) + .ToString(); #endif - } + } - internal static TreeObjectBase[] Format( - this TreeObjectBase[] tree) - { - int index; - RenderingSet set = TreeStyle.Instance.RenderingSet; - for (int i = 0; i < tree.Length; i++) +#if NETCOREAPP + [SkipLocalsInit] + private string ReplaceAt(int index, char newChar) + => string.Create( + input.Length, (input, index, newChar), + static (buffer, state) => + { + state.input.AsSpan().CopyTo(buffer); + buffer[state.index] = state.newChar; + }); +#else + private unsafe string ReplaceAt(int index, char newChar) { - TreeObjectBase current = tree[i]; + if (input.Length > 0x200) + { + char[] chars = input.ToCharArray(); + chars[index] = newChar; + return new string(chars); + } - if ((index = current.Hierarchy.IndexOf(set.UpRight)) == -1) + char* pChars = stackalloc char[0x200]; + fixed (char* source = input) { - continue; + Buffer.MemoryCopy( + source, + pChars, + 0x200 * sizeof(char), + input.Length * sizeof(char)); } - for (int z = i - 1; z >= 0; z--) + pChars[index] = newChar; + return new string(pChars, 0, input.Length); + } +#endif + } + + extension(TreeObjectBase[] tree) + { + internal TreeObjectBase[] Format() + { + int index; + RenderingSet set = TreeStyle.Instance.RenderingSet; + for (int i = 0; i < tree.Length; i++) { - current = tree[z]; - string hierarchy = current.Hierarchy; + TreeObjectBase current = tree[i]; - if (char.IsWhiteSpace(hierarchy[index])) + if ((index = current.Hierarchy.IndexOf(set.UpRight)) == -1) { - current.Hierarchy = hierarchy.ReplaceAt(index, set.Vertical); continue; } - if (hierarchy[index] == set.UpRight) + for (int z = i - 1; z >= 0; z--) { - current.Hierarchy = hierarchy.ReplaceAt(index, set.VerticalRight); - } + current = tree[z]; + string hierarchy = current.Hierarchy; - break; - } - } + if (char.IsWhiteSpace(hierarchy[index])) + { + current.Hierarchy = hierarchy.ReplaceAt(index, set.Vertical); + continue; + } - return tree; - } + if (hierarchy[index] == set.UpRight) + { + current.Hierarchy = hierarchy.ReplaceAt(index, set.VerticalRight); + } -#if NETCOREAPP - [SkipLocalsInit] - private static string ReplaceAt(this string input, int index, char newChar) - => string.Create( - input.Length, (input, index, newChar), - static (buffer, state) => - { - state.input.AsSpan().CopyTo(buffer); - buffer[state.index] = state.newChar; - }); -#else - private static unsafe string ReplaceAt(this string input, int index, char newChar) - { - if (input.Length > 0x200) - { - char[] chars = input.ToCharArray(); - chars[index] = newChar; - return new string(chars); - } + break; + } + } - char* pChars = stackalloc char[0x200]; - fixed (char* source = input) - { - Buffer.MemoryCopy( - source, - pChars, - 0x200 * sizeof(char), - input.Length * sizeof(char)); + return tree; } - - pChars[index] = newChar; - return new string(pChars, 0, input.Length); } -#endif - internal static IEnumerable ToSafeSortedEnumerable( - this TPrincipal principal, - Func> selector, - PSCmdlet cmdlet) - where TPrincipal : Principal + extension(TPrincipal principal) where TPrincipal : Principal { - List principals = []; - using PrincipalSearchResult search = selector(principal); - using IEnumerator enumerator = search.GetEnumerator(); - - while (true) + internal IEnumerable ToSafeSortedEnumerable( + Func> selector, + PSCmdlet cmdlet) { - try + List principals = []; + using PrincipalSearchResult search = selector(principal); + using IEnumerator enumerator = search.GetEnumerator(); + + while (true) { - if (!enumerator.MoveNext()) + try { - break; - } + if (!enumerator.MoveNext()) break; - principals.Add(enumerator.Current); - } - catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) - { - throw; - } - catch (Exception exception) - { - cmdlet.WriteError(exception.ToEnumerationFailure(principal)); + principals.Add(enumerator.Current); + } + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) + { + throw; + } + catch (Exception exception) + { + cmdlet.WriteError(exception.ToEnumerationFailure(principal)); + } } - } - return principals - .OrderBy(static e => e.StructuralObjectClass == "group") - .ThenBy(static e => e, PSADTreeComparer.Value); + return principals + .OrderBy(static e => e.StructuralObjectClass == "group") + .ThenBy(static e => e, PSADTreeComparer.Value); + } } - - internal static string GetDefaultNamingContext(this string distinguishedName) => - s_reDefaultNamingContext.Match(distinguishedName).Value; } From 6469eb7bcea159f290fdeeac703e0dc1b3215009 Mon Sep 17 00:00:00 2001 From: AJ Raymond <19650958-poshAJ@users.noreply.gitlab.com> Date: Fri, 17 Apr 2026 15:37:35 +0000 Subject: [PATCH 10/17] switch to instance methods --- module/PSADTree.Formats.ps1xml | 44 ++++++++++++++++----------------- module/PSADTree.Types.ps1xml | 38 ---------------------------- module/PSADTree.psd1 | 2 +- src/PSADTree/Style/TreeStyle.cs | 10 ++++---- 4 files changed, 28 insertions(+), 66 deletions(-) delete mode 100644 module/PSADTree.Types.ps1xml diff --git a/module/PSADTree.Formats.ps1xml b/module/PSADTree.Formats.ps1xml index c035941..1c056f3 100644 --- a/module/PSADTree.Formats.ps1xml +++ b/module/PSADTree.Formats.ps1xml @@ -54,31 +54,31 @@ - [PSADTree.Style.TreeStyle]::EscapeSequence($_.Circular) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.Circular) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.Processed) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.Processed) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.Computer) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.Computer) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.Group) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.Group) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.User) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.User) @@ -121,97 +121,97 @@ - [PSADTree.Style.TreeStyle]::EscapeSequence($_.Black) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.Black) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightBlack) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.BrightBlack) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.White) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.White) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightWhite) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.BrightWhite) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.Red) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.Red) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightRed) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.BrightRed) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.Magenta) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.Magenta) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightMagenta) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.BrightMagenta) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.Blue) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.Blue) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightBlue) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.BrightBlue) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.Cyan) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.Cyan) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightCyan) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.BrightCyan) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.Green) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.Green) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightGreen) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.BrightGreen) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.Yellow) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.Yellow) - [PSADTree.Style.TreeStyle]::EscapeSequence($_.BrightYellow) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.BrightYellow) @@ -239,7 +239,7 @@ - [PSADTree.Style.TreeStyle]::EscapeSequence($_.Reset) + [PSADTree.Style.TreeStyle]::Instance.EscapeSequence($_.Reset) diff --git a/module/PSADTree.Types.ps1xml b/module/PSADTree.Types.ps1xml deleted file mode 100644 index 613ad77..0000000 --- a/module/PSADTree.Types.ps1xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - PSADTree.Style.TreeStyle - - - CombineSequence - - - - ToItalic - - - - ToBold - - - - EscapeSequence - - - - ResetSettings - - - - - diff --git a/module/PSADTree.psd1 b/module/PSADTree.psd1 index 7825a14..d3429d5 100644 --- a/module/PSADTree.psd1 +++ b/module/PSADTree.psd1 @@ -64,7 +64,7 @@ # ScriptsToProcess = @() # Type files (.ps1xml) to be loaded when importing this module - TypesToProcess = @('PSADTree.Types.ps1xml') + # TypesToProcess = @() # Format files (.ps1xml) to be loaded when importing this module FormatsToProcess = @('PSADTree.Formats.ps1xml') diff --git a/src/PSADTree/Style/TreeStyle.cs b/src/PSADTree/Style/TreeStyle.cs index 4c51338..5fee2dc 100644 --- a/src/PSADTree/Style/TreeStyle.cs +++ b/src/PSADTree/Style/TreeStyle.cs @@ -49,29 +49,29 @@ public RenderingStyle RenderingStyle internal TreeStyle() { } - public static string CombineSequence(string left, string right) + public string CombineSequence(string left, string right) { ThrowIfInvalidSequence(left); ThrowIfInvalidSequence(right); return $"{left.TrimEnd('m')};{right.Substring(2)}"; } - public static string ToItalic(string vt) + public string ToItalic(string vt) { ThrowIfInvalidSequence(vt); return $"{vt.TrimEnd('m')};3m"; } - public static string ToBold(string vt) + public string ToBold(string vt) { ThrowIfInvalidSequence(vt); return $"{vt.TrimEnd('m')};1m"; } - public static string EscapeSequence(string vt) => + public string EscapeSequence(string vt) => $"{vt}{vt.Replace("\x1B", "`e")}\x1B[0m"; - public static void ResetSettings() => + public void ResetSettings() => s_instance = new(); internal static string FormatType(object instance) From f989cedcad5ae07b3a6475ddd031ab2265c8dd31 Mon Sep 17 00:00:00 2001 From: AJ Raymond <19650958-poshAJ@users.noreply.gitlab.com> Date: Fri, 17 Apr 2026 16:36:01 +0000 Subject: [PATCH 11/17] fix misc compiler suggestions --- src/PSADTree/Extensions/MiscExtensions.cs | 3 ++- src/PSADTree/Extensions/TreeExtensions.cs | 2 +- .../Internal/_SecurityDescriptorInternals.cs | 26 +++++++------------ src/PSADTree/PSADTreeComparer.cs | 5 ++-- src/PSADTree/Style/PrincipalStyle.cs | 4 +-- src/PSADTree/Style/TreeStyle.cs | 4 +-- src/PSADTree/TreeGroup.cs | 4 +-- src/PSADTree/TreeObjectBase.cs | 4 +-- 8 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/PSADTree/Extensions/MiscExtensions.cs b/src/PSADTree/Extensions/MiscExtensions.cs index bfd8333..239d0ea 100644 --- a/src/PSADTree/Extensions/MiscExtensions.cs +++ b/src/PSADTree/Extensions/MiscExtensions.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.DirectoryServices; using System.DirectoryServices.AccountManagement; +using System.Globalization; using System.Linq; using PSADTree.Internal; @@ -69,7 +70,7 @@ internal static class MiscExtensions DirectoryEntry entry = principal.GetDirectoryEntry(); object? uac = entry.Properties["userAccountControl"]?.Value; if (uac is null) return null; - return (UserAccountControl)Convert.ToUInt32(uac); + return (UserAccountControl)Convert.ToUInt32(uac, CultureInfo.InvariantCulture); } } diff --git a/src/PSADTree/Extensions/TreeExtensions.cs b/src/PSADTree/Extensions/TreeExtensions.cs index f5a0535..28a05dd 100644 --- a/src/PSADTree/Extensions/TreeExtensions.cs +++ b/src/PSADTree/Extensions/TreeExtensions.cs @@ -108,7 +108,7 @@ internal TreeObjectBase[] Format() { TreeObjectBase current = tree[i]; - if ((index = current.Hierarchy.IndexOf(set.UpRight)) == -1) + if ((index = current.Hierarchy.IndexOf(set.UpRight, StringComparison.Ordinal)) == -1) { continue; } diff --git a/src/PSADTree/Internal/_SecurityDescriptorInternals.cs b/src/PSADTree/Internal/_SecurityDescriptorInternals.cs index 5b8393e..4a8ddea 100644 --- a/src/PSADTree/Internal/_SecurityDescriptorInternals.cs +++ b/src/PSADTree/Internal/_SecurityDescriptorInternals.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using System.DirectoryServices; +using System.Globalization; using System.Management.Automation; using System.Reflection; using System.Security.AccessControl; @@ -14,22 +15,13 @@ namespace PSADTree.Internal; [EditorBrowsable(EditorBrowsableState.Never)] public static class _SecurityDescriptorInternals { - private readonly static Type s_target = typeof(NTAccount); - private static readonly MethodInfo s_getOwner; - private static readonly MethodInfo s_getGroup; - private static readonly MethodInfo s_getSddlForm; - private static readonly MethodInfo s_getAccessRules; - private static readonly MethodInfo s_getAccessToString; - - static _SecurityDescriptorInternals() - { - Type type = typeof(_SecurityDescriptorInternals); - s_getOwner = GetMethod(type, nameof(GetOwner)); - s_getGroup = GetMethod(type, nameof(GetGroup)); - s_getSddlForm = GetMethod(type, nameof(GetSddlForm)); - s_getAccessRules = GetMethod(type, nameof(GetAccessRules)); - s_getAccessToString = GetMethod(type, nameof(GetAccessToString)); - } + private static readonly Type s_type = typeof(_SecurityDescriptorInternals); + private static readonly Type s_target = typeof(NTAccount); + private static readonly MethodInfo s_getOwner = GetMethod(s_type, nameof(GetOwner)); + private static readonly MethodInfo s_getGroup = GetMethod(s_type, nameof(GetGroup)); + private static readonly MethodInfo s_getSddlForm = GetMethod(s_type, nameof(GetSddlForm)); + private static readonly MethodInfo s_getAccessRules = GetMethod(s_type, nameof(GetAccessRules)); + private static readonly MethodInfo s_getAccessToString = GetMethod(s_type, nameof(GetAccessToString)); private static MethodInfo GetMethod(Type type, string name) => type.GetMethod(name) @@ -55,7 +47,7 @@ public static string GetAccessToString(PSObject target) { StringBuilder builder = new(); foreach (ActiveDirectoryAccessRule rule in GetAccessRules(target)) - builder.AppendLine($"{rule.IdentityReference} {rule.AccessControlType}"); + builder.AppendLine(CultureInfo.InvariantCulture, $"{rule.IdentityReference} {rule.AccessControlType}"); return builder.ToString(); } diff --git a/src/PSADTree/PSADTreeComparer.cs b/src/PSADTree/PSADTreeComparer.cs index 34a462d..d0a3820 100644 --- a/src/PSADTree/PSADTreeComparer.cs +++ b/src/PSADTree/PSADTreeComparer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.DirectoryServices.AccountManagement; @@ -12,6 +13,6 @@ internal sealed class PSADTreeComparer : IComparer public int Compare(Principal lhs, Principal rhs) => lhs.StructuralObjectClass == "group" && rhs.StructuralObjectClass == "group" - ? rhs.SamAccountName.CompareTo(lhs.SamAccountName) // Groups in descending order - : lhs.SamAccountName.CompareTo(rhs.SamAccountName); // Other in ascending order + ? string.Compare(rhs.SamAccountName, lhs.SamAccountName, StringComparison.Ordinal) // Groups in descending order + : string.Compare(lhs.SamAccountName, rhs.SamAccountName, StringComparison.Ordinal); // Other in ascending order } diff --git a/src/PSADTree/Style/PrincipalStyle.cs b/src/PSADTree/Style/PrincipalStyle.cs index c64a316..82aa80d 100644 --- a/src/PSADTree/Style/PrincipalStyle.cs +++ b/src/PSADTree/Style/PrincipalStyle.cs @@ -4,6 +4,8 @@ namespace PSADTree.Style; public sealed class PrincipalStyle { + private static TreeStyle TreeStyle { get => TreeStyle.Instance; } + public string Circular { get; @@ -34,8 +36,6 @@ public string User set => field = TreeStyle.ThrowIfInvalidSequence(value); } = string.Empty; - private TreeStyle TreeStyle { get => TreeStyle.Instance; } - internal PrincipalStyle() { } diff --git a/src/PSADTree/Style/TreeStyle.cs b/src/PSADTree/Style/TreeStyle.cs index 5fee2dc..5c625d4 100644 --- a/src/PSADTree/Style/TreeStyle.cs +++ b/src/PSADTree/Style/TreeStyle.cs @@ -69,7 +69,7 @@ public string ToBold(string vt) } public string EscapeSequence(string vt) => - $"{vt}{vt.Replace("\x1B", "`e")}\x1B[0m"; + $"{vt}{vt.Replace("\x1B", "`e", StringComparison.Ordinal)}\x1B[0m"; public void ResetSettings() => s_instance = new(); @@ -111,5 +111,5 @@ internal static string ThrowIfInvalidSequence(string vt) } private static string EscapeSequence(string vt, int padding) => - $"{vt}{vt.Replace("\x1B", "`e").PadRight(padding)}\x1B[0m"; + $"{vt}{vt.Replace("\x1B", "`e", StringComparison.Ordinal).PadRight(padding)}\x1B[0m"; } diff --git a/src/PSADTree/TreeGroup.cs b/src/PSADTree/TreeGroup.cs index 0f9ccdf..5f07bb4 100644 --- a/src/PSADTree/TreeGroup.cs +++ b/src/PSADTree/TreeGroup.cs @@ -7,14 +7,14 @@ namespace PSADTree; public sealed class TreeGroup : TreeObjectBase { + private static TreeStyle TreeStyle { get => TreeStyle.Instance; } + private List _children; public ReadOnlyCollection Children => new(_children); public bool IsCircular { get; private set; } - private TreeStyle TreeStyle { get => TreeStyle.Instance; } - private TreeGroup( TreeGroup group, TreeGroup parent, diff --git a/src/PSADTree/TreeObjectBase.cs b/src/PSADTree/TreeObjectBase.cs index 8a53669..c820a8b 100644 --- a/src/PSADTree/TreeObjectBase.cs +++ b/src/PSADTree/TreeObjectBase.cs @@ -9,6 +9,8 @@ namespace PSADTree; public abstract class TreeObjectBase { + private static TreeStyle TreeStyle { get => TreeStyle.Instance; } + public int Depth { get; } public TreeGroup? Parent { get; } @@ -37,8 +39,6 @@ public abstract class TreeObjectBase internal string Source { get; } - private TreeStyle TreeStyle { get => TreeStyle.Instance; } - protected TreeObjectBase( TreeObjectBase treeObject, TreeGroup? parent, From 5607124559963f8c192e74886f8d06c3cf38d1d0 Mon Sep 17 00:00:00 2001 From: AJ Raymond <19650958-poshAJ@users.noreply.gitlab.com> Date: Fri, 17 Apr 2026 17:45:39 +0000 Subject: [PATCH 12/17] fix compiler errors --- src/PSADTree/Extensions/TreeExtensions.cs | 23 +++++++++++-------- .../Internal/_SecurityDescriptorInternals.cs | 6 +++++ src/PSADTree/Nullable.cs | 2 +- src/PSADTree/Style/TreeStyle.cs | 21 +++++++++++++---- src/PSADTree/TreeCache.cs | 2 +- 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/PSADTree/Extensions/TreeExtensions.cs b/src/PSADTree/Extensions/TreeExtensions.cs index 28a05dd..90ad764 100644 --- a/src/PSADTree/Extensions/TreeExtensions.cs +++ b/src/PSADTree/Extensions/TreeExtensions.cs @@ -3,7 +3,7 @@ using System.DirectoryServices.AccountManagement; using System.Linq; using System.Management.Automation; -#if NETCOREAPP +#if NET8_0_OR_GREATER using System.Runtime.CompilerServices; #else using System.Text; @@ -15,7 +15,7 @@ namespace PSADTree.Extensions; internal static partial class TreeExtensions { -#if NETCOREAPP +#if NET8_0_OR_GREATER [GeneratedRegex("(?<=,)DC=.+$", RegexOptions.Compiled)] private static partial Regex GetDefaultNamingContextRegex(); @@ -24,23 +24,22 @@ internal static partial class TreeExtensions private static readonly Regex s_reDefaultNamingContext = new( "(?<=,)DC=.+$", RegexOptions.Compiled); + + [ThreadStatic] + private static StringBuilder? s_sb; #endif extension(string input) { internal string GetDefaultNamingContext() => s_reDefaultNamingContext.Match(input).Value; -#if !NETCOREAPP - [ThreadStatic] - private static StringBuilder? s_sb; -#endif internal string Indent(int indentation) { string corner = TreeStyle.Instance.RenderingSet.Corner; int repeatCount = (4 * indentation) - 4; int capacity = repeatCount + 4 + input.Length; -#if NETCOREAPP +#if NET8_0_OR_GREATER return string.Create( capacity, (repeatCount, corner, input), static (buffer, state) => @@ -62,7 +61,7 @@ internal string Indent(int indentation) #endif } -#if NETCOREAPP +#if NET8_0_OR_GREATER [SkipLocalsInit] private string ReplaceAt(int index, char newChar) => string.Create( @@ -108,7 +107,13 @@ internal TreeObjectBase[] Format() { TreeObjectBase current = tree[i]; - if ((index = current.Hierarchy.IndexOf(set.UpRight, StringComparison.Ordinal)) == -1) +#if NET8_0_OR_GREATER + index = current.Hierarchy.IndexOf(set.UpRight, StringComparison.Ordinal); +#else + index = current.Hierarchy.IndexOf(set.UpRight); +#endif + + if (index == -1) { continue; } diff --git a/src/PSADTree/Internal/_SecurityDescriptorInternals.cs b/src/PSADTree/Internal/_SecurityDescriptorInternals.cs index 4a8ddea..0c4834b 100644 --- a/src/PSADTree/Internal/_SecurityDescriptorInternals.cs +++ b/src/PSADTree/Internal/_SecurityDescriptorInternals.cs @@ -47,7 +47,13 @@ public static string GetAccessToString(PSObject target) { StringBuilder builder = new(); foreach (ActiveDirectoryAccessRule rule in GetAccessRules(target)) + { +#if NET8_0_OR_GREATER builder.AppendLine(CultureInfo.InvariantCulture, $"{rule.IdentityReference} {rule.AccessControlType}"); +#else + builder.AppendLine($"{rule.IdentityReference} {rule.AccessControlType}"); +#endif + } return builder.ToString(); } diff --git a/src/PSADTree/Nullable.cs b/src/PSADTree/Nullable.cs index ad74d3b..73c7eea 100644 --- a/src/PSADTree/Nullable.cs +++ b/src/PSADTree/Nullable.cs @@ -1,4 +1,4 @@ -#if !NETCOREAPP +#if !NET8_0_OR_GREATER namespace System.Diagnostics.CodeAnalysis; diff --git a/src/PSADTree/Style/TreeStyle.cs b/src/PSADTree/Style/TreeStyle.cs index 5c625d4..8121c44 100644 --- a/src/PSADTree/Style/TreeStyle.cs +++ b/src/PSADTree/Style/TreeStyle.cs @@ -68,9 +68,14 @@ public string ToBold(string vt) return $"{vt.TrimEnd('m')};1m"; } - public string EscapeSequence(string vt) => - $"{vt}{vt.Replace("\x1B", "`e", StringComparison.Ordinal)}\x1B[0m"; - + public string EscapeSequence(string vt) + { +#if NET8_0_OR_GREATER + return $"{vt}{vt.Replace("\x1B", "`e", StringComparison.Ordinal)}\x1B[0m"; +#else + return $"{vt}{vt.Replace("\x1B", "`e")}\x1B[0m"; +#endif + } public void ResetSettings() => s_instance = new(); @@ -110,6 +115,12 @@ internal static string ThrowIfInvalidSequence(string vt) return vt; } - private static string EscapeSequence(string vt, int padding) => - $"{vt}{vt.Replace("\x1B", "`e", StringComparison.Ordinal).PadRight(padding)}\x1B[0m"; + private static string EscapeSequence(string vt, int padding) + { +#if NET8_0_OR_GREATER + return $"{vt}{vt.Replace("\x1B", "`e", StringComparison.Ordinal).PadRight(padding)}\x1B[0m"; +#else + return $"{vt}{vt.Replace("\x1B", "`e").PadRight(padding)}\x1B[0m"; +#endif + } } diff --git a/src/PSADTree/TreeCache.cs b/src/PSADTree/TreeCache.cs index 185fd7d..0f8dc52 100644 --- a/src/PSADTree/TreeCache.cs +++ b/src/PSADTree/TreeCache.cs @@ -11,7 +11,7 @@ internal sealed class TreeCache internal bool TryAdd(TreeGroup group) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER return _cache.TryAdd(group.DistinguishedName, group); #else if (_cache.ContainsKey(group.DistinguishedName)) From 557e34e4720704f4e05df4fd34662cf5598a7cec Mon Sep 17 00:00:00 2001 From: AJ Raymond <19650958-poshAJ@users.noreply.gitlab.com> Date: Fri, 17 Apr 2026 18:52:47 +0000 Subject: [PATCH 13/17] add docs --- assets/EscapeSequence.png | Bin 0 -> 8294 bytes assets/Get-ADTreeGroupMember.After.png | Bin 0 -> 26630 bytes assets/Get-ADTreeGroupMember.Before.png | Bin 0 -> 23408 bytes assets/TreeStyle.png | Bin 0 -> 40006 bytes docs/en-US/Get-ADTreeStyle.md | 49 ++++++++++++ docs/en-US/about_TreeStyle.md | 102 ++++++++++++++++++++++++ 6 files changed, 151 insertions(+) create mode 100644 assets/EscapeSequence.png create mode 100644 assets/Get-ADTreeGroupMember.After.png create mode 100644 assets/Get-ADTreeGroupMember.Before.png create mode 100644 assets/TreeStyle.png create mode 100644 docs/en-US/Get-ADTreeStyle.md create mode 100644 docs/en-US/about_TreeStyle.md diff --git a/assets/EscapeSequence.png b/assets/EscapeSequence.png new file mode 100644 index 0000000000000000000000000000000000000000..13f96e8c65f05521b9fe3046fd2598753afe257b GIT binary patch literal 8294 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 literal 0 HcmV?d00001 diff --git a/assets/Get-ADTreeGroupMember.Before.png b/assets/Get-ADTreeGroupMember.Before.png new file mode 100644 index 0000000000000000000000000000000000000000..b17a24f787a6153aaa4bc7402a4bda1876d3fdb0 GIT binary patch literal 23408 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 literal 0 HcmV?d00001 diff --git a/assets/TreeStyle.png b/assets/TreeStyle.png new file mode 100644 index 0000000000000000000000000000000000000000..2a2da500dab40a5c426eee7dc22f082172eeb095 GIT binary patch literal 40006 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
4bqa_#on+~IZ2^*puB z@cIgaoY`K=88uM1rUQt9q1M%ktHw}|^74eLASL_rR{qAs4#)ai%e_wuC`Xv#mfmmS z=J4Oz#OR)yi)msJO?rkbnYv^CwV>Q1^(< z@0;Da;f~)OT_QcVW9YAT;KZ(5x^C%M&|aOF&%ma9onK78QCW9lBrONfqs8wSCL=+R zraidRTMKx+mN1BxfX74T(0YiP*%CIh7_Qu!m{PjPKKo0K|; zK4tUp%pTfOXxeq(!+Tl@z4*hJLffw!thC-WxEog7+$_Rj*Kw}xELx@5Iib}HoJnyJ zSpB4}g~dV%72cD>X10Xu-f%`HKz3#p>|;X4ZuvCZ}nsc z7UJ2AB766Ndr@WOv;4rvmu2PU;r`GlDT3VjJd-sl!g{AMKdA7Lt!)`hxPf8l?C53rM0KV9 zjTYw~*NY(?RqE$|vH*sK;DlFX?mMJ@7_*Fp`Ga@WXoKi`9nkeo#+!w_8h>iJze+h5 z6c*th_B+zlgxQqv5%i3v=j(5+_ev+y^6GOQ2WIp#5u(?0-O!e<0w~`(0UQG#dr{|S z6g^2U_$U=OY^Bb}Kh@LUzlU93UQ+7HgEE1Gi#rXN zm$P`GOffc)xN$;jfmaO536$?{4JHz9dmb-;MJ%YST^FL>G>H z9@^WW8!0~GFAQ~J;^W59>&@#5I~DE!YY|)0ako0qwYHvaccIAja2#}v8H~cSXc>Lp zG01AXQ$ctyRnZ)(DJbyLG}H33KipdEKG1nDXOvaiA!We#lAj24oxk#WCE$0% zvFa`Ryz-Yv>-o~!BS9sRXdQkmK#Xe^w9h-s-0}f0j*zlq?%e-|cmu}``UI@LpO(RuWoeGm#OBFE z;8A@;Xd06TeJeHuDmcNr5A*(&Vv|2RxQ$H``h-VxByJL)8FyI#Z6n@?Yiai|Dr#`6 z-g%_nkxAOfQ8=R9Sx^Ho8BBd_ZIdbJCnh2f(_-tYB*5Y_bS)X)%7<^6z_7<5b-dII z&a5b8#v*9VtZX}Lo!_2hKaR^D8rXj8aHcP?qtBnViJN9f@kIx%oLBG8t###s9xR9j z5td1_K<`Wt+LBRjRHh*!9|{KlKfQB+v4`;fmyd$hgzz)95)5PP>ZB1s=(d%i7tcXg z;YtI8qbNbVU+UJyVR{ga;k3`PZpJ#$@^6OfN*X`oY z5C;3eCO92AZ-iSZ4{nNV8u_y9aZ3Iza2Yq)61MwIC6enzhm4N9k|hCI;*YivLEQbF ziRnNl=MERekGw*gdGGVG_a?+Ps`HeKLR^sX2!Ai85^9Eq_!2qr5bOqA1duLx%Sd>5 z@R|4?^NMM4_W?)aTl#N&7=TuU{}nE3%(`J;Hy8k>+V;qdD>&4v1UvGZ%&1Qi=VD(8Y#hgt7PVuOZQ%Wez5E$ zF>;f{^4)t!E2G0A!+0p~s=lt-X5>Xyb2>Zvp-ZdcYieu{CNzh)U(2YeDh>=doS2E)$sHjld2XO?bmad~i z0DMg#NVK%H07ncsnlXTvb#0xS4WL(9u|pk52qf^~(3I}qi!^5?EK3zza1_~v%?bkWg=e*mM0nAgQ*wMq7L?LHAO8cI&&5JVw{RWN4S8cv`7%YDPvu`VF zPCkRg;)~l_bvG!Sn14O^0_*2+#7hKxKf0x*Mkm6NFH6TB8%~g^19xctz{Di4&j$ul z-F6W}*$R|c`4TM==gZr@e9StVv%xp^_EO^FFaVK;ZvjUJA{7IkCR6@tOnptaJcWS+V@)D4$5N*izqC)alABY5?piqV3`55s5v`-EuR zxI&a3ZT~(=i;Z;Eb0OeEPmsmOiTE~nzmL}(22zr5lB$W`0Cj-g6KFl*XoS@Kx^~aO zeoC(?jl>l~^u!n1ytzT9qQ-ecq8q`2D{E?MigFR;xi&}W!SM>< z+RI;lu(jq|o>6!ySqr;mk$~^ergIhJ8>7h~l@7Z)j)(d`TWZ-m$q#PLtGywxSwHD5 zew1m;$;*pdTG9v#=@-Oyo4t>fMgWnP-CY}{{E0EW{{DVQkgCY76umqkTRS*9%~)G2 zXno2^n*^}N%d_=s{Ua{dk2dQt!5z3q#^b`;zt5Oiy^1(>zN-05lzpArWBP`7lj!1L z3FqMILz3A*?cvfzH={|DVG-u~$EeJgh&x5!JHkS#egVDRBoWHF0X$e|A$C=a#KE8t zK?i=%{rx@VVR&Y{?{$wASK-&Mcq#PCA;6v(pa;d0TAi(8tfD%P{OsXctNzEcXe0ZO z>RenEwGOVXxe*rH`(H@;v6WTt0ZH+rW(qJJXZ2_Jl$?sUXLZ|zliz-tiS$xo^5up# z8`~y_;X{$i?k1mL$<`&gl|${da}}CniIS6LSIe{}-0PDa9M=-LYWcXy{?ynoUV8~$ z1~}n{BY}sN-ohZ$K#@>F31GHB7MtKbl91tRaZw4X)9zMSLKpZg?11l=V?lXdd5@xXj z2+66-DBa6L1W|Fjll4>#6}B0@Ahgk-nd>wPB{5o~-+b{bVBd~V^J z;k>m;Gy=0P@N|32OFb3n5~MN=rKt|jhJp<97TYnRSknjWkx|B^mU@~a^bTfz(;H?L zPFT+!IAQ9^|80$w41OXxX_*}IEec(690cODIj9Qi>M)H<&p>fW8HNXU2LX+MQHSl< z;oyOYrA*I_!NKaH-_D$2ink*`U(Ok0lyn^hq~p-MI>EX`gcV{bf)9%Xc% zC^r!!K08=#;$mmioacG1*ZHOhJ{AC$%xO^AdlR`I^+1CiiW*{-fPdfT=*%nm9@)!thz==opl4H7i`eL!dIk@jkjpto_Bhn_ zmytmPd8`~2h7vk(!1v)a*bt*Y=Y9gQH9y&q@?06XcYK6mzOZMHSwAGH*q(t}4{#B< zb%x*U)k#M|H+Lx!=loAnGUmToyJ1*U5tQay-2pvKYd95{86!XnXe#0$LYGt7{WT7i zlq4nOMx{;rD{tV{rWvJpv2Iv$E-JG7hUkTaNUGbfo&yP(+F0eNln}4>JqV|wQtgIr+Rsxwc{GqEX^j5C8-F^ zR0uR4cv1DayYzR!N%LL}uneG)Nl=Qw8V0G9ro=)ohYIh+yT^QP;!emCNq?N-y)4$l?xwAk#6PQKdQv;YAf zh!Hj?Q00rKqqNJZ6|KvD*i8ew85C|151f9W z3c6gcc*Z6G_692FPuD^PlvEm7*n+I?G_<1UJOJ+LXruH13qI z0|(DR!<%WMtC%VEbh_j=$B~p&#bt8{mp`LD+4RvAczgL#J5JE%-e_33TBi;kO;(1a<$QD)xdtvIxYaqkJK=`tiSHn3i z2ZpkHk~1@-#lW)OQG~H?Z@}`v%c@!ufIc9 zg@blgZ2JGzj4Su=)x|nGx5Ty+sE}WRm#8ra9oN6RKF#0{@7p@31lEv8D;bc01-kUb zAnT*-ovHHB%~yzAZWMKXg0T4s&IK))jcqV&t-k`Ny%WV0b~!L6YtXmpxTzGPEZ+65 z=tf|0zSXV*oQ{dUF)PO;=lo-{|DhUFqpqjA-JF>N#S-{yXeSxO+%^yhu&u%hRKUeh zzZywHV=icp)o1_SY$Q=>_sx#Rg>31n!L0`+I1R={ah|Vb{+n7}8@Cyot}E%8PMiuQ zP-D}2$m$zi_LWcjx}tV0Z2c<5<}|#W+~>c(!G*@e!2SY|45YsxIk7a!r1(J70F~xH zp>kz`c5mNu z`dq#Sq`SwW7T`PZe$pQcpmascHMgl}y%OvAc6sRr0i{Q-aDWca;I9pVVOxj-Q+m{r`3WlTU}k9 z%3kQ-WHwILM#6&qKwmU&EohY9y5EUMY9p3xIhQ9QiF|-k2&6a!4bBdo^n1I41&%Fp z>z&4j!HABs1GR)5;Ozb(r%}O@k;?tQC&|ll(=#$G7r(v%WhNvJ1fy~6&jD610>%z7 zVBGd5(k_S5@?YkveE;#o9LU}fewn6UrM678s;)Iz~8QS~%J<&*=^8O?m*^myQX&c7DnC*I^=(T5?Gq@Vf>l+N}A`YJSDPnW_ zpI%ETB&OlB;}X)4Fh#hsm@-d|8wd70_h#qBI}-(YNqJBRy{;B)YuyhtKrt_rYWZJd z-15IpK(rNb5}?pzj_(#wyaPqx(lHis*6x z+?st8{F`h~q}Ql$DQ{^nE70)RN$l8m_4=m%wUSr+S^y>mD+z!Z3?!XJpZcEYm%AV^ z)D`FfYV!l?BrsxusYk=k&Mw#d+Q}&+?$59CYHD_NRW&IuZ{V-BwY?%0_R*piB~|wc zWYg{O=cl%2G{P%v%Cro(_!!6^r6}7u5>8Z|_JZ&i`nO8|{Pw%0;FBL>%aRoZM4cqYz{zAL;lpMCDN<@Nqj$8Z$nqj3*Y)gwERG$HL3MCs`r~&I{rL{s36LA!;TgumX zhe6>!tD75m6F$5$bb`+Hm0{HMFZHm4L2@o?s#ca>Zx{NiIqOHVqnugd!Ck+!JpU{G)Qk)o07ckfAw&OKZ6`<}U8YZeb>)2piryJ3s9orv zokKQ#rwD=J%55{&1$y)J)D$GNo01fcx}OT*a3HgSIt=n^HF_){b$~!kI%-i{P+N-) zzVYJ43oj7gpvxx<%=w?mw7zT3V^eY`N#7Js(hl{vsP;Rfqjq0><}Xzf7S=P~Xd!|$ zSm^)q+UfB$BP3v)%-EHI?9yHM6UsN6GlForfnQ%xkM{OPLI#(6()9T>8x6AJDJxXiYbB|+YbxAvd1HM-~L2a6#~vJVyO6y zctv%!EXWV)+zW`NCvQ=N3&c2QVZw^P|~HPmqqcf`*eVQ0nRB< zo|%*XSya#KvpstdLiB@m{2#Q~H$5#4A#t$y#;D+I5h>=rwQ##0YY^_NuFz{IO&5Lv zr1=%g&Ms7MCPkn8-t&fb<|oDd86a^&zB|8;eCmivggd!2*3_IWEoUdyo$(B9Y62#8 zAFKJP#KEOX*Iktn>REI^K2i&dCMoT&w;3O_zvE`{N_1#g7gV912MB8`HLJZ@_{Q{X zY7EXoDjnTf0T=t_#iTtY*k5o(VhV|ap#X-UJ^cCeCnVbUf6e$px*Qr;=#M5TMggE; zW{yJ~0jLaQh_=ULu#kddbdv$XE=a^@f^gW35s4MXY5Z*!Rha*mZ_}Qm)2F=HoVl8A zV`IE=JPZ5F0`mWbp6>2)e}O8#?ko^O-_=2HNf&j!RAUYYPOxk&F5;R*&tNH;8;cGYuS%84CBK@b=-Pt)iQM#CnNzq|y*GLekUQU={*0?mi zyc~BhnSQEgf` z2{oBfnOW1`I0UVlUY?k1RU;6e01D(Yt&-@}3K7bFnpd3MS%!U+EI;5R$S>(-N@v5- zuQu~Nz{~)2RO@Cd5zQaS1dPngk}fW_Sd0v|U=%6n=nMbz|25+pmkG8S=}T8yIi2AL zm>?S21PBBK!lZFDu1;3a0F?yPPU!;=3V9;Kb~_sHg7jgF7w89U^pThp5yMt7A8t*K zR5lpib{JRBSspJo^t8_%xF>fhzAO}p9erpOM>hr8Z;8TM$S-@b++l~co^S0Ga;zk^ zu`u|AV}qj?RRsCQ&4$Ic%vhq>QGp2vet!k{n*@pXeIg#nhV^RujnEMw&j=&8A1J3N zG$A)nD4%6uJ~XrFX{W&g?lAuMz>RZ<8mPr$KZeCr9o+E>lEhdDC!xH5{uSA~pH;@O zRaIoqONLF_^r48D9daCjf0_AdYjH&puuTS-NQVC=}xq7X*{n z)~ZLfzxt`^>8Q-OUiyzhQ_IU0^Oex4yZ=d*2s;E@8b?P@itA^QbS%6gi4!(}L9J7w zpupw`d13%yAp?dN0HX%)cs_5LD4*UOamT{8cZ$f_MqJ}sIFT+gUS3*HgpzMuX4I#I z8Bn)({aeC2$m*1Q9X4qGb}Xb&#p*afy~3E*(%T^?jXI00_b#%_``RA0Lgl=XyM%UN zXdu2_w#?|VZCFR*v)jV_qlehLcLh%hgHO-)G=ykQqsjj(!4S@%SL;m*acuvi6ZD&P z^q+jfHjnxOE*OdFOJ_Bt9+B;0Alckth_unapn8m4`X`lOLD7IioR`9&+Ou^I-wz|! z{jdQtNjJw%fw9uD1o9Oytsck8Lm>KwGB=-b`y2vhK0?7V0>S_w7?{1;kQXqR`$3@~ z8Vjfcys&)iwPu+SsHR&$TpaYjUO_6JMy^apaEZ?Eq9HB^*nz-N{u96mAVxu#bNzo5 zROCA9H^8301$)}*pW)3>5?L-kgWnsQCsa|RNub{LzUHw2MBS<*f$Lr&9XuY5_tz1N z$UYB@bXL6rN+L++MFJi>vn?)HaEjQ?ZPthkbZHDAV*{`x0GN4ZjhFe!Llf0zFLe+NQ2zy6Pu15^u0^1lFS0t}%V?kvEmd%w^DsR!XAYHn`LA=E{XU-|fR zSFta$hqYYAr`hHAIKL&B?EsPX-U|Kt?G^0Vsi`1}r<=hG8%Wn-b!P&=vf|Tk9US_f z9#5Y(+pkx9L9>n`H1$*Oh!yko6@vQm-oLF?<#s|MbuqpVv!Gf3Lkz5O8=Ks{c`69} zbkw{F)IdTW7*qp0t{XS!TSTG(uei9l)?J&osnyMai44{XTr>|jb-=&l47m5A!rfV@ zw@m^*Dk!f&Edu-ijAu}CbK|Z(Jzlf>BJ?^o76U;A@V{i7ooO4O_)1K-1KI#=0g$ZU z$g<5#`l#V4Kq}yK`MG9%6uR3TGHC9ujwOLA{7E%GA={~g82mFORw{hXG>{DNC22!L zfV`fA3CNg(+0v~vU6Z6#Qm`kC99ZegfzIgz|uN|GwUB_t1wUF*-Do? z%^M-(!>qs)Dl)& zJ&}gPC5xxmzrQ#TC3%7y%T9YINCvXUtm@9tCw%{|6$b)%TLbaEU<1(SRL z}-qYNKW@IW|()*eEcGDj^Ya_R;TXo>Q;#XF*87 zzp5r6fX=yRd#L}dp;g({_~mTCJGD2R50Hezn_2|?DL36%^*zP!@Qja-zgd_JAfP40 zsMJBr9;bgJ_@Nm4mp|(=s?*Y#5`9CI8I!OK`@EN)xWz|*BhOwq^l#PgqqPACqF z=`6uOc35rq4d-)yB78en6|Pe*4R#i=@Brx0vT$SL!`5fBXr3phxE7;K_yU1wKYy>+ zpT?Eg z6~WAwQ9fDv2;>J)VTKc}!25t{oft43tPsN4>jK990qHLSb50=VB5q+(WQKBZazd~T z7&NEDA`fVmpd_u=Pm17@g}dyI5t9lyLvC+x1w2nkJ0AZY7Et`%hylvx?=PJ-H5`nY z$?U@U`T1+M@7C1Lx{KFgc|;#BHJhWt3>}*hAR(|Wi2%*~o3`B89!>#?pL8uZvWcUm z#%k~x?@l{K88a_*JOAR72i%xL^Z8z-Ca*B$Q zc6LmQi;K>nRf2^>J;8SV8LKM#fCqjC!?ifh3OE%I_^d@3rL)~uTqtZwNh&gk+2|m<`QlW<&aSNO1+C~!H|E*j!)h9SenOC32K(s- zs4nk884X3k9O(}iQ(2|s(m2p<#@Zwjgi}<%)h0q=uWLd$9fY*#W&7QhY4}yLm`JKr z@7JIE_6hqJOUVrD!HdJYfsacn=q|S!2UL-vYzfz^BYpdo7VVj<^-#>7!#}7$Q9y$Q zrMhd!6{QX|oFGOKOIi>FMK9-_zia{N67LjhFzm`_KOc0yH3)*$1(VuLZN2~*smzEZ zzX7ci^ix@HxA!~rq$;JFHBx7Oe-BEgI+g%&%y%Q>Pvz?Z1h&oLSS7CSE_Roj-GB&q z1C4~JLu>_r&lbAm)JX>9l*&pNit?pn83Th4R`N%n^}g#dWmkE=$^8nE60w*LXIhi^$e`#@%_C-1-nP3vi5wsIIFU3EjMqQi|aJ-AUO*rtQ8_Ug|W7}kWB1O%v{LsaR^L{YlI+I+e=W_5|IZX_{t>7 zD3D8Gkh&=JiDaV0LuOUd$nlIV7C;9S9e8I&DVly@7J=Ih#cQSc0nhQo{w~<&Q-L8& z&2udO9{&r@Kz5V!Ke{+plBw=aWDl#X>%nu)heR^63KKli}{q28M6i- zbdg9x)k_SK<4d+c{(wC+Hi=@*FXuH1qj*@_=n%8~PLk@slfB2+Fw4zjVk|aaHeGU= z=GK)>%NzqT`!N0tivD zLQn3wCylW%xo-J<-;MRDd7Ee9bK3_NQ^_hDrrmy|y|+<6n~fFduS_DXa}}ou+|X4S z>bQP~n&*GQcN%bOV6(d4m%w(s+NE9yd2s@ZfL@E$2%erRxIGNoGCC^0Yd`W`Btlwu6TSiZGV< zJbs9Vb;>>m+cX1#_mOQ(r@vS(Er{?z5|`$jhvV* z&wt8Cdz(nnUCV}vF`2`>5J-D=eIiJN<96QOw4^qxO?R4rdTQc@6kS&C{E=Txc6++- zsqYVGH$JWE75VAxUzg^3)+@>7BVG%WNiscRZl3TS$3H5OiVsh;1W|XbXq?q|rkoA@ zNT>f7dvEMioO2byeRPb-j|{H%4r2vIM~~vV-#uK&v7=0d+LL?A-_^%<`qnI;=7&%qqXv_APHu zPC%W`A`d0b45gWUvD6-)hC>M~%7byr>d5TeTF;qvIy|TgiXu859LFnngL$R(Q(ULs zf8~y}Tf9m=arIsg|K0FRAa0+AcBCE^<`|Ox@xmia3JE0ed?x5U`O(kW;YY6wYRueZ6tksSZY$^gR}}PNE2FO~pV*xv6pnuHpOS`$sdnd&svZt~^tCI6DbKqj8wyK? zgPEOd;4}NLlAvPdtCDVI*LN70v&KhEum>pWSuV{rG%rRlTVyrPQ8yga8~WR%2hzqIRlelI*)$oFuQj8UG(7ns~jz7dWGCz2k^ ztWE>9$_+E&KZ?o>4e#AE49-yQUEN~p&*$|**7)-B=qCPU!%q1IWa*39uDPEqUFz4| zZf~@@UM5Q!#ZevBtAox9rWiY)-<{~3IY#-t8y*#P8^EkiN*?jS^lH=lnOD^5>D;3+ zGCUw&j~3~)gB6HB*DYy8@Y#PrS$`e5Z&>+fd?J9Oi%iuqM%ad<670Z(aaKWyR7~~C z+}Yz?)7ka=B9D`e^tqXU0}>>p00BX9HQ{&01?$KvT6X3I+=rO)wJO$@9*1%*IxQtj z)|N#rCEuk=G+U-Whys7;0x)Q>YcP23kp=-yzoKPJ(awC+}ilj0LBFI)9l*_Wa`V zB1HLdrt;M6+&!VqSiAnP^h9JaNwa;ty4a3sE93yOr&q`lHDgNu_iK^AQcR#zOf=H& zDca$-Q~OlM<22z~@Qq^r(XNS@7~UYu`Uc0PMnRH>?O)|bs|`V}O=rR{P?!1=b(_EV zXCl(fZ;Ns{WyzIzkpEEEDef|;UNjlA_hx4)Js&WSj)?gsWsq^BHNvDT>_2%2aif(V zR#~C^nGiog`%#`onJF2*uot(0k45RyW6CE3LQ}mnUq5WGTYs2P=Zm>)oU+A$gk_l4 zr!RyF?|Z^?(3(7YK9${PC>|9z`h!p#4Mw}3!z$mPZ0+^O+l_2-39YTBui)2|_9Fv{ z_F23>LK67&a<)H2?BZPy$@d4gimW}jlKP>L9Lc$O zl!5Vc9GLsA6Dez~+SPN7YKt5T!o3GRL#O^&Y&k+HDigYvi}2>nmz$263kF6VF86KJTd-t4}lxaz}o)(K$iJ z9d<|Rhgnw|9G6y+Q>e+`l z*UFl#xEVZ)nqkkycTnnlWnOxfg9o*#TJxiZ`fL~vUJa}Y)s@imf&JhEL`l4bRqP;0fqIq z#K^JY!rpErQ&N7eYD;nW6n`AsnzO3kIN8Z`JdRf9+AUs$G74~6FS!nT8=j5sZUZj6 zjSb7CYzOTz&Bcd|f|xPUrx)>%bw0t+e&#bxby9GULyfIB`{kCz`VgA#Zk!{y^N_bc zv=&||s2;lMj%V)Wd@UXSWf7m2Qah4;kq;M|v-RSUw;J%cQ+)2-#hy;SsK`aBhxE@TgN&pcrx<+;QY>-%!8VX%u8d z%i;#1=wKy})b5*X0UQj*7I+J%vHywemulRnn z@Q{B2XAi&Cxll5UgjOiB$LwHDcZX)b48XAt!0N;;Hj6^yx=B+L3mTVolbuq1Y5dMs zjmLM=*pOt@wUVMb)%$nXF2hU0y+lP!-Fi3*-L;C%3pZ{W_r5(F|C6ghvTGt$FYhSU zyR+o_jZf-R9^SURQG%$M{iJ}DIN~_jg}hZ##N4mv=BW*Mc-XdUe0nW809=v|F`@#>&Ec{=e^O_I4JB&NCJl8~6-FaEv z<6z|Qwb*#P*RLtSYou02r@lsve-+v00*!d5jaTAM(L{nu&&?Fo@LHDo?j)E6@AvWC zN{;6oQs;hxlhOkDOaIQGu_gHk!}R-e_rgbKT3YQiEhc>PV8J@T^vWJj=n^pV70Jx*OZteBOklr#KNFn_wX7 z?Dw%m8Z*45#osxR-|fO%g4ieE(~B7C9jnWshkY0GM0H?kR)+}S!UFUO#?B(4MrU~T2*!R~yRBdnUaG?wh+jt_~yC*q|u#=l?q6Zv^& zkE=1N(8Xq)4UX}_FVAi(EpRN?s%NusMRFpIf5Lq=@Hk=7(Oa&4p}zv{5#uY)%2C*y zzMd&^6ya_GN&0j5VhUp;*?9_gk`pBc4+wSyL zbNTeo2K{|h#>;W!Z_`33D1tAcc(-0duF+i9xZC#^Wz~cn?QLcJH!H(IbeglLx{o>5 z2zp1*s~9F1`dX(==wh`YBI4sEdxZ63KJIqk0O@1pAhyR=BKhCV&rROYuaP*Af7A5x zs0EvQnM{v{LXT$3xi^d(a&?U{`Uz6dK6y$SR66`TZxjODS{+W_>)+f^o zu0{(s0~u;c5{Q4!_gN$d%GRo7Nsjf5#-iAj!5|$4#1gHbha$Z-%uh3o z)N8B;n}FI$71A;D2Kvc~O!+L0chY)Z_%xO}TQ)iOFe0(xYGw4g%sVb^%cOAGGp11{ zS)BWjbqhMZ&y2eEWE}ud=0EqO$u{<=Zy%vPbI)U~KPp?m5|QT+vNC@W)4WB(^(b zz+cF}>Gd&m${lYkxe5ziCo1dsxq$aellc^vcg6kF}Iws}U6$MHtr!o38lNi({vtiVa@ci?#@Cc5Hu-vb|cX5z%RGM(1WM z8_uq$*{WRQx3rYt-({HXlh>-AyDQJzHQX$_&cetu4gw7i@M-%4T{X}OZmX~! z(E3Gm(d10Ql4qN2XusJ&$YPndpDIA%^GHI2(UVbN#>x%Ae<2%xPUY2eXvA=UDi1paIsr$vEj`)&x97|hlx)KtsOCvBrRrbTVCk{r5&(ZY1UR30EC=pD{ zPHBnGAf9b;Yh%@c5-(j#&<-eRY0rn=r~q5n=0g zg=C)qSQKL|6h$4pMv-m;;6nmb|63b|TE1YGN^ zHm=dYf0SSKlfxM4*x~Jl#?GwNTGFK&LpJ)7d%A2NK7XXwKoU9Y*SLgxvdZfw&3v!J zHM{gL2S)eWB|Fwg(k-@d6F#k>1y|O@mUC!Ik@`-#f6DDYa>Zb72P9TQR=$oHQMg^^ z6xx%0p3{AlJiNUKqdrJ=fP3F~sqx)b!NTv!Jd?ici>GtOh_n&iZi^Z)etUrg|AHh0 z9=;wAkBN%h?j+gPVd^KH*8Ns;?6P~2MaoBtK&a0RifB{nc_K@Jb{k>Vl5EFmOxf^F zX2?@f0lXJf=i*MGHDYAu>&BmGyW|2$vqBtm88iCCqm}jtmrA~JTl_v11&_58uAG_3yE0*w` zOeCp)ExW*`&SS{hKQ?)3!aXA4D52{_ryb0P9J$Y_BMasBAwO$YcGa-3Q-x5Ni$Wj^ z;?vEha&4;6n|SS@riv&ZuNwAM27q_?Zrd1gP&M-XeGk6)zAqfPT;gxHQ68b*wa$c? zn2Ox@G_IaYORu%8$JMoE9*pD1%8gMFisC9v(me_I`=nBjHOy#|jLj;%i+nzp@qOIc!t&t;JYZuCKjt@q^3c3yw zdU@O>-f9Z+5R%(#t(pwuYT-8gN{8W^zo}QxFNfyT6uL$cVAsz{|^N16?l-&@gjfa1(= zp^wKFv%iX=TbtK?Wgp3H;O?f7ZV%-JjFiGT|8^v#A-DmJM_}GOFN(()`Q!fdShELv zzhZ7co)}CR8{U4~Maglj)L6|7Gqh+{A6Zz5DIQNr=*P%N|;)x-sx3{ls#q|mF{o@hyGVWei>gbP(PJ#c?;LjKbd zUR!(8Pcgw0Rq^*pL?cPSDwS&Rt+{7@xGfj*T@khWNF2VsRd-jA(-N zE(P(d`f^`B3)%iPl|>g&1Y%tW>R^^ZI!m)yoxKo zcO@x=eH6J#492$JCI6c$HrP5BGY-VPK%V~ku=IFvrBG@@eP3-eix z)0j)h-IfX~vSduZe3TFoVdXh)|AEYbxY0LYa0G{@CndLC6|aL_*Mx^xk(*oiUGj{f z>E*3-Y8cy7Kuc@bEj$0q6Hp{Dmn^t#`&?jpE~vW3Mec|J_V*PU#}=vjX=e=~!j+CR z9fRdj&ayB~wbB<>i}@R|N-SttW|^13)rs6mQ*3(7@&PueB#_*;qnS4~ip?q3Rx`33 zUWu(JeizxVyN|u4E7&ueJYQSs)423PhFo(mO?e!@+?cm|u6pdC|LU1AYg>JIPs5*Uay)r;Eu-k;`Oh0)gmGf2L%!;L$}89u85)j~ zI+qmzI}VPx;8-$cV+6U*WlC7}dRkRsEzXs@2$-GF#1Yjk9WgJU9t2PhYxz|PsGCUH zaqh|Y>97OGGnQEVdPt%AP(upZ6dBwb;JoJaZU_a-tTFOv^O`%7Ix*}szE(gkEv1&z z8}%~}c9x=rx9Ub7q#JFw%3MW71?qJXLOyqz!m#FMSR-K#j-BvHGThGPcW6G})TfQI z#{e8zec}C!EM?VxXQ$R6JL>?BdMnqVYiBBN7c%d<%89I}ucaSv=s^?$+g|{bE>@-U zmGCZxWW2+Y@5J%7oDKdJ2eMOfgu2y=Uvhe}O7b4cYbCG|N)Tg}r3KGHn){zxgxd-E ziInhK5p1UaXTjnpyB2G3>nr<3vlQ`>CG+6@bn!sp6l1}==Ep1>P?6XkITYB;u96AFW}>f0`h^O)?_(?tzsEq_4HcGwB&KuGYVTRr40(dqWY z!rt|9YxC*Pw5DVcF_>~-i0OeBmr+=TG!4Asx%CUYeYe@joWyiuaakPF4DI1P;FllF zbR?Q|Mw>$o1;kSa`Sn@?%==3;OE=3BhuX3Fl(*iG`*;Zo zu_d@w4pZp`1Kb^5Vy{0uTEj%a)?!X-WP zoQ0r3uR#@uj6fJik;*V3uk(89dNfGfLtsABh~Rvpx~0Of6(vkczy{%Qe$`PHZV)Z@ zmtZ`6ZSN<3RX3T38se$1CsvG0*m`|)W=at#{0#Zwy;>mDMWkhjmI^Q-qN zAp0-^X{8FC3sKzba*lZ=Pe?Z#E_Nv3(Hf^gMJ3X$el&q&+v`oSW5@l+ozDY9d2g4vkoIdD6ocpRW?e*5!_6X z&Dd?!=&pSq+&SSH!X7b#vAVm~m*uy{F8kSP^q(r@bGFgzYQ@(4$S+&2Kji} z#Xkfp6h)N`R1IcDa;tZV!yX>|mvXFI_ilWxb-yvZOJE+=Yz=M#>YKDe*T+sc&+XDF@VE^-HMP4DdgX=Er74(L>3-kU{T)j! zS!Czr#4CO!=ZEHpB$4~A?U^85n)a9?_R3R)&6nw$x{uc2@lIoHituYvTRvi>x+*ui?LrRSOI%?emw9>uL;h`$_ z-d}BLPfk}036r>;cuvr&ocoY^$*ClH>UzS4Nfk9->us=+v4sw~Io^Zxf2=dId1T(j znaN8dtq>Yv%do~>rw!9yRA#Mz$lb&Vt@hOM*|eFeH+zdk^GUexHw%h#osh|BQXkxv zPRD-PqKu*=)k=dDfk<8s#c|O}OJJwI{zGDnI+$jBR223ytzuj(G~m>~;W@xx(~z?oZ=AHmR}Xx6DnW>do|;+@E0%kYiGGUZy*zPfmXkO8j)fy-#NA94;bPi zRh6K`QiV#2hmU*CyXDN`2DfO&4Mj=_?}I%eT#Ye4)QvzcC%5w^NfBM-9;vw1jJDnAF;T2$j;gk-L)fL)D!Xok z&jtZ~JJ`E28aB!@mOnUG?3S7K`9zIms;JKdMb>b=G=;eLS)KMgJ^F!=M|zwx_VwOX ziczk33ir(DX*VBb1WLY2fiQ~+YtoN+1OdlGjQLU$I&N)yD3L4hlTX{@$=`yVGFN%Q zsF?Q`q~|XXI14z7u;XKX*V$+d4d9Jr)^ku%(x{7)zR`M-<>w?XI$vxZ*lsFx$B1gF z@(e*cS3xXeFkZO^{Z@goWReCarWYZd4M!%MclEKZ7W3Jd_Fq^@FVRTKGpjofyM_Wv z7Jaq+V$vwhU)%gsURiUc4siQ*qTW0Z`p6+C;jC7X(Us|>e%W|6&&Kridja9;>^7#X zB@o_tu6~Qnn=CJs%Pp|}N}7L1Uw>^{SX-Ie_N!^3lV#NBmjxBagKw~|@o9DiZG+xz zd8yG2vq-B4JjqDk#Q?MN%9#+&YM5xg04ptK*fw={mO@S#r2l$lg;c|VZinjAYP=Bb zDt-XHRKLNFLag6V>tQbDK(sR0na*8%oMMeh?z|w7MYif;E5MqvDrC;u(IhcteKP)n zp@>}NX4~0Hf@z0p0^E@tpFIoCZU_%F#iUdT&~k4N#Cg!4n&QuF2eHd$^cz$oo#fS9 zUNir>A53OVDr1lC)BTC1>GZ*m^JJi=VYP}x zRE-PZi!2dZnAhZ&p{}3AIxl@VwuM>QXfYI=psbH;M4tG*%vVr+HJUX>14+DIMn;QV z+Awix@SR58#CK{TR~Vm4GCHkKUcagJtrGdx;m zJaLOSTbt$A(}N0wkGlV(_A-svk`#CYX?uHAMgJn9>DH>`CWsp!&sGf<NK$sS!QR`RS6Q*1 z6IdwrTy7F=!uB*J%fQg49h&NxLoC8t!Nb>1+i`MWt=?@y7Z!63fn#NWYP#K<^@gcr zP={Ko(yoFK9IzlXC=|R7Vy%MuQWtQYXUQvNA@7O5G&Iv{$Om+0czy-`;CRnc)Acy7 zte_{H5_I`-pm}A)U7Ul4lBvS)xk1k!kMPnZmJ5lK!kta zXeDKhOG`_`xiZO0)`ADSPFzOn(`+bI1%-u@UjV0t(fL44Ir$eQGuChFoc#Q6fcIrx z_1X}qj{tz1bro?JMP#oK@#{ZqDE_Y#QS`fL=;^oQd~Pui(FlzIF#w#PEP(4wB6&{1 z5z1FvpSuMBl27wIm4E?|&DST*PfFE5>GKI!8b8<#Xf>iDmt+(afoCJ!k<-&E%U)O7 z)>l8KW@gTI^U^Jn0i_wJ>P8k9)1srJBY3s~{3_6~0XP1%Z$DjeLKFr4W-=tsk}?;# z=Dz6q_rx{7un;iP83C&SJZA3j(z^6E#&Zv%EkPD={`K$!xt<*ri@ zQCU`&KhP?(grJ+S;wAzO=~_2ojDI7eq1lL$&{rX~)&b|Al977mL-px^hX6m#rXzH;E^2^gSgHD8(_ zAuCG>2D-SYWv!|P_$n{}C>ch;{S!cS4J*`uk|J2Pz}`R$PQl0Bivd+92L}gN>E_oH zq=H&6j2Qq^MAqG%3s40LAL3|KE1h=7kFoBl(mj8d0VV+`6p-jj=7IiNEsYfb+&>Kh zkSOgIfb2E(hY#!L6LN^FV*IzC!Qnyk=>UxuSS^@EK%pwUi(t(-5CLxqxVS(Fevg5U z&KSsBtkDzzDs-*Q5-yVsP!Xq_&wT;5TXk)1VNX!y2SD=4$;@02FoXeYSd51|7{C=} zaXU4FVY6App0dP2B_qk_7&{-*^CF>g4`a}L?*Zi5Pi{N`vrE|r+}rUa_YU<2F$fU{ z2M1ID7&6vT;653Hiu@k1*nm(CuEa{I_+#=OpqWjoIPt&X15(TV6$!Qzc!Os@l9TbK zEN?QOYC(2zDZ(&ddL^=&M;Af?40{Uzvk~E8jNR9;?Qa4!^?5h1`#1a9Qdwgqbv z6O2kx98jFsXHEp0Juf#GA*LS)VtsNQiYJ*L5~$v&mUU`Vdk$aW8n*WH=g%O(r`x|M zoY&OU1PWFNFXV&(4-N=QC~p{iiY5n~P`>m?V2~4IV{cIPwCGhi45Jbfuv_IUEoq|3 zMV{k(x1|H*J-Nhhz;6PuYI16Pqp-K2S<(e0N-CeSYv5D>??_eqi*mg+AkAy}Krd3< zwh}$>PkIYa;QE2!^QJ>816u3+88YsC}3uWR? zOiw>uvz5TyxO#djt)(`Q>H8&+q3J7p1|j z0aFV2LsQo=#*Em2m5`g21sBE~IpiU)3 z2|QstP=>$Q%$9#zz&r`%OaRg@sDwQE))4?Q`~l7gtRPo-{i!efS%0Qf z8?e_z7kGBwis^c(W-L06@)ov&H~}OS`+>s@8QdhLp$P?gF-P?qs4q+;UdUpGIRmQ( zG7lEXWQEIeKS#Ga*X;g`kE7Gy(}L>;`MT{vv7;QGJJe}cIcEjc`P$V;tX4J?yiYHP zC6qy(_}~{TPf7arPcPY1W(EQNRyvI*F__SO$5FRM7F_V?Hx~*9tgH)m{MpvaQxnm}{_c$AdF!Vgw#$rkC~GoI%Hft6V+f{-E7|U$)CQ2?bu8g0z8MD4<=O$hilIU z0Y?y+4~Td#%65RjP%jADii%mt2AMc4N#EK@YX#MPFE1~TKalwiehvm&=~6Wk@hcEC zIm_9@%VXvV0pcgpBM=?{9D5M!?p_((F0-fY0@8tNFLGX(d=N>jC>B7aj7IH0cKr+Tw*EmZi_HpXxf zJJKZSTHc$|1eq`+hfbR&K)nR_VOBpidH;iNdHQ2a(B195phkC3v1@4Tci_LWTdvwVVgS?4W6yiT{{BND>!2t5eI@+odSJn95TCMEC|cmPITBf#_kfXfB=qI(AAQfd z2YG-b8K2g^_BlzY#eo4=4t=V_dz|n=Y_B|0o;ushlNR+k5h-x;6N(&fd^85Wj?UERp)NU^tjbA!f}9x8 zmB}0PXL_YXnMh~&bGA|EZdBRSF1~RafXLSz?C3q(-jyX&H*>~+m0CY`9AH}CZsF9t zXr9~7VY(PWO7eK~gr#AQpOsMOMbb@7-r64k{pPKk?3f5d%j)3-sROm22)0&7HasHAYeZvQ-3WL3*FGQjb;urG7{hw^D?L+I zt|v+VrhCUE)-1X6e2~7q{NCX2G@G^RBFogIKCjbr-4Qj<4R@E-u$od%mfa+NJTFy9 z={Jp1^$9cGM(lp8->ueYLAmd=azv`Vq2tF1W`w5X7W|Vk6VuM2TQrfKEvq|&1B8N15KlW+gY;G-E2b?oplR27vJCF(~Q4hy|RHVHP-wo z_&aq@Z(`%t6y?DJ?dMs~kNc`ViqckQ_EMkPfX6e>x9N}Xoo1b|04k zs3k$ZoDthcg)(=3qp%{Q_`B)&^Mi$}lw*6Xs@$idZloD{e`ni3 zg{25dc?~+oV!F1(SK}{XoEJU5T?&!0JH8#zNigLiZS8_hsWUQYB8L|WQm9+M$&a}V zwj}L&MfsNYWsj0kuj-ac_=V@BFR*Q6oo7evu^TT0tp*iflnyh@WB#ll{<;ek1^Wor ztmRN#scbWpG*={`{*CJv#WRRcn0KW^pYpf00b}gaCP|#COWy48L7m9}UYF{xCENa65EKctXgEJ$wJl?sv;+*07R87*{`E!RlwI+(;0w03)W2 z{?YEnE8^&G>6w(o`3&9s@7s}M$De7C;P(omng?0>bu*-;2t4XSg3vT2lJ@?dr@t%c zN&QAPgDh}{y1C7kaBvn!Y_OM#!Ev`a@xET=M*9U56%OZVogwxiLUb;t`E@>K){?-8 z$N&~VI6$}E7x(8GF)z}(q{_69=Mcl$kZr_q=`|w4KWc2b3`g&;NaRn3Z zV@e7zEBwpMv*9)Y8U&BN6{ev82Qp)WT8CqM&lR*^D(JIil^vP2*!-RbL+gmdm>YqV zsDSi%01w-?<;~5kJq2FnU;M-JH~mVAVh;SO=fH>lg6xp*Z~GwEJ?+?$?z7!Qg|8O^ zR`CLR+)cY2E+nNBd24vd5sqi5hd=TYBG}jOcNFL2>3e1ugK`QHW_Bm4J6r7zgB$N! z_K50OQ`0CJh?f80{e7pEEcEc5c6^I+teT?HooHW;#amVHk5zD8r(Cb;QuX0}=KeRl z1vQk0c#|J(q*532z_gXxROHs`9PDS@%oY|zE^j1r4YSXV94|aXY<1Hle|3m$h_nP2 zjc794d(&VHW(xBVb=6Lj{5j}!lcKzoQaF~WU2~U!a+1~D#2xgE*xU!ZGyYAmD;Dp) zDUtTPTU@hmbzol_WskBmWqZJ7XeFSpH!^~M_ia@W`;}9}z^*5BN?^xt_s(nw1?2~7 z_a%x0>q#Cpksfr4UA)ttr@A}rtds6qF{WGzf9f(q;}V}@!)`fw;9N#`t80h95`&6! z=bDz;b+^b|N>jLeeg9JNxmT@`Wj(6>w4Gxy& z!>C94G&C;KXSfY40>B@uy`%c@Nf&)T_2wT98<3 zio&Tj%jI}M@A4|~miIzI(J?y2&(e|OUW*Qd!nMQWqGw^ZSDNqdRZ92xvf^Aku#VVC zSw5u=kIlRm5jy($XFK8K$Z^c!>SMSpA_p>j$8Xp?ns#f2k(TB;Pw5k~#_I|Gz?%+*f5j_&##$&&CD%pHJyr;^|7P%_Fpq!J5OYQJ&2Mr~vqbugf>2+lL3ML{RQ z7Jwo5=Ep1G7BEa-{oSh7|88JOp`K0$Y=dtezE}^wC&4Q{r;}OY9s|EUo<}UiY)%PASgDy zI|E(dBVa+kd=tvP&k;-F83I?Cl@()Xc_l))xO zy}RTUXQO$_^SHcl-6OH?mi-Hfw}c}x$^@l8RgK6|SUW@FJtn4ia)pCPtLsDt%tMUQ z%ZxZQuACJvETG#XNnN3TnB^8Kft0s@oe^&j5~>Rl+llXM2~s_E@6toeD- zFWOfBlAAQtP5Tawehc!t(Y6KUPXPLkGKhmT8ya`#QRe~%JW+0{668O!>ohPg`Ux8; zXru4TV54eOa#ED0pShmB9S+85N}e^Bc?}x$wU-!Gke;&;z7|4}OP6=eA2-{P+`RtK zI+>f(H;D0{%v&ni#c9KrxKaiD(F*3!)>oNfT958aYb8XU`lsL3UY&L`PN6mr7QE)B z(s)JM6;xL2LY@Db0N~z5i`V+JUPJ|vIg$wDMu0|*IR`VAU^a#^eHUu#e|q2v@7{@3H;qdqDmG{zeKA={Ex zWAb@{3Hu)|zuOH~0b|3quE{kf`GB+o*T?c~L#C8Wz)zw%Jyj zr!Ys<&fjb{8h~yqLB8DIz>+;jx)*$-yA8u{BM*tmGECdIHF+A+ZZMMj#CI}!ZCBe* znBL4qZn8v-Z?GRv^KIS-ij^(0frS0-Mk?%4+I+{D4P4u7o)xSYGir76wWi1;q-E2M z-kqGlcIrLkYGv5^@X9S_2>pzOCpPRP$|i1hj``TICsbN}OR_E*p(QQ8gepCir*P&h zP2?sOLZkRI@!*U}Ct5r@8#diGcT`M*SRk5wZEBapg7G{VYA24K;|t^F3nNiEv<92z zlKrpsyP#UIo#iF^{p?w+H1={)HD+kbP*S#4EXxnGp02FIcieZ8ktFPM-Pf^e+`je? zT?ivdivnV!?K5m=l&p?(VYov*FzHIUqTCAHvQ#sxj zdPwO&0NcYIe03jD)$5+{7yrIRBkAu(X#oLk{}D+CxqbGacrwF78}8m0uELiH%;X_M z`a@i?t}R2ycQ^;^Cf`>^pQWQ^(JGoS{>h#Ayn0pDwXYnW{Q8AmPH(4GI$9I0V&2R| zN#v+c=(Xq6Z|dz)TWZy3?7G=$cVRikrti(~lX881=rVz^OdqK-!${>4PB?cD<&xnn zK5nskw^fKQV6wS1SHfS$5Xr3vmS9|iRR)Wl$XOalo_y-dW9#Q2uC3^DuRT>#pv;UU z%6T1o=kM7!PYpMi~_aB@Qr>-VB|3qKTB!&~Uc~iEt z>Zt)ss1c3ecc|pox@n6|-q67K5*6HYcjS9-{Ah=czdy)FuH2d6_J>SV7Od_E4fLxD zt`?(RnU8b)iNlBi>|)3d21f$@mGvIlkS6`5U4Nnub95|CY~&@5+qLa9$jSR96utXh zJlT%RxT#JvvTUlk(R=bg@d?fM9tHB; zFp_S14hD`_5cLe*5KLukU{)g`SfUpiK|h1P^d<8{F-3_O*q=YVwPl1y|8=F#Puu3(iuF^=RRP=O(cA&^v_=EF2Trzuxl~qB z28NhxFFwRMOx3HE2@Sh+~L>d@9)$@An5E1w5?w$8i zR||(l9_N}h9uba=Rl_*8yS<{zHD}YlGxH19>Ja!58l19~H@1y!cG+Vu>4u^j6Eq#D9|8&oLTt4poL}=5>-t zq->lpc0=-bv0oS=#`-)espm@Nb$%FJTy=JA8ti^$12}1AOkI?No))YIKaDHlU{Uuk z5{wh0^<9^!3|C!JiNhH0K3p6Wg-OrQ>+a-a$n=U_U3&K~I#fj1(p{9BC8eWX3SY5} zO1uIN8jp41%`o=YQw`qHHq)&U8uxtj@v03LviKAnq9Uq7jRU0`;dcfj>9fMg3tdoK zS+qKBC5JGS(Ea!Q7N)w|R*~CBJ4}_;f3Z!a%_vR7lF?92^KTUiBr~={Dk*CY*{0)7 z9e`^~YNW$Ams|~7jU9+DC>#_}Ry7*?>-UnuT8H53P#U>Tn^m?q`hZX}#`C}$j{{>& zwQx>OPJrF4mTczDuZ-Sy@KMBh8MSb(Tyl5y=%voq+U0c1lEL=d4Hf!P$BDmVj`f3G zI34EkQKdS0S*_`QebEA6=~E-7YL`c2-8#M+3K9$C=pUne%W!}vYU6lKto(B>t6$bg z9NpTTm%9&1Z?B!G=@PQPxkn3lSC_c7pjq0XZ+ zy6I-~F8k^wR}MNP+D{Rsu8EW-Wm^9|%*TDF1I9$`jT1YoukGFM2b&El0`iPaZary_ zZ{DA{BQ}qUQ~06iE6wWItcJ^xmg&i{dc7HU!5r|KDOCJj5Q1K(b+G2&(n3@X7hXV+ z6PQMl{*h}=Aubny$|t+zFRou@bGheRR0N~&Gko~`T{^OEL8(YxWvKM!BMx!L-bQA$ z;hK4@>$!Sx)bMarnNK-&MC)y-;Eiaj`$qlqm@bix#kI^~*>s(_rP(oqt<&Z;8dd^{ zh~10W3&j;Xrxzcw#*X)#$i>kwxU}n!Sy-3G#>2t!u&rO}q~=0Fn%gMfV+S9d}bIS{Ck%ackZBd{8h!XtxA8OzL=7^pX5 zAGML-SX1iiFEp=VqHYO2p)R!>+nlU8ggMBU*s8W4Q1-H^3|%z3h%=2I?gig}Gx7c; z6YMb#v9G2P*odj)*h9j3r-8BiFMjOHx2eVqF;f_NHY*!u4HuDhkQaQH+%|n*L(A(M zr3gf^=g-~8jS{nT+Z{$JQzH&mqi5yEjCJfjWbq_K?Tqi14^|c)SF>{TaQh*D%mS-I za>NUb2)rwEJUe~dH`&&Q8zvZF$XqH6b+?3Uudtlmdqn9b%)hFXWu<(=m)~A>RCpRX zQ}DOi@rxy;ta*JX7UeW>Eb7n#R>;(`nf;vQRmMRl2*64;vtS8(vd`&N(r$1N_b~c# zHYh>1F_QFle$fP&VPz{%)o9j-TboQwjY3@8<&r7~8E^JS&JuqzZBee~fx9`2Ns*SC zD_~O7M3J5lOZG9YKc$nhiyv!*lpV(~{+uk^(})-v@GpH-uwudGoY~*RBSib+?&X?} zTqQdL*;0W^1P8V8G0$d=6CHUZzt)agfk2WqWJbQQZ!95)25d~hIPs;#difYODtW}- z#aqOyh8;~ukXFs=vqmp$bFTY7b@i@#s0N(6D_NRp-|s>h@6FO(J$lEt29#bLeR?xM z`dW*W>#bsMZBgYb2{zsO_ET}iXUcTWAfOts-z4%^gs@PuPHeE3S5X>^f0AxSrb9}N z-_zeqSl0XE<8iQ0B-mspi?ab>=HAo#e=m|U=sd#3GB(q?z4pkmuuUMebgo8oUdx z^{P9*Nr^gZembRKu*75iA7pzX%3ADca7bj4&CJc7>-COL!F)i>RsJs0)ZxbDiQ)=e zA=}#DIplQZrJr6k=SuhDPlpksg}p^KnnT_Wz0-yB&p-aFEMlw!$?;-pz*&UMG-j0a z_geLDOf4~$<9vS00=3L%!(>=e!5noqb$*Dm54Sy!~w^*?8{NGwss0q?{n*u!+s} z-`#9(p&#y|M7dU&mSsY`xttHym>&1RGjWw8KSpEYQ>amui8{os@j0c|dh|UbNMGf> z{RNIm97mR3@APyC=k|=71qSZwj?f?5!ED=VjC2M&EJZJa!$1Y*dMr(rWG-u@Z|4#s%3uE-D;VX$}u`26!?eMsBNGjt~=@TaFiYU|< zYrA9SBHbliYPgbCxkcL*cD{ZI|6#lhD^}hKtf5vu$|Bv_{Me)f7ipyPxOp{m^k=Y7 z+N~P%AEuH0ZxQI;N1)>)^!8#5juLHmOqtSGpm)yFHiy|J{RuskL%KJ;3ri- z)^^9l1xZT)IObJO-_&aIH}P8Ys~mJ6U~}ik;r8bXUu3Fns%mV!dR|h#u&>eI!2Qzj z%-z2w|C8suPtrsqZ<#k}y8$>)zZ8In0{3x9DuaMh$FWFvHvdaHO*Ha);1v$aWo(gx z{}usw#`H494DT0qEcojGj3IFWrp%iO9Jxc=9TUcsWdKN?8f{mYr}{oHrVwcVAlzmz zeig$Y0ZBZ`Ol_vK+d+MEDig8?{yChC)TDMYQ=2du{p=}MPVm)g?NH8gnUGzF&7zz* z`&spP+B%(#O*3LL`pF%89KYW{f}?j;(V?n*y4~@)A)N>=gt!pOUX<9Uk!DPbn1Z-I z<0dXlT0)vB9e_`}HuKxsUqy@?%viH>g|OGPN81$^nl9<9)!)iwTOkAkl)H0&7=En) zul@h_?mk9}@($qmcV}jIZ)W%IcG85D4J+WQHmGjE`an-q)W-nT#l|&0);$v-= zw{eHGmZ+!jIh$|u>-;li^Cr?^o-Q3Kvs7xP(>mjtd)$17W_cN3kiNuW^B^x6zjxgO zkR(B~$Nmrg4(Tp-D7&2B2jH*9U--KG4FI0VKRTuNG82l~3psc*9&fB{3BuR79XWE( z<)wYP`_5 zhhu|BS*p5sl)OK(o{bIm8uFp!0sb{~loo9TKVSTeYu;oQ`L6}w#K`f|OA`t!VLCG% zuCb6D=1;wQ&?JqO+DeWM9z~Nhz8bQhCTFv~<{sWOjpb_k=6%-mp6Ae$uOn&hr(JKuM!by=+H>NsU<|&79K0FzhTpp8O-53$ z2f!G9r}R=8InaWyal8E*dYoL>)2{0tWgep+KL=6(DfmV7kmuP!^K3Rh5~nUW zOg85}L#RI)rZ-_1!-ney3Du5}&ZwOKXpw7PB^aiJq8R9Th3=k4l0)_Qy!N4VsY0Mo zl*y^|#G{=5Xc1o7U}4N%=aJ9L#OC#5m=dXsN?mXm!<2{*Oz!}}nI=dx$n zAK%A!8^6cJ(TlvFKE<73JJmnib(p8=p!|t`t$a8ARv%mR_4a^aXEHX(TUaHvU0$B3 zkffDlle~>J@>&*KOZdC-a_JP8d!+}6dKx%v9B_@FH_!31`DebvLwsyqq}@8n8b1HO z3nSG&?o+=vdLO3VOQwCDGv;gDj<@kab)2vCrhVb zWIECN_TpQ(12flcXG0>NZ*VqMsy{>1{PHLI#83!D(OJIu^0w?f@hH8C2*atMYk8s) z6gf{%JW6t?o@~xfBs5Su#VJAsP!t_aE8ohz#-Ae^PV#Yo44+pgpk_!8&91-#5nKp! zL6gsZ(m2%eM(HK$<*EOpYgTV8%Ro$Ib!{7)V&9^Bv}@eulDVeIV}H)$)k)Md_0M^# zR7qvO@e&KnYk1!(|H2zyUE?m7R1=T?Iy{BWj#3^uR66vDByBlnx;#iM zWu)3ky>|;9ZwznbKD@CF$ZB&%I$373+^Bu3>3DS#`5991k z1rV!i+t}6gB#C^So#!8Q&7aFonJ0P8bY}(t-ZlSII&?+Sq-kUU9p-6vns&kCW_cR` z7p(KHxvQi$9#?)q!iw{R{)p>ZwK7IdkWc)rba+Zj92Bw|}Mm1ifs zN}lH9g6SKr3l8&AIGZw#2qN zb0_LDnG5zAd)cAv;#uDw+Rguxw31L*zL?I|-karZM6kO}C=_8nEboLun`(!7n%B$& ztdLsSE&rGkp5rW*meA~3#xd)N6F@ws>}G}3N}4q1&2wxjWHtcWCs{_B%|?5k$A9fp zXv_CujJ$`b#{rNOZOU7F>wSCC`^pdSD&0h@X34JroYzo-t$6&`VWv-Gq}oyZOI-KQ zB@N*O-93$TURaE#W}JO0EK8o&*4{nwD0=?#SE+8uboVqup-olwX9(8~k;-U%cyR%> zTAFOmPbODGsCL8&Y(Bi$1cllRfOJ-MHsw}h=!>It7F`X#+yw@qP z45*nxa50EsNF;_rB!}veC6iF?h%1PZC5z#dhGE*-3PrIu)0!Wt&djJM7z|pagMu&{ z_)WdKALmT%cVmdIQPSC#2>g9x8q>0T4U%$Q0yh;Zdp zf3|c;n1l!}gsVy=4n7kmH{L;HLgmf%cG20Q8e2g>Z] +``` + +## DESCRIPTION + +The `Get-ADTreeStyle` cmdlet provides access to the `TreeStyle` instance that controls the rendering and customization of output for the `Get-ADTreeGroupMember` and `Get-ADTreePrincipalGroupMembership` cmdlets. + +For details, see [__about_TreeStyle__](./about_TreeStyle.md). + +## EXAMPLES + +### Example 1 + +```powershell +PS ..\PSADTree> $style = Get-ADTreeStyle +``` + +Stores the `TreeStyle` instance in the `$style` variable. + +## PARAMETERS + +### CommonParameters + +This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### None + +## OUTPUTS + +### TreeStyle diff --git a/docs/en-US/about_TreeStyle.md b/docs/en-US/about_TreeStyle.md new file mode 100644 index 0000000..ec50462 --- /dev/null +++ b/docs/en-US/about_TreeStyle.md @@ -0,0 +1,102 @@ +# about_TreeStyle + +## TOPIC + +Customizing PSADTree Output with TreeStyle. + +## SHORT DESCRIPTION + +The `TreeStyle` class enables customization of the hierarchical output for `Get-ADTreeGroupMember` and `Get-ADTreePrincipalGroupMembership` cmdlets in the PSADTree module. + +## LONG DESCRIPTION + +PSADTree version 1.3.0 and later introduces support for coloring the hierarchical output of the `Get-ADTreeGroupMember` and `Get-ADTreePrincipalGroupMembership` cmdlets using the `TreeStyle` class. This class provides a subset of features similar to those in PowerShell’s built-in [PSStyle][1]. +You can access the singleton instance of `TreeStyle` through either the [Get-ADTreeStyle][2] cmdlet or the `[PSADTree.Style.TreeStyle]::Instance` property: + +
+     + TreeStyle +
+ +The `TreeStyle` class offers methods for combining escape sequences and applying text accents, such as bold or italic. See the next section for additional details. + +Here are its members: + +```powershell + TypeName: PSADTree.Style.TreeStyle + +Name MemberType Definition +---- ---------- ---------- +CombineSequence Method string CombineSequence(string left, string right) +Equals Method bool Equals(System.Object obj) +EscapeSequence Method string EscapeSequence(string vt) +GetHashCode Method int GetHashCode() +GetType Method type GetType() +ResetSettings Method void ResetSettings() +ToBold Method string ToBold(string vt) +ToItalic Method string ToItalic(string vt) +ToString Method string ToString() +OutputRendering Property PSADTree.Style.OutputRendering OutputRendering {get;set;} +Palette Property PSADTree.Style.Palette Palette {get;} +Principal Property PSADTree.Style.PrincipalStyle Principal {get;} +RenderingStyle Property PSADTree.Style.RenderingStyle RenderingStyle {get;set;} +Reset Property string Reset {get;} +``` + +The `.EscapeSequence()` method reveals the escape sequence applied to generate specific colors or accents. For example: + +
+     + EscapeSequence +
+ +## CUSTOMIZING OUTPUT + +You can customize the output by modifying the properties of the `TreeStyle` class, much like you would with PowerShell’s `PSStyle`. This allows you to update colors for computers, groups, and users, as well as the circular and processed tags. + +Consider the standard output of `Get-ADTreeGroupMember`: + +
+     + Get-ADTreeGroupMember.Before +
+ +You can adjust the appearance by modifying the `PSADTree.Style.TreeStyle` object. Here’s an example of how to apply customizations: + +```powershell +$style = Get-ADTreeStyle +$palette = $style.Palette + +# Update users to white text on a red background +$style.Principal.User = $style.CombineSequence($palette.Foreground.White, $palette.Background.Red) + +# Change the rendering style to use ASCII +$style.RenderingStyle = 'Classic' +``` + +> [!TIP] +> +> - PowerShell 6 and later support the `` `e `` escape character for VT sequences. For __Windows PowerShell 5.1__, use `[char] 27` instead. For example, replace ``"`e[45m"`` with `"$([char] 27)[45m"`. See [about_Special_Characters][3] for more details. +> - The `TreeStyle` class provides methods like `.ToItalic()`, `.ToBold()`, and `.CombineSequence()` to apply text accents or combine VT sequences. +> - To reset the `TreeStyle` instance to its default state, use `.ResetSettings()`. If stored in a variable, reassign it afterward, e.g., `$style.ResetSettings()` followed by `$style = Get-ADTreeStyle`. + +After applying these changes, re-running the same `Get-ADTreeGroupMember` command will display the updated styles: + +
+     + Get-ADTreeGroupMember.After +
+ +## DISABLING ANSI OUTPUT + +Just like PowerShell’s `PSStyle`, you can disable ANSI rendering in PSADTree’s output by modifying the `.OutputRendering` property of the `TreeStyle` instance. Simply set it to `'PlainText'` using the following command: + +```powershell +(Get-ADTreeStyle).OutputRendering = 'PlainText' +``` + +This disables all ANSI-based coloring and formatting, resulting in plain text output for commands like `Get-ADTreeGroupMember` and `Get-ADTreePrincipalGroupMembership`. It’s a straightforward way to simplify the display when you don’t need the extra visual styling. + +[1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_ansi_terminals +[2]: ./Get-ADTreeStyle.md +[3]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_special_characters?view=powershell-7.4 From 33527b4a416e4ab89807c51ecb25cdfa4939cf8c Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 17 Apr 2026 16:15:17 -0300 Subject: [PATCH 14/17] bump module version --- module/PSADTree.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/PSADTree.psd1 b/module/PSADTree.psd1 index d3429d5..19ef06c 100644 --- a/module/PSADTree.psd1 +++ b/module/PSADTree.psd1 @@ -16,7 +16,7 @@ } # Version number of this module. - ModuleVersion = '1.2.0' + ModuleVersion = '1.3.0' # Supported PSEditions # CompatiblePSEditions = @() From 6753f4dc2154aa982a39e53014ea8b55d1d31130 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 17 Apr 2026 16:32:36 -0300 Subject: [PATCH 15/17] updates readme & get-adtreestyle doc --- README.md | 23 +++++++++++++++-------- docs/en-US/Get-ADTreeStyle.md | 18 ++++++++++++++++-- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 4252620..a14b903 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,25 @@ -PSADTree is a PowerShell module that brings `tree`-like visualization to Active Directory group structures — perfect for spotting nested membership and circular references at a glance. +PSADTree is a PowerShell module that brings intuitive `tree`-like visualization to Active Directory group structures. It helps administrators and security professionals quickly understand nested group memberships, identify effective permissions, and spot potential circular references at a glance. -This Module currently includes two cmdlets: +## Cmdlets -- [Get-ADTreeGroupMember](docs/en-US/Get-ADTreeGroupMember.md) for AD Group Members. -- [Get-ADTreePrincipalGroupMembership](docs/en-US/Get-ADTreePrincipalGroupMembership.md) for AD Principal Group Membership. +- **`Get-ADTreeGroupMember`** +Displays the members of an Active Directory group in a clear hierarchical tree view. It recursively shows nested groups, users, computers, and other principals, making it easy to visualize complex group nesting. -__Both cmdlets help with discovery of Circular Nested Groups.__ +- **`Get-ADTreePrincipalGroupMembership`** +Shows all groups that a given Active Directory principal (user, computer, group, etc.) belongs to, presented in a tree structure. This reverse view is especially useful for understanding effective membership and troubleshooting access issues. + +- **`Get-ADTreeStyle`** +Retrieves the singleton `TreeStyle` instance used to customize the colored, hierarchical output of `Get-ADTreeGroupMember` and `Get-ADTreePrincipalGroupMembership`. +Allows you to change colors for groups, users, computers, other principals, and apply accents. You can also control ANSI output rendering. ## Documentation -Check out [__the docs__](./docs/en-US/PSADTree.md) for information about how to use this Module. +- Learn how to use the cmdlets in the [official documentation](./docs/en-US/). + +- To Customize output rendering, see [about_TreeStyle](./docs/en-US/about_TreeStyle.md). ## Installation @@ -170,7 +177,7 @@ mail john.doe@mylab.com >[!TIP] > -> - `-Properties *` retrieves __all__ available attributes from each object. +> - `-Properties *` retrieves **all** available attributes from each object. > - Use friendly names (e.g. `Country` → `c`, `City` → `l`, `PasswordLastSet` → `pwdLastSet`) or raw LDAP names — the key in `.AdditionalProperties` matches what you requested. > - See the full list of supported friendly names in the [source code `LdapMap.cs`](https://github.com/santisq/PSADTree/tree/main/src/PSADTree/LdapMap.cs) @@ -274,4 +281,4 @@ ChildDomain group └── Users ## Contributing -Contributions are more than welcome, if you wish to contribute, fork this repository and submit a pull request with the changes. +Contributions are welcome, if you wish to contribute, fork this repository and submit a pull request with the changes. diff --git a/docs/en-US/Get-ADTreeStyle.md b/docs/en-US/Get-ADTreeStyle.md index 4340d73..dab84b5 100644 --- a/docs/en-US/Get-ADTreeStyle.md +++ b/docs/en-US/Get-ADTreeStyle.md @@ -20,9 +20,10 @@ Get-ADTreeStyle ## DESCRIPTION -The `Get-ADTreeStyle` cmdlet provides access to the `TreeStyle` instance that controls the rendering and customization of output for the `Get-ADTreeGroupMember` and `Get-ADTreePrincipalGroupMembership` cmdlets. +The `Get-ADTreeStyle` cmdlet provides access to the `TreeStyle` instance that controls the rendering and customization +of output for the `Get-ADTreeGroupMember` and `Get-ADTreePrincipalGroupMembership` cmdlets. -For details, see [__about_TreeStyle__](./about_TreeStyle.md). +To Customize output rendering, see [about_TreeStyle](about_TreeStyle.md). ## EXAMPLES @@ -47,3 +48,16 @@ This cmdlet supports the common parameters. For more information, see [about_Com ## OUTPUTS ### TreeStyle + +## NOTES + +Modifying the properties of this object (such as colors for groups, users, computers, etc.) will immediately affect the +visual output of `Get-ADTreeGroupMember` and `Get-ADTreePrincipalGroupMembership` in the current PowerShell session. + +## RELATED LINKS + +[__`Get-ADTreeGroupMember`__](Get-ADTreeGroupMember.md) + +[__`Get-ADTreePrincipalGroupMembership`__](Get-ADTreePrincipalGroupMembership.md) + +[__about_TreeStyle__](about_TreeStyle.md) From f4633ba8a9d8e0cfc5a4166a7b4c556019eaa75d Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 17 Apr 2026 16:53:06 -0300 Subject: [PATCH 16/17] add GeneratedRegex for net8 --- src/PSADTree/Internal/_FormattingInternals.cs | 8 +++++++- src/PSADTree/Style/TreeStyle.cs | 13 ++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/PSADTree/Internal/_FormattingInternals.cs b/src/PSADTree/Internal/_FormattingInternals.cs index 9a11246..793c8cf 100644 --- a/src/PSADTree/Internal/_FormattingInternals.cs +++ b/src/PSADTree/Internal/_FormattingInternals.cs @@ -7,11 +7,17 @@ namespace PSADTree.Internal; #pragma warning disable IDE1006 [EditorBrowsable(EditorBrowsableState.Never)] -public static class _FormattingInternals +public static partial class _FormattingInternals { +#if NET8_0_OR_GREATER + [GeneratedRegex(@"^DC=|(? treeObject.Source; diff --git a/src/PSADTree/Style/TreeStyle.cs b/src/PSADTree/Style/TreeStyle.cs index 8121c44..215c1ab 100644 --- a/src/PSADTree/Style/TreeStyle.cs +++ b/src/PSADTree/Style/TreeStyle.cs @@ -6,13 +6,20 @@ namespace PSADTree.Style; -public sealed class TreeStyle +public sealed partial class TreeStyle { private static TreeStyle? s_instance; +#if NET8_0_OR_GREATER + [GeneratedRegex(@"^\x1B\[(?:[0-9]+;?){1,}m$", RegexOptions.Compiled)] + private static partial Regex ValidateRegex(); + + private static readonly Regex s_validate = ValidateRegex(); +#else private static readonly Regex s_validate = new( @"^\x1B\[(?:[0-9]+;?){1,}m$", RegexOptions.Compiled); +#endif public static TreeStyle Instance { get => s_instance ??= new(); } @@ -76,8 +83,8 @@ public string EscapeSequence(string vt) return $"{vt}{vt.Replace("\x1B", "`e")}\x1B[0m"; #endif } - public void ResetSettings() => - s_instance = new(); + + public void ResetSettings() => s_instance = new(); internal static string FormatType(object instance) { From 70c02bf49a6b397fa53c55911579424411505e3a Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Fri, 17 Apr 2026 16:56:46 -0300 Subject: [PATCH 17/17] indent ctor args for renderingset --- src/PSADTree/Style/RenderingSet.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/PSADTree/Style/RenderingSet.cs b/src/PSADTree/Style/RenderingSet.cs index d64dc83..0ed90a3 100644 --- a/src/PSADTree/Style/RenderingSet.cs +++ b/src/PSADTree/Style/RenderingSet.cs @@ -20,7 +20,12 @@ internal readonly struct RenderingSet internal string Arrows { get; } - private RenderingSet(string corner, char upRight, char vertical, char verticalRight, string arrows) + private RenderingSet( + string corner, + char upRight, + char vertical, + char verticalRight, + string arrows) { Corner = corner; UpRight = upRight;