我有以下Powershell脚本:
Function Get-InstalledPrograms {
[CmdletBinding()]
param (
[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][string]$computername=$env:computername,
[parameter(Mandatory=$false)] [string]$ExportFile
)
Begin {
$results = @()
function ItemizeApps ($hive, $computer, $uninstallkey) {
write-verbose "Opening registry uninstall root key $uninstallkey"
$regkey=$hive.OpenSubKey($uninstallkey)
if ($regkey) {
write-verbose "Getting uninstall subkeys"
$subkeys=$regkey.GetSubKeyNames()
write-verbose "Itemizing $($subkeys.count) subkeys"
foreach($key in $subkeys){
$path=$uninstallkey+"\"+$key
$thisSubKey=$reg.OpenSubKey($path)
write-verbose "Processing uninstall key $path"
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $computer
$obj | Add-Member -MemberType NoteProperty -Name "UninstallKey" -Value $path
$obj | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value $($thisSubKey.GetValue("DisplayName"))
$obj | Add-Member -MemberType NoteProperty -Name "DisplayVersion" -Value $($thisSubKey.GetValue("DisplayVersion"))
$obj | Add-Member -MemberType NoteProperty -Name "InstallLocation" -Value $($thisSubKey.GetValue("InstallLocation"))
$obj | Add-Member -MemberType NoteProperty -Name "Publisher" -Value $($thisSubKey.GetValue("Publisher"))
$results += $obj
}
} else {
write-verbose "No subkeys"
}
}
}
Process {
# Process HKEY_LOCAL_MACHINE uninstall keys
$reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$computername)
if ($reg) {
# Process x86 uninstalls
ItemizeApps $reg $computername "\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
# Process x86 uninstalls
ItemizeApps $reg $computername "\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
}
# Process HKEY_USERS uninstall keys
$reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey('Users',$computername)
if ($reg) {
$subkeys = $reg.GetSubKeyNames()
foreach ($key in $subkeys) {
ItemizeApps $reg $computername "$key\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
}
}
}
End {
write-verbose 'Complete'
if ($ExportFile) {
$results | Where-Object { $_.DisplayName } | select ComputerName, UninstallKey, DisplayName, DisplayVersion, InstallLocation, Publisher | Export-Csv $ExportFile -append
} else {
$results
}
}
}
我在$ result + = $ obj中遇到了一些奇怪的问题。有时它会添加对象(第一次?),然后有时会出现错误:
Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'. At D:\Scripts\Get-InstalledPrograms.ps1:26 char:11
+ $global:results += $obj
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (op_Addition:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
无论哪种方式,当我退出函数时,$ results为空。我猜想它与$ results的范围有关,但是我不知道如何解决。我尝试了$ global:results和$ script:results,但都没有用。我想念什么?
答案 0 :(得分:1)
我不会使用+=
向数组添加对象,因为这样做效率极低,因为每次添加时都需要重新创建整个数组。
代码设置的方式,并且无需对其进行太多更改,我建议您创建一个[System.Collections.Generic.List[object]]
来存储不同的结果。
您没有标记这个问题PowerShell 2.0
,所以我认为您使用的是3.0版或更高版本。从PS 3.0开始,您可以使用[PsCustomObject]@{}
来轻松创建PSObject。
我还注意到您打开了注册表项,但是从不关闭它们。
对于下面的修订代码,我特意为结果列表使用了不同的名称,因为您可能仍将$results
作为内存中的全局变量。
尝试
function Get-InstalledPrograms {
[CmdletBinding()]
param (
[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string]$computername=$env:computername,
[parameter(Mandatory=$false)]
[string]$ExportFile
)
Begin {
# create a List object to store PsObjects
$softwareList = [System.Collections.Generic.List[object]]::new()
function ItemizeApps ($hive, $computer, $uninstallkey) {
Write-Verbose "Opening registry uninstall root key $uninstallkey"
$regkey = $hive.OpenSubKey($uninstallkey)
if ($regkey) {
Write-Verbose "Getting uninstall subkeys"
$subkeys = $regkey.GetSubKeyNames()
Write-Verbose "Itemizing $($subkeys.count) subkeys"
foreach($key in $subkeys){
$path = Join-Path -Path $uninstallkey -ChildPath $key
$thisSubKey = $reg.OpenSubKey($path)
Write-Verbose "Processing uninstall key $path"
# Power shell v 3.0 has the easy [PsCustomObject] type accellerator
$softwareList.Add([PsCustomObject]@{
ComputerName = $computer
UninstallKey = $path
DisplayName = $thisSubKey.GetValue("DisplayName")
DisplayVersion = $thisSubKey.GetValue("DisplayVersion")
InstallLocation = $thisSubKey.GetValue("InstallLocation")
Publisher = $thisSubKey.GetValue("Publisher")
UninstallString = $thisSubKey.GetValue("UninstallString")
})
# close the opened key
$thisSubKey.Close()
}
# close the opened key
$regkey.Close()
} else {
Write-Verbose "No subkeys"
}
}
}
Process {
# Process HKEY_LOCAL_MACHINE uninstall keys
$reg = [microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$computername)
if ($reg) {
# Process x86 uninstalls
ItemizeApps $reg $computername "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
# Process x86 uninstalls
ItemizeApps $reg $computername "SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
# close the opened key
$reg.Close()
}
# Process HKEY_USERS uninstall keys
$reg = [microsoft.win32.registrykey]::OpenRemoteBaseKey('Users',$computername)
if ($reg) {
$subkeys = $reg.GetSubKeyNames()
foreach ($key in $subkeys) {
ItemizeApps $reg $computername "$key\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
}
# close the opened key
$reg.Close()
}
}
End {
Write-Verbose 'Complete'
if ($ExportFile) {
# since you want to export all the properties, there is no need to insert a Select-Object here
$softwareList | Where-Object { $_.DisplayName } | Export-Csv $ExportFile -Append -NoTypeInformation
} else {
$softwareList
}
}
}
Get-InstalledPrograms -Verbose
答案 1 :(得分:1)
Theo's helpful answer提供了很好的建议并提供了可靠的解决方案。我想在代码中添加引起错误和意外结果的原因的解释。
调用函数时,它将创建自己的作用域。如果没有针对特定的变量范围或在特定范围内显式调用函数,则将无法访问其变量。如果要通过函数创建数据,则最佳实践是将数据返回到调用范围,然后将其存储。
如果您使用.
调用函数,它将在当前范围内运行。函数未返回时,未明确限定范围的变量修改将反映在调用范围中。
使用global:
范围修饰符时,全局变量将在调用范围和被调用范围中可用。
使用+=
向数组添加元素效率不高。这是因为您实际上并没有在数组中添加新元素。您正在输出当前数组和新元素值,并将这些结果存储到新数组中。随着阵列的增大,该过程将占用更多的系统资源。
关于+=
的问题,如果$results
既不是数组,又是无法使用+
转换为数组的类型,则会收到该错误操作或不支持附加+
的标量。如果$results
是类型PSCustomObject
,则不能向其添加另一个PSCustomObject
来构成一个数组。
# Example of storing function output in calling scope
$results = Get-InstalledPrograms
# Example Using . to call a function. $results will be updated in the calling scope
. Get-InstalledPrograms
$data | . Get-InstalledPrograms
# Example using global scope modifier
Function Get-InstalledPrograms {
$global:results = 'I am global'
}
Get-InstalledPrograms
$global:results
I am global
# Example of problematic +=
$results = $null
$results += [pscustomobject]@{a=1}
$results += [pscustomobject]@{a=2} # This will error because $results is not yet an array
# Example of error free +=
$results = @()
$results += [pscustomobject]@{a=1}
$results += [pscustomobject]@{a=2} # Works because $results was already an array
# Example of mixing scopes and problematic +=
$results = 25 # This is global since it is in top level scope
Function Foo {
$global:results += [pscustomobject]@{a=2}
}
Foo # Error because $results already existed as a non-array and incompatible type for the + operation in global scope