CLI tool
adamabdelhamed/PowerArgs avatar
adamabdelhamed/PowerArgs

PowerArgs: turning command line arguments into .NET objects you can validate

The ultimate .NET Standard command line argument parser

580 stars58 forksC#MIT

At a glance

What is it?
PowerArgs is a .NET Standard library that parses command line arguments into a plain object decorated with attributes, and can go further by handling user errors, generating usage text, offering tab completion and invoking a chosen action method for you. It suits a .NET developer who wants typed arguments with declarative validation instead of hand-rolled parsing, and the newest work in the project is a source-generated parsing pipeline that avoids reflection on .NET 10.
Who is it for?
Adopt PowerArgs if you write .NET command line tools and want typed arguments with declarative validation, generated usage text and action dispatch instead of manual parsing.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Arguments as an object, with the constraints declared as attributes

PowerArgs converts command line arguments into .NET objects that are easy to work with, and it also provides a ton of additional, optional capabilities that you can try, such as argument validation, auto generated usage, tab completion and plenty of extensibility.

The basic shape is a class whose properties describe the arguments, with attributes carrying the constraints:

cs
// A class that describes the command line arguments for this program
public class MyArgs
{
    // This argument is required and if not specified the user will
    // be prompted.
    [ArgRequired(PromptIfMissing=true)]
    public string StringArg { get; set; }

    // This argument is not required, but if specified must be >= 0 and <= 60
    [ArgRange(0,60)]
    public int IntArg { get; set; }
}

That is the whole model. A property is an argument, an attribute is its rule, and `Args.Parse<MyArgs>(args)` gives you back a populated instance. There is no separate schema object, no builder to configure, and the same class is the type your code works with afterwards.

The attributes do more than validation. `ArgRequired` with `PromptIfMissing=true` does not merely reject a missing argument, it asks for it, which is the difference between a parser that errors and one that interviews the user. `ArgRange(0,60)` states a bound as part of the type description rather than as an `if` in a `Main` method.

The comparison worth making is with the standard library and with the usual hand-written approach. `Environment.GetCommandLineArgs()` gives you strings, and everything after that is your problem: knowing which argument is which, which are required, what the valid range is, and what to print when the user gets it wrong. PowerArgs moves all of that into declarations, and the declarations are checked in one place rather than spread across a parsing function and a validation function.

The package is on the Official NuGet Gallery, and the project is MIT licensed and written in C#.

The exception path decides whether your code stays short

The first example in the README parses the arguments but leaves the error handling to you, and the contrast with the second example is the point of the library.

In the parsing-only version you catch `ArgException` yourself and print both the message and the generated usage:

cs
class Program
{
    static void Main(string[] args)
    {
        try
        {
            var parsed = Args.Parse<MyArgs>(args);
            Console.WriteLine("You entered string '{0}' and int '{1}'", parsed.StringArg, parsed.IntArg);
        }
        catch (ArgException ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ArgUsage.GenerateUsageFromTemplate<MyArgs>());
        }
    }
}

The second example moves the application logic out of the Program class into a non-static `Main` method on the arguments class, and lets PowerArgs do the work:

cs
// A class that describes the command line arguments for this program
[ArgExceptionBehavior(ArgExceptionPolicy.StandardExceptionHandling)]
public class MyArgs
{
    // This argument is required and if not specified the user will
    // be prompted.
    [ArgRequired(PromptIfMissing=true)]
    public string StringArg { get; set; }

    // This argument is not required, but if specified must be >= 0 and <= 60
    [ArgRange(0,60)]
    public int IntArg { get; set; }

    // This non-static Main method will be called and it will be able to access the parsed and populated instance level properties.
    public void Main()
    {
        Console.WriteLine("You entered string '{0}' and int '{1}'", this.StringArg, this.IntArg);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Args.InvokeMain<MyArgs>(args);
    }
}

The rule is simple and worth stating precisely. Any exception deriving from ArgException is treated as user error, and PowerArgs displays the message along with auto-generated usage documentation for the program. All other exceptions still bubble up and need to be handled by your code.

That is a well-chosen boundary. A validation failure is a user error and the user should see a helpful message; a null reference in your business logic is a bug and it should reach your exception handler with its stack trace intact. The attribute `[ArgExceptionBehavior(ArgExceptionPolicy.StandardExceptionHandling)]` is what opts this model in.

