Using C# to Create PowerShell Cmdlets: Beyond the Basics

It is just the first stage to make your C# Cmdlet do what it is supposed to do. Even though cmdlets are used at the commandline, they need a whole range of features to make life easier for the end user. These include such refinements as providing documentation, validating inputs, providing a manifest, and implementing the common parameters.

Contents

  • Recap
  • Step 16: Document your cmdlet
  • Step 17: Use a module manifest
  • Step 18: Enforcing required vs optional inputs
  • Step 19: Untainting user input
  • Step 20: Honoring common parameters

    In the first installment (Using C# to Create PowerShell Cmdlets: The Basics), you learned how to put together a complete, working PowerShell cmdlet. But that was really just the fundamentals. There are more things you should be aware of in constructing new, professional-quality cmdlets; this article covers the next 5 steps in this endeavor.

    Recap

    Let’s first summarize the steps from the last article:

    1. Create a Visual Studio class library project to house your cmdlets.
    2. Add a reference to .NET PowerShell resources using the NuGet Package Manager.
    3. Rename the default class in your new project to <verb><noun>Cmdlet.cs.
    4. Update that class to inherit from System.Management.Automation.Cmdlet (or PSCmdlet if needed).
    5. Decorate your class with CmdletAttribute to specify the exposed verb and noun for the API.
    6. Create another class that is a container for your cmdlet’s output.
    7. Decorate your cmdlet class with OutputTypeAttribute to expose the output container for the API (supporting, e.g. tab completion of your cmdlet’s parameters).
    8. Create public properties to be used for your cmdlet’s inputs.
    9. Decorate each input with ParameterAttribute to expose it to the API.
    10. Specify ValueFromPipeline if you want a single parameter to accept an entire object fed through the pipeline.
    11. Specify ValueFromPipelineByPropertyName for each parameter that you want to accept a property of the object fed through the pipeline.
    12. Add symmetry to the input accepting an entire object-so that it can accept multiple items not just through the pipeline but also as a direct command-line parameter-by making that parameter an array.
    13. Allow your parameters to be used positionally (i.e. without explicit parameter names) by specifying Position to the parameter.
    14. Provide aliases to your parameter names with the AliasAttribute.
    15. Write some production code.

    In this article I will add several additional, important steps. Let’s start with the first new step, number 16, next.

    Step 16: Document your cmdlet

    I mentioned documentation in passing in step 11 in the first installment, but I am making it explicit-and putting it as the first additional step here-because it is so important. In the Agile world, we are taught to eschew documentation. Well, perhaps not entirely, but to consider it less valuable than the visible parts of the working system you are delivering. But advocating documentation here so prominently is not at all going against that grain, because the documentation of a PowerShell cmdlet is an integral part of its deliverable. There is no GUI per se when you’re typing in a PowerShell window. There’s no arrow to click to expand a list, no button to press and no slider to slide. Unlike a well-designed GUI, you cannot as a rule work how to use a PowerShell cmdlet merely by intuition; it must be properly documented so that you can quickly look up what the cmdlet does, what any particular parameter does, what kind of output it generates, what side effects it produces, and so forth.

    When you write C# code, there is a standard that prescribes exactly how to instrument your code with documentation comments so that when you build in Visual Studio it will generate a proper MAML file that can then be used to generate user documentation, typically done with Sandcastle and SandcastleHelpFileBuilder. (See, for example, Taming Sandcastle: A .NET Programmer’s Guide to Documenting Your Code.) When it comes to PowerShell code, on the other hand, there is no single set-in-stone standard, so the way that you document your cmdlets will vary depending on what tools you plan to use to post-process. However, I suggest that there should be a standard because, at the time of writing, there is a single utility that makes documenting your PowerShell cmdlets written in C# as easy and as simple as documenting your other C# code. This utility is XmlDoc2CmdletDoc, and I wrote an extensive guide on how to use it effectively-see Documenting Your PowerShell Binary Cmdlets. With XmlDoc2CmdletDoc you instrument your cmdlet classes using inline documentation comments. (Most other current tools do not support that.) Furthermore, you use a doc-comment grammar quite similar to the standard grammar you use for other C# classes.

    Step 17: Use a module manifest

    Probably the next most critical piece you need for production-ready PowerShell is a manifest file. As the Wikipedia article explains, a manifest typically enumerates metadata for an accompanying set of files. Mapping this to a PowerShell context, the manifest specifies the contents and attributes of your PowerShell module, its prerequisites, its dependencies, and how its components are processed. The manifest file in PowerShell is an XML file with a .psd1 extension. The file contains a single hash table containing quite a number of parameters. However, you can easily generate a default manifest by specifying just a single parameter to New-ModuleManifest:

    You then need to open that file in a text editor and make a couple of updates.

    • Change the RootModule to the name of your compiled library file, e.g. your-module-name.dll.
    • Update the author, company name, and copyright info as your company guidelines dictate.
    • Update the description to give a synopsis of what your module does.
    • Update the RequiredAssemblies list to include any DLLs that your module depends on.
    • Update the FileList list to include all the files that you wish to ship with the module (this would include at a minimum your compiled library file and its accompanying help file (your-module-name.dll and your-module-name.dll-Help.xml)

    For more details about the values that go into that hash, see the documentation for the cmdlet itself, New-ModuleManifest, in combination with the MSDN article, How to Write a Module Manifest.

    Though in the previous article I showed how to import the module using the DLL directly, the sample project already included the corresponding manifest. So whenever you have a manifest, which should be always, you should import the module using the manifest rather than the DLL, to wit:

    For this simple project it does not make any visible difference in executing the cmdlet. However, you can see the difference internally if you run this command after importing the module first with the DLL, then with the manifest:

    Step 18: Enforcing required vs optional inputs

    Step 9 introduced the ParameterAttribute to those public properties that are part of your cmdlet’s API. Steps 10, 11, and 13 added several properties for particular behaviors. (You can see the list of all available properties on MSDN.) Here you will learn one more property to require a parameter to be entered by the user, the Mandatory property. If the user does not specify a value for a parameter adorned with this property, PowerShell will prompt the user to enter a value. And if he or she still refuses-by just hitting enter-the cmdlet throws a terminating error. As an example, if you make the Name parameter mandatory…

    … and then fail to provide -Name on the command line, this is the error:

    While accurate, that message is less than ideal; something like “Required ‘Name’ parameter was not supplied” would be a bit more clear. However, that does bring to light what Mandatory really means-which is not even spelled out fully in the official documentation!-and that is that the parameter must be present, non-null, and non-empty. Thus, these will all generate the same error:

    Be warned, though, that you could circumvent that security blanket rather easily by providing just a single space:

    So you need to be aware that the Mandatory property, while useful, is not quite as comprehensive as one always needs.

    Step 19: Untainting user input

    Similar to making arguments ‘required’, you should typically make sure that any parameters that are supplied are valid and/or safe. The latter is referred to as untainting a value (see Taint checking for more) but I am lumping both together under the “untainting” heading because they are treated the same in PowerShell.

    PowerShell provides several attributes for validating inputs (Cmdlet Attributes). First, a few examples taken from the sample project, showing how you might use them.

    All the validation attributes are listed below along with the resulting error messages presented to the user if invalid inputs are given. To use any one of these, just decorate the parameter of interest with the attribute, as shown above. In each of the error messages, I have just generically used X to represent your parameter name and J to represent the user’s input.

    ValidateCount(I1,I2) – enforces the number of values allowed for an array parameter

    Use this when you want to restrict the number of items to be supplied to a single array parameter. If the user exceeds those values when using your cmdlet, the result is either of these messages, as appropriate.

    — or —

    ValidateLength(I1,I2) – enforces a minimum/maximum length of a for a parameter

    Use this to restrict the length of a single parameter. Possible failure messages:

    — or —

    ValidateRange(N1,N2) – enforces a minimum/maximum value for a parameter

    Use this to restrict the value of a single numerical parameter. Possible failure messages:

    — or —

    ValidateSet(V1,V2,… Vn) – limits values of a parameter to a specific set of values

    Use this to restrict the value of a single string parameter, where it has a small set of possible values. If the supplied argument does not match one of the supplied value, it results in this error message:

    Cannot validate argument on parameter X. The argument J does not belong to the set V1, V2, V3, … Vn specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.

    Notes on the Validate attributes

    At first glance, those error messages above look reasonable, conveying what is wrong so the user can address it. If you look closer, though, there are several issues with the text of those messages. Here they are again, with my corrections for accuracy, grammar, and consistency in place:

    2443-img2C.jpg

    And then there’s ValidatePattern, discussed next.

    ValidatePattern – enforces that an input matches a given regular expression

    If the user provides an input that does not match the pattern supplied to ValidatePattern, then the error message is, well, intractable for most users. For this example I specified a regular expression that recognizes one format of a US phone number.

    Woe betide the user who enters it wrong and is presented with this:

    But PowerShell is nothing if not flexible. Joel Bennett wrote a blog post some time back on this very topic-that’s where I got the phone number example from, in fact. So instead of using ValidatePattern use a custom validator-ValidatePatternFriendly is my streamlined and updated version of his ValidatePatternEx:

    Upon transgressing, that will produce a much more palatable error for the typical user, along with the technical info for the support folks:

    I have included the ValidatePatternFriendly custom validator in the sample solution attached to this article; you are free to use that in production code. (Bennett’s original post, Better error messages for PowerShell ValidatePattern, regrettably, is no longer available, but last I checked you could retrieve it from the magical WayBack Machine; search for “http://huddledmasses.org/better-error-messages-for-powershell-validatepattern/”.)

    For convenience here is the code for the custom validator:

    Side note: If writing in PowerShell rather than C#, this type of custom validation can be done more simply and on-the-fly; there’s no need to write a custom validator, just use the ValidateScriptAttribute with whatever custom logic you need. Here’s the equivalent in PowerShell for the phone number validation:

    Also see How to Validate Parameter Input for more details.

    Summary of Validate attributes

    The following table (based on the information provided in System.Management.Automation Namespace but reformatted and condensed) summarizes the attributes mentioned above, plus includes a few remaining ones not previously discussed.

    2443-img66.jpg

    And here is the inheritance tree for the above list:

    • ValidateArgumentsAttribute
      • ValidateCountAttribute
      • ValidateDriveAttribute
        • ValidateUserDriveAttribute
      • ValidateEnumeratedArgumentsAttribute
        • ValidateLengthAttribute
        • ValidatePatternAttribute
        • ValidateRangeAttribute
        • ValidateScriptAttribute
        • ValidateSetAttribute
      • ValidateNotNullAttribute
      • ValidateNotNullOrEmptyAttribute

    Note that several of those you have seen inherit from ValidateEnumeratedArgumentsAttribute, which automatically provides support for the user entering either a single value or an array of values for a given parameter. Similarly, the custom ValidatePatternFriendlyAttribute does aw well. Thus, while this would clearly evaluate the single phone number against the regular expression specified for the parameter…

    …you can supply multiple values-because USPhoneNumber is an array parameter-and each of them will individually be validated by the regular expression.

    (Note that if there are multiple invalid values in the passed array, the reported exception only describes the first one, since it aborts at that point in any case.)

    Step 20: Honoring common parameters

    There are several command-line switches that you can specify with any standard PowerShell cmdlet. As an author of custom PowerShell cmdlets, you should provide support for at least some of these in your cmdlets as well. You can see the full list at about_CommonParameters. This section explains how to deal with a subset of these-those that are more commonly used-and that affect output and workflow: -Verbose , -Debug , –WhatIf, and -Confirm .

    -Verbose

    In PowerShell you can output extra information on demand to PowerShell’s verbose data stream. Typically this just comes out on the console in a different color.

    To support -Verbose you need to:

    • enable v erbose output functionality, and
    • emit messages to the verbose stream.

    When writing cmdlets in PowerShell (as opposed to C#), you need to make sure that a function has the CmdletBindingAttribute . The presence of that attribute is what enables verbose output. When writing in C#, on the other hand, any cmdlet you write must have the CmdletAttribute -you don’t have a choice, so in essence all your cmdlets automatically have verbose output enabled. (That’s not a typo; you use CmdletBinding in PowerShell, but just Cmdlet in C#.)

    To emit messages on the verbose stream once enabled, you simply use the Write-Verbose cmdlet (PowerShell) or WriteVerbose method (C#) to send output to the verbose stream instead of the standard output stream (Write-Output or WriteObject, respectively).

    -Debug

    Debug output-extra output on PowerShell’s debug data stream-is almost identical to verbose output. To support -Debug you need to:

    • enable debug output functionality, and
    • emit messages to the debug stream.

    You enable this, just like -Verbose , with CmdletBindingAttribute (PowerShell) or CmdletAttribute (C#), then you emit messages with the Write-Debug cmdlet (PowerShell) or WriteDebug method (C#).

    A distinction of debug output, however, makes it serve quite a different purpose than -Verbose . The thing about the debug output statement (Write-Debug or WriteDebug) is that it besides producing additional output it also acts as a breakpoint after emitting your debug message! That is, it pauses your cmdlet-whether written in PowerShell or in C#-with a prompt similar to what -Confirm does (which you will see below). Consider this simple PowerShell cmdlet:

    Here is the default output:

    And here is what happens when you add debugging:

    Here, while your cmdlet is paused, you can examine the state of any external resources that may have been modified, or you can suspend and get back to a PowerShell prompt to dig further right here.

    (I noticed as I was putting this together that, at least in PowerShell 5, -Debug had one slight quirk, and this only applies to cmdlets written in C# (not in PowerShell). Adding -Debug also seemed to enable -Verbose automatically.)

    WhatIf

    The –WhatIf parameter is a safety net for cmdlets that may make substantial changes to your system (read: do damage). It let’s you do a dry run, to see an overview of what would happen, before you run the real thing.

    To support – WhatIf you need to:

    • enable WhatIf functionality, and
    • gate appropriate code blocks .

    Enable –WhatIf with CmdletBindingAttribute (PowerShell) or CmdletAttribute (C#) and include the SupportsShouldProcess argument. Here’s an example from the sample project:

    You then need to identify one or more blocks in your code that you wish to protect. Surround each block with a condition that is gated with the result of the Cmdlet.ShouldProcess method. Thus, if you have a block like this…

    … change it to this:

    Each occurrence of the ShouldProcess method will emit an informational message to the user. ShouldProcess actually comes with several different signatures, giving you quite a bit of flexibility as to how the message will appear. Here are the variations, applied to the sample project:

    I used “don’t care” for the second and third arguments because they are not used by –WhatIf. However, they are used with -Confirm , as you will see next, so you should be sure to use real phrases, not “don’t care”!

    There is actually a fourth signature for ShouldProcess as well, which you could read about on the MSDN documentation page here, but it has little utility.

    -Confirm

    The -Confirm parameter is also a safety net for cmdlets that may make substantial changes to your system. For each significant change your code will perform it lets the user choose to proceed or skip it.

    To support – Confirm you need to:

    • enable Confirm functionality, and
    • gate appropriate code blocks .

    Enable –Whatif with CmdletBindingAttribute (PowerShell) or CmdletAttribute (C#) and include the SupportsShouldProcess argument. Here’s an example from the sample project:

    You then need to identify one or more blocks in your code that you wish to protect. These need to be exactly the same code blocks that you want to protect with –WhatIf because ShouldProcess handles support for both of those simultaneously.

    Each occurrence of the ShouldProcess method will prompt the user for an action. ShouldProcess actually comes with several different signatures, giving you quite a bit of flexibility as to how the prompt will appear. Here are the variations, applied to the sample project:

    Notes on WhatIf and Confirm

    Many things are often a bit simpler when writing cmdlets in PowerShell as compared to C#. However, ShouldProcess is quite the reverse; here’s why. In PowerShell, your ShouldProcess calls can be gating other cmdlets which may in turn support WhatIf and Confirm. So you need to think about whether you want those wrapped cmdlets to also prompt or whether assenting to your cmdlet should automatically assent to its wrapped calls, then pass on appropriate values of WhatIf and Confirm switches. In C#, however, you’re just calling other C# methods, no cmdlets, so you don’t have that issue at all.

    Microsoft advocates always combining ShouldProcess with a call to ShouldContinue and the introduction of an additional Force parameter to override it. Their recommendation seems rather unwieldy, though. Why should you have to add a -Force to avoid getting a second prompt if you’ve already added a -Confirm ? I have not found a good reason to use the pattern they suggest, but you should decide for yourself-see How to Request Confirmations.

    Conclusion

    PowerShell gives you unprecedented command-line power to carry out systems operations. With this article and its predecessor, I hope to convey that it is reasonably straightforward, with some diligence to create your own custom cmdlets to augment that power even further. There are a lot of articles out there that reveal bits and pieces of the process. My goal here is to collect it all in one place so you have a single recipe/checklist to help you generate your cmdlets quickly and efficiently.

Downloads

  • 6369 views

  • Rate
    [Total: 4    Average: 4.5/5]
  • himby bimbu

    Hello, do you have any tips on using PSCredential in C#?