Skip to content

Instantly share code, notes, and snippets.

@meatcar
Last active March 12, 2024 01:38
Show Gist options
  • Save meatcar/907d07918b4e184405e62a39bb295f99 to your computer and use it in GitHub Desktop.
Save meatcar/907d07918b4e184405e62a39bb295f99 to your computer and use it in GitHub Desktop.
A better "Reboot to {OS}" script for rEFInd Next Boot selection for Windows
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
public class RefindNextBoot
{
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
[DllImport("kernel32.dll", ExactSpelling = true)]
internal static extern IntPtr GetCurrentProcess();
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
}
internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
internal const int TOKEN_QUERY = 0x00000008;
internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
internal const string SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege";
[DllImport("kernel32.dll", SetLastError = true)]
static extern UInt32 GetFirmwareEnvironmentVariableA(string lpName, string lpGuid, IntPtr pBuffer, UInt32 nSize);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetFirmwareEnvironmentVariableA(string lpName, string lpGuid, IntPtr pBuffer, UInt32 nSize);
private static bool SetPrivilege()
{
try
{
bool retVal;
TokPriv1Luid tp;
IntPtr hproc = GetCurrentProcess();
IntPtr htok = IntPtr.Zero;
retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
tp.Count = 1;
tp.Luid = 0;
tp.Attr = SE_PRIVILEGE_ENABLED;
retVal = LookupPrivilegeValue(null, SE_SYSTEM_ENVIRONMENT_NAME, ref tp.Luid);
retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
return retVal;
}
catch (Exception)
{
return false;
}
}
public static string Get()
{
if (!RefindNextBoot.SetPrivilege())
{
return "Error: Unable to set privilege";
}
string s = "";
UInt32 size = (uint)256;
int[] array = new int[size];
UInt32 ret;
GCHandle handle = default(GCHandle);
try
{
handle = GCHandle.Alloc(array, GCHandleType.Pinned);
IntPtr pointer = handle.AddrOfPinnedObject();
ret = GetFirmwareEnvironmentVariableA("PreviousBoot", "{36d08fa7-cf0b-42f5-8f14-68df73ed3740}", pointer, size);
s = Marshal.PtrToStringAuto(pointer);
}
finally
{
if (handle.IsAllocated)
{
handle.Free();
}
}
if (ret > 0)
{
return s;
}
else
{
string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
return "Error: " + errorMessage;
}
}
public static string Set(string efivar)
{
if (!RefindNextBoot.SetPrivilege())
{
return "Error: Unable to set privilege";
}
// build efivar as per https://gist.github.com/Darkhogg/82a651f40f835196df3b1bd1362f5b8c
UInt32 size = 4 + (2 * (uint)efivar.Length);
byte[] buffer = new byte[size];
for (int i = 0; i < efivar.Length; i++)
{
buffer[2 * i] = (byte)efivar[i];
buffer[(2 * i) + 1] = 0x00;
}
buffer[size - 4] = 0x20;
buffer[size - 3] = 0x00;
buffer[size - 2] = 0x00;
buffer[size - 1] = 0x00;
// Console.Write(BitConverter.ToString(buffer)); // debug
bool success;
GCHandle handle = default(GCHandle);
try
{
handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
IntPtr pointer = handle.AddrOfPinnedObject();
success = SetFirmwareEnvironmentVariableA("PreviousBoot", "{36d08fa7-cf0b-42f5-8f14-68df73ed3740}", pointer, size);
}
finally
{
if (handle.IsAllocated)
{
handle.Free();
}
}
if (success)
{
return "ok";
}
else
{
string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
return "Error: " + errorMessage;
}
}
}
#Requires -Version 2
<#
.Synopsis
Get and Set the PreviousBoot efivar that is read by rEFInd.
.DESCRIPTION
Modify the PreviousBoot EFI variable that is read and modified by the rEFInd bootloader.
If rEFInd is properly configured, modifying this variable to a substring
of an available entry will boot the entry on the next boot.
This script uses a complied Win32 API call to read and modify the EFI.
It is effectively a re-write of https://gist.github.com/Darkhogg/82a651f40f835196df3b1bd1362f5b8c
using a bundled C# script to avoid a compilation step or distributing a binary.
.PARAMETER action
The action to take upon running the script. One of 'none' (default), 'get'
or 'set'. Use `none` if you just want to source the script.
.PARAMETER value
A value to set if $action = 'set'.
.EXAMPLE
& RefindNextBoot.ps1 get
.EXAMPLE
& RefindNextBoot.ps1 set Linux
.EXAMPLE
. RefindNextBoot.ps1
Get-RefindNextBoot
Set-RefindNextBoot Linux
.NOTES
Author: Denys Pavlov <me@denys.me>
#>
Param(
[Parameter()]
[ValidateSet('set', 'get', 'none')]
[String]$action = 'none',
[Parameter()]
[String]$value = ''
)
# Inject the C# script into the PowerShell Session.
$scriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$script = [io.file]::readalltext("$scriptDir\RefindNextBoot.cs")
Add-Type -Language CSharp -TypeDefinition $script
<#
.Synopsis
Get the PreviousBoot efivar that is read by rEFInd.
.DESCRIPTION
This function uses a complied Win32 API call to determine the underlying
system firmware type.
Inspired/guided by https://gist.github.com/Darkhogg/82a651f40f835196df3b1bd1362f5b8c
and re-written using a C# bundled script to avoid a compilation step.
.OUTPUTS
The PreviousBoot EFI variable value, or the error prefixed with "Error:" if encountered.
.EXAMPLE
Get-RefindNextBoot
#>
function Get-RefindNextBoot {
[RefindNextBoot]::Get()
}
<#
.Synopsis
Get the PreviousBoot efivar that is read by rEFInd.
.DESCRIPTION
This function uses a complied Win32 API call to determine the underlying
system firmware type.
Inspired/guided by https://gist.github.com/Darkhogg/82a651f40f835196df3b1bd1362f5b8c
and re-written using a C# bundled script to avoid a compilation step.
.PARAMETER value
The value to assign to the efivar.
.OUTPUTS
"ok" if EFI variable set successfully, or the error prefixed with "Error:" if encountered.
.EXAMPLE
Set-RefindNextBoot Linux
#>
function Set-RefindNextBoot {
Param(
[Parameter()]
[String]$value = ''
)
[RefindNextBoot]::Set([string]$value)
}
if ($action -eq 'get') {
Get-RefindNextBoot
}
elseif ($action -eq 'set') {
Set-RefindNextBoot $value
}
@rtorrero
Copy link

Hi! Thanks for this.

Is there a way in windows to add this as a shortcut somehow?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment