问题
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
- 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 usingRemove-VBComments
- 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 afterRemove-VBComments
in the pipeline - 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 CmdletRemove-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