This question already has an answer here:
Determine if an array of PSCustomObject's contains an instance with a property value
(1 answer)
Closed 6 months ago.
I have a powershell object array like this
$EdosContainers= #([pscustomobject]#{Container='FoundationObjectStoreConfigMetadata';PartitionKey='/templateName'}
[pscustomobject]#{Container='FoundationObjectStoreObjectInformation';PartitionKey='/processExecutionId'}
[pscustomobject]#{Container='FoundationObjectStoreObjectMetadata';PartitionKey='/processExecutionId'}
[pscustomobject]#{Container='FoundationObjectStoreConfigMetadataTransfomed';PartitionKey='/templateName'})
But I need to check whether a given value exists in the array or not in the container attribute.
I know we can use -contains method for checking the existence of an item in array. But since mine is an object array how can we accomplish this?
You can take advantage of member-access enumeration, which extracts all .Container property values from the array's elements by directly applying the property access to the array, allowing you to use -contains:
# -> $true
$EdosContainers.Container -contains 'FoundationObjectStoreConfigMetadataTransfomed'
If you want to avoid the implicit creation of an array with all .Container values, use the intrinsic .Where() method:
[bool] $EdosContainers.Where(
{ $_.Container -eq 'FoundationObjectStoreConfigMetadataTransfomed' },
'First' # Stop, once a match is found.
)
While this is more memory-efficient than using member-access enumeration, it also noticeably slower with large arrays.
Related
I have a powershell 5.1 script that I've created that imports a csv file, manipulates some of the data, then converts the object of data to json format using the ConvertTo-Json cmdlet at the end of the script. The problem that I'm running into; in one of my fields I need to create a single array for the object property with brackets. So I need the object to be like:
"PersonGroups":[
{
"Name":"test Name",
"Id": 3433
}
]
Here is the call to the function:
$_.PersonGroups = Set-DataHash -InputObject $_
Below is my code that I have:
function Set-DataHash{
param(
[psobject] $InputObject
)
$customObject = [psobject]#{
Name = "Test"
Id = 78888
}
$customArray = #($customObject)
return $customArray
}
Of course if I have more than one object in the array it works fine; but since it's only one object ConvertTo-Json makes it a single object. Any suggestions on what to do?
How about:
$_.PersonGroups = #(Set-DataHash -InputObject $_)
You don't need the "return".
js2010's helpful answer shows how to solve the problem on the caller's side, using #(), the array-subexpression operator, around your Set-DataHash function call to ensure that the return value is an array.
If you want to solve the problem from within your function, you must ensure that the array being output (returned) is output as a whole, as a single object, which can be achieved with an auxiliary single-element wrapper array created with (the unary form of) ,, the array constructor operator:
function Set-DataHash {
param(
[psobject] $InputObject
)
$customObject = [psobject] #{
Name = "Test"
Id = 78888
}
# Wrap $customObject in an array with #(...),
# then *additionally* wrap this array in an *auxiliary* single-element array,
# via `,` (array-construction operator).
# Outputting this aux. array causes *it* to be enumerated, which therefore
# outputs its only element as-is: the *original* array - see explanation below.
# Note: I've omitted `return`, which is always optional.
, #($customObject)
}
Explanation:
By default - irrespective of whether you use return or implicit output in a function or script - outputting a collection (including arrays) causes its elements to be enumerated (aka streamed, unwrapped or unrolled); that is, the elements are output one by one to the (invisible, in this case) pipeline through which the caller receives the output.
In the case of a single-element collection, the nature of the pipeline is such that the caller receives just that single element itself - the collection wrapper is lost.
In the case of a multi-element collection, the specific original collection is lost too, and the enumerated elements are automatically collected in an [object[]] array).
Therefore, wrapping a collection that you want to output as a whole, as a single object requires the aux. single-element wrapper array technique shown above; a less efficient alternative is to use Write-Output with the -NoEnumerate switch, which too prevents enumeration of a collection passed to it as an argument (not via the pipeline).
Note:
In general, in functions/scripts intended to be called directly by others, it is better not to output collections as a whole, so as not to confound the general expectation of streaming (enumerating) behavior in the pipeline; in the streaming case, it is the caller that must ensure that the collected output is an array, via #(...), as shown in js2010's answer; see this answer for background information.
Conversely, however, outputting collections as a whole is faster and allows you to output a specific collection type.
I have a powershell 5.1 script that I've created that imports a csv file, manipulates some of the data, then converts the object of data to json format using the ConvertTo-Json cmdlet at the end of the script. The problem that I'm running into; in one of my fields I need to create a single array for the object property with brackets. So I need the object to be like:
"PersonGroups":[
{
"Name":"test Name",
"Id": 3433
}
]
Here is the call to the function:
$_.PersonGroups = Set-DataHash -InputObject $_
Below is my code that I have:
function Set-DataHash{
param(
[psobject] $InputObject
)
$customObject = [psobject]#{
Name = "Test"
Id = 78888
}
$customArray = #($customObject)
return $customArray
}
Of course if I have more than one object in the array it works fine; but since it's only one object ConvertTo-Json makes it a single object. Any suggestions on what to do?
How about:
$_.PersonGroups = #(Set-DataHash -InputObject $_)
You don't need the "return".
js2010's helpful answer shows how to solve the problem on the caller's side, using #(), the array-subexpression operator, around your Set-DataHash function call to ensure that the return value is an array.
If you want to solve the problem from within your function, you must ensure that the array being output (returned) is output as a whole, as a single object, which can be achieved with an auxiliary single-element wrapper array created with (the unary form of) ,, the array constructor operator:
function Set-DataHash {
param(
[psobject] $InputObject
)
$customObject = [psobject] #{
Name = "Test"
Id = 78888
}
# Wrap $customObject in an array with #(...),
# then *additionally* wrap this array in an *auxiliary* single-element array,
# via `,` (array-construction operator).
# Outputting this aux. array causes *it* to be enumerated, which therefore
# outputs its only element as-is: the *original* array - see explanation below.
# Note: I've omitted `return`, which is always optional.
, #($customObject)
}
Explanation:
By default - irrespective of whether you use return or implicit output in a function or script - outputting a collection (including arrays) causes its elements to be enumerated (aka streamed, unwrapped or unrolled); that is, the elements are output one by one to the (invisible, in this case) pipeline through which the caller receives the output.
In the case of a single-element collection, the nature of the pipeline is such that the caller receives just that single element itself - the collection wrapper is lost.
In the case of a multi-element collection, the specific original collection is lost too, and the enumerated elements are automatically collected in an [object[]] array).
Therefore, wrapping a collection that you want to output as a whole, as a single object requires the aux. single-element wrapper array technique shown above; a less efficient alternative is to use Write-Output with the -NoEnumerate switch, which too prevents enumeration of a collection passed to it as an argument (not via the pipeline).
Note:
In general, in functions/scripts intended to be called directly by others, it is better not to output collections as a whole, so as not to confound the general expectation of streaming (enumerating) behavior in the pipeline; in the streaming case, it is the caller that must ensure that the collected output is an array, via #(...), as shown in js2010's answer; see this answer for background information.
Conversely, however, outputting collections as a whole is faster and allows you to output a specific collection type.
I am new to powershell scripting. Apologies if I am missing something simple.
Let's say I have an object called object that has a field called field. Now let there be a list of these objects.
How would I get the list of fields in the same order?
In python it would be:
list_of_objects = [o1, o2, o3]
list_of_fields = [i.field for i in list_of_object]
powershell is nice, and not so nice, because it unwraps collections for you, and sometimes this can hide that it is masking the elements' members. When you're using $parents.item, you're accessing the array's method, and trying to access its members (which there aren't any, so powershell is giving you $null):
Item ParameterizedProperty System.Object IList.Item(int index) {get;set;}
You can overcome this by using the method I shared in the comments to iterate over each member and avoid this masking:
$list = $parents | ForEach-Object -MemberName item
$list.registration.parentCompoundNumber
Alternatively, a syntax more people are familiar with:
$list = $parents | Select-Object -ExpandProperty item
or unrolling it yourself:
# you could directly assign the outputs of a `foreach` loop to a variable by
# removing these comments (<##>)
<# $items = #>
foreach ($parent in $parents) {
$parent.item.registration.parentCompoundNumber
}
To see when this masking is happening, consider this example which uses the unary array operator:
, #('a', 'b', 'c') | Get-Member
This will let you observe the wrapping array or collection's members.
To complement Maximilian Burszley's helpful answer:
The linked answer contains viable workarounds for the member-name collisions; let me add a PSv4+ alternative that is both more concise and faster than a pipeline-based approach:
$parent.ForEach('item').registration.parentCompoundNumber
Using the .ForEach() array method with a property name ('item') unambiguously targets the elements' members.
To offer a slight reframing of the explanation for why a workaround is needed:
PowerShell's member-access enumeration essentially treats $someCollection.someProp as if you had written foreach ($element in $someCollection) { $element.someProp }; that is, the elements of $someCollection are enumerated, and the the elements' .someProp property values are returned as an array.
Note: As in the pipeline, if the collection happens to have just one element, that element's property value is returned as-is, not as a single-element array.
However, if the collection type itself happens to have a member named someProp, it is used, and no enumeration takes place; that is, collection-level members shadow (take precedence over) element-level members of the same name - and that is what happened with .Item in your case.
When in doubt, output $someCollection.someProp interactively / during debugging to see what it evaluates to.
this question succeeds the following question: Moose: Array of Objects->loop through Attribute
I'm struggling to implement the grep syntax into a List::Compare Object:
my #aIdentList=("z003","t302","p032");
my $lc=List::Compare->new('-u',\#aIdentList,\#{(grep {$_->Identifier}#aArray1)});
my #unique=$lc->get_unique;
\#{(grep {$_->Identifier}#aArray1)}
This part is weird. You are trying to dereference a list via #{}. That doesn't work.
The grep returns a list already. You do not need to use parenthesis () to construct a new list. Perl will ignore them. Then your #{} is simply wrong. You need a new array reference. That's []. It will construct a new array ref from the list that grep returns. That's already enough to pass to List::Compare->new. The backslash \ is only needed if you want to take a reference of something, but you now already have a reference.
But you actually do not want to grep here. You want to map!
This is what your code should look like.
List::Compare->new( '-u', \#aIdentList, [ map { $_->Identifier } #aArray1 ] )
grep will filter out every element of the list you pass in where the block does not return a true value. Since all objects in #aArray1 (typing this is horrible!) have an Identifier property, this will let all elements through. The second array ref arg to new will then be a list of objects. That's not what you want, because you'd be comparing apples to oranges.
Instead, you need to use map, which will pass the return value of the block for each element. It's like an inline foreach loop. Then your second list will be the actual identifiers of the objects, and not the objects themselves.
I have two data arrays:
$arrnames=('a','b','c')
$arrtypes=('x','y','z')
and I have a lookup array:
$arrlookup=('y','z')
I need to return the elements in $arrnames where the matching element in $arrtypes is contained in $arrlookup. I've been playing with foreach looping over $arrnames and looking at the same element in $arrtypes by index but it's getting sloppy and I know there has to be a better way.
I've seen some similar questions, but not anything that strikes me as specifically answering this problem. Being a Powershell very newbie I'm still learning how to adapt examples to my needs. Any help is greatly appreciated.
My code (technique used as applied to above example - actual code is using complex arrays derived from an XML file and has a lot of debugging code so copy/paste is impractical)
foreach ($a in $arrnames) {
$t=$arrtypes[$arrnames.IndexOf($a)]
if ($arrlookup.Contains($t)) {
$arrresult+=$a
}
)
$arrresult should contain the members of $arrnames that have a type (from $arrtypes) that is in $arrlookup.
Is there a way to use object methods, filtering and the pipeline to simply extract the elements without a foreach loop
Edit - here's the actual code that creates the actual arrays - $builds is an XML document:
$names=$builds.builds.project.name
$types=$builds.builds.project.type
The lookup table is known:
$FXCopTypes=#('batch','component','web')
The XML file I also have control over, but I don't see any way to simplify it more than implementing the hash table code but with the above arrays.
I think you need to change you input inorder to be able to do anything different here. What you are asking for is $arrnames and $arrtypes to really be a hashtable. That way you can access the values using keys.
As it stands I would do this to create the hashtable. The second loop shows how to return each matching value.
$hash = #{}
for($index = 0; $index -lt $arrtypes.Count; $index++){
$hash.($arrtypes[$index]) = $arrnames[$index]
}
$arrresult = $arrlookup | ForEach-Object{
$hash[$_]
}
This would return
b
c
If you could get your input to create that hash table then it reduces the need to rebuild it. Also if you know the lookup before hand as well you can filter it then and just have the output you want.