2

I'm on Catalina 10.15.6.

Previously, you could write shell scripts that would set the boot volume and restart the Mac, either by using bless --mount X --setBoot or by using systemsetup -setstartupdisk, but neither of these commands seem to work now when System Integrity Protection is enabled.

There must be some way to automate this, since the System Preferences app is able to change your boot disk without SIP being disabled, you just need to provide an admin password. What mechanism does the pref pane use to set the bootup disk now? Can we invoke it in a shell script?

2
  • Off the top of my head, isn't the startup disk stored in nvram? Can you modify it with the nvram command?
    – benwiggy
    Aug 6, 2020 at 7:19
  • @benwiggy SIP status is also stored in nvram. :P When SIP is enabled, you can't change protected nvram values. May 4, 2021 at 15:58

3 Answers 3

2

The System Preferences app is able to change the startup disk because the application is signed with special, Apple-only entitlements that allow it to bypass System Integrity Protection (SIP). In order to grant those entitlements to your own application, you would need to disable AMFI, which would in turn require you to (wait for it...) disable SIP!

Many Apple apps contain these types of entitlements. Think about it—if all Apple software was confined by SIP, how could you install updates which modify system files?

This also points to a possible workaround. Because System Preferences is allowed to change the startup disk (bypassing a part of SIP), you could write a script which tells System Preferences to do everything for you. I've done this—many years ago, I wrote and used the below Applescript to quickly reboot into Bootcamp. Because it uses UI scripting, it may need tweaking to work on the latest versions of macOS, but it should function as a starting point.

tell application "System Events"
    try
        tell application "System Preferences"
            set current pane to pane id "com.apple.preference.startupdisk"
            activate
        end tell
        tell application process "System Preferences"
            delay 0.3
            tell window "Startup Disk"
                set lockFound to false
                repeat with x from 1 to number of buttons
                    if lockFound is false then
                        if title of button x is "Click the lock to make changes." then
                            click button x
                            set lockFound to true
                            repeat while title of button x is "Authenticating..."
                                delay 1
                            end repeat
                        else if title of button x is "Click the lock to prevent further changes." then
                            set lockFound to true
                        end if
                    end if
                end repeat
                click radio button "BOOTCAMP" of radio group 1 of scroll area 1 of group 1 of splitter group 1
                delay 0.4
                click button "Restart…"
                delay 0.3
                click button "Restart" of sheet 1
                return true
            end tell
        end tell
    on error
        delay 0.5
        tell application "System Preferences"
            if current pane is pane id "com.apple.preference.startupdisk" then quit
        end tell
        return false
    end try
end tell
2
  • The tell application "System Preferences" blocks do not belong within the tell application "System Events" block. They belong by themself, as they do not need System Events to tell the application what to do in this use case. Only the application process needs to be told what to do by System Events. May 4, 2021 at 16:01
  • @user3439894 Thank you, please feel free to make edits! Let me be clear that I haven't touched this code in more than five years, and I was pretty bad at Applescript back then. It probably needs adjustments for Big Sur as well since it's all UI scripting, which is inherently fragile. But, since I knew I had written code for what the OP wanted, I figured I should share it! May 4, 2021 at 16:11
1

The designers macOS pick which commands that are constrained by SIP. To be more precise, certain commands are given the ability to bypass the constrains of SIP. The bless command is not one of the commands that is contained by SIP. To use the command to set the default to boot would require disabling at least part of SIP.

An alternative would be to use a boot manager which can be configured from a macOS script or application. An example of such a boot manager is rEFInd. If installed in a EFI partition, then a password would be required to first mount the partition. If installed to a FAT of ExFAT partition then no password would be required, but would be less secure. The default_selection token can be use to choose the default operating system to boot. Typically, this token and parameters are stored in a file of your choosing. The name of this file is then given as the parameter to the include token stored in the refind.conf file.

2
  • Yes, bless does not work without disabling SIP. But the System Preferences app is able to change the boot disk without disabling SIP. How does the System Preferences app do it? As far as I know, everything that app does can be scripted somehow (for example, many settings can be changed with /usr/bin/defaults and you can do system updates with /usr/sbin/softwareupdate)
    – Karew
    Aug 11, 2020 at 20:20
  • When building a application or command with Xcode the proper credentials have to exist in the project to enable the executable to bypass the restrictions presented by SIP. This is not done for bless. For commands that can bypass SIP, Apple may publish the project and source code, but not the files containing the credentials. For example, you can build the fsck_hfs executable by downloading the source. However, your executable will not be able to bypass SIP, where as the one provided by macOS 10.15 can. Aug 12, 2020 at 18:45
