Displaying Help for a Script in an Executable File

December 9th, 2015 by June Blender
Last updated on December 10th, 2015

 

This is the third blog in a multi-part series about designing a Windows PowerShell scripts that will be packaged in an executable file.

—–
The Get-Help 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.

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.

Recognize Help Strings

As we learned in Part 1 of this series, 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.

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.

When I run the .exe normally, it returns input type information.

PS C:\> .\Get-InputTypes.exe -CmdletName Get-Command

TypeNames       ParameterName ValueFromPipelineByPropertyName
---------       ------------- -------------------------------
System.String[] Name          True

The CmdletName parameter is positional, so I don’t need to type the parameter name.

 .\Get-InputTypes.exe Invoke-Command

TypeNames                             ParameterName ValueFromPipelineByPropertyName
---------                             ------------- ---------------------------
System.Management.Automation.PSObject InputObject True

But, when I run it with a value of ‘Help’ or ‘/?’, it displays help.

PS C:\> .\Get-InputTypes.exe /?
NAME
Get-InputTypes.exe
SYNOPSIS
Gets input types for cmdlet help topics.
SYNTAX
Get-InputTypes.ps1 [-CmdletName] <String> [-Full <String>]

DESCRIPTION
This app gets the input types for a cmdlet help topic, that is, it gets the .NET types that
you can pipe to the cmdlet. By default, it gets only types that can be piped
...

Here’s how I did it.

