Como forçar o Powershell a retornar uma matriz quando uma chamada retorna apenas um objeto?

123

Estou usando o Powershell para configurar ligações do IIS em um servidor web e tendo um problema com o seguinte 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]

Se houver mais de 2 IPs no servidor, tudo bem - o Powershell retorna uma matriz, e eu posso consultar o comprimento da matriz e extrair o primeiro e o segundo endereços.

O problema é - se houver apenas um IP, o Powershell não retornará uma matriz de um elemento, ele retornará o endereço IP (como uma string, como "192.168.0.100") - a string possui uma .lengthpropriedade, é maior que 1, portanto o teste passa e acabo com os dois primeiros caracteres da sequência, em vez dos dois primeiros endereços IP da coleção.

Como forçar o Powershell a retornar uma coleção de um elemento ou, alternativamente, determinar se a "coisa" retornada é um objeto e não uma coleção?

Dylan Beattie
fonte
28
A maioria aspecto sozinho irritante / bug-ridden de PowerShell ..
user2864740
Considero o seu exemplo muito complicado. Pergunta mais simples: << $ x = echo Olá; $ x -is [Matriz] >> produz Falso.
Raúl Salinas-Monteagudo
esse comportamento foi alterado no PowerShell 5? Eu tenho um problema semelhante que eu não posso reproduzir em 5, mas pode em 4
NickL 08/01

Respostas:

143

Defina a variável como uma matriz de uma das duas maneiras ...

Coloque seus comandos canalizados entre parênteses com um @no início:

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

Especifique o tipo de dados da variável como uma matriz:

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

Ou verifique o tipo de dados da variável ...

IF ($ServerIps -isnot [array])
{ <error message> }
ELSE
{ <proceed> }
JNK
fonte
28
A quebra de um comando @(...)retornará uma matriz mesmo se houver zero objeto. Enquanto a atribuição do resultado a uma [Array]variável de tipo ainda retornará $ null se houver zero objeto.
Nic
1
Apenas observe que nenhuma dessas soluções funciona se o objeto que está sendo retornado for um PSObject (possivelmente outros).
Deadly-Bagel
2
@ Deadly-Bagel Você pode mostrar um exemplo disso? Para mim, @(...)trabalho corretamente (produza o resultado que espero que produza) para qualquer tipo de objeto.
user4003407
1
Engraçado como você acaba voltando às mesmas perguntas. Eu tive (e tenho novamente) um problema um pouco diferente, sim, como na pergunta isso funciona bem, mas ao retornar de uma função é uma história diferente. Se houver um elemento, a matriz será ignorada e somente o elemento será retornado. Se você colocar uma vírgula antes da variável, ela a força a uma matriz, mas uma matriz com vários elementos retornará uma matriz bidimensional. Muito entediante.
Deadly-Bagel
1
Gah, foi o que aconteceu da última vez também, agora não consigo replicar. De qualquer forma, resolvi meu problema recente usando o Return ,$outque parece sempre funcionar. Se eu encontrar o problema novamente, postarei um exemplo.
Deadly-Bagel
13

Force o resultado a uma matriz para que você possa ter uma propriedade Count. Objetos únicos (escalares) não têm uma propriedade Count. As strings têm uma propriedade length, para que você possa obter resultados falsos, use a propriedade Count:

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

A propósito, em vez de usar um curinga que também pode corresponder a cadeias, use o operador -as:

[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration -filter "IPEnabled=TRUE" | Select-Object -ExpandProperty IPAddress | Where-Object {($_ -as [ipaddress]).AddressFamily -eq 'InterNetwork'}
Shay Levy
fonte
Por isso, ele não poderia apenas verificar o tipo de dados -is?
21712 JNK
Cordas tem uma propriedade .length - é por isso que ele está trabalhando ... :)
Dylan Beattie
8

Se você declarar a variável como uma matriz antes do tempo, poderá adicionar elementos a ela - mesmo que seja apenas uma ...

Isso deve funcionar ...

$serverIps = @()

gwmi Win32_NetworkAdapterConfiguration 
    | Where { $_.IPAddress } 
    | Select -Expand IPAddress 
    | Where { $_ -like '*.*.*.*' } 
    | Sort | ForEach-Object{$serverIps += $_}
Kyle Neier
fonte
Na verdade, sinto que essa é a opção mais clara e segura. Você pode usar com segurança ".Conta - ge 1 'na coleção ou' Foreach '
Jaigene Kang 11/17/17
2

Você pode usar Measure-Objectpara obter a contagem real de objetos, sem recorrer à Countpropriedade de um 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
}
Patrick
fonte
1

Você pode adicionar uma vírgula ( ,) antes de retornar a lista como return ,$listou convertê-la [Array]ou [YourType[]]em onde você costuma usar a lista.

Luckybug
fonte
0

Eu tive esse problema ao passar uma matriz para um modelo de implantação do Azure. Se houvesse um objeto, o PowerShell o "converteria" em uma sequência. No exemplo abaixo, $aé retornado de uma função que obtém objeção de VM de acordo com o valor de uma tag. Eu passo o $apara o New-AzureRmResourceGroupDeploymentcmdlet envolvendo-o @(). Igual a:

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

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

VMObject é um dos parâmetros do modelo.

Pode não ser a maneira mais técnica / robusta de fazer isso, mas é suficiente para o Azure.


Atualizar

Bem, o acima funcionou. Eu tentei tudo o que precede e alguns, mas a única maneira de conseguir passar $vmObjectcomo uma matriz, compatível com o modelo de implantação, com um elemento é o seguinte (espero que a Microsoft esteja reproduzindo novamente (este foi um relatório e foi corrigido bug em 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 é a saída do Get-AzureRmVM.

Eu passo $DeserializedJsonpara o parâmetro do modelo de implantação (do tipo array).

Para referência, o adorável erro New-AzureRmResourceGroupDeploymenté

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

Retorne como um objeto referenciado, para que nunca seja convertido ao passar.

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