Search code examples
powershellautomationpowershell-remotingbcdedit

passing guid and path to bcdedit /set as a variable


I need to run these 4 commands to boot a new image into a device:

bcdedit /copy {current} /d "Describe"
bcdedit /set {guid} osdevice vhd=somepath 
bcdedit /set {guid} device vhd=somepath
bcdedit /default {$guid} 

To automate my script, I want to extract the guid/identifier returned as output from the first command and pass it as a parameter to the other 3 commands. Right now, I'm doing it in this way:

$guid = Invoke-Command -ComputerName $comp -Credential $cred -ScriptBlock {cmd /c "bcdedit /copy {current} /d "Describe""}

#output
#$guid = This entry was successfully copied to {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

$guid = $guid.Replace("This entry was successfully copied to {","")
$guid = $guid.Replace("}","")

$path1 = "xxx/xxxx/...."

#passing the guid and path as inputs
Invoke-Command -ComputerName $comp -Credential $cred -ScriptBlock {cmd /c "bcdedit /set {$guid} osdevice vhd=$path1"}
Invoke-Command -ComputerName $comp -Credential $cred -ScriptBlock {cmd /c "bcdedit /set {$guid} device vhd=$path1"}
Invoke-Command -ComputerName $comp -Credential $cred -ScriptBlock {cmd /c "bcdedit /default {$guid} "}

But, each time I get an error:

The element data type is not recognized, or does not apply to the specified entry. Run "bcdedit /?" for command line assistance. Element not found

This works fine when I manually copy and paste the path in the UI, but I'm not sure how I can automate it.


Solution

    • As explained in this answer to your prior question, there is no need to use cmd /c in order to invoke bcdedit - all that is needed is to quote literal arguments that contain { and }, because these characters, when used unquoted, have special meaning in PowerShell.

      • Calling via cmd /c is not only inefficient (though in practice that will hardly matter), but can also introduce quoting headaches.[1]
    • A script block passed to Invoke-Command -ComputerName ... executes remotely and therefore has no access to the caller's variables; the simplest way to include variable values from the caller's scope is via the $using: scope (see this answer).

    Therefore:

    $guid = Invoke-Command -ComputerName $comp -Credential $cred -ScriptBlock { 
      bcdedit /copy '{current}' /d "Describe" 
    }
    
    # Extract the GUID from the success message, which has the form
    # "This entry was successfully copied to {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"
    $guid = '{' + ($guid -split '[{}]')[1] + '}'
    
    $path1 = "xxx/xxxx/...."
    
    # Passing the guid and path as inputs, which must
    # be done via the $using: scope.
    Invoke-Command -ComputerName $comp -Credential $cred -ScriptBlock { 
      bcdedit /set $using:guid osdevice vhd=$using:path1
      bcdedit /set $using:guid vhd=$using:path1
      bcdedit /default $using:guid
    }
    

    [1] For instance, cmd /c "bcdedit /copy {current} /d "Describe"" doesn't work the way you think it does; it would have to be cmd /c "bcdedit /copy {current} /d `"Describe`""