加快 Windows 注册表搜索的 PowerShell 脚本(当前为 30 分钟)
Speed up PowerShell script for Windows Registry search (currently 30 minutes)
我正在编写用于 Windows 7 和 Windows 10 的脚本,用于在 HKLM:\Software\Classes 中进行 Windows 注册表搜索。到目前为止,我的代码有效,但速度非常慢。大约需要 30 分钟才能完成。
我还需要使用 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 than 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 the main key
Get-RegItems -path $path -match $match
# Retrieve items of all child keys
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
}
您可以在此处做出的最大改进是改变:
Set-Location -literalpath $path
$d= Get-Item .
至
$d = Get-Item -LiteralPath $path
为层次结构中的每个键操作位置堆栈会引入大量不必要的开销
用户函数调用开销(包括脚本块)非常大(例如 0.1-1 毫秒)。当函数执行 thousands/millions 次时,这将成为一个非常严重的问题。令人惊讶的是,与优化相关的文章中没有提到它(至少我从未见过它并且我在谷歌上搜索了很多这个主题)。
不幸的是,解决此特定问题的唯一真正解决方案是以重复和降低可读性为代价的内联代码。
优化应包括代码分析。
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
这是您的示例脚本的更快版本。
持续约。在我的机器上 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
}
如果您希望更快,请不要使用注册表驱动器提供程序。
我也读过 类 静态方法更快。
我接受了挑战并成功了"as fast as possible"。
现在它甚至比 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."
我正在编写用于 Windows 7 和 Windows 10 的脚本,用于在 HKLM:\Software\Classes 中进行 Windows 注册表搜索。到目前为止,我的代码有效,但速度非常慢。大约需要 30 分钟才能完成。
我还需要使用 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 than 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 the main key
Get-RegItems -path $path -match $match
# Retrieve items of all child keys
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
}
您可以在此处做出的最大改进是改变:
Set-Location -literalpath $path
$d= Get-Item .
至
$d = Get-Item -LiteralPath $path
为层次结构中的每个键操作位置堆栈会引入大量不必要的开销
用户函数调用开销(包括脚本块)非常大(例如 0.1-1 毫秒)。当函数执行 thousands/millions 次时,这将成为一个非常严重的问题。令人惊讶的是,与优化相关的文章中没有提到它(至少我从未见过它并且我在谷歌上搜索了很多这个主题)。
不幸的是,解决此特定问题的唯一真正解决方案是以重复和降低可读性为代价的内联代码。
优化应包括代码分析。
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
这是您的示例脚本的更快版本。 持续约。在我的机器上 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
}
如果您希望更快,请不要使用注册表驱动器提供程序。
我也读过 类 静态方法更快。
我接受了挑战并成功了"as fast as possible"。 现在它甚至比 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."