Detecting if a script is first run

I’m looking for a way to more gracefully handle the permission dialogs in Mojave/Catalina. Given that those dialogs happen upon the first run of the script, is there a way for my script to know that it is first run?

I’m wanting to have a firstRun() subroutine to initialize a few things, specifically to have each app being called out in the larger script to have some task in the firstRun(), so that all of the permission dialogs trigger immediately, instead of scattered through various runs of the script in the future.

Is there an official way to determine if it has been run?

Unless I am missing something, this is usually done by storing a folder or file at a known (to you) location.

Then when the script is run, it checks for this folder. If it is not there (on error) then

  1. Do all the things you wanted to do in your setup routine
  2. Create and store the folder indicating first run has happened

If it IS there, just return.
Of course, this is not perfect, because the user could kill your script between setting the folder and performing the tasks. Thus ideally this whole thing should be atomic - namely either it all gets done or none of it gets done.
I am not aware of a way in pure AppleScript to do this. But I know in “real” programming there are ways to “test and set” so as to do an atomic operation.

I am a complete amateur and beginner, but one method that I’ve used (it’s not “official” in any way) is to do something like this:

set firstRun to true
do shell script "defaults read org.myorg.myapp FirstRun"
on error
set firstRun to false
end try

Then, after you’ve done the tasks you want to perform on the first run, use

do shell script "defaults write org.myorg.myapp FirstRun true"

I’m writing this from memory, without looking at my actual code, and if this doesn’t actually work you can easily work out something similar.

Again, this is pure amateurism on my part, but I wrote it because I hadn’t seen anything else.

Whatever method you choose, it might be more useful to store a version number rather than a yes/no value, so you can re-check when you update the app.

If your app isn’t code-signed and doesn’t use AppleScriptObjC, you can probably use a simple property. Otherwise my PrefsStorageLib is an option:

use script "PrefsStorageLib" version "1.0.0"
use scripting additions

prepare storage for (path to me) default values {lastVersionRun:"0"}
set lastVersionRun to value for key "lastVersionRun"
considering numeric strings
	if (value for key "lastVersionRun") as text < my version then
		--first run
		assign value my version to key "lastVersionRun"
	end if
end considering

Thanks for the replies. I’ve done something similar to these in the past - my main script has a packager script that creates a firstRun file within the script bundle when I create a final version of a build, that is deleted after the initial setup() routine that runs if that file is there when the script is run by the user. That process requires a separate script though, that I don’t have for smaller scripts.

I had wondered if there was a way to patch in to whatever OSX uses to know to check in the first place, but I should know by now that is asking for too much.

All of these suggestions are great, and broke me of the one angle I was looking at this, so I appreciate it!

Shane, I really do like the approach of using the version number, though. That allows the setup() code to run for each new version, as I do now. It would also allow the various permission triggers to fire off only on the first run of each version, if I choose not to code sign.

For that matter, is all of this even necessary? I am using the following code at the beginning of the script to trigger security dialogs all at once:

	tell application "Finder" to count windows
	tell application "System Events" to count windows
	tell application "Safari" to count windows
	-- etc

Is there any significant runtime consequence in doing that EVERY time the script is run? If not I can just forego the whole firstRun construct and count windows even when it is not necessary. Or for that matter, would counting windows be faster than looking for the defaults info? Am I gaining anything by trying to only count windows on the first run?

It’s generally not a good idea to alter the bundle of an application if you can avoid it.

There is if you have a large number of windows :grinning:. But if the scripts doing a lot of work otherwise, it’s a cheap-and-simple solution.

OK, so over a year later I am finally at a point of implementing this…and running into weird trouble.

When I tried to incorporate the simple code (which works in a test app) from your example above into my main app code, it causes my main app to close immediately on launch. My code is essentially what is below (plus hundreds of lines, but this is the top level of each section). I’ve tracked it down to the ‘prepare storage’ line - if I comment that out the script runs, although it gives an error later when trying to set the value of course. I re-enable that line, and the app bounces once, and then stops.

