加快我的PowerShell脚本的处理速度

时间:2014-01-31 05:59:18

标签: powershell csv

如何加快PowerShell脚本的处理速度?

它从apps.csv文件(包含2个应用程序 - Windows XP和Windows 7版本)中提取信息,并从hosts.csv文件中提取信息。

应用程序位置通过不同的主机名进行测试(我可以判断它们是否是Windows XP / Windows 7计算机),通过检查指纹来查看它们是否已安装。

目前运行40多条记录大约需要5-10分钟,真的需要加快速度。

clear all

$Applications = @{}
$ComputerObjects = @()

# Gets OS
function Get-OS ([string]$hostname) {
    $os = "offline"

    gwmi win32_operatingsystem -computername $hostname -ea silentlycontinue | ForEach-Object {
        if($_.Version.ToString().StartsWith("6.1")) {
            $os = "Windows 7"
        } elseif ($_.Version.ToString().StartsWith("5.1")){
            $os = "Windows XP"
        } else {
            $os = "N/A"
        }
    } > $null

    return $os
}

# Sorts out the application information, breaking it into OS, LOB and then finally Location
Import-CSV C:\RDC\apps.csv | ForEach-Object {
    $appname = $_.appname.ToLower()
    $os = $_.os.ToLower()
    $lob = $_.lob.ToLower()
    $location = $_.location.ToLower()

    if ($Applications.Keys -notcontains $appname) {
        $WindowsOS=@{}

        # hashtable for windows xp and windows 7 applications
        $WindowsOS["windows xp"]=@{}
        $WindowsOS["windows 7"]=@{}
        $Applications[$appname]=$WindowsOS
    } 

    if ($Applications[$appname][$os].Keys -notcontains $lob) {
        $Applications[$appname][$os][$lob]=@()
    }

    if ($Applications[$appname][$os][$lob].Keys -notcontains $location) {
        $Applications[$appname][$os][$lob]+=$location
    }
}

# Sorts the Hostnames out and tests all Application Locations
Import-CSV C:\RDC\hosts.csv | ForEach-Object {
    $Properties = @{}
    $Properties["hostname"]=$_.hostname.ToLower()
    $Properties["lob"]=$_.type.ToLower()
    $Properties["os"]=Get-OS $Properties["hostname"]

    $Applications.Keys | ForEach-Object {
        $currAppName = $_
        $Properties[$currAppName]=$false;

        if ($Applications[$currAppName].Keys -contains $Properties["os"] -and $Applications[$currAppName][$Properties["os"]].Keys -contains $Properties["lob"]) {
            $Applications[$currAppName][$Properties["os"]][$Properties["lob"]] | ForEach-Object {
                $Properties[$currAppName]=$Properties[$currAppName] -or (Test-Path "\\$($Properties["hostname"])\$($_)")
            }
        }
    }

    $HostObject = New-Object PSObject -Property $Properties
    $ComputerObjects += $HostObject
}

$ComputerObjects | ft

$a = [int][double]::Parse((Get-Date -UFormat %s))

#$ComputerObjects | Export-csv "C:\Working\results_$a.csv" -NoTypeInformation

5 个答案:

答案 0 :(得分:3)

当你的脚本需要几分钟来处理40行时,每次循环迭代都会重复读取文件。

从我可以收集到的内容中,您只需通过调用

进行一次网络通话
gwmi win32_operatingsystem -computername $hostname -ea silentlycontinue

我怀疑这个命令需要几秒钟才能完成,并使你的脚本变慢。您可以通过查看仅为所有已知主机名调用此命令所需的时间来测试这一点,但我认为它与整个脚本的运行时间几乎相同。

我不知道更快的方法来确定操作系统版本,但我可以想象这些信息不会每天都在变化,所以也许你可以缓存这些信息并让它每天更新一次?

- 编辑:正如大卫·马丁所指出的,更好的选择可能是尝试使这些调用并行。显然,Powershell通过ForEach -Parallel命令支持这一点。

答案 1 :(得分:3)

这是一个使用作业的例子:

param( $computername=$fullnameRODC)
Invoke-Command -ComputerName $computername -AsJob -JobName checkOS -ScriptBlock {
    $o=@{}
    $os = "offline"

    gwmi win32_operatingsystem  -ea silentlycontinue | ForEach-Object {
        if($_.Version.ToString().StartsWith("6.1")) {
            $os = "Windows 7"
        } elseif ($_.Version.ToString().StartsWith("5.1")){
            $os = "Windows XP"
        } else {
            $os = "N/A"
        }
    }
    $o["os"]=$os
    $r=new-Object -TypeName psObject -Property $o
    $r
    }

wait-Job checkOS
receive-Job checkOS
remove-job checkOS

使用Measure-Command -Expression {c:\temp\cheskOS.ps1}此脚本将在大约10秒内执行。

没有工作

param( $computername=$fullnameRODC)


    gwmi win32_operatingsystem -computername $computername  -ea silentlycontinue | ForEach-Object {
        if($_.Version.ToString().StartsWith("6.1")) {
            $os = "Windows 7"
        } elseif ($_.Version.ToString().StartsWith("5.1")){
            $os = "Windows XP"
        } else {
            $os = "N/A"
        }
    }
    $os

需要4分钟,快24倍


