问题
I'm writing a script to check the version on about 15 remote servers and the script is taking much longer to execute than I would expect.
$listServers = @("compName1", "compName2", "compName3", ... "compName15")
"" | Out-File C:\temp\javaVersion.txt
"" | Out-File C:\temp\javaVersionLog.txt
$cred = Get-Credential
ForEach ($server in $listServers)
{
Measure-Command {$javaVersion = Invoke-Command -ComputerName $server -Credential $cred -Authentication Kerberos -ScriptBlock {Get-WmiObject -Class Win32_Product -Filter "Name like 'Java [0-9]%'" | Select -ExcludeProperty Version}} -ErrorAction SilentlyContinue -ErrorVariable errorOutput
$errorOutput | Out-File C:\temp\javaVersionLog.txt -Append
$server + $javaVersion | Out-File C:\temp\javaVersion.txt -Append
}
This takes about 21 seconds to complete according to the Measure-Command output. Is there a reason I'm missing that the script is taking so long to complete?
Edit:
After being distracted by other issues, I finally finished the script.
Start-Transcript C:\temp\javaVersion.txt
$listServers = @("compName1", "compName2", "compName3", ... "compName15")
$javaVersVerbose = ""
Invoke-Command -ComputerName $listServers -ScriptBlock {
$registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $_);
$javaKey = $registry.OpenSubKey('SOFTWARE\JavaSoft\Java Runtime Environment');
$javaVers = $javaKey.GetValue('CurrentVersion');
$javaVersVerbose = $javaKey.GetValue('Java' + $javaVers.Substring(2, 1) + 'FamilyVersion');
$nameKey = $registry.OpenSubKey('SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName');
$name = $nameKey.GetValue('ComputerName');
$name + " " + $javaVersVerbose | echo
} -ErrorAction SilentlyContinue -ErrorVariable errorOutput
$errorOutput | echo
Write-Host -NoNewLine 'Press any key to continue...'
$null = $Host.UI.RawUI.ReadKey('NoEcho, IncludeKeyDown')
回答1:
You don't need to do this in a loop, nor serially. invoke-command
takes a collection of ComputerName
s, and can run the requests in parallel.
$listServers = @("compName1", "compName2", "compName3", ... "compName15")
Invoke-Command -throttlelimit 4 -ComputerName $listServers -Credential $cred -Authentication Kerberos -ScriptBlock {Get-WmiObject -Class Win32_Product -Filter "Name like 'Java [0-9]%'" | Select -ExcludeProperty Version}} -ErrorAction SilentlyContinue -ErrorVariable errorOutput
However, as was pointed out by Tim Ferrell, you can use Get-WMIObject to ping the servers remotely, and if you do it as a job, it will run multiple requests in parallel.
Get-WMIObject Win32_Product -Filter "Name like 'Java [0-9]%'" -computername $listServers -throttlelimit 4 -asjob |select -excludeproperty version
Then use the Job cmdlets to receive the results.
回答2:
There are a couple ways you could improve performance. PowerShell WorkFlow supports ForEach in parallel, which would hit each computer simultaneously. You could also use Get-WMIObject with -ComputerName to query the list of computers. Get-WMIObject also supports the -AsJob switch, which could also help.
回答3:
Yeah, I think using jobs is the way to go. WMI calls can take a long time, especially if you run into a host or two that aren't responding.
Maybe consider something like this:
$listServers = @((1..15 | % {"compName$_"}))
$jobList = @()
$JavaBlock = {
function checkServer ($serverName) {
$returnValue = Get-WmiObject -computerName $serverName -Class Win32_Product -Credential $cred -Filter "Name like 'Java [0-9]%'" | Select -ExcludeProperty Version
return $returnValue
}
}
foreach ($server in $listServer) {
$job = start-job -InitializationScript $JavaBlock -ScriptBlock { checkServer $args } -argumentList $hostname
$jobList += $job
while (($jobList | where { $_.state -eq "Running" }).count -ge 30) { start-sleep -s 1 }
}
while (($jobList | | where { $_.state -eq "Running" }).count -ge 1) { start-sleep -ms 500 }
The two while statements control the job flow. The one within the foreach statement throttles the jobs so that only 30 are running at once. The last just waits for all jobs to complete before finishing off.
To gather your results, you can use this:
$jobList | % { $jobResults = Receive-Job $_; write-host $jobResults.result }
The Job object has other properties that might be worth exploring as well.
回答4:
Queries against Win32_Product
trigger a reconfiguration of installed packages, which is what makes the process so time-consuming. It also makes such queries potentially harmful, since it may inadvertently reset configuration parameters.
For something like a version check on a particular program you're better off reading the information directly from the (remote) registry:
$listServers | % {
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $_)
$key = $reg.OpenSubKey('SOFTWARE\JavaSoft\Java Runtime Environment')
New-Object -Type PSObject -Property @{
'Server' = $_
'JavaVersion' = $key.GetValue('CurrentVersion')
}
} | Export-Csv 'C:\temp\javaVersion.csv' -NoType
来源:https://stackoverflow.com/questions/24166998/powershell-script-running-slowly