From fae8110de5737fba9b3cabdd2576d4987668754a Mon Sep 17 00:00:00 2001 From: Cody Batt Date: Thu, 25 Jun 2026 12:22:35 -0600 Subject: [PATCH 1/6] Hide console window for launch/ui verbs (fixes #131) - Upgrade from .NET 6 to .NET 8 - Call FreeConsole() on Windows for 'launch' and 'ui' verbs to detach from the console, causing the OS to destroy the console window when SCALUS is invoked as a protocol handler from a browser - Add --debug flag to launch/ui verbs to keep console visible for troubleshooting - CLI commands (info, register, --help, etc.) remain unaffected - Update RuntimeIdentifiers from win10-x64 to win-x64 for .NET 8 - Bump package versions (Newtonsoft.Json, ASP.NET Core MVC, System.Security.Cryptography.ProtectedData) --- src/Launch/Options.cs | 3 +++ src/OneIdentity.Scalus.csproj | 14 ++++++------- src/Program.cs | 31 +++++++++++++++++++++++++++++ src/Ui/Options.cs | 2 ++ test/OneIdentity.Scalus.Test.csproj | 6 +++--- 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/Launch/Options.cs b/src/Launch/Options.cs index 190b3c6..2bdbd83 100644 --- a/src/Launch/Options.cs +++ b/src/Launch/Options.cs @@ -31,5 +31,8 @@ public class Options : IVerb [Option('p', "preview", Required = false, HelpText = "Show me what will launch, but dont run it. This will also report the token values and show the contents of the generated file, if applicable.")] public bool Preview { get; set; } + + [Option("debug", Required = false, HelpText = "Keep the console window visible for troubleshooting.")] + public bool Debug { get; set; } } } diff --git a/src/OneIdentity.Scalus.csproj b/src/OneIdentity.Scalus.csproj index 41e9be5..a665112 100644 --- a/src/OneIdentity.Scalus.csproj +++ b/src/OneIdentity.Scalus.csproj @@ -10,14 +10,14 @@ Exe - net6.0 + net8.0 OneIdentity.Scalus scalus OneIdentity.Scalus.Program false signer.pfx Latest - linux-x64;osx-x64;win10-x64 + linux-x64;osx-x64;win-x64 dc8f57ba-8a9d-40c4-bf34-a2ffba1ff687 One Identity LLC Copyright 2021 One Identity LLC @@ -38,11 +38,11 @@ $(DefineConstants);TRACE - 1701;1702;CA1416 + 1701;1702;CA1416;CA1854;CA1860;CA1865;SYSLIB1045;SYSLIB1054 - 1701;1702;CA1416 + 1701;1702;CA1416;CA1854;CA1860;CA1865;SYSLIB1045;SYSLIB1054 @@ -116,9 +116,9 @@ - + - + @@ -131,7 +131,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Program.cs b/src/Program.cs index 950c420..54e6830 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -24,6 +24,7 @@ namespace OneIdentity.Scalus using System; using System.Diagnostics; using System.IO; + using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; @@ -37,8 +38,20 @@ namespace OneIdentity.Scalus internal class Program { + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool FreeConsole(); + private static int Main(string[] args) { + // On Windows, detach from the console for 'launch' and 'ui' verbs so no console + // window is visible. When launched from a browser/Explorer, the console window + // (which was created just for this process) is destroyed. (GitHub issue #131) + // CLI commands (info, register, --help, etc.) keep the console as normal. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && ShouldHideConsole(args)) + { + FreeConsole(); + } + bool community = false; #if COMMUNITY_EDITION community = true; @@ -117,6 +130,24 @@ private static void ReleaseLaunchSemaphore() } } + private static bool ShouldHideConsole(string[] args) + { + if (args.Length == 0) + { + // No verb defaults to 'ui' + return true; + } + + // --debug flag keeps the console visible for troubleshooting + if (args.Any(a => string.Equals(a, "--debug", StringComparison.OrdinalIgnoreCase))) + { + return false; + } + + return string.Equals(args[0], "launch", StringComparison.OrdinalIgnoreCase) || + string.Equals(args[0], "ui", StringComparison.OrdinalIgnoreCase); + } + private static void HandleUnexpectedError(Exception ex) { Serilog.Log.Error($"Unexpected error: {ex.Message}", ex); diff --git a/src/Ui/Options.cs b/src/Ui/Options.cs index 7f8cd77..9bc1dc6 100644 --- a/src/Ui/Options.cs +++ b/src/Ui/Options.cs @@ -26,5 +26,7 @@ namespace OneIdentity.Scalus.Ui [Verb("ui", isDefault: true, HelpText = "Run the configuration UI")] public class Options : IVerb { + [Option("debug", Required = false, HelpText = "Keep the console window visible for troubleshooting.")] + public bool Debug { get; set; } } } diff --git a/test/OneIdentity.Scalus.Test.csproj b/test/OneIdentity.Scalus.Test.csproj index 202c5e0..d9ae69a 100644 --- a/test/OneIdentity.Scalus.Test.csproj +++ b/test/OneIdentity.Scalus.Test.csproj @@ -4,14 +4,14 @@ - net6.0 + net8.0 false false signer.pfx false WinExe OneIdentity.Scalus.Test - linux-x64;osx-x64;win10-x64 + linux-x64;osx-x64;win-x64 true @@ -29,7 +29,7 @@ - + all From 2f5212cb2d9b62e0ab52fb85e6323f404c0e43dc Mon Sep 17 00:00:00 2001 From: Cody Batt Date: Mon, 6 Jul 2026 09:28:24 -0600 Subject: [PATCH 2/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/Program.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Program.cs b/src/Program.cs index 54e6830..a7fd32f 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -144,6 +144,16 @@ private static bool ShouldHideConsole(string[] args) return false; } + // Don't hide the console when the user explicitly requests help/version output. + if (args.Any(a => + string.Equals(a, "--help", StringComparison.OrdinalIgnoreCase) || + string.Equals(a, "-h", StringComparison.OrdinalIgnoreCase) || + string.Equals(a, "--version", StringComparison.OrdinalIgnoreCase) || + string.Equals(a, "-v", StringComparison.OrdinalIgnoreCase))) + { + return false; + } + return string.Equals(args[0], "launch", StringComparison.OrdinalIgnoreCase) || string.Equals(args[0], "ui", StringComparison.OrdinalIgnoreCase); } From d69a05a2d0af3f632fbf9417cb134efc51c662af Mon Sep 17 00:00:00 2001 From: Cody Batt Date: Mon, 6 Jul 2026 09:51:30 -0600 Subject: [PATCH 3/6] Don't write to console after FreeConsole() --- src/Program.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 54e6830..64a56be 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,4 +1,4 @@ -// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // // This software is licensed under the Apache 2.0 open source license. // https://github.com/OneIdentity/SCALUS/blob/master/LICENSE @@ -38,6 +38,8 @@ namespace OneIdentity.Scalus internal class Program { + private static bool consoleHidden; + [DllImport("kernel32.dll", SetLastError = true)] private static extern bool FreeConsole(); @@ -50,6 +52,12 @@ private static int Main(string[] args) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && ShouldHideConsole(args)) { FreeConsole(); + consoleHidden = true; + + // Redirect stdout/stderr to nowhere so that subsequent Console.WriteLine + // and Serilog console sinks don't throw on the invalid handle. + Console.SetOut(TextWriter.Null); + Console.SetError(TextWriter.Null); } bool community = false; @@ -64,7 +72,13 @@ private static int Main(string[] args) try { // Register components with autofac - var logger = new LoggerConfiguration().WriteTo.Console(theme: ConsoleTheme.None).CreateLogger(); + var logConfig = new LoggerConfiguration().WriteTo.File(ConfigurationManager.LogFile, shared: true); + if (!consoleHidden) + { + logConfig.WriteTo.Console(theme: ConsoleTheme.None); + } + + var logger = logConfig.CreateLogger(); using var container = Ioc.RegisterApplication(logger); using var lifetimeScope = container.BeginLifetimeScope(); services = lifetimeScope.Resolve(); @@ -217,7 +231,7 @@ private static void ConfigureLogging() config.MinimumLevel.ControlledBy(new Serilog.Core.LoggingLevelSwitch(ConfigurationManager.MinLogLevel.Value)); } - if (ConfigurationManager.LogToConsole) + if (!consoleHidden && ConfigurationManager.LogToConsole) { config.WriteTo.Console(); } From 37f557629b3657f45da8b8a767e455d49ec9b8b5 Mon Sep 17 00:00:00 2001 From: Cody Batt Date: Mon, 6 Jul 2026 10:48:02 -0600 Subject: [PATCH 4/6] Imply --debug when using -p with launch --- src/Program.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 3d35649..4c3219f 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -152,8 +152,10 @@ private static bool ShouldHideConsole(string[] args) return true; } - // --debug flag keeps the console visible for troubleshooting - if (args.Any(a => string.Equals(a, "--debug", StringComparison.OrdinalIgnoreCase))) + // --debug or -p/--preview flags keep the console visible for troubleshooting + if (args.Any(a => string.Equals(a, "--debug", StringComparison.OrdinalIgnoreCase) || + string.Equals(a, "--preview", StringComparison.OrdinalIgnoreCase) || + string.Equals(a, "-p", StringComparison.OrdinalIgnoreCase))) { return false; } From 2effa43b7616b80945e8a23fe31555dec1d4ea6f Mon Sep 17 00:00:00 2001 From: Cody Batt Date: Mon, 6 Jul 2026 11:32:58 -0600 Subject: [PATCH 5/6] Use -NoProfile when running powershell --- src/UrlParser/DefaultRdpUrlParser.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/UrlParser/DefaultRdpUrlParser.cs b/src/UrlParser/DefaultRdpUrlParser.cs index d847133..b4f0e25 100644 --- a/src/UrlParser/DefaultRdpUrlParser.cs +++ b/src/UrlParser/DefaultRdpUrlParser.cs @@ -292,15 +292,9 @@ private static X509Certificate2 CreateScalusSigningCert(IOsServices services) var res = services.Execute("powershell", new List { - "New-SelfSignedCertificate", - "-Subject", - "SCALUS", - "-NotAfter", - "(Get-Date).AddYears(5)", - "-KeyUsage", - "DigitalSignature", - "-CertStoreLocation", - "Cert:\\CurrentUser\\My", + "-NoProfile", + "-Command", + "New-SelfSignedCertificate -Subject SCALUS -NotAfter (Get-Date).AddYears(5) -KeyUsage DigitalSignature -CertStoreLocation Cert:\\CurrentUser\\My", }, out output, out err); From 9a243f58759070cd69bbdc05bc05c661dca35c25 Mon Sep 17 00:00:00 2001 From: Cody Batt Date: Mon, 6 Jul 2026 11:48:08 -0600 Subject: [PATCH 6/6] Import PKI module just in case --- src/UrlParser/DefaultRdpUrlParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UrlParser/DefaultRdpUrlParser.cs b/src/UrlParser/DefaultRdpUrlParser.cs index b4f0e25..1fae2ae 100644 --- a/src/UrlParser/DefaultRdpUrlParser.cs +++ b/src/UrlParser/DefaultRdpUrlParser.cs @@ -294,7 +294,7 @@ private static X509Certificate2 CreateScalusSigningCert(IOsServices services) { "-NoProfile", "-Command", - "New-SelfSignedCertificate -Subject SCALUS -NotAfter (Get-Date).AddYears(5) -KeyUsage DigitalSignature -CertStoreLocation Cert:\\CurrentUser\\My", + "Import-Module PKI; New-SelfSignedCertificate -Subject SCALUS -NotAfter (Get-Date).AddYears(5) -KeyUsage DigitalSignature -CertStoreLocation Cert:\\CurrentUser\\My", }, out output, out err);