PowerShell -如何将两个自定义对象的属性添加在一起以创建一个合并的自定义对象



我正在使用一些PowerShell函数从一些REST API端点获取数据。遍历第一个查询的结果允许我使用id字段来检索第二个查询中的匹配结果,但是,第二个查询返回的结果没有任何与原始结果集匹配的属性,因此我认为我不能执行JOIN或类似的操作。

例如,假设查询第一个REST端点的结果存储在$a中。

$a = 
ID  Title                  Location  Internal IP Address
--  -----                  --------  -------------------
1   Endpoint 1             UK        10.0.0.1
2   Endpoint 2             US        10.1.0.1
3   Endpoint 3             AUS       10.2.0.1

并使用ID = 1查询第二个REST端点,我们得到$b中存储的以下内容:

$b = 
NATed IP Address Last Provisioned Time
---------------- ---------------------
1.2.3.4          2022-02-10T04:09:31.988909+01:00

我想做的是结合两者的属性,一次一个记录,所以在我检索了$b之后,我将创建$c,它看起来像这样:

$c =
ID  Title       Location  Internal IP Address NATed IP Address Last Provisioned Time
--  -----       --------  ------------------- ---------------- ---------------------
1   Endpoint 1  UK        10.0.0.1            1.2.3.4          2022-02-10T04:09:31.988909+01:00

当代码遍历$a的结果时,因此我应该构建一个完整的合并对象,并且在$a的三次迭代之后,我们应该以类似的内容结束:

$d =
ID  Title       Location  Internal IP Address NATed IP Address Last Provisioned Time
--  -----       --------  ------------------- ---------------- ---------------------
1   Endpoint 1  UK        10.0.0.1            1.2.3.4          2022-02-10T04:09:31.988909+01:00
2   Endpoint 2  US        10.1.0.1            1.2.3.5          2022-02-10T04:09:31.988909+01:00
3   Endpoint 3  AUS       10.2.0.1            1.2.3.6          2022-02-10T04:09:31.988909+01:00

我觉得这将是相当简单的,然而,我看过许多不同的帖子和网站,但似乎没有一个能帮助我实现我想要的,或者更可能的是,我只是误解了Add-Member等方法如何在多个管道循环中起作用,或者错过了盲目的明显

我最接近的是下面的代码,它实际上导致一个数组或哈希表(取决于我如何初始化$collection变量),但结果是相似的:

$collection = @{}
foreach ($item in $a) {
$collection += @{
"Title" = $item.Title
"Location" = $item.Location
"Internal IP Address" = $item."Internal IP Address"
}
}
foreach ($item in $b) {
$collection += @{
"NATed IP Address" = $item."NATed IP Address"
"Last Provisioned Time" = $item."Last Provisioned Time"
}
}

生成如下键/值表:

Name                           Value
----                           -----
ID                             1
Title                          Endpoint 1
Internal IP Address            10.0.0.1 
Location                       UK
NATed IP Address               1.2.3.4
Last Provisioned Time          2022-02-10T03:21:29.265257+01:00

这意味着我最终将得到一个数组的数组(或一个哈希表的哈希表)。虽然我认为我仍然可以使用这种类型的数据格式,但我很想知道我需要做些什么来实现我的初始目标。

获取$a$b的代码如下:有一些函数封装了API调用:

$a = Get-Endpoints -Creds $Credentials -URI $FQDN
$a| ForEach-Object {
$b = Get-ProvisioningInfoEndpoint -Creds $Credentials -URI $FQDN -id $_.id |
Select-Object -Property @{Name="NATed IP Address";Expression={$_.ip}}, @{Name="Last Provisioned Time";Expression={$_.ts_created}}
}

更新2

下面是完整的代码,它可以工作,但有点冗长,可能真的效率低下,并导致hashtable的hashtable:

$a = Get-Endpoints -Creds $Credentials -URI $FQDN
$a| ForEach-Object {
$b = Get-ProvisioningInfoEndpoint -Creds $Credentials -URI $FQDN -id $_.id |
Select-Object -Property @{Name="NATed IP Address";Expression={$_.ip}}, @{Name="Last Provisioned Time";Expression={$_.ts_created}}
$EndpointSelectedData = $_ | Select-Object -Property ID, @{Name="Title";Expression={$_.title}}, @{Name="Location";Expression={$_.location}}, @{Name="Internal IP Address";Expression={$_.ip}}
$c = @{}
foreach ($EndpointData in $EndpointSelectedData) {
$c += @{
"ID" = $EndpointData.id
"Title" = $EndpointData.Title
"Location" = $EndpointData.Location
"Internal IP Address" = $EndpointData."Internal IP Address"
}
}
foreach ($ProvisionedD in $ProvisionedData) {
$collection += @{
"NATed IP Address" = $ProvisionedD."NATed IP Address"
"Last Provisioned Time" = $ProvisionedD."Last Provisioned Time"
}
}
$d = $d + @{"Node_$i" = $c}
$i = $i + 1
}

