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

问题描述 投票:2回答:5

我正在开发一个脚本,用于在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
performance powershell foreach powershell-v2.0 powershell-v3.0
5个回答
0
投票

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

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

$d = Get-Item -LiteralPath $path

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


0
投票

用户函数调用开销(包括的脚本块)非常大(例如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

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
}

0
投票

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


0
投票

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

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

请享用!

# [email protected]
# 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."
© www.soinside.com 2019 - 2024. All rights reserved.