Bagaimana saya bisa memaksa Powershell untuk mengembalikan array ketika panggilan hanya mengembalikan satu objek?

123

Saya menggunakan Powershell untuk menyiapkan pengikatan IIS di server web, dan mengalami masalah dengan kode berikut:

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

Jika ada 2+ IP di server, baiklah - Powershell mengembalikan array, dan saya dapat menanyakan panjang array dan mengekstrak alamat pertama dan kedua dengan baik.

Masalahnya adalah - jika hanya ada satu IP, Powershell tidak mengembalikan larik satu elemen, Powershell mengembalikan alamat IP (sebagai string, seperti "192.168.0.100") - string memiliki .lengthproperti, lebih besar dari 1, jadi tes lulus, dan saya berakhir dengan dua karakter pertama dalam string, bukan dua alamat IP pertama dalam kumpulan.

Bagaimana saya bisa memaksa Powershell untuk mengembalikan koleksi satu elemen, atau secara alternatif menentukan apakah "benda" yang dikembalikan adalah objek daripada koleksi?

Dylan Beattie
sumber
28
Aspek PowerShell yang paling menjengkelkan / penuh bug sendirian ..
pengguna2864740
Saya menganggap teladan Anda terlalu rumit. Pertanyaan yang lebih sederhana: << $ x = echo Hello; $ x -is [Array] >> menghasilkan False.
Raúl Salinas-Monteagudo
apakah perilaku ini berubah di PowerShell 5? Saya punya masalah serupa yang tidak dapat saya produksi ulang pada tanggal 5, tetapi dapat pada tanggal 4
NickL

Jawaban:

143

Tentukan variabel sebagai array dengan salah satu dari dua cara ...

Bungkus perintah pip Anda dalam tanda kurung dengan @di awal:

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

Tentukan tipe data dari variabel sebagai array:

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

Atau, periksa tipe data variabel ...

IF ($ServerIps -isnot [array])
{ <error message> }
ELSE
{ <proceed> }
JNK
sumber
28
Membungkus perintah @(...)akan mengembalikan larik meskipun tidak ada objek. Sedangkan menugaskan hasil ke [Array]variabel -typed masih akan mengembalikan $ null jika ada objek nol.
Nic
1
Perhatikan bahwa tidak ada solusi ini yang berfungsi jika objek yang dikembalikan adalah PSObject (mungkin yang lain).
Deadly-Bagel
2
@ Deadly-Bagel Dapatkah Anda menunjukkan contoh ini? Bagi saya @(...)bekerja dengan baik (menghasilkan hasil yang saya harapkan harus menghasilkan) untuk semua jenis objek.
pengguna4003407
1
Lucu sekali bagaimana Anda akhirnya kembali ke pertanyaan yang sama. Saya punya (dan sekali lagi) masalah yang sedikit berbeda, ya seperti dalam pertanyaan ini berfungsi dengan baik tetapi ketika kembali dari fungsi itu adalah cerita yang berbeda. Jika ada satu elemen, larik diabaikan dan hanya elemen yang dikembalikan. Jika Anda meletakkan koma sebelum variabel, itu memaksanya ke array tetapi array multi-elemen kemudian akan mengembalikan array dua dimensi. Sangat membosankan.
Deadly-Bagel
1
Gah, ini yang terjadi terakhir kali juga, sekarang saya tidak bisa menirunya. Bagaimanapun saya memecahkan masalah saya baru-baru ini dengan menggunakan Return ,$outyang tampaknya selalu berhasil. Jika saya mengalami masalah lagi, saya akan memposting contoh.
Deadly-Bagel
13

Paksa hasilnya ke Array sehingga Anda bisa memiliki properti Count. Objek tunggal (skalar) tidak memiliki properti Hitungan. String memiliki properti panjang sehingga Anda mungkin mendapatkan hasil yang salah, gunakan properti Hitung:

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

Ngomong-ngomong, alih-alih menggunakan karakter pengganti yang juga bisa mencocokkan string, gunakan operator -as:

[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration -filter "IPEnabled=TRUE" | Select-Object -ExpandProperty IPAddress | Where-Object {($_ -as [ipaddress]).AddressFamily -eq 'InterNetwork'}
Shay Levy
sumber
Untuk ini tidak bisakah dia juga hanya memeriksa tipe datanya -is?
JNK
String memiliki properti .length - itulah mengapa berfungsi ... :)
Dylan Beattie
8

Jika sebelumnya Anda mendeklarasikan variabel sebagai array, Anda dapat menambahkan elemen ke dalamnya - meskipun itu hanya satu ...

Ini harus bekerja ...

$serverIps = @()

gwmi Win32_NetworkAdapterConfiguration 
    | Where { $_.IPAddress } 
    | Select -Expand IPAddress 
    | Where { $_ -like '*.*.*.*' } 
    | Sort | ForEach-Object{$serverIps += $_}
Kyle Neier
sumber
Saya sebenarnya merasa ini adalah opsi yang paling jelas dan aman. Anda dapat menggunakan ".Count - ge 1 'pada koleksi dengan andal atau' Foreach '
Jaigene Kang
2

Anda dapat menggunakan Measure-Objectuntuk mendapatkan jumlah objek sebenarnya, tanpa menggunakan Countproperti objek.

$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
sumber
1

Anda dapat menambahkan koma ( ,) sebelum daftar kembali seperti return ,$listatau melemparkannya [Array]atau [YourType[]]di tempat Anda cenderung menggunakan daftar.

Luckybug
sumber
0

Saya mengalami masalah ini saat meneruskan array ke template penerapan Azure. Jika ada satu objek, PowerShell "mengubahnya" menjadi string. Dalam contoh di bawah ini, $adikembalikan dari fungsi yang membuat VM diobjek sesuai dengan nilai sebuah tag. Saya mengoper $ake New-AzureRmResourceGroupDeploymentcmdlet dengan membungkusnya @(). Seperti:

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

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

VMObject adalah salah satu parameter template.

Mungkin bukan cara paling teknis / kuat untuk melakukannya, tetapi cukup untuk Azure.


Memperbarui

Nah di atas berhasil. Saya sudah mencoba semua hal di atas dan beberapa, tetapi satu-satunya cara saya berhasil lulus $vmObjectsebagai array, kompatibel dengan template penerapan, dengan satu elemen adalah sebagai berikut (saya berharap MS telah bermain lagi (ini adalah laporan dan diperbaiki bug di 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 adalah keluaran dari Get-AzureRmVM.

Saya meneruskan $DeserializedJsonke parameter template penerapan (dari tipe array).

Sebagai referensi, kesalahan yang bagus New-AzureRmResourceGroupDeploymentadalah

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

Kembali sebagai objek yang direferensikan, jadi tidak pernah dikonversi saat meneruskan.

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