Cannot add single element array to hashtable in powershell [duplicate] - arrays

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.

Related

Using contains operator for array of objects in powershell [duplicate]

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.

Create a Single-Element Json Array Object Using PowerShell

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.

How to get a list of a particular field from a list of objects in powershell

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.

using List::Compare on an array of Moose Objects

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.

Trying to edit single elements in dynamic jagged array edits several instead

I am trying to make a dynamically sized jagged array to represent a game grid for a very simple console game.
# Determine the size of the board. Enforce the size so as to not make it too large.
$boardSize = 5
# Game board characters
[char]$bushCharacter = "#"
# Initialize Game board object
$gameBoard = ,(#($bushCharacter) * $boardSize) * $boardSize
Idea being that if I was to print it out on the screen. I would get something like this which I do.
0..($boardSize - 1) | ForEach-Object{$gameBoard[$_] -join ""}
#####
#####
#####
#####
#####
All is well until I try to make a change to an individual item: $gameBoard[0][1] = "F". My expected result from reprinting the grid is
#F###
#####
#####
#####
#####
My actual output is:
#F###
#F###
#F###
#F###
#F###
This tells me that the array I made has all rows referencing each other. Why would this code not create separate unique arrays? What change could I make to make it work. I know other approaches would work like a index loop with += but I thought this approach was a better practice.
If you look at the documentation for the multiplication operator (*), you'll find that (from about_Arithmetic_Operators):
* Multiplies integers; copies strings 6*2
and arrays the specified number of "w" * 3
times.
Notice the word copies.
Now take a look at the MSDN documentation for the .NET Array.Copy() method, under the Remarks section:
If sourceArray and destinationArray are both reference-type arrays or are both arrays of type Object, a shallow copy is performed. A shallow copy of an Array is a new Array containing references to the same elements as the original Array. The elements themselves or anything referenced by the elements are not copied. In contrast, a deep copy of an Array copies the elements and everything directly or indirectly referenced by the elements.
It's fair to assume that the array multiplication operator always produces the same kind of array - ie. either a reference-type array or an Object array (whatever that is) - and this is basically what you see the effects of.
I'd do:
$gameBoard = 1..$boardSize |ForEach-Object { ,#(#($bushCharacter) * $boardSize) }
Array is a reference type and because of that you are only multiplying (copying) the reference/pointer and not the content itself.
Workaround: Create a loop to generate unique arrays.
$gameBoard = 0..($boardSize - 1) | ForEach-Object{ ,(#($bushCharacter) * $boardSize) }
You can verify (without documentation) that an object is a reference type by using:
Add-Type -AssemblyName "Microsoft.VisualBasic"
[Microsoft.VisualBasic.Information]::IsReference($gameBoard[0])
True
#or as mentioned by Mathias
-not $gameBoard[0].GetType().IsValueType
True

Resources