{"id":2172,"date":"2016-02-19T00:00:00","date_gmt":"2016-02-19T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/using-c-to-create-powershell-cmdlets-the-basics\/"},"modified":"2026-04-15T18:05:44","modified_gmt":"2026-04-15T18:05:44","slug":"using-c-to-create-powershell-cmdlets-the-basics","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/using-c-to-create-powershell-cmdlets-the-basics\/","title":{"rendered":"Create PowerShell Cmdlets in C#: Step-by-Step Visual Studio Guide"},"content":{"rendered":"<h2>Executive Summary<\/h2>\n<p><strong>This article walks through creating a PowerShell cmdlet in C# from scratch using Visual Studio. The finished cmdlet follows the PowerShell verb-noun naming convention (e.g., Get-NetworkAdapter), inherits from System.Management.Automation.Cmdlet, reads input from command-line parameters or the pipeline, and writes strongly-typed objects to the pipeline output. The 15 steps cover everything: Visual Studio project setup, NuGet reference to PowerShell assemblies, class naming and inheritance, CmdletAttribute, output type declaration, parameter decoration with ParameterAttribute, single and multiple pipeline inputs, positional parameters, and parameter aliases. By the end you have a fully functional, importable .dll that PowerShell can load as a module.<\/strong><\/p>\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 class=\"series-articles--active\">Part 2: Using C# to Create PowerShell Cmdlets: The Basics<\/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<\/ul>\n<h2>Contents<\/h2>\n<ul>\n<li><a href=\"#first\">Step 1: Create a Visual Studio project<\/a><\/li>\n<li><a href=\"#second\">Step 2: Provide a reference to .NET PowerShell resources<\/a><\/li>\n<li><a href=\"#third\">Step 3: Rename the default Class1 class to reflect your cmdlet<\/a><\/li>\n<li><a href=\"#fourth\">Step 4: Inherit from Cmdlet<\/a><\/li>\n<li><a href=\"#fifth\">Step 5: Decorate your class with CmdletAttribute<\/a><\/li>\n<li><a href=\"#sixth\">Step 6: Create your output container<\/a><\/li>\n<li><a href=\"#seventh\">Step 7: Decorate your class with OutputTypeAttribute<\/a><\/li>\n<li><a href=\"#eighth\">Step 8: Create your inputs<\/a><\/li>\n<li><a href=\"#ninth\">Step 9: Decorate your parameters with the ParameterAttribute<\/a><\/li>\n<li><a href=\"#tenth\">Step 10: Setup a single pipeline input<\/a><\/li>\n<li><a href=\"#eleventh\">Step 11: Setup multiple pipeline-able properties<\/a><\/li>\n<li><a href=\"#twelveth\">Step 12: Setup multiple inputs without the pipeline<\/a><\/li>\n<li><a href=\"#thirteenth\">Step 13: Setup inputs without accompanying parameter names<\/a><\/li>\n<li><a href=\"#fourteenth\">Step 14: Provide parameter aliases<\/a><\/li>\n<li><a href=\"#fifteenth\">Step 15: Write some production code<\/a><\/li>\n<li><a href=\"#sixteenth\">Conclusion<\/a><\/li>\n<\/ul>\n<p class=\"start\">So you really enjoy the power and flexibility you get from using PowerShell cmdlets. You have played around with writing some functions and perhaps some cmdlets in PowerShell. Now your organization wants to include a PowerShell front-end to the .NET libraries it produces, which are all developed in C#. Thus, it makes sense to write the cmdlets in C# as well, giving better integration with your build tools and unit tests&#8230; not to mention that the rest of your development team is not as versed in writing in PowerShell. This is not difficult to do; in fact, the hardest part is finding the information on how to do it&#8230; a task which you have just accomplished!<\/p>\n<p>There are different ways to accomplish most significant development tasks. This one is no different. \u00a0This guide presents one way but it is certainly not the only way. In fact, you can take a short-cut around some of the things I will present: The &#8216;best practices&#8217; I will describe herein are just that, but don&#8217;t discard them lightly.<\/p>\n<p>The following is a complete recipe for creating a simple cmdlet, <span class=\"style4\"> <code>Get-NetworkAdapter<\/code><\/span>, which reports on all the network adapters on your computer, with some filtering capabilities on different properties. Attached to this article is a complete, working solution with the implementation of the cmdlet. All the bits of code you see here come from that sample.<\/p>\n<h2 id=\"first\">Step 1: Create a Visual Studio project<\/h2>\n<p>Within your Visual Studio solution, you will house your cmdlets in a project, just as you would any other component you are building. For PowerShell, create a <span class=\"style4\"> Class Library<\/span> project so that once you have built the project, you have a DLL that comprises your PowerShell cmdlets.<\/p>\n<h2 id=\"second\">Step 2: Provide a reference to .NET PowerShell resources<\/h2>\n<p>Until recently (December, 2015) this step was tedious, vague, and wishy-washy. Now it is clean and streamlined-thanks, Microsoft, for &#8220;nuget-izing&#8221; PowerShell&#8217;s reference assemblies! Here the goal is to add a reference to <span class=\"style4\"> <code>System.Management.Automation<\/code><\/span>-the core of PowerShell-but you won&#8217;t find it if you just browse .NET assemblies. You need to use the NuGet Package Manager in Visual Studio to install the PowerShell reference assemblies first. They are available for PowerShell versions 3, 4, or 5, so choose the appropriate one for your environment. You can easily find it in the package manager by searching for &#8220;PowerShell&#8221;, as shown.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2370-img39.jpg\" alt=\"2370-img39.jpg\" \/><\/p>\n<p>Then just select your PowerShell project created in step 1 to attach it to. Once installed, it will not only make the reference assemblies available, but it will also add just the reference you need in the project.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2370-img3A.jpg\" alt=\"2370-img3A.jpg\" \/><\/p>\n<h2 id=\"third\">Step 3: Rename the default Class1 class to reflect your cmdlet<\/h2>\n<p>Rename the default <span class=\"style4\"> <code>Class1<\/code><\/span> class, or create a new class if you are adding another cmdlet to the project, so that its name conforms to this format:<\/p>\n<p><i>&lt;verb&gt;&lt;noun&gt;<\/i>Cmdlet.cs<\/p>\n<p>For this walkthrough, the cmdlet will be <span class=\"style4\"> Get-NetworkAdapter<\/span> so rename <span class=\"style4\"> <code>Class1<\/code><\/span> to <span class=\"style4\"> <code>GetNetworkAdapterCmdlet.cs<\/code><\/span>.<\/p>\n<h2 id=\"fourth\">Step 4: Inherit from Cmdlet<\/h2>\n<p>Instantly make your class into a cmdlet by inheriting from <span class=\"style4\"> <code>System.Management.Automation.Cmdlet<\/code><\/span>:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">public\u00a0class\u00a0GetNetworkAdapterCmdlet:\u00a0Cmdlet\n{\n}\n<\/pre>\n<p>There are times that you may need to inherit from <span class=\"style4\"> <code>PSCmdlet<\/code><\/span> instead of <span class=\"style4\"> <code>Cmdlet<\/code><\/span>, but for this article <span class=\"style4\"> Cmdlet<\/span> will suffice.<\/p>\n<h2 id=\"fifth\">Step 5: Decorate your class with CmdletAttribute<\/h2>\n<p>Sadly, step 4 did not really make it a cmdlet. You also need to use <span class=\"style4\"> <code>System.Management.Automation.CmdletAttribute<\/code><\/span> with appropriate arguments, namely the same verb and noun used in the class name defined in step 3. Verbs, by the way, should always come from the <a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms714428(v=VS.85).aspx\">list of approved verbs<\/a>, and you should use the predefined value for it here, rather than a string constant. Nouns, on the other hand, are unconstrained, so you must supply your own string (or put it in a constant).<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">[Cmdlet(VerbsCommon.Get,\u00a0\"NetworkAdapter\")]\npublic\u00a0class\u00a0GetNetworkAdapterCmdlet:\u00a0Cmdlet\n{\n}\n<\/pre>\n<p>At this point, you have a cmdlet, though it does not yet do anything. To confirm that it is really a cmdlet, first build your project, \u00a0then load and examine what is in your module:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">PS&gt; Import-Module .\\bin\\Debug\\PowerShellModuleInCSharp.dll\nPS&gt; Get-Command -module PowerShellModuleInCSharp\n    \u00a0\n    CommandType\u00a0\u00a0\u00a0\u00a0 Name\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0ModuleName\n    -----------\u00a0\u00a0\u00a0\u00a0 ----\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ----------\n    Cmdlet\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Get-NetworkAdapter\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 PowerShellModuleInCSharp\n<\/pre>\n<p>Note that the exposed name of the cmdlet comes from the <span class=\"style4\"> <code>Cmdlet<\/code><\/span>, <i>not<\/i> from the class name!<\/p>\n<h2 id=\"sixth\">Step 6: Create your output container<\/h2>\n<p>Inputs and outputs for your cmdlet need to be properly managed. You do <i>not<\/i>, for example, read from or write to the console as you would in a typical interactive program. Let&#8217;s consider outputs first. And let&#8217;s assume your cmdlet will have just one kind of output. (You can certainly have more: <span class=\"style4\"> <code> Get-ChildItem<\/code><\/span>, for example, returns both <span class=\"style4\"><code> FileInfo<\/code><\/span> and <span class=\"style4\"><code> DirectoryInfo<\/code><\/span> objects.) For the sample cmdlet we are returning information about network adapters so we will simply call the output object a <span class=\"style4\"> <code>NetworkAdapter<\/code><\/span>. <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/aa394582(v=vs.85).aspx\">Windows Management Instrumentation<\/a>, or WMI, is the library to explore your system&#8217;s resources. It returns quite a lot of properties about a network adapter; for this exercise we will just use these select few:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\"> public\u00a0class\u00a0NetworkAdapter\n    {\n    \u00a0\u00a0\u00a0\u00a0public\u00a0string\u00a0Name\u00a0{\u00a0get;\u00a0set;\u00a0}\n    \u00a0\u00a0\u00a0\u00a0public\u00a0string\u00a0Description\u00a0{\u00a0get;\u00a0set;\u00a0}\n    \u00a0\u00a0\u00a0\u00a0public\u00a0int\u00a0DeviceId\u00a0{\u00a0get;\u00a0set;\u00a0}\n    \u00a0\u00a0\u00a0\u00a0public\u00a0string\u00a0Manufacturer\u00a0{\u00a0get;\u00a0set;\u00a0}\n    \u00a0\u00a0\u00a0\u00a0public\u00a0string\u00a0NetConnectionId\u00a0{\u00a0get;\u00a0set;\u00a0}\n    \u00a0\u00a0\u00a0\u00a0public\u00a0bool\u00a0PhysicalAdapter\u00a0{\u00a0get;\u00a0set;\u00a0}\n    }\n<\/pre>\n<h2 id=\"seventh\">Step 7: Decorate your class with OutputTypeAttribute<\/h2>\n<p>With your output object defined, you can now tell PowerShell what your cmdlet emits to the pipeline with the <span class=\"style4\"> <code> System.Management.Automation.OutputType<\/code><\/span><code> <\/code>attribute. \u00a0I have highlighted the new code in yellow here:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">[Cmdlet(VerbsCommon.Get,\u00a0\"NetworkAdapter\")]\n[OutputType(typeof(NetworkAdapter))]\npublic\u00a0class\u00a0GetNetworkAdapterCmdlet:\u00a0Cmdlet\n{\n}\n<\/pre>\n<p>This is important for two reasons. First, it contributes to your cmdlet&#8217;s auto-generated help text. Here is what <span class=\"style4\"> <code>Get-Help<\/code><\/span> provides before and after applying the <span class=\"style4\"><code> OutputType<\/code><\/span> attribute (note the use of the <span class=\"style4\"> <code>-Full<\/code><\/span> switch). Everything is the same except for the <i>OUTPUTS<\/i> section, highlighted below:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">  PS&gt; Import-Module .\\bin\\Debug\\PowerShellModuleInCSharp.dll -Force\n  PS&gt; Get-Help Get-NetworkAdapter -full\n    \u00a0\n    NAME\n    \u00a0\u00a0\u00a0 Get-NetworkAdapter\n    \u00a0\n    SYNTAX\n    \u00a0\u00a0\u00a0 Get-NetworkAdapter\u00a0 [&lt;CommonParameters&gt;]\n    \u00a0\n    INPUTS\n    \u00a0\u00a0\u00a0 None\n    \u00a0\n    OUTPUTS # &lt;&lt;BEFORE&gt;&gt;\n    \u00a0\u00a0\u00a0 System.Object\n    \u00a0\n    OUTPUTS # &lt;&lt;AFTER&gt;&gt;\n    \u00a0\u00a0\u00a0 PowerShellModuleInCSharp.Containers.NetworkAdapter\n<\/pre>\n<p>Second, and perhaps more importantly, using the <span class=\"style4\"><code> OutputType<\/code><\/span> provides tab completion! Before you add the <span class=\"style4\"> <code> OutputType<\/code><\/span> attribute, if you type this (the last part is &#8220;<code>Select-Object<\/code>&#8220;, then a space, then a tab)&#8230;<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">PS&gt; Import-Module .\\bin\\Debug\\PowerShellModuleInCSharp.dll -Force\nPS&gt; Get-NetworkAdapter | Select-Object&lt;space&gt;&lt;tab&gt;\n<\/pre>\n<p>&#8230; nothing happens. But <i>after<\/i> you apply the <span class=\"style4\"><code> OutputType<\/code><\/span> attribute, here is what happens. (As this is not a video, you need to imagine the time-lapse nature of this figure.) When you type the first line of each pair, PowerShell turns it into the second line. I have added the highlighting to draw your attention to the iteration through the list of <code>NetworkAdapter<\/code>&#8216;s properties as defined above.<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">    PS&gt; Import-Module .\\bin\\Debug\\PowerShellModuleInCSharp.dll -Force\n    \u00a0\n    PS&gt; Get-NetworkAdapter | Select-Object&lt;space&gt;&lt;tab&gt;\n    PS&gt; Get-NetworkAdapter | Select-Object Description\n    \u00a0\n    PS&gt; Get-NetworkAdapter | Select-Object Description&lt;tab&gt;\n    PS&gt; Get-NetworkAdapter | Select-Object DeviceId\n    \u00a0\n    PS&gt; Get-NetworkAdapter | Select-Object DeviceId&lt;tab&gt;\n    PS&gt; Get-NetworkAdapter | Select-Object Manufacturer\n    \u00a0\n    PS&gt; Get-NetworkAdapter | Select-Object Manufacturer&lt;tab&gt;\n    PS&gt; Get-NetworkAdapter | Select-Object Name\n    \u00a0\n    PS&gt; Get-NetworkAdapter | Select-Object Name&lt;tab&gt;\n    PS&gt; Get-NetworkAdapter | Select-Object NetConnectionId\n    \u00a0\n    PS&gt; Get-NetworkAdapter | Select-Object NetConnectionId&lt;tab&gt;\n    PS&gt; Get-NetworkAdapter | Select-Object PhysicalAdapter\n    \u00a0\n    PS&gt; Get-NetworkAdapter | Select-Object PhysicalAdapter&lt;tab&gt;\n    PS&gt; Get-NetworkAdapter | Select-Object Description\n<\/pre>\n<p>The first <span class=\"style4\"> <code>&lt;tab&gt;<\/code><\/span> activated the tab-completion, filling in the remaining portion of the first available choice lexicographically (from the set of <code>NetworkAdapter<\/code>&#8216;s properties), i.e.\u00a0 <span class=\"style4\"> <code>Description<\/code><\/span>.<\/p>\n<p>By immediately pressing <span class=\"style4\"> <code>&lt;tab&gt;<\/code><\/span> again, you will cycle through to the next choice, <span class=\"style4\"> <code>DeviceId<\/code><\/span>. If you keep pressing <span class=\"style4\"> &lt;tab&gt;<\/span>\u00a0 it will eventually circle back to the start of the list, as shown. Note that I said the &#8220;remaining portion&#8221; just above. So if you had typed, say, &#8220;Select-Object De&lt;tab&gt;&#8221; then it will just fill in the first available choice beginning with &#8220;De&#8221;, namely, <span class=\"style4\"> <code>Description<\/code><\/span> and cycle between <span class=\"style4\"> Description<\/span> and <span class=\"style4\"> <code>DeviceId<\/code><\/span>.<\/p>\n<h2 id=\"eighth\">Step 8: Create your inputs<\/h2>\n<p>Now let&#8217;s turn our attention to the input side of things. Inputs to your cmdlet come from command-line parameters or from the pipeline or both. To provide parameters, you start with standard C# public properties. For this exercise, we will use three parameters whose names correspond to properties of the <code>NetworkAdapter<\/code> object defined earlier. These will be used as filters by the cmdlet. That is, if for example the <span class=\"style4\"><code> Name<\/code><\/span> parameter is supplied, the cmdlet will only return <span class=\"style4\"> <code> NetworkAdapter<\/code><\/span> objects whose name matches that <span class=\"style4\"> <code>Name<\/code><\/span> parameter. We will also define a fourth parameter, <span class=\"style4\"><code> MaxEntries<\/code><\/span>, which lets us throttle the number of output records we receive from the cmdlet.<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">public\u00a0string\u00a0Name\u00a0{\u00a0get;\u00a0set;\u00a0}\n\npublic\u00a0string\u00a0Manufacturer\u00a0{\u00a0get;\u00a0set;\u00a0}\n\npublic\u00a0bool\u00a0PhysicalAdapter\u00a0{\u00a0get;\u00a0set;\u00a0}\n\npublic\u00a0int\u00a0MaxEntries\u00a0{\u00a0get;\u00a0set;\u00a0} = 100;\n<\/pre>\n<h2 id=\"ninth\">Step 9: Decorate your parameters with the ParameterAttribute<\/h2>\n<p>For PowerShell to know that you want certain properties to be exposed as parameters of your cmdlet, you must add the <span class=\"style4\"> <code>System.Management.Automation.ParameterAttribute<\/code><\/span>:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">[Parameter]\npublic\u00a0string\u00a0Name\u00a0{\u00a0get;\u00a0set;\u00a0}\n\n[Parameter]\npublic\u00a0string\u00a0Manufacturer\u00a0{\u00a0get;\u00a0set;\u00a0}\n\n[Parameter]\npublic\u00a0bool\u00a0PhysicalAdapter\u00a0{\u00a0get;\u00a0set;\u00a0}\n\n[Parameter]\npublic\u00a0int\u00a0MaxEntries\u00a0{\u00a0get;\u00a0set;\u00a0}\n<\/pre>\n<p>Each property decorated with the Parameter attribute becomes able to accept input as a command-line parameter. Examples:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">    PS&gt; Get-NetworkAdapter -Manufacturer Microsoft | Format-Table\n    PS&gt; Get-NetworkAdapter -Manufacturer Microsoft -Name WAN\n<\/pre>\n<p>But this does <i>not<\/i> support obtaining input <i>from the pipeline<\/i>. You can accept pipeline input in two different ways, covered in the next two steps.<\/p>\n<h2 id=\"tenth\">Step 10: Setup a single pipeline input<\/h2>\n<p>Say you want to be able to list all the <span class=\"style4\"><code> NetworkAdapter<\/code><\/span> objects for one or more specific manufacturers by feeding them from the pipeline:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">PS&gt; 'Intel', 'Microsoft' | Get-NetworkAdapter\n<\/pre>\n<p>You want to direct that pipeline input into the appropriate parameter-in this case, the <span class=\"style4\"> Manufacturer<\/span> parameter. To do this, you add the <span class=\"style4\"> <b> ValueFromPipeline<\/b><b><\/b><\/span> argument to the <span class=\"style4\"> <code>Parameter<\/code><\/span> attribute for that parameter:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">[Parameter(ValueFromPipeline\u00a0=\u00a0true)]\npublic\u00a0string\u00a0Manufacturer\u00a0{\u00a0get;\u00a0set;\u00a0}\n<\/pre>\n<p>There&#8217;s nothing technically stopping you from applying the <span class=\"style4\"> <code>ValueFromPipeline<\/code><\/span> argument to more than one parameter (say, for example, if you wanted to see whether the manufacturer <i>or the name<\/i> of the network adapter contained Intel or Microsoft). But in practice, <span class=\"style4\"> <code>ValueFromPipeline<\/code><\/span> is typically attached to only one parameter because there is really no need to have two parameters receiving identical values. When you do want to get multiple <i>different<\/i> inputs from the pipeline, set up multiple properties as described next.<\/p>\n<h2 id=\"eleventh\">Step 11: Setup multiple pipeline-able properties<\/h2>\n<p>Say you have a <span class=\"style4\"> <code>NetworkAdapter<\/code><\/span> object at hand; let&#8217;s call it <span class=\"style4\"> <code>$adapter1<\/code><\/span>. You want to feed <span class=\"style4\"> <code>$adapter1<\/code><\/span> to <span class=\"style4\"> <code>Get-NetworkAdapter<\/code><\/span> as a template, so that <span class=\"style4\"> Get-NetworkAdapter<\/span> only returns objects that match the <span class=\"style4\"> <code>Name<\/code><\/span>, <span class=\"style4\"> <code>Manufacturer<\/code><\/span>, and <span class=\"style4\"> <code>PhysicalAdapter<\/code><\/span> values of <span class=\"style4\"> <code>$adapter1<\/code><\/span>; we will choose to ignore the other properties of <span class=\"style4\"> $adapter1<\/span> for filtering purposes.<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">PS&gt; $adapter1 | Get-NetworkAdapter\n<\/pre>\n<p>Each property that we want to treat as significant must use the <span class=\"style4\"> <code>ValueFromPipelineByPropertyName<\/code><\/span> argument to the <span class=\"style4\"> <code>Parameter<\/code><\/span> attribute:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">[Parameter(ValueFromPipelineByPropertyName\u00a0=\u00a0true)]\npublic\u00a0string\u00a0Name\u00a0{\u00a0get;\u00a0set;\u00a0}\n\n[Parameter(ValueFromPipelineByPropertyName\u00a0=\u00a0true,\u00a0ValueFromPipeline\u00a0=\u00a0true)]\npublic\u00a0string\u00a0Manufacturer\u00a0{\u00a0get;\u00a0set;\u00a0}\n\n[Parameter(ValueFromPipelineByPropertyName\u00a0=\u00a0true)]\npublic\u00a0bool\u00a0PhysicalAdapter\u00a0{\u00a0get;\u00a0set;\u00a0}\n\n[Parameter]\npublic\u00a0int\u00a0MaxEntries\u00a0{\u00a0get;\u00a0set;\u00a0}\n<\/pre>\n<p>Notice that <span class=\"style4\"> <code>Manufacturer<\/code><\/span> now has <i>two<\/i> arguments on its <span class=\"style4\"> <code>Parameter<\/code><\/span> attribute so you can still feed it an array of strings, as was done in the previous step, or you can feed an array of objects containing appropriate properties.<\/p>\n<p>Let&#8217;s check our work so far. Recompile, re-import and examine the help. I&#8217;ve reduced the output to just the relevant parts here:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">PS&gt; Import-Module .\\bin\\Debug\\PowerShellModuleInCSharp.dll -Force\nPS&gt; Get-Help Get-NetworkAdapter -full\n    \u00a0\n    NAME\n    \u00a0\u00a0\u00a0 Get-NetworkAdapter\n    \u00a0\n    SYNTAX\n    \u00a0\u00a0\u00a0 Get-NetworkAdapter [-Name &lt;string&gt;] [-Manufacturer &lt;string&gt;]\n[-PhysicalAdapter &lt;bool&gt;] [-MaxEntries &lt;int&gt;]\n    \u00a0\n    \u00a0\n    PARAMETERS\n    \u00a0\u00a0\u00a0 -Manufacturer &lt;string&gt;\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Accept pipeline input?\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 true (ByValue, ByPropertyName)\n    \u00a0\n    \u00a0\u00a0\u00a0 -MaxEntries &lt;int&gt;\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Accept pipeline input?\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 false\n    \u00a0\n    \u00a0\u00a0\u00a0 -Name &lt;string&gt;\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Accept pipeline input?\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 true (ByPropertyName)\n    \u00a0\n    \u00a0\u00a0\u00a0 -PhysicalAdapter &lt;bool&gt;\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Accept pipeline input?\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 true (ByPropertyName)\n    \u00a0\n    INPUTS\n    \u00a0\u00a0\u00a0 System.String\n    \u00a0\u00a0\u00a0 System.Boolean\n<\/pre>\n<p>Note that each parameter shows up in the <i>SYNTAX<\/i> section, indicating its name and type. Then in the <i>PARAMETERS<\/i> section, you again see the name and type of each parameter along with additional properties of the parameters-notably whether it accepts pipeline input. Finally, the <i>INPUTS<\/i> section is there; not to be redundant, but to show-along with the <i>OUTPUTS<\/i> section you have already seen-what data comes and goes in the pipeline. It <i>seems<\/i> to be telling you that it accepts one string and one Boolean from the pipeline as inputs, but it really means that this cmdlet will accept one <i>or more<\/i> strings and one <i>or more<\/i> Booleans from the pipeline as input. It does <i>not<\/i> reveal, as with this particular cmdlet, that, two strings and one Boolean may come from the pipeline.<\/p>\n<p>Note: This is an appropriate time to mention that you have about reached the limit of the documentation PowerShell can generate from your code alone, i.e. without you having decorated your code with documentation-comments. While this documentation is useful, it is but a mere skeleton of what your documentation should be. For a practical guide to easily instrumenting your code with doc-comments, see my recent article, <a href=\"https:\/\/www.simple-talk.com\/dotnet\/software-tools\/documenting-your-powershell-binary-cmdlets\/\">Documenting Your PowerShell Binary Cmdlets<\/a>.<\/p>\n<h2 id=\"twelveth\">Step 12: Setup multiple inputs without the pipeline<\/h2>\n<p>You have seen that you can feed multiple inputs to the cmdlet like this:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">PS&gt; 'Intel', 'Microsoft' | Get-NetworkAdapter\n<\/pre>\n<p>That works even though the receiving parameter, <span class=\"style4\"> <code>Manufacturer<\/code><\/span>, is just a simple string, <i>not<\/i> a list or array!<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">[Parameter(ValueFromPipelineByPropertyName\u00a0=\u00a0true,\u00a0ValueFromPipeline\u00a0=\u00a0true)]\npublic\u00a0string\u00a0Manufacturer\u00a0{\u00a0get;\u00a0set;\u00a0}\n<\/pre>\n<p>It works because of the nature of the pipeline: the pipeline itself handles feeding multiple values one at a time, so <span class=\"style4\"> <code>Manufacturer<\/code><\/span> ever only gets asked to accept a single string at a time. But it would seem very reasonable to be able to specify multiple values to a parameter via direct parameters as well (i.e. <i>not<\/i> through the pipeline):<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">PS&gt; Get-NetworkAdapter -Manufacturer Intel, Microsoft\n<\/pre>\n<p>To do that, though, you must specify that <span class=\"style4\"> <code>Manufacturer<\/code><\/span> is an array-the only change here is using <span class=\"style4\"> <code>string[]<\/code><\/span> in place of <span class=\"style4\"> <code>string<\/code><\/span>:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">   [Parameter(ValueFromPipelineByPropertyName = true, ValueFromPipeline = true)]\n    public string[] Manufacturer { get; set; }\n<\/pre>\n<p>Thus, while the commands typed by the user seem very similar, just inverted in a sense&#8230;<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">PS&gt; 'Intel', 'Microsoft' | Get-NetworkAdapter\nPS&gt; Get-NetworkAdapter -Manufacturer Intel, Microsoft\n<\/pre>\n<p>&#8230;internally they are handled quite differently. With the pipeline inputs, they are processed individually; the cardinality of the <span class=\"style4\"> <code>Manufacturer<\/code><\/span> array is always one. With direct inputs, the cardinality of <span class=\"style4\"> Manufacturer<\/span> is the length of the array that you pass in.<\/p>\n<h2 id=\"thirteenth\">Step 13: Setup inputs without accompanying parameter names<\/h2>\n<p>Revisiting the last examples, we can make the parallel between pipeline and direct inputs even closer by not forcing the user to specify the name of the parameter in the latter case (i.e. &#8220;-Manufacturer&#8221; has been omitted):<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">PS&gt; 'Intel', 'Microsoft' | Get-NetworkAdapter\nPS&gt; Get-NetworkAdapter Intel, Microsoft\n<\/pre>\n<p>To accomplish this, you need to map your cmdlet parameters to positions on the command line, so that PowerShell will know-by position alone-which parameters you are filling with values. In this case, because we assume <span class=\"style4\"> <code>Manufacturer<\/code><\/span> will be wanted the most, we give that the first position, position zero. It is good practice to give <i>all<\/i> your parameters position values, though, to allow the user to decide whether they want to use names at all.<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">[Parameter(Position=1,ValueFromPipelineByPropertyName = true)]\npublic string Name { get; set; }\n    \u00a0\n[Parameter(Position=0,ValueFromPipelineByPropertyName = true, ValueFromPipeline = true)]\npublic string[] Manufacturer { get; set; }\n    \u00a0\n[Parameter(Position=2,ValueFromPipelineByPropertyName = true)]\npublic bool? PhysicalAdapter { get; set; }\n    \u00a0\n[Parameter(Position=3)]\npublic int MaxEntries { get; set; } = 100;\n<\/pre>\n<p>With these positions specified, as I&#8217;ve just demonstrated, \u00a0any of the commands below achieve precisely the same result. If you use parameter names, you can put them in any order you please, but if you want to omit names you <i>must<\/i> honor the order based on the <span class=\"style4\"> <code>Position<\/code><\/span> argument to each <span class=\"style4\"> Parameter<\/span> attribute:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true \"> PS&gt; Get-NetworkAdapter -Name WAN -PhysicalAdapter true -Manufacturer Intel\n PS&gt; Get-NetworkAdapter -PhysicalAdapter true\u00a0 -Name WAN -Manufacturer Intel\n PS&gt; Get-NetworkAdapter -Manufacturer Intel -Name WAN -PhysicalAdapter true\n PS&gt; Get-NetworkAdapter Intel WAN true\n<\/pre>\n<h2 id=\"fourteenth\">Step 14: Provide parameter aliases<\/h2>\n<p>PowerShell automatically provides prefix-parameter recognition, which is a great convenience feature: you <i>may<\/i> type the entire name of a given parameter, but you are only <i>required<\/i> to type enough to make it unambiguous. For example, for the <span class=\"style4\"> <code>Manufacturer<\/code><\/span> parameter all of these are equivalent:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">PS&gt; Get-NetworkAdapter -Manufacturer Intel\nPS&gt; Get-NetworkAdapter -Manufacture Intel  \nPS&gt; Get-NetworkAdapter -Manufac Intel  \nPS&gt; Get-NetworkAdapter -M Intel  \n<\/pre>\n<p>That&#8217;s fine, you say, but everyone in your shop abbreviates &#8216;manufacturer&#8217; as &#8216;mfg&#8217;. This is actually short for manufactur<i>ing<\/i>, but let&#8217;s run with it! Of course, &#8216;mfg&#8217; is not a proper prefix of &#8216;manufacturer&#8217; so that will not work by default. However, you can easily define an alias to accept an alternative name or several alternative names. \u00a0This example shows two aliases being defined.<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">  [Parameter(\n    \u00a0 \u00a0 Position=0,\n    \u00a0 \u00a0 ValueFromPipelineByPropertyName =  true,\n    \u00a0 \u00a0 ValueFromPipeline =  true)]\n    [Alias(\"mfg\", \"vendor\")]\n    public string[] Manufacturer { get; set; }\n<\/pre>\n<p>And PowerShell provides prefix-parameter recognition on the aliases as well! So all of these are equivalent:<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true\">PS&gt; Get-NetworkAdapter -Manufacturer Intel\nPS&gt; Get-NetworkAdapter -Manufac Intel  \nPS&gt; Get-NetworkAdapter -mfg Intel  \nPS&gt; Get-NetworkAdapter -mf Intel\nPS&gt; Get-NetworkAdapter -vend Intel\n<\/pre>\n<h2 id=\"fifteenth\">Step 15: Write some production code<\/h2>\n<p>To wrap up this short and simple (!) 15-step process to create PowerShell cmdlets in C#, let&#8217;s add just one more little detail-actual production code to do the work! To do this in your cmdlet skeleton, there are four principal methods you need to override:<\/p>\n<ul>\n<li><code>BeginProcessing <\/code>(to do initialization),<\/li>\n<li><code> ProcessRecord<\/code> (to process each item in the pipeline),<\/li>\n<li><code>EndProcessing <\/code>(to do finalization), and<\/li>\n<li><code>StopProcessing<\/code> (to handle abnormal termination).<\/li>\n<\/ul>\n<p>Without further ado, here is the complete code for the <span class=\"style4\"> <code>Get-NetworkAdapter<\/code><\/span> cmdlet. Note that this uses a bit of syntactic C# 6.0 (e.g. the amazingly useful <span class=\"style4\"> <code>nameof<\/code><\/span> operator) so if you are using a Visual Studio edition prior to 2015 you will just need to substitute constant strings for those expressions. In the interests of brevity-and the fact that you are reading this article to understand how to build cmdlets, not how to report network adapter statistics!-I present the code without commentary. Take a look at the sample project attached to this article to load it up in Visual Studio and experiment with it!<\/p>\n<pre class=\"theme:powershell-ise lang:ps decode:true \">    using System.Collections.Generic;\n    using System.Linq;\n    using System.Management;\n    using System.Management.Automation;\n    using System.Text.RegularExpressions;\n    using PowerShellModuleInCSharp.Containers;\n    \u00a0\n    namespace PowerShellModuleInCSharp.CSharpCmdlets\n    {\n    \u00a0\u00a0\u00a0 [Cmdlet(VerbsCommon.Get, nameof(NetworkAdapter))]\n    \u00a0\u00a0\u00a0 [OutputType(typeof(NetworkAdapter))]\n    \u00a0\u00a0\u00a0 public class GetNetworkAdapterCmdlet : Cmdlet\n    \u00a0\u00a0\u00a0 {\n    \u00a0\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 [Parameter(Position=1,ValueFromPipelineByPropertyName = true)]\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  public string Name { get; set; }\n    \u00a0\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 [Parameter(\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Position=0,\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ValueFromPipelineByPropertyName = true,\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ValueFromPipeline = true)]\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 [Alias(\"mfg\", \"vendor\")]\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  public string[] Manufacturer { get; set; }\n    \u00a0\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 [Parameter(Position=2,ValueFromPipelineByPropertyName = true)]\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  public bool? PhysicalAdapter { get; set; }\n    \u00a0\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 [Parameter(Position=3)]\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  public int MaxEntries { get; set; } = 100;\n    \u00a0\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  private IEnumerable&lt;NetworkAdapter&gt; _wmiResults;\n    \u00a0\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  protected override void BeginProcessing()\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  {\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 base.BeginProcessing();\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 _wmiResults = GetWmiResults();\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  }\n    \u00a0\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  protected override void ProcessRecord()\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  {\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 var query = _wmiResults;\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (Name != null)\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 query = query.Where(adapter =&gt;\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 adapter.Name != null &amp;&amp; adapter.Name.StartsWith(Name));\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n    \u00a0  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/ Just like \"Name\" above, this checks for prefix matches\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ but on multiple values instead of just a single value.\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0if (Manufacturer != null)\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 query = query.Where(\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 adapter =&gt;\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 adapter.Manufacturer != null &amp;&amp;\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Regex.IsMatch(adapter.Manufacturer,\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 string.Format(\"^(?:{0})\", string.Join(\"|\", Manufacturer))));\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Being a Boolean, an exact match is used here.\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if (PhysicalAdapter != null)\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 query = query.Where(adapter =&gt;\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 adapter.PhysicalAdapter == PhysicalAdapter);\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 query.Take(MaxEntries).ToList().ForEach(WriteObject);\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  }\n    \u00a0\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  private static IEnumerable&lt;NetworkAdapter&gt; GetWmiResults()\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  {\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 const string wmiQuery = \"Select * from Win32_NetworkAdapter\";\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return new ManagementObjectSearcher(wmiQuery).Get()\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .Cast&lt;ManagementObject&gt;()\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .Select(BuildOutputObject);\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  }\n    \u00a0\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  private static NetworkAdapter BuildOutputObject(ManagementBaseObject item)\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  {\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return new NetworkAdapter\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Name\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 = (string) item[nameof(NetworkAdapter.Name)],\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Description\u00a0\u00a0\u00a0\u00a0 = (string) item[nameof(NetworkAdapter.Description)],\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DeviceId\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 = int.Parse((string)item[nameof(NetworkAdapter.DeviceId)]),\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Manufacturer\u00a0\u00a0\u00a0 = (string) item[nameof(NetworkAdapter.Manufacturer)],\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NetConnectionId = (string) item[nameof(NetworkAdapter.NetConnectionId)],\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 PhysicalAdapter = (bool)\u00a0\u00a0 item[nameof(NetworkAdapter.PhysicalAdapter)]\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 };\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  }\n    \u00a0\u00a0\u00a0 }\n    }\n    namespace PowerShellModuleInCSharp.Containers\n    {\n    \u00a0\u00a0\u00a0 public class NetworkAdapter\n    \u00a0\u00a0\u00a0 {\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  public string Name { get; set; }\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  public string Description { get; set; }\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  public int DeviceId { get; set; }\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  public string Manufacturer { get; set; }\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  public string NetConnectionId { get; set; }\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  public bool PhysicalAdapter { get; set; }\n    \u00a0\u00a0\u00a0 }\n    }\n<\/pre>\n<h2 id=\"sixteenth\">Conclusion<\/h2>\n<p>The process of writing a PowerShell cmdlet is actually quite straightforward, though it may seem daunting the first time you go through it. Jump right in to the sample project and play with it. Use it as a model as you start on your own cmdlets. By carefully following the recipe provided in this article, you should be able to achieve what you were likely looking to do: writing a cmdlet that works in a way that a user would reasonably expect a cmdlet to work.<\/p>\n<div class=\"note\">\n<p><strong>Update &#8211; April 2016<\/strong><br \/>Since this article, I&#8217;ve created a wallchart putting both XmlDoc2Cmdlet and DocTreeGenerator in context, showing you how to do a complete documentation solution for your PowerShell work in both C# and PowerShell. <a href=\"https:\/\/www.simple-talk.com\/sysadmin\/powershell\/unified-approach-to-generating-documentation-for-powershell-cmdlets\/\">Click here for more details<\/a>. <a href=\"https:\/\/www.simple-talk.com\/sysadmin\/powershell\/unified-approach-to-generating-documentation-for-powershell-cmdlets\/\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2407-1-5728ddc3-433a-4b82-a6dc-84f5f450b519.png\" alt=\"2407-1-5728ddc3-433a-4b82-a6dc-84f5f450b\" \/><\/a><\/p>\n<\/div>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: Using C# to Create PowerShell Cmdlets: The Basics<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. How do I create a PowerShell cmdlet in C#?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Create a C# Class Library project in Visual Studio. Add the PowerShellStandard.Library NuGet package. Create a class that inherits from System.Management.Automation.Cmdlet and decorate it with [Cmdlet(VerbsCommon.Get, &#8220;YourNoun&#8221;)]. Override ProcessRecord() to write output with WriteObject(). Add properties decorated with [Parameter] for cmdlet parameters. Build the project to produce a .dll, then import it in PowerShell with Import-Module. The cmdlet name is automatically the verb-noun combination from the CmdletAttribute.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. What is the difference between inheriting from Cmdlet and PSCmdlet in C#?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Cmdlet is the base class for all cmdlets and provides the core pipeline infrastructure (WriteObject, WriteError, ShouldProcess, etc.). PSCmdlet inherits from Cmdlet and adds access to the PowerShell runspace through the SessionState property &#8211; giving the cmdlet access to variables, drives, and providers in the current session. Use Cmdlet when your cmdlet only needs to process input and produce output. Use PSCmdlet when you need to interact with the PowerShell session context, invoke scripts, or access providers.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. How do I accept pipeline input in a C# PowerShell cmdlet?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Decorate the property with [Parameter(ValueFromPipeline = true)]. Override ProcessRecord() &#8211; this method is called once per pipeline input object. To accept input from specific property names on piped objects, use [Parameter(ValueFromPipelineByPropertyName = true)] and name your property to match the property name on the incoming object (or use [Alias()] to map different names). For multiple pipeline-bindable properties, only one can use ValueFromPipeline = true; others should use ValueFromPipelineByPropertyName = true.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. How do I load a C# PowerShell cmdlet into a PowerShell session?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Build the C# class library project to produce a .dll assembly. In a PowerShell session, run Import-Module &#8216;C:pathtoYourModule.dll&#8217;. The cmdlets defined in the assembly become immediately available. To make the module persistent across sessions, place the .dll in a folder under $env:PSModulePath (typically %USERPROFILE%DocumentsPowerShellModulesYourModule) and PowerShell will find it automatically. For distribution, package the .dll and an optional .psd1 manifest file as a module directory.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Create PowerShell cmdlets in C# using Visual Studio: 15 steps covering project setup, Cmdlet inheritance, CmdletAttribute, parameter decorators, pipeline input, and output type handling &#8211; with complete C# code examples.&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],"tags":[4143,4242,4371,4359,4635,4194],"coauthors":[6802],"class_list":["post-2172","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","tag-net","tag-basics","tag-c","tag-development","tag-powershell","tag-visual-basic"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2172","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=2172"}],"version-history":[{"count":14,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2172\/revisions"}],"predecessor-version":[{"id":110043,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2172\/revisions\/110043"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=2172"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=2172"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=2172"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=2172"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}