【问题标题】:Getting BCD entries with .NET (PowerShell or .NET)使用 .NET(PowerShell 或 .NET)获取 BCD 条目
【发布时间】:2018-11-29 12:18:03
【问题描述】:

我正在创建一个应用程序来分析引导配置数据 (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 是可能的,但它需要解析命令的输出,这是一项繁琐的任务。

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

【问题讨论】:

  • Related。对于 PowerShell 解决方案,我可能会将 bcdedit /enum 的输出解析为自定义对象。
  • @AnsgarWiechers 有用,但不是我想要的:(

标签: c# .net powershell wmi bcdedit


【解决方案1】:

bcdedit.exe /enum 输出解析为自定义对象列表的PSv4+ 解决方案:

# 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 Manager 在西班牙语中是 Administrador de arranque de Windows
      • 奇怪的是,还有名为 identifier 的英文属性名称(例如,Identificador 西班牙文)。
    • 为简洁起见,代码不会尝试将本地化名称映射到其美国英语对应项,但可以这样做。

    • 此外,与 this ServerFault question(重复)一起发布的示例 bcdedit 输出表明,可能存在属性名称太长以至于它们遇到它们的值,而没有插入空格并且没有截断。
      如果这不仅仅是发布的人工制品,则需要更多的工作来处理这种情况; this article 包含属性名称列表。

  • 使用[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                    \WINDOWS\system32\winload.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 = 
          [
            \WINDOWS\system32\winload.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 { &lt;# work with entry $_ #&gt; },即使用管道也是一种选择,但如果条目列表已经在内存中,foreach 循环会更快。

【讨论】:

  • @SuperJMN:是的,我刚刚意识到多值属性存在问题;你能发布一个(临时)链接到破坏命令的特定bcdedit 输出吗?
  • SuperJMN 已经在serverfault.com/questions/917036/… 上发布,并且他有一个超过左列宽度processcustomactionsfirstYes 的样本,因此没有简单的方法可以使用正则表达式进行 grep。 BCDEdit 输出部分本地化,部分和 ID 属性 (EN=identifier/ES=Identificador/DE=Bezeichner) 所有其他属性名称似乎都是英文。
  • @LotPings:谢谢,很高兴知道。我已经相应地更新了答案。它现在可以找到不考虑特定名称的标题,并正确处理多值属性(同时不对单个值的形式做任何假设)。
  • @SuperJMN:请在答案底部查看我的更新。
【解决方案2】:

我对@mklement0 脚本做了一些更改,太多无法放入 cmets。

  • 为了解决多行属性问题,这些属性(所有 似乎包含在花括号中)与 RegEx 替换连接。
  • 要独立于语言环境,脚本仅使用虚线标记 部分标题,用于拆分内容(一个警告它会插入一个空白 第一个条目)
  • 我想知道为什么字典中只有 4 个条目 输出直到我找到$FormatEnumerationLimit 的默认值 是 4

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


## Q:\Test\2018\06\20\SO_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 '\}\n\s+\{','},{'
## for a live "bcdedit /enum all" uncomment the following line
# $bcdOutput = (bcdedit /enum all) -join "`n" -replace '\}\n\s+\{','},{'

# 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
      }
    ]

}

【讨论】:

  • 如何遍历 $entries 对象?我希望能够过滤条目,例如Get-BcdEntries | ForEach-Object { Write-Output $_.Name }
  • 这里是一个班轮foreach($section in $entries){$section.name;"-"*40;foreach($Property in $Section.Properties){$property}}
【解决方案3】:

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

(bcdedit /enum | Out-String) -split '(?<=\r\n)\r\n' | ForEach-Object {
    $name, $data = $_ -split '\r\n---+\r\n'

    $props = [ordered]@{
        'name' = $name.Trim()
    }

    $data | Select-String '(?m)^(\S+)\s\s+(.*)' -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
}

【讨论】:

    猜你喜欢
    • 2019-11-27
    • 2023-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多