¿Cómo puedo obligar a Powershell a devolver una matriz cuando una llamada solo devuelve un objeto?

123

Estoy usando Powershell para configurar enlaces IIS en un servidor web y tengo un problema con el siguiente código:

$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]

Si hay más de 2 direcciones IP en el servidor, está bien: Powershell devuelve una matriz y puedo consultar la longitud de la matriz y extraer la primera y la segunda direcciones sin problemas.

El problema es que, si solo hay una IP, Powershell no devuelve una matriz de un elemento, devuelve la dirección IP (como una cadena, como "192.168.0.100"), la cadena tiene una .length propiedad, es mayor que 1, entonces la prueba pasa y termino con los dos primeros caracteres de la cadena, en lugar de las dos primeras direcciones IP de la colección.

¿Cómo puedo forzar a Powershell a que devuelva una colección de un elemento o, alternativamente, determinar si la "cosa" devuelta es un objeto en lugar de una colección?

Dylan Beattie
fuente
28
El aspecto más molesto / plagado de errores de PowerShell por sí solo ...
user2864740
Considero que su ejemplo es demasiado complicado. Pregunta más simple: << $ x = echo Hola; $ x -is [Array] >> produce False.
Raúl Salinas-Monteagudo
¿Este comportamiento cambió en powershell 5? Tengo un problema similar que no puedo reproducir en 5, pero puedo en 4
NickL

Respuestas:

143

Defina la variable como una matriz de una de estas dos formas ...

Envuelva sus comandos canalizados entre paréntesis con un @al principio:

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

Especifique el tipo de datos de la variable como una matriz:

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

O verifique el tipo de datos de la variable ...

IF ($ServerIps -isnot [array])
{ <error message> }
ELSE
{ <proceed> }
JNK
fuente
28
Si envuelve un comando, @(...)se devolverá una matriz incluso si no hay objetos. Mientras que la asignación del resultado a una [Array]variable con tipo todavía devolverá $ null si hay cero objetos.
Nic
1
Solo tenga en cuenta que ninguna de estas soluciones funciona si el objeto que se devuelve es un PSObject (posiblemente otros).
Deadly-Bagel
2
@ Deadly-Bagel ¿Puede mostrar un ejemplo de esto? Para mí, @(...)funciona correctamente (produce el resultado que espero que produzca) para cualquier tipo de objeto.
user4003407
1
Es curioso cómo terminas volviendo a las mismas preguntas. Tuve (y tengo nuevamente) un problema ligeramente diferente, sí, como en la pregunta esto funciona bien, pero al regresar de una función es una historia diferente. Si hay un elemento, la matriz se ignora y solo se devuelve el elemento. Si coloca una coma antes de la variable, la fuerza a una matriz, pero una matriz de elementos múltiples devolverá una matriz bidimensional. Muy tedioso.
Deadly-Bagel
1
Gah, esto es lo que pasó la última vez también, ahora no puedo replicarlo. En cualquier caso, resolví mi problema reciente usando Return ,$outque parece funcionar siempre. Si vuelvo a encontrar el problema, publicaré un ejemplo.
Deadly-Bagel
13

Forzar el resultado a una matriz para que pueda tener una propiedad Count. Los objetos individuales (escalares) no tienen una propiedad Count. Las cadenas tienen una propiedad de longitud, por lo que puede obtener resultados falsos, use la propiedad Count:

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

Por cierto, en lugar de usar un comodín que también puede coincidir con cadenas, use el operador -as:

[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration -filter "IPEnabled=TRUE" | Select-Object -ExpandProperty IPAddress | Where-Object {($_ -as [ipaddress]).AddressFamily -eq 'InterNetwork'}
Shay Levy
fuente
Para esto, ¿no podría simplemente verificar el tipo de datos con -is?
JNK
Las cadenas tienen una propiedad .length, por eso funciona ... :)
Dylan Beattie
8

Si declara la variable como una matriz antes de tiempo, puede agregarle elementos, incluso si es solo uno ...

Esto debería funcionar...

$serverIps = @()

gwmi Win32_NetworkAdapterConfiguration 
    | Where { $_.IPAddress } 
    | Select -Expand IPAddress 
    | Where { $_ -like '*.*.*.*' } 
    | Sort | ForEach-Object{$serverIps += $_}
Kyle Neier
fuente
De hecho, siento que esta es la opción más clara y segura. Puede utilizar de forma fiable ".Count - ge 1 'en la colección o' Foreach '
Jaigene Kang
2

Puede usar Measure-Objectpara obtener el recuento real de objetos, sin recurrir a la Countpropiedad de un objeto .

$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
}
Patricio
fuente
1

Puede agregar una coma ( ,) antes de devolver la lista como return ,$listo enviarla [Array]o [YourType[]]en el lugar donde tiende a usar la lista.

Luckybug
fuente
0

Tuve este problema al pasar una matriz a una plantilla de implementación de Azure. Si había un objeto, PowerShell lo "convertía" en una cadena. En el ejemplo siguiente, $ase devuelve desde una función que hace que la VM se objete según el valor de una etiqueta. Paso el $aal New-AzureRmResourceGroupDeploymentcmdlet envolviéndolo @(). Al igual que:

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

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

VMObject es uno de los parámetros de la plantilla.

Puede que no sea la forma más técnica / sólida de hacerlo, pero es suficiente para Azure.


Actualizar

Bueno, lo anterior funcionó. Probé todo lo anterior y algunos, pero la única forma en que logré pasar $vmObjectcomo una matriz, compatible con la plantilla de implementación, con un elemento es la siguiente (espero que MS haya estado reproduciendo nuevamente (esto fue un informe y arreglado error en 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 es el resultado de Get-AzureRmVM.

paso $DeserializedJson al parámetro de la plantilla de implementación (de tipo matriz).

Como referencia, el encantador error New-AzureRmResourceGroupDeploymentarroja es

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

Regrese como un objeto referenciado, por lo que nunca se convirtió al pasar.

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