Search code examples
xmlpowershellformattable

Getting an empty pipe error when trying to move format-table outside of loop


Trying to loop this code through multiple XML docs listed in a CSV, and not sure where to move my Format-Table without getting the An empty pipe element is not allowed error.

$CSV = Import-Csv "C:\Users\Megan\Documents\EC_Export\AllDocs.csv"

foreach($LINE in $CSV)
    {
    $docPath = $LINE.filepath

# Note: The following should be preferred over Get-Content which
#       doesn't respect XML encoding!

$xmlFile = [xml]::new(); $xmlFile.Load(( Convert-Path -LiteralPath $docPath ))

# Create an ordered hashtable as a precursor for a PSCustomObject
$ht = [ordered] @{}

# Process all ChildNodes
$xmlFile.files.file.ChildNodes |
    # Filter by Name property (which is either element name or Name attribute) (Can only do 10 fields at a time this way)
    Where-Object Name -match 'title|lcmSubject|lcmPrincipal|lcmClosingDate|lcmAclList' | 
    ForEach-Object {
        # Get element text, trim whitespace, replace any line breaks by comma.
        $value = $_.'#text' #.Trim() -replace '\r?\n', ',' 

        # Add hashtable entry (associate name with value)
        $ht[ $_.Name ] = $value 
    }

# Convert hashtable to a PSCustomObject so Format-Table prints it as expected.
[PSCustomObject] $ht} | Format-Table -Wrap

Solution

    • foreach is a language statement and as such (unfortunately) cannot directly participate in a pipeline; that is, in your case you cannot directly append | FormatTable -Wrap to it.

      • This limitation is rooted in the fundamentals of PowerShell grammar; see GitHub issue #10967 for background information.

      • The reason for the An empty pipe element is not allowed error is that the foreach language statement is invariably considered a complete PowerShell statement, causing | Format-Table -Wrap to be considered the next statement - which breaks, given that you cannot start a statement with |, the pipeline operator.

    • There are various workarounds:

      • To stream your language statement's output to the pipeline, enclose it in & { ... }, i.e. enclose it in a script block ({ ... }) that you invoke via &, the call operator.

      • To collect your language statement's output up front before sending it to the pipeline (still object by object, enclose it in $(...), the subexpression operator

      • In your simple case, switch from a foreach statement to the analogous ForEach-Object cmdlet.

        • Note that, unlike the foreach statement, the ForEach-Object cmdlet has no explicit iterator variable, and requires you to refer to the pipeline input object at hand via the automatic $_ variable

        • As an aside: Somewhat confusingly, foreach (in addition to %) also exists as an alias of ForEach-Object. It is the syntactic context that determines whether foreach refers to the cmdlet or to the language statement.

    Here's a simplified version of a ForEach-Object solution:

    $CSV | ForEach-Object {
      # Note the need to use of $_ to refer to the input object at hand
      $docPath = $_.filepath
      # ... 
    } | Format-Table -Wrap