1

When I run the following Get-Command Get-ChildItem -Syntax I get:

<... empty line here ...>
Get-ChildItem [[-Path] <string[]>] [[-Filter] <string>] [-Include <string[]>] [-Exclude <string[]>] [-Recurse] [-Depth <uint32>] [-Force] [-Name] [-UseTransaction] [-Attributes <FlagsExpression[FileAttributes]>] [-Directory] [-File] [-Hidden] [-ReadOnly] [-System] [<CommonParameters>]
<... empty line here ...>
Get-ChildItem [[-Filter] <string>] -LiteralPath <string[]> [-Include <string[]>] [-Exclude <string[]>] [-Recurse] [-Depth <uint32>] [-Force] [-Name] [-UseTransaction] [-Attributes <FlagsExpression[FileAttributes]>] [-Directory] [-File] [-Hidden] [-ReadOnly] [-System] [<CommonParameters>]
<... empty line here ...>

However, this is not how I would like things to format and would prefer to strip the empty lines, and to prefix each syntax line with a # (so that when the lines wrap, it's clear where each syntax definition starts). This is just an example, want to do similar with many Cmdlet outputs etc, to be able to manipulate the output as text with no empty lines above, below or in between, just the text, formatted and compact, so would really appreciate in general how to format in this way. i.e.

# Get-ChildItem [[-Path] <string[]>] [[-Filter] <string>] [-Include <string[]>] [-Exclude <string[]>] [-Recurse] [-Depth <uint32>] [-Force] [-Name] [-UseTransaction] [-Attributes <FlagsExpression[FileAttributes]>] [-Directory] [-File] [-Hidden] [-ReadOnly] [-System] [<CommonParameters>]
# Get-ChildItem [[-Filter] <string>] -LiteralPath <string[]> [-Include <string[]>] [-Exclude <string[]>] [-Recurse] [-Depth <uint32>] [-Force] [-Name] [-UseTransaction] [-Attributes <FlagsExpression[FileAttributes]>] [-Directory] [-File] [-Hidden] [-ReadOnly] [-System] [<CommonParameters>]

Can anyone advise on an easy way to achieve formatting output like that please?

YorSubs
  • 3,194
  • 7
  • 37
  • 60

3 Answers3

2

Here you go!

(Get-Command Get-ChildItem -Syntax) -split '\r\n' |
    where {$_} | foreach {"# $_"}

# Get-ChildItem [[-Path] <string[]>] [[-Filter] <string>] [-Include <string[]>] [-Exclude <string[]>] [-Recurse] [-Depth <uint32>] [-Force] [-Name] [-UseTransaction] [-Attributes <FlagsExpression[FileAttributes]>] [-Directory] [-File] [-Hidden] [-ReadOnly] [-System] [<CommonParameters>]
# Get-ChildItem [[-Filter] <string>] -LiteralPath <string[]> [-Include <string[]>] [-Exclude <string[]>] [-Recurse] [-Depth <uint32>] [-Force] [-Name] [-UseTransaction] [-Attributes <FlagsExpression[FileAttributes]>] [-Directory] [-File] [-Hidden] [-ReadOnly] [-System] [<CommonParameters>]

You will be bound by the max width of the console so even if they are one line it will wrap. You could pipe into clip if you want to paste it into something else.

(Get-Command Get-ChildItem -Syntax) -split '\r\n' |
    where {$_} | foreach {"# $_"} | clip
Doug Maurer
  • 8,090
  • 3
  • 12
  • 13
0

That's fantastic, thanks Doug, really useful. I also use a small function that assists me in nicely formatting line-wraps, so I can then use your code to get around the max width:

(Get-Command Get-ChildItem -Syntax) -split '\r\n' | where {$_} | foreach {"# $_"} | Write-Wrap

