Boot Into

From NSIS Wiki
Jump to navigationJump to search

Description

The script displays the boot entries in a list view and boots into the selected boot entry in case the Boot button is going to be clicked.

Script

; Licensed under the zlib/libpng license (same as NSIS)
;
; Unicode builds:
; x86 unicode
; makensis "-XTarget x86-unicode" script.nsi
; amd64 unicode
; makensis "-XTarget amd64-unicode" script.nsi
 
Name "Boot Into"
 
!include MUI2.nsh
!include bootcfg.nsh
 
; Include required functions
${BOOTCFG_ConnectWMI}
${BOOTCFG_GetObject}
${BOOTCFG_OpenDefaultBcdStore}
${BOOTCFG_GetBcdObject}
${BOOTCFG_GetBcdElement}
${BOOTCFG_GetObjectPropertyValue}
${BOOTCFG_GetElementFromBcd}
!define BOOTCFG_ENUMERATION_CALLBACK AddBootEntry
${BOOTCFG_EnumerateBcdObjectList}
${BOOTCFG_SetActiveBootEntry}
 
!ifndef LVM_GETITEMTEXT
!define /math LVM_GETITEMTEXTA ${LVM_FIRST} + 45
!define /math LVM_GETITEMTEXTW ${LVM_FIRST} + 115
${_NSIS_DEFAW} LVM_GETITEMTEXT
!endif
 
!ifndef LVIS_FOCUSED
!define LVIS_FOCUSED   0x1
!endif
!ifndef LVIS_SELECTED
!define LVIS_SELECTED  0x2
!endif
 
!define NSD_CreateListView "nsDialogs::CreateControl SysListView32 \
  ${DEFAULT_STYLES}|${LVS_REPORT}|${LVS_SHOWSELALWAYS}|${LVS_SINGLESEL} \
  ${WS_EX_WINDOWEDGE}|${WS_EX_CLIENTEDGE}"
 
!define NSD_LV_InsertColumn `!insertmacro __NSD_LV_InsertColumn`
!macro __NSD_LV_InsertColumn CONTROL COLUMN WIDTH STRING
  ; Fill LVCOLUMN structure
  System::Call "*(i${LVCF_WIDTH}|${LVCF_TEXT},i,i${WIDTH}, \
    t'${STRING}',i${NSIS_MAX_STRLEN},i,i,i,i,i,i) p.s"
  Exch $0
  SendMessage ${CONTROL} ${LVM_INSERTCOLUMN} ${COLUMN} $0
  System::Free $0
  Pop $0
!macroend
 
!define NSD_LV_InsertItem `!insertmacro __NSD_LV_InsertItem`
!macro __NSD_LV_InsertItem CONTROL INDEX STRING
  System::Call "*(i${LVIF_TEXT},i${INDEX},i,i,&i${NSIS_PTR_SIZE},\
    t'${STRING}',i${NSIS_MAX_STRLEN},i,p,i,i,i,i,i,i) p.s"
  Exch $0
  SendMessage ${CONTROL} ${LVM_INSERTITEM} 0 $0
  System::Free $0
  Pop $0
!macroend
 
!define NSD_LV_SetItemState `!insertmacro __NSD_LV_SetItemState`
!macro __NSD_LV_SetItemState CONTROL INDEX STATE
  System::Call "*(i${LVIF_STATE},i${INDEX},i,\
    i${STATE},&i${NSIS_PTR_SIZE} ${STATE},\
    t,i,i,p,i,i,i,i,i,i) p.s"
  Exch $0
  SendMessage ${CONTROL} ${LVM_SETITEMSTATE} ${INDEX} $0
  System::Free $0
  Pop $0
!macroend
 
!define NSD_LV_SetItemText `!insertmacro __NSD_LV_SetItemText`
!macro __NSD_LV_SetItemText CONTROL INDEX SUBINDEX STRING
  System::Call "*(i${LVIF_TEXT},i${INDEX},i${SUBINDEX},i,\
    &i${NSIS_PTR_SIZE},t'${STRING}',i${NSIS_MAX_STRLEN},\
    i,p,i,i,i,i,i,i) p.s"
  Exch $0
  SendMessage ${CONTROL} ${LVM_SETITEMTEXT} ${INDEX} $0
  System::Free $0
  Pop $0
!macroend
 
Var ListView
Var Services
Var Locator
Var BaseBcdStore
Var BaseBcdObject
Var BcdStore
 
