【问题标题】:Speed up the processing of my PowerShell script加快我的 PowerShell 脚本的处理速度
【发布时间】:2014-01-31 05:59:17
【问题描述】:

如何加快 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

【问题讨论】:

  • 我要做的第一件事是从您要导入的 CSV 文件中创建一个变量,而不是直接将它们导入到 foreach 循环中。
  • 谢谢大家 - 任何关于如何改进我的代码的建议都会有帮助

标签: powershell csv


【解决方案1】:

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

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 倍


更新您的脚本。我认为最简单的方法是使用作业检查操作系统并将结果存储到数组中。然后替换你的 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"

【讨论】:

  • 谢谢。如何将其合并到我的整个代码中?我的意思是 - 更新后的代码在作业中会是什么样子?
  • 感谢您的回复。我仍然想从 hosts.csv 文件中提取计算机名,并从 apps.csv 文件中提取应用程序 - 这似乎不会在您的代码中的任何地方发生。能否请您提供这些详细信息?
  • 我不会复制粘贴,我已经给了你所有的元素。您所要做的就是将我的代码粘贴到您的脚本之上并删除您的 get-os 函数
【解决方案2】:

当您的脚本需要几分钟来处理 40 行时,除了在每次循环迭代中重新读取文件之外,还有更多事情要做。

据我所知,您仅通过调用进行了一次网络调用

gwmi win32_operatingsystem -computername $hostname -ea silentlycontinue

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

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

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

【讨论】:

  • 我认为这是花费时间的地方,如果您可以使用作业并行运行它,您应该会看到一个很好的改进。
  • @DavidMartin:不错,还没有想到。
【解决方案3】:

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


第一节

你有一个带有条件逻辑的函数

我认为这没有什么问题。它简单而标准。

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 循环之前将其导入内存,它可能会更快。

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 {} 中,以查看哪个部分占用了所有/大部分时间。

【讨论】:

    【解决方案4】:

    您可以通过对 Get-WMIObject 进行多线程处理来加速它(但不要尝试通过后台作业来实现)。如果您将多个计算机名称传递给 Get-WMIObject,它将自行执行多线程。这将需要一些重构,但您应该首先收集所有主机名,然后将该集合一次性传递给 Get-WMIObject。将结果排序到主机名/操作系统的哈希表中,然后通过您的 csv 进行 foreach,返回哈希表以获取每个主机的操作系统。

    【讨论】:

    • 嗨,mjolinor,请您提出您的回复。我已经使用作业进行了测试,它比将计算机名数组传递给 gwmi 快 24 倍 ...
    • 您正在使用远程作业,它将负载卸载并分配到目标计算机。这需要在每个远程系统上启用和配置远程处理。后台作业都在本地系统上运行。使用远程作业会加快速度,但在目标计算机上启动远程 PS 会话只是为了获得它的操作系统版本,从资源管理的角度来看似乎有点笨拙,恕我直言。
    • 谢谢你。我明白你的意思了。在这种情况下,他只想要操作系统,但也许下次他会想要获得更多信息……干杯
    【解决方案5】:

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

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

    我自己目前正在做一个项目,我知道桌面出现在第 15000 行之后。然后我告诉循环 $i = 15000; $i -lt $csv.count;$i++ 这在处理小文件时基本上没用,但对于像我这样的大文件,这实际上很有用。

    【讨论】:

      猜你喜欢
      • 2011-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-23
      • 1970-01-01
      • 2015-04-19
      相关资源
      最近更新 更多