为注册表搜索加速powershell脚本(目前为30分钟)

时间:2016-08-30 07:48:08

标签: performance powershell foreach powershell-v2.0 powershell-v3.0

我正在开发一个脚本,用于Windows 7和Windows 10中的HKLM中的注册表搜索:\ Software \ Classes。 到目前为止我的代码工作,但它非常慢。大约需要30分钟才能完成。

更新1: 已将$d= Get-Item .更改为$d= Get-Item -literalpath $path。 需要使用Set-Location也可以避免Get-ItemProperty出错,因为$ path不是有效对象而发生

如何加快此代码的速度?怎么了?

请帮助加快......

#regsearch.ps1
Function Get-RegItems
{
 Param(
  [Parameter(Mandatory=$true)]
  [string]$path,
  [string]$match)

  #Set Local Path and ignore wildcard (literalpath)
   Set-Location -literalpath $path 
   $d= Get-Item -literalpath $path

   # If more then one value -> process 
   If ($d.Valuecount -gt 0) {    
    $d | 
    # Get unkown property
    Select-Object -ExpandProperty Property |
    ForEach {
      $val = (Get-ItemProperty -Path . -Name $_).$_
      #if Filter $match found, generate ReturnObject
      if (($_ -match $match) -or ($val -match $match ) -or ($path-match $match)) { 
        New-Object psobject -Property @{ “key”=$path; “property”=$_; “value” = $val ;}
      }}
  }
} #end function Get-RegItems

Function RegSearch
{
 Param(
  [Parameter(Mandatory=$true)]
  [string]$path,
  [string]$match)

  # Expand $path if necessary to get a valid object
  if ( $path.Indexof("HKEY") -ne "-1" -and $path.Indexof("Registry::") -eq "-1" )  { $path = "Microsoft.PowerShell.Core\Registry::" +$path }

  # Retrieve Items of Main Key
  Get-RegItems -path $path -match $match

  # Retrieve Items of all Childkeys
  Get-ChildItem $path -Recurse -ErrorAction SilentlyContinue |
  ForEach { Get-RegItems -path $_.PsPath -match $match }
} #end function RegSearch


#$search ="HKCU:\SOFTWARE\Microsoft\Office" 
$searchkey =‘HKLM:\SOFTWARE\Microsoft\Office\’ 
#$searchkey = "HKLM:\Software\Classes\"
$pattern = "EventSystem"

cls
$result = @()

measure-command {$result = Regsearch -path $searchkey -match $pattern }

# TESTING
#$t = @( "Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\Software\Classes",
#       "HKLM:\Software\Classes\Wow6432Node\CLSID\",
#       "HKCU:\SOFTWARE\Microsoft\Office\",
#       "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office")
#cls       
#$t |ForEach { Get-RegItems -path $_ } | fl      

if ($result.Count) {
  $result 
  "Count: {0}" -f ($result.Count-1) }
else { "Path: {0} `nNo Items found" -f $searchkey}  

#regsearch.ps1

5 个答案:

答案 0 :(得分:0)

您可以在这里做出的最大改进是改变:

Set-Location -literalpath $path 
$d= Get-Item .

$d = Get-Item -LiteralPath $path

操作层次结构中每个键的位置堆栈会引入 A LOT 不必要的开销

答案 1 :(得分:0)

用户函数调用开销(包括的scriptblocks)非常大(例如0.1-1ms)。当函数执行数千次/数百万次时,这成为一个非常严重的问题。令人惊讶的是,在优化相关的文章中没有提到它(至少我从未见过它,而且我搜索了很多这个主题)。

不幸的是,这个特定问题唯一真正的解决方案是以复制和降低可读性为代价来内联代码。

优化应包括代码分析。
PowerShell没有代码分析器,因此您需要使用Measure-Command手动执行此操作 在循环内使用System.Diagnostics.Stopwatch来显示累计时间:

