Как заставить Powershell возвращать массив, когда вызов возвращает только один объект?

123

Я использую Powershell для настройки привязок IIS на веб-сервере, и у меня возникла проблема со следующим кодом:

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

Если на сервере 2+ IP-адреса, отлично - Powershell возвращает массив, и я могу запросить длину массива и легко извлечь первый и второй адреса.

Проблема в том, что если есть только один IP-адрес, Powershell не возвращает одноэлементный массив, он возвращает IP-адрес (в виде строки, например "192.168.0.100") - строка имеет .lengthсвойство, оно больше 1, поэтому тест проходит, и я получаю первые два символа в строке вместо первых двух IP-адресов в коллекции.

Как я могу заставить Powershell возвращать одноэлементную коллекцию или, альтернативно, определить, является ли возвращенная «вещь» объектом, а не коллекцией?

Дилан Битти
источник
29
Самый раздражающий и связанный с ошибками аспект PowerShell в
одиночку
Я считаю ваш пример слишком сложным. Более простой вопрос: << $ x = echo Привет; $ x -is [Array] >> возвращает значение False.
Рауль Салинас-Монтеагудо
было ли это поведение изменено в PowerShell 5? У меня аналогичная проблема, которую я не могу воспроизвести на 5, но могу на 4
NickL

Ответы:

143

Определите переменную как массив одним из двух способов ...

Заключите передаваемые по конвейеру команды в круглые скобки с @началом:

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

Укажите тип данных переменной в виде массива:

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

Или проверьте тип данных переменной ...

IF ($ServerIps -isnot [array])
{ <error message> }
ELSE
{ <proceed> }
JNK
источник
28
Заключение команды в @(...)оболочку вернет массив, даже если есть нулевые объекты. В то время как присвоение результата [Array]переменной с типом -типом все равно вернет $ null, если есть нулевые объекты.
Nic
1
Просто обратите внимание, что ни одно из этих решений не работает, если возвращаемый объект является PSObject (возможно, другим).
Deadly-Bagel
2
@ Deadly-Bagel Можете ли вы показать этому пример? Для меня @(...)нормально работать (давать результат, который, как я ожидаю, должен) для любых типов объектов.
user4003407
1
Забавно, как ты снова отвечаешь на те же вопросы. У меня была (и снова есть) немного другая проблема, да, поскольку в вопросе это работает нормально, но при возврате из функции это другая история. Если есть один элемент, массив игнорируется и возвращается только элемент. Если вы поставите запятую перед переменной, она будет преобразована в массив, но многоэлементный массив вернет двумерный массив. Очень утомительно.
Deadly-Bagel
1
Ага, вот что случилось и в прошлый раз, теперь я не могу это повторить. Во всяком случае, я решил свою недавнюю проблему, используя то, Return ,$outчто, кажется, всегда работает. Если я снова столкнусь с проблемой, я опубликую пример.
Deadly-Bagel
14

Переведите результат в массив, чтобы у вас было свойство Count. Отдельные объекты (скалярные) не имеют свойства Count. Строки имеют свойство длины, поэтому вы можете получить ложные результаты, используйте свойство Count:

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

Кстати, вместо использования подстановочного знака, который также может соответствовать строкам, используйте оператор -as:

[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration -filter "IPEnabled=TRUE" | Select-Object -ExpandProperty IPAddress | Where-Object {($_ -as [ipaddress]).AddressFamily -eq 'InterNetwork'}
Шэй Леви
источник
Для этого он не мог просто проверить тип данных с помощью -is?
JNK
У строк есть свойство .length - поэтому оно работает ... :)
Дилан Битти,
8

Если вы заранее объявите переменную как массив, вы можете добавлять в нее элементы - даже если это всего лишь один ...

Это должно работать ...

$serverIps = @()

gwmi Win32_NetworkAdapterConfiguration 
    | Where { $_.IPAddress } 
    | Select -Expand IPAddress 
    | Where { $_ -like '*.*.*.*' } 
    | Sort | ForEach-Object{$serverIps += $_}
Кайл Нейер
источник
Мне кажется, что это наиболее понятный и безопасный вариант. Вы можете надежно использовать ".Count - ge 1" в коллекции или "Foreach"
Джайген Канг
2

Вы можете использовать Measure-Objectдля получения фактического количества объектов, не прибегая к Countсвойствам объекта.

$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

Вы можете либо добавить запятую ( ,) перед списком возврата, например, return ,$listлибо указать его, [Array]либо [YourType[]]там, где вы обычно используете список.

Luckybug
источник
0

У меня возникла проблема с передачей массива в шаблон развертывания Azure. Если был один объект, PowerShell «конвертировал» его в строку. В приведенном ниже примере $aвозвращается из функции, которая вызывает возражения ВМ в соответствии со значением тега. Я передать $aв New-AzureRmResourceGroupDeploymentкомандлет, окружив его @(). Вот так:

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

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

VMObject - один из параметров шаблона.

Возможно, это не самый технический / надежный способ сделать это, но для Azure этого достаточно.


Обновить

Что ж, все вышеперечисленное сработало. Я пробовал все вышеперечисленное и некоторые, но единственный способ передать его $vmObjectкак массив, совместимый с шаблоном развертывания, с одним элементом, выглядит следующим образом (я ожидаю, что MS снова играет (это был отчет и исправлен баг в 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 является результатом Get-AzureRmVM.

Перехожу $DeserializedJsonк параметру шаблона развертывания (типа массив).

Для справки, прекрасные ошибки 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
источник
0

Возврат как объект, на который есть ссылка, поэтому он никогда не преобразовывался при передаче.

return @{ Value = @("single data") }
Масато
источник