3 Answers
3

You can't just place plain scripts in that folder. You need a "specialized bundle" how Apple calls it, basically a folder with your executable, and a .plist configuration. And you should put it in /Library/StartupItems since /System/Library/StartupItems/ is reserved for the operating system. Read all about it here:

specific to a given user ("[user-SPECIFIC]"); i.e., the installation must be performed for each user, if desired; scripts are typically stored in a user-specific location, and root (administrative) privileges are NOT required for installation.

effective for ALL users ("[ALL users]"); i.e., the installation takes effect for ALL users; scripts are typically stored in a shared location and root (administrative) privileges ARE required for installation.

The scripts themselves will run invisibly, but - with the exception of the com.apple.loginwindow login-hook method - you can open applications visibly from them; things to note:

There is no guarantee that any such application will be frontmost, so it may be obscured by other windows opened during login.

If you want to run another shell script visibly, simply use open /path/to/your-script, which will open it in Terminal.app; however, the Terminal window will automatically close when your script terminates.

Automator [user-SPECIFIC]:

File > New, type Application

Add a Run Shell Script action, which adds an embedded bash script, and either paste your script code there or add a command that invokes an existing script from there.

Save the *.app bundle and add it to the Login Items list in System Preferences > User & Groups > Login Items.

Note:

The embedded script runs with the default "C" locale.

$PATH is fixed to /usr/bin:/bin:/usr/sbin:/sbin, which notably does NOT include /usr/local/bin

If you have admin privileges, this is the easiest method, but it is DEPRECATED, for a variety of reasons (security, limited to a single, shared script, synchronous execution); Apple especially cautions against use of this mechanism as part of a software product.

Place your script, e.g., Test.sh, in a shared location - e.g., /Users/Shared - and make sure it is executable (chmod +x /Users/Shared/Test.sh).

The script will run as the root user, so exercise due caution.Among the methods listed here, this is the only way to run a script as root.

There's only one system-wide login hook.

Note that there's also a log-OUT hook, LogoutHook, which provides run-at-logout functionality - unlike the other approaches.

The login-hook script runs synchronously before other login actions, and should therefore be kept short.

Notably, it runs before the desktop is displayed; you cannot launch applications from the script, but you can create simple interactions via osascript and AppleScript snippets (e.g., osascript -e 'display dialog "Proceed?"'); however, any interactions block the login process.

The script runs in the context of the root user and he username of the user logging on is passed as the 1st argument to the script.

The script runs with the default "C" locale.

$PATH is fixed to /usr/bin:/bin:/usr/sbin:/sbin, which notably does NOT include /usr/local/bin

The working dir. is /.

launchd agents:

launchd-agent-executed scripts can be installed for a SPECIFIC user OR for ALL users - the latter requires administrative privileges.

While using launchd is Apple's preferred method, it's also the most cumbersome, as it requires creating a separate *.plist configuration file.
On the upside, you can install multiple scripts independently.

Note:

No specific timing or sequencing of launchd scripts is guaranteed; loosely speaking, they "run at the same time at login"; there is even no guaranteed timing between the user-specific and the all-user tasks.

The script runs with the default "C" locale.

$PATH is fixed to /usr/bin:/bin:/usr/sbin:/sbin, which notably does NOT include /usr/local/bin

The working dir. is / by default, but you can configure it via the .plist file - see below.

The script-file path must be specified as a full, literal path (e.g., /Users/jdoe/script.sh; notably , ~-prefixed paths do not work.

For a description of all keys that can be used in *.plist configuration files, see man launchd.plist.

Both user-specific and all-users tasks run as the current user (the user logging on).

launchd [user-SPECIFIC]:

Note: Lingon 3 ($5 as of early 2014) is a GUI application that facilitates the process below, but only for user-specific scripts.

The <!-- ... --> comments indicate the places to customize; you're free to choose a label, but it should be unique - ditto for the .plist filename; for simplicity, keep the label and the filename root the same.

From Terminal.app, run the following:

launchctl load ~/Library/LaunchAgents/LoginScripts.Test.plist

Note that, as a side effect, the script will execute right away. From that point on, the script will execute whenever the CURRENT user logs on.

It is not strictly necessary to run launchctl load -- since, by virtue of the file's location, it will be picked up automatically on next login -- but it's helpful for verifying that the file loads correctly.

The <!-- ... --> comments indicate the places to customize; you're free to choose a label, but it should be unique - ditto for the .plist filename; for simplicity, keep the label and the filename root the same.

Note that, as a side effect, the script will execute right away. From that point on, the script will execute whenever ANY user logs on.

It is not strictly necessary to run launchctl load -- since, by virtue of the file's location, it will be picked up automatically on next login -- but it's helpful for verifying that the file loads correctly.

While this is a great answer, the PATH info for Launch Agents may not be correct. I had to add /usr/local/bin/:/bin/:/usr/bin/. Those were not already included in the PATH available to the script - what @mklement0 terms the "fixed" path
– stonedauwgMar 21 at 18:00

Thanks, @stonedauwg, but from what I can tell the answer still applies as of macOS 10.14 - the PATH environment variable that launch agent-launched scripts see is fixed at /usr/bin:/bin:/usr/sbin:/sbin, which - as stated - doesn't include the commonly used /usr/local/bin dir. So, what is not correct?
– mklement0Mar 21 at 19:59

As i said, i had to add 3 paths total, 2 of which are paths you said are fixed, namely /usr/bin and /bin. My script complained abt not finding tools at those paths. Once I added those (plus /usr/local/bin as well, for other tools) it could see things at all 3 of those paths. My point is in my macOS 10.14 env, /usr/bin and /bin are NOT fixed - I had to add them explicitly. /usr/local/bin Im in agreement, that one is not fixed either
– stonedauwgMar 21 at 23:56

@stonedauwg: So, to be clear: you're sayin that the only PATH entries you saw when running via launchd were /usr/sbin:/sbin? From my personal experience, I'd say that's very unusual, and looking around the web, I see references to the same list of dirs. as in my answer (e.g., apple.stackexchange.com/a/284758/28668, serverfault.com/q/694439/176094). So, if you could explain why and when someone would see different paths - such as in your case - that would be helpful.
– mklement0Mar 22 at 1:22

1

You are correct. I was trying to use $PATH as part of the plist variable new path, which I found, from ServerFault articles, does NOT work. Variable expansion does not work in the launchd plists, so you need to specific all of the path yourself, basically forcing you to repeat what the default is plus more :(
– stonedauwgMar 22 at 17:27

Kudos for figuring out a way to run a script at login time as root. It's implied by the CLI's name, but just to state it explicitly: It is only for one-time runs of scripts. I suggest you state in your answer that this is a project of yours.
– mklement0Mar 11 '17 at 18:13