command-line-api
                                
                                
                                
                                    command-line-api copied to clipboard
                            
                            
                            
                        IsRequired is ignored for common options when commands are specified
I have a global option that is required for all commands. But the required flag on the global option is ignored when specifying the command.
Project here: https://github.com/ptr727/PlexCleaner
Code snippets.
        private static int Main()
        {
            // Prevent sleep
            KeepAwake.PreventSleep();
            // TODO : Quoted paths ending in a \ fail to parse properly, use our own parser
            // https://github.com/gsscoder/commandline/issues/473
            RootCommand rootCommand = CommandLineOptions.CreateRootCommand();
            int ret = rootCommand.Invoke(CommandLineEx.GetCommandLineArgs());
            // Allow sleep
            KeepAwake.AllowSleep();
            return ret;
        }
    public class CommandLineOptions
    {
        public string SettingsFile { get; set; }
        public List<string> MediaFiles { get; set; }
        public string LogFile { get; set; }
        public bool LogAppend { get; set; }
        public bool TestSnippets { get; set; }
        public bool TestNoModify { get; set; }
        public static RootCommand CreateRootCommand()
        {
            // Root command and global options
            RootCommand rootCommand = new RootCommand("Utility to optimize media files for DirectPlay on Plex");
            AddGlobalOptions(rootCommand);
            // Create default settings
            rootCommand.AddCommand(CreateDefaultSettingsCommand());
            // Check for new tools
            rootCommand.AddCommand(CreateCheckForNewToolsCommand());
...
            return rootCommand;
        }
        private static void AddGlobalOptions(RootCommand rootCommand)
        {
            if (rootCommand == null)
                throw new ArgumentNullException(nameof(rootCommand));
            // Path to the settings file, required
            rootCommand.AddOption(
                new Option<string>("--settingsfile")
                {
                    Description = "Path to settings file",
                    IsRequired = true
                });
            // Path to the log file, optional
            rootCommand.AddOption(
                new Option<string>("--logfile")
                {
                    Description = "Path to log file",
                    IsRequired = false
                });
            // Append to log vs. overwrite, optional
            rootCommand.AddOption(
                new Option<bool>("--logappend")
                {
                    Description = "Append to the log file vs. default overwrite",
                    IsRequired = false
                });
        }
        private static Command CreateDefaultSettingsCommand()
        {
            // Create default settings file
            return new Command("defaultsettings")
            {
                Description = "Write default values to settings file",
                Handler = CommandHandler.Create<CommandLineOptions>(Program.WriteDefaultSettingsCommand)
            };
        }
Calling with command specific help:
pieter@DESKTOP-UUIL10M:~/PlexCleaner/PlexCleaner/bin/Debug/net5.0$ ./PlexCleaner --help defaultsettings
defaultsettings:
  Write default values to settings file
Usage:
  PlexCleaner defaultsettings [options]
Options:
  -?, -h, --help    Show help and usage information
Calling with no arguments correctly shows that --settingsfile is required:
pieter@DESKTOP-UUIL10M:~/PlexCleaner/PlexCleaner/bin/Debug/net5.0$ ./PlexCleaner
Required command was not provided.
Option '--settingsfile' is required.
PlexCleaner:
  Utility to optimize media files for DirectPlay on Plex
Usage:
  PlexCleaner [options] [command]
Options:
  --settingsfile <settingsfile> (REQUIRED)    Path to settings file
  --logfile <logfile>                         Path to log file
  --logappend                                 Append to the log file vs. default overwrite
  --version                                   Show version information
  -?, -h, --help                              Show help and usage information
Commands:
  defaultsettings    Write default values to settings file
  process            Process media files
  monitor            Monitor and process media file changes in folders
  remux              Re-Multiplex media files
  reencode           Re-Encode media files
  deinterlace        De-Interlace media files
  verify             Verify media files
  createsidecar      Create sidecar files
  getsidecar         Print sidecar file attribute information
  gettagmap          Print attribute tag-map created from media files
  getmediainfo       Print media file attribute information
  getbitrateinfo     Print media file bitrate information
Calling with any command ignores the --settingsfile is required option:
pieter@DESKTOP-UUIL10M:~/PlexCleaner/PlexCleaner/bin/Debug/net5.0$ ./PlexCleaner defaultsettings
12/16/2020 15:11:46 : Writing default settings to ""
Unhandled exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.ArgumentNullException: Value cannot be null. (Parameter 'path')
Expected behavior is that any global options with required flags still be honored for commands.
I'm encountering the same issue. Did you find a resolution?
@ptr727 @asasine What versions of System.CommandLine are you using?
The code provided isn't complete enough for me to reproduce the issue and at least as of 2.0.0-beta1.21308.1, my attempt at a minimal repro seems to work as expected. What detail am I missing?

using System.CommandLine;
using System.CommandLine.Invocation;
var defaultSettings = new Command("defaultSettings");
var root = new RootCommand
{
    defaultSettings
};
var opt = new Option<string>("-x")
{
    IsRequired = true
};
root.AddGlobalOption(opt);
root.Handler = CommandHandler.Create<string>(x => Console.WriteLine(x));
defaultSettings.Handler = CommandHandler.Create<string>(x => Console.WriteLine(x));
root.Parse("").Errors.Select(e => e.Message).Display();
root.Parse("defaultSettings").Errors.Select(e => e.Message).Display();
                                    
                                    
                                    
                                
Still happens with latest (2021-11-17) 2.0.0-beta1.21308.1 and .NET 6.
But your code uses a command I did not previously see, AddGlobalOption()?
Looks like AddGlobalOption(): https://github.com/dotnet/command-line-api/blob/main/src/System.CommandLine/Command.cs#L77
Was added in January 2020: https://github.com/dotnet/command-line-api/commit/2db5ead8ecab56205f0293346f0284d6de832f4e#diff-f893b7b8175140676b5c4bd6a5aecd55ac4d2d179afa8fd01640426eeab49c28
Fixed in July 2020: https://github.com/dotnet/command-line-api/pull/958
But I can't find it in any docs?
Anyway, I changed my global options to use AddGlobalOption() instead of AddOption(), and the output is as expected.
My code: https://github.com/ptr727/PlexCleaner/blob/develop/PlexCleaner/CommandLineOptions.cs
Before:
PS C:\Users\piete\source\repos\ptr727\PlexCleaner\PlexCleaner\bin\Debug\net6.0> ./PlexCleaner --help defaultsettings
defaultsettings
  Write default values to settings file
Usage:
  PlexCleaner [options] defaultsettings
Options:
  -?, -h, --help  Show help and usage information
After:
PS C:\Users\piete\source\repos\ptr727\PlexCleaner\PlexCleaner\bin\Debug\net6.0> ./PlexCleaner --help defaultsettings
defaultsettings
  Write default values to settings file
Usage:
  PlexCleaner [options] defaultsettings
Options:
  --settingsfile <settingsfile> (REQUIRED)  Path to settings file
  --logfile <logfile>                       Path to log file
  --logappend                               Append to the log file vs. default overwrite
  -?, -h, --help                            Show help and usage information
                                    
                                    
                                    
                                
Can you provide a small and complete repro of the issue, ideally in the form of a test with an assertion showing the expectation?
Apologies, maybe my last comment was not clear?
I changed my code to use AddGlobalOption() instead of AddOption(), and the behavior is now as expected.
My code (develop branch): https://github.com/ptr727/PlexCleaner/blob/develop/PlexCleaner/CommandLineOptions.cs
Do note, that until you presented your snippet of code, I had never seen, and I still see no documentation or reference of AddGlobalOption().
If anything, then the solution to this issue is to document AddGlobalOption().
I understood that AddGlobalOption gives the desired behavior. The way this works is that it effectively adds the specified option as a child of the Command you invoke it on and all of its child commands.
What I'm wondering is whether there's a bug when not using AddGlobalOption. If so, a more concise demonstration of that bug would be helpful. Otherwise, feel free to close this.
I went to reproduce my error, but discovered the .UseParseErrorReporting() extension on CommandLineBuilder in the process. Once I added this to my builder, the program works as desired.
Hi, i have a same problem.
using System.CommandLine;
var cmd = new RootCommand("Test application");
var optHost = new Option("--host", "hostname");
optHost.IsRequired = true;
cmd.AddOption(optHost);
var optPort = new Option<int>("--port", "Port");
optPort.IsRequired = true;
cmd.AddOption(optPort);
var cmdSend= new Command("send","Send mail");
cmd.Add(cmdSend);
await cmd.InvokeAsync(args);
if esecute without option the result are
#  dotnet run --     
Required command was not provided.
Option '--host' is required.
Option '--port' is required.
Description:
  Test application
Usage:
  TestConsole [command] [options]
Options:
  --host (REQUIRED)         hostname
  --port <port> (REQUIRED)  Port
  --version                 Show version information
  -?, -h, --help            Show help and usage information
Commands:
  send  Send mail
If specify the command send without parameter not receive error
# dotnet run -- send
#
I a my problem?
best regards
@franklupo What you're seeing is because your required options were only added to the root command. This means they're only required when invoking the root command.
If you add them as well to send you'll see the error on invocations of send as well. Or if you add them to the root as global options (cmd.AddGlobalOption(optHost)) then the error will be shown for all subcommands unless the options are provided.
ok, if add AddGlobalOption the message is unclear. The parameters refer to the root
#  dotnet run -- send
Option '--host' is required.
Option '--port' is required.
Description:
  Send mail
Usage:
  TestConsole send [options]
Options:
  --host (REQUIRED)         hostname
  --port <port> (REQUIRED)  Port
  -?, -h, --help            Show help and usage information
                                    
                                    
                                    
                                
If the option is global then it can be specified in either position. Either of these would be valid:
> myapp --port 123
> myapp send --port 123
                                    
                                    
                                    
                                
new case
using System.CommandLine;
var cmd = new RootCommand("Test application");
var optHost = new Option("--host", "hostname");
optHost.IsRequired = true;
cmd.AddGlobalOption(optHost);
var optPort = new Option<int>("--port", "Port");
optPort.IsRequired = true;
cmd.AddGlobalOption(optPort);
var cmdSend = new Command("send", "Send mail");
cmd.Add(cmdSend);
var optEmail = new Option("--email", "email");
optEmail.IsRequired = true;
cmdSend.AddOption(optEmail);
var cmdVerify = new Command("verify", "verify");
cmdSend.Add(cmdVerify);
await cmd.InvokeAsync(args);
Add option on send and new sub command.
execute partial send
dotnet run --  send --host myhost --port 123       
Required command was not provided.
Option '--email' is required.
Unrecognized command or argument 'myhost'.
Description:
  Send mail
Usage:
  TestConsole send [command] [options]
Options:
  --email (REQUIRED)        email
  --host (REQUIRED)         hostname
  --port <port> (REQUIRED)  Port
  -?, -h, --help            Show help and usage information
Commands:
  verify  verify
execute sub command without --email
dotnet run --  send --host myhost --port 123 verify
Unrecognized command or argument 'myhost'.
Description:
  verify
Usage:
  TestConsole send verify [options]
Options:
  --host (REQUIRED)         hostname
  --port <port> (REQUIRED)  Port
  -?, -h, --help            Show help and usage information
it is no longer clear what the problem is
Best regards
I notice that you're using a number of non-generic Option instances. This class has been made abstract.
Try changing new Option to new Option<string> for the name and email options and see if the behavior is what you're looking for.
using System.CommandLine;
var cmd = new RootCommand("Test application");
var optHost = new Option<string>("--host", "hostname");
optHost.IsRequired = true;
cmd.AddGlobalOption(optHost);
var optPort = new Option<int>("--port", "Port");
optPort.IsRequired = true;
cmd.AddGlobalOption(optPort);
var cmdSend = new Command("send", "Send mail");
cmd.Add(cmdSend);
var optEmail = new Option<string>("--email", "email");
optEmail.IsRequired = true;
cmdSend.AddOption(optEmail);
var cmdVerify = new Command("verify", "verify");
cmdSend.Add(cmdVerify);
await cmd.InvokeAsync(args);
change host and email to string option
dotnet run --host myhost --port 123 send
Required command was not provided.
Option '--email' is required.
Description:
  Send mail
Usage:
  TestConsole send [command] [options]
Options:
  --email <email> (REQUIRED)  email
  --host <host> (REQUIRED)    hostname
  --port <port> (REQUIRED)    Port
  -?, -h, --help              Show help and usage information
Commands:
  verify  verify
this is ok
dotnet run --host myhost --port 123 send verify
no error message. --email is required. This is an error
bets regards
I'm still not clear what the issue is. What are you seeing and what are you expecting to see? If you're expecting to see an error on send verify because --email is missing, then you would need to use cmd.AddGlobalOption(optEmail) instead of AddOption.
Hi,
if --email is required why do i have to specify it as global?
Currently you've only added it to the send command so it is only required when the send command is the one being invoked. But once you specify verify, then send is no longer being invoked.
Making it global would mean --email is an option for both send and verify.
I do not think it works correctly. If mandatory it should ask. It is currently not possible to verify that the data is entered correctly.
Best regards
If anything, then the solution to this issue is to document AddGlobalOption().
@ptr727, the documentation for this feature can now be found here: https://docs.microsoft.com/en-us/dotnet/standard/commandline/define-commands#global-options
If mandatory it should ask.
@franklupo A required option is only required when it's in scope. Take this example:
var root = new RootCommand("myapp")
{
    new Command("one"),
    new Command("two")
    {
        new Option<int>("-x")
        {
            IsRequired = true
        }
    }
};
Commands one and two are mutually exclusive. This is pretty universal subcommand/verb behavior. Running myapp one two is not valid.
Option -x is only defined under command two, so running myapp one -x 123 is also not valid. But it's required if I want to run the subcommand two.
If I want option -x to be valid everywhere, I could add it directly to each command, something like this:
var optionX = new Option<int>("-x")
{
    IsRequired = true
};
var root = new RootCommand("myapp")
{
    optionX,
    new Command("one")
    {
        optionX
    },
    new Command("two")
    {
        optionX
    }
};
AddGlobalOption is essentially a shorthand for that approach. It lets you get this behavior more succinctly:
var optionX = new Option<int>("-x")
{
    IsRequired = true
};
var root = new RootCommand("myapp")
{
    new Command("one"),
    new Command("two")
};
root.AddGlobalOption(optionX);