{"id":10370,"date":"2015-12-09T06:00:29","date_gmt":"2015-12-09T14:00:29","guid":{"rendered":"https:\/\/www.sapien.com\/blog\/?p=10370"},"modified":"2015-12-10T11:29:46","modified_gmt":"2015-12-10T19:29:46","slug":"displaying-help-for-a-script-in-an-executable-file","status":"publish","type":"post","link":"https:\/\/dev.sapien.com\/blog\/2015\/12\/09\/displaying-help-for-a-script-in-an-executable-file\/","title":{"rendered":"Displaying Help for a Script in an Executable File"},"content":{"rendered":"<p>This is the third blog in a multi-part series about designing a Windows PowerShell scripts that will be packaged in an executable file.<\/p>\n<ul>\n<li><a href=\"https:\/\/wp.me\/p3tXTf-2FE\" target=\"_blank\">Passing Parameters to a Script in an Executable File<\/a> explains how to use the special parsing features of PowerShell Studio and PrimalScript to make passing parameters easy for PowerShell users and authors.<\/li>\n<li><a href=\"https:\/\/wp.me\/p3tXTf-2HV\" target=\"_blank\">Parsing Parameters for a Script in an Executable File<\/a> explains how to parse parameters manually for special uses.<\/li>\n<li><strong>Displaying Help for a Script in an Executable File<\/strong> explains how to display help for a script in an executable file.<\/li>\n<li><a href=\"https:\/\/wp.me\/p3tXTf-2L5\" target=\"_blank\">Output from a Script in an Executable File<\/a> explains how to manage string output from a script in an executable file.<\/li>\n<\/ul>\n<p>&#8212;&#8211;<br \/>\nThe <a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=289584\" target=\"_blank\">Get-Help<\/a> cmdlet displays help topics about concepts and modules (About topics) and about commands (cmdlets, scripts, functions, workflows, and CIM commands). It does not display help topics for applications in executable files.<\/p>\n<p>When you wrap a Windows PowerShell script in an executable file, even if the script has comment-based or XML help, Get-Help cannot find the help file. However, you can use a few techniques to restore some help functionality.<\/p>\n<h1>Recognize Help Strings<\/h1>\n<p>As we learned in <a href=\"https:\/\/wp.me\/p3tXTf-2FE\" target=\"_blank\">Part 1 of this series<\/a>, all parameters in a script that is wrapped in an executable file must take string types or types that Windows PowerShell converts from strings. So, we can use selected strings to recognize that the user is looking for help, instead of trying to run the command.<\/p>\n<p>For example, I have a Get-InputTypes.ps1 script that finds parameters that take pipeline input. The script has a mandatory CmdletName parameter that specifies the name of the command to analyze.<\/p>\n<p>When I run the .exe normally, it returns input type information.<\/p>\n<pre class=\"output\">PS C:\\&gt; .\\Get-InputTypes.exe -CmdletName Get-Command\r\n\r\nTypeNames       ParameterName ValueFromPipelineByPropertyName\r\n---------       ------------- -------------------------------\r\nSystem.String[] Name          True<\/pre>\n<p>The CmdletName parameter is positional, so I don&#8217;t need to type the parameter name.<\/p>\n<pre class=\"output\"> .\\Get-InputTypes.exe Invoke-Command\r\n\r\nTypeNames                             ParameterName ValueFromPipelineByPropertyName\r\n---------                             ------------- ---------------------------\r\nSystem.Management.Automation.PSObject InputObject True<\/pre>\n<p>But, when I run it with a value of &#8216;Help&#8217; or &#8216;\/?&#8217;, it displays\u00a0help.<\/p>\n<pre class=\"output\">PS C:\\&gt; .\\Get-InputTypes.exe \/?\r\nNAME\r\nGet-InputTypes.exe\r\nSYNOPSIS\r\nGets input types for cmdlet help topics.\r\nSYNTAX\r\nGet-InputTypes.ps1 [-CmdletName] &lt;String&gt; [-Full &lt;String&gt;]\r\n\r\nDESCRIPTION\r\nThis app gets the input types for a cmdlet help topic, that is, it gets the .NET types that\r\nyou can pipe to the cmdlet. By default, it gets only types that can be piped\r\n...<\/pre>\n<p>Here&#8217;s how I did it.<\/p>\n<p>First, I added a $HelpString variable that contains help for the cmdlet. (For details, see <a href=\"#helpstring\">Create a Help String<\/a>&#8216;. Next, I changed the\u00a0first parameter in the script (Position 0)\u00a0to recognize values of &#8216;\/?&#8217; and &#8216;Help&#8217;. Windows PowerShell has special processing for the &#8216;-?&#8217; string, so I avoid that one.<\/p>\n<p>This script has a CmdletName parameter that is mandatory and, by default, position 0.<\/p>\n<pre lang=\"PowerShell\">[CmdletBinding()]\r\nParam\r\n(\r\n[Parameter(Mandatory = $true)]\r\n[ValidateScript({Get-Command $_})]\r\n[String]$CmdletName,\r\n\r\n[Parameter()]\r\n[ValidateSet('True', 'False')]\r\n[String]$Full\r\n)<\/pre>\n<p>To add help functionality, removed the ValidateScript attribute.<\/p>\n<pre lang=\"PowerShell\">[CmdletBinding()]\r\nParam\r\n(\r\n[Parameter(Mandatory = $true)]\r\n[String]$CmdletName,\r\n\r\n[Parameter()]\r\n[ValidateSet('True', 'False')]\r\n[String]$Full\r\n)<\/pre>\n<p>Then, I added a condition that looks for CmdletName values of &#8216;Help&#8217; and &#8216;\/?&#8217;. If found, the script displays a $HelpString string, instead of its regular processing.<\/p>\n<pre lang=\"PowerShell\">if ($CmdletName -eq 'Help' -or $CmdletName -eq '\/?')\r\n{\r\n    $HelpString\r\n}\r\nelseif ($full -eq 'True')\r\n...<\/pre>\n<h1>Add a Help Parameter<\/h1>\n<p>If you want to be a bit more sophisticated, rather than using the CmdletName parameter for an unintended use, you can add a Help parameter.<\/p>\n<p>Here&#8217;s the original parameter set of the script:<\/p>\n<pre lang=\"PowerShell\">[CmdletBinding()]\r\nParam\r\n(\r\n[Parameter(Mandatory = $true)]\r\n[ValidateScript({Get-Command $_})]\r\n[String]$CmdletName,\r\n\r\n[Parameter()]\r\n[ValidateSet('True', 'False')]\r\n[String]$Full\r\n)<\/pre>\n<p>I added a Help parameter that takes a string value. Then, because the CmdletName and Help parameters are exclusive, I&#8217;ve created separate &#8216;DefaultSet&#8217; and &#8216;HelpSet&#8217; parameter sets.<\/p>\n<p>In a script or function, the parameters are positional by default. But when you add parameter sets, all parameters become named. For most cases, named parameters are a best practice. However, in this case, I want the user to be able to type: &#8216;Get-InputTypes.exe \/?&#8217; so I make the Help parameter mandatory and position 0 in the HelpSet parameter set.<\/p>\n<p>Here&#8217;s the result.<\/p>\n<pre lang=\"PowerShell\">[CmdletBinding(DefaultParameterSetName = 'DefaultSet')]\r\nparam\r\n(\r\n[Parameter(ParameterSetName = 'DefaultSet', Mandatory = $true. Position=0)]\r\n[ValidateScript({Get-Command $_})]\r\n[String]$CmdletName,\r\n\r\n[Parameter(ParameterSetName = 'DefaultSet', Position=1)]\r\n[ValidateSet('True', 'False')]\r\n[String]$Full,\r\n\r\n[Parameter(ParameterSetName = 'HelpSet', Mandatory = $true, Position = 0)]\r\n[String]$Help\r\n)<\/pre>\n<p>I also added code to take any value of the Help parameter.<\/p>\n<pre lang=\"PowerShell\">if ($Help)\r\n{ \r\n    $HelpString\r\n}\r\nelseif ($Full -eq 'True')\r\n{\r\n...<\/pre>\n<p>Now, when I wrap the script in an exe and run it:<\/p>\n<pre class=\"output\">PS C:\\&gt;.\\Get-InputTypes.exe -CmdletName Set-ExecutionPolicy\r\n\r\nTypeNames                            ParameterName ValueFromPipelineByPropertyName\r\n---------                            ------------- --------------------------\r\nMicrosoft.PowerShell.ExecutionPolicy ExecutionPolicy True\r\n\r\nPS C:\\&gt; .\\Get-InputTypes.exe -Help True\r\nNAME\r\nGet-InputTypes.exe\r\nSYNOPSIS\r\nGets input types for cmdlet help\r\nSYNTAX\r\nGet-InputTypes.exe [-CmdletName] &lt;String&gt; [-Full &lt;String&gt;]\r\nGet-InputTypes.exe [-Help] &lt;String&gt;\r\n...\r\n\r\nPS C:\\&gt; .\\Get-InputTypes.exe \/?\r\nNAME\r\nGet-InputTypes.exe\r\nSYNOPSIS\r\nGets input types for cmdlet help\r\nSYNTAX\r\nGet-InputTypes.exe [-CmdletName] &lt;String&gt; [-Full &amp;lt:String&gt;]\r\nGet-InputTypes.exe [-Help] &lt;String&gt;\r\n...<\/pre>\n<h1>Make Help the Default<\/h1>\n<p>Many traditional command-line tools display help as the default. That is, if you run the command without parameters, it displays help. This is not true for Windows PowerShell. In fact, many PowerShell cmdlets, including Get-Command, Get-Process, and Get-Module, have very useful default displays. But, if you want to make your .exe file display help by default, it&#8217;s pretty easy to do that.<\/p>\n<p>If you made your Position 0 parameter accepts special values, like &#8216;\/?&#8217; and &#8216;Help&#8217;, you can make that parameter optional. Then, add a case for a $Null or empty string value (if !$CmdletName&#8230;).<\/p>\n<pre lang=\"PowerShell\">[CmdletBinding()]\r\nParam\r\n(\r\n[Parameter()]\r\n[String]$CmdletName,\r\n\r\n[Parameter()]\r\n[ValidateSet('True', 'False')]\r\n[String]$Full\r\n)\r\n\r\nif (!$CmdletName -or $CmdletName -eq 'Help' -or $CmdletName -eq '\/?')\r\n{\r\n    $HelpString\r\n}\r\nelseif ($full -eq 'True')\r\n...<\/pre>\n<p>If you created a Help parameter in its own parameter set, to make the Help parameter accept an empty value:<\/p>\n<p>&#8212; Make the Help parameter optional<br \/>\n&#8212; Make the HelpSet parameter set the default<br \/>\n&#8212; Check for the HelpSet parameter set in the script<\/p>\n<p>For example:<\/p>\n<pre lang=\"PowerShell\">[CmdletBinding(DefaultParameterSetName = 'HelpSet')]\r\nparam\r\n(\r\n[Parameter(ParameterSetName = 'DefaultSet', Mandatory = $true)]\r\n[ValidateScript({Get-Command $_})]\r\n[String]$CmdletName,\r\n\r\n[Parameter(ParameterSetName = 'DefaultSet')]\r\n[ValidateSet('True', 'False')]\r\n[String]$Full,\r\n\r\n[Parameter(ParameterSetName = 'HelpSet', Mandatory = $false, Position = 0)]\r\n[String]$Help\r\n)\r\n\r\nif ($PSCmdlet.ParameterSetName -eq 'HelpSet')\r\n{ \r\n    $HelpString\r\n}\r\nelseif ($Full -eq 'True')\r\n{\r\n...<\/pre>\n<p>Now, when I wrap the script in an exe and run it with no parameters, I get the help.<\/p>\n<pre class=\"output\">PS C:\\&gt; .\\Get-InputTypes.exe\r\nNAME\r\nGet-InputTypes.exe\r\nSYNOPSIS\r\nGets input types for cmdlet help topics.\r\nSYNTAX\r\nGet-InputTypes.ps1 [[-Help] &lt;String&gt;]\r\nGet-InputTypes.ps1 [-CmdletName] &lt;String&gt; [-Full &lt;String&gt;]\r\n\r\nDESCRIPTION\r\nThis app gets the input types for a cmdlet help topic, that is, it gets the .NET types that\r\nyou can pipe to the cmdlet. By default, it gets only types that can be piped\r\n...<\/pre>\n<p>And, I can still use the Help parameter explicitly with any value.<\/p>\n<pre class=\"output\">PS C:\\&gt; .\\Get-InputTypes.exe -Help \/?\r\nNAME\r\nGet-InputTypes.exe\r\nSYNOPSIS\r\nGets input types for cmdlet help topics.\r\nSYNTAX\r\nGet-InputTypes.ps1 [[-Help] &lt;String&gt;]\r\nGet-InputTypes.ps1 [-CmdletName] &lt;String&gt; [-Full &lt;String&gt;]\r\n\r\nDESCRIPTION\r\nThis app gets the input types for a cmdlet help topic, that is, it gets the .NET types that\r\nyou can pipe to the cmdlet. By default, it gets only types that can be piped\r\n...<\/pre>\n<p><a name=\"#helpstring\"><\/a><\/p>\n<h1>Create a Help String<\/h1>\n<p>There are many ways to create the $HelpString value for a Windows PowerShell script in an executable file.<\/p>\n<p>To make it look like the Get-Help display, I write comment-based or XML help for script, run &#8216;Get-Help &lt;ScriptName&gt;.ps1 -Full,&#8217; copy the content that Get-Help returns, and paste it in a here-string in my script. To make it easier to read, I add a few blank lines at the top and bottom of the string.<\/p>\n<pre lang=\"PowerShell\">$helpString = @\"\r\n\r\nNAME\r\nC:\\ps-test\\Get-InputTypes.ps1\r\n\r\nSYNOPSIS\r\nGets input types for cmdlet help\r\n\r\nSYNTAX\r\nGet-InputTypes.exe [-CmdletName] &lt;String&gt; [-Full &amp;lt:String&gt;]\r\nGet-InputTypes.exe [-Help] &lt;String&gt;\r\n\r\nDESCRIPTION\r\nThis script gets the input types for a cmdlet\r\nhelp topic, that is, it gets the .NET types that\r\nyou can pipe to the cmdlet.\r\n\r\n\u2026\r\n\"@<\/pre>\n<p>It&#8217;s much easier to use Get-Help to display help, but when it&#8217;s not available, it&#8217;s great to have easy substitutes.<\/p>\n<p><i>June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at <\/i><a href=\"mailto:juneb@sapien.com\"><em>juneb@sapien.com<\/em><\/a><em> or follow her on Twitter at <\/em><a href=\"https:\/\/twitter.com\/juneb_get_help\"><em>@juneb_get_help<\/em><\/a><em>.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is the third blog in a multi-part series about designing a Windows PowerShell scripts that will be packaged in an executable file. Passing Parameters to a Script in an Executable File explains how to use the special parsing features of PowerShell Studio and PrimalScript to make passing parameters easy for PowerShell users and authors. [&hellip;]<\/p>\n","protected":false},"author":31,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[932,1034,2,283,703,410,25],"tags":[1042,934,28,1016,37,48,997],"class_list":["post-10370","post","type-post","status-publish","format-standard","hentry","category-beginners","category-best-practices","category-general","category-howto","category-powershell-studio","category-primalscript-software-news","category-windows-powershell","tag-executable-files","tag-juneb","tag-powershell","tag-powershell-studio","tag-primalscript","tag-training","tag-windows-powershell"],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/10370","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/users\/31"}],"replies":[{"embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/comments?post=10370"}],"version-history":[{"count":53,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/10370\/revisions"}],"predecessor-version":[{"id":10626,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/10370\/revisions\/10626"}],"wp:attachment":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/media?parent=10370"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/categories?post=10370"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/tags?post=10370"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}