using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Text; using PowershellModule.Calendar; namespace PowershellHarness; // https://github.com/FuseCP/FuseCP/blob/278a19dc06949600f25a1b4ed74d0419a8fa3fc2/FuseCP/Sources/FuseCP.Providers.HostedSolution.SfB2015/SfBBase.cs#L224 // some useful code in here tbh class Program { static void Main(string[] args) { // I've gotta work out how to get the rider terminal to act closer to powershell because .PadLeft in powershell // will correctly output lines when output with WriteOutput, but the Jetbrains debug worker (or whatever // it is that rider spawns), has none of that and operates in its own world. // Which is fine if you never want new lines. // Ideally you'd just launch pwsh.exe and attach to that, but _that_ has its own issues as well. // The new line issue might be related to how I've set up the host seeing as I just did a bunch of copy paste shit // from something I found on github to get it across the line so I could debug it. // I'll eventually revisit those hosts and rewrite them properly once I start adding more commands, and if I care. // Threads & variables has all the visuals I need after all. #if DEBUG if (!System.Diagnostics.Debugger.IsAttached) { Console.BackgroundColor = ConsoleColor.Yellow; Console.WriteLine(CenterText("Running without a debugger attached may result in lines not being output correctly")); Console.ResetColor(); } #endif Console.WriteLine(CenterText(" PowerShell Debug Harness ")); Console.WriteLine($"Reported console window size: {Console.WindowWidth}x{Console.WindowHeight}"); Console.WriteLine($"Reported console buffer size: {Console.BufferWidth}x{Console.BufferHeight}"); Console.WriteLine(CenterText(" WIDTH ", '-')); var host = new CustomHost(Console.WindowWidth); var runspace = InitialisePowershellHost(host); // InvokeCommand(runspace, GetCalendarCommand.FullName); foreach (var day in Enum.GetValues()) { InvokeCommand(runspace, GetCalendarCommand.FullName, [ CreateCommand(nameof(GetCalendarCommand.MarkedDaySymbol), "faker"), CreateCommand(nameof(GetCalendarCommand.StartOfWeek), day.ToString()), CreateCommand(nameof(GetCalendarCommand.Date), "22/6/26"), // CreateCommand(nameof(GetCalendarCommand.AlignRight)) ]); } // InvokeCommand(runspace, GetCalendarCommand.FullName, [ // new CommandParameter(nameof(GetCalendarCommand.MarkedDaySymbol), "🤫"), // new CommandParameter(nameof(GetCalendarCommand.StartOfWeek), DayOfWeek.Saturday) // ]); // InvokeCommand(runspace, GetCalendarCommand.FullName, [new CommandParameter(nameof(GetCalendarCommand.StartOfWeek), "Sunday")]); } private static CommandParameter CreateCommand(string name, string? argument = null) { if (argument is not null) { return new CommandParameter(name, argument); } return new CommandParameter(name); } static string CenterText(string text, char paddingChar = ' ') { return text.PadLeft((Console.WindowWidth + text.Length) / 2, paddingChar).PadRight(Console.WindowWidth, paddingChar); } /// /// Initialises a PowerShell session and returns an open Runspace /// /// /// static Runspace InitialisePowershellHost(CustomHost host) { // Create the initial session state for the host. Yes, that 2 on the end does indicate that the underlying // implementation for this is C++ // Welcome to the lands of fuck all documentation and figuring shit out from an increasingly shit internet // where finding non-slop answers gets harder by the day as people close sources of information to prevent // scraping. Fuck every single person involved modern AI. // It wasn't much better finding documentation about the System.Management.* namespace before that, but it's // definitely a lot worse because of it var initialSessionState = InitialSessionState.CreateDefault2(); // No idea what the helpFileName should be. As is common with _a lot_ of the System.Management.* namespace, // fuck all is actually documented with comments, or documented at all! // From https://learn.microsoft.com/en-us/powershell/scripting/developer/hosting/creating-a-constrained-runspace?view=powershell-7.6 // (which yes, this section _is_ under a legacy category!), it's perfectly fine to leave it as null. // We also don't really care as we're just doing this so we can test commands without having to deal with Rider // and it's quirks around runnning a powershell terminal and attaching to it. Yeah it technically works, but // it's way too common to end up with Rider refusing to build correctly or ensure the right dll is used for the module var getCalendarCommand = new SessionStateCmdletEntry(GetCalendarCommand.FullName, typeof(GetCalendarCommand), null); initialSessionState.Commands.Add(getCalendarCommand); // Create a runspace from the state, open and return it var runspace = RunspaceFactory.CreateRunspace(host, initialSessionState); // A runspace is technically disposable, but we're not reimplementing a full host and we won't be doing anything // that would require a fresh runspace multiple times over (yet), so we can just treat the lifetime of the // disposable as application lifetime runspace.Open(); return runspace; } static void InvokeCommand(Runspace runspace, string command, IEnumerable? parameters = null) { // Not too sure on the difference of runspace vs PowerShell here. Doesn't seem to make a difference either way // and this is just a debug harness so it doesn't really matter for now using var pipeline = runspace.CreatePipeline(); // using var powershell = PowerShell.Create(runspace); var cmd = new Command(command); if (parameters is not null) { foreach (var commandParameter in parameters) { cmd.Parameters.Add(commandParameter); } } pipeline.Commands.Add(cmd); // powershell.Commands.AddCommand(cmd); try { var results = pipeline.Invoke(); // var results = powershell.Invoke(); foreach (var result in results) { Console.Write(result); } } catch (Exception ex) { Console.Error.WriteLine($"Unable to execute command {command}"); Console.Error.WriteLine(ex.GetBaseException().Message); } } }