我创建了一个高级功能,以从在VMware ESXi上运行的VM获取MAC地址。

function Get-MacFromVm {
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param(
        # The name of the VM of which we want to obtain the mac address.
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [string[]]
        $Name
    )

    Begin {}
    Process {
        foreach ($item in $Name) {
            if ($PSCmdlet.ShouldProcess($item, "Getting the mac address")) {
                Get-VM $item -PipelineVariable vm |
                    Get-NetworkAdapter |
                    Select-Object @{n="Name"; e={$vm.Name}},
                        @{n="ClientId"; e={$_.MacAddress -replace ":","-"}}
            }
        }
    }
    End {}
}

到目前为止,一切正常。
我可以通过以下任何一种方式使用它并获得结果。

它通过命名参数或作为管道输入接受单个字符串或字符串数​​组。
Get-MacFromVm -Name "playground"
Get-MacFromVm -Name "playground", "DC01"
"playground", "DC01" | Get-MacFromVm

输出是带有2个属性(名称和ClientId)的[PSCustomObject]

现在,当我想通过使用-PipelineVariable参数将结果链接到多个其他cmdlet时,问题就开始了。

通常我应该可以这样使用它:
Get-MacFromVm -Name "playground" -PipelineVariable pv | % {$pv}

但这并没有显示任何结果。如果我将$pv替换为$_,它的确显示了正确的结果,但我不能在管道链中更远的地方使用该自动变量2或3 cmdlet。

尽管我可以通过使用-OutVariable和/或将其拆分为多行来解决此问题。
我想知道为什么这行不通,我想知道我在这里缺少什么。

最佳答案

我没有-PipelineVariable参数的经验。因此,我以此为契机学习了-PipelineVariable参数:

由于我也无法轻松访问VM,因此我对该功能进行了如下模拟:

Function Get-MacFromVm {
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param(
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [string[]]
        $Name
    )
    Function MacAddress {(1..4 | ForEach {'{0:x2}' -f (Get-Random -Min 0 -Max 255)}) -Join ":"}
    Function Get-VM {[cmdletbinding()] Param ($Name) [pscustomobject]@{Name = $Name; MacAddress = (MacAddress)}}
    ForEach ($Item in $Name) {
        If ($PSCmdlet.ShouldProcess($Item, "Getting the mac address")) {
            Get-VM $item -PipelineVariable vm |
            Select-Object @{n="Name"; e={$vm.Name}}, @{n="ClientId"; e={$_.MacAddress -replace ":","-"}}
        }
    }
}

但我无法重现该问题:
PS C:\> Get-MacFromVm -Name "playground", "DC01" -PipelineVariable pv | % {$pv}

Name       ClientId
----       --------
playground 3c-23-55-c4
DC01       4f-38-42-a7

因此,我想知道此模拟功能是否也适用于您。
如果是的话,也许您可​​以找到一个真正的VM对象的差异。

了解PowerShell,我会质疑是否没有输出或没有显示任何内容。那么,如果仅输出单个属性,将会发生什么:
Get-MacFromVm -Name "playground" -PipelineVariable pv | % {$pv.Name}

要么:
Get-MacFromVm -Name "playground" -PipelineVariable pv | % {$pv.GetType()}

这会返回“You cannot call a method on a null-valued expression.”错误吗?

更新2019年10月30日

根据mklement0的注释:

您没有看到the bug的原因是由于未使用begin / process / end块,您的函数隐式地在end块中运行,并且所有这些脚本块的第一个调用都在管道变量中捕获,但是而不是后续脚本块调用的输出,这会在具有process块的函数中发生。

意味着正确的模拟应该是:
Function Get-MacFromVm {
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param(
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [string[]]
        $Name
    )
    Begin {
        Function MacAddress {(1..4 | ForEach {'{0:x2}' -f (Get-Random -Min 0 -Max 255)}) -Join ":"}
        Function Get-VM {[cmdletbinding()] Param ($Name) [pscustomobject]@{Name = $Name; MacAddress = (MacAddress)}}
    }
    Process {
        ForEach ($Item in $Name) {
            If ($PSCmdlet.ShouldProcess($Item, "Getting the mac address")) {
                Get-VM $item -PipelineVariable vm |
                Select-Object @{n="Name"; e={$vm.Name}}, @{n="ClientId"; e={$_.MacAddress -replace ":","-"}}
            }
        }
    }
}

的确对于PipelineVariable没有任何结果:
PS C:\> Get-MacFromVm -Name "playground", "DC01" -PipelineVariable pv | % {$pv}
PS C:\>

当前项目($_)可以:
PS C:\> Get-MacFromVm -Name "playground", "DC01" -PipelineVariable pv | % {$_}

Name       ClientId
----       --------
playground e7-b5-a0-31
DC01       0f-ed-94-cc

10-04 16:36