First, I added a $HelpString variable that contains help for the cmdlet. (For details, see Create a Help String‘. Next, I changed the first parameter in the script (Position 0) to recognize values of ‘/?’ and ‘Help’. Windows PowerShell has special processing for the ‘-?’ string, so I avoid that one.

This script has a CmdletName parameter that is mandatory and, by default, position 0.

[CmdletBinding()]
Param
(
[Parameter(Mandatory = $true)]
[ValidateScript({Get-Command $_})]
[String]$CmdletName,
 
[Parameter()]
[ValidateSet('True', 'False')]
[String]$Full
)

To add help functionality, removed the ValidateScript attribute.

[CmdletBinding()]
Param
(
[Parameter(Mandatory = $true)]
[String]$CmdletName,
 
[Parameter()]
[ValidateSet('True', 'False')]
[String]$Full
)

Then, I added a condition that looks for CmdletName values of ‘Help’ and ‘/?’. If found, the script displays a $HelpString string, instead of its regular processing.

if ($CmdletName -eq 'Help' -or $CmdletName -eq '/?')
{
    $HelpString
}
elseif ($full -eq 'True')
...

Add a Help Parameter

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.

Here’s the original parameter set of the script:

[CmdletBinding()]
Param
(
[Parameter(Mandatory = $true)]
[ValidateScript({Get-Command $_})]
[String]$CmdletName,
 
[Parameter()]
[ValidateSet('True', 'False')]
[String]$Full
)

I added a Help parameter that takes a string value. Then, because the CmdletName and Help parameters are exclusive, I’ve created separate ‘DefaultSet’ and ‘HelpSet’ parameter sets.

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: ‘Get-InputTypes.exe /?’ so I make the Help parameter mandatory and position 0 in the HelpSet parameter set.

Here’s the result.

[CmdletBinding(DefaultParameterSetName = 'DefaultSet')]
param
(
[Parameter(ParameterSetName = 'DefaultSet', Mandatory = $true. Position=0)]
[ValidateScript({Get-Command $_})]
[String]$CmdletName,
 
[Parameter(ParameterSetName = 'DefaultSet', Position=1)]
[ValidateSet('True', 'False')]
[String]$Full,
 
[Parameter(ParameterSetName = 'HelpSet', Mandatory = $true, Position = 0)]
[String]$Help
)

I also added code to take any value of the Help parameter.

if ($Help)
{ 
    $HelpString
}
elseif ($Full -eq 'True')
{
...

Now, when I wrap the script in an exe and run it:

PS C:\>.\Get-InputTypes.exe -CmdletName Set-ExecutionPolicy

TypeNames                            ParameterName ValueFromPipelineByPropertyName
---------                            ------------- --------------------------
Microsoft.PowerShell.ExecutionPolicy ExecutionPolicy True

PS C:\> .\Get-InputTypes.exe -Help True
NAME
Get-InputTypes.exe
SYNOPSIS
Gets input types for cmdlet help
SYNTAX
Get-InputTypes.exe [-CmdletName] <String> [-Full <String>]
Get-InputTypes.exe [-Help] <String>
...

PS C:\> .\Get-InputTypes.exe /?
NAME
Get-InputTypes.exe
SYNOPSIS
Gets input types for cmdlet help
SYNTAX
Get-InputTypes.exe [-CmdletName] <String> [-Full &lt:String>]
Get-InputTypes.exe [-Help] <String>
...

Make Help the Default

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’s pretty easy to do that.

If you made your Position 0 parameter accepts special values, like ‘/?’ and ‘Help’, you can make that parameter optional. Then, add a case for a $Null or empty string value (if !$CmdletName…).

[CmdletBinding()]
Param
(
[Parameter()]
[String]$CmdletName,
 
[Parameter()]
[ValidateSet('True', 'False')]
[String]$Full
)
 
if (!$CmdletName -or $CmdletName -eq 'Help' -or $CmdletName -eq '/?')
{
    $HelpString
}
elseif ($full -eq 'True')
...

If you created a Help parameter in its own parameter set, to make the Help parameter accept an empty value:

— Make the Help parameter optional
— Make the HelpSet parameter set the default
— Check for the HelpSet parameter set in the script

For example:

[CmdletBinding(DefaultParameterSetName = 'HelpSet')]
param
(
[Parameter(ParameterSetName = 'DefaultSet', Mandatory = $true)]
[ValidateScript({Get-Command $_})]
[String]$CmdletName,
 
[Parameter(ParameterSetName = 'DefaultSet')]
[ValidateSet('True', 'False')]
[String]$Full,
 
[Parameter(ParameterSetName = 'HelpSet', Mandatory = $false, Position = 0)]
[String]$Help
)
 
if ($PSCmdlet.ParameterSetName -eq 'HelpSet')
{ 
    $HelpString
}
elseif ($Full -eq 'True')
{
...

Now, when I wrap the script in an exe and run it with no parameters, I get the help.

PS C:\> .\Get-InputTypes.exe
NAME
Get-InputTypes.exe
SYNOPSIS
Gets input types for cmdlet help topics.
SYNTAX
Get-InputTypes.ps1 [[-Help] <String>]
Get-InputTypes.ps1 [-CmdletName] <String> [-Full <String>]

DESCRIPTION
This app gets the input types for a cmdlet help topic, that is, it gets the .NET types that
you can pipe to the cmdlet. By default, it gets only types that can be piped
...

And, I can still use the Help parameter explicitly with any value.

PS C:\> .\Get-InputTypes.exe -Help /?
NAME
Get-InputTypes.exe
SYNOPSIS
Gets input types for cmdlet help topics.
SYNTAX
Get-InputTypes.ps1 [[-Help] <String>]
Get-InputTypes.ps1 [-CmdletName] <String> [-Full <String>]

DESCRIPTION
This app gets the input types for a cmdlet help topic, that is, it gets the .NET types that
you can pipe to the cmdlet. By default, it gets only types that can be piped
...

Create a Help String

There are many ways to create the $HelpString value for a Windows PowerShell script in an executable file.

To make it look like the Get-Help display, I write comment-based or XML help for script, run ‘Get-Help <ScriptName>.ps1 -Full,’ 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.

$helpString = @"
 
NAME
C:\ps-test\Get-InputTypes.ps1
 
SYNOPSIS
Gets input types for cmdlet help
 
SYNTAX
Get-InputTypes.exe [-CmdletName] &lt;String&gt; [-Full &amp;lt:String&gt;]
Get-InputTypes.exe [-Help] &lt;String&gt;
 
DESCRIPTION
This script gets the input types for a cmdlet
help topic, that is, it gets the .NET types that
you can pipe to the cmdlet.
 
…
"@

It’s much easier to use Get-Help to display help, but when it’s not available, it’s great to have easy substitutes.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

 

 
[Google+]   [Facebook]   [LinkedIn]   [StumbleUpon]   [Digg]   [Reddit]   [Google Bookmark]  

Tags: , , , , , ,