{"id":519,"date":"2021-10-14T18:00:16","date_gmt":"2021-10-15T01:00:16","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/powershell-community\/?p=519"},"modified":"2022-12-17T13:24:56","modified_gmt":"2022-12-17T21:24:56","slug":"a-closer-look-at-the-crescendo-configuration","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/powershell-community\/a-closer-look-at-the-crescendo-configuration\/","title":{"rendered":"A closer look at the Crescendo configuration"},"content":{"rendered":"<p>In my <a href=\"https:\/\/devblogs.microsoft.com\/powershell-community\/a-closer-look-at-the-parsing-code-of-a-crescendo-output-handler\/\">previous post<\/a>, I looked at the details of a Crescendo output handler from my <a href=\"https:\/\/github.com\/sdwheeler\/ToolModules\/tree\/main\/Modules\/VssAdmin\">VssAdmin<\/a> module. In this post, I explain the details of a cmdlet definition in the Crescendo JSON configuration file.<\/p>\n<h2>The purpose of the configuration<\/h2>\n<p>The structure for the interface of a cmdlet is a reasonably predictable thing.<\/p>\n<ul>\n<li>The cmdlet uses a standard <em>Verb-Noun<\/em> format<\/li>\n<li>The cmdlets take input using sets of parameters<\/li>\n<li>Cmdlets that make changes to the system support <code>-Confirm<\/code> and <code>-WhatIf<\/code> parameters<\/li>\n<\/ul>\n<p>The pattern of the script code to support these fits a template.<\/p>\n<p>The more difficult part of the cmdlet is in the code that does the work. Crescendo separates the functional code (the output handler) from the cmdlet interface code. The Crescendo configuration file defines the interfaces of cmdlets that you want Crescendo to create.<\/p>\n<p>The Crescendo configuration file is a JSON file containing an array of cmdlet definitions. JSON provides an expressive, structured syntax for defining the properties of objects. But so does, PowerShell. So why use JSON and not a PowerShell data (PSD1) file? The answer is simple: schema! Unlike PowerShell&#8217;s PSD1 files, JSON supports a schema. Having a schema ensures that the syntax of your definition is correct. And with tools like Visual Studio Code (VS Code), the schema provides IntelliSense, making it easier to author.<\/p>\n<h2>Defining a cmdlet interface<\/h2>\n<p>The structure of a cmdlet definition can be divided into three property categories in the JSON file:<\/p>\n<ul>\n<li>Required properties \n<ul>\n<li><strong>Verb<\/strong><\/li>\n<li><strong>Noun<\/strong><\/li>\n<li><strong>OriginalName<\/strong><\/li>\n<li><strong>OriginalCommandElements[]<\/strong><\/li>\n<li><strong>OutputHandlers[]<\/strong><\/li>\n<\/ul>\n<\/li>\n<li>As-required properties \n<ul>\n<li><strong>DefaultParameterSetName<\/strong><\/li>\n<li><strong>Parameters[]<\/strong><\/li>\n<\/ul>\n<\/li>\n<li>Nice-to-have properties \n<ul>\n<li>&#8220;Help&#8221; properties like <strong>Description<\/strong>, <strong>Usage<\/strong>, and <strong>Examples[]<\/strong><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>You might notice that defining <strong>Parameters<\/strong> is optional. This is not uncommon. In my VssAdmin module, the cmdlets <code>Get-VssProvider<\/code>, <code>Get-VssVolume<\/code>, and <code>Get-VssWriter<\/code> do not have parameters. These simple cmdlets don&#8217;t require any input to return the requested information.<\/p>\n<p>Let&#8217;s take a closer look at a simple cmdlet definition.<\/p>\n<ul>\n<li>The <strong>Verb<\/strong> and <strong>Noun<\/strong> properties form the name of the cmdlet.<\/li>\n<li>The <strong>OriginalName<\/strong> property contains the path to the native command that the cmdlet runs to get the output.<\/li>\n<li>The <strong>OriginalCommandElements<\/strong> is an array of strings that are passed to the native command as parameters. A typical CLI like <code>vssadmin<\/code> has its own set of commands that perform different actions. Those commands may have additional parameters. In this example, the <code>vssadmin list providers<\/code> command has no additional parameters.<\/li>\n<li>The <strong>OutputHandlers<\/strong> property is an array containing one or more handler definitions. The handlers receive the output of the native command and return an object containing the data parsed from the output. \n<ul>\n<li>The <strong>HandlerType<\/strong> can be <code>Inline<\/code>, <code>Function<\/code>, or <code>Script<\/code>. In this example I use <code>Function<\/code>.<\/li>\n<li>The <strong>Handler<\/strong> is the name of the Script or Function to be called, or the actual PowerShell code to run if the type is <code>Inline<\/code>.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<pre><code class=\"language-json\">{\n    \"$schema\": \"https:\/\/aka.ms\/Crescendo\/Schema.json\",\n    \"Commands\": [\n        {\n            \"Verb\": \"Get\",\n            \"Noun\": \"VssProvider\",\n            \"OriginalName\": \"$env:Windir\/system32\/vssadmin.exe\",\n            \"OriginalCommandElements\": [\n                \"list\",\n                \"providers\"\n            ],\n            \"Description\": \"List registered volume shadow copy providers\",\n            \"Usage\": {\n                \"Synopsis\": \"List registered volume shadow copy providers\"\n            },\n            \"Examples\": [\n                {\n                    \"Command\": \"Get-VssProvider\",\n                    \"Description\": \"Get a list of VSS Providers\",\n                    \"OriginalCommand\": \"vssadmin list providers\"\n                }\n            ],\n            \"OutputHandlers\": [\n                {\n                    \"ParameterSetName\": \"Default\",\n                    \"HandlerType\": \"Function\",\n                    \"Handler\": \"ParseProvider\"\n                }\n            ]\n        }\n    ]\n}<\/code><\/pre>\n<p>The remaining properties &#8212; <strong>Description<\/strong>, <strong>Usage<\/strong>, and <strong>Examples<\/strong> &#8212; are optional. Crescendo uses these values to create the comment-based help for the cmdlet when it creates the module.<\/p>\n<h2>Defining parameters and parameter sets<\/h2>\n<p>Some of the <code>vssadmin<\/code> commands have optional parameters that can be used in various combinations. For example:<\/p>\n<ul>\n<li><code>vssadmin List Shadows [\/For=ForVolumeSpec] [\/Shadow=ShadowId|\/Set=ShadowSetId]<\/code> &#8211; 3 optional parameters in 2 parameter sets<\/li>\n<li><code>vssadmin List ShadowStorage [\/For=ForVolumeSpec|\/On=OnVolumeSpec]<\/code> &#8211; 2 parameter sets with 1 optional parameter each<\/li>\n<\/ul>\n<p>Let&#8217;s take a look at the help for <code>vssadmin Resize ShadowStorage<\/code>.<\/p>\n<pre><code>vssadmin 1.1 - Volume Shadow Copy Service administrative command-line tool\n(C) Copyright 2001-2013 Microsoft Corp.\n\nResize ShadowStorage \/For=ForVolumeSpec \/On=OnVolumeSpec \/MaxSize=MaxSizeSpec\n    - Resizes the maximum size for a shadow copy storage association between\n    ForVolumeSpec and OnVolumeSpec.  Resizing the storage association may cause shadow\n    copies to disappear.  As certain shadow copies are deleted, the shadow copy storage\n    space will then shrink.  If MaxSizeSpec is set to the value UNBOUNDED, the shadow copy\n    storage space will be unlimited.  MaxSizeSpec can be specified in bytes or as a\n    percentage of the ForVolumeSpec storage volume.  For byte level specification,\n    MaxSizeSpec must be 320MB or greater and accepts the following suffixes: KB, MB, GB, TB,\n    PB and EB.  Also, B, K, M, G, T, P, and E are acceptable suffixes.  To specify MaxSizeSpec\n    as percentage, use the % character as the suffix to the numeric value.  If a suffix is not\n    supplied, MaxSizeSpec is in bytes.\n\n    Example Usage:  vssadmin Resize ShadowStorage \/For=C: \/On=D: \/MaxSize=900MB\n                    vssadmin Resize ShadowStorage \/For=C: \/On=D: \/MaxSize=UNBOUNDED\n                    vssadmin Resize ShadowStorage \/For=C: \/On=C: \/MaxSize=20%\n<\/code><\/pre>\n<p>The <code>vssadmin Resize ShadowStorage<\/code> command has three required parameters, but the third parameter <code>\/MaxSize<\/code> can take three different types of input. In PowerShell, we prefer fixed types for parameter values. We can solve this by creating three different parameters, each used in a different parameter set.<\/p>\n<p>The following JSON defines the <code>Resize-VssShadowStorage<\/code> cmdlet. The definition starts with the required properties and some help information. This definition also has <strong>SupportsShouldProcess<\/strong> set to <code>true<\/code>. With this property, Crescendo adds the <code>[SupportsShouldProcess()]<\/code> attribute to the cmdlet, which automatically adds the <code>-WhatIf<\/code> and <code>-Confirm<\/code> parameters.<\/p>\n<p>The interesting part starts in the parameter definitions.<\/p>\n<pre><code class=\"language-json\">  {\n      \"Verb\": \"Resize\",\n      \"Noun\": \"VssShadowStorage\",\n      \"OriginalName\": \"c:\/windows\/system32\/vssadmin.exe\",\n      \"OriginalCommandElements\": [\n          \"Resize\",\n          \"ShadowStorage\"\n      ],\n      \"Description\": \"Resizes the maximum size for a shadow copy storage association between ForVolumeSpec and OnVolumeSpec. Resizing the storage association may cause shadow copies to disappear. As certain shadow copies are deleted, the shadow copy storage space will then shrink.\",\n      \"Usage\": {\n          \"Synopsis\": \"Resize the maximum size of a shadow copy storage association.\"\n      },\n      \"Examples\": [\n          {\n              \"Command\": \"Resize-VssShadowStorage -For C: -On C: -MaxSize 900MB\",\n              \"Description\": \"Set the new storage size to 900MB\",\n              \"OriginalCommand\": \"vssadmin Resize ShadowStorage \/For=C: \/On=C: \/MaxSize=900MB\"\n          },\n          {\n              \"Command\": \"Resize-VssShadowStorage -For C: -On C: -MaxPercent '20%'\",\n              \"Description\": \"Set the new storage size to 20% of the OnVolume size\",\n              \"OriginalCommand\": \"vssadmin Resize ShadowStorage \/For=C: \/On=C: \/MaxSize=20%\"\n          },\n          {\n              \"Command\": \"Resize-VssShadowStorage -For C: -On C: -Unbounded\",\n              \"Description\": \"Set the new storage size to unlimited\",\n              \"OriginalCommand\": \"vssadmin Resize ShadowStorage \/For=C: \/On=C: \/MaxSize=UNBOUNDED\"\n          }\n      ],\n      \"SupportsShouldProcess\": true,\n      \"DefaultParameterSetName\": \"ByMaxSize\",\n      \"Parameters\": [\n          {\n              \"OriginalName\": \"\/For=\",\n              \"Name\": \"For\",\n              \"ParameterType\": \"string\",\n              \"ParameterSetName\": [ \"ByMaxSize\", \"ByMaxPercent\", \"ByMaxUnbound\" ],\n              \"NoGap\": true,\n              \"Description\": \"Provide a volume name like 'C:'\"\n          },\n          {\n              \"OriginalName\": \"\/On=\",\n              \"Name\": \"On\",\n              \"ParameterType\": \"string\",\n              \"ParameterSetName\": [ \"ByMaxSize\", \"ByMaxPercent\", \"ByMaxUnbound\" ],\n              \"Mandatory\": true,\n              \"NoGap\": true,\n              \"Description\": \"Provide a volume name like 'C:'\"\n          },\n          {\n              \"OriginalName\": \"\/MaxSize=\",\n              \"Name\": \"MaxSize\",\n              \"ParameterType\": \"Int64\",\n              \"ParameterSetName\": [ \"ByMaxSize\" ],\n              \"AdditionalParameterAttributes\": [\n                  \"[ValidateScript({$_ -ge 320MB})]\"\n              ],\n              \"Mandatory\": true,\n              \"NoGap\": true,\n              \"Description\": \"New maximum size in bytes. Must be 320MB or more.\"\n          },\n          {\n              \"OriginalName\": \"\/MaxSize=\",\n              \"Name\": \"MaxPercent\",\n              \"ParameterType\": \"string\",\n              \"ParameterSetName\": [ \"ByMaxPercent\" ],\n              \"AdditionalParameterAttributes\": [\n                  \"[ValidatePattern('[0-9]+%')]\"\n              ],\n              \"Mandatory\": true,\n              \"NoGap\": true,\n              \"Description\": \"A percentage string like '20%'.\"\n          },\n          {\n              \"OriginalName\": \"\/MaxSize=UNBOUNDED\",\n              \"Name\": \"Unbounded\",\n              \"ParameterType\": \"switch\",\n              \"ParameterSetName\": [ \"ByMaxUnbound\" ],\n              \"Mandatory\": true,\n              \"Description\": \"Sets the maximum size to UNBOUNDED.\"\n          }\n      ],\n      \"OutputHandlers\": [\n          {\n              \"ParameterSetName\": \"ByMaxSize\",\n              \"HandlerType\": \"Function\",\n              \"Handler\": \"ParseResizeShadowStorage\"\n          },\n          {\n              \"ParameterSetName\": \"ByMaxPercent\",\n              \"HandlerType\": \"Function\",\n              \"Handler\": \"ParseResizeShadowStorage\"\n          },\n          {\n              \"ParameterSetName\": \"ByMaxUnbound\",\n              \"HandlerType\": \"Function\",\n              \"Handler\": \"ParseResizeShadowStorage\"\n          }\n      ]\n  }<\/code><\/pre>\n<p>The parameters have the following properties:<\/p>\n<ul>\n<li><strong>OriginalName<\/strong> contains the original parameter used by the native command. Crescendo combines the value passed into the cmdlet with the original parameter string. The resulting native parameter is added to the original native command that gets executed by the cmdlet.<\/li>\n<li><strong>Name<\/strong> is the name of the parameter for the PowerShell cmdlet you are defining.<\/li>\n<li><strong>ParameterType<\/strong> is the type of the parameter for the cmdlet.<\/li>\n<li><strong>ParameterSetName<\/strong> is an array of one or more parameter set names that the parameter belongs to.<\/li>\n<li><strong>AdditionalParameterAttributes<\/strong> is an array of strings that contain any additional attribute you want added to the parameter. You can use this to add parameter validation attributes.<\/li>\n<li><strong>NoGap<\/strong> tells Crescendo not so use a space between the <strong>OriginalName<\/strong> parameter and the value passed into the cmdlet.<\/li>\n<li><strong>Description<\/strong> is the description of the parameter displayed by <code>Get-Help<\/code>.<\/li>\n<\/ul>\n<p>For this cmdlet, the first two parameters <code>-For<\/code> and <code>-On<\/code> are in all three parameter sets. The remaining three parameters are unique to each parameter set.<\/p>\n<ul>\n<li>The <code>-MaxSize<\/code> parameter accepts a 64-bit integer. That value is added to the <code>\/MaxSize=<\/code> string to form the native parameter. The parameter validation ensures that the value passed in is greater than 320MB.<\/li>\n<li>The <code>-MaxPercent<\/code> parameter accepts a string containing a percentage value. That string is added to the <code>\/MaxSize=<\/code> string to form the native parameter. The parameter validation ensures that the string represents a valid percentage.<\/li>\n<li>The <code>-Unbounded<\/code> switch parameter is used select a native parameter of <code>\/MaxSize=UNBOUNDED<\/code>.<\/li>\n<\/ul>\n<h2>Defining the output handlers<\/h2>\n<p>Since there are three parameters sets, I need to define an output handler for each set. You could have a separate function for each set. In my case that was not necessary. The <code>vssadmin Resize ShadowStorage<\/code> command does not have any output unless there is an error. Also, since the command makes changes, I thought I should call <code>Get-VssShadowStorage<\/code> to show the new settings.<\/p>\n<pre><code class=\"language-powershell\">function ParseResizeShadowStorage {\n    param(\n        [Parameter(Mandatory)]\n        $cmdResults\n    )\n    $textBlocks = ($cmdResults | Out-String) -split \"`r`n`r`n\"\n\n    if ($textBlocks[1] -like 'Error*') {\n        Write-Error $textBlocks[1]\n    } elseif ($textBlocks[1] -like 'Success*') {\n        Get-VssShadowStorage\n    } else {\n        $textBlocks[1]\n    }\n}<\/code><\/pre>\n<h2>The final step<\/h2>\n<p>Once the configuration file was complete, I used the <code>Export-CrescendoModule<\/code> cmdlet to create my <strong>VssAdmin<\/strong> module.<\/p>\n<pre><code class=\"language-powershell\">Export-CrescendoModule -ConfigurationFile vssadmin.crescendo.config.json -ModuleName VssAdmin.psm1<\/code><\/pre>\n<p>Crescendo created two new files:<\/p>\n<ul>\n<li>The module code file &#8211; <code>VssAdmin.psm1<\/code><\/li>\n<li>The module manifest file &#8211; <code>VssAdmin.psd1<\/code><\/li>\n<\/ul>\n<p>These are the only two files that need to be installed. The <code>VssAdmin.psm1<\/code> file contains all the cmdlets that Crescendo generated from the configuration and the Output Handler functions I wrote to parse the output into objects.<\/p>\n<h2>Conclusion<\/h2>\n<p>Crescendo separates the structural interface code required to create a cmdlet from the functional code that extracts the data. The configuration file defines the cmdlet interfaces. The <code>Export-CrescendoModule<\/code> cmdlet creates a new module containing the cmdlets defined in the configuration (complete with the help text provided) and the output handler functions required by the cmdlets. It also creates a proper module manifest, complete with exports for the new cmdlets.<\/p>\n<h2>Resources<\/h2>\n<p>Posts in this series<\/p>\n<ul>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/powershell-community\/my-crescendo-journey\/\">My Crescendo journey<\/a> \n<ul>\n<li><a href=\"https:\/\/github.com\/sdwheeler\/ToolModules\/tree\/main\/Modules\/VssAdmin\">My VssAdmin module<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/powershell-community\/converting-string-output-to-objects\/\">Converting string output to objects<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/powershell-community\/a-closer-look-at-the-parsing-code-of-a-crescendo-output-handler\/\">A closer look at a Crescendo Output Handler<\/a><\/li>\n<li>A closer look at a Crescendo configuration file &#8211; this post<\/li>\n<\/ul>\n<p>References<\/p>\n<p><!-- link reference --><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In my previous post, I looked at the details of a Crescendo output handler from my VssAdmin module. In this post, I explain the details of a cmdlet definition in the Crescendo JSON configuration file. The purpose of the configuration The structure for the interface of a cmdlet is a reasonably predictable thing. The cmdlet [&hellip;]<\/p>\n","protected":false},"author":7674,"featured_media":520,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[13],"tags":[51,53,44,52],"class_list":["post-519","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-powershell","tag-cmdlet","tag-configuration","tag-crescendo","tag-json"],"acf":[],"blog_post_summary":"<p>In my previous post, I looked at the details of a Crescendo output handler from my VssAdmin module. In this post, I explain the details of a cmdlet definition in the Crescendo JSON configuration file. The purpose of the configuration The structure for the interface of a cmdlet is a reasonably predictable thing. The cmdlet [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts\/519","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/users\/7674"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/comments?post=519"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts\/519\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/media\/520"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/media?parent=519"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/categories?post=519"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/tags?post=519"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}