How can I force Powershell to return an array when a call only returns one object?

Powershell

Powershell Problem Overview


I'm using Powershell to set up IIS bindings on a web server, and having a problem with the following code:

$serverIps = gwmi Win32_NetworkAdapterConfiguration 
    | Where { $_.IPAddress } 
    | Select -Expand IPAddress 
    | Where { $_ -like '*.*.*.*' } 
    | Sort

if ($serverIps.length -le 1) {
    Write-Host "You need at least 2 IP addresses for this to work!"
    exit
}

$primaryIp = $serverIps[0]
$secondaryIp = $serverIps[1]

If there's 2+ IPs on the server, fine - Powershell returns an array, and I can query the array length and extract the first and second addresses just fine.

Problem is - if there's only one IP, Powershell doesn't return a one-element array, it returns the IP address (as a string, like "192.168.0.100") - the string has a .length property, it's greater than 1, so the test passes, and I end up with the first two characters in the string, instead of the first two IP addresses in the collection.

How can I either force Powershell to return a one-element collection, or alternatively determine whether the returned "thing" is an object rather than a collection?

Powershell Solutions


Solution 1 - Powershell

Define the variable as an array in one of two ways...

Wrap your piped commands in parentheses with an @ at the beginning:

$serverIps = @(gwmi Win32_NetworkAdapterConfiguration 
    | Where { $_.IPAddress } 
    | Select -Expand IPAddress 
    | Where { $_ -like '*.*.*.*' } 
    | Sort)

Specify the data type of the variable as an array:

[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration 
    | Where { $_.IPAddress } 
    | Select -Expand IPAddress 
    | Where { $_ -like '*.*.*.*' } 
    | Sort

Or, check the data type of the variable...

IF ($ServerIps -isnot [array])
{ <error message> }
ELSE
{ <proceed> }

Solution 2 - Powershell

Force the result to an Array so you could have a Count property. Single objects (scalar) do not have a Count property. Strings have a length property so you might get false results, use the Count property:

if (@($serverIps).Count -le 1)...

By the way, instead of using a wildcard that can also match strings, use the -as operator:

[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration -filter "IPEnabled=TRUE" | Select-Object -ExpandProperty IPAddress | Where-Object {($_ -as [ipaddress]).AddressFamily -eq 'InterNetwork'}

Solution 3 - Powershell

You can either add a comma(,) before return list like return ,$list or cast it [Array] or [YourType[]] at where you tend to use the list.

Solution 4 - Powershell

If you declare the variable as an array ahead of time, you can add elements to it - even if it is just one...

This should work...

$serverIps = @()

gwmi Win32_NetworkAdapterConfiguration 
    | Where { $_.IPAddress } 
    | Select -Expand IPAddress 
    | Where { $_ -like '*.*.*.*' } 
    | Sort | ForEach-Object{$serverIps += $_}

Solution 5 - Powershell

You can use Measure-Object to get the actual object count, without resorting to an object's Count property.

$serverIps = gwmi Win32_NetworkAdapterConfiguration 
    | Where { $_.IPAddress } 
    | Select -Expand IPAddress 
    | Where { $_ -like '*.*.*.*' } 
    | Sort

if (($serverIps | Measure).Count -le 1) {
    Write-Host "You need at least 2 IP addresses for this to work!"
    exit
}

Solution 6 - Powershell

Return as a referenced object, so it never converted while passing.

return @{ Value = @("single data") }

Solution 7 - Powershell

I had this problem passing an array to an Azure deployment template. If there was one object, PowerShell "converted" it to a string. In the example below, $a is returned from a function that gets VM objected according to the value of a tag. I pass the $a to the New-AzureRmResourceGroupDeployment cmdlet by wrapping it in @(). Like so:

$TemplateParameterObject=@{
     VMObject=@($a)
}

New-AzureRmResourceGroupDeployment -ResourceGroupName $RG -Name "TestVmByRole" -Mode Incremental -DeploymentDebugLogLevel All -TemplateFile $templatePath -TemplateParameterObject $TemplateParameterObject -verbose

VMObject is one of the template's parameters.

Might not be the most technical / robust way to do it, but it's enough for Azure.


Update

Well the above did work. I've tried all the above and some, but the only way I have managed to pass $vmObject as an array, compatible with the deployment template, with one element is as follows (I expect MS have been playing again (this was a report and fixed bug in 2015)):

[void][System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")
    
    foreach($vmObject in $vmObjects)
    {
        #$vmTemplateObject = $vmObject 
        $asJson = (ConvertTo-Json -InputObject $vmObject -Depth 10 -Verbose) #-replace '\s',''
        $DeserializedJson = (New-Object -TypeName System.Web.Script.Serialization.JavaScriptSerializer -Property @{MaxJsonLength=67108864}).DeserializeObject($asJson)
    }

$vmObjects is the output of Get-AzureRmVM.

I pass $DeserializedJson to the deployment template' parameter (of type array).

For reference, the lovely error New-AzureRmResourceGroupDeployment throws is

"The template output '{output_name}' is not valid: The language expression property 'Microsoft.WindowsAzure.ResourceStack.Frontdoor.Expression.Expressions.JTokenExpression' 
can't be evaluated.."

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDylan BeattieView Question on Stackoverflow
Solution 1 - PowershellJNKView Answer on Stackoverflow
Solution 2 - PowershellShay LevyView Answer on Stackoverflow
Solution 3 - PowershellLuckybugView Answer on Stackoverflow
Solution 4 - PowershellKyle NeierView Answer on Stackoverflow
Solution 5 - PowershellPatrickView Answer on Stackoverflow
Solution 6 - PowershellmasatoView Answer on Stackoverflow
Solution 7 - Powershellwoter324View Answer on Stackoverflow