Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Powershell equivalent of ".Single()" For a C#/Java programmer

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?)

like image 459
Groostav Avatar asked Apr 11 '19 18:04

Groostav


2 Answers

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"
like image 173
Patrick Mcvay Avatar answered Oct 19 '22 09:10

Patrick Mcvay


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.

like image 29
Jacob Colvin Avatar answered Oct 19 '22 09:10

Jacob Colvin