Comment puis-je forcer Powershell à renvoyer un tableau lorsqu'un appel ne renvoie qu'un seul objet?

123

J'utilise Powershell pour configurer des liaisons IIS sur un serveur Web et j'ai un problème avec le code suivant:

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

S'il y a plus de 2 adresses IP sur le serveur, très bien - Powershell renvoie un tableau, et je peux interroger la longueur du tableau et extraire les première et deuxième adresses très bien.

Le problème est - s'il n'y a qu'une seule adresse IP, Powershell ne renvoie pas de tableau à un élément, il renvoie l'adresse IP (sous forme de chaîne, comme "192.168.0.100") - la chaîne a une .lengthpropriété, elle est supérieure à 1, donc le test réussit et je me retrouve avec les deux premiers caractères de la chaîne, au lieu des deux premières adresses IP de la collection.

Comment puis-je forcer Powershell à renvoyer une collection à un élément ou déterminer si la "chose" renvoyée est un objet plutôt qu'une collection?

Dylan Beattie
la source
28
Aspect le plus ennuyeux /
bogué
Je considère votre exemple comme trop compliqué. Question plus simple: << $ x = echo Bonjour; $ x -is [Array] >> renvoie False.
Raúl Salinas-Monteagudo
ce comportement a-t-il été modifié dans PowerShell 5? J'ai un problème similaire que je ne peux pas reproduire sur 5, mais je peux sur 4
NickL

Réponses:

143

Définissez la variable sous forme de tableau de l'une des deux manières suivantes ...

Mettez vos commandes canalisées entre parenthèses avec un @au début:

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

Spécifiez le type de données de la variable sous forme de tableau:

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

Ou vérifiez le type de données de la variable ...

IF ($ServerIps -isnot [array])
{ <error message> }
ELSE
{ <proceed> }
JNK
la source
28
L'encapsulation d'une commande @(...)retournera un tableau même s'il n'y a aucun objet. Alors que l'affectation du résultat à une [Array]variable de type -typé retournera toujours $ null s'il n'y a aucun objet.
Nic
1
Notez simplement qu'aucune de ces solutions ne fonctionne si l'objet renvoyé est un PSObject (éventuellement d'autres).
Deadly-Bagel
2
@ Deadly-Bagel Pouvez-vous montrer un exemple de cela? Pour moi, @(...)fonctionne correctement (produit le résultat que je pense qu'il devrait produire) pour tous les types d'objets.
user4003407
1
C'est drôle comme vous vous retrouvez sur les mêmes questions. J'ai eu (et j'ai encore) un problème légèrement différent, oui comme dans la question cela fonctionne bien mais au retour d'une fonction, c'est une autre histoire. S'il y a un élément, le tableau est ignoré et seul l'élément est renvoyé. Si vous mettez une virgule avant la variable, cela la force dans un tableau, mais un tableau à plusieurs éléments renverra alors un tableau à deux dimensions. Très fastidieux.
Deadly-Bagel
1
Gah, c'est ce qui s'est passé la dernière fois aussi, maintenant je ne peux pas le reproduire. En tout cas, j'ai résolu mon problème récent en utilisant Return ,$outce qui semble toujours fonctionner. Si je rencontre à nouveau le problème, je posterai un exemple.
Deadly-Bagel
13

Forcez le résultat à un tableau afin que vous puissiez avoir une propriété Count. Les objets uniques (scalaires) n'ont pas de propriété Count. Les chaînes ont une propriété length, vous pouvez donc obtenir de faux résultats, utilisez la propriété Count:

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

À propos, au lieu d'utiliser un caractère générique qui peut également correspondre à des chaînes, utilisez l'opérateur -as:

[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration -filter "IPEnabled=TRUE" | Select-Object -ExpandProperty IPAddress | Where-Object {($_ -as [ipaddress]).AddressFamily -eq 'InterNetwork'}
Shay Levy
la source
Pour cela, ne pouvait-il pas simplement vérifier le type de données avec -is?
JNK
Les chaînes ont une propriété .length - c'est pourquoi ça marche ... :)
Dylan Beattie
8

Si vous déclarez la variable en tant que tableau à l'avance, vous pouvez y ajouter des éléments - même s'il ne s'agit que d'un seul ...

Cela devrait fonctionner ...

$serverIps = @()

gwmi Win32_NetworkAdapterConfiguration 
    | Where { $_.IPAddress } 
    | Select -Expand IPAddress 
    | Where { $_ -like '*.*.*.*' } 
    | Sort | ForEach-Object{$serverIps += $_}
Kyle Neier
la source
J'ai vraiment l'impression que c'est l'option la plus claire et la plus sûre. Vous pouvez utiliser de manière fiable ".Count - ge 1" sur la collection ou "Foreach"
Jaigene Kang
2

Vous pouvez utiliser Measure-Objectpour obtenir le nombre d'objets réel, sans recourir à la Countpropriété d' un objet .

$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
la source
1

Vous pouvez soit ajouter une virgule ( ,) avant la liste de retour comme return ,$listou la transtyper [Array]ou [YourType[]]à l'endroit où vous avez tendance à utiliser la liste.

Luckybug
la source
0

J'ai eu ce problème en passant un tableau à un modèle de déploiement Azure. S'il y avait un objet, PowerShell l'a «converti» en chaîne. Dans l'exemple ci-dessous, $aest retourné à partir d'une fonction qui obtient une VM objectée en fonction de la valeur d'une balise. Je passe le $aà l' New-AzureRmResourceGroupDeploymentapplet de commande en l'enveloppant @(). Ainsi:

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

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

VMObject est l'un des paramètres du modèle.

Ce n'est peut-être pas le moyen le plus technique / robuste de le faire, mais c'est suffisant pour Azure.


Mettre à jour

Eh bien, ce qui précède a fonctionné. J'ai essayé tout ce qui précède et certains, mais la seule façon dont j'ai réussi à passer en $vmObjecttant que tableau, compatible avec le modèle de déploiement, avec un élément est la suivante (je suppose que MS a recommencé (c'était un rapport et corrigé bug 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 est la sortie de Get-AzureRmVM.

Je passe $DeserializedJsonau paramètre du modèle de déploiement (de type array).

Pour référence, la belle erreur New-AzureRmResourceGroupDeploymentjette est

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

Renvoie en tant qu'objet référencé, donc il n'est jamais converti en passant.

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