From 5bd57561dba2eaf9703efca51772d97a57240c83 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 4 Oct 2026 12:44:56 +0200 Subject: [PATCH 1/2] ExecutingPowerShellScript: retarget to .NET 10 and fix the samples - Both projects net7.0 to net10.0; Microsoft.PowerShell.SDK 7.3.3 to 7.6.6 (7.6.x targets net10.0 only); test packages bumped, xunit kept. - ProcessStart: start pwsh instead of powershell.exe, build arguments with ArgumentList, set UseShellExecute = false, read stdout and stderr asynchronously and wait for exit, return exit code, output and error. - PowerShellClass.ExecuteScript: AddCommand instead of AddScript (a path with a space fails under AddScript) and a Bypass execution policy through InitialSessionState, since a hosted runspace otherwise inherits Restricted. - ExecuteCommand: FirstOrDefault and a HadErrors check instead of First(). - PSCustomRunspace implements IDisposable and disposes its runspace. - echo.ps1 added to the project; no more hardcoded desktop path. - Program.cs uses top-level statements. --- .../ExecutingPowerShellScript.csproj | 10 ++- .../PSCustomRunspace.cs | 84 ++++++++++--------- .../PowerShellClass.cs | 67 +++++++-------- .../PowerShellResult.cs | 3 + .../ExecutingPowerShellScript/ProcessStart.cs | 74 +++++++++------- .../ExecutingPowerShellScript/Program.cs | 44 ++++------ .../ExecutingPowerShellScript/echo.ps1 | 1 + .../Tests/PSCustomRunspaceLiveTest.cs | 37 ++++---- .../Tests/PowerShellClassLiveTest.cs | 54 +++++++----- .../Tests/ProcessStartLiveTest.cs | 58 ++++++++----- .../Tests/Tests.csproj | 10 +-- 11 files changed, 239 insertions(+), 203 deletions(-) create mode 100644 dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PowerShellResult.cs create mode 100644 dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/echo.ps1 diff --git a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/ExecutingPowerShellScript.csproj b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/ExecutingPowerShellScript.csproj index 05f8c26c9d..e45a0046e6 100644 --- a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/ExecutingPowerShellScript.csproj +++ b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/ExecutingPowerShellScript.csproj @@ -2,13 +2,19 @@ Exe - net7.0 + net10.0 enable enable - + + + + + + PreserveNewest + diff --git a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PSCustomRunspace.cs b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PSCustomRunspace.cs index ed96f71ba5..fc43579d2a 100644 --- a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PSCustomRunspace.cs +++ b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PSCustomRunspace.cs @@ -1,56 +1,64 @@ -using Microsoft.PowerShell.Commands; +using Microsoft.PowerShell.Commands; using System.Management.Automation; using System.Management.Automation.Runspaces; -namespace ExecutingPowerShellScript +namespace ExecutingPowerShellScript; + +public class PSCustomRunspace : IDisposable { - public class PSCustomRunspace + private readonly Runspace _rs; + + public PSCustomRunspace() + { + InitialSessionState iss = InitialSessionState.Create(); + var entry = new SessionStateVariableEntry( + "AllowedCommands", new[] { "Get-Date" }, "List of allowed commands"); + iss.Variables.Add(entry); + + var ms = new ModuleSpecification("Microsoft.PowerShell.Utility"); + iss.ImportPSModule(new[] { ms }); + var getDateCmdlet = new SessionStateCmdletEntry("Get-Date", + typeof(GetDateCommand), ""); + iss.Commands.Add(getDateCmdlet); + + _rs = RunspaceFactory.CreateRunspace(iss); + _rs.Open(); + } + + public string ExecuteCommand(string command) { - private readonly Runspace? _rs; + using var ps = PowerShell.Create(); + ps.Runspace = _rs; + ps.AddCommand(command); + var results = ps.Invoke(); - public PSCustomRunspace() + if (ps.HadErrors) { - InitialSessionState iss = InitialSessionState.Create(); - var entry = new SessionStateVariableEntry( - "AllowedCommands", new[] { "Get-Date" }, "List of allowed commands"); - iss.Variables.Add(entry); - - var ms = new ModuleSpecification("Microsoft.PowerShell.Utility"); - iss.ImportPSModule(new[] { ms }); - var getDateCmdlet = new SessionStateCmdletEntry("Get-Date", - typeof(GetDateCommand), ""); - iss.Commands.Add(getDateCmdlet); - _rs = RunspaceFactory.CreateRunspace(iss); - _rs.Open(); + throw new InvalidOperationException(ps.Streams.Error[0].ToString()); } - public string ExecuteCommand(string command) + return results.FirstOrDefault()?.ToString() ?? string.Empty; + } + + public bool StartProcess(string processName) + { + try { using var ps = PowerShell.Create(); ps.Runspace = _rs; - ps.AddCommand(command); - var processes = ps.Invoke(); + ps.AddCommand("Start-Process").AddArgument(processName); + ps.Invoke(); - return processes.First().ToString(); + return true; } - - public bool StartProcess(string processName) + catch (Exception) { - try - { - using (var ps = PowerShell.Create()) - { - ps.Runspace = _rs; - ps.AddCommand("Start-Process").AddArgument(processName); - ps.Invoke(); - - return true; - } - } - catch (Exception) - { - return false; - } + return false; } } + + public void Dispose() + { + _rs.Dispose(); + } } diff --git a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PowerShellClass.cs b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PowerShellClass.cs index 2fe026c547..93b035b053 100644 --- a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PowerShellClass.cs +++ b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PowerShellClass.cs @@ -1,45 +1,42 @@ -using System.Management.Automation; +using Microsoft.PowerShell; +using System.Management.Automation; +using System.Management.Automation.Runspaces; -namespace ExecutingPowerShellScript +namespace ExecutingPowerShellScript; + +public class PowerShellClass { - public class PowerShellClass + public bool ExecuteScript(string pathToScript) { - public bool ExecuteScript(string pathToScript) - { - using var ps = PowerShell.Create(); - ps.AddScript(pathToScript).Invoke(); - if (ps.HadErrors) - { - return false; - } - else - { - return true; - } - } + var iss = InitialSessionState.CreateDefault(); + iss.ExecutionPolicy = ExecutionPolicy.Bypass; - public string ExecuteCommand(string command) - { - using var ps = PowerShell.Create(); - ps.AddCommand(command); - var processes = ps.Invoke(); + using var ps = PowerShell.Create(iss); + ps.AddCommand(pathToScript).Invoke(); - return processes.First().ToString(); - } + return !ps.HadErrors; + } - public bool StartProcess(string processName) + public string ExecuteCommand(string command) + { + using var ps = PowerShell.Create(); + ps.AddCommand(command); + var results = ps.Invoke(); + + if (ps.HadErrors) { - using var ps = PowerShell.Create(); - ps.AddCommand("Start-Process").AddArgument(processName); - ps.Invoke(); - if (ps.HadErrors) - { - return false; - } - else - { - return true; - } + throw new InvalidOperationException(ps.Streams.Error[0].ToString()); } + + return results.FirstOrDefault()?.ToString() ?? string.Empty; + } + + public bool StartProcess(string processName) + { + using var ps = PowerShell.Create(); + ps.AddCommand("Start-Process").AddArgument(processName); + ps.Invoke(); + + return !ps.HadErrors; } } diff --git a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PowerShellResult.cs b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PowerShellResult.cs new file mode 100644 index 0000000000..63cb50c5b4 --- /dev/null +++ b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PowerShellResult.cs @@ -0,0 +1,3 @@ +namespace ExecutingPowerShellScript; + +public record PowerShellResult(int ExitCode, string Output, string Error); diff --git a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/ProcessStart.cs b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/ProcessStart.cs index f6ee1c7d21..59999b5198 100644 --- a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/ProcessStart.cs +++ b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/ProcessStart.cs @@ -1,40 +1,52 @@ -using System.Diagnostics; +using System.Diagnostics; -namespace ExecutingPowerShellScript +namespace ExecutingPowerShellScript; + +public class ProcessStart { - public class ProcessStart + public async Task ExecuteScriptAsync(string pathToScript) { - public string ExecuteScript(string pathToScript) + var processStartInfo = new ProcessStartInfo("pwsh") { - var scriptArguments = "-ExecutionPolicy Bypass -File \"" + pathToScript + "\""; - var processStartInfo = new ProcessStartInfo("powershell.exe", scriptArguments); - processStartInfo.RedirectStandardOutput = true; - processStartInfo.RedirectStandardError = true; + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + processStartInfo.ArgumentList.Add("-ExecutionPolicy"); + processStartInfo.ArgumentList.Add("Bypass"); + processStartInfo.ArgumentList.Add("-File"); + processStartInfo.ArgumentList.Add(pathToScript); + + using var process = new Process { StartInfo = processStartInfo }; + process.Start(); - using var process = new Process(); - process.StartInfo = processStartInfo; - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); + var outputTask = process.StandardOutput.ReadToEndAsync(); + var errorTask = process.StandardError.ReadToEndAsync(); + await Task.WhenAll(outputTask, errorTask); + await process.WaitForExitAsync(); - return output; - } + return new PowerShellResult(process.ExitCode, outputTask.Result, errorTask.Result); + } - public string ExecuteCommand(string command) + public async Task ExecuteCommandAsync(string command) + { + var processStartInfo = new ProcessStartInfo("pwsh") { - ProcessStartInfo processStartInfo = new ProcessStartInfo(); - processStartInfo.FileName = "powershell.exe"; - processStartInfo.Arguments = $"-Command \"{command}\""; - processStartInfo.UseShellExecute = false; - processStartInfo.RedirectStandardOutput = true; - using (var process = new Process()) - { - process.StartInfo = processStartInfo; - process.Start(); - string output = process.StandardOutput.ReadToEnd(); - - return output; - } - } + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + processStartInfo.ArgumentList.Add("-Command"); + processStartInfo.ArgumentList.Add(command); + + using var process = new Process { StartInfo = processStartInfo }; + process.Start(); + + var outputTask = process.StandardOutput.ReadToEndAsync(); + var errorTask = process.StandardError.ReadToEndAsync(); + await Task.WhenAll(outputTask, errorTask); + await process.WaitForExitAsync(); + + return new PowerShellResult(process.ExitCode, outputTask.Result, errorTask.Result); } -} \ No newline at end of file +} diff --git a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/Program.cs b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/Program.cs index 4216264fba..9126497b74 100644 --- a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/Program.cs +++ b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/Program.cs @@ -1,35 +1,21 @@ -namespace ExecutingPowerShellScript -{ - internal class Program - { - static void Main(string[] args) - { - var processStart = new ProcessStart(); - var output1 = processStart.ExecuteScript(@"C:\Users\scule\Desktop\echo.ps1"); - Console.WriteLine(output1.ToString()); +using ExecutingPowerShellScript; - var output2 = processStart.ExecuteCommand("echo 'I am invoked using echo command!'"); - Console.WriteLine(output2.ToString()); +var scriptPath = Path.Combine(AppContext.BaseDirectory, "echo.ps1"); - var powerShellClass = new PowerShellClass(); - var output3 = powerShellClass.ExecuteScript(@"C:\Users\scule\Desktop\echo.ps1"); - Console.WriteLine(output3.ToString()); +var processStart = new ProcessStart(); +var scriptResult = await processStart.ExecuteScriptAsync(scriptPath); +Console.WriteLine(scriptResult.Output); - var output4 = powerShellClass.ExecuteCommand("Get-Date"); - Console.WriteLine(output4); +var commandResult = await processStart.ExecuteCommandAsync("echo 'I am invoked using echo command!'"); +Console.WriteLine(commandResult.Output); - var output5 = powerShellClass.StartProcess("notepad"); - Console.WriteLine(output5.ToString()); +var powerShellClass = new PowerShellClass(); +Console.WriteLine(powerShellClass.ExecuteScript(scriptPath)); +Console.WriteLine(powerShellClass.ExecuteCommand("Get-Date")); - var customRunspace = new PSCustomRunspace(); - var output6 = customRunspace.ExecuteCommand("Get-Date"); - Console.WriteLine(output6.ToString()); - customRunspace.StartProcess("notepad"); +// notepad is a Windows example; on macOS or Linux use open or xdg-open +Console.WriteLine(powerShellClass.StartProcess("notepad")); - var output7 = customRunspace.StartProcess("notepad"); - Console.WriteLine(output7.ToString()); - - Console.ReadLine(); - } - } -} \ No newline at end of file +using var customRunspace = new PSCustomRunspace(); +Console.WriteLine(customRunspace.ExecuteCommand("Get-Date")); +Console.WriteLine(customRunspace.StartProcess("notepad")); diff --git a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/echo.ps1 b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/echo.ps1 new file mode 100644 index 0000000000..c7f1d33c81 --- /dev/null +++ b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/echo.ps1 @@ -0,0 +1 @@ +'I am invoked using ProcessStartInfoClass!' diff --git a/dotnet-cli/ExecutingPowerShellScript/Tests/PSCustomRunspaceLiveTest.cs b/dotnet-cli/ExecutingPowerShellScript/Tests/PSCustomRunspaceLiveTest.cs index 414f6d38ba..df93b9b1b2 100644 --- a/dotnet-cli/ExecutingPowerShellScript/Tests/PSCustomRunspaceLiveTest.cs +++ b/dotnet-cli/ExecutingPowerShellScript/Tests/PSCustomRunspaceLiveTest.cs @@ -1,27 +1,26 @@ -using ExecutingPowerShellScript; +using ExecutingPowerShellScript; -namespace Tests +namespace Tests; + +public class PSCustomRunspaceLiveTest { - public class PSCustomRunspaceLiveTest + [Fact] + public void GivenCommand_WhenInvoked_ThenExecutesCommandGiven() { - [Fact] - public void GivenCommand_WhenInvoked_ThenExecutesCommandGiven() - { - var customRunspace = new PSCustomRunspace(); - var result = customRunspace.ExecuteCommand("Get-Date"); - var expectedResult = DateTime.Now.ToShortDateString(); + using var customRunspace = new PSCustomRunspace(); + + var result = customRunspace.ExecuteCommand("Get-Date"); - Assert.Equal(expectedResult, DateTime.Parse(result).ToShortDateString()); - } + Assert.Equal(DateTime.Now.ToShortDateString(), DateTime.Parse(result).ToShortDateString()); + } + + [Fact] + public void GivenName_WhenInvoked_ThenDoesntStartAProcess() + { + using var customRunspace = new PSCustomRunspace(); - [Fact] - public void GivenName_WhenInvoked_ThenDoesntStartAProcess() - { - var customRunspace = new PSCustomRunspace(); - var result = customRunspace.StartProcess("notepad"); - var expectedResult = false; + var result = customRunspace.StartProcess("notepad"); - Assert.Equal(expectedResult, result); - } + Assert.False(result); } } diff --git a/dotnet-cli/ExecutingPowerShellScript/Tests/PowerShellClassLiveTest.cs b/dotnet-cli/ExecutingPowerShellScript/Tests/PowerShellClassLiveTest.cs index 2571496cb4..dae366cd29 100644 --- a/dotnet-cli/ExecutingPowerShellScript/Tests/PowerShellClassLiveTest.cs +++ b/dotnet-cli/ExecutingPowerShellScript/Tests/PowerShellClassLiveTest.cs @@ -1,27 +1,37 @@ -using ExecutingPowerShellScript; +using ExecutingPowerShellScript; -namespace Tests +namespace Tests; + +public class PowerShellClassLiveTest { - public class PowerShellClassLiveTest + [Fact] + public void GivenPath_WhenInvoked_ThenExecutesScript() { - [Fact] - public void GivenCommand_WhenInvoked_ThenExecutesCommand() - { - var powerShellClass = new PowerShellClass(); - var result = powerShellClass.ExecuteCommand("Get-Date"); - var expected = true; - - Assert.Equal(expected, result!=""); - } - - [Fact] - public void GivenName_WhenInvoked_ThenStartsAProcess() - { - var powerShellClass = new PowerShellClass(); - var result = powerShellClass.StartProcess("notepad"); - var expectedResult = "True"; - - Assert.Equal(expectedResult, result.ToString()); - } + var powerShellClass = new PowerShellClass(); + var scriptPath = Path.Combine(AppContext.BaseDirectory, "echo.ps1"); + + var result = powerShellClass.ExecuteScript(scriptPath); + + Assert.True(result); + } + + [Fact] + public void GivenCommand_WhenInvoked_ThenExecutesCommand() + { + var powerShellClass = new PowerShellClass(); + + var result = powerShellClass.ExecuteCommand("Get-Date"); + + Assert.NotEqual(string.Empty, result); + } + + [Fact] + public void GivenName_WhenInvoked_ThenStartsAProcess() + { + var powerShellClass = new PowerShellClass(); + + var result = powerShellClass.StartProcess("notepad"); + + Assert.True(result); } } diff --git a/dotnet-cli/ExecutingPowerShellScript/Tests/ProcessStartLiveTest.cs b/dotnet-cli/ExecutingPowerShellScript/Tests/ProcessStartLiveTest.cs index d9bedf76f0..fee4e8192d 100644 --- a/dotnet-cli/ExecutingPowerShellScript/Tests/ProcessStartLiveTest.cs +++ b/dotnet-cli/ExecutingPowerShellScript/Tests/ProcessStartLiveTest.cs @@ -1,27 +1,41 @@ -using ExecutingPowerShellScript; +using ExecutingPowerShellScript; -namespace Tests +namespace Tests; + +public class ProcessStartLiveTest { - public class ProcessStartLiveTest + [Fact] + public async Task GivenPath_WhenInvoked_ThenExecutesGivenScript() { - [Fact] - public void GivenPath_WhenInvoked_ThenExecutesGivenScript() - { - var processStart = new ProcessStart(); - var result = processStart.ExecuteScript(@"C:\Users\scule\Desktop\echo.ps1"); - var expected= "I am invoked using ProcessStartInfoClass!\r\n"; - - Assert.Equal(expected, result.ToString()); - } - - [Fact] - public void GivenCommand_WhenInvoked_ThenExecutesGivenCommand() - { - var processStart = new ProcessStart(); - var result= processStart.ExecuteCommand("echo 'I am invoked using echo command!'"); - var expected = "I am invoked using echo command!\r\n"; - - Assert.Equal(expected, result.ToString()); - } + var processStart = new ProcessStart(); + var scriptPath = Path.Combine(AppContext.BaseDirectory, "echo.ps1"); + + var result = await processStart.ExecuteScriptAsync(scriptPath); + + Assert.Equal(0, result.ExitCode); + Assert.Equal("I am invoked using ProcessStartInfoClass!" + Environment.NewLine, result.Output); + Assert.Equal(string.Empty, result.Error); + } + + [Fact] + public async Task GivenCommand_WhenInvoked_ThenExecutesGivenCommand() + { + var processStart = new ProcessStart(); + + var result = await processStart.ExecuteCommandAsync("echo 'I am invoked using echo command!'"); + + Assert.Equal(0, result.ExitCode); + Assert.Equal("I am invoked using echo command!" + Environment.NewLine, result.Output); + } + + [Fact] + public async Task GivenFailingCommand_WhenInvoked_ThenReturnsNonZeroExitCodeAndError() + { + var processStart = new ProcessStart(); + + var result = await processStart.ExecuteCommandAsync("Get-Item 'does-not-exist.txt'"); + + Assert.Equal(1, result.ExitCode); + Assert.NotEqual(string.Empty, result.Error); } } diff --git a/dotnet-cli/ExecutingPowerShellScript/Tests/Tests.csproj b/dotnet-cli/ExecutingPowerShellScript/Tests/Tests.csproj index 137567461d..a7c8f19621 100644 --- a/dotnet-cli/ExecutingPowerShellScript/Tests/Tests.csproj +++ b/dotnet-cli/ExecutingPowerShellScript/Tests/Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net10.0 enable enable @@ -9,13 +9,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From ec2c499f247865e25840bf75513febfe2c4c7fd0 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 4 Oct 2026 14:55:53 +0200 Subject: [PATCH 2/2] ExecutingPowerShellScript: custom runspace exposes only Get-Date Drop the Microsoft.PowerShell.Utility import from PSCustomRunspace, which made every cmdlet in that module (Get-Random, Invoke-Expression and more) callable. The runspace now holds the Get-Date entry alone. Program.cs comment names gedit as the Linux example. --- .../ExecutingPowerShellScript/PSCustomRunspace.cs | 2 -- .../ExecutingPowerShellScript/Program.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PSCustomRunspace.cs b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PSCustomRunspace.cs index fc43579d2a..f7b7ce1f80 100644 --- a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PSCustomRunspace.cs +++ b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/PSCustomRunspace.cs @@ -15,8 +15,6 @@ public PSCustomRunspace() "AllowedCommands", new[] { "Get-Date" }, "List of allowed commands"); iss.Variables.Add(entry); - var ms = new ModuleSpecification("Microsoft.PowerShell.Utility"); - iss.ImportPSModule(new[] { ms }); var getDateCmdlet = new SessionStateCmdletEntry("Get-Date", typeof(GetDateCommand), ""); iss.Commands.Add(getDateCmdlet); diff --git a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/Program.cs b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/Program.cs index 9126497b74..f55501cc2b 100644 --- a/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/Program.cs +++ b/dotnet-cli/ExecutingPowerShellScript/ExecutingPowerShellScript/Program.cs @@ -13,7 +13,7 @@ Console.WriteLine(powerShellClass.ExecuteScript(scriptPath)); Console.WriteLine(powerShellClass.ExecuteCommand("Get-Date")); -// notepad is a Windows example; on macOS or Linux use open or xdg-open +// notepad is a Windows example; on Linux, start a GUI program such as gedit Console.WriteLine(powerShellClass.StartProcess("notepad")); using var customRunspace = new PSCustomRunspace();