!define MUI_CUSTOMFUNCTION_ABORT CleanUp
Page custom BootSelector Boot
!define MUI_PAGE_CUSTOMFUNCTION_PRE Skip
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
 
Function .onInit
  Push $0
  ${BOOTCFG_ConnectWMI} $Locator $Services $0
  ${If} $0 != 0
    IntFmt $0 "0x%08X" $0
    MessageBox MB_OK "$Services: $0"
    StrCpy $Services ""
    StrCpy $Locator ""
  ${EndIf}
  Pop $0
FunctionEnd
 
Function .onRebootFailed
  MessageBox MB_OK|MB_ICONSTOP "Reboot failed. Please reboot manually." \
    /SD IDOK
FunctionEnd
 
; Skip page
Function Skip
  Quit
FunctionEnd
 
; Determine the boot entries and present them via the list view control
Function SetUp
  ; Save registers
  Push $0
  Push $1
  Push $2
  Push $3
  Push $4
  Push $5
 
  ${If} $Services != ""
    ${BOOTCFG_GetObject} $Services "BcdStore" $BaseBcdStore $0
    ${If} $0 != 0
      StrCpy $1 $BaseBcdStore
      StrCpy $BaseBcdStore ""
    ${Else}
      ${BOOTCFG_GetObject} $Services "BcdObject" $BaseBcdObject $0
      ${If} $0 != 0
        StrCpy $1 $BaseBcdObject
        StrCpy $BaseBcdObject ""
      ${Else}
        ${BOOTCFG_OpenDefaultBcdStore} $Services $BaseBcdStore \
          $BcdStore $0
        ${If} $0 != 0
          StrCpy $1 $BcdStore
          StrCpy $BcdStore ""
        ${Else}
          ${BOOTCFG_GetBcdObject} $Services $BaseBcdStore \
            $BcdStore ${BOOTCFG_CURRENT_GUID} $2 $0
          ; bcdobject=$5
          ${If} $0 != 0
            StrCpy $1 $2
          ${Else}
            ${BOOTCFG_GetBcdElement} $Services $BaseBcdObject \
              $2 ${BOOTCFG_BCDE_AUTORECOVERYENABLED} \
              $1 $0
            ${If} $0 == 0
              ${BOOTCFG_GetObjectPropertyValue} $1 "Boolean" $3 $0
              ; Release element [auto recovery enabled]
              ${BOOTCFG_ReleaseObject} $1
              ${If} $0 == 0
                System::Call "*$3(i .r0, i, i .r1)"
                System::Free $3
                IntOp $0 $0 & 0xFFFF
                IntOp $0 $0 - ${VT_BOOL}
                ${If} $0 == 0
                ${AndIf} $1 != 0
                  ${BOOTCFG_GetBcdElement} $Services $BaseBcdObject \
                    $2 ${BOOTCFG_BCDE_RECOVERYSEQUENCE} $3 $0
                ${EndIf}
              ${EndIf}
            ${EndIf}
            ; Release bcdobject
            ${BOOTCFG_ReleaseObject} $2
          ${EndIf}
          ${If} $0 != 0
            StrCpy $3 ""
          ${EndIf}
          ${BOOTCFG_GetElementFromBcd} $Services $BaseBcdStore \
            $BcdStore $BaseBcdObject ${BOOTCFG_BOOTMGR_GUID} \
            ${BOOTCFG_DISPLAY_ORDER} $2 $4 $0
          ; element=$4
          ; bcdobject=$2
          ${If} $0 != 0
            StrCpy $1 $4
          ${Else}
            ; Release bcdobject
            ${BOOTCFG_ReleaseObject} $2
            ${If} $3 != "" ; recovery boot entries
              ${BOOTCFG_EnumerateBcdObjectList} $Services \
                $BaseBcdStore $BcdStore $BaseBcdObject $3 $1
              ; Release object list element
              ${BOOTCFG_ReleaseObject} $3
            ${EndIf}
            ${BOOTCFG_EnumerateBcdObjectList} $Services $BaseBcdStore \
              $BcdStore $BaseBcdObject $4 $1
            ; Release object list element
            ${BOOTCFG_ReleaseObject} $4
          ${EndIf}
        ${EndIf}
      ${EndIf}
    ${EndIf}
  ${Else}
    StrCpy $1 "Invalid $$Services variable"
    StrCpy $0 ${ERROR_INVALID_DATA}
  ${EndIf}
 
  ${If} $0 != 0
    IntFmt $0 "0x%08X" $0
    MessageBox MB_OK "$1: $0"
  ${EndIf}
 
  ; Restore registers
  Pop $5
  Pop $4
  Pop $3
  Pop $2
  Pop $1
  Pop $0
