0

I have an array being returned to me which I am stepping through with the foreach, I want to use one element of each member of that array in a hash table that will be converted to a json object. When I add the element to a powershell array object and then add it to the hash the strings are being combined instead of added as arrays.

Spec

"destinationInterclusterLifIps": [
  "172.31.5.119",
  "172.31.15.103"
]

Actual return:

"destinationInterclusterLifIps":  [
     "172.31.33.150172.31.42.41"
 ],

Building the array

if(!$destinationInterclusterLifIps){
    $destinationInterclusterLifIps =  @()
    foreach($record in $interclusterIpInfo.peerInterClusterLifs){
        $destinationInterclusterLifIps += $record.address
    }
}

Hash table

$body = @{
        destinationInterclusterLifIps = @($destinationInterclusterLifIps)
}

This has to be something simple with hash tables that I am missing

mklement0
  • 382,024
  • 64
  • 607
  • 775
Nicholas Elliott
  • 339
  • 1
  • 2
  • 11
  • 1
    PowerShell's auto-collection-feature-thingy could reduce this to just `$interclusterIpInfo.peerInterClusterLifs.address` without the need for looping. – Jeroen Mostert Mar 22 '19 at 15:40
  • 1
    @JeroenMostert: That thingy doesn't have a name in the official docs, unfortunately; the closest thing we have to an official names is _member enumeration_, in the [blog post that announced the feature in v3](https://blogs.msdn.microsoft.com/powershell/2012/06/13/new-v3-language-features/). It's the name I've been using in my answers here on SO, notably in [this one](https://stackoverflow.com/a/44620191/45375) that tries to explain the feature comprehensively. – mklement0 Mar 22 '19 at 16:10
  • Wasn't aware of that. I'm not a big fan of that name -- it sounds quite ambiguous with, you know, enumerating the individual members, but I suppose it's better than nothing. – Jeroen Mostert Mar 22 '19 at 16:12
  • Agreed on both counts, @JeroenMostert. The docs now at least [describe the behavior](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_properties?view=powershell-6#properties-of-scalar-objects-and-collections) (superficially), but without giving it a name, so at least hypothetically a better name could be chosen. – mklement0 Mar 22 '19 at 16:33
  • On second thought, @JeorenMostert: coming up with names that are both expressive and concise is difficult, and _member enumeration_ isn't half bad: _enumeration_ covers the aspect of operating on all elements of a collection, and _member_ covers what is being accessed on each element. – mklement0 Mar 25 '19 at 21:23
  • Update: The official name that was chosen is [_member-access enumeration_](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Member-Access_Enumeration), and I've updated my previous answers accordingly. – mklement0 Aug 05 '22 at 01:40

3 Answers3

3

Your code alone doesn't explain the symptom, but your own answer hints at an interesting pitfall that is worth exploring:

  • By default, a PowerShell variable can accept a value of any type, and values of different types can be assigned at any time:

    $var = 42   # $var now contains an [int]
    # ...
    $var = 'hi' # $var now contains a [string]
    
  • However, you can type-constrain variables, in which case they only ever accept values of that type - or values that can automatically be converted to that type (PowerShell is very flexible when it comes to automatic conversions):

    # Create a type-constrained variable.
    [int] $var = 42  # $var now contains an [int] AND is locked into storing [int]s
    
    # Try to assign a [string] that CANNOT be converted to an [int]
    $var = 'hi' # FAILS: 'Cannot convert value "hi" to type "System.Int32"...'
    
    # Try to assign a [string] that CAN be converted to an [int]
    $var = ' 42  ' # OK - string was converted; $var now contains [int] 42
    

Typically, but not necessarily, parameter variables are type-constrained, as part of a script or function's list of declared parameters.

If you later reuse a type-constrained parameter variable by assigning a new value to it, it will enforce its original type, as described above.

The likeliest explanation in your case is that your $destinationInterclusterLifIps was declared as a type-constrained [string] $destinationInterclusterLifIps parameter, in which case a later attempt to assign an array resulted in implicit stringification of that array; to illustrate with a simple example:

function foo {
  param(
   # Type-constrained parameter.
   [string] $destinationInterclusterLifIps
  )

  # ...

  # Try to assign an *array*:
  # Instead of storing an array, the [string] type constraint 
  # converts the array to a *single string*, as a space-separated list of
  # its elements.
  $destinationInterclusterLifIps = 1, 2, 3

  # Output the value.
  $destinationInterclusterLifIps

}

# Call the function (no argument needed)
foo

This outputs:

1 2 3

i.e., the array converted to a string, as a space-separated list of its elements. (An array would print element by element, each on its own line).

Note that your problem was therefore unrelated to the use of a hash table and its conversion to JSON - it's just where you happened to notice the problem.


Optional reading: Modifying / removing a variable's type constraint:

It is possible to later recreate a type-constrained variable with a different type; e.g.:

[int] $var = 42; [string] $var = 'hi'

While you can use Remove-Variable to effectively remove a type constraint by removing the variable first and then recreating it unconstrained, as Maximilian Burszley suggests -
[int] $var = 42; Remove-Variable var; $var = 'hi' - you can use [object] $var = 'hi' as a shortcut, since type-constraining to [object] is tantamount to not constraining the value.

(Get-Variable var).Attributes contains a variable's attributes, and a type-constraining attribute is stored as a [System.Management.Automation.ArgumentTypeConverterAttribute] instance. If you're not concerned about removing all attributes, another simple, though obscure, option for removing a type constraint is to use (Get-Variable var).Attributes.Clear().

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Thanks. Isn't `[psobject]` preferred for performance as the boxing/unboxing of `[object]` doesn't need to happen? – Maximilian Burszley Mar 22 '19 at 18:53
  • 1
    @TheIncorrigible1: [What is `[psobject]`?](https://github.com/PowerShell/PowerShell/issues/5579). [Never heard of it](https://github.com/PowerShell/PowerShell/issues/5551). But, seriously, I prefer to keep explicit use of `[psobject]` out of my PowerShell code - I wouldn't worry about performance here, but a quick test shows, surprisingly, that using `[psobject]` actually slows things down: `$a=1..1e5; tcm { foreach($i in $a) { [object] $v = 1 } }, { foreach($i in $a) { [psobject] $v = 1 } }`; `tcm` = https://gist.github.com/mklement0/9e1f13978620b09ab2d15da5535d1b27 – mklement0 Mar 22 '19 at 19:02
0

Looks like I was having a conflict with a variable. Maybe because it is being used as a param as well. Setting the variable to any other name cleared the issue.

if(!$sourceInterclusterLifIps){
    $InterclusterIPSource =  @()
    foreach($record in $interclusterIpInfo.interClusterLifs){
        $InterclusterIPSource += $record.address
    }
} else {
    $InterclusterIPSource = $sourceInterclusterLifIps
}
Nicholas Elliott
  • 339
  • 1
  • 2
  • 11
-1

try using an ArrayList with .add(), It should do what you want.

$destinationInterclusterLifIps = New-Object System.Collections.ArrayList
if(!$destinationInterclusterLifIps){
$destinationInterclusterLifIps =  @()
foreach($record in $interclusterIpInfo.peerInterClusterLifs){
    $destinationInterclusterLifIps.add( $record.address )
}

}

Dsabadash
  • 445
  • 1
  • 4
  • 12
  • While this may be more efficient (though it will hardly matter in this case), it neither explains Nicholas' problem nor does it provide a solution, given that it is functionally equivalent to the original code (except for the specific collection type, but that doesn't matter). – mklement0 Mar 22 '19 at 16:56