当一个调用只返回一个对象时,我如何强制Powershell返回一个数组?

zvokhttg  于 2023-05-29  发布在  Shell
关注(0)|答案(9)|浏览(137)

我正在使用PowerShell在Web服务器上设置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返回一个单元素集合,或者确定返回的“事物”是否是对象而不是集合?

gxwragnw

gxwragnw1#

通过以下两种方式之一将变量定义为数组...

将管道命令用括号括起来,并在开头加上@

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

e5nqia272#

将结果强制为Array,以便您可以拥有Count属性。单个对象(标量)没有“计数”特性。字符串有一个length属性,所以你可能会得到错误的结果,使用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'}
2ledvvac

2ledvvac3#

你可以在return list前加上一个逗号(,),比如return ,$list,或者在你倾向于使用列表的地方将其转换为[Array][YourType[]]

iugsix8n

iugsix8n4#

如果你提前将变量声明为一个数组,你可以向它添加元素--即使它只是一个数组。
这应该可以。。

$serverIps = @()

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

bxpogfeg5#

您可以使用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
}
11dmarpk

11dmarpk6#

我在向Azure部署模板传递数组时遇到此问题。如果有一个对象,PowerShell会将其“转换”为字符串。在下面的示例中,$a是从一个函数返回的,该函数根据标记的值获取VM对象。我通过将$a Package 在@()中将其传递给New-AzureRmResourceGroupDeploymentcmdlet。就像这样:

$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传递给部署模板的参数(类型为array)。
作为参考,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.."
swvgeqrz

swvgeqrz7#

作为被引用对象返回,因此在传递时从不进行转换。

return @{ Value = @("single data") }
bq8i3lrv

bq8i3lrv8#

有一种方法可以处理你的情况。让大部分代码保持原样,只需更改处理$serverIps对象的方式。这段代码可以处理$null,只有一个项目,和许多项目。

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

# Always use ".Count" instead of ".Length".
# This works on $null, only one item, or many items.
if ($serverIps.Count -le 1) {
    Write-Host "You need at least 2 IP addresses for this to work!"
    exit
}

# Always use foreach on a array-possible object, so that
# you don't have deal with this issue anymore.
$serverIps | foreach {
    # The $serverIps could be $null. Even $null can loop once.
    # So we need to skip the $null condition.
    if ($_ -ne $null) {
        # Get the index of the array.
        # The @($serverIps) make sure it must be an array.
        $idx = @($serverIps).IndexOf($item)

        if ($idx -eq 0) { $primaryIp = $_ }
        if ($idx -eq 1) { $secondaryIp = $_ }
    }
}

在PowerShell Core中,每个对象都有一个.Count属性。在Windows PowerShell中,“几乎”每个对象都有一个.Count属性。

kd3sttzy

kd3sttzy9#

以下是在PowerShell中使用流水线与数组时发生的情况。
举个例子:

@(
    @('single-element-array') | 
        ForEach-Object { "$_".Trim() } |
        Sort-Object -Unique
) | ConvertTo-Json

结果将简单地是:

"single-element-array"

首先,上面的代码看起来很傻,但是,假设你查询了一些数据,结果是一个单元素数组,其元素可能由空格组成,也可能是$null,你想确保值折叠为'',因此是"$_".Trim()。好了,这样一来,生成的JSON可能不是人们所期望的来自其他编程语言(如C#)的JSON。
正如其他答案所描述的那样,由于该表达式流经管道,因此适用与其他答案所描述的相同的规则,并且结果对象是“未 Package 的”。
通常,函数实现流水线如下:

function Some-Function {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory, ValueFromPipeline)]
        [object[]] $InputObject
    )

    Begin {
        # This block is optional
    }

    Process {
        # This block is optional, too, but is required
        # to process pipelined objects

        foreach ($o in $Object) {
            # do something
        }
    }

    End {
        # This block is optional IF a Process block
        # is defined.

        # If no Begin, Process, or End blocks are defined,
        # the code after the parameters is assumed to be
        # an End block, which effectively turns the function
        # into a PowerShell 2.0 filter function.

        # When data is piped into this function, and a Process
        # block exists, the value of $InputObject in this block
        # is the very last item that was piped in.
        # In cases where the pipeline is not being used, $InputObject
        # is the value of the parameter as passed to the function.
    }
}

要使用上述内容:

$Stuff | Some-Function

在这样的函数中,使用管道,值被 * 串行 * 处理。$Stuff中的每个值都通过管道传递到函数中。如果函数有一个Begin块,在处理通过管道传入的参数的任何值之前运行一次,即在Begin块中可获得其它非管线化参数值。然后,Process块对每个流水线输入的值执行 * 一次 *。最后,如果函数有一个End块,则在处理完所有输入的值后,在最后运行。
那么,为什么要在Process块中使用foreach语句呢?当函数被这样调用时处理:

Some-Function -InputObject $Stuff

在这个调用中,$Stuff的整个数组通过参数传递给函数。因此,为了正确处理数据,在Process块中使用foreach循环。现在,这应该为您提供了所需的信息,以了解如何规避我第一个示例中的问题。为了使该示例正确运行,需要按以下方式执行:

ConvertTo-JSON -InputObject @(
    @('single-element-array') |
        ForEach-Object { "$_".Trim() } |
        Sort-Object -Unique
)

这将导致预期的JSON:

[
  "single-element-array"
]

有了这些知识,要修复代码以使其按预期工作,您应该执行以下操作:

# Get-WmiInstance is deprecated in favor of GetCimInstance
# It's generally a good idea to avoid aliases--they can be redefined!
# That could result in unexpected results.
# But the big change here is forcing the output into an array via @()
$serverIps = @(
    Get-CimInstance Win32_NetworkAdapterConfiguration 
        | Where-Object { $_.IPAddress } 
        | Select-Object -ExpandProperty IPAddress 
        | Where-Object { $_ -like '*.*.*.*' } 
        | Sort-Object
)

# Don't use length. In certain circumstances (esp. strings), Length
# can give unexpected results. PowerShell adds a "synthetic property"
# to all "collection objects" called Count. Use that instead, as
# strings (oddly enough, since they are IEnumerbale<Char>) don't get a
# Count synthetic property. :/
if ($serverIps.Count -le 1) {
    Write-Host "You need at least 2 IP addresses for this to work!"
    exit
}

希望这有助于澄清问题。

相关问题