# global stopwatch
$sw1 = [Diagnostics.Stopwatch]::new()
$sw2 = [Diagnostics.Stopwatch]::new()
............
forEach(....) {
    ........
    $sw1.start()
    ........
    $sw1.stop()
    ........
    $sw2.start()
    ........
    $sw2.stop()
    ........
}
............
echo $sw1.ElapsedMilliseconds, $sw2.ElapsedMilliseconds

答案 2 :(得分:0)

这是您的示例脚本的更快版本。 持续约在我的机器上等待1分钟。 如果需要更快,则需要使用advapi32.dll-Pinvokes,但是 它将结束相当复杂。

Function Get-RegItems {
    Param(
        [Parameter(Mandatory=$true)]
        [string]$path,
        [string]$match
    )
    #write-host $path.Substring(30)
    $key = Get-Item -literalpath $path
    ForEach ($entry in $key.Property) {
        $value = $key.GetValue($entry)
        if (($entry -match $match) -or ($value -match $match ) -or ($path -match $match)) { 
            write-host "key=$path property=$entry value=$value"
        }
    }
}

Function RegSearch {
    Param(
        [Parameter(Mandatory=$true)]
        [string]$path,
        [string]$match
    )
    Get-RegItems -path $path -match $match
    ForEach ($item in get-ChildItem -literalpath $path -ea 0) {
        RegSearch -path $item.PsPath -match $match 
    }
}

cls
Remove-Variable * -ea 0
[System.GC]::Collect()

$searchkey =‘HKLM:\SOFTWARE\Microsoft\Office’ 
$pattern = "EventSystem"
measure-command {
    $result = RegSearch -path $searchkey -match $pattern
}

答案 3 :(得分:0)

如果希望更快,请不要使用注册表驱动器提供程序。

答案 4 :(得分:0)

我接受了挑战,并使其“尽可能快”。 现在,它甚至比REGEDIT或任何其他工具快。 下面的示例持续11秒,以解析完整的OFFICE键和所有子键。

此外,它还会在REG-BINARY等中搜索字符串匹配项。

享受!

# carsten.giese@googlemail.com
# reference: https://msdn.microsoft.com/de-de/vstudio/ms724875(v=vs.80)

cls
remove-variable * -ea 0
$ErrorActionPreference = "stop"

$signature = @'
[DllImport("advapi32.dll")]
public static extern Int32 RegOpenKeyEx(
    UInt32 hkey,
    StringBuilder lpSubKey,
    int ulOptions,
    int samDesired,
    out IntPtr phkResult
    );

[DllImport("advapi32.dll")]
public static extern Int32 RegQueryInfoKey(
    IntPtr hKey,
    StringBuilder lpClass, Int32 lpCls, Int32 spare, 
    out int subkeys, out int skLen, int mcLen, out int values,
    out int vNLen, out int mvLen, int secDesc,                
    out System.Runtime.InteropServices.ComTypes.FILETIME lpftLastWriteTime
);

[DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
public static extern Int32 RegEnumValue(
  IntPtr hKey,
  int dwIndex,
  IntPtr lpValueName,
  ref IntPtr lpcchValueName,
  IntPtr lpReserved,
  out IntPtr lpType,
  IntPtr lpData,
  ref int lpcbData
);

[DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
public static extern Int32 RegEnumKeyEx(
  IntPtr hKey,
  int dwIndex,
  IntPtr lpName,
  ref int lpcName,
  IntPtr lpReserved,
  IntPtr lpClass,
  int lpcClass,
  out System.Runtime.InteropServices.ComTypes.FILETIME lpftLastWriteTime
);

[DllImport("advapi32.dll")]
public static extern Int32 RegCloseKey(IntPtr hkey);
'@ 
$reg = add-type $signature -Name reg -Using System.Text -PassThru
$marshal = [System.Runtime.InteropServices.Marshal]

function search-RegistryTree($path) {

    # open the key:
    [IntPtr]$hkey = 0
    $result = $reg::RegOpenKeyEx($global:hive, $path, 0, 25,[ref]$hkey)
    if ($result -eq 0) {

        # get details of the key:
        $subKeyCount  = 0
        $maxSubKeyLen = 0
        $valueCount   = 0
        $maxNameLen   = 0
        $maxValueLen  = 0
        $time = $global:time
        $result = $reg::RegQueryInfoKey($hkey,$null,0,0,[ref]$subKeyCount,[ref]$maxSubKeyLen,0,[ref]$valueCount,[ref]$maxNameLen,[ref]$maxValueLen,0,[ref]$time)
        if ($result -eq 0) {
           $maxSubkeyLen += $maxSubkeyLen+1
           $maxNameLen   += $maxNameLen  +1
           $maxValueLen  += $maxValueLen +1
        }

        # enumerate the values:
        if ($valueCount -gt 0) {
            $type = [IntPtr]0
            $pName  = $marshal::AllocHGlobal($maxNameLen)
            $pValue = $marshal::AllocHGlobal($maxValueLen)
            foreach ($index in 0..($valueCount-1)) {
                $nameLen  = $maxNameLen
                $valueLen = $maxValueLen
                $result = $reg::RegEnumValue($hkey, $index, $pName, [ref]$nameLen, 0, [ref]$type, $pValue, [ref]$valueLen)
                if ($result -eq 0) {
                    $name = $marshal::PtrToStringUni($pName)
                    $value = switch ($type) {
                        1 {$marshal::PtrToStringUni($pValue)}
                        2 {$marshal::PtrToStringUni($pValue)}
                        3 {$b = [byte[]]::new($valueLen)
                           $marshal::Copy($pValue,$b,0,$valueLen)
                           if ($b[1] -eq 0 -and $b[-1] -eq 0 -and $b[0] -ne 0) {
                                [System.Text.Encoding]::Unicode.GetString($b)
                           } else {
                                [System.Text.Encoding]::UTF8.GetString($b)}
                           }
                        4 {$marshal::ReadInt32($pValue)}
                        7 {$b = [byte[]]::new($valueLen)
                           $marshal::Copy($pValue,$b,0,$valueLen)
                           $msz = [System.Text.Encoding]::Unicode.GetString($b)
                           $msz.TrimEnd(0).split(0)}
                       11 {$marshal::ReadInt64($pValue)}
                    }
                    if ($name -match $global:search) {
                        write-host "$path\$name : $value"
                        $global:hits++
                    } elseif ($value -match $global:search) {
                        write-host "$path\$name : $value"
                        $global:hits++
                    }
                }
            }
            $marshal::FreeHGlobal($pName)
            $marshal::FreeHGlobal($pValue)
        }

        # enumerate the subkeys:
        if ($subkeyCount -gt 0) {
            $subKeyList = @()
            $pName = $marshal::AllocHGlobal($maxSubkeyLen)
            $subkeyList = foreach ($index in 0..($subkeyCount-1)) {
                $nameLen = $maxSubkeyLen
                $result = $reg::RegEnumKeyEx($hkey, $index, $pName, [ref]$nameLen,0,0,0, [ref]$time)
                if ($result -eq 0) {
                    $marshal::PtrToStringUni($pName)
                }
            }
            $marshal::FreeHGlobal($pName)
        }

        # close:
        $result = $reg::RegCloseKey($hkey)

        # get Tree-Size from each subkey:
        $subKeyValueCount = 0
        if ($subkeyCount -gt 0) {
            foreach ($subkey in $subkeyList) {
                $subKeyValueCount += search-RegistryTree "$path\$subkey"
            }
        }
        return ($valueCount+$subKeyValueCount)
    }
}

$timer = [System.Diagnostics.Stopwatch]::new()
$timer.Start()

# setting global variables:
$search = "enterprise"
$hive   = [uint32]"0x80000002" #HKLM
$subkey = "SOFTWARE\Microsoft\Office"
$time   = New-Object System.Runtime.InteropServices.ComTypes.FILETIME
$hits   = 0

write-host "We start searching for pattern '$search' in Registry-Path '$subkey' ...`n"
$count = search-RegistryTree $subkey

$timer.stop()
$sec = [int](100 * $timer.Elapsed.TotalSeconds)/100
write-host "`nWe checked $count reg-values in $sec seconds. Number of hits = $hits."