Come posso forzare Powershell a restituire un array quando una chiamata restituisce solo un oggetto?


123

Sto usando Powershell per configurare i collegamenti IIS su un server Web e ho un problema con il codice seguente:

$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 ci sono più di 2 IP sul server, va bene: Powershell restituisce un array e posso interrogare la lunghezza dell'array ed estrarre il primo e il secondo indirizzo senza problemi.

Il problema è: se c'è un solo IP, Powershell non restituisce un array di un elemento, restituisce l'indirizzo IP (come una stringa, come "192.168.0.100") - la stringa ha una .lengthproprietà, è maggiore di 1, quindi il test viene superato e mi ritrovo con i primi due caratteri nella stringa, invece dei primi due indirizzi IP nella raccolta.

Come posso forzare Powershell a restituire una raccolta di un elemento o, in alternativa, determinare se la "cosa" restituita è un oggetto piuttosto che una raccolta?


28
L'aspetto più fastidioso /
pieno di

Considero il tuo esempio troppo complicato. Domanda più semplice: << $ x = echo Hello; $ x -è [Array] >> restituisce False.
Raúl Salinas-Monteagudo

questo comportamento è stato modificato in PowerShell 5? ho un problema simile che non posso riprodurre su 5, ma posso su 4
NickL

Risposte:


143

Definisci la variabile come array in uno dei due modi ...

Racchiudi i tuoi comandi in pipe tra parentesi con un @all'inizio:

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

Specificare il tipo di dati della variabile come array:

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

Oppure controlla il tipo di dati della variabile ...

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

28
Il wrapping di un comando @(...)restituirà un array anche se ci sono zero oggetti. Considerando che l'assegnazione del risultato a una [Array]variabile di tipo restituirà ancora $ null se ci sono zero oggetti.
Nic

1
Solo una nota che nessuna di queste soluzioni funziona se l'oggetto restituito è un PSObject (forse altri).
Deadly-Bagel

2
@ Deadly-Bagel Puoi mostrare un esempio di questo? Per me @(...)funziona correttamente (produce risultati che mi aspetto che dovrebbe produrre) per qualsiasi tipo di oggetto.
user4003407

1
Buffo come tu finisca di nuovo sulle stesse domande. Ho avuto (e ho di nuovo) un problema leggermente diverso, sì, poiché nella domanda funziona bene, ma quando si torna da una funzione è una storia diversa. Se è presente un elemento, la matrice viene ignorata e viene restituito solo l'elemento. Se metti una virgola prima della variabile, la forza su un array, ma un array multi-elemento restituirà quindi un array bidimensionale. Molto noioso.
Deadly-Bagel

1
Gah, questo è quello che è successo anche l'ultima volta, ora non posso replicarlo. In ogni caso ho risolto il mio problema recente utilizzando Return ,$outche sembra funzionare sempre. Se mi imbatto di nuovo nel problema, posterò un esempio.
Deadly-Bagel

13

Forza il risultato su un array in modo da poter avere una proprietà Count. Gli oggetti singoli (scalari) non hanno una proprietà Count. Le stringhe hanno una proprietà length, quindi potresti ottenere risultati falsi, usa la proprietà Count:

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

A proposito, invece di usare un carattere jolly che può anche corrispondere a stringhe, usa l'operatore -as:

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

Per questo non poteva controllare anche il tipo di dati con -is?
JNK

Le corde hanno una proprietà .length - ecco perché funziona ... :)
Dylan Beattie,

8

Se dichiari la variabile come un array in anticipo, puoi aggiungere elementi ad essa, anche se è solo uno ...

Questo dovrebbe funzionare ...

$serverIps = @()

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

In realtà sento che questa è l'opzione più chiara e sicura. Puoi utilizzare in modo affidabile ".Count - ge 1" nella raccolta o "Foreach"
Jaigene Kang

2

È possibile utilizzare Measure-Objectper ottenere il conteggio effettivo degli oggetti, senza ricorrere alla Countproprietà di un oggetto .

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

1

Puoi aggiungere una virgola ( ,) prima dell'elenco di restituzione return ,$listo lanciarlo [Array]o [YourType[]]nel punto in cui tendi a usare l'elenco.


0

Ho avuto questo problema durante il passaggio di un array a un modello di distribuzione di Azure. Se era presente un oggetto, PowerShell lo "convertiva" in una stringa. Nell'esempio seguente, $aviene restituito da una funzione che ottiene oggetto di VM in base al valore di un tag. Passo il $aal New-AzureRmResourceGroupDeploymentcmdlet avvolgendolo @(). Così:

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

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

VMObject è uno dei parametri del modello.

Potrebbe non essere il modo più tecnico / robusto per farlo, ma è sufficiente per Azure.


Aggiornare

Ebbene quanto sopra ha funzionato. Ho provato tutto quanto sopra e alcuni, ma l'unico modo in cui sono riuscito a passare $vmObjectcome array, compatibile con il modello di distribuzione, con un elemento è il seguente (mi aspetto che MS abbia giocato di nuovo (questo era un rapporto e risolto bug nel 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 è l'output di Get-AzureRmVM.

Passo $DeserializedJsonal parametro del modello di distribuzione (di tipo array).

Per riferimento, l'errore adorabile 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.."

0

Restituisce come oggetto di riferimento, quindi non viene mai convertito durante il passaggio.

return @{ Value = @("single data") }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.