/ src / modules / fancyzones / FancyZonesCLI / Program.cs
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  }