Property values not saving with script

I have a few scripts which for some reason no longer save their values into the property variables when the script finishes.

I started noticing this with Yosemite I believe, but continue seeing it on El Capitan.

I first thought it was only something with my machine, as others I distribute the script to did not seem to have the issue. I migrated one user on El Capitan to see if it was related to the OS version, and his did save the value, so that made me think it was isolated to my machine. However, we recently purchased a new Mac that has El Capitan on it, and it has started showing the same issue. So I now have the same script used across 20-ish macs on 10.9 that do not have the issue, one mac on 10.11 that does not have the issue, and two macs with 10.11 that do have the issue.

If I tried to write a simple script from scratch to create a property, then write to it and end, it does not seem to be an issue.

I took one of the scripts with the issue and kept whittling it down to less code to see if I could get it down to a point where it would start working correctly again. I took the original script, opened a new document in Script Debugger and pasted in the code into the new document (in case it was something in the original app package), and then started deleting lines. I got about as far as I could go, down to:



property previousTesters : {"one"}
on run
	my publishBeta()
end run
on publishBeta()
	set allTesters to {"one", "two", "three"}
	set betaChoice to (choose from list allTesters default items previousTesters with prompt "Choose One" with title "Choose One" with multiple selections allowed)
	set previousTesters to betaChoice
end publishBeta



If I run this code in an open Script Debugger window, it works fine. However, when I save it as an application and run it, it does not keep the variable set within the script, and the list dialog will always show “one”.

If I take it to the El Capitan machine that is not seeing the issue, it works fine as an application.

Further confusing, if I hand type this entire code into a new document in Script Debugger, and save it as an application, THEN it does work fine as an application.

What could possibly be going on that the same code (one pasted into a new document and one hand typed into a new document) is working two different ways?

I can’t tell whether this will solve your problem, but you have a coding error in your script: the reply from choose from list is a list. Change to

	set betaChoice to 1st item of (choose from list allTesters default items previousTesters with prompt "Choose One" with title "Choose One" with multiple selections allowed)

@Alastor933

As the dialog use the “with multiple selections allowed” option, I’m not sure that it’s not a good idea to restrict the result to a single item.

Yvan KOENIG running El Capitan 10.11.3 in French (VALLAURIS, France) vendredi 19 février 2016 10:23:55

Hi,

properties in compiled scripts (.scpt, .scptd) persist as long as the script is not recompiled.

Unfortunately the common runner environments behave differently, some recompile scripts before running, some don’t.

To preserve property values reliably either save the script as application or use a dedicated script object within the script which can be saved on disk.

Do you mean only “one” is shown (in which case previousTesters is being used as the direct object of the ‘choose from list’ command) or that only “one” is shown as selected each time the script’s run?

The script works as I’d expect on my 10.11 system, with the user’s selection being used as the default selection on the following run. The exception to this is when the “Cancel” button’s clicked, since there’s no provision to catch the ‘false’ then uniquely returned by ‘choose from list’.

Or write a pref file in either AppleScript record format or pist using System Events.

on run
	my publishBeta()
end run

on publishBeta()
	set allTesters to {"one", "two", "three"}
	set betaChoice to (choose from list allTesters default items getPreviousTesters() with prompt "Choose One" with title "Choose One" with multiple selections allowed)
	setPreviousTesters(betaChoice)
end publishBeta

on getPreviousTesters()
	set prefFile to (path to preferences folder as string) & "com.mycompany.myscript.pref"
	try
		set fd to open for access file prefFile
		set rec to read fd as record
		close access fd
	on error
		close access file prefFile
		return {"one"}
	end try
	return previousTesters of rec
end getPreviousTesters

on setPreviousTesters(previousTesters)
	set prefFile to (path to preferences folder as string) & "com.mycompany.myscript.pref"
	try
		set fd to open for access file prefFile with write permission
		try
			set rec to read fd as record
		on error
			set rec to {} as record
		end try
		-- if there are any other values stored in the record keep them
		set rec to {previousTesters:previousTesters} & rec
		set eof of fd to 0
		write rec to fd as record
		close access fd
	on error
		close access file prefFile
	end try
end setPreviousTesters

Or even using DefaultsLib, so it’s included in the app/bundle’s standard plist file and you don’t have to do the file reading and writing:

use AppleScript version "2.3.1" -- supports 10.9+
use scripting additions
use defaultsLib : script "DefaultsLib" -- loads the lib
property defaultsObj : missing value

on run
	my setUpDefaults()
	my publishBeta()
end run