I tried to put the prepare storage line at the beginning of the on run handler instead of in its own handler, and I thought it worked at least once that way…but then when I tried to replicate it it still crashes.

My script is built as a scptd from Big Sur running Script Debugger 7, and exported as a run-only code signed app (but not notarized). It has several bundled scripts and files, and is quite large, but it only chokes when that line is added.

use script "PrefsStorageLib" version "1.1.0" --
use scripting additions 

on beginScript()
    prepare storage for (path to me) default values {lastVersionRun:"0"} -- PrefsStorageLib: sets deafult if necessary; need to be in run and open handlers
end beginScript

on run
    considering numeric strings
        --set lastVersionRun to value for key "lastVersionRun"
        if my version > (value for key "lastVersionRun") as text then -- PrefsStorageLib: gets key value
            --first run
            display alert "First Run"
        end if
    end considering
    assign value my version to key "lastVersionRun" -- always update prefs with last version; 
end run

on open theFiles
end open

Any ideas of what could be at issue?

Try adding a write lock parameter with a value of false.

Where do I add that?

prepare storage for (path to me) default values {lastVersionRun:"0"} without write lock

OK, that seems to work…sporadically?

I put the code in, exported, and it one-bounced. I tried to double click the script a second time and it worked that time, and triggered the defaults, and the code to set the latest version. Confirmed with the plist file.

So I went to run it again to see it work as NOT first-run. The first launch only one-bounced, but the second one seemed to work. Now, though, it continues to one-bounce.

Try some cave-man debugging by adding display dialogs, to try to see where it’s failing.

So now if gets weirder - the prepare storage line is really the first line, very similar to the example code posted. Before adding any caveman debugging, I did find that the app will very occasionally run - maybe 1 out of 10 times it will get past the first bounce and run, but most times it will only one-bounce.

When I tried to add display alert “Yo” as the next line, SD7 crashed. If I commented out the prepare storage line, then the display dialog line compiled. I uncommented the prepare storage line and got some kind of “scripting component” error (sorry I hit the enter button accidentally). So having trouble adding caveman deugging with that line enabled.

I was able to get the script to add a display alert line below the prepare storage line, and got to save via SD8 beta. When it only one-bounces, it does not display the alert, even though it would only be the fourth line of code to fire effectively. The sporadic times it does show the alert match the times it would continue the entire script. So it seems somehow related to that prepare storage line at the beginning of the script? Maybe? commenting and uncommenting that line is what starts it to one-bounce, and now in cases to crash the script.

I am unfortunately new to using ‘use scripting additions’ in this script, which I had to add to use the library. Is there a chance some conflict in code in a tell block further down would cause it to stall before the fourth line alert loads, even if it never gets to that code?

Don’t know why I didn’t do it earlier, but if I put a dialog BEFORE the prepare storage line, it does fire off, but not the dialog after the prepare storage line - so the script is bailing at the prepare storage line - confirmed.

As mentioned, if I keep launching the app enough times, it will occasionally get past that line and progress with the rest of the script - but very sporadically. If I comment out the prepare line, it runs every time - but then errors when setting a value since the prepare wasn’t done…but it is somehow related to the prepare line. I just can’t figure out how. I have restarted the computer a couple times in the process as well.

So I modified your script as follows:

on beginScript() 
	display alert "Starting"
	prepare storage for (path to me) default values {lastVersionRun:"3"} without write lock
	set x to value for key "lastVersionRun"
	display alert x
	set x to ((x as integer) + 1) as text
	assign value x to key "lastVersionRun"
	display alert x

And it behaved fine (SD8, 10.15.7), both in SD and as an exported applet. It is, however, very big — too big to run in debug mode. And it won’t run much beyond that, because I don’t have the appropriate set-up.

In SD8 I had to comment out a few lines of code addressing an app not on that Mac, including a line that called system info inside an application tell block, which is not a good idea. It was only a half-dozen lines I had to comment, but I wonder whether you’re hitting AS’s limits.