更新您的脚本。我认为最简单的方法是使用job来检查os并将结果存储到数组中。然后替换你的get-os函数。那就是:

param( $computername=@("computer1","computer2"))
$results=@()
Invoke-Command -ComputerName $computername -AsJob -JobName checkOS -ScriptBlock {
    $o=@{}
    $os = "offline"
    gwmi win32_operatingsystem  -ea silentlycontinue | ForEach-Object {
        if($_.Version.ToString().StartsWith("6.1")) {
            $os = "Windows 7"
        } elseif ($_.Version.ToString().StartsWith("5.1")){
            $os = "Windows XP"
        } else {
            $os = "N/A"
        }
    }
    $o["os"]=$os
    $r=new-Object -TypeName psObject -Property $o
    $r
    }

wait-Job checkOS
$results+=receive-Job checkOS
remove-job checkOS

function Get-OS ([string]$computername) {
$script:results | ?{$_.pscomputername -eq $computername} | select os
}

然后你可以做

get-os "computer2"

答案 2 :(得分:1)

我查看了您的代码,这是我推断的内容:


第一节

你有一个带条件逻辑的功能

我认为这没有错。它简单而标准。

function Get-OS ([string]$hostname) {
    $os = "offline"

    gwmi win32_operatingsystem -computername $hostname -ea silentlycontinue | ForEach-Object {
        if($_.Version.ToString().StartsWith("6.1")) {
            $os = "Windows 7"
        } elseif ($_.Version.ToString().StartsWith("5.1")){
            $os = "Windows XP"
        } else {
            $os = "N/A"
        }
    } > $null

    return $os
}

第二节

你有两个foreach循环从磁盘读取一个CSV文件,并在循环遍历该数组中的每个对象时将CSV文件转换为object []数组。

第一个foreach循环包含条件逻辑。我认为这很好但是如果你在将CSV传递给管道/ foreach循环之前将CSV导入内存可能会更快。

Import-CSV C:\RDC\apps.csv | ForEach-Object {
    $appname = $_.appname.ToLower()
    $os = $_.os.ToLower()
    $lob = $_.lob.ToLower()
    $location = $_.location.ToLower()

    if ($Applications.Keys -notcontains $appname) {
        $WindowsOS=@{}

        # hashtable for windows xp and windows 7 applications
        $WindowsOS["windows xp"]=@{}
        $WindowsOS["windows 7"]=@{}
        $Applications[$appname]=$WindowsOS
    } 

    if ($Applications[$appname][$os].Keys -notcontains $lob) {
        $Applications[$appname][$os][$lob]=@()
    }

    if ($Applications[$appname][$os][$lob].Keys -notcontains $location) {
        $Applications[$appname][$os][$lob]+=$location
    }
}

第三节

第二个foreach循环包含:

  • 另一个foreach循环
    • 包含条件逻辑
      • 包含另一个foreach循环

再次,您应该在将CSV传递给循环之前将其读入内存(变量)。

我认为你需要做的第二件事是通过编写不同的代码来执行相同的工作来减少嵌套,或者至少将内部的foreach循环与if语句分开。

Import-CSV C:\RDC\hosts.csv | ForEach-Object {
    $Properties = @{}
    $Properties["hostname"]=$_.hostname.ToLower()
    $Properties["lob"]=$_.type.ToLower()
    $Properties["os"]=Get-OS $Properties["hostname"]

    $Applications.Keys | ForEach-Object {
        $currAppName = $_
        $Properties[$currAppName]=$false;

        if ($Applications[$currAppName].Keys -contains $Properties["os"] -and $Applications[$currAppName][$Properties["os"]].Keys -contains $Properties["lob"]) {
            $Applications[$currAppName][$Properties["os"]][$Properties["lob"]] | ForEach-Object {
                $Properties[$currAppName]=$Properties[$currAppName] -or (Test-Path "\\$($Properties["hostname"])\$($_)")
            }
        }
    }

    $HostObject = New-Object PSObject -Property $Properties
    $ComputerObjects += $HostObject
}

最终评论

这些是您的脚本的主要部分,将花费最多的时间。

您还可以尝试在Measure-Command {}中包装每个部分,以查看哪个部分占用了所有/大部分时间。

答案 3 :(得分:1)

您可以通过多线程化Get-WMIObject来加快速度(但不要尝试使用后台作业)。如果您传递多个计算机名称,Get-WMIObject将自行多线程。这将需要一些重新分解,但您应该首先收集所有主机名,然后一次将该集合传递给Get-WMIObject。将结果排序为hostname / os的哈希表,然后通过csv进行预处理,返回哈希表以获取每个主机的操作系统。

答案 4 :(得分:0)

您还可以通过不使用foreach来加速脚本。这将在运行循环之前调用整个对象。使用For($ i = 0; $ i -lt $ csv.count; $ i){Commands}如果你只是寻找某些数据,你应该在一个函数中并在循环内返回值而不是等待它完成。

如果您正在使用已排序的csv文件。您可以通过检查输入并将$ i设置为某一行来跳转到您的csv的某个值。

我目前正在处理一个项目,我知道桌面显示在15000行之后。然后我告诉循环$ i = 15000; $ i -lt $ csv.count; $ i ++在使用小文件时这基本没用,但是在我的情况下使用更大的文件这是非常有用的。