; Boots Windows 2000,XP,Vista,Windows 7 into different startup modes ; ; ChangeLog: ; 1.0.2: Fixed removeBCD(). Now removes BCD entry with specified GUID ; Fixed backupBootINI(). Now returns 0 on failure to create backup ; Removed #Include - not needed ; 1.0.1: Removed 'Normal' mode for now (use /restore to revert config) ; 1.0.0: Initial release ; #RequireAdmin #Include #Include #Include Opt("MustDeclareVars", 1) ; Debug Mode: ; 0 = no debug messages are displayed ; 1 = debug messages are displayed (often a lot of them!) ; Set this to 1 to turn Debug Mode on. ; NOTE: Turning Debug Mode on in production is NOT recommended. ; Only turn this on for troubleshooting purposes. Global $debug = 0 Global $cmdopts = $CmdLine Global $systemdrive = "" Global $mode = "" Global $autoreboot = 0 Global $restore = 0 Global $noerror = 0 Global $timeout = 5 Global $returncode = 0 Dim $osversion = "" Dim $drives = "" Dim $boottype = "" Dim $activesysdrive = "" Dim $activesys32drive = "" Dim $activesysroot = "" Dim $activesys32root = "" Dim $predesc = "Boot Mode: " ; Temp vars Dim $stringsplit = "" Dim $tmpstring = "" If ($cmdopts[0] = 0) Then displayHelp() Exit ($returncode) EndIf ; Loop through command args for switches For $a = 1 to ($cmdopts[0]) Switch (StringLower($cmdopts[$a])) Case "/reboot" $autoreboot = 1 Case "/restore" $restore = 1 Case "/noerror" $noerror = 1 Case "/noreboot" $autoreboot = 2 Case "/?" displayHelp() Exit ($returncode) Case "safemode", _ "safemodenetwork", _ "safemodecommand", _ "bootlogging", _ "vga", _ "dsr", _ "debug" ; If mode is empty, set it, else ; If mode is already set, display help. Only one mode allowed If ($mode = "") Then $mode = $cmdopts[$a] Else displayHelp() Exit ($returncode) EndIf Case Else ; /sysdrive: not implemented yet. It will allow ; modifying boot data on a non-active drive with possiblity ; to make it active. ;If (StringLeft($cmdopts[$a], 10) = "/sysdrive:") Then ; $stringsplit = StringSplit($cmdopts[$a], ":") ; If ($stringsplit[0] = 2) Then ; If ($stringsplit[2] <> "") Then ; $systemdrive = $stringsplit[2] & ":" ; Else ; displayHelp() ; Exit ($returncode) ; EndIf ; Else ; displayHelp() ; Exit ($returncode) ; EndIf ;Else displayHelp() Exit ($returncode) ;EndIf EndSwitch Next ; Get active drive and paths _PathSplit(@ComSpec, $activesys32drive, $activesys32root, $tmpstring, $tmpstring) _PathSplit(@WindowsDir & "\explorer.exe", $activesysdrive, $activesysroot, $tmpstring, $tmpstring) ; Get system drive If ($systemdrive <> "") Then ; Get boottype for system drive ; This feature is not implemented yet. Else ; system drive not specified, use current one ; Try to get boot info for current system and OS version ; Set system drive $systemdrive = "active" debugMsg ("Using active system drive: " & $activesysdrive) ; Get boot config determined by current OS version $osversion = @OSVersion $osversion = StringUpper($osversion) If ($osversion <> "") Then If ( $osversion = "WIN_2000" Or _ $osversion = "WIN_XP" Or _ $osversion = "WIN_2003" ) Then ; boot.ini boot configuration $boottype = "bootini" debugMsg ("boot.ini boot config detected") ElseIf ( $osversion = "WIN_VISTA" Or _ $osversion = "WIN_2008" ) Then ; bcd boot configuration $boottype = "bcd" debugMsg ("BCD store config detected") EndIf EndIf EndIf ; At this point, we must have a systemdrive specified ; If not, bail out If ($systemdrive = "") Then errorMsg ("Boot configuration not found") Exit ($returncode) EndIf ; Perform action for boottype If ($boottype = "bcd") Then ; BCD bootloader debugMsg ("boottype is set to BCD") If ($restore = 1) Then ; If restore is specified... ; Restore BCD store from backup If (restoreBCD() = 0) Then errorMsg ("Unable to restore BCD store") Exit ($returncode) EndIf Else ; If restore is not specified... ; Check if a mode was selected If ($mode = "") Then displayHelp() Exit ($returncode) EndIf ; Backup BCD store If (backupBCD() = 0) Then errorMsg ("Could not backup BCD store") Exit ($returncode) EndIf ; Edit BCD store If (editBCD() = 0) Then errorMsg ("Could not edit active BCD store") Exit ($returncode) EndIf EndIf ElseIf ($boottype = "bootini") Then ; boot.ini bootloader debugMsg ("boottype is bootINI") Local $bootini = "" If ($systemdrive = "active") Then $bootini = $activesysdrive & "\boot.ini" Else $bootini = $systemdrive & "\boot.ini" EndIf ; If restore option was specified, restore boot configuration If ($restore = 1) Then ; If restore is specified... ; if bootini exists, unlock it If (FileExists($bootini)) Then If (unlockBootINI($bootini) = 0) Then errorMsg ("Could write to " & $bootini & ". File must be writable") Exit ($returncode) EndIf EndIf ; Restore bootINI from backup If (restoreBootINI($bootini) = 0) Then errorMsg ("Unable to restore " & $bootini) Exit ($returncode) EndIf ; Lock bootINI If (lockBootINI($bootini) = 0) Then errorMsg ("Could not write to " & $bootini & ". File must be writable") Exit ($returncode) EndIf Else ; If restore is not specified... ; Check if a mode was selected If ($mode = "") Then displayHelp() Exit ($returncode) EndIf ; Check that boot.ini file exists If (FileExists($bootini) = 0) Then errorMsg ("Could not locate boot.ini boot configuration") Exit ($returncode) EndIf ; Backup bootINI If (backupBootINI($bootini) = 0) Then errorMsg ("Could not backup " & $bootini) Exit ($returncode) EndIf ; Unlock bootINI so we can modify it If (unlockBootINI($bootini) = 0) Then errorMsg ("Could not open " & $bootini & ". Could not unlock boot.ini") Exit ($returncode) EndIf ; Edit bootINI If (editBootINI($bootini) = 0) Then errorMsg ("Could not write to " & $bootini) Exit ($returncode) EndIf ; Lock bootINI If (lockBootINI($bootini) = 0) Then errorMsg ("Could not write to " & $bootini & ". Could not lock boot.ini") Exit ($returncode) EndIf EndIf Else ; If boottype is unknown errorMsg ("Boot configuration not found on drive " & $systemdrive & ". Unable to get boot type") Exit ($returncode) EndIf ; Check whether to ask for reboot If ($autoreboot = 0) Then ; ask if we should reboot Local $action = "" Local $modemsg = "" Local $drive = "" If ($restore = 1) Then $action = "restored" $modemsg = "" ElseIf ($restore = 0) Then $action = "updated" $modemsg = "Mode: " & modeDesc($mode) EndIf If ($systemdrive = "active") Then $drive = $activesysdrive Else $drive = $systemdrive EndIf If ( MsgBox(4 + 32 + 256, "Reboot", "Boot configuration " & $action & " for drive " & $drive & @LF _ & $modemsg & @LF _ & "Reboot now?") = 6) Then reboot() EndIf ElseIf ($autoreboot = 1) Then ; reboot automatically reboot() EndIf ; Exit with status code Exit ($returncode) Func reboot() ; force reboot ; force hung apps to close Shutdown (2 + 4 + 16) EndFunc Func restoreBCD() ; return 1 = success ; return 0 = fail debugMsg ("Executing restoreBCD") Local $rc Local $backup = $activesysdrive & "\BCD_bootmode.bak" Local $runcmd = @ComSpec & " /c bcdedit.exe /import " & Chr(34) & $backup & Chr(34) ; if backup exists, restore it, then delete it If (FileExists($backup)) Then $rc = RunWait($runcmd, @ScriptDir, @SW_HIDE) If (@error) Then Return 0 FileDelete ($backup) Return 1 Else ; If backup BCD store doesn't exist, warn user warnMsg ("Backup BCD store " & $backup & " doesn't exist.") EndIf Return 0 EndFunc Func backupBCD() ; return 1 = success ; return 0 = fail debugMsg ("Executing backupBCD") Local $rc Local $backup = $activesysdrive & "\BCD_bootmode.bak" Local $runcmd = @ComSpec & " /c bcdedit.exe /export " & Chr(34) & $backup & Chr(34) ; If backup doesn't exist, create one If (FileExists($backup) = 0) Then $rc = RunWait($runcmd, @ScriptDir, @SW_HIDE) If (@error) Then Return 0 If (FileExists($backup) = 0) Then Return 0 EndIf Return 1 EndFunc Func editBCD() ; return 1 = success ; return 0 = fail ; edit active BCD store debugMsg ("Executing editBCD") Local $rc Local $bcdcmd = "" Local $pid = 0 Local $stdout = "" Local $stderr = "" Local $modedesc = "" Local $modeopts[4] Local $guid = "" Local $tmpstring ; Set BCD store elements $modeopts[0] = " device partition=" & $activesys32drive $modeopts[1] = " path " & Chr(34) & StringReplace (@SystemDir, $activesys32drive, "") & "\winload.exe" & Chr(34) $modeopts[2] = " osdevice partition=" & $activesysdrive $modeopts[3] = " systemroot " & Chr(34) & StringReplace (@WindowsDir, $activesysdrive, "") & Chr(34) ; Set mode options $modedesc = $predesc & modeDesc($mode) Switch (StringLower($mode)) Case "safemode" _ArrayAdd($modeopts, "SAFEBOOT Minimal") Case "safemodenetwork" _ArrayAdd($modeopts, "SAFEBOOT Network") Case "safemodecommand" _ArrayAdd($modeopts, "SAFEBOOT Minimal") _ArrayAdd($modeopts, "SAFEBOOTALTERNATESHELL YES") Case "bootlogging" _ArrayAdd($modeopts, "BOOTLOG YES") Case "vga" _ArrayAdd($modeopts, "VGA YES") Case "dsr" _ArrayAdd($modeopts, "SAFEBOOT DsRepair") Case "debug" _ArrayAdd($modeopts, "DEBUG YES") Case Else Return 0 EndSwitch ; Write to either "active" BCD store or other specified store If ($systemdrive = "active") Then ; If sysdrive is "active", search current BCD store for our entry $bcdcmd = @ComSpec & " /c bcdedit /enum ALL" $stdout = "" $stderr = "" If (execCommandStdOut ($bcdcmd, $stdout, $stderr) = 0) Then Return 0 $stdout = $stdout & $stderr ; If no output from command, bail out If ($stdout = "") Then Return 0 ; Search for entry we created. ; If not found, create a new BCD entry debugMsg ("Searching active BCD store for '" & $modedesc & "'") If (parseStdOut($stdout, $modedesc) = 0) Then ; If our entry is not found, add entry in BCD store debugMsg ("Creating new BCD entry '" & $modedesc & "' in active store") $bcdcmd = @ComSpec & " /c bcdedit /create /d " & Chr(34) & $modedesc & Chr(34) & " /application OSLOADER" $stdout = "" $stderr = "" If (execCommandStdOut ($bcdcmd, $stdout, $stderr) = 0) Then Return 0 $stdout = $stdout & $stderr ; Get GUID if it's returned from stdout $guid = getGUID($stdout) If ($guid = "") Then Return 0 debugMsg ("New GUID: " & $guid) ; Set element data for store as defined in $modeopts array For $x = 0 To (UBound ($modeopts, 1) - 1) $bcdcmd = @ComSpec & " /c bcdedit /set " & $guid & " " & $modeopts[$x] If (execCommand ($bcdcmd) = 0) Then removeBCDEntry($guid) Return 0 EndIf Next $bcdcmd = @ComSpec & " /c bcdedit /displayorder " & $guid & " /addlast" If (execCommand ($bcdcmd) = 0) Then removeBCDEntry($guid) Return 0 EndIf $bcdcmd = @ComSpec & " /c bcdedit /default " & $guid If (execCommand ($bcdcmd) = 0) Then removeBCDEntry($guid) Return 0 EndIf $bcdcmd = @ComSpec & " /c bcdedit /timeout " & $timeout If (execCommand ($bcdcmd) = 0) Then removeBCDEntry($guid) Return 0 EndIf Else ; If our mode entry is found... Return 1 EndIf Else ; This feature is not implemented yet. It will allow ; modifying boot data on a non-active drive with possiblity ; to make it active. ; If sysdrive is not "active", use that drive's BCD store ;debugMsg ("Creating new BCD entry in NON-active store") EndIf ; In debug mode, display new entry If ($debug > 0) Then $stdout = "" $stderr = "" $bcdcmd = @ComSpec & " /c bcdedit /enum " & $guid If (execCommandStdOut ($bcdcmd, $stdout, $stderr)) Then $stdout = $stdout & $stderr MsgBox (0, "BCD Boot Entry", "GUID: " & $guid & @LF & $stdout) EndIf EndIf Return 1 EndFunc Func restoreBootINI ($infile) ; return 1 = success ; return 0 = fail ; restore boot configuration from backup debugMsg ("Executing restoreBootINI") Local $backup = $infile & "_bootmode.bak" ; if $infile exists, unlock it If (FileExists($infile)) Then unlockBootINI ($infile) ; If backup exists, restore it If (FileExists($backup)) Then unlockBootINI ($backup) If (FileCopy($backup, $infile, 1)) Then FileDelete($backup) Return 1 EndIf Else ; If backup file doesn't exist, warn user warnMsg ("Backup file " & $backup & " doesn't exist.") EndIf Return 0 EndFunc Func backupBootINI ($infile) ; return 1 = success ; return 0 = fail debugMsg ("Executing backupBootINI") Local $backup = $infile & "_bootmode.bak" ; If backup doesn't exist, create one If (FileExists($backup) = 0) Then If (FileCopy($infile, $backup) = 0) Then Return 0 EndIf Return 1 EndFunc Func editBootINI ($infile) ; return 1 = success ; return 0 = fail debugMsg ("Executing editBootINI") Local $rc Local $default Local $modeopts = "" ; Read default entry from boot.ini $default = IniRead ( $infile, "boot loader", "default", "" ) If ($default = "") Then errorMsg ("Could not read default entry in " & $infile) Return 0 EndIf ; Check that default entry boots to a Windows installation If (isWinBootINIEntry($default) = 0) Then Return 0 ; Write timeout entry in boot.ini $rc = IniWrite ( $infile, "boot loader", "timeout", $timeout) If ($rc = 0) Then Return 0 ; Set mode options Switch (StringLower($mode)) Case "safemode" $modeopts = "/safeboot:minimal /sos /bootlog /noguiboot" Case "safemodenetwork" $modeopts = "/safeboot:network /sos /bootlog /noguiboot" Case "safemodecommand" $modeopts = "/safeboot:minimal(alternateshell) /sos /bootlog /noguiboot" Case "bootlogging" $modeopts = "/bootlog" Case "vga" $modeopts = "/basevideo" Case "dsr" $modeopts = "/safeboot:dsrepair /sos" Case "debug" $modeopts = "/debug" Case Else Return 0 EndSwitch $rc = IniWrite ( $infile, "operating systems", $default, Chr(34) & $predesc & modeDesc($mode) & Chr(34) & " " & $modeopts) If ($rc = 0) Then Return 0 ; return success Return 1 EndFunc Func unlockBootINI ($infile) ; return 1 = success ; return 0 = fail ; Unlocks a boot file by removeing hidden,readonly, and system permissions debugMsg ("Executing unlockBootINI") If (FileSetAttrib ($infile, "-RSH")) Then Return 1 Return 0 EndFunc Func lockBootINI ($infile) ; return 1 = success ; return 0 = fail ; Locks a boot file by adding hidden,readonly, and system permissions debugMsg ("Executing lockBootINI") If (FileSetAttrib ( $infile, "+RSH" )) Then Return 1 Return 0 EndFunc Func bootType ($indrive) ; determine boot type for a given drive ; return "bcd" = boot loader uses BCD store ; return "bootini" = boot loader uses boot.ini file debugMsg ("Executing bootType") Local $volumes = @ScriptDir & "\volinfo.txt" Local $partitions = @ScriptDir & "\partinfo.txt" Local $disks = @ScriptDir & "\diskinfo.txt" Local $searchstring = $activesys32drive & $activesys32root & "bcdedit.exe" ; Current environment must have 'bcdedit' available debugMsg ("Searching for: " & @LF & $searchstring) If (FileExists($searchstring)) Then debugMsg ("Found bcdedit at: " & $searchstring) ; Look for \Boot\BCD on selected drive If FileExists($indrive & "\Boot\BCD") Then debugMsg ("BCD store found on " & $indrive & "\Boot\BCD") Return "bcd" EndIf ElseIf ( FileExists($indrive & "\boot.ini") And _ FileGetSize ($indrive & "\boot.ini") > 0 ) Then Return "bootini" EndIf ; if no boot config was found, return empty string Return "" EndFunc Func modeDesc ($inmode) ; returns the description given a boot mode ; returns empty string if mode not recognized Switch (StringLower($inmode)) Case "normal" Return "Normal" Case "safemode" Return "Safe Mode" Case "safemodenetwork" Return "Safe Mode with Networking" Case "safemodecommand" Return "Safe Mode with Command Prompt" Case "bootlogging" Return "Enable Boot Logging" Case "vga" Return "Enable VGA Mode" Case "dsr" Return "Directory Services Restore Mode (Domain Controllers Only)" Case "debug" Return "Debugging Mode" EndSwitch Return "" EndFunc Func displayHelp () ; display program help message ; & " /sysdrive: = drive letter to modify boot config" & @CRLF _ Local $helpmsg $helpmsg = "BootModes.exe [options] mode" & @CRLF _ & " [options]:" & @CRLF _ & " /? = display this help message" & @CRLF _ & " /reboot = enable reboot" & @CRLF _ & " /noreboot = disable reboot" & @CRLF _ & " /restore = restore configuration" & @CRLF _ & " /noerror = disable error messages" & @CRLF _ & @CRLF _ & " mode: (choose one)" & @CRLF _ & " SafeMode = Safe Mode" & @CRLF _ & " SafeModeNetwork = Safe Mode with Networking" & @CRLF _ & " SafeModeCommand = Safe Mode with Command Prompt" & @CRLF _ & " BootLogging = Enable Boot Logging" & @CRLF _ & " VGA = Enable VGA Mode" & @CRLF _ & " DSR = Directory Services Restore Mode (Domain Controllers Only)" & @CRLF _ & " Debug = Debugging Mode" & @CRLF _ & @CRLF MsgBox (0, "Help", $helpmsg) EndFunc Func parseStdOut($instring, $insearch) ; return 1 = success ; return 0 = fail ; Search through $instring for $insearch debugMsg ("Executing parseStdOut") Local $lines ; Try to split output line-by-line using @LF or @CR $lines = StringSplit($instring, @LF, 1) If ($lines[0] = 1) Then $lines = StringSplit($instring, @LF, 1) If ($lines[0] = 1) Then $lines = StringSplit($instring, @CR, 1) For $x = 1 To $lines[0] If (StringInStr($lines[$x], $insearch, 0)) Then debugMsg("Parse found: " & @LF & $lines[$x]) Return 1 EndIf Next Return 0 EndFunc Func getGUID ($instring) ; return empty string = fail ; return = success ; return GUID from instring debugMsg ("Executing getGUID on string: " & @LF & $instring) Local $beginguid = 0 Local $endguid = 0 Local $rcguid = "" Local $curchar Local $guidlen = 0 For $x = 1 To StringLen($instring) $curchar = StringLower(StringMid($instring, $x, 1)) If ($curchar = "{" And $beginguid = 0) Then $rcguid = $rcguid & $curchar $beginguid = ($beginguid + 1) ElseIf ( (Int(Asc($curchar)) > 47 And Int(Asc($curchar)) < 58) Or _ (Int(Asc($curchar)) > 96 And Int(Asc($curchar)) < 123) Or _ $curchar = "-" ) Then If ($beginguid = 1 And $endguid = 0) Then $guidlen = ($guidlen + 1) $rcguid = $rcguid & $curchar EndIf ElseIf ($curchar = "}" And $endguid = 0) Then $rcguid = $rcguid & $curchar $endguid = ($endguid + 1) EndIf Next ; GUID must be at leaset 16 chars long If ( $beginguid = 1 And _ $endguid = 1 And _ $guidlen > 16 ) Then Return $rcguid EndIf Return "" EndFunc Func isWinBootINIEntry ($instring) ; return 1 = success ; return 0 = fail ; Checks given entry against Windows Boot.ini ARC format debugMsg ("Executing isWinBootINIEntry") Local $multi = "[mM][uU][lL][tT][iI]\(.*\)[dD][iI][sS][kK]\(.*\)[rR][dD][iI][sS][kK]\(.*\)[pP][aA][rR][tT][iI][tT][iI][oO][nN]\(.*\)\\.*" Local $scsi = "[sS][cC][sS][iI]\(.*\)[dD][iI][sS][kK]\(.*\)[rR][dD][iI][sS][kK]\(.*\)[pP][aA][rR][tT][iI][tT][iI][oO][nN]\(.*\)\\.*" Local $signature = "[sS][iI][gG][nN][aA][tT][uU][rR][eE]\(.*\)[dD][iI][sS][kK]\(.*\)[rR][dD][iI][sS][kK]\(.*\)[pP][aA][rR][tT][iI][tT][iI][oO][nN]\(.*\).*" If ( StringRegExp($instring, $multi, 0) Or _ StringRegExp($instring, $scsi, 0) Or _ StringRegExp($instring, $signature, 0) ) Then Return 1 EndIf Return 0 EndFunc Func debugMsg ($inmsg) If ($debug = 1) Then MsgBox (64, "Debug Message", $inmsg) EndIf EndFunc Func warnMsg ($inmsg) ; Only show warning message if $noerror = 0 If ($noerror = 0) Then MsgBox (48, "Warning", $inmsg) EndIf EndFunc Func errorMsg ($inmsg) ; Displayes error messages. ; If switch $noerror = 0, display message If ($noerror = 0) Then MsgBox (16, "Error", $inmsg) EndIf $returncode = 1 EndFunc Func execCommand ($incmd) ; return 1 = success ; return 0 = fail debugMsg ("execCommand: " & @LF & $incmd) Local $pid = 0 $pid = RunWait($incmd, @ScriptDir, @SW_HIDE) If (@error) Then debugMsg ("Command " & $incmd & " failed.") Return 0 EndIf Return 1 EndFunc Func execCommandStdOut ($incmd, ByRef $refstdout, ByRef $refstderr) ; return 1 = success ; return 0 = fail ; byref to $stdout and $stderr debugMsg ("execCommandStdOut: " & @LF & $incmd) Local $pid = 0 $pid = Run($incmd, @ScriptDir, @SW_HIDE, $STDOUT_CHILD + $STDERR_CHILD) If (@error) Then debugMsg ("Command " & $incmd & " failed.") Return 0 EndIf $refstdout = "" $refstderr = "" While (1) $refstdout = $refstdout & StdoutRead($pid) If @error Then ExitLoop WEnd While (1) $refstderr = $refstderr & StderrRead($pid) If @error Then ExitLoop WEnd Return 1 EndFunc Func removeBCDEntry ($inguid) ; return 1 = success ; return 0 = fail debugMsg ("Removing BCD entry " & @LF & $inguid) Local $pid = 0 Local $bcdcmd $bcdcmd = @ComSpec & " /c bcdedit /delete " & $inguid $pid = RunWait($bcdcmd, @ScriptDir, @SW_HIDE) If (@error) Then Return 0 If ($pid = 0) Then Return 0 Return 1 EndFunc