1

The SIP was the first problem I've encountered on Big Sur. Turned it off looks like a bad idea. So I tried something like @Wowfunhappy suggested but with update to fit new OS ui and also add some additional functional. The second problem was target volumes list items doesn't have actions. Which make impossible to click on them via click or "click at" functions perhaps because of some new additional protections on Big Sur. Click with AST and other scripts also doesn't works due to new MacOS restrictions. The only way I found is using python click(but this leads to a slight delay while script selects target volume).

So here is a fully automated switching:

property targetVolume : "BOOTCAMP" # find name of required volume inside System Preference > Startup Disk
property passwordValue : "yourSystemPassword" # Can be empty

tell application "System Events"
    tell application "System Preferences"
        set current pane to pane id "com.apple.preference.startupdisk"
        activate
    end tell
    tell application process "System Preferences"
        tell window "Startup Disk"
            set volumePosition to {0, 0}
            set lockFound to false
            
            # Check if auth required
            set authButtonText to "Click the lock to make changes."
            if exists button authButtonText then
                click button authButtonText
                
                # Wait for auth modal
                set unlockButtonText to "Unlock"
                repeat
                    if (exists sheet 1) and (exists button unlockButtonText of sheet 1) then exit repeat
                end repeat
                
                # Autofill password if setted
                if passwordValue is not equal to "" then
                    set value of text field 1 of sheet 1 to passwordValue
                    click button unlockButtonText of sheet 1
                end if
                
                # Wait for auth success
                repeat
                    if exists button "Click the lock to prevent further changes." then exit repeat
                end repeat
            end if
            
            # Wait until loading volumes list
            repeat
                if exists group 1 of list 1 of scroll area 1 then exit repeat
            end repeat
            
            # Click on target volume (posible a slight delay because of shell script executing)
            repeat with m in (UI element of list 1 of scroll area 1)
                if (value of first static text of m = targetVolume) then
                    tell static text targetVolume of m
                        set volumePosition to position
                    end tell
                end if
            end repeat
            set volumePositionX to item 1 of volumePosition
            set volumePositionY to item 2 of volumePosition
            my customClick(volumePositionX, volumePositionY)
            
            click button "Restart…"
            
            # Wait for restart modal appears
            repeat
                if (exists sheet 1) and (exists value of first static text of sheet 1) then exit repeat
            end repeat
            
            click button "Restart" of sheet 1
        end tell
    end tell
end tell

# shell script to make click work on target volume
on customClick(x, y)
    do shell script " 

/usr/bin/python <<END

import sys

import time

from Quartz.CoreGraphics import * 

def mouseEvent(type, posx, posy):

          theEvent = CGEventCreateMouseEvent(None, type, (posx,posy), kCGMouseButtonLeft)

          CGEventPost(kCGHIDEventTap, theEvent)

def mousemove(posx,posy):

          mouseEvent(kCGEventMouseMoved, posx,posy);

def mouseclick(posx,posy):

          mouseEvent(kCGEventLeftMouseDown, posx,posy);

          mouseEvent(kCGEventLeftMouseUp, posx,posy);

ourEvent = CGEventCreate(None); 

currentpos=CGEventGetLocation(ourEvent);             # Save current mouse position

mouseclick(" & x & "," & y & ");

mousemove(int(currentpos.x),int(currentpos.y));      # Restore mouse position

END"
end customClick

on simpleEncryption(_str)
    set x to id of _str
    repeat with c in x
        set contents of c to c + 100
    end repeat
    return string id x
end simpleEncryption

on simpleDecryption(_str)
    set x to id of _str
    repeat with c in x
        set contents of c to c - 100
    end repeat
    return string id x
end simpleDecryption

You just need to change two properties targetVolume and passwordValue. Password can be empty and in that case you can provide it manually. Then just copy this script, paste it to the Script Editor and export via File -> Export -> file format - Application, select Run-only -> Save. You can do the same process for all systems you have, for example Big Sur 1, Big Sur 2, Bootcamp.

1
  • Disks can be selected with arrow buttons, that's what I've ended up doing (gist)
    – stek29
    Nov 2, 2021 at 1:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .