{"id":54562,"date":"2016-07-22T00:00:00","date_gmt":"2016-07-22T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/using-c-to-create-powershell-cmdlets-beyond-the-basics\/"},"modified":"2021-07-29T19:44:19","modified_gmt":"2021-07-29T19:44:19","slug":"using-c-to-create-powershell-cmdlets-beyond-the-basics","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/using-c-to-create-powershell-cmdlets-beyond-the-basics\/","title":{"rendered":"Using C# to Create PowerShell Cmdlets: Beyond the Basics"},"content":{"rendered":"<div class=\"article-content\">\n<ul class=\"series-articles\">\n<li>Part 1: <a href=\"https:\/\/www.simple-talk.com\/sysadmin\/powershell\/how-to-document-your-powershell-library\/\">How to Document your PowerShell Library<\/a><\/li>\n<li>Part 2: <a href=\"https:\/\/www.simple-talk.com\/dotnet\/development\/using-c-to-create-powershell-cmdlets-the-basics\/\">Using C# to Create PowerShell Cmdlets: The Basics<\/a><\/li>\n<li>Part 3: <a href=\"https:\/\/www.simple-talk.com\/dotnet\/software-tools\/documenting-your-powershell-binary-cmdlets\/\">Documenting Your PowerShell Binary Cmdlets<\/a><\/li>\n<li>Part 4: <a href=\"https:\/\/www.simple-talk.com\/sysadmin\/powershell\/unified-approach-to-generating-documentation-for-powershell-cmdlets\/\">Unified Approach to Generating Documentation for PowerShell Cmdlets<\/a><\/li>\n<li class=\"series-articles--active\">Part 5: Using C# to Create PowerShell Cmdlets: Beyond the Basics<\/li>\n<\/ul>\n<h1>Contents<\/h1>\n<ul>\n<li><a href=\"#first\">Recap<\/a><\/li>\n<li><a href=\"#second\">Step 16: Document your cmdlet<\/a><\/li>\n<li><a href=\"#third\">Step 17: Use a module manifest<\/a><\/li>\n<li><a href=\"#fourth\">Step 18: Enforcing required vs optional inputs<\/a><\/li>\n<li><a href=\"#fifth\">Step 19: Untainting user input<\/a>\n<ul>\n<li><a href=\"#sixth\">ValidateCount(I1,I2) &#8211; enforces the number of values allowed for an array parameter<\/a><\/li>\n<li><a href=\"#seventh\">ValidateLength(I1,I2) &#8211; enforces a minimum\/maximum length of a for a parameter<\/a><\/li>\n<li><a href=\"#eighth\">ValidateRange(N1,N2) &#8211; enforces a minimum\/maximum value for a parameter<\/a><\/li>\n<li><a href=\"#ninth\">ValidateSet(V1,V2,&#8230; Vn) &#8211; limits values of a parameter to a specific set of values<\/a><\/li>\n<li><a href=\"#tenth\">Notes on the Validate attributes<\/a><\/li>\n<li><a href=\"#eleventh\">ValidatePattern &#8211; enforces that an input matches a given regular expression<\/a><\/li>\n<li><a href=\"#twelveth\">Summary of Validate attributes<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#thirteenth\">Step 20: Honoring common parameters<\/a>\n<ul>\n<li><a href=\"#fourteenth\">-Verbose<\/a><\/li>\n<li><a href=\"#fifteenth\">-Debug<\/a><\/li>\n<li><a href=\"#sixteenth\">-WhatIf<\/a><\/li>\n<li><a href=\"#seventeenth\">-Confirm<\/a><\/li>\n<li><a href=\"#eighteenth\">Notes on WhatIf and Confirm<\/a><\/li>\n<\/ul>\n<p>In the first installment (<a href=\"https:\/\/www.simple-talk.com\/dotnet\/development\/using-c-to-create-powershell-cmdlets-the-basics\/\">Using C# to Create PowerShell Cmdlets: The Basics<\/a>), 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.<\/p>\n<h2 id=\"first\">Recap<\/h2>\n<p>Let&#8217;s first summarize the steps from the last article:<\/p>\n<ol>\n<li>Create a Visual Studio class library project to house your cmdlets.<\/li>\n<li>Add a reference to .NET PowerShell resources using the NuGet Package Manager.<\/li>\n<li>Rename the default class in your new project to &lt;verb&gt;&lt;noun&gt;<code>Cmdlet.cs.<\/code><\/li>\n<li>Update that class to inherit from <code>System.Management.Automation.Cmdlet<\/code> (or PSCmdlet if needed).<\/li>\n<li>Decorate your class with <code>CmdletAttribute<\/code> to specify the exposed verb and noun for the API.<\/li>\n<li>Create another class that is a container for your cmdlet&#8217;s output.<\/li>\n<li>Decorate your cmdlet class with<code> OutputTypeAttribute <\/code>to expose the output container for the API (supporting, e.g. tab completion of your cmdlet&#8217;s parameters).<\/li>\n<li>Create public properties to be used for your cmdlet&#8217;s inputs.<\/li>\n<li>Decorate each input with<code> ParameterAttribute<\/code> to expose it to the API.<\/li>\n<li>Specify <code>ValueFromPipeline<\/code> if you want a single parameter to accept an entire object fed through the pipeline.<\/li>\n<li>Specify<code> ValueFromPipelineByPropertyName<\/code> for each parameter that you want to accept a <em>property<\/em> of the object fed through the pipeline.<\/li>\n<li>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.<\/li>\n<li>Allow your parameters to be used positionally (i.e. without explicit parameter names) by specifying Position to the parameter.<\/li>\n<li>Provide aliases to your parameter names with the <code>AliasAttribut<\/code>e.<\/li>\n<li>Write some production code.<\/li>\n<\/ol>\n<p>In this article I will add several additional, important steps. Let&#8217;s start with the first new step, number 16, next.<\/p>\n<h2 id=\"second\">Step 16: Document your cmdlet<\/h2>\n<p>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 <em>per <\/em> <em>se<\/em> when you&#8217;re typing in a PowerShell window. There&#8217;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.<\/p>\n<p>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 <code>SandcastleHelpFileBuilder<\/code>. (See, for example, <a href=\"https:\/\/www.simple-talk.com\/dotnet\/.net-tools\/taming-sandcastle-a-.net-programmers-guide-to-documenting-your-code\/\">Taming Sandcastle: A .NET Programmer&#8217;s Guide to Documenting Your Code<\/a>.) 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 <em>should<\/em> 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 <span class=\"user-instruction\"> <strong>XmlDoc2CmdletDoc<\/strong><\/span>, and I wrote an extensive guide on how to use it effectively-see <a href=\"https:\/\/www.simple-talk.com\/dotnet\/software-tools\/documenting-your-powershell-binary-cmdlets\/\">Documenting Your PowerShell Binary Cmdlets<\/a>. With <code>XmlDoc2CmdletDoc<\/code> 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.<\/p>\n<h2 id=\"third\">Step 17: Use a module manifest<\/h2>\n<p>Probably the next most critical piece you need for production-ready PowerShell is a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Manifest_file\">manifest file<\/a>. 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 <code>New-ModuleManifest:<\/code><\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; New-ModuleManifest c:\\path\\to\\your\\module\\your-module-name.psd1<\/pre>\n<p>You then need to open that file in a text editor and make a couple of updates.<\/p>\n<ul>\n<li>Change the <code>RootModule<\/code> to the name of your compiled library file, e.g. <em>your-module-name<\/em>.dll.<\/li>\n<li>Update the author, company name, and copyright info as your company guidelines dictate.<\/li>\n<li>Update the description to give a synopsis of what your module does.<\/li>\n<li>Update th<code>e RequiredAssemblies<\/code> list to include any DLLs that your module depends on.<\/li>\n<li>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 (<em>your-module-name<\/em>.dll and <em>your-module-name<\/em>.dll-Help.xml)<\/li>\n<\/ul>\n<p>For more details about the values that go into that hash, see the documentation for the cmdlet itself, <code>New-ModuleManifest<\/code>, in combination with the MSDN article, <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/dd878297(VS.85).aspx\">How to Write a Module Manifest<\/a>.<\/p>\n<p>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:<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; Import-Module .\\bin\\Debug\\PowerShellModuleInCSharp.psd1 -Force<\/pre>\n<p>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:<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; Get-Module PowerShellModuleInCSharp | Format-List -force *<\/pre>\n<h2 id=\"fourth\">Step 18: Enforcing required vs optional inputs<\/h2>\n<p>Step 9 introduced the ParameterAttribute to those public properties that are part of your cmdlet&#8217;s API. Steps 10, 11, and 13 added several properties for particular behaviors. (You can see the list of all available properties on <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/system.management.automation.parameterattribute_properties(v=vs.85).aspx\">MSDN<\/a>.) Here you will learn one more property to require a parameter to be entered by the user, the<code> Mandatory<\/code> 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 <code>Name<\/code> parameter mandatory&#8230;<\/p>\n<pre class=\"theme:powershell-ise lang:ps mark:1\">[Parameter(Position=1, ValueFromPipelineByPropertyName=true,  Mandatory=true)]\r\npublic  string Name {  get;  set; }\r\n<\/pre>\n<p>&#8230; and then fail to provide -Name on the command line, this is the error:<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">Get-NetworkAdapter :Cannot bind argument to parameter 'Name' because it is an empty string. <\/pre>\n<p>While accurate, that message is less than ideal; something like &#8220;Required &#8216;Name&#8217; parameter was not supplied&#8221; would be a bit more clear. However, that does bring to light what <code>Mandatory <\/code>really means-which is not even spelled out fully in the <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/system.management.automation.parameterattribute.mandatory(v=vs.85).aspx\">official documentation<\/a>!-and that is that the parameter must be present, non-null, and non-empty. Thus, these will all generate the same error:<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; Get-NetworkAdapter -name $null\r\nPS&gt; Get-NetworkAdapter -name \"\"\r\nPS&gt; Get-NetworkAdapter\r\n<\/pre>\n<p>Be warned, though, that you could circumvent that security blanket rather easily by providing just a single space:<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; Get-NetworkAdapter -name ' '<\/pre>\n<p>So you need to be aware that the<code> Mandatory<\/code> property, while useful, is not quite as comprehensive as one always needs.<\/p>\n<h2 id=\"fifth\">Step 19: Untainting user input<\/h2>\n<p>Similar to making arguments &#8216;required&#8217;, you should typically make sure that any parameters that are supplied are valid and\/or safe. The latter is referred to as <em>untainting<\/em> <em> a value<\/em> (see <a href=\"https:\/\/en.wikipedia.org\/wiki\/Taint_checking\">Taint checking<\/a> for more) but I am lumping both together under the &#8220;untainting&#8221; heading because they are treated the same in PowerShell.<\/p>\n<p>PowerShell provides several attributes for validating inputs (<a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms714836(v=vs.85).aspx\">Cmdlet Attributes<\/a>). First, a few examples taken from the sample project, showing how you might use them.<\/p>\n<pre class=\"theme:powershell-ise lang:ps mark:2,3,9,13 decode:true \">[Parameter(Position=1,ValueFromPipelineByPropertyName =  true)]\r\n[ValidateLength(1, 50)]\r\n[ValidateSet(\"Intel\",\"WAN\",\"Cisco\",\"Bluetooth\")]\r\n public  string Name {  get;  set; }\r\n\r\n[Parameter(Position=0,\r\n    ValueFromPipelineByPropertyName =  true,ValueFromPipeline =  true)]\r\n[Alias(\"mfg\",  \"vendor\")]\r\n [ValidateCount(1,3)]\r\n public  string[] Manufacturer {  get;  set; }\r\n\r\n[Parameter(Position=3)]\r\n [ValidateRange(5, 150)]\r\n public  int MaxEntries {  get;  set; } = 100;\r\n<\/pre>\n<p>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 <strong>X<\/strong> to represent your parameter name and <strong>J<\/strong> to represent the user&#8217;s input.<\/p>\n<h3 id=\"sixth\" class=\"attribute\">ValidateCount(I1,I2) &#8211; enforces the number of values allowed for an array parameter<\/h3>\n<p>Use this when you want to restrict the <strong> <em>number of items<\/em> <\/strong> 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.<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">Cannot validate argument on parameter X. The number of provided arguments, J, exceeds the maximum number of allowed arguments I2. Provide fewer than I2 arguments, and then try the command again.\r\n<\/pre>\n<p>&#8212; or &#8212;<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">Cannot validate argument on parameter X. The number of provided arguments J is fewer than the minimum number of allowed arguments I1. Provide more than I1 arguments, and then try the command again.\r\n<\/pre>\n<h3 id=\"seventh\" class=\"attribute\">ValidateLength(I1,I2) &#8211; enforces a minimum\/maximum length of a for a parameter<\/h3>\n<p>Use this to restrict the <strong> <em>length<\/em> <\/strong> of a single parameter. Possible failure messages:<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">Cannot validate argument on parameter X. The character length of the J argument is too long. Shorten the character length of the argument so it is fewer than or equal to I2 characters, and then try the command again.\r\n<\/pre>\n<p>&#8212; or &#8212;<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">Cannot validate argument on parameter X. The character length J of the argument is too short. Specify an argument with a length that is greater than or equal to I1, and then try the command again.\r\n<\/pre>\n<h3 id=\"eighth\" class=\"attribute\">ValidateRange(N1,N2) &#8211; enforces a minimum\/maximum value for a parameter<\/h3>\n<p>Use this to restrict the <strong> <em>value <\/em> <\/strong>of a single numerical parameter. Possible failure messages:<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">Cannot validate argument on parameter X. The J argument is less than the minimum allowed range of N1. Supply an argument that is greater than or equal to N1 and then try the command again.\r\n<\/pre>\n<p>&#8212; or &#8212;<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">Cannot validate argument on parameter X. The J argument is greater than the maximum allowed range of N2. Supply an argument that is less than or equal to N2 and then try the command again.\r\n<\/pre>\n<h3 id=\"ninth\" class=\"attribute\">ValidateSet(V1,V2,&#8230; Vn) &#8211; limits values of a parameter to a specific set of values<\/h3>\n<p>Use this to restrict the <strong> <em>value <\/em> <\/strong>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:<\/p>\n<p>Cannot validate argument on parameter X. The argument J does not belong to the set V1, V2, V3, &#8230; Vn specified by the <code>ValidateSet<\/code> attribute. Supply an argument that is in the set and then try the command again.<\/p>\n<h4 id=\"tenth\">Notes on the Validate attributes<\/h4>\n<p>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:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2443-img2C.jpg\" alt=\"2443-img2C.jpg\" \/><\/p>\n<p>And then there&#8217;s ValidatePattern, discussed next.<\/p>\n<h3 id=\"eleventh\" class=\"attribute\">ValidatePattern &#8211; enforces that an input matches a given regular expression<\/h3>\n<p>If the user provides an input that does not match the pattern supplied to <code>ValidatePattern<\/code>, 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.<\/p>\n<pre class=\"theme:powershell-ise lang:ps\">[Parameter]\r\n[ValidatePattern(Pattern=@\"^\\(?\\d{3}\\)?[-\\s.]?\\d{3}[-\\s.]\\d{4}$\")]\r\npublic  string USPhoneNumber {  get;  set; }\r\n<\/pre>\n<p>Woe betide the user who enters it wrong and is presented with this:<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">Cannot validate argument on parameter 'USPhoneNumber'. The argument \"foo\" does not match the \"^\\(?\\d{3}\\)?[-\\s.]?\\d{3}[-\\s.]\\d{4}$\" pattern. Supply an argument that matches \"^\\(?\\d{3}\\)?[-\\s.]?\\d{3}[-\\s.]\\d{4}$\" and try the command again.<\/pre>\n<p>But PowerShell is nothing if not flexible. Joel Bennett wrote a blog post some time back on this very topic-that&#8217;s where I got the phone number example from, in fact. So instead of using <code>ValidatePattern <\/code>use a <em>custom<\/em> validator-<code>ValidatePatternFriendly<\/code> is my streamlined and updated version of his <code>ValidatePatternEx:<\/code><\/p>\n<pre class=\"theme:powershell-ise lang:ps\">[Parameter]\r\n[ValidatePatternFriendly(\r\n    Pattern =  @\"^\\(?\\d{3}\\)?[-\\s.]?\\d{3}[-\\s.]\\d{4}$\",\r\n    Message =  \"Please enter a 10-digit phone number like: (123) 555-1212\")]\r\npublic  string USPhoneNumber {  get;  set; }\r\n    \r\n<\/pre>\n<p>Upon transgressing, that will produce a much more palatable error for the typical user, along with the technical info for the support folks:<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">Cannot validate argument on parameter 'USPhoneNumber'. Please enter a 10-digit phone number like: (123) 555-1212ValidatePatternEx failure, the value didn't match the pattern: ^\\(?\\d{3}\\)?[-\\s.]?\\d{3}[-\\s.]\\d{4}$<\/pre>\n<p>I have included th<code>e ValidatePatternFriendly<\/code> custom validator in the sample solution attached to this article; you are free to use that in production code. (Bennett&#8217;s original post, <em>Better error messages for PowerShell <\/em> <em>ValidatePattern<\/em>, regrettably, is no longer available, but last I checked you could retrieve it from the magical <a href=\"https:\/\/web.archive.org\/\">WayBack Machine<\/a>; search for &#8220;http:\/\/huddledmasses.org\/better-error-messages-for-powershell-validatepattern\/&#8221;.)<\/p>\n<p>For convenience here is the code for the custom validator:<\/p>\n<pre class=\"theme:vs2012 lang:c# decode:true\">[AttributeUsage(AttributeTargets.Field |  AttributeTargets.Property)]\r\npublic  class  ValidatePatternFriendlyAttribute :  ValidateEnumeratedArgumentsAttribute\r\n{\r\n     private  string Name {  get; } =\r\n         nameof(ValidatePatternFriendlyAttribute).Replace(\"Attribute\",\"\");\r\n     public  RegexOptions Options {  get;  set; } =  RegexOptions.IgnoreCase;\r\n     public  string Message {  get;  set; }\r\n     public  string Pattern {  get;  set; }\r\n\r\n    protected  override  void ValidateElement(object element)\r\n    {\r\n         if (element ==  null)\r\n        {\r\n             throw  new  ValidationMetadataException(\r\n                 $\"{Message}\\n{Name} failure: argument is null\");\r\n        }\r\n         if (!new  Regex(Pattern, Options).Match(element.ToString()).Success)\r\n        {\r\n             throw  new  ValidationMetadataException(\r\n                $\"{Message}\\n{Name} failure, the value '{element}' does not match the pattern \/{Pattern}\/\");\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>Side note: If writing in PowerShell rather than C#, this type of custom validation can be done more simply and on-the-fly; there&#8217;s no need to write a custom validator, just use the<code> ValidateScriptAttribute <\/code>with whatever custom logic you need. Here&#8217;s the equivalent in PowerShell for the phone number validation:<\/p>\n<pre class=\"theme:powershell-ise lang:ps\">[ValidateScript(\r\n    { if($_ -notmatch '^\\(?\\d{3}\\)?[-\\s.]?\\d{3}[-\\s.]\\d{4}$') { \r\n        throw 'Please enter a 10-digit phone number like: (123) 555-1212'\r\n      } else { $true }\r\n   })] <\/pre>\n<p>Also see <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/dd878347(v=vs.85).aspx\">How to Validate Parameter Input<\/a> for more details.<\/p>\n<h3 id=\"twelveth\">Summary of Validate attributes<\/h3>\n<p>The following table (based on the information provided in <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/system.management.automation(v=vs.85).aspx\">System.Management.Automation Namespace<\/a> but reformatted and condensed) summarizes the attributes mentioned above, plus includes a few remaining ones not previously discussed.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2443-img66.jpg\" alt=\"2443-img66.jpg\" \/><\/p>\n<p>And here is the inheritance tree for the above list:<\/p>\n<ul>\n<li>ValidateArgumentsAttribute\n<ul>\n<li>ValidateCountAttribute<\/li>\n<li>ValidateDriveAttribute\n<ul>\n<li>ValidateUserDriveAttribute<\/li>\n<\/ul>\n<\/li>\n<li>ValidateEnumeratedArgumentsAttribute\n<ul>\n<li>ValidateLengthAttribute<\/li>\n<li>ValidatePatternAttribute<\/li>\n<li>ValidateRangeAttribute<\/li>\n<li>ValidateScriptAttribute<\/li>\n<li>ValidateSetAttribute<\/li>\n<\/ul>\n<\/li>\n<li>ValidateNotNullAttribute<\/li>\n<li>ValidateNotNullOrEmptyAttribute<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Note that several of those you have seen inherit from <code>ValidateEnumeratedArgumentsAttribute<\/code>, which automatically provides support for the user entering either a single value <em>or an array of values<\/em> for a given parameter. Similarly, the custom <code>ValidatePatternFriendlyAttribute <\/code>does aw well. Thus, while this would clearly evaluate the single phone number against the regular expression specified for the parameter&#8230;<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; Get-NetworkAdapter -USPhoneNumber '(123) 555-1212'<\/pre>\n<p>&#8230;you can supply multiple values-because USPhoneNumber is an array parameter-and each of them will individually be validated by the regular expression.<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; Get-NetworkAdapter -USPhoneNumber '(123) 555-1212','(123) 555-abcd'\r\nGet-NetworkAdapter : Cannot validate argument on parameter 'USPhoneNumber'. Please enter a 10-digit phone number like: (123) 555-1212\r\nValidatePatternFriendly failure, the value '(123) 555-abcd' does not match the pattern \/^\\(?\\d{3}\\)?[-\\s.]?\\d{3}[-\\s.]\\d{4}$\/\r\n<\/pre>\n<p>(Note that if there are multiple <em>invalid<\/em> values in the passed array, the reported exception only describes the first one, since it aborts at that point in any case.)<\/p>\n<h2 id=\"thirteenth\">Step 20: Honoring common parameters<\/h2>\n<p>There are several command-line switches that you can specify with any standard PowerShell cmdlet. As an author of <em>custom<\/em> PowerShell cmdlets, you should provide support for at least some of these in your cmdlets as well. You can see the full list at <a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/hh847884.aspx\">about_CommonParameters<\/a>. This section explains how to deal with a subset of these-those that are more commonly used-and that affect output and workflow: <code>-Verbose<\/code> , <code>-Debug<\/code> , &#8211;<code>WhatIf<\/code>, and <code>-Confirm<\/code> .<\/p>\n<h3 id=\"fourteenth\" class=\"attribute\"><code>-Verbose<\/code><\/h3>\n<p>In PowerShell you can output extra information on demand to PowerShell&#8217;s verbose data stream. Typically this just comes out on the console in a different color.<\/p>\n<p><strong>To support <\/strong> <strong><code>-Verbose<\/code> you <\/strong> <strong>need to: <\/strong><\/p>\n<ul>\n<li><strong>enable v<\/strong> <strong>erbose output functionality, and<\/strong><\/li>\n<li><strong>emit<\/strong> <strong> messages to the <\/strong> <strong> <em>verbose<\/em> <\/strong> <strong> stream. <\/strong><\/li>\n<\/ul>\n<p>When writing cmdlets in PowerShell (as opposed to C#), you need to make sure that a function has the <code>CmdletBindingAttribute<\/code> . The presence of that attribute is what enables verbose output. When writing in C#, on the other hand, <em>any<\/em> cmdlet you write <em>must<\/em> have the <code>CmdletAttribute<\/code> -you don&#8217;t have a choice, so in essence all your cmdlets automatically have verbose output enabled. (That&#8217;s not a typo; you use <code>CmdletBinding<\/code> in PowerShell, but just Cmdlet in C#.)<\/p>\n<p>To emit messages on the verbose stream once enabled, you simply use the Write<code>-Verbose<\/code> cmdlet (PowerShell) or <span class=\"user-instruction\">WriteVerbose<\/span> method (C#) to send output to the verbose stream instead of the standard output stream (<code>Write-Output<\/code> or <code>WriteObject,<\/code> respectively).<\/p>\n<h3 id=\"fifteenth\" class=\"attribute\"><code>-Debug<\/code><\/h3>\n<p>Debug output-extra output on PowerShell&#8217;s debug data stream-is almost identical to verbose output. <strong>To support <code>-Debug<\/code> you need to: <\/strong><\/p>\n<ul>\n<li><strong>enable <\/strong> <strong>debug<\/strong> <strong> output functionality, and<\/strong><\/li>\n<li><strong>emit<\/strong> <strong> messages to the <\/strong> <strong> <em>debug<\/em> <\/strong> <strong> stream. <\/strong><\/li>\n<\/ul>\n<p>You enable this, just like <code>-Verbose<\/code> , with <code>CmdletBindingAttribute<\/code> (PowerShell) or <code>CmdletAttribute<\/code> (C#), then you emit messages with the<code> Write-Debug<\/code> cmdlet (PowerShell) or <code>WriteDebug <\/code>method (C#).<\/p>\n<p>A distinction of debug output, however, makes it serve quite a different purpose than <code>-Verbose<\/code> . The thing about the debug output statement <code>(Write-Debug<\/code> or <code>WriteDebug)<\/code> 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 <code>-Confirm<\/code> does (which you will see below). Consider this simple PowerShell cmdlet:<\/p>\n<pre class=\"theme:powershell-ise lang:ps\">  function Get-Foo\r\n    {\r\n        [CmdletBinding ()]\r\n        param()\r\n           Write-Debug  \"debug output\"\r\n           Write-Verbose  \"verbose output\"\r\n           Write-Output \"standard output\"\r\n    } \r\n<\/pre>\n<p>Here is the default output:<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; Get-Foo\r\nstandard output\r\n<\/pre>\n<p>And here is what happens when you add debugging:<\/p>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; Get-Foo -Debug \r\nDEBUG: debug output\r\nConfirm\r\nContinue with this operation?\r\n[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is \"Y\"):\r\n<\/pre>\n<p>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.<\/p>\n<p>(I noticed as I was putting this together that, at least in PowerShell 5, <code>-Debug<\/code> had one slight quirk, and this only applies to cmdlets written in C# (not in PowerShell). Adding <code>-Debug<\/code> also seemed to enable <code>-Verbose<\/code> automatically.)<\/p>\n<h3 id=\"sixteenth\" class=\"attribute\">&#8211;<code>WhatIf<\/code><\/h3>\n<p>The &#8211;<code>WhatIf<\/code> parameter is a safety net for cmdlets that may make substantial changes to your system (read: do damage). It let&#8217;s you do a dry run, to see an overview of what would happen, before you run the real thing.<\/p>\n<p><strong>To support &#8211;<\/strong> <strong><code>WhatIf<\/code><\/strong> <strong>you need to: <\/strong><\/p>\n<ul>\n<li><strong>enable <\/strong> <strong><code>WhatIf<\/code><\/strong> <strong> functionality, and<\/strong><\/li>\n<li><strong>gate<\/strong> <strong> appropriate code blocks<\/strong> <strong>. <\/strong><\/li>\n<\/ul>\n<p>Enable &#8211;<code>WhatIf<\/code> with <code>CmdletBindingAttribute<\/code> (PowerShell) or <code>CmdletAttribute<\/code> (C#) and include the SupportsShouldProcess argument. Here&#8217;s an example from the sample project:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true \">[Cmdlet(VerbsCommon.Get, \"NetworkAdapter\", SupportsShouldProcess = true)]<\/pre>\n<p>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.<code>ShouldProcess<\/code> method. Thus, if you have a block like this&#8230;<\/p>\n<pre class=\"theme:powershell-ise lang:ps\">      \/* code to erase the root disk *\/\r\n    <\/pre>\n<p class=\"MsoNormal\">&#8230; change it to this:<\/p>\n<pre class=\"theme:powershell-ise lang:ps\">if (ShouldProcess(\"root disk\"))\r\n    {\r\n         \/* code to erase the root disk *\/\r\n    }\r\n<\/pre>\n<p>Each occurrence of the <code>ShouldProcess<\/code> method will emit an informational message to the user. <code>ShouldProcess<\/code> 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:<\/p>\n<pre class=\"theme:powershell-ise lang:ps\">if (ShouldProcess(\"network data store\"))<\/pre>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; Get-NetworkAdapter -WhatIf\r\nWhat if: Performing the operation \"Get-NetworkAdapter\" on target \"network data store\".\r\n<\/pre>\n<pre class=\"theme:powershell-ise lang:ps\">if (ShouldProcess(\"network data store\",     \"read\"))<\/pre>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; Get-NetworkAdapter -WhatIf\r\nWhat if: Performing the operation \"read\" on target \"network data store\".\r\n<\/pre>\n<pre class=\"theme:powershell-ise lang:ps\">if (ShouldProcess(\"reading network data store\",     \"don't care\",   \"don't care\"))<\/pre>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; Get-NetworkAdapter -WhatIf\r\nWhat if: reading network data store\r\n<\/pre>\n<p>I used &#8220;don&#8217;t care&#8221; for the second and third arguments because they are not used by &#8211;<code>WhatIf<\/code>. However, they are used with <code>-Confirm<\/code> , as you will see next, so you should be sure to use real phrases, not &#8220;don&#8217;t care&#8221;!<\/p>\n<p>There is actually a fourth signature for <code>ShouldProcess<\/code> as well, which you could read about on the MSDN documentation page <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms568276(v=vs.85).aspx\">here<\/a>, but it has little utility.<\/p>\n<h3 id=\"seventeenth\" class=\"attribute\"><code>-Confirm<\/code><\/h3>\n<p>The <code>-Confirm<\/code> 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.<\/p>\n<p><strong>To support &#8211;<\/strong> <strong>Confirm <\/strong> <strong>you need to: <\/strong><\/p>\n<ul>\n<li><strong>enable <\/strong> <strong>Confirm<\/strong> <strong> functionality, and<\/strong><\/li>\n<li><strong>gate<\/strong> <strong> appropriate code blocks<\/strong> <strong>. <\/strong><\/li>\n<\/ul>\n<p>Enable <code>\u2013Whatif<\/code> with <code>CmdletBindingAttribute<\/code> (PowerShell) or <code>CmdletAttribute<\/code> (C#) and include the <code>SupportsShouldProcess<\/code> argument. Here&#8217;s an example from the sample project:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">[Cmdlet(VerbsCommon.Get, \"NetworkAdapter\", SupportsShouldProcess = true)]<\/pre>\n<p>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 &#8211;<code>WhatIf<\/code> because <code>ShouldProcess<\/code> handles support for <em>both<\/em> of those simultaneously.<\/p>\n<p>Each occurrence of the <code>ShouldProcess<\/code> method will prompt the user for an action. <code>ShouldProcess<\/code> 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:<\/p>\n<pre class=\"theme:powershell-ise lang:ps\">if (ShouldProcess(\"network data store\"))<\/pre>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; Get-NetworkAdapter -Confirm \r\nConfirm\r\nAre you sure you want to perform this action?\r\nPerforming the operation \"Get-NetworkAdapter\" on target \"network data store\".\r\n[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is \"Y\"):\r\n<\/pre>\n<pre class=\"theme:powershell-ise lang:ps\">if (ShouldProcess(\"network data store\",     \"read\"))<\/pre>\n<pre class=\"theme:powershell-output lang:ps decode:true\">PS&gt; Get-NetworkAdapter -Confirm\r\nConfirm\r\nAre you sure you want to perform this action?\r\nPerforming the operation \"read\" on target \"network data store\".\r\n[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is \"Y\"):\r\n<\/pre>\n<pre class=\"theme:powershell-ise lang:ps\">if (ShouldProcess(\"don't care\",     \"   This may take several seconds\",     \"   Fetching network data\"))<\/pre>\n<pre class=\"theme:powershell-output lang:ps decode:true \">PS&gt; Get-NetworkAdapter -Confirm \r\nFetching network data\r\nThis may take several seconds\r\n[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is \"Y\"):\r\n<\/pre>\n<h3 id=\"eighteenth\" class=\"attribute\">Notes on <code>WhatIf<\/code> and <code>Confirm<\/code><\/h3>\n<p>Many things are often a bit simpler when writing cmdlets in PowerShell as compared to C#. However, <code>ShouldProcess<\/code> is quite the reverse; here&#8217;s why. In PowerShell, your <code>ShouldProcess<\/code> calls can be gating other cmdlets which may in turn support <code>WhatIf<\/code> 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 <code>WhatIf<\/code> and Confirm switches. In C#, however, you&#8217;re just calling other C# methods, no cmdlets, so you don&#8217;t have that issue at all.<\/p>\n<p>Microsoft advocates always combining <code>ShouldProcess<\/code> with a call to <code>ShouldContinue<\/code> 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&#8217;ve already added a <code>-Confirm<\/code> ? I have not found a good reason to use the pattern they suggest, but you should decide for yourself-see <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/dd878341(v=vs.85).aspx\">How to Request Confirmations<\/a>.<\/p>\n<h2>Conclusion<\/h2>\n<p>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.<\/p>\n<\/li>\n<\/ul>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>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.&hellip;<\/p>\n","protected":false},"author":221868,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538,35],"tags":[],"coauthors":[6802],"class_list":["post-54562","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","category-powershell"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/54562","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221868"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=54562"}],"version-history":[{"count":17,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/54562\/revisions"}],"predecessor-version":[{"id":91248,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/54562\/revisions\/91248"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=54562"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=54562"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=54562"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=54562"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}