PowerShell: programmatically access script documentation - powershell

Is there a way to programmatically load the documentation of a .ps1 script file outside of commands like get-help? In other words, can text defined under .SYNOPSIS, .DESCRIPTION, etc. be accessed programmatically other than filtering the string output of get-help itself?
Among other things, I'm trying to find where I have gaps in documentation coverage in my script library. I'd also like to be able to display lists of certain scripts with their synopsis attached.

Yes, those are all accessible. Get-Help returns (just like any other cmdlet) an object, and the default rendering of that object is what you see in the console.
However, if you pump get-help's output through format-list, like this:
get-help get-childitem | format-list
You'll get a list of name-value pairs of the properties. To get the synopsis, you can do the following:
get-help get-childitem |select-object -property synopsis
And the output:
Synopsis
--------
Gets the files and folders in a file system drive.
If your .ps1 file has no cmdlets defined in it (your comment-based help covers the whole script), get-help file.ps1|select synopsis should work. Otherwise, you'll need to "dot-source" the files to load the cmdlet definitions into memory, then use get-help as above.

Related

Why is PS Get-ChildItem so difficult

I did a ton of reading and searching about a way to have Get-ChildItem return a dir listing in wide format, in alphabetical order, with the number of files and directories in the current directory. Here is a image of what I ended up with, but not using GCI.
I ended up writing a small PS file.
$bArgs = "--%/c"
$cArgs = "Dir /n/w"
& cmd.exe -ArgumentList $bArgs $cArgs
As you can see I ended up using the old cmd.exe and passing the variables I wanted. I made an alias in my PS $Profile to call this script.
Can this not be accomplished in PS v5.1? Thanks for any help or advice for an old noob.
PowerShell's for-display formatting differs from cmd.exe's, so if you want the formatting of the latter's internal dir command, you'll indeed have to call it via cmd /c, via a function you can place in your $PROFILE file (note that aliases in PowerShell are merely alternative names and can therefore not include baked-in arguments):
function lss { cmd /c dir /n /w /c $args }
Note that you lose a key benefit of PowerShell: the ability to process rich objects:
PowerShell-native commands output rich objects that enable robust programmatic processing; e.g., Get-ChildItem outputs System.IO.FileInfo and System.IO.DirectoryInfo instances; the aspect of for-display formatting is decoupled from the data output, and for-display formatting only kicks in when printing to the display (host), or when explicitly requested.
For instance, (Get-ChildItem -File).Name returns an array of all file names in the current directory.
By contrast, PowerShell can only use text to communicate with external programs, which makes processing cumbersome and brittle, if information must be extracted via text parsing.
As Pierre-Alain Vigeant notes, the following PowerShell command gives you at least similar output formatting as your dir command, though it lacks the combined-size and bytes-free summary at the bottom:
Get-ChildItem | Format-Wide -AutoSize
To wrap that up in a function, use:
function lss { Get-ChildItem #args | Format-Wide -Autosize }
Note, however, that - due to use of a Format-* cmdlet, all of which output objects that are formatting instructions rather than data - this function's output is also not suited to further programmatic processing.
A proper solution would require you to author custom formatting data and associate them with the System.IO.FileInfo and System.IO.DirectoryInfo types, which is nontrivial however.
See the conceptual about_Format.ps1xml help topic, Export-FormatData, Update-FormatData, and this answer for a simple example.

Running help <command> and piping output to Where-Object or Select-Object returns empty rows

Running the command help firewall | Select-Object Category. The result is one column blank Category.
The strange thing is that the empty rows number represent the amount of rows that help firewall would result without calling piping it to Select-Object
Or I'm trying to filter the output of help firewall to return only rows with Name that starts with "Get". Running help firewall | Where-Object Name -like "Get" just returns nothing.
Why aren't these pipes on help working? They are working perfectly on other commands.
Powershell Version 5.1 and using default windows console.
To complement Zilog80's helpful answer with background information:
Get-Command help reveals that help is not a mere alias of the Get-Help cmdlet, but a (built-in) function (submit $function:help to see its definition).
As you've noticed yourself:
while Get-Help outputs an object ([pscsustomobject]) with properties that reflect help-topic metadata such as Category, which is then rendered as display text by PowerShell's output-formatting system,
the help function returns strings - a stream of text lines representing the rendered help topic - of necessity.
You can observe the difference in output type by piping to the Get-Member cmdlet (help firewall | Get-Member vs. Get-Help firewall | Get-Member)
The purpose of the help function is to wrap Get-Help with interactive paging, to allow convenient navigation through lengthy help topics that don't fit onto a single console (terminal) screen.
This paging is provided via an external program (by default, more.com on Windows, and less on Unix-like platforms, configurable via $env:PAGER, but only in PowerShell (Core) 7+), and since PowerShell only "speaks text" when communicating with external programs, help must send a stream of strings (lines for display) to them, which it does via
Out-String -Stream.
Note:
When the external paging programs find that their stdout stream isn't connected to a console (terminal), they take no action other than simply passing the input through (in Unix terms, they then behave like cat).
Hypothetically, the help function itself could determine this condition and then (a) not pipe to the paging program and (b) relay the object output by Get-Help as-is.[1] However, determining a command's output target from inside that command, using PowerShell code, may not even be possible.
[1] The function actually already takes this action when a custom pager defined via $env:PAGER is found to be a PowerShell command rather than an external program.
Check the feedback from help help in PowerShell :
You can also type `help` or `man`, which displays one screen of text at a
time. Or, ` -?`, that is identical to `Get-Help`, but only
works for cmdlets.
The helpcommand display "screen of text" which means it is outputting [System.String] objects, not [PSCustomObject] objects.
Only -? will behave identically to Get-help and will provide [PSCustomObject] objects.
To see what's going on, check the different output from :
help firewall | %{ $_.GetType() }
And
Get-help firewall | %{ $_.GetType() }
And, for cmdlet,
Select-Object -? | %{ $_.gettype() }

Refer to other CmdLets with module prefix in PowerShell documentation

When I write comment based help in a PowerShell CmdLet ps1 script,
that is contained in a module, is it possible to refer to other
CmdLets in the same module so that the resulting output to the user
will print the name of the referred CmdLet when imported with a
prefix?
So for example, if I write my comment based help like this:
function Get-Thing {
<#
.SYNOPSIS
Get the thing.
.DESCRIPTION
The Get-Thing CmdLet will get a thing.
#>
[CmdletBinding()]
Param(...)
and the user imports the module using a prefix:
Import-Module -Prefix My
Then I want the help to print the CmdLet name Get-Thing in the description field as Get-MyThing, honoring the module prefix value that the user provided:
> help Get-MyThing
NAME
...
SYNOPSIS
...
SYNTAX
...
DESCRIPTION
This Get-MyThing CmdLet will get a thing.
Is this possible?
The NAME and SYNTAX sections will be automatically updated to include the prefix when you use one. It's not possible to change it if you use it elsewhere in the help text because you cannot embed variables in a comment.
I would just suggest avoiding using the cmdlet name elsewhere where you can, although you'll generally want to in Examples and that will then be unchanged. However at least with the NAME and SYNTAX sections being accurate that should help users self-correct.
The only other way I can think to do it would be to have external help files or placeholders in the comments that are rewritten as the module is loaded by using PowerShell to read and edit the files, but the effort required doesn't seem worthwhile.

Get all references to a given PowerShell module

Is there a way to find a list of script files that reference a given module (.psm1)? In other words, get all files that, in the script code, use at least 1 of the cmdlets defined in the module.
Obviously because of PowerShell 3.0 and above, most of my script files don't have an explicit Import-Module MODULE_NAME in the code somewhere, so I can't use that text to search on.
I know I can use Get-ChildItem -Path '...' -Recurse | Select-String 'TextToSearchFor' to search for a particular string inside of files, but that's not the same as searching for any reference to any cmdlet of a module. I could do a search for every single cmdlet in my module, but I was wondering if there is a better way.
Clarification: I'm only looking inside of a controlled environment where I have all the scripts in one file location.
Depending on the scenario, the callstack could be interesting to play around with. In that case you need to modify the functions which you want to find out about to gather information about the callstack at runtime and log it somewhere. Over time you might have enough logs to make some good assumptions.
function yourfunction {
$stack = Get-PSCallStack
if ($stack.Count -gt 1) {
$stack[1] # log this to a file or whatever you need
}
}
This might not work at all in your scenario, but I thought I throw it in there as an option.

Referencing powershell functions from script modules

Is there a shorthand I can write in PowerShell whereby I can only reference my own functions from modules that I have loaded?
For Example:
I have module that I have loaded by doing
Import-Module Worktasks
Now as the module gets bigger I will start to forget about some of the earlier functions that I wrote. I can, of course, do something like this:
(Get-Module -Name worktasks | % ExportedCommands).Values.Name
But this will only list the functions that I have wrote but not their parameters. So it would be nice if I could narrow down the results pulled by Intellisense if I could directly reference functions/cmdlets from individual modules in my code.
I am using PowerShell 4.0 in my environment.
Thanks.
You can get the functions from a specific module, e.g. worktasks, like this:
(Get-Module -Name worktasks).ExportedCommands.Values
By default you'll get a limited amount of information but there is more. If you select everything, then you'll see more information including the parameter sets:
(Get-Module -Name worktasks).ExportedCommands.Values | select *
If you just want the names and the parameter sets, then this will do just that:
(Get-Module -Name worktasks).ExportedCommands.Values | select Name, ParameterSets