Extend NuGet Command Line

Yesterday I started a discussion about adding a new command to nuget.exe. It ended in creating an extension to the command line that behaves in the same way without having to dive into the nuget code base or add more complexity to it.

I haven’t seen any blog posts or documentation surrounding this new concept (new in nuget 1.4) of extending the command line except for Matt Hamilton’s posts on using NuGet for plug-ins and NuGet with MEF (which are not quite this concept).

Here is my experience. I found looking at the commands in nuget.exe (NuGet.Commands) to make it easy to create my own command and extend NuGet.  Some of this information may not be exactly what you should do, but it worked for me.

The Code

1. Create a new Visual Studio Solution and add a Class Library (full .NET 4.0 framework).
2. Add A NuGet Reference to Nuget.CommandLine.
3. Now manually add a reference to nuget.exe. Yes, you can add references to .NET executables.
4.Adding a reference to nuget.exe gives you access to Command and the CommandAttribute. These are what I see as necessary to extending NuGet.
5. Add a reference to System.ComponentModel.Composition (MEF). 
6. Now you provide a class and resource file per command.

The resource file contains the command descriptions:

CopyResources.resx

The class is attributed with CommandAttribute and inherits from Command.

[Command(typeof (CopyResources), "copy", "Description", MinArgs = 1, MaxArgs = 5, UsageSummaryResourceName = "UsageSummary",UsageDescriptionResourceName = "UsageDescription")]
public class Copy : Command {}

The Command Attribute asks for a few things:

1. typeof (CopyResources)ResourceClass - What is the resx file that the command will draw its help information from?
2. “copy”CommandName - what is the name of the command?
3.“Description”Description - resource name in the resource file that gives the description on the command line.
Description - Copies a package from one source to another source
4. MinArgs=1Minimum Arguments - what are the minimum number of arguments that can be passed to this command and satisfy its needs? 
5. MaxArgs = 5Maximum Arguments - what are the maximum number of arguments that can be passed to this command?
6. UsageSummaryResourceName = “UsageSummary”UsageSummary - a very short amount of information that explains the usage of the command.
7. UsageDescription = “Usage Description”UsageDescription - more detailed information about how to use the command.

Here is what the command looks like when run with help. Notice where from the CopyResources.resx these items match up to the screen.

The help command showing the items from the resx file.

Now that we have the command attribute finished, let’s work on the command itself.

private readonly IPackageRepositoryFactory _repositoryFactory;
private readonly IPackageSourceProvider _sourceProvider;

[ImportingConstructor]
public Copy(IPackageRepositoryFactory repositoryFactory, IPackageSourceProvider sourceProvider)
{
    _repositoryFactory = repositoryFactory;
    _sourceProvider = sourceProvider;
}

[Option(typeof (CopyResources), "SourceDescription", AltName = "src")]
public string Source { get; set; }

[Option(typeof (CopyResources), "DestinationDescription", AltName = "dest")]
public string Destination { get; set; }

[Option(typeof (CopyResources), "VersionDescription")]
public string Version { get; set; }

[Option(typeof (CopyResources), "ApiKeyDescription")]
public string ApiKey { get; set; }

public override void ExecuteCommand()
{
    string packageId = base.Arguments[0];

    Console.WriteLine("Copying {0} from {1} to {2}.", string.IsNullOrEmpty(Version) ? packageId : packageId + " " + Version,
                      string.IsNullOrEmpty(Source) ? "any source" : Source, Destination);

    //do work son
 
}

Important things to note:

1. [ImportingConstructor] is the constructor that will be called. Notice I am having it get passed an IPackageRepositoryFactory and an IPackageSourceProvider. Most of my work is already done for me setting these up in the command line application.
2. Each command line option has [Option(typeof (CopyResources), "DestinationDescription", AltName = "dest")] .
3. Notice again with the options we specify the Resource File with an Option Description (“DestinationDescription”) and an optional short parameter for the Option (AltName). Notice how that translates into options on the help command.
options available with the custom command 
4. I am overriding ExecuteCommand and pulling the packageId as the first item from base.Arguments. 

From here I can do all the work necessary for my custom command.

Get NuGet to Recognize It

Nuget looks for custom commands in %LocalAppData%\NuGet\Commands. Drop your DLL in there and then call nuget. See if it registers your command. This will allow you to continue to tweak your package prior to releasing it in the wild.

Use AddExtension to Add New Commands

NuGet Extend (AddConsoleExtension) makes installing new commands to the console from NuGet packages super easy. You install it by typing the following:

NuGet.exe Install /ExcludeVersion /OutputDir %LocalAppData%\NuGet\Commands AddConsoleExtension

From then on, adding a new extension is as easy as

nuget.exe addExtension packageName

nuget addExtension nuget.copy.extension

Distribute Your Extension as a NuGet Package And Enjoy Your New Custom Command

When I created my package, I noted in the nuspec description that Nuget Extend should be installed first.  I also added the tag ConsoleExtension since that is what NuGet Extend used. All I package up is the DLL. The nuspec has ZERO dependencies specified (I do not include that I have a dependency on NuGet.CommandLine). I don’t need to include the nuget dependency since that will be resolved when the extension is run with nuget.exe.

Now that you have your custom command, enjoy life a little easier. And tell others about it.

nuget copy castle.windsor -destination awesome

NuGet Copy Extension is the extension I wrote that pulls packages from one source and pushes them to another. If you are a company that wants to house packages in a company feed and perhaps shut off the official feed for the rest of your developers, then this command may come in very handy.

Have Fun!