Jak mogę zmusić program PowerShell do zwrócenia tablicy, gdy wywołanie zwraca tylko jeden obiekt?

123

Używam programu PowerShell do konfigurowania powiązań usług IIS na serwerze sieci Web i mam problem z następującym kodem:

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

Jeśli na serwerze jest ponad 2 adresy IP, dobrze - Powershell zwraca tablicę i mogę zapytać o długość tablicy i wyodrębnić pierwszy i drugi adres.

Problem polega na tym, że jeśli istnieje tylko jeden adres IP, Powershell nie zwraca tablicy jednoelementowej, zwraca adres IP (jako ciąg znaków, np. „192.168.0.100”) - ciąg ma .lengthwłaściwość większą niż 1, więc test kończy się pomyślnie i otrzymuję pierwsze dwa znaki w ciągu zamiast pierwszych dwóch adresów IP w kolekcji.

Jak mogę zmusić program Powershell do zwrócenia jednoelementowej kolekcji lub alternatywnie określić, czy zwracana „rzecz” jest obiektem, a nie kolekcją?

Dylan Beattie
źródło
28
Najbardziej irytujący /
pełen
Uważam, że twój przykład jest zbyt skomplikowany. Prostsze pytanie: << $ x = echo Witaj; $ x -is [Array] >> daje wartość Fałsz.
Raúl Salinas-Monteagudo
czy to zachowanie zostało zmienione w programie PowerShell 5? mam podobny problem, którego nie mogę odtworzyć na 5, ale mogę na 4
NickL

Odpowiedzi:

143

Zdefiniuj zmienną jako tablicę na jeden z dwóch sposobów ...

Zawiń potokowe polecenia w nawiasach z @na początku:

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

Określ typ danych zmiennej jako tablicę:

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

Lub sprawdź typ danych zmiennej ...

IF ($ServerIps -isnot [array])
{ <error message> }
ELSE
{ <proceed> }
JNK
źródło
28
Opakowanie polecenia @(...)zwróci tablicę, nawet jeśli nie ma żadnych obiektów. Natomiast przypisanie wyniku do [Array]zmiennej typu -typ nadal zwróci $ null, jeśli nie będzie żadnych obiektów.
Nic
1
Zwróć uwagę, że żadne z tych rozwiązań nie działa, jeśli zwracany obiekt jest PSObject (prawdopodobnie innymi).
Deadly-Bagel
2
@ Deadly-Bagel Czy możesz pokazać przykład tego? Dla mnie @(...)działa poprawnie (generuje wynik, którego oczekuję, że powinien dać) dla dowolnego rodzaju obiektów.
user4003407
1
Zabawne, jak w końcu wracasz do tych samych pytań. Miałem (i znowu mam) nieco inny problem, tak jak w pytaniu to działa dobrze, ale po powrocie z funkcji to już inna historia. Jeśli istnieje jeden element, tablica jest ignorowana i zwracany jest tylko element. Jeśli umieścisz przecinek przed zmienną, wymusi to na tablicy, ale tablica wieloelementowa zwróci tablicę dwuwymiarową. Bardzo nudne.
Deadly-Bagel
1
Gah, to też stało się ostatnim razem, teraz nie mogę tego powtórzyć. W każdym razie rozwiązałem swój ostatni problem, używając, Return ,$outktóre wydaje się zawsze działać. Jeśli znowu napotkam problem, podam przykład.
Deadly-Bagel
13

Wymuś wynik na Array, aby mieć właściwość Count. Pojedyncze obiekty (skalarne) nie mają właściwości Count. Ciągi mają właściwość length, więc możesz otrzymać fałszywe wyniki, użyj właściwości Count:

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

Nawiasem mówiąc, zamiast używać symbolu wieloznacznego, który może również pasować do ciągów, użyj operatora -as:

[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration -filter "IPEnabled=TRUE" | Select-Object -ExpandProperty IPAddress | Where-Object {($_ -as [ipaddress]).AddressFamily -eq 'InterNetwork'}
Shay Levy
źródło
W tym celu nie mógł po prostu sprawdzić typu danych -is?
JNK,
Ciągi mają właściwość .length - dlatego to działa ... :)
Dylan Beattie
8

Jeśli wcześniej zadeklarujesz zmienną jako tablicę, możesz dodać do niej elementy - nawet jeśli jest to tylko jeden ...

To powinno działać ...

$serverIps = @()

gwmi Win32_NetworkAdapterConfiguration 
    | Where { $_.IPAddress } 
    | Select -Expand IPAddress 
    | Where { $_ -like '*.*.*.*' } 
    | Sort | ForEach-Object{$serverIps += $_}
Kyle Neier
źródło
Właściwie czuję, że jest to najbardziej przejrzysta i bezpieczna opcja. Możesz niezawodnie używać „.Count - ge 1” w kolekcji lub „Foreach”
Jaigene Kang.
2

Możesz użyć, Measure-Objectaby uzyskać rzeczywistą liczbę obiektów, bez uciekania się do Countwłaściwości obiektu.

$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
źródło
1

Możesz albo dodać przecinek ( ,) przed listą zwrotów, na przykład, return ,$listrzucić ją [Array]lub [YourType[]]w miejscu, w którym zwykle używasz listy.

Luckybug
źródło
0

Miałem ten problem z przekazaniem tablicy do szablonu wdrożenia platformy Azure. Jeśli był jeden obiekt, PowerShell „przekonwertował” go na łańcuch. W poniższym przykładzie $afunkcja jest zwracana przez funkcję, która pobiera obiekt wirtualny na podstawie wartości tagu. Przekazuję $ado polecenia New-AzureRmResourceGroupDeploymentcmdlet, zawijając go @(). Tak jak to:

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

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

VMObject jest jednym z parametrów szablonu.

Może nie być najbardziej technicznym / niezawodnym sposobem, aby to zrobić, ale wystarczy na platformę Azure.


Aktualizacja

Cóż, powyższe zadziałało. Wypróbowałem wszystkie powyższe i niektóre, ale jedyny sposób, w jaki udało mi się przekazać $vmObjectjako tablicę, kompatybilną z szablonem rozmieszczenia, z jednym elementem jest następujący (spodziewam się, że MS znowu grało (to był raport i naprawiono błąd w 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 jest wynikiem działania Get-AzureRmVM.

Przechodzę $DeserializedJsondo parametru szablonu wdrożenia (typu tablica).

Dla porównania, piękny błąd New-AzureRmResourceGroupDeploymentgenerowany przez błąd to

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

Zwraca jako obiekt, do którego istnieje odwołanie, więc nigdy nie jest konwertowany podczas przekazywania.

return @{ Value = @("single data") }
masato
źródło