Program.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.CommandLine; 7 using System.Linq; 8 using System.Threading.Tasks; 9 using FancyZonesCLI.CommandLine; 10 11 namespace FancyZonesCLI; 12 13 internal sealed class Program 14 { 15 private static readonly string[] HelpFlags = ["--help", "-h", "-?"]; 16 17 private static async Task<int> Main(string[] args) 18 { 19 Logger.InitializeLogger(); 20 Logger.LogInfo($"CLI invoked with args: [{string.Join(", ", args)}]"); 21 22 // Initialize Windows messages used to notify FancyZones. 23 NativeMethods.InitializeWindowMessages(); 24 25 // Intercept help requests early and print custom usage. 26 if (TryHandleHelpRequest(args)) 27 { 28 return 0; 29 } 30 31 // Detect PowerShell script block expansion (when {} is interpreted as script block) 32 if (DetectPowerShellScriptBlockArgs(args)) 33 { 34 return 1; 35 } 36 37 RootCommand rootCommand = FancyZonesCliCommandFactory.CreateRootCommand(); 38 int exitCode = await rootCommand.InvokeAsync(args); 39 40 if (exitCode == 0) 41 { 42 Logger.LogInfo("Command completed successfully"); 43 } 44 else 45 { 46 Logger.LogWarning($"Command failed with exit code {exitCode}"); 47 } 48 49 return exitCode; 50 } 51 52 /// <summary> 53 /// Handles help requests for root command and subcommands. 54 /// </summary> 55 /// <returns>True if help was printed, false otherwise.</returns> 56 private static bool TryHandleHelpRequest(string[] args) 57 { 58 bool hasHelpFlag = args.Any(a => HelpFlags.Any(h => string.Equals(a, h, StringComparison.OrdinalIgnoreCase))); 59 if (!hasHelpFlag) 60 { 61 return false; 62 } 63 64 // Get non-help arguments to identify subcommand 65 var nonHelpArgs = args.Where(a => !HelpFlags.Any(h => string.Equals(a, h, StringComparison.OrdinalIgnoreCase))).ToArray(); 66 67 if (nonHelpArgs.Length == 0) 68 { 69 // Root help: fancyzones cli --help 70 FancyZonesCliUsage.PrintUsage(); 71 } 72 else 73 { 74 // Subcommand help: fancyzones cli <command> --help 75 string subcommandName = nonHelpArgs[0]; 76 FancyZonesCliUsage.PrintCommandUsage(subcommandName); 77 } 78 79 return true; 80 } 81 82 /// <summary> 83 /// Detects when PowerShell interprets {GUID} as a script block and converts it to encoded command args. 84 /// This happens when users forget to quote GUIDs with braces in PowerShell. 85 /// </summary> 86 /// <returns>True if PowerShell script block args were detected, false otherwise.</returns> 87 private static bool DetectPowerShellScriptBlockArgs(string[] args) 88 { 89 // PowerShell converts {scriptblock} to: -encodedCommand <base64> -inputFormat xml -outputFormat text 90 bool hasEncodedCommand = args.Any(a => string.Equals(a, "-encodedCommand", StringComparison.OrdinalIgnoreCase)); 91 bool hasInputFormat = args.Any(a => string.Equals(a, "-inputFormat", StringComparison.OrdinalIgnoreCase)); 92 bool hasOutputFormat = args.Any(a => string.Equals(a, "-outputFormat", StringComparison.OrdinalIgnoreCase)); 93 94 if (hasEncodedCommand || (hasInputFormat && hasOutputFormat)) 95 { 96 Console.ForegroundColor = ConsoleColor.Red; 97 Console.WriteLine(Properties.Resources.error_powershell_scriptblock_title); 98 Console.ResetColor(); 99 Console.WriteLine(); 100 Console.WriteLine(Properties.Resources.error_powershell_scriptblock_explanation); 101 Console.WriteLine(Properties.Resources.error_powershell_scriptblock_hint); 102 Console.WriteLine(); 103 Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option1}"); 104 Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option1_example}"); 105 Console.WriteLine(); 106 Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option2}"); 107 Console.WriteLine($" {Properties.Resources.error_powershell_scriptblock_option2_example}"); 108 Console.WriteLine(); 109 110 Logger.LogWarning("PowerShell script block expansion detected - user needs to quote GUID or omit braces"); 111 return true; 112 } 113 114 return false; 115 } 116 }