1

This case only appears with arrays of the length one.

Example:

$array = @("55") 
$arrayAfterUnique = $array | Select-Object -Unique 

So I want $arrayAfterUnique to be an array of length one.
However, Select-Object will make a string out of it.

Is there an easy workaround for this problem?

mklement0
  • 382,024
  • 64
  • 607
  • 775
Jenew
  • 39
  • 8

3 Answers3

3

However Select-Object will make a string out of it

Not quite - Select-Object will return 0 or more objects, and depending on the number, the PowerShell runtime will decide whether the result is to be stored as a scalar or an array.

You can force the result to be an array regardless of how many objects are in the output, by using the array sub-expression operator @():

$array = @("55") 
$arrayAfterUnique = @($array | Select-Object -Unique)
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
2

To complement Mathias R. Jessen's helpful answer, which explains the issue well:

An alternative to @(...) is to type-constrain the result variable with [array] placed to the left of the variable, in order to ensure that the collected result is always an array:

# Ensure that $arrayAfterUnique always contains an array.
[array] $arrayAfterUnique = 1, 1 | Select-Object -Unique 

[array] is in effect the same as [object[]], i.e., a regular PowerShell array.

If the output from the RHS command is a single value (scalar), that value is now automatically converted to a single-element array; with multiple output values, the [object[]] array implicitly created by PowerShell for collecting the outputs is stored as-is.

Note: Type-constraining a variable means that future assignments too must be of the locked-in type or convertible to it; e.g., later execution of $arrayAfterUnique = 42 again converts integer 42 to a single-element array.

However, it is often not necessary to distinguish between scalars and arrays, because PowerShell allows you treat scalars like arrays, by given them a .Count property with value 1 and allowing indexing with [0] and [-1] - see this answer for more information.


As for the input side:

On sending arrays to the pipeline, PowerShell enumerates them - that is, the elements are sent one by one to the command in the next pipeline segment.

Therefore, single-value input is effectively the same as single-element array input.

That is, the following two commands behave identically:

# In both cases, Select-Object receives single value '55'
  '55'  | Select-Object -Unique   # single value
@('55') | Select-Object -Unique   # single-element array

If you need to ensure that an array is sent as a whole to the pipeline, precede it with , (the array-construction operator)[1]:

# Send array 1, 2 as a whole to ForEach-Object
# Note the need for (...) to clarify precedence.
, (1, 2) | ForEach-Object { $_.GetType().Name } # -> 'Object[]'

# Less efficient, but conceptually clearer alternative:
Write-Output -NoEnumerate 1, 2

[1] This wraps the array in an auxiliary single-element helper array, and it is that helper array that PowerShell enumerates, therefore sending its one and only element - the original array - as-is through the pipeline.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

You can add a custom function to convert to Array as shown here.

function ToArray
{
  begin
  {
    $output = @()
  }
  process
  {
    $output += $_
  }
  end
  {
    return ,$output
  }
}

Usage:

$array = @("55") 
$arrayAfterUnique = $array | Select-Object -Unique | ToArray
$arrayAfterUnique.GetType() # Returns System.Array

Update:

Worth mentioning, that this approach might have performance penalties as a new array is being created for every item due to use of +-.

EylM
  • 5,967
  • 2
  • 16
  • 28