PowerShell PipelineVariable parameter contains only first value in a collection of PSCustomObject

和自甴很熟 提交于 2019-12-13 00:27:19

问题


Question

I get count for lines of code for only one file in the array of type PSCustomObject. Rest all entries in the array error out with following message -

Get-Content : Cannot bind argument to parameter 'Path' because it is null.

At line:42 char:100

... -Content -Path $files.UncommentedFileName) | Measure-Object -Line | Select-Objec ...

How do I overcome this error and display the lines of code for all the files in the array?

Constraints

  1. I intend to re-use the function Remove-VBComments for other scenarios where I have to process VB6 files. Thus, I cannot avoid defining and using Remove-VBComments
  2. I intend to have the function Remove-VBComments accept pipelined input as will be pipeline friendly to be useable in the scenarios where other cmdlets could operate after Remove-VBComments in the pipeline
  3. I am constrained in the customer environment to use PowerShell version 4.0.

Details follow--

Intent

Of my code is to summarize the Lines of Code for a VB6 code file which is stripped off from comments. Comments are always a single ' with no provision for multi-line comments. Following PowerShell script is my attempt to fulfill this intent.

PowerShell code

$filesToStripComment = @([PSCustomObject]@{
"SourceFileName" = ".\vb6-master\ClassTemplate\ClassBuilder\App.cls";
"UncommentedFileName" = ".\vb6-master\ClassTemplate\ClassBuilder\App.cls.uncommented"
},[PSCustomObject]@{
"SourceFileName" = ".\vb6-master\ClassTemplate\ClassBuilder\Class1.cls";
"UncommentedFileName" = ".\vb6-master\ClassTemplate\ClassBuilder\Class1.cls.uncommented"
},[PSCustomObject]@{
"SourceFileName" = ".\vb6-master\ClassTemplate\ClassBuilder\IFilterReporter.cls";
"UncommentedFileName" = ".\vb6-master\ClassTemplate\ClassBuilder\IFilterReporter.cls.uncommented"
},[PSCustomObject]@{
"SourceFileName" = ".\vb6-master\ClassTemplate\ClassBuilder\ITemplateFilter.cls";
"UncommentedFileName" = ".\vb6-master\ClassTemplate\ClassBuilder\ITemplateFilter.cls.uncommented"
});

Function Remove-VBComments {
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName=$True
        )]
        [String]
        $SourceFileName,

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName=$True
        )]
        [String]
        $UncommentedFileName
    )
    Process {
        (Get-Content -LiteralPath $SourceFileName) `
            -replace "\'.*$",$ReplaceString -notmatch "^\s*$" `
        | Out-File -LiteralPath     $UncommentedFileName;
        $result =  [PSCustomObject]@{
                        SourceFileName=$($SourceFileName); 
                        UncommentedFileName=$($UncommentedFileName)
                    };
        Write-Output $result;
    }
}

$filesToStripComment | `
    Remove-VBComments -PipelineVariable "files" | `
    &{Process{ `
               (Get-Content -Path $files.UncommentedFileName) `
                  | Measure-Object -Line `
                  | Select-Object -Property `
                      @{n="FileName";e={$files.UncommentedFileName}}, ` 
                      @{n="LoC";e={$_.Lines}} `
     } }

The cls files are the ones I copied from a github project.

Output of the above code

FileName                                                          LoC
-------------                                                     -------
.\vb6-master\ClassTemplate\ClassBuilder\App.cls.uncommented       29

Get-Content : Cannot bind argument to parameter 'Path' because it is null.
At line:42 char:100
...... 
# The above error repeats for all other entries in the array $filesToStripComment.

Observation #1

If I make following small change after the call to Cmdlet Remove-VBComments, I get the LoC count for all files in the array $filesToStripComment.

... | Remove-VBComments | Select-Object -PipelineVariable "files" | `
& { Process { (Get-Content -Path $files.UncommentedFileName) | `
Measure-Object -Line | `
Select-Object -Property @{n="FileName",e={$files.UncommentedFileName} ...

Now I do not understand why does this subtle change of piping Select-Object gets me the desired result but using the PipelineVariable on Cmdlet Remove-VBComments does not get me the result?!

Observation #2

Instead of passing an array of PSCustomObject I have improvised the code in the meanwhile by introducing a new Cmdlet Get-ClsFilesRecursively. The function looks like this -

Function Get-ClsFilesRecursively{
[CmdletBinding()]
Param(
      [Parameter(
        Mandatory=$True,
        ValueFromPipelineByPropertyName=$True,
      )]
      [String]
      $SourceCodePath
)
  Process{
    Write-Output (Get-ChildItem -Path $SourceCodePath `
                              -Filter "*.cls" `
                              -Recurse | `
               Select-Object -Property `
                    @{n="LiteralPath";e={$_.FullName}});
  }
}

To put this function to use I make a call like this

Get-ClsFilesRecursively -SourceCodePath . -PipelineVariable "files" | `
& { Process { `
   Get-Content -LiteralPath $files.LiteralPath | `
   Measure-Object -Line | `
   Select-Object -Property @{n="File name";e={$files.LiteralPath}} `
                        @{n="LoC"; e={$_.Lines}}
}}

Now this gives the me the Lines of Code for all the files in the directory; without any exception. However, this call includes comments in the files. Now if I pipe the Remove-VBCommments in the above call sequence I get back the problem I mentioned in the question above. I am perplexed with what causes Remove-VBComments to fail in working properly with PipelineVariable whereas other Cmdlets behave properly with PipelineVariable!

Please help!


回答1:


Unfortunately -PipelineVariable doesn't work with script cmdlets like Remove-VBComments. That's why moving if to Select-Object worked - it's a C# cmdlet. But you don't actually need it since $_ in the foreach loop has all of the information that would have been in the $files variable. The update code would look something like

$filesToStripComment |
    Remove-VBComments |
    &{Process{
                $UncommentedFileName = $_.UncommentedFileName
                Get-Content -Path $UncommentedFileName |
                    Measure-Object -Line |
                        Select-Object -Property `
                            @{n="FileName";e={$UncommentedFileName}}, 
                            @{n="LoC";e={$_.Lines}} `
     } }


来源:https://stackoverflow.com/questions/50533763/powershell-pipelinevariable-parameter-contains-only-first-value-in-a-collection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!