The second benefit of the arrangement is the action dispatch. The library can also orchestrate the execution of your program, giving consistent and natural user error handling, invoking the correct code based on an action, for example 'git push' versus 'git pull', and letting you focus on writing your code. That is the same shape as a version control program's subcommands, and it is why the project mentions a full-blown interactive text UI application built with it.

The source-generated pipeline and its two static tiers

The most substantial recent work in PowerArgs is a source-generated parsing pipeline, and the README describes it in unusually precise terms.

On .NET 10, a scaffold and its public argument properties can be declared `partial` to opt into source-generated property bodies and parsing metadata:

cs
public partial class MyGeneratedArgs
{
    [ArgRequired]
    public partial string Input { get; set; }

    public partial int Count { get; set; }
}

There are two statically selected tiers. Common scalar models use the fastest direct parser, which avoids reflected scaffold discovery, activation, property assignment and the general hook and alias processing machinery. Feature-rich models still use generated construction, metadata, property setters and method invokers, but reuse the mature shared parser where its richer lifecycle is required. Both tiers are source-generated pipelines, and neither reflects over the scaffold to discover or assign its argument properties.

That last sentence is the design constraint worth holding on to. The whole point of the exercise is that the reflection cost disappears for the common case, so the fallback exists but is narrow. The reflection pipeline is reserved for structural cases such as abstract or generic scaffolds, inaccessible accessors, non-partial public properties declared on the opted-in scaffold, and action signatures outside the three documented forms.

And pipeline selection is entirely static. At compile time the generator selects the direct generated parser, the shared-parser generated tier, or the reflection pipeline. Runtime argument syntax and values never make that decision. That is a deliberate property, because a parser whose performance depends on the user's input is the kind of thing that produces baffling slow paths on someone else's command line.

There is one gotcha that the README states plainly. All public properties declared by an opted-in scaffold, including `[ArgIgnore]` properties, must be declared `partial`. Inherited public argument properties can remain ordinary properties, because the derived generated model emits direct setters for them.

Two details show the project's maturity. The generator ships inside the normal PowerArgs NuGet package as an analyzer, so consumers do not need a second package or a separate setup path. And there are two ways to check which path was selected at run time, `GeneratedArgumentModelRegistry.IsGenerated<MyGeneratedArgs>()` and `Args.GetAmbientDefinition().UsesGeneratedPipeline`, with a benchmark project you can run from the repository root to compare both paths on your own machine.

The attribute surface, grouped by what it controls

The generated pipeline supports the features documented in the README, and the list is grouped by what each group controls.

Required, naming and layout metadata come first: `[ArgRequired]`, including prompting and conditional requirements, `[ArgShortcut]`, `[ArgPosition]`, `[ArgIgnore]`, `[ArgIgnoreCase]` and `[ArgEnforceCase]`. That group decides what an argument is called, where it may appear, and whether case matters.

Validation and defaults are next: built-in validators, custom `ArgValidator` attributes, cross-argument validators, `[DefaultValue]` and `[ArgDefaultValue]`. The cross-argument validator is the interesting one, because some constraints are between two arguments rather than within one, and those are the ones a per-property attribute cannot express.

Hooks and interactive features follow: `[HelpHook]`, `[StickyArg]`, `[StickyArgPersistence]`, `[TabCompletion]`, argument-aware completion metadata, and custom `ArgHook` attributes. The sticky arguments and the help hook are the pieces that make a command line tool feel finished rather than merely correct.

Type-level behaviour is the fourth group: `[ArgExceptionBehavior]`, `[AllowUnexpectedArgs]`, descriptions, examples, usage omission, and product, version and copyright metadata. `[AllowUnexpectedArgs]` is the escape hatch for a tool that should tolerate extra arguments, which is how you keep a script that passes extra flags working against a newer version of your program.

Values cover the built-in scalar types, enums, collections, `SecureStringArgument`, component-model conversions and custom `[ArgReviver]` methods. A reviver is how a string on the command line becomes a richer object, and `SecureStringArgument` is the one to notice for a tool that takes a credential.

The last group is the structural one: inherited public argument properties, ambient arguments, generated usage definitions and the documented action method shapes.

