从 PowerShell select-object 结果中删除列名称

Remove column name from PowerShell select-object results

总的来说,我的目标是获取远程计算机列表的 VNC 版本以及卸载 GUID,以便我可以从某些计算机远程卸载 VNC 查看器。我使用了 Get-WmiObject -Class Win32_Product 但这非常慢。

我有以下脚本,但在结果中它包含 select-object 参数的名称。

$computers = Get-Content -Path "C:\Computers.txt"

$Results = @()

ForEach ($Computer in $Computers) {
    $Results += New-Object PSObject -Property @{
        "ComputerName" = $Computer

        "Name" = Invoke-Command -ComputerName $Computer -ScriptBlock { Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* } `
        | Where-Object -FilterScript {$_.DisplayName -like "VNC V*"} | select-object DisplayName

        "DisplayVersion" = Invoke-Command -ComputerName $Computer -ScriptBlock { Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* } `
        | Where-Object -FilterScript {$_.DisplayName -like "VNC V*"} | select-object DisplayVersion

        "ModifyPath" = Invoke-Command -ComputerName $Computer -ScriptBlock { Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* } `
        | Where-Object -FilterScript {$_.DisplayName -like "VNC V*"} | select-object ModifyPath

        "Vendor" = Invoke-Command -ComputerName $Computer -ScriptBlock { Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* } `
        | Where-Object -FilterScript {$_.DisplayName -like "VNC V*"} | select-object Publisher

    }
}

$Results | Select-Object ComputerName,Name,DisplayVersion,ModifyPath,Vendor | Sort-Object ComputerName  | Export-Csv  C:\VNC.csv -notype ;

我的结果是这样的:

ComputerName : ComputerName
姓名 :@{DisplayName=VNC Viewer 5.2.3}
显示版本 :@{DisplayVersion=5.2.3}
修改路径 :@{ModifyPath=MsiExec.exe /I{18B1E36F-0DA3-4FDA-BC57-DD815B0DF3B2}}
供应商 :@{Publisher=RealVNC Ltd}

我希望它看起来像这样:

ComputerName : ComputerName
名称 :VNC 查看器 5.2.3
显示版本 :5.2.3
修改路径 :MsiExec.exe /I{18B1E36F-0DA3-4FDA-BC57-DD815B0DF3B2}
供应商 :RealVNC Ltd

这可能吗,还是我对这个脚本的处理完全错误?我还没有想出一种方法来 运行 这个 Invoke-Command 的多个参数,并且仍然以任何其他方式在单独的列中输出结果。

此脚本有效,但需要 100 台计算机永远使用:

if (Test-Path C:\VNCInstalled.csv) {Remove-Item C:\VNCInstalled.csv}
if (Test-Path C:\Computers.txt) {Remove-Item C:\Computers.txt}
$DirSearcher = New-Object System.DirectoryServices.DirectorySearcher([adsi]'')
$DirSearcher.Filter = '(&(objectClass=Computer)(!(cn=*esx*)) (!(cn=*slng*)) (!(cn=*dcen*)) )'
$DirSearcher.FindAll().GetEnumerator() | sort-object { $_.Properties.name } `
| ForEach-Object { $_.Properties.name }`
| Out-File -FilePath C:\Computers.txt

Get-Content -Path c:\Computers.txt `
| ForEach-Object {Get-WmiObject -Class Win32_Product -ComputerName $_} `
| Where-Object -FilterScript {$_.Name -like "VNC V*"} `
| select-object @{Name="ComputerName";Expression={$_.PSComputerName}},
                Name,
                @{Name="InstallLocation";Expression={$_.PackageCache}},
                Vendor,
                Version,
                @{Name="GUID";Expression={$_.IdentifyingNumber}} `
| Sort-Object ComputerName `
| Export-CSV -path c:\VNCInstalled.csv -notype

Change all of your Select-Object commands to Select-Object -ExpandProperty PropertyName, to discard the property name / column header.

这是我三年前给出的答案,我认为这是一个非常糟糕的答案。让我现在做得更好。

为什么您当前的代码很慢

您当前的代码从 AD 中枚举所有机器并将它们放在一个名为 Computers.txt 的文件中。简单,你做的很好。

接下来,您的代码将执行此操作:

Get-Content -Path c:\Computers.txt | 
ForEach-Object {Get-WmiObject -Class Win32_Product -ComputerName $_} `
| Where-Object -FilterScript {$_.Name -like "VNC V*"} [...]

这可以概括为'For each computer, request the full Win32_product table, and then after that, filter down to apps named VNC.'这非常性能影响,原因有几个。

  1. 即使在快速的现代计算机上,查询 Win32_Product 也需要 30 秒或更长时间,因为 returns 每个 应用程序都已安装. (在我的新虚拟机上,只安装了几个应用程序就花了一分多钟!)

  2. 查询 Win32_Product 也有这个有趣的怪癖,这使得它需要更长的时间,引用自 Win32_Product Class

    [ 上的 MSDN 文档=63=]

Warning Win32_Product is not query optimized. Queries such as "select * from Win32_Product where (name like 'Sniffer%')" require WMI to use the MSI provider to enumerate all of the installed products and then parse the full list sequentially to handle the “where” clause. This process also initiates a consistency check of packages installed, verifying and repairing the install. With an account with only user privileges, as the user account may not have access to quite a few locations, may cause delay in application launch and an event 11708 stating an installation failure. For more information, see KB Article 794524.

总而言之,查询 Win32_Product 很慢,并且它还会触发对每个应用程序的一致性检查,并且我们还编写了此查询以在过滤之前检索每个应用程序。这些加起来是一个过程,每台电脑可能需要大约 3 分钟,并且将连续运行(一个接一个)并持续很长时间。

如何修复

可以在两个地方可靠地检索软件信息:

  • 如果你的设备上安装了SCCM/ConfigMgr,它会添加你可以查询的Win32_AddRemoveProgram WMI Class,这是Win32_Product的超快速版本
  • 如果没有,我们可以随时从注册表中检索信息。

这里有一个简短的片段,可以在计算机上安装 VLC 等应用程序(我没有像你一样的 VNC,所以我正在到期)

Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-object DisplayName -like "VLC*" |Select-Object DisplayName, DisplayVersion, Publisher, InstallDate,UninstallString


DisplayName     : VLC media player
DisplayVersion  : 3.0.8
Publisher       : VideoLAN
InstallDate     :
UninstallString : "C:\Program Files (x86)\VideoLAN\VLC\uninstall.exe"

这个操作快多了,只有400毫秒左右。遗憾的是,我们无法通过注册表获得更快的速度,因为它有一个非常奇怪的 PowerShell 提供程序,它没有实现 -Filter 参数,所以我们必须检索所有程序,然后过滤到我们想要的选择。

正在更新您的脚本以改用此函数

我冒昧地重写了您的脚本以使用这种方法,并对其进行了一些重组以提高可读性。

$results = New-object System.Collections.ArrayList
$computers = Get-Content -Path c:\Computers.txt 
foreach ($computer in $computers){
    #get VNC entries from remote computers registry
    $VNCKeys = Invoke-Command -ComputerName $computer -ScriptBlock {
        Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | 
            Where-object DisplayName -like "VNC V*" | 
                Select-Object DisplayName, DisplayVersion, Publisher, UninstallString, @{Name=‘ComputerName‘;Expression={$computer}}
        }#end of remote command

    if ($VNCKeys -ne $null){
        forEach($VNCKey in $VNCKeys){
            [void]$results.Add($VNCKey)
        } 
    }
}

$results | Sort-Object ComputerName | Export-CSV -path c:\VNCInstalled.csv -NoTypeInformation