更新3

在@Santiago的建议答案和@iRon的指针之后,我现在有以下代码:

$Endpoints = Get-Endpoints -Creds $Credentials -URI $FQDN
foreach($i in $Endpoints) {
foreach($z in Get-ProvisioningInfoEndpoint -Creds $Credentials -URI $FQDN -Id $i.id) {
[pscustomobject]@{
'ID'                    = $i.Id
'Title'                 = $i.Title
'Location'              = $i.Location
'Internal IP Address'   = $i.Ip
'NATed IP Address'      = $z.Ip
'Last Provisioned Time' = $z.Ts_Created
}
}
}

这将导致屏幕上的转储(我猜这是管道清理):

ID                    : 1
Title                 : Endpoint 1
Location              : UK
Internal IP Address   : 10.0.0.1
NATed IP Address      : 1.2.3.4 
Last Provisioned Time : 2022-02-10T04:09:32.126357+01:00
ID                    : 2
Title                 : Endpoint 2
Location              : US
Internal IP Address   : 10.1.0.1
NATed IP Address      : 1.2.3.5
Last Provisioned Time : 2022-02-10T04:21:32.657364+01:00
ID                    : 3
Title                 : Endpoint 3
Location              : Aus
Internal IP Address   : 10.2.0.1
NATed IP Address      : 1.2.3.6
Last Provisioned Time : 2022-02-10T04:09:31.990202+01:00
...

所以,我想这是接近我想要的,但仍然没有真正在那里。我现在需要弄清楚如何处理管道输出…这与思维方式的改变有很大关系,所以可能需要一些挠头。

更新4 -可能的解决方案

好,扩展上面的代码块,我创建了一些额外的逻辑来将pscustomobject的输出管道到CSV文件。最终目的是根据前一天的配置检查各个端点的状态,以查看是否有任何更改(因为它经常在管理员不知情的情况下更改!)。

指定一个变量来存储嵌套的foreach循环的结果。然后将其用于导出到CSV。我在脚本的开头添加了一些进一步的逻辑,以检查文件是否存在,如果存在,则删除今天的配置文件,以便脚本在每天意外运行多次时创建一个新的配置文件。

主循环完成后,将重新导入今天和昨天的文件,并使用Compare-ObjectCmdLet查看是否有任何更改。如果两个文件之间有差异,我可能会添加一些发送电子邮件的代码。

$DateStamp = get-date -Format FileDate
if (Test-Path -Path ".endpoint-$($DateStamp).csv" -PathType Leaf) {
Remove-Item -Path ".endpoint-$($DateStamp).csv"
}
$Endpoints = Get-Endpoints -Creds $Credentials -ERMMgr $MgrFQDN
$result = foreach($i in $Endpoints) {
foreach($z in Get-ProvisioningInfoEndpoint -Creds $Credentials -URI $FQDN -Id $i.id) {
[pscustomobject]@{
'ID'                    = $i.Id
'Title'                 = $i.Title
'Location'              = $i.Location
'Internal IP Address'   = $i.Ip
'NATed IP Address'      = $z.Ip
'Last Provisioned Time' = $z.Ts_Created
}
}
}
$result | Export-Csv ".endpoint-$DateStamp.csv"
if (Test-Path -Path ".endpoint-$($DateStamp-1).csv" -PathType Leaf) {
$InFile2 = Import-CSV -Path ".endpoint-$($DateStamp-1).csv"
Compare-Object -ReferenceObject $result -DifferenceObject $InFile2 -Property Title, "NATed IP Address" | Export-Csv ".endpoint_diff-$DateStamp.csv"
}

从它的外观来看,这可能会得到你想要的:

$result = foreach($i in Get-Endpoints -Creds $Credentials -URI $FQDN) {
foreach($z in Get-ProvisioningInfoEndpoint -Creds $Credentials -URI $FQDN -Id $id.id) {
[pscustomobject]@{
'ID'                    = $i.Id
'Title'                 = $i.Title
'Location'              = $i.Location
'Internal IP Address'   = $i.Ip
'NATed IP Address'      = $z.Ip
'Last Provisioned Time' = $z.Ts_Created
}
}
}

基本上,对象可以在运行时创建,而不是同时收集$a$b的结果,然后将它们连接/合并。