简体   繁体   中英

Powershell Script Running Slowly

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')

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.

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.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM