使用 .NET (PowerShell 或 .NET)获取 BCD 条目



我正在创建一个应用程序来分析启动配置数据 (BCD( 中的条目。

我已经尝试过使用PowerShell,但它似乎没有提供任何cmdlet来处理它。所以,我已经回到了.NET,尤其是C#。

我想让一些东西获得这样的 BCD 条目

var entries = bcd.GetEntries();

条目是IList<BcdEntry>

class BcdEntry
{
public string Name {get; set; }
IDictionary<string, IList<string>> Properties { get; set; }
}

问题是我不知道如何获取条目。调用 BCDEdit 是可能的,但它需要分析命令的输出,这是一项繁琐的任务。

我希望你能为我的问题想出一个解决方案。

一种 PSv4+ 解决方案,可将bcdedit.exe /enum输出解析为自定义对象列表:

# IMPORTANT: bcdedit /enum requires an ELEVATED session.
$bcdOutput = (bcdedit /enum) -join "`n" # collect bcdedit's output as a *single* string
# Initialize the output list.
$entries = New-Object System.Collections.Generic.List[pscustomobject]]
# Parse bcdedit's output.
($bcdOutput -split '(?m)^(.+n-)-+n' -ne '').ForEach({
if ($_.EndsWith("`n-")) { # entry header 
$entries.Add([pscustomobject] @{ Name = ($_ -split 'n')[0]; Properties = [ordered] @{} })
} else {  # block of property-value lines
($_ -split 'n' -ne '').ForEach({
$propAndVal = $_ -split 's+', 2 # split line into property name and value
if ($propAndVal[0] -ne '') { # [start of] new property; initialize list of values
$currProp = $propAndVal[0]
$entries[-1].Properties[$currProp] = New-Object Collections.Generic.List[string]
}
$entries[-1].Properties[$currProp].Add($propAndVal[1]) # add the value
})
}
})
# Output a quick visualization of the resulting list via Format-Custom
$entries | Format-Custom

注意:

  • 正如LotPing所观察到的,

    • bcdedit.exe输出已部分本地化;具体而言,包括以下各项:
      • 条目标题(例如,英语Windows Boot ManagerAdministrador de arranque de Windows西班牙语(
      • 奇怪的是,还有以英语命名identifier的属性名称(例如,西班牙语中的Identificador(。
    • 为了简洁起见,
    • 代码没有尝试将本地化名称映射到其美国英语对应项,但可以这样做。

    • 此外,与此 ServerFault 问题(重复(一起发布的示例bcdedit输出表明,可能存在太长的属性名称,以至于它们碰到它们的值,没有干预空格,也没有截断。
      如果这不仅仅是发布工件,则需要做更多的工作来处理这种情况;本文包含属性名称的列表。

  • 使用[pscustomobject]实例而不是自定义BcdEntry类的实例;在 PSv5+ 中,可以直接在 PowerShell 中创建此类自定义类。

  • 属性值全部捕获为字符串值,收集在[List[string]]列表中(即使只有 1 个值(; 需要额外的工作才能将它们解释为特定类型;
    例如,[int] $entries[1].Properties['allowedinmemorysettings'][0]将字符串'0x15000075'转换为整数。


示例输入/输出:

鉴于bcdedit.exe /enum这样的输出...

Windows Boot Manager
--------------------
identifier              {bootmgr}
device                  partition=C:
displayorder            {current}
{e37fc869-68b0-11e8-b4cf-806e6f6e6963}
description             Windows Boot Manager
locale                  en-US
inherit                 {globalsettings}
default                 {current}
resumeobject            {9f3d8468-592f-11e8-a07d-e91e7e2fad8b}
toolsdisplayorder       {memdiag}
timeout                 0
Windows Boot Loader
-------------------
identifier              {current}
device                  partition=C:
path                    WINDOWSsystem32winload.exe
description             Windows 10
locale                  en-US
inherit                 {bootloadersettings}
recoverysequence        {53f531de-590e-11e8-b758-8854872f7fe5}
displaymessageoverride  Recovery
recoveryenabled         Yes
allowedinmemorysettings 0x15000075
osdevice                partition=C:
systemroot              WINDOWS
resumeobject            {9f3d8468-592f-11e8-a07d-e91e7e2fad8b}
nx                      OptIn
bootmenupolicy          Standard

。上面的命令产生这个:

class PSCustomObject
{
Name = Windows Boot Manager
Properties = 
[
class DictionaryEntry
{
Key = identifier
Value = 
[
{bootmgr}
]
Name = identifier
}
class DictionaryEntry
{
Key = device
Value = 
[
partition=C:
]
Name = device
}
class DictionaryEntry
{
Key = displayorder
Value = 
[
{current}
{e37fc869-68b0-11e8-b4cf-806e6f6e6963}
]
Name = displayorder
}
class DictionaryEntry
{
Key = description
Value = 
[
Windows Boot Manager
]
Name = description
}
...
]
}
class PSCustomObject
{
Name = Windows Boot Loader
Properties = 
[
class DictionaryEntry
{
Key = identifier
Value = 
[
{current}
]
Name = identifier
}
class DictionaryEntry
{
Key = device
Value = 
[
partition=C:
]
Name = device
}
class DictionaryEntry
{
Key = path
Value = 
[
WINDOWSsystem32winload.exe
]
Name = path
}
class DictionaryEntry
{
Key = description
Value = 
[
Windows 10
]
Name = description
}
...
]
}

以编程方式处理条目

foreach($entry in $entries) { 
# Get the name.
$name = $entry.Name
# Get a specific property's value.
$prop = 'device'
$val = $entry.Properties[$prop] # $val is a *list*; e.g., use $val[0] to get the 1st item
}

注意:$entries | ForEach-Object { <# work with entry $_ #> },即使用管道也是一种选择,但如果条目列表已经在内存中,则foreach循环会更快。

我对脚本进行了一些更改@mklement0太多了,无法在注释中放置。

  • 为了解决多行属性问题,这些属性(其中所有 似乎括在大括号中(与正则表达式替换连接。
  • 为了独立于区域设置,脚本仅使用虚线标记 节标题,用于拆分内容(一个警告,它插入了一个空白 第一次输入(
  • 我想知道为什么只有 4 个字典条目 输出,直到我找到$FormatEnumerationLimit的默认值 为 4

  • 为了避免输出中的换行符,脚本使用Out-String -Width 4096


## Q:Test2018620SO_50946956.ps1
# IMPORTANT: bcdedit /enu, requires an ELEVATED session.
#requires -RunAsAdministrator
## the following line imports the file posted by SupenJMN for testing
$bcdOutput = (gc ".BCDEdit_ES.txt") -join "`n" -replace '}ns+{','},{'
## for a live "bcdedit /enum all" uncomment the following line
# $bcdOutput = (bcdedit /enum all) -join "`n" -replace '}ns+{','},{'
# Create the output list.
$entries = New-Object System.Collections.Generic.List[pscustomobject]]
# Parse bcdedit's output into entry blocks and construct a hashtable of
# property-value pairs for each.
($bcdOutput -split '(?m)^([a-z].+)n-{10,100}n').ForEach({
if ($_ -notmatch '  +') {
$entries.Add([pscustomobject] @{ Name = $_; Properties = [ordered] @{} })
} else {
($_ -split 'n' -ne '').ForEach({
$keyValue = $_ -split 's+', 2
$entries[-1].Properties[$keyValue[0]] = $keyValue[1]
})
}
})
# Output a quick visualization of the resulting list via Format-Custom
$FormatEnumerationLimit = 20
$entries | Format-Custom | Out-String -Width 4096 | Set-Content BCDEdit_ES_Prop.txt

脚本的短样本输出(~700 行(

class PSCustomObject
{
Name = 
Properties = 
[
]
}
class PSCustomObject
{
Name = Administrador de arranque de firmware
Properties = 
[
class DictionaryEntry
{
Key = Identificador
Value = {fwbootmgr}
Name = Identificador
}
class DictionaryEntry
{
Key = displayorder
Value = {bootmgr},{e37fc869-68b0-11e8-b4cf-806e6f6e6963},{05d4f193-712c-11e8-b4ea-806e6f6e6963},{05d4f194-712c-11e8-b4ea-806e6f6e6963},{cb6d5609-712f-11e8-b4eb-806e6f6e6963},{cb6d560a-712f-11e8-b4eb-806e6f6e6963},{cb6d560b-712f-11e8-b4eb-806e6f6e6963}
Name = displayorder
}
class DictionaryEntry
{
Key = timeout
Value = 1
Name = timeout
}
]
}

我的方法看起来有点像这样:

(bcdedit /enum | Out-String) -split '(?<=rn)rn' | ForEach-Object {
$name, $data = $_ -split 'rn---+rn'
$props = [ordered]@{
'name' = $name.Trim()
}
$data | Select-String '(?m)^(S+)ss+(.*)' -AllMatches |
Select-Object -Expand Matches |
ForEach-Object { $props[$_.Groups[1].Value] = $_.Groups[2].Value.Trim() }
[PSCustomObject]$props
}

上面的代码基本上是从将bcdedit输出合并为单个字符串开始,就像其他答案一样,然后将该字符串拆分为引导配置数据块。然后再次拆分这些块中的每一个,以将标题与实际数据分开。标题作为引导配置部分的名称添加到哈希表中,然后使用键/值对的正则表达式解析数据块。这些将附加到哈希表,最终转换为自定义对象。

由于orderedPSCustomObject类型的加速器,代码至少需要 PowerShell v3。

当然,您可以对上述基本示例代码应用各种优化。例如,不同的引导配置部分可能具有不同的属性。引导管理器部分具有引导加载程序部分中不存在的toolsdisplayordertimeout等属性,而引导加载程序部分具有引导管理器部分中不存在的osdevicesystemroot等属性。如果您希望所有生成的对象都有一组一致的属性,则可以通过一个Select-Object通过管道传输它们,其中包含您希望对象具有的属性列表,例如:

... | Select-Object 'name', 'identifier', 'default', 'osdevice' 'systemroot'

列表中不存在的属性将从对象中删除,而对象中不存在的属性将使用空值添加。

此外,与其将所有值创建为字符串,不如将它们转换为更合适的类型或仅修改值,例如从字符串中删除大括号。

... | ForEach-Object {
$key = $_.Groups[1].Value
$val = $_.Groups[2].Value.Trim()
$val = $val -replace '^{(.*)}$', '$1'
if ($val -match '^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$') {
$val = [guid]$val
} elseif ($val -eq 'yes' -or $val -eq 'true') {
$val = $true
} elseif ($val -eq 'no' -or $val -eq 'false') {
$val = $false
} elseif ($key -eq 'locale') {
$val = [Globalization.CultureInfo]$val
}
$props[$key] = $val
}

最新更新