Writing a NuGet Package That Adds A Command To The PowerShell Console

code, open source, nuget 0 comments suggest edit

The Magic 8-ball toy is a toy usually good for maybe one or two laughs before it quickly gets boring. Even so, some have been known to make all their important life/strategic decisions using it, or an equivalent mechanism.

The way the toy works is you ask it a question, shake it, and the answer to your question appears in a little viewport. What you’re seeing is one side of an icosahedron (20-sided polyhedron, or for you D&D folks, a d20). On each face of the d20 is a potential answer to your yes or no question.

magic-8-ball

I thought it would be fun to write a NuGet package that emulates this toy as one of my demos for the NuGet talk at Mix11. Yes, I am odd when it comes to defining what I think is fun. When you install the package, it adds a new command to the Package Manager Console.

The command I wrote didn’t have twenty possible answers, because I was lazy, but it followed the same general format. This command also includes support for tab expansions, which feel a lot like Intellisense.

The following screenshot shows an example of this new command, Get-Answer, in use. Note that when you hit tab after typing the command, you can see a tab expansion suggesting a set of questions. It’s important to note that unlike Intellisense, you are free to ignore the tab expansion here and type in any question you want.

magic-eight-ball

In this blog post, I will walk through how I wrote and packaged that command. I must warn you, I’m no PowerShell expert. I wrote this as a learning experience with the help of other PS experts.

The first thing to do is write an init.ps1 file. As described in the NuGet documentation for creating a package on CodePlex:

Init.ps1 runs the first time a package is installed in a solution. If the same package is installed into additional projects in the solution, the script is not run during those installations. The script also runs every time the solution is opened. For example, if you install a package, close Visual Studio, and then start Visual Studio and open the solution, the Init.ps1script runs again.

This script is useful for packages that need to add commands to the console because they’ll run each time the solution is opened. Here’s what my init.ps1 file looks like:

param($installPath, $toolsPath, $package)

Import-Module (Join-Path $toolsPath MagicEightBall.psm1)

The first line declares the set of parameters to the script. These are the parameters that NuGet will pass into the init.ps1 script (note that install.ps1, a different script that can be included in NuGet packages, receives a fourth $project parameter).

  • $installPath is the path to your package install
  • $toolsPath is the path to the tools directory under the package
  • $package is a reference to your package

The second line of the script is used to import a PowerShell module. In this case, we specify a script named MagicEightBall.psm1 by its full path. We could write the entire script here in init.ps1, but I’ve been told it’s good form to simply write scripts as modules and then import them via init.ps1and I have no reason to not believe my source. I suppose init.ps1 could also import multiple modules rather than one.

Let’s look at the code for MagicEightBall.psm1. It’s pretty brief!

$answers =  "As I see it, yes", 
            "Reply hazy, try again", 
            "Outlook not so good"

function Get-Answer($question) {
    $answers | Get-Random
}

Register-TabExpansion 'Get-Answer' @{
    'question' = { 
        "Is this my lucky day?",
        "Will it rain tonight?",
        "Do I watch too much TV?"
    }
}

Export-ModuleMember Get-Answer

The first line of code simply declares an array of answers. The real Magic Eight Ball has 20 in all, so feel free to add them all there.

I then define a function named Get-Answer. The implementation demonstrates one of the cool things I like about Powershell. I can simply pipe it into the Get-Random method and it returns a random answer from the array.

Skipping to the end, the last line of code calls Export-Module on this function, which makes it available in the Package Manager Console.

So what about that middle bit of code that calls Register-TabExpansion? Glad you asked. That function provides the Intellisense-like behavior for our function by registering a tab expansion.

It takes two parameters, the first is the name of the function, in this case Get-Answer. The second is a dictionary where the keys are the names of the parameters of the function, and the values contain an array of expansion options for that function. Since are function only has one parameter named question, we add 'question' as the key to the dictionary and supply an array of potential questions as the value.

With these two files in place, I simply opened up Package Explorer and selected File > New from the menu to start a new package and dragged both of the script files into the Package contentswindow. NuGet recognized the files as being PowerShell scripts and offered to put them in the Tools folder.

I then selected Edit > Edit Package Metadata from the menu to enter the NuSpec metadata for the package and clicked OK at the bottom.

magic-eight-ball-pkg

With all that done, I selected the File > Save As… menu to save the package on disk so I could test it out. Once I was done testing, I selected File > Publish to publish the package to the real NuGet feed.

It’s really that simple to write a package that adds a command to the Package Manager console complete with tab expansions.

In a future blog post, I’ll write about how I wrote MoodSwings, a package that can automate Visual Studio from within the Package Manager Console. If you have the NuGet Package Manager Console open, you can try out this package by running the command:

Install-Package MagicEightBall
Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

17 responses

  1. Avatar for TweeZz
    TweeZz April 19th, 2011

    Is it possible you made a small mistake? :)
    "one of the cool things I like about Razor. I can simply pipe it into the Get-Random method and it returns a random answer from the array"
    Razor? :) Didn't you mean PowerShell?

  2. Avatar for haacked
    haacked April 19th, 2011

    Indeed! I corrected it. Thanks. :)

  3. Avatar for tugberk
    tugberk April 19th, 2011

    sir,
    I am currently on windows vista sp2 machine and I am using vs2010 pro with sp1. also latest version of nuget is installed.
    my problem is that; when I write install-package and hit space and write, for exaple, el on PMC, I am hitting tab button but I am not getting any intelisense. I am having this issue for long time but actually at the beginning it was working 2 or 3 months ago.
    can u help me on this?

  4. Avatar for James
    James April 19th, 2011

    I'm pretty sure the Magic 8 Ball only has an 8 sided die in it, not a d20 ;-)

  5. Avatar for haacked
    haacked April 20th, 2011

    @James not according to the wikipedia article


    Each of the 20 faces of the die has an affirmative, negative, or non-committal statement printed on it in raised letters.


    Check out the Amazon.com description near the bottom where it has the Product Description.


    The Magic 8 Ball has All the Answers. Ask a Question. Turn Over for the Answer. 20 Possible Answers. Made in China.


    I think the rip-offs you see given out at conferences only have 8 answers.

  6. Avatar for haacked
    haacked April 20th, 2011

    @tugberk can you run fiddler and see what's happening over the wire?

  7. Avatar for tugberk
    tugberk April 20th, 2011

    @tugberk can you run fiddler and see what's happening over the wire?


    @haacked nothing is happening on the wire :S here is the screenshot of the fiddler and PMC when I hit the tab.
    http://www.flickr.com/photos/tugberk/5639478519/in/photostream

  8. Avatar for tugberk
    tugberk April 20th, 2011

    hmm, here is another strange thing;
    fiddler cannot catch anything going on at the PMC. here is the screenshot of PMC and Fiddler after I installed Elmah;
    www.flickr.com/.../photostream
    butt, now I realized that fiddler is only processing Web Browser requests and responses. here is the screenshot of the fiddler and PMC when I installed Elmah;
    www.flickr.com/.../photostream
    but the sad thing is it doesn't change the first image that I uploaded. it still says there is nothing going on on the wire when I hit tab :S

  9. Avatar for haacked
    haacked April 21st, 2011

    What does $host tell you?
    Just type that into the console.

  10. Avatar for tugberk
    tugberk April 21st, 2011

    @haacked
    here how it looks like after $host thing on PMC;
    www.flickr.com/.../photostream
    and should I keep writing here or move this to nuget.codeplex.com discussions tab?

  11. Avatar for haacked
    haacked April 21st, 2011

    Yes, best to take the discussion to our CodePlex page. I'm on vacation :)

  12. Avatar for tugberk
    tugberk April 22nd, 2011

    @haacked hmmm, then let's ask more questions to you :D kidding :) wish you a happy vocation :)

  13. Avatar for grinder1243
    grinder1243 May 12th, 2011

    Do you mind if I quote a couple of your articles as long as I provide credit and sources back to your weblog? My website is in the exact same area of interest as yours and my visitors would truly benefit from a lot of the information you present here. Please let me know if this alright with you. Appreciate it!Thanks for your hard work!This was my google

  14. Avatar for Chris Ortman
    Chris Ortman May 16th, 2011

    One thing I've noticed is that development / testing of your powershell scripts is easier if you just put them straight into the init.ps1 instead of going the module route.
    The commands i am adding use the $installPath and $toolsPath variables so this lets them access it easier. Also i have a hard time getting it to pick up my changes when i use modules, even if i use Remove-Module i still sometimes need to completely restart VS to get them noticed.

  15. Avatar for James Gaisford
    James Gaisford August 4th, 2011

    Awesome post. Really simple to follow and our internal NuGet repository now has a package with it's very own command!
    I've got the bug and am now trying to create a simple command that creates a folder to publish files to. I would like to offer an "intellisense" option as one of the params to the command that inspects the publish profiles of the VS solution and uses values from there as the intellisense options. Is something like this possible?

  16. Avatar for haacked
    haacked August 5th, 2011

    @James yeah, just use the Register-TabExpansion function. Post this question to http://nuget.codeplex.com/discussions to get more help.

  17. Avatar for William_D99
    William_D99 April 23rd, 2017

    You might want to share the actual nupkg file that would help.