I'm writing some scripts for my CI, and I've noticed that I'm not doing a good job of asserting on the uniqueness of my filters. For example, one script assumes that
$availableZip = $(Get-ChildItem -Path .\ -Filter "*SomeName*.zip" -Recurse).FullName
will provide a unique entry, but it may provide no entries or it may provide multiple entries.
This can of course be handled down-stream with some If-Else checking, but what I'd like to do is elegantly push PowerShell to generate an error for me, with something like
$availableZip = $(Get-ChildItem -Path .\ -Filter "*SomeName*.zip" -Recurse | Where -Single).FullName
such that Where -Single
would throw some kind of SetIsEmptyException
or SetContainsMultipleElementsException
, with all the PowerShell accoutrements pointing specifically to this line and maybe even including the duplicate members.
Where-Object : value contains multiple elements where only one is allowed, available elements: firstDirectory\SomeSoftware.zip, Another-SomeSoftware.zip at C:\Users\geoff\Code\Project\MyScript.ps1:33 char:73
+ ...ChildItem -Path .\ -Filter "SomeSoftware.zip" -recurse | Where -Single).FullName
+ ~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-ChildItem], SingletonSetContainsMultipleElementsException
+ FullyQualifiedErrorId : TooManyElements,Microsoft.PowerShell.Commands.WhereObjectCommand
Is there a built-in way for me to do this? Is there some PowerShell trick I can employ, or should I use a little module with a private function (and, if so, whats the most elegant implementation?)
You could always just use Linq like below. I tried to make it more efficient by only getting the names of the zip folders at first.
[string[]]$zips = @(Get-ChildItem -Path .\ -Filter "*SomeName*.zip" -Recurse -Name)
[string]$availableZipName = [System.Linq.Enumerable]::Single($zips)
$availableZip = Get-ChildItem -Path $availableZipName -Recurse
Below I have put it into a function for ease of use.
function Get-AvailableZip (
[ValidateScript({ Test-Path $_ })]
[string]$Path,
[ValidateNotNullOrEmpty()]
[string]$Filter
)
{
[string[]]$zips = @(Get-ChildItem -Path $Path -Filter $Filter -Recurse -Name);
[string]$availableZipName
$availableZip = $null
try
{
$availableZipName = [System.Linq.Enumerable]::Single($zips)
$availableZip = Get-ChildItem -Path "$Path\$availableZipName" -Recurse
}
catch [System.InvalidOperationException]
{
if ($_.Exception.Message -eq "Sequence contains more than one element")
{
Write-Error -Message ([Environment]::NewLine + $_.Exception.Message + [Environment]::NewLine + "Files Found:" + [Environment]::NewLine + [string]::Join([Environment]::NewLine, $zips)) -Category LimitsExceeded -Exception ($_.Exception)
}
else
{
if ($_.Exception.Message -eq "Sequence contains no elements")
{
Write-Error -Message ([Environment]::NewLine + $_.Exception.Message) -Category ObjectNotFound -Exception ($_.Exception)
}
else
{
throw
}
}
}
return $availableZip;
}
Usage:
Get-AvailableZip -Path ".\" -Filter "*SomeName*.zip"
If you want to A. use the Pipeline, B. have something reusable and C. have something reasonably fast:
function Where-SingleObject {
param (
[Parameter(Mandatory, ValueFromPipeline, HelpMessage='Data to process')]$InputObject
)
begin {
$i = 0
}
process {
if($i -eq 1) {
throw "Error"
}; $i++
}
end {
return $InputObject
}
}
(Get-ChildItem -Path '' -Filter '' -Recurse).FullName | Where-SingleObject
You can remove Mandatory
and add a custom Error for 0, if you would like.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With