ListFunctions.psm1
Function BuildPredicate() { [CmdletBinding()] [OutputType([System.Predicate[object]])] param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [scriptblock] $ScriptBlock ) Begin { $rebuild = New-Object -TypeName 'System.Collections.Generic.List[string]' -ArgumentList 2 } Process { # Replace all instances of '$_' and '$PSItem' in the ScriptBlock with '$x' $sbString = $ScriptBlock.ToString().Replace('$_', '$x') $matchCol = [regex]::Matches($sbString, '\$PSItem', "IgnoreCase") $matchCol | Select-Object Value -Unique | ForEach-Object { $sbString = $sbString.Replace($PSItem.Value, [string]'$_') } # Split the ScriptBlock by new lines and add them to $rebuild $rebuild.AddRange(($sbString -split "`n")) if ($rebuild[0] -cnotmatch '^\s*param') # If the first line is not the start of a 'param' block then... { $rebuild.Insert(0, 'param ($x)') # ...insert one at the beginning of the list. } # Cast all joined strings from the list into a System.Predicate[object] [System.Predicate[object]][scriptblock]::Create(($rebuild -join "`n")) } } Function Assert-All() { <# .SYNOPSIS Asserts all objects of a collections satisfy a condition. .DESCRIPTION Determines whether all elements of a sequence satisfy a condition. The condition is given in the form of a Filter ScriptBlock which will be converted into a System.Predicate (this means the scriptblock must return 'True/False'). This function is more useful the more complex the InputObjects become. .PARAMETER InputObject The collection(s) that contains the elements to apply the condition to. If an empty collection or null is passed, then the function will return 'False'. .PARAMETER Condition A ScriptBlock that each is run against each InputObject to satisfy the condition. The condition must be a predicate, meaning that a 'True/False' value is returned. .INPUTS System.Object - any .NET object or array of objects. .OUTPUTS System.Boolean .EXAMPLE @(1, 2, 3) | All { $_ -eq 1 } # returns 'False' .EXAMPLE @(1, 2, 3) | All { $_ -is [int] } # returns 'True' .EXAMPLE @( [pscustomobject]@{ Greeting = @{ 1 = "Hi" }}, [pscustomobject]@{ Greeting = @{ 2 = "Hey"}} ) | All { $_.Greeting.Count -gt 0 } # returns 'True' #> [CmdletBinding()] [Alias("Assert-AllObjects", "All-Objects", "All")] [OutputType([bool])] param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [AllowNull()] [AllowEmptyCollection()] [object[]] $InputObject, [Parameter(Mandatory=$true, Position=0)] [scriptblock] $Condition ) Begin { $list = New-Object -TypeName "System.Collections.Generic.List[object]" } Process { if ($null -ne $InputObject -and $InputObject.Length -gt 0) { $list.AddRange($InputObject) } } End { if ($list.Count -gt 0) { $list.Where($Condition).Count -eq $list.Count } else { $false } } } Function Assert-Any() { <# .SYNOPSIS Asserts any object of a collection exists or matches a condition. .DESCRIPTION Determines whether any element of a sequence satisifies a condition. If no condition (scriptblock) is specified, then the functions determines whether the sequence contains any elements at all. This function is more useful the more complex the InputObjects become. .PARAMETER InputObject The collection(s) whose elements to apply the condition to. If the incoming collection(s) of objects are empty or null, the function returns 'False'. .PARAMETER Condition OPTIONAL. A ScriptBlock that each is run against each InputObject to satisfy the condition. The condition must be a predicate, meaning that a 'True/False' value is returned. .INPUTS System.Object - any .NET object or array of objects. .OUTPUTS System.Boolean .EXAMPLE @(1, 2, 3) | Any { $_ -eq 1 } # returns 'True' .EXAMPLE @(1, 2, 3) | Any # returns 'True' - (identical to '@(1, 2, 3).Count -gt 0') .EXAMPLE @( [pscustomobject]@{ Greeting = @{ 1 = "Hi" }}, [pscustomobject]@{ Greeting = @{ 2 = "Hey"}} ) | Any { $_.Greeting.ContainsKey(2) -and $_.Greeting[2] -eq "Hey" } # returns 'True' #> [CmdletBinding()] [Alias("Any-Object", "Any")] [OutputType([bool])] param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [AllowNull()] [AllowEmptyCollection()] [object[]] $InputObject, [Parameter(Mandatory=$false, Position=0)] [scriptblock] $Condition ) Begin { $list = New-Object -TypeName "System.Collections.Generic.List[object]" } Process { if ($null -ne $InputObject -and $InputObject.Length -gt 0) { $list.AddRange($InputObject) } } End { if ($list.Count -gt 0) { if ($PSBoundParameters.ContainsKey("Condition")) { $list.Where($Condition).Count -gt 0 } else { $true } } else { $false } } } Function Find-IndexOf() { <# .SYNOPSIS Finds the index of the first element that matches a condition. .DESCRIPTION Searches for an element that matches the condition defined by the specified conditional ScriptBlock, and returns the zero-based index of the first occurrence within the entire collection of objects. When a 'StartIndex' value is specified, the function returns the first occurrence that starts from the first element of the specified index. When combined with 'Count', only the specified number of elements are searched. If no elements satisfy the condition specified, then function returns -1. This function is more useful the more complex the InputObjects become. @@!! IMPORTANT - READ NOTES SECTION !!@@ .PARAMETER InputObject The collection(s) of objects that are searched through. .PARAMETER Condition The predicate (scriptblock) that defines the conditions of the element to search for. .PARAMETER StartIndex The zero-based index where the search starts. .PARAMETER Count The number of elements in the section to search. .INPUTS System.Object - any .NET object or array of objects. .OUTPUTS System.Int32 - the zero-based index of the last occurrence that matches the condition if found; otherwise, - .EXAMPLE @(1, 2, 3, 1, 4) | Find-IndexOf { $_ -eq 1 } # returns '0' .EXAMPLE @(1, 2, 3, 1, 4) | Find-IndexOf { $_ -eq 3 -or $_ -eq 4 } # returns '2' .EXAMPLE @( [pscustomobject]@{ Whatev = @(1, 7) }, [pscustomobject]@{ Whatev = @(2, 7) }, [pscustomobject]@{ Whatev = @(3, @{ 7 = "Seven" }) }, [pscustomobject]@{ Whatev = @(4, @{ 7 = "Seven" }) }, [pscustomobject]@{ Whatev = @{ 7 = "Seven" }; Another = "Property" }, [pscustomobject]@{ Whatev = @( 4,5,6 ) } ) | Find-IndexOf { $_.Whatev -is [array] -and $($_.Whatev | Any { $PSItem -is [hashtable] -and $PSItem.ContainsKey(7) } ) } # returns '2' .NOTES @@!! IMPORTANT NOTE !!@@ -- In the conditional ScriptBlock, '$_' should ALWAYS represent the value of each InputObject. If using additional nested ScriptBlock inside the condition, DO NOT USE '$_'! Instead use '$PSItem'. The function replaces all instances of '$_' in order to create the System.Predicate[object]. Look at Example #3 to see an example of this. #> [CmdletBinding()] [Alias("Find-Index", "IndexOf")] [OutputType([int])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [object[]] $InputObject, [Parameter(Mandatory = $true, Position = 0)] [scriptblock] $Condition, [Parameter(Mandatory = $false)] [int] $StartIndex, [Parameter(Mandatory = $false)] [int] $Count ) Begin { $list = New-Object -TypeName "System.Collections.Generic.List[object]" $Predicate = BuildPredicate -ScriptBlock $Condition } Process { $list.AddRange($InputObject) } End { if (-not $PSBoundParameters.ContainsKey("Count")) { $list.FindIndex($StartIndex, $Predicate) } else { $list.FindIndex($StartIndex, $Count, $Predicate) } } } Function Find-LastIndexOf() { <# .SYNOPSIS Finds the index of the last element that matches a condition. .DESCRIPTION Searches for an element that matches the condition defined by the specified conditional ScriptBlock, and returns the zero-based index of the last occurrence within the entire collection of objects. When a 'StartIndex' value is specified, the function returns the last occurrence that extends from the first element to the specified index. When combined with 'Count', only the specified number of elements are searched. If no elements satisfy the condition specified, then function returns -1. This function is more useful the more complex the InputObjects become. @@!! IMPORTANT - READ NOTES SECTION !!@@ .PARAMETER InputObject The collection(s) of objects that are searched through. .PARAMETER Condition The predicate (scriptblock) that defines the conditions of the element to search for. .PARAMETER StartIndex The zero-based index of the backward search. .PARAMETER Count The number of elements in the section to search. .INPUTS System.Object - any .NET object or array of objects. .OUTPUTS System.Int32 - the zero-based index of the last occurrence that matches the condition if found; otherwise, -1. .EXAMPLE @(1, 2, 3, 1, 4) | Find-LastIndexOf { $_ -eq 1 } # returns '3' .EXAMPLE @(1, 2, 3, 1, 4) | Find-LastIndexOf { $_ -eq 1 -or $_ -eq 4 } # returns '4' .EXAMPLE @( [pscustomobject]@{ Whatev = @(1, 7) }, [pscustomobject]@{ Whatev = @(2, 7) }, [pscustomobject]@{ Whatev = @(3, @{ 7 = "Seven" }) }, [pscustomobject]@{ Whatev = @(4, @{ 7 = "Seven" }) }, [pscustomobject]@{ Whatev = @{ 7 = "Seven" }; Another = "Property" }, [pscustomobject]@{ Whatev = @( 4,5,6 ) } ) | Find-LastIndexOf { $_.Whatev -is [array] -and $($_.Whatev | Any { $PSItem -is [hashtable] -and $PSItem.ContainsKey(7) } ) } # returns '3' .NOTES @@!! IMPORTANT NOTE !!@@ -- In the conditional ScriptBlock, '$_' should ALWAYS represent the value of each InputObject. If using additional nested ScriptBlock inside the condition, DO NOT USE '$_'! Instead use '$PSItem'. The function replaces all instances of '$_' in order to create the System.Predicate[object]. Look at Example #3 to see an example of this. #> [CmdletBinding(DefaultParameterSetName="None")] [Alias("Find-LastIndex", "LastIndexOf")] [OutputType([int])] param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [object[]] $InputObject, [Parameter(Mandatory=$true, Position=0)] [scriptblock] $Condition, [Parameter(Mandatory=$true, ParameterSetName="ByStartingIndex")] [int] $StartIndex, [Parameter(Mandatory=$false, ParameterSetName="ByStartingIndex")] [int] $Count ) Begin { $list = New-Object -TypeName "System.Collections.Generic.List[object]" $Predicate = BuildPredicate -ScriptBlock $Condition } Process { $list.AddRange($InputObject) } End { if (-not $PSBoundParameters.ContainsKey("StartIndex")) { $StartIndex = $list.Count - 1 } if (-not $PSBoundParameters.ContainsKey("Count")) { $list.FindLastIndex($StartIndex, $Predicate) } else { $list.FindLastIndex($StartIndex, $Count, $Predicate) } } } Function Remove-All() { <# .SYNOPSIS Removes objects from collection(s) that satisfy a condition. .DESCRIPTION Removes all elements that match the conditions defined by the specified ScriptBlock. .PARAMETER InputObject The collection(s) of objects whose elements will be removed. .PARAMETER Condition The predicate (scriptblock) condition that determines whether any one element should be removed from the collection. .INPUTS System.Management.Automation.PSReference - [ref]. This must be a reference to an Array, Collection, or List (this includes Dictionaries; e.g. - Hashtables). .OUTPUTS None. .EXAMPLE $numbers = @(1, 2, 3) [ref]$numbers | Remove-All { $_ -eq 1 } # '$numbers' now equals: # @(2, 3) .EXAMPLE $strings = [System.Collections.ArrayList]@('hi', 'bye', 'so long') Remove-All -InputObject ([ref]$strings) -Condition { $_.Contains(' ') } # '$strings' now equals: # @('hi', 'bye') .EXAMPLE $hash = @{ "hi" = 1; "bye" = 2; "so long" = @{ multi = $true } } [ref]$hash | Remove-All { $_.Key -eq "Hi" -or $_.Value -is [hashtable] } # '$hash' now equals: # @{ "bye" = 2 } .NOTES If 'InputObject' is not a single-dimensional array or does not inherit from 'System.Collections.ICollection', then no removal operation will take place. @@!! IMPORTANT NOTE !!@@ -- In the conditional ScriptBlock, '$_' should ALWAYS represent the value of each InputObject. If using additional nested ScriptBlock inside the condition, DO NOT USE '$_'! Instead use '$PSItem'. The function replaces all instances of '$_' in order to create the System.Predicate[object]. #> [CmdletBinding()] [Alias("RemoveAll")] param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [ref] $InputObject, [Parameter(Mandatory=$true, Position=0)] [scriptblock] $Condition ) Process { $Predicate = BuildPredicate $Condition if ($InputObject.Value.GetType().IsArray) { $elementType = $InputObject.Value.GetType().GetElementType() $list = New-Object -TypeName "System.Collections.Generic.List[object]" -ArgumentList $InputObject.Value.Length $list.AddRange(@($InputObject.Value)) [void] $list.RemoveAll($Predicate) $InputObject.Value = New-Object -TypeName "$($elementType.FullName)[]" $list.Count for ($i = 0; $i -lt $list.Count; $i++) { $InputObject.Value[$i] = $list[$i] } } elseif ($InputObject.Value -is [System.Collections.ICollection]) { $list = New-Object -TypeName "System.Collections.Generic.List[object]" -ArgumentList $InputObject.Value.Count $list.AddRange(@($InputObject.Value.GetEnumerator())) [void] $list.RemoveAll($Predicate) $newCol = [System.Activator]::CreateInstance($InputObject.Value.GetType()) foreach ($item in $list) { if ($item -is [System.Collections.DictionaryEntry]) { [void] $newCol.Add($item.Key, $item.Value) } else { [void] $newCol.Add($item) } } $InputObject.Value = $newCol } } } Function Remove-At() { <# .SYNOPSIS Removes item(s) of a collection at the specified indices. .DESCRIPTION Removes the elemetns at the specified indexes of the supplied collection of objects. When 'Index' and 'Count' are specified, the number ('Count') of elements from the specified index will be removed. There cannot be more than one (1) index specified when both parameters are used. .PARAMETER InputObject The collection(s) of objects whose elements will be removed. .PARAMETER Index The zero-based index of the element to remove. When used with 'Count', it is the starting index from which elements will be removed. .PARAMETER Count The number of elements to remove counting up from the starting index. .INPUTS System.Object - any .NET object or array of objects. .OUTPUTS System.Object[] - the resulting array sans the removed elements. .EXAMPLE @(1, 2, 3) | Remove-At 1 # returns: # @(1, 3) #> [CmdletBinding()] [Alias("RemoveAt")] param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [object[]] $InputObject, [Parameter(Mandatory=$true, Position=0)] [int[]] $Index, [Parameter(Mandatory=$false)] [int] $Count ) Begin { if ($PSBoundParameters.ContainsKey("Count") -and $Index.Length -gt 1) { throw "When using 'Count', only 1 index may be specified at a time." } $list = New-Object -TypeName "System.Collections.Generic.List[object]" } Process { $list.AddRange($InputObject) } End { if (-not $PSBoundParameters.ContainsKey("Count")) { $itemsToRemove = foreach ($remIndex in $Index) { $list[$remIndex] } foreach ($item in $itemsToRemove) { [void] $list.Remove($item) } } else { $list.RemoveRange($Index[0], $Count) } $list } } |