on publishBeta()
	set defaultChoices to defaultsObj's listForKey:"betaChoice"
	set allTesters to {"one", "two", "three"}
	set theResult to (choose from list allTesters default items defaultChoices with prompt "Choose One" with title "Choose One" with multiple selections allowed)
	defaultsObj's setObject:theResult forKey:"betaChoice"
end publishBeta

on setUpDefaults()
	if my id = missing value then error "This code works only in an applet or script bundle."
	if current application's id = my id then
		set theID to missing value
	else
		set theID to my id
	end if
	set my defaultsObj to defaultsLib's makeDefaultsWithID:theID factoryValues:{betaChoice:{"one"}}
end setUpDefaults

Hello Shane

I saved the script as a script bundle but when I run it - from Script Editor - I continue to get the warning : error “This code works only in an applet or script bundle.” number -2700 from «script» to item
It works when I run it from ASObjC Explorer.
If I save it as an application it works too.

Yvan KOENIG running El Capitan 10.11.3 in French (VALLAURIS, France) vendredi 19 février 2016 15:14:13

That is the expected behavior, yes. The user’s selection should be used as the default selection on the following run (including multiple selections). I stripped my example script down to almost the bare essential, to work as a demo - in the full deployed code I have a trap for the user cancelling, which I did not include here.

The problem is that in some cases this same code, saved and run as an application, does not behave as expected, and always shows “one” as the selected option when you run the code, no matter what you chose the previous time.

The code is saved as an application, and the issue happens with subsequent runs of the script (not recompiled by opening and saving the script between each run). I’m not sure I fully understand what you mean by common runner environments behave differently, but in this case the baseline is the same: the code is compiled and saved as an application once, and then run multiple times. Between each run the application is not retaining the property value between runs on some machines, as would be expected.

Thanks for the replies. I had already figured a few other ways of saving the info, and your suggestions add a few more I may try.

Even, so I’m still a bit bewildered why something that absolutely should be working is not.


Scott Dye

AppleScript runner environments are Script Editor, Script Debugger, Automator, ASObjC Explorer but also the stand alone Script Menu in the menu bar, FastScripts, Keyboard Maestro etc.

That’s what I thought you meant but wasn’t sure. thanks.

For the problem script, the variable does persist between runs while open in Script Debugger, but does not when run as an application.

It works from Script Editor, but not on an unsaved (or .scpt) document. You have to save as an applet or .scptd file first. The issue is that an unsaved document or .scpt file in SE has no Info.plist file, and therefore has no id – so the id returned would be that of SE, and then the value would be saved in SE’s prefs.

Explorer uses bundles as its native file type, so every document has an id.

Right – and that’s generally where it matters.

The main advantages are that there’s only one prefs file, if you have multiple values it’s quicker because the system looks after the actual saving, and you don’t need to do the updating all at once.

There have always been exceptions, and these days it’s more complicated. It never works on locked disks, or where there were permission limitations, for example. Now it can regularly fail if a script uses ASObjC because pointers can’t be saved. The confusing thing is that when it fails, it does so silently.

And a complication is that in some cases you don’t want it to work – for example, if your script uses GUI scripting, saving properties modifies the script bundle and means you get asked to approve it every time you run it. You also don’t want it to work if you are using codesigning.

The whole practice of modifying the executable every time it runs is well past its use-by date – it’s just that it hasn’t been replaced by something universal and simple yet.

Hi Shane

As I wrote, the script was saved as a script bundle when I got the reported error -2700.

At first, I ran it after saving it - but without closing it.
When I closed it and reopened it I got the error #-2700.

So I posted my message.
This morning, after reading your message I decided to retry.
Copy-paste the code in a new window. Save as bundle script (save defaults.scptd)
Ran it → error #-2700
Close the script then reopen and execute it → error #-2700
As I’m pig headed I decided to go further.
Close the Script Editor, restart it, reload the script bundle, execute it.
This time, at last, the script worked.
Good point but I really don’t understand why the process failed when I didn’t closed the editor and it really puzzle me.
I know that when we install a revised library we must restart the Editor but this time I didn’t install a new library. I just used one.
More, after getting the script running I decided to make a complementary test. The idea was that the first time the problem was that it was the first call to the library “DefaultsLib” in a session.
After getting a correct behavior, I copied-pasted one more time in a new window. Saved it as a script bundle. Closed it, reopened it and executed it. This time, the library was already used before. Alas, one more time, I got the error #-2700.
Not an annoying problem in real life, just a puzzling one.

Yvan KOENIG running El Capitan 10.11.3 in French (VALLAURIS, France) samedi 20 février 2016 11:38:21

