Dictionaries/Dict.Windows/Includes/BCD.psm1
# TODO get or write a crescendo module for bcdedit.exe # trick seen @url https://github.com/PowerShell/Crescendo/issues/10#issuecomment-742784265 # and @url https://github.com/Devolutions/WaykAgent-ps/blob/fdca9595797b2318ff5904b9ec6f24f43d69174a/WaykNow/Private/BcdEdit.ps1 # to un-localize bcdedit if ($IsWindows) { $TEMP = [io.path]::GetTempPath().TrimEnd('\') $system32Path = [System.Environment]::SystemDirectory $bcdEditLocation = "$system32Path/bcdedit.exe" Copy-Item "$bcdEditLocation" -Destination "$TEMP" -Force -ErrorAction SilentlyContinue if (Test-FileExist "$TEMP/bcdedit.exe") { $Script:bcdedit = "$TEMP/bcdedit.exe" } else { $bcdEditLocation = "$system32Path\\bcdedit.exe" Copy-Item "$bcdEditLocation" -Destination "$TEMP" -Force -ErrorAction Stop $Script:bcdedit = "$TEMP\\bcdedit.exe" } } function Parse-BCDeditEnum { [CmdletBinding()] [OutputType([String])] Param ( [Parameter(Mandatory = $true, ValueFromPipeLine = $true)]$output ) Begin { Write-EnterFunction } Process { $title = $null $entries = @() $entry = @{} $prevline = $null $line = $null $lineno = 0 ForEach ($line in $output) { Write-Devel "line n°$($lineno++) = $line" # empty lines is the end of the object if ($line.Trim() -match "^$") { Write-Devel "empty line" if (!([string]::IsNullOrEmpty($entry))) { $entries += $entry } $title = $null $prevline = $null $entry = @() continue } # skip type underline if ($line -match "^---*") { $title = $prevline Write-Devel "underline -> title = '$prevline'" continue } # type of the BCDEntry (text underlined) if ([string]::IsNullOrEmpty($title)) { Write-Devel "type " $entry = @{} $entry.type = $title } else { Write-Devel "data" # entry is of the form "key some value" if ($line -match "^\w") { Write-Devel "word" $data = $line.split(' ',2).Trim() $key = $data[0] $value = $data[1] $entry.Add($key,$value) } else { # entry is another value of the previous key # the form is " some other value" Write-Devel "value" $value = $entry.$key $entry.Remove($key) $value += $line.Trim() $entry.Add($key,$value) } } $prevline = $line } return $entries } End { Write-LeaveFunction } } <# .SYNOPSIS List all BCD entries configured .DESCRIPTION List all BCD entries available .EXAMPLE An example .NOTES General notes #> function Get-BCDAllEntries { [CmdletBinding(DefaultParameterSetName = "STRING")] [OutputType([String])] Param ( # Specify alternate BCD store [Parameter()][string]$BCDStoreFilename, # list entries with their GUID [Parameter(ParameterSetName = "GUID")][switch]$AsGUID, # list entries in their human-readable form if available [Parameter(ParameterSetName = "STRING")][switch]$AsString ) Begin { Write-EnterFunction } Process { switch ($PSCmdlet.ParameterSetName) { 'STRING' { $params = "/enum all" } 'GUID' { $params = "/enum all /v" } } if ($BCDStoreFilename) { $params = "/store $BCDStoreFilename $params" } Write-Devel "$Script:bcdedit $params" $ids = Invoke-Expression -Command "$Script:bcdedit $params" | select-string "^identifier" | ForEach-Object { ($_ -Split " +")[1] } return $ids } End { Write-LeaveFunction } } function Get-BCDEnum { [CmdletBinding()] [OutputType([String])] Param ( # Specify alternate BCD store [Parameter()][string]$BCDStoreFilename, # list entries with their GUID [Parameter()][switch]$AsGUID, # type of enum to list [ValidateSet('active', 'firmware', 'bootapp', 'bootmgr', 'osloader', 'resume', 'inherit', 'all')] [Parameter()][string]$Type = "Active", # list all boot entries. synonym of -Type All [switch]$All ) Begin { Write-EnterFunction } Process { $params = "" if ($BCDStoreFilename) { $params += "/store $BCDStoreFilename" } if ($All) { $params += " /enum all" } else { $params += " /enum $Type" } if ($AsGuid) { $params += " /v" } $enum = @{} Write-Devel "$Script:bcdedit $params" $ids = Invoke-Expression -Command "$Script:bcdedit $params" | select-string "^identifier" | ForEach-Object { ($_ -Split " +")[1] } foreach ($id in $ids) { $entry = Get-BCDEntryDetails -Id $id -AsGUID:$AsGUID -BCDStoreFilename:$BCDStoreFilename $enum.Add($id, $entry) } return $enum } End { Write-LeaveFunction } } function Get-BCDEntryDetails { [CmdletBinding(DefaultParameterSetName = "STRING")] [OutputType([String])] Param ( # Specify alternate BCD store [Parameter()][string]$BCDStoreFilename, # Specify identifier to get details [Parameter(Mandatory = $true)][string]$Id, # list entries with their GUID [Parameter(ParameterSetName = "GUID")][switch]$AsGUID, # list entries in their human-readable form if available [Parameter(ParameterSetName = "STRING")][switch]$AsString ) Begin { Write-EnterFunction } Process { switch ($PSCmdlet.ParameterSetName) { 'STRING' { $params = "/enum '$Id'" } 'GUID' { $params = "/enum '$Id' /v" } } if ($BCDStoreFilename) { $params = "/store $BCDStoreFilename $params" } # it seems that real keys/value pair all start with lowercase char # titles starts with uppercase $entry = @{} $entry.Add("id", $Id) Write-Devel "$Script:bcdedit $params" Invoke-Expression -Command "$Script:bcdedit $params" | select-string "^[a-z ]" | ForEach-Object { [string]$key = ($_ -Split " +")[0] $value = ($_ -Split " +",2)[1] # Write-Devel "key = '$key' / value = '$value'" # handle arrays if ([string]::IsNullOrEmpty($key)) { $key = $previousKey # [array]$aValue = @($entry.$key,$value) # Write-Devel "key = '$key' / aValue = '$aValue'" # [array]$entry.$key += $aValue [array]$entry.$key += ,$value } else { # Write-Devel "key = '$key' / value = '$value'" $entry.Add($key,$value) } $previousKey = $key # $previousValue = $value } # duplicate description key to label key to make it behave like Get-BCDEntry in linux dictionnary if ($entry.ContainsKey("description")) { $entry.Add("label", $entry.description) } if ($entry.ContainsKey("path")) { $entry.Add("filename", $entry.path) } return $entry } End { Write-LeaveFunction } } <# .SYNOPSIS Get the BCD from system to an hastable .DESCRIPTION Read BCD configuration and convert it to a useable hashtable .EXAMPLE An example .NOTES General notes #> function Get-BCD { [CmdletBinding()] [OutputType([String])] Param ( # Specify alternate BCD store [Alias('File', 'Filename', 'BCD')] [Parameter()][string]$BCDStoreFilename ) Begin { Write-EnterFunction } Process { # $bcd = @{} # Foreach ($id in (Get-BCDAllEntries -BCDStoreFilename $BCDStoreFilename -AsGUID)) { # $bcd[$id] = Get-BCDEntryDetails -BCDStoreFilename $BCDStoreFilename -Id $id -AsGUID # } $bcd = @() Foreach ($id in (Get-BCDAllEntries -BCDStoreFilename $BCDStoreFilename -AsGUID)) { $bcd += Get-BCDEntryDetails -BCDStoreFilename $BCDStoreFilename -Id $id -AsGUID } return $bcd } End { Write-LeaveFunction } } function Set-BCDBootSequence { [CmdletBinding(DefaultParameterSetName = "ADDFIRST")] [OutputType([String])] Param ( # id of the BCD entry [Parameter(ParameterSetName = "ADDFIRST")] [Parameter(ParameterSetName = "ADDLAST")] [Parameter(ParameterSetName = "REMOVE")] [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][string]$id, # Adds the specified entry identifier to the top of the boot sequence [Parameter(ParameterSetName = "ADDFIRST")][switch]$AddFirst, # Adds the specified entry identifier to the end of the boot sequence [Parameter(ParameterSetName = "ADDLAST")][switch]$AddLast, # Removes the specified entry identifier from the boot sequence [Parameter(ParameterSetName = "REMOVE")][switch]$Remove ) Begin { Write-EnterFunction } Process { switch ($PSCmdlet.ParameterSetName) { 'ADDFIRST' { $params = "/addfirst" } 'ADDLAST' { $params = "/addlast" } 'REMOVE' { $params = "/remove" } } # this command change boot order of the {current} namespace. That is the name space of the Windows menu. We want to change UEFI boot order # $rc = Execute-Command -exe $Script:bcdedit -args "/bootsequence $id $params" $rc = Execute-Command -exe $Script:bcdedit -args "--% /set {fwbootmgr} displayorder $id $params" -AsBool return $rc } End { Write-LeaveFunction } } function Set-BCDDisplayOrder { [CmdletBinding(DefaultParameterSetName = "ADDFIRST")] [OutputType([String])] Param ( # id of the BCD entry [Parameter(ParameterSetName = "ADDFIRST")] [Parameter(ParameterSetName = "ADDLAST")] [Parameter(ParameterSetName = "REMOVE")] [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][string]$id, # Adds the specified entry identifier to the top of the boot sequence [Parameter(ParameterSetName = "ADDFIRST")][switch]$AddFirst, # Adds the specified entry identifier to the end of the boot sequence [Parameter(ParameterSetName = "ADDLAST")][switch]$AddLast, # Removes the specified entry identifier from the boot sequence [Parameter(ParameterSetName = "REMOVE")][switch]$Remove ) Begin { Write-EnterFunction } Process { switch ($PSCmdlet.ParameterSetName) { 'ADDFIRST' { $params = "/addfirst" } 'ADDLAST' { $params = "/addlast" } 'REMOVE' { $params = "/remove" } } # this command change boot order of the {current} namespace. That is the name space of the Windows menu. We want to change UEFI boot order # $rc = Execute-Command -exe $Script:bcdedit -args "/bootsequence $id $params" $rc = Execute-Command -exe $Script:bcdedit -args "--% /displayorder $id $params" -AsBool return $rc } End { Write-LeaveFunction } } |