The action methods have their own rules, and `InvokeMain` and `InvokeAction` avoid reflection for `void` and `Task` methods. Generated actions may be parameterless, take a single action-argument object with supported public scalar properties, or take multiple directly revivable scalar parameters. Their defaults, validation, shortcuts, descriptions, positions, usage metadata and case rules are generated too.

A solution file with the generator and the benchmarks in it

The repository layout tells you how the project is organised as a .NET solution rather than a single project.

There is `PowerArgs.sln` at the root, with `PowerArgs/` holding the library itself and `PowerArgs.Generator/` holding the source generator, and `PowerArgs.Benchmarks/` as a separate project for comparing the paths. `PowerArgsTestCore/` is the shared test core, `PowerArgs.vsmdi` is a Visual Studio solution item file, and there is a `Release Process.txt` documenting how a release is cut.

The separation of the generator into its own project is what makes the packaging claim work. The generator ships as an analyzer inside the normal PowerArgs NuGet package, so a consumer adds one package and the generator runs at their build. If the generator were a separate package, every consumer would have to know to install it, and the source-generated path would be opt-in in a way that is easy to miss. Bundling it as an analyzer means the path is available without a second decision.

The benchmarks project is a separate project rather than a test, which is the right call for the comparison it exists to support. The README says to run `dotnet run -c Release --project PowerArgs.Benchmarks` from the repository root to compare both paths on the current machine, and the framing is careful, that is on the current machine, since a comparison on your own hardware is the one that informs your own decision.

Two other files at the root are worth a look. `StyledUsageExampleOutput.PNG` is a rendered image of what the generated usage output looks like, which is a useful thing to have in the repository because the whole generated-usage feature is visual and prose descriptions of a layout are not much help. And `TestSettings1.testsettings` is a Visual Studio test settings file, which alongside the presence of `PowerArgsTestCore/` suggests the test suite is driven through the Visual Studio test runner rather than only from the command line.

The README also points at a companion project for building command line applications with text-based graphics, called klooie, by the same author, and shows an interactive text UI application built with the library.

Editorial conclusion

Adopt PowerArgs if you write .NET command line tools and want typed arguments with declarative validation, generated usage text and action dispatch instead of manual parsing. Do not adopt it if you are on .NET Framework or an older runtime, since the current source-generated path targets .NET 10, and check first whether the reflection fallback applies to your model, because the pipeline is chosen at compile time and the fallback covers abstract or generic scaffolds, inaccessible accessors and non-partial public properties.

Frequently asked questions

What is PowerArgs?

It is a .NET Standard library that converts command line arguments into .NET objects that are easy to work with, plus optional capabilities such as argument validation, auto generated usage, tab completion and extensibility. It can also orchestrate the execution of your program by invoking the correct code based on an action.

How do I install PowerArgs?

It is available at the Official NuGet Gallery, so `Install-Package PowerArgs` or the equivalent dotnet add package command works. The project is MIT licensed and the source is in the repository as a solution containing the library, the generator and a benchmarks project.

How does PowerArgs handle a validation error?

Any exception deriving from ArgException is treated as user error, and PowerArgs displays the message along with auto-generated usage documentation. All other exceptions still bubble up and have to be handled by your code, and the [ArgExceptionBehavior] attribute with StandardExceptionHandling is what selects that policy.

What does the source-generated pipeline in PowerArgs change?

On .NET 10 a scaffold and its public argument properties can be declared partial to opt into source-generated parsing, which avoids reflected scaffold discovery, activation and property assignment. Pipeline selection is entirely static at compile time, and the reflection fallback is reserved for structural cases such as abstract or generic scaffolds, inaccessible accessors and non-partial public properties.

Do I need a second package to use the PowerArgs generator?

No. The normal PowerArgs NuGet package includes the generator as an analyzer, so consumers do not need a second package or setup path. You can check which path was selected with GeneratedArgumentModelRegistry.IsGenerated<T>() or Args.GetAmbientDefinition().UsesGeneratedPipeline.

Is PowerArgs still being maintained?

The repository is not archived and the last push was on 2026-10-22. There are no GitHub releases, and the project is MIT licensed with a solution containing PowerArgs, PowerArgs.Generator, PowerArgs.Benchmarks and PowerArgsTestCore, plus a Release Process document.

Official sources

  1. adamabdelhamed/PowerArgs on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/adamabdelhamed-powerargs.svg)](https://hysenlabs.com/projects/adamabdelhamed-powerargs)

Was this useful?