It puzzles me too, because alas I can’t reproduce it. I presumed you hadn’t saved as a bundle.

I changed the language to English and rebooted assuming that the fact that I work in French may be the origin of the difference but nada. I got exactly the same behavior when I ran in English.
Of course, I’m back in French.

Yvan KOENIG running El Capitan 10.11.3 in French (VALLAURIS, France) samedi 20 février 2016 12:35:32

This thread has been helpful for me - thanks to all.It just bothered me that something just wasn’t working correctly, especially with no apparent consistency between machines. This thread has helped me move on.

I had already come up with alternative methods to “save” this info, but this thread is giving me other ideas to play with as well.

It also made me realize that all of my users are now on OS versions that will allow me to use some of the new features that started coming out around 10.9 - so now I can actually look into them!

Shane, one question on DefaultsLib - how do you get a applescript date into the defaults? I see where you have specific code to handle the setting of applescript dates in the library, but just adding (current date) doesn’t seem to work in the defaults line:


set my defaultsObj to defaultsLib's makeDefaultsWithID:theID factoryValues:{betaChoice:{"one"},nextUpdateCheck:(current date)}

I was able to enter a date after the fact, but it required me to have the default start blank and have a specific entry line for the starting date…but I assume there is a better way.

That’s right – you need to use registerDateDefault:forKey:. So after calling setUpDefaults() in the original I posed, you’d add something like:

  defaultsObj's registerDateDefault:(current date) forKey:"firstLaunchDate"

OK, now I’m lost again - maybe to the whole workings of the prefs files themselves.

I used the code from above earlier, then later added code to put in a date for _nextUpdateCheck. That worked.

Now, I thought that deleting the plist file itself would reset these values, but somehow they are sticking. If you look at the current version of my code, there is nothing left that actually sets the _nextUpdateCheck time (they are commented out), but when the plist file is recreated, it is in the XML. Where is it being stored? In the script? I thought that was the whole point to not have it save with the script…


use AppleScript version "2.3.1" -- supports 10.9+
use scripting additions
use defaultsLib : script "DefaultsLib" -- loads the lib
property defaultsObj : missing value

on run
	my setUpDefaults() -- use for clearing out to the defaults
	--defaultsObj's registerDateDefault:(current date) forKey:"firstLaunchDate" -- does this go here or inside setUpDefaults?
	
	my publishBeta()
	
	--defaultsObj's setDate:(current date) forKey:"_nextUpdateCheck" -- resets to current time
	--defaultsObj's setDate:((current date) + 4 * hours) forKey:"_nextUpdateCheck" -- sets delayed time
	
	--defaultsObj's setDate:(current date) forKey:"_nextUpdateCheck" -- resets to current time
	--display alert ((defaultsObj's dateForKey:"_nextUpdateCheck") as text)
end run

on publishBeta()
	set defaultChoices to defaultsObj's listForKey:"betaChoice"
	set allTesters to {"one", "two", "three"}
	set theResult to (choose from list allTesters default items defaultChoices with prompt "Choose One" with title "Choose One" with multiple selections allowed)
	defaultsObj's setObject:theResult forKey:"betaChoice"
end publishBeta

on setUpDefaults()
	if my id = missing value then error "This code works only in an applet or script bundle."
	if current application's id = my id then
		set theID to missing value
	else
		set theID to my id
	end if
	set my defaultsObj to defaultsLib's makeDefaultsWithID:theID factoryValues:{betaChoice:{"one"}, _nextUpdateCheck:"", firstLaunchDate:""}
end setUpDefaults


Question 1: How is this date value persisting when there is no plist file?
Question 2: In your previous reply, do I put the defaultsObj’s registerDateDefault:(current date) forKey:“firstLaunchDate” inside or outside the setUpDefaults routine?

The .plist file is an implementation detail. Apple could drop them or move them somewhere else (as they do with sandboxed apps) and the defaults system would still work.

When you address defaults like this, what happens is that your app talks to a system daemon, which keeps track of all this stuff. Periodically, at its own discretion and depending on how busy it is (in some versions of the OS it can take quite a while), this daemon writes it to a .plist file as a permanent record.

This is what makes it fast: your app doesn’t have to wait while stuff is saved to disk, and if it’s already been read, you get values returned much quicker.

But once you’ve launched an app, and the daemon has read the .plist file and loaded the values, what’s on disk is effectively irrelevant – it’s what the defaults system holds that counts. So throwing out .plist files will work if the defaults daemon has cleared its cache, but otherwise it won’t. And you have no obvious way of telling.

Whichever you prefer.