Powershell: Cmdlet description using attribute declarations

Created on 24 Jun 2020  路  9Comments  路  Source: PowerShell/PowerShell


PowerShell by default generates most of the help text for cmdlets in binary modules in an expected way except for the Cmdlet Description,synopsis and related links. Just for specifying the cmdlet synopsis, description,related links looks like we need to generate the whole help text in MAML format. Is there a way to specify Cmdlet description,synopsis, related links as an Attribute in c# source code like we specify the output type?
[Cmdlet("Get", "Greeting")]
[OutputType(string)]
public class GetGreeting : PSCmdlet
{
[Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, HelpMessage = @"Person's name")]
public string Name { get; set; }
}

Steps to reproduce


Expected behavior


Actual behavior


Environment data


Area-Cmdlets Issue-Enhancement WG-Interactive-HelpSystem

Most helpful comment

One solution to that is to use a resource file.

And we fall to an design with an external file. In the case it is better to think about an universal format which can be used for both script and binary cmdlets. And I'd expect that the universal format is Markdown :-) This dramatically simplify HelpSystem.

All 9 comments

Not currently, although it's not impossible to implement, it would just need changes to the help documentation to go looking for additional attributes or attribute properties for help content.

Currently the most "straightforward" way I've found to go about it is with the PlatyPS module; it can generate a Markdown file with your cmdlet name and parameters that you can fill out with help information, and then it will generate a MAML file to put in the module from the Markdown. There are still a handful of odd edge cases and it's a bit weird to get used to working with, but on the whole it's a ton easier than hand-crafting MAML.

Can this be taken up as a new feature request if native help documentation support for this does not exist?

What about localization? I do not think this is the right way.

The suggestion is interesting for sure and I agree that it could be helpful. Localization is the primary reason to have downloadable help in MAML.

Look at the following example of help for Get-Process. As you can see the description and related link blocks can get very long. It would just make the C# code look messy. Though adding a attribute property for Synopsis could be fine. Still MAML / PlatyPS is the right way to go as it also allows you to specify examples, which I believe are most useful.

NAME
    Get-Process

SYNOPSIS
    Gets the processes that are running on the local computer.


SYNTAX
    Get-Process [[-Name] <String[]>] [-FileVersionInfo] [-Module] [<CommonParameters>]

    Get-Process [-FileVersionInfo] -Id <Int32[]> [-Module] [<CommonParameters>]

    Get-Process [-FileVersionInfo] -InputObject <Process[]> [-Module] [<CommonParameters>]

    Get-Process -Id <Int32[]> -IncludeUserName [<CommonParameters>]

    Get-Process [[-Name] <String[]>] -IncludeUserName [<CommonParameters>]

    Get-Process -IncludeUserName -InputObject <Process[]> [<CommonParameters>]


DESCRIPTION
    The `Get-Process` cmdlet gets the processes on a local computer.

    Without parameters, this cmdlet gets all of the processes on the local computer. You can also specify a particular
    process by process name or process ID (PID) or pass a process object through the pipeline to this cmdlet.

    By default, this cmdlet returns a process object that has detailed information about the process and supports
    methods that let you start and stop the process. You can also use the parameters of the `Get-Process` cmdlet to
    get file version information for the program that runs in the process and to get the modules that the process
    loaded.


RELATED LINKS
    Online Version: https://docs.microsoft.com/powershell/module/microsoft.powershell.management/get-process?view=power
    shell-7&WT.mc_id=ps-gethelp
    Debug-Process
    Get-Process
    Start-Process
    Stop-Process
    Wait-Process

@adityapatwardhan PlatyPS is good if you want many changes. We are happy with the default PowerShell Help except we need one or 2 additional sections. We generate code for our cmdlets so it wouldn't be a big deal for us to include these as attributes in our code irrespective of how unwieldy those attributes look.
While using PlatyPS, we are having to go through multiple steps - Generate MD files from PlatyPS; those MD files have sections which are not complete. Generate some intermediate XML files with the info that we need to populate the MD files empty sections generated by PlatyPS. Use these MD files to finally patch the MAML files that we need for help text. The MAML files are missing the Synopsis section (probably due to a bug in PlatyPS that we have filed with them).
If the above feature that we are asking for was supported, we had much simpler process for help text generation.

If the above feature that we are asking for was supporting, we had much simpler process for help text generation.

Current HelpSystem was designed many years ago. Since then, much has changed. We have already discussed that this system should be modernized. Unfortunately, MSFT team has not yet published a public roadmap.
There was an idea to exclude MAML altogether.
Now I think we could use ideas from new PowerShellGet 3.0 for HelpSystem too.
This would dramatically simplify the help creation and support.

Just to tack on to this, C# doc comments allow any XML within them. This can then be parsed out at build time, so you could also document something like this:

/// <powershell-help>
///   <description>
///    The `Get-Process` cmdlet gets the processes on a local computer.
///
///    Without parameters, this cmdlet gets all of the processes on the local computer. You can also specify a particular
///    process by process name or process ID (PID) or pass a process object through the pipeline to this cmdlet.
///
///    By default, this cmdlet returns a process object that has detailed information about the process and supports
///    methods that let you start and stop the process. You can also use the parameters of the `Get-Process` cmdlet to
///    get file version information for the program that runs in the process and to get the modules that the process
///    loaded.
///   </description>
/// </powershell-help>
[Cmdlet(VerbsCommon.Get, "Process")]
public class GetProcessCommand : PSCmdlet
{
...
}

Pros:

  • Aligns with existing C# help
  • Easy to structure, declare and read
  • Documentation lives next to code

Cons:

  • Not easily localisable (then again, .NET will have needed to solve this already)
  • Not available at runtime, so requires an extra build step and asset

The essential issue for attributes is that it's hard to structure strings provided as arguments to them. One solution to that is to use a resource file. For example, something I'm working on for PSScriptAnalyzer:

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
    public sealed class RuleAttribute : ScriptAnalyzerAttribute
    {
        private readonly Lazy<string> _descriptionLazy;

        public RuleAttribute(string name, string description)
        {
            Name = name;
            _descriptionLazy = new Lazy<string>(() => description);
        }


        public RuleAttribute(string name, Type descriptionResourceProvider, string descriptionResourceKey)
        {
            Name = name;
            _descriptionLazy = new Lazy<string>(() => GetStringFromResourceProvider(descriptionResourceProvider, descriptionResourceKey));
        }

        public string Name { get; }

        public DiagnosticSeverity Severity { get; set; } = DiagnosticSeverity.Warning;

        public string Namespace { get; set; }

        public string Description => _descriptionLazy.Value;

        private static string GetStringFromResourceProvider(Type resourceProvider, string resourceKey)
        {
            PropertyInfo resourceProperty = resourceProvider.GetProperty(resourceKey, BindingFlags.Static | BindingFlags.NonPublic);
            return (string)resourceProperty.GetValue(null);
        }
    }

One solution to that is to use a resource file.

And we fall to an design with an external file. In the case it is better to think about an universal format which can be used for both script and binary cmdlets. And I'd expect that the universal format is Markdown :-) This dramatically simplify HelpSystem.

And we fall to an design with an external file

Yeah I think anything with localisation support is going to need to separate implementation from documentation, and markdown is a better way to go on that

Was this page helpful?
0 / 5 - 0 ratings