If you follow my blog I'm sure you noticed that I post a lot of advanced functions and scripts. While I don't expect every one to be developing advanced functions, the closer you can get the more powerful your work. With the Scripting Games approaching I thought I'd offer up a little something to help take your scripts to the next level and that is adding comment based help. This is a specially structured comment block that you can insert into your functions and scripts. The comment block is parsed by Get-Help to produce the same type of help output you see with cmdlets. But creating the help comment can be tricky so I wrote a script based wizard.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
My function, New-CommentHelp, prompts for all the necessary information and constructs an appropriate comment help block. Here's the function and then I'll go through a few key points.
[cc lang="PowerShell"]
Function New-CommentHelp {
Param()
#define beginning of comment based string
$comment=@"
<#
.SYNOPSIS
{0}
.DESCRIPTION
{1}
"@
#prompt for command name (default to script name)
$name=Read-Host "What is the name of your function or command?"
#prompt for synopsis
$Synopsis=Read-Host "Enter a synopsis"
#prompt for description
$description=Read-Host "Enter a description. You can expand and edit later"
#Create comment based help string
$help=$comment -f $synopsis,$description
#test if command is loaded and if so get parameters
#ignore common:
$common="VERBOSE|DEBUG|ERRORACTION|WARNINGACTION|ERRORVARIABLE|WARNINGVARIABLE|OUTVARIABLE|OUTBUFFER"
Try
{
$command=get-command -Name $name -ErrorAction Stop
$params=$command.parameters.keys | where {$_ -notmatch $common}
}
Catch
{
#otherwise prompt
$scriptname=Read-Host "If your command is a script file, enter the full file name with extension. Otherwise leave blank"
if ($scriptname)
{
Try
{
$command=get-command -Name $scriptname -ErrorAction Stop
$params=$command.parameters.keys | where {$_ -notmatch $common}
}
Catch
{
Write-Warning "Failed to find $scriptname"
Return
}
} #if $scriptname
else
{
#prompt for a comma separated list of parameter names
$EnterParams=Read-Host "Enter a comma separated list of parameter names"
$Params=$EnterParams.Split(",")
}
}
#get parameters from help or prompt for comma separated list
Foreach ($param in $params) {
#prompt for a description for each parameter
$paramDesc=Read-host "Enter a short description for parameter $($Param.ToUpper())"
#define a new line
#this must be left justified to avoid a parsing error
$paramHelp=@"
.PARAMETER $Param
$paramDesc
"@
#append the parameter to the help comment
$help+=$paramHelp
} #foreach
Do
{
#prompt for an example command
$example=Read-Host "Enter an example command. You do not need to include a prompt. Leave blank to continue"
if ($example)
{
#prompt for an example description
$exampleDesc=Read-Host "Enter a brief description of this example"
#this must be left justified to avoid a parsing error
$exHelp=@"
.EXAMPLE
PS C:\> $example
$exampleDesc
"@
#add the example to the help comment
$help+=$exHelp
} #if $example
} While ($example)
#Prompt for version #
$version=Read-Host "Enter a version number"
#Prompt for date
$resp=Read-Host "Enter a last updated date or press Enter for the current date."
if ($resp)
{
$verDate=$resp
}
else
{
#use current date
$verDate=(Get-Date).ToShortDateString()
}
#construct a Notes section
$NoteHere=@"
.NOTES
NAME : {0}
VERSION : {1}
LAST UPDATED: {2}
AUTHOR : {3}\{4}
"@
#insert the values
$Notes=$NoteHere -f $Name,$version,$verDate,$env:userdomain,$env:username
#add the section to help
$help+=$Notes
#prompt for URL Link
$url=Read-Host "Enter a URL link. This is optional"
if ($url)
{
$urlLink=@"
.LINK
$url
"@
#add the section to help
$help+=$urlLink
}
#prompt for comma separated list of links
$links=Read-Host "Enter a comma separated list of Link references or leave blank for none"
if ($links)
{
#define a here string
$linkHelp=@"
.LINK
"@
#add each link
Foreach ($link in $links.Split(",")) {
#insert the link and a new line return
$linkHelp+="$link `n"
}
#add the section to help
$help+=$linkHelp
}
#Inputs
$inputHelp=@"
.INPUTS
{0}
"@
$Inputs=Read-Host "Enter a description for any inputs. Leave blank for NONE."
if ($inputs)
{
#insert the input value and append to the help comment
$help+=($inputHelp -f $inputs)
}
else
{
#use None as the value and insert into the help comment
$help+=($inputHelp -f "None")
}
#outputs
$outputHelp=@"
.OUTPUTS
{0}
"@
$Outputs=Read-Host "Enter a description for any outputs. Leave blank for NONE."
if ($Outputs)
{
#insert the output value and append to the help comment
$help+=($outputHelp -f $Outputs)
}
else
{
#use None as the value and insert into the help comment
$help+=($outputHelp -f "None")
}
#close the help comment
$help+="#>"
#if ISE insert into current file
if ($psise)
{
$psise.CurrentFile.Editor.InsertText($help) | Out-Null
}
else
{
#else write to the pipeline
$help
}
} #end function
[/cc]
The function uses a series of Read-Host commands to prompt for items like the Synopsis and Description. To get all the parameters was a little trickier. If you are editing the function in the PowerShell ISE and run the script, this will dot source the function in the current session. Or you could manually dot source it in the regular console. My function asks for the command name and checks to see if the command is currently loaded.
[cc lang="PowerShell"]
Try
{
$command=get-command -Name $name -ErrorAction Stop
$params=$command.parameters.keys | where {$_ -notmatch $common}
}
[/cc]
If found, the function retrieves all the parameter names, except those that match the common parameters like ErrorAction. If the command is not found, then you are prompted for a script name.
[cc lang="PowerShell"]
$scriptname=Read-Host "If your command is a script file, enter the full file name with extension. Otherwise leave blank"
[/cc]
At which point, the same Get-Command code is used to retrieve parameters. You only need to enter something if you are trying to add comment help to a script and not a function. If this is the case, enter the full path and filename to the .ps1 file. Otherwise, the assumption is that you are working on a function in which case leave the script file blank and then you'll be prompted to enter a comma separated list of parameter names.
[cc lang="PowerShell"]
#prompt for a comma separated list of parameter names
$EnterParams=Read-Host "Enter a comma separated list of parameter names"
$Params=$EnterParams.Split(",")
[/cc]
The function will go through each parameter, prompting you for a short description. The parameter section is defined with a here string and appended to the overall help comment.
[cc lang="PowerShell"]
Foreach ($param in $params) {
#prompt for a description for each parameter
$paramDesc=Read-host "Enter a short description for parameter $($Param.ToUpper())"
#define a new line
#this must be left justified to avoid a parsing error
$paramHelp=@"
.PARAMETER $Param
$paramDesc
"@
#append the parameter to the help comment
$help+=$paramHelp
} #foreach
[/cc]
You are prompted for an example command and description. The function uses a Do loop so you can add as many examples as you want.
[cc lang="PowerShell"]
Do
{
#prompt for an example command
$example=Read-Host "Enter an example command. You do not need to include a prompt. Leave blank to continue"
if ($example)
{
#prompt for an example description
$exampleDesc=Read-Host "Enter a brief description of this example"
#this must be left justified to avoid a parsing error
$exHelp=@"
.EXAMPLE
PS C:\> $example
$exampleDesc
"@
#add the example to the help comment
$help+=$exHelp
} #if $example
} While ($example)
[/cc]
The rest of the prompts are self-explanatory. But once the function defines the comment string, what next? Well, if the function detects I'm in the ISE then it inserts the comment into the current location of the current file. Otherwise, it simply writes it to the pipeline.
[cc lang="PowerShell"]
#if ISE insert into current file
if ($psise)
{
$psise.CurrentFile.Editor.InsertText($help) | Out-Null
}
else
{
#else write to the pipeline
$help
}
[/cc]
My reason for this behavior is because in my ISE profile I dot source the New-CommentHelp.ps1 script. Then I add a new item to the Add-Ons menu.
[cc lang="PowerShell"]
. C:\scripts\New-CommentHelp.ps1
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Add Help",{New-CommentHelp},"ALT+H") | Out-Null
[/cc]
Now, when I'm working on a new function in the ISE I position my cursor after the Function declaration and press Alt+H. Assuming I've loaded the function into the ISE, the "wizard" will find it and prompt for everything I need, including the parameters. Once inserted, I can then tweak and edit. This is an important note: the comment based help will likely need minor tweaks and edits given the limitations of Read-Host. But at least you'll have the vast majority of the work already completed.
If you are using this from the PowerShell console, I would save the results to a variable.
[cc lang="PowerShell"]
PS C:\> $h=New-CommentHelp
[/cc]
Follow the prompts and when complete you can pipe $h to a file or wherever you need it. Here's a sample of the help comment created by the function.
[cc lang="PowerShell"]
<#
.SYNOPSIS
Calculate the area of a circle
.DESCRIPTION
This command will return a value for the area of a circle based on a given radius.
.PARAMETER Radius
The length of the circle's radius, greater than 0.
.EXAMPLE
PS C:\> Get-Radius 4
Returns the area of a circle with a radius of 4.
.NOTES
NAME : Get-Area
VERSION : 0.1
LAST UPDATED: 3/29/2011
AUTHOR : QUARK\Jeff
.LINK
https://jdhitsolutions.com/blog
.LINK
about_Arithmetic_Operators
.INPUTS
None
.OUTPUTS
[double]
#>
[/cc]
Adding comment-based help to your scripts and functions should score you extra brownie points and gold stars in the Scripting Games, and definitely makes work easier to use and understand.
Download New-CommentHelp.
This script is awesome. Thanks !
Awesome stuff, Jeff! I will be using the heck out of this during the Scripting Games – much appreciated!
Cheers
Hello Jeff..
I got this command to run against 1 Machine to get all the GPO’s that are applied
Get-GPResultantSetofPolicy -reporttype html -computer win7-pc.contoso.com -path c:\pc.html
But how can i script this to run against all my machines in the domain
Basically how can i script this against all my machines.. i have the list of machine Names with me or we can use another Powershell commandlet to pull out the machine names
Thanks in advance
The computername parameter does not support pipeline binding so you have to use a ForEach construct.
Get-content computers.txt | foreach {
$computer=$_
$file=”c:\RSOP_$computer.html”
Get-GPResultantSetofPolicy -reporttype html -computer $computer -path $file
}
I defined some variables instead of just using $_ to make it easier to follow. This should create an html file called RSOP_Computerame.html. This is a quick and dirty approach and doesn’t take things into account like error handling.