Where the Write-Wrap function always formats cleanly to the width of the console (edited with mklment0's points below, and with the essential PROCESS block that he pointed out):

function Write-Wrap {
    [CmdletBinding()]Param( [parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Object[]]$chunk )
    PROCESS {
        $Lines = @()
        foreach ($line in $chunk) {
            $str = ''; $counter = 0
            $line -split '\s+' | % {
                $counter += $_.Length + 1
                if ($counter -gt $Host.UI.RawUI.BufferSize.Width) {
                    $Lines += ,$str.trim()
                    $str = ''
                    $counter = $_.Length + 1
                }
                $str = "$str$_ "
            }
            $Lines += ,$str.trim()
        }
        $Lines
    }
}
YorSubs
  • 3,194
  • 7
  • 37
  • 60
  • 1
    Also: Incrementally "extending" an array in a loop is inefficient, because a _new_ array must be created behind the scenes _in every iteration_, because arrays are _immutable_; a much more efficient approach is to use the `foreach` loop as an _expression_ and let PowerShell automatically collect the outputs in an array: `[array] $outputs = foreach (...) { ... }` - see [this answer](https://stackoverflow.com/a/60029146/45375). – mklement0 Oct 14 '20 at 23:04
  • What PowerShell host you use (such as the ISE) has nothing to do with the behavior (and neither does the edition - PS Core behaves the same). As expected, I see only _one_ pipeline input getting processed wherever I run this, using your function definition as currently posted and the `1,2,3 ...` test command from my previous comment. The reason is that the absence of `begin` / `process` / `end` blocks in a function body is the same as running inside an `end` block, at which point only the _last_ input object is bound to your parameter variable. See the minimal example in the following comment. – mklement0 Oct 15 '20 at 13:37
0

Here was my current use for this. I find navigating Modules to be awkward and wanted a better/faster way to get a compact overview. I keep these in my personal Custom-Tools.psm1 Module and it helps me to very quickly see what's on a system.

Type mods to see all installed Modules and where they are located.

Type mods <partial_name> to see all matches. e.g. mods mic or mods soft

Type mod <module-name> to see quick details on the contents of a given Module.

Type mod <module-name> -i | more to get all syntax details on the contents of that Module.

I find this to be a very quick and convenient way to interrogate Modules. Hopefully of use to some folks.

mods

function mods ($search) {
    ""
    ":: Complete `$env:PSModulePath string:`n"
    $env:PSModulePath
    $env:PSModulePath -Split ";" -replace "\\+$", "" | sort
    ""
    ""
    $PathArray = $env:PSModulePath -Split ";" -replace "\\+$", "" | sort
    ""
    foreach ($Path in $PathArray) {
        if ($Path -ne '') {
            $Path = $Path.TrimEnd('\')
            echo ":: Modules under '$Path':`n"
            # if (Test-Path $Path) {
            $temp = (dir "$Path\*$search*" -Directory -EA Silent | Select -ExpandProperty Name) -join ", "   # mods w*  => mods w** which resolves fine
            if ($temp -eq "") { "No matches for '$search' exist in this path" }
            # } else { "This path is in `$env:PSModulePath but the folder does not exist"}
            Write-Wrap $temp
            ""
        }
    }
    ""
}

mod

function mod ($Module, $def, [switch]$ShowModulesHere, [switch]$Info) {
    if ($null -eq $Module) { "You must specify a Module to examine. Run 'mods' to see available Modules." ; break }
    ""
    if ([bool](Get-Module $Module -ListAvailable) -eq $true) {
        if ([bool](Get-Module $Module) -eq $true) { ":: Module '$Module' is already imported, so all functions are available`n" }
        else { ":: Module '$Module' was not imported, running import now ..." ; Import-Module $Module }
    }
    else { "Could not find Module in available Module folders`n" ; break }
    
    $ModulePath = ((Get-Module $Module | select Path).Path).TrimEnd('\')
    $ModuleVer = (Get-Module $Module -ListAvailable | select Version).Version | sls "\d"
    ":: '$Module' is version $($ModuleVer)`n`n$ModulePath"
    
    $ModuleRoot = Split-Path ((Get-Module $Module | select Path).Path).TrimEnd("\")
    $ModulesHere = (dir $Path -Directory | Select -ExpandProperty Name) -join ", "
    
    if ($Info) {
        ""
        foreach ($i in (Get-Command -Module $Module).Name) { 
            $out = $i   # Parse the info string from after the "{" 
            $type = "" ; try { $type = ((gcm $i -EA silent).CommandType); } catch { $deferr = 1 }
            $out += "   # $type"
            $syntax = Get-Command $i -Syntax
            $definition = "" ; if ($type -eq "Alias") { $definition = (get-alias $i).Definition }
            $syntax = $syntax -replace $definition, ""
            if ($type -eq "Alias") { $out += " for '$definition'" }
            $out
            if ($type -eq "Function") { $syntax = $syntax -replace $i, "" }
            if ($type -eq "Cmdlet") { $syntax = $syntax -replace $i, "" }
            if (!([string]::IsNullOrWhiteSpace($syntax))) { 
                $syntax -split '\r\n' | where {$_} | foreach { "Syntax =>   $_" | Write-Wrap }
            }
            ""
        }
        ""
    }
    else {
        ""
        ":: Module functions:"
        $out = ""; foreach ($i in (Get-Command -Module $Module).Name) { $out += " $i," } ; "" ; Write-Wrap $out.TrimEnd(", ")
        ""
    }
    $ModPaths = $env:PSModulePath -Split ";" -replace "\\+$", "" | sort
    ":: Module Paths (`$env:PSModulePath):"
    foreach ($i in $ModPaths) { "   $i"}
    ""
    ":: '$Module' Path:`n`n   $ModulePath"
    ""
    foreach ($i in $ModPaths) {
        if (!([string]::IsNullOrWhiteSpace($i))) {
           if ($ModulePath | sls $i -SimpleMatch) { $ModRoot = $i ; ":: `$env:PSModulePath parent location is:`n`n   $i" }
        }
    }
    ""

    if ($def -ne $null) {
        ":: Press any key to open '$def' definition:"
        pause
        ""
        def $def
        ""
    }

    if ($ShowModulesHere -eq $true) {
        ":: This `$env:PSModulePath root also contains the following Modules:"
        ""
        (dir $ModRoot -Directory | Select -ExpandProperty Name) -join ", "
        ""
    }
}
YorSubs
  • 3,194
  • 7
  • 37
  • 60