FunctionEnd
 
Function CleanUp
  ${BOOTCFG_ReleaseObject} $BcdStore
  ${BOOTCFG_ReleaseObject} $BaseBcdObject
  ${BOOTCFG_ReleaseObject} $BaseBcdStore
  ${BOOTCFG_ReleaseObject} $Services
  ${BOOTCFG_ReleaseObject} $Locator
FunctionEnd
 
; Add boot entry to list view control
; Parameters
;   name - description of boot entry
;   GUID - unique identifier of boot entry
Function AddBootEntry
  Exch $0
  Exch
  Exch $1
  Push $2
 
  ; name=$1 GUID=$0
  SendMessage $ListView ${LVM_GETITEMCOUNT} "" "" $2
  ${NSD_LV_InsertItem} $ListView $2 $1
  ${NSD_LV_SetItemText} $ListView $2 1 $0
 
  Pop $2
  Pop $1
  Pop $0
FunctionEnd
 
; Dialog to select boot entry
Function BootSelector
  !insertmacro MUI_HEADER_TEXT "Boot Into" "Select an entry from below list to boot"
  nsDialogs::Create 1018
  Pop $0
  ${If} $0 != error
    ; Change the label of the "Next" button
    GetDlgItem $1 $HWNDPARENT 1
    SendMessage $1 ${WM_SETTEXT} 0 "STR:Boot"
    ; Create list view control
    ${NSD_CreateListView} 0 0 100% 100% "ListView"
    Pop $ListView
    ; Explicitly set full row selection
    SendMessage $ListView ${LVM_SETEXTENDEDLISTVIEWSTYLE} 0 ${LVS_EX_FULLROWSELECT}
    ; Calculate width of columns
    System::Call "user32::GetClientRect(p $ListView, @r2) i.r0"
    ${If} $0 != 0
      ; 2/5 (40%) of width for first column and 3/5 (60%) for second column
      System::Call "*$2(i.r0, i, i.r3, i)"
      IntOp $3 $3 - $0
      IntOp $2 $3 / 5
      IntOp $2 $2 + $2
      IntOp $3 $3 - $2
    ${Else}
      ; Fall back to default values
      StrCpy $2 100
      StrCpy $3 200
    ${EndIf}
    ${NSD_LV_InsertColumn} $ListView 0 $2 "Label"
    ${NSD_LV_InsertColumn} $ListView 1 $3 "GUID"
    Call SetUp
    ; Select first item in list
    ${NSD_LV_SetItemState} $ListView 0 ${LVIS_FOCUSED}|${LVIS_SELECTED}
    nsDialogs::Show
  ${Else}
    Abort
  ${EndIf}
FunctionEnd
 
Function Boot
  ; Go through the items and find the selected one
  SendMessage $ListView ${LVM_GETITEMCOUNT} "" "" $1
  ${If} $1 > 0
    IntOp $1 $1 - 1
    ${For} $0 0 $1
      SendMessage $ListView ${LVM_GETITEMSTATE} $0 ${LVIS_SELECTED} $2
      ${If} $2 != 0
        System::Call '*(&t${NSIS_MAX_STRLEN})p.r3'
        ; Choose second column => subindex = 1
        System::Call "*(i${LVIF_TEXT},i,i 1,i,&i${NSIS_PTR_SIZE},\
          pr3,i${NSIS_MAX_STRLEN},i,p,i,i,i,i,i,i) p.r4"
        SendMessage $ListView ${LVM_GETITEMTEXT} $0 $4 $2
        ${If} $2 > 0
          System::Call "*$3(&t$2.r2)"
          ${BOOTCFG_SetActiveBootEntry} $Services $BaseBcdStore \
            $BcdStore $BaseBcdObject $2 $6 $5
          ${If} $5 != 0
            IntFmt $5 "0x%08X" $5
            MessageBox MB_OK "$6: $5"
          ${Else}
            ${BOOTCFG_ReleaseObject} $6
            SetRebootFlag true
          ${EndIf}
        ${EndIf}
        System::Free $4
        System::Free $3
      ${EndIf}
    ${Next}
  ${EndIf}
  Call CleanUp
 
  ${If} ${RebootFlag}
    Reboot
  ${EndIf}
FunctionEnd
 
Section -Ignore
SectionEnd