I suggest defining a helper function (using the simpler -le 1 conditional from your Method 2, which returns $True for $null, "" and " " alike):
Function test-NoMetaData {
param([object] $obj)
# Loop over all property names and inspect that property's value on the input object.
foreach ($propName in 'Vendor', 'Company', 'Product', 'Description') {
if ($obj.$propName -le 1) { return $False }
}
return $True
}
# Filter $Files down to those objects that lack the (complete) metadata.
$filesWithoutMetaData = $Files | Where-Object { test-NoMetaData $_ }
You could also place the code directly in the Where-Object block and refer to $_ directly.
Optional reading: If you want to make the function more sophisticated, read on.
Consider implementing a Filter function that you can use directly in the pipeline:
Filter select-WithMetaData {
param([switch] $NotPresent) # To invert the logic
if ($Args) { Throw "Unrecognized arguments: $Args" }
if (-not $MyInvocation.ExpectingInput) { return } # no pipeline input; nothing to do.
$haveAllMetaData = $True
foreach ($propName in 'Vendor', 'Company', 'Product', 'Description') {
if ($_.$propName -le 1) { $haveAllMetaData = $False; break }
}
# Pass the input object through only if it has/doesn't have the requisite metadata.
if ($haveAllMetaData -ne $NotPresent) { $_ }
}
$filesWithoutMetaData = $Files | select-WithMetaData -NotPresent
$filesWithMetaData = $Files | select-WithMetaData
Filters are simplified functions that make it easier to define functionality that only accepts pipeline input: the body of the Filter function is invoked for each input object and $_ refers to that input object.
Filter functions are convenient, but have down-sides:
- You cannot pass input as direct arguments as an alternative to pipeline input (unless you explicitly define a pipeline-binding parameter, which nullifies the advantages of the simplified syntax that
Filter offers).
- You cannot run initialization / cleanup code before / after pipeline input is received.
Use Function syntax to avoid these limitations - see below.
To write a function that alternatively accepts direct argument input and supports common parameters (which makes it an advanced function (cmdlet-like)), you must use the Function construct and explicitly declare a parameter as accepting pipeline input.
Additionally, your function must have a process { ... } block, which is invoked for each input item; optionally, it can have a begin {...} and an end { ... } block for pre-pipeline-input initialization / post-pipeline-input cleanup.
Caveat: If you do not use a process block, your function is only invoked once, at which point the pipeline-binding parameter variable only contains the last input object.
PSv3+ syntax:
Function Select-WithMetaData {
[CmdletBinding()] # Make this an advanced function with common-parameter support.
param(
# Declare -File as accepting a single file directly or multiple files via the pipeline.
[Parameter(ValueFromPipeline, Mandatory)] [object] $File,
[switch] $NotPresent
)
# Invoked once with a directly passed -File argument bound to $File,
# and for each input object, also bound to $File, if used in the pipeline.
process {
$haveAllMetaData = $True
foreach ($propName in 'Vendor', 'Company', 'Product', 'Description') {
if ($File.$propName -le 1) { $haveAllMetaData = $False; break }
}
if ($haveAllMetaData -ne $NotPresent) { $File }
}
}