Powershell Script Running Slowly

别来无恙 提交于 2019-12-04 06:10:15

问题


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 ComputerNames, 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!