Scripts embedded in bundles - can I update their properties?

Hi all,
got a weird one for you. I’ve saved a script inside the Bundle Resources Scripts folder and given it a property that can be updated when the script is run. The idea is that it will remember the property for next time so the user only will have to specify the property once. It is saved as “TestInternalScript.scpt” and the content is:


property MyMessage : missing value
if MyMessage is missing value then
	display dialog "Create message" default answer ""
	set MyMessage to text returned of result
end if
display dialog MyMessage

The contents of my “envelope” script bundle which contains the above script (in its Resources/Scripts/ folder) is as follows:


set MyInternalScript to path to resource "Scripts/TestInternalScript.scpt"
run script MyInternalScript

It all works fine except that the property in the internal script DOES NOT get remembered after it runs. So every time I run the script bundle, I have to respecify the value of the message.

Why is the property not being saved?

If you want properties to be saved like that, you’ll have to use load script and store script. But it’s a really Bad Idea, IMO. Save the value somewhere else, like the preferences folder.

Oh dear, that’s what I was trying to avoid. This whole thing came about from a series of scripts that I want to run sequentially. So I put them all inside a script bundle. The reason I don’t want to load them is because there are many common variable names in the scripts and that will probably cause problems if I load them all into one script.

I just tried an alternative of using a script (not a bundle) to run the sequential scripts from a Finder folder instead of from inside a script bundle.


set PathToMe to path to me
tell application "Finder"
	set MyFolder to ((the container of PathToMe) as alias) as string
	set Script1 to (MyFolder & "Scripts:TestInternalScript.scpt") as alias
end tell

run script Script1

But this made no difference in terms of saving the properties. It seems that if you run a script from another script, the properties don’t get saved. I think I will just have to run the separate scripts once each to define their properties, then use a master script to run them sequentially after that.

That doesn’t matter because you load a script in a new script object, you don’t extend your script. The only thing to take into consideration are global variables who are explicitly defined as global. Scripts with the same property only loses scope but doesn’t lose inheritance. For local variables they have only scope to their handler (keep in mind AppleScript has an implicit run handler).

You can launch them using osascript on the command line (using a do shell script).

If you export your scripts as applets they can be launched instead of opened by SE so you can launch them by using the system standard AppleEvents. That includes telling the finder to open the applet, use the open command on the command line or use the AppleScript command reopen instead of run script.

Hi DJ Bazzie Wazzie, I’m reading Matt Neuburg’s Definitive Guide to AppleScript at the moment and have just finished the chapter on scope… but it looks like I need to re read that chapter! For now though, your suggestion about running apps by opening them with the Finder seems like the best solution for me. I tried it and not only does it work on apps saved in a Finder folder but it also works on an app saved in a script bundle. The properties can be defined and they do get saved. Thanks very much!

Some more feedback for those interested. I tried DJ Bazzie Wazzie’s suggestion and used the Finder to run the scripts. This worked OK in my test but in the real thing the main script did not wait for the Finder-initiated script to finish - it just continued with the next thing. So I went back to plan B and ran each of the individual scripts separately to define their properties, then used the main script to run them all sequentially via a “run script ScriptName”.

An interesting thing I found out is that if a script uses System Events to click a button or something, it will require authorisation in the Security and Privacy control panel of the System Preferences. Whenever this happens, the properties of the script do not get saved. I assume the reason for this is that saving the properties would modify the script and this would prompt Security and Privacy to demand that it be authorised again. So AppleScript decides not to save the properties in this case.

I’ve been fooling around with this today out of interest. The observations here are musings rather than actual recommendations.

If you use ‘run script’ to run a script directly from the file, the script can save any property changes it makes itself by resaving itself after the changes:


property MyMessage : missing value

if MyMessage is missing value then
	display dialog "Create message" default answer ""
	set MyMessage to text returned of result
	store script me in (path to me) replacing yes -- Get the script to resave itself.
end if
display dialog MyMessage

BUT this is a dangerous thing to do because you might one day forgetfully use it in a script for loading. In this case, ‘path to me’ would refer to the file of the main script and the loaded script would save itself there ” with obvious consequences for the main script.

A safer approach, as Shane suggested, would be to store the property values separately. For instance, the auxiliary script could itself be a bundle and contain a file or files containing the required values. In the following example, the script’s a bundle whose resource folder also contains a “Properties” folder and therein an empty file called “MyMessage.txt”.


set messageFile to (path to resource "MyMessage.txt" in directory "Properties")

if ((get eof messageFile) > 0) then
	set MyMessage to (read messageFile as «class utf8»)
else
	display dialog "Create message" default answer ""
	set MyMessage to text returned of result
	set fref to (open for access messageFile with write permission)
	try
		write MyMessage as «class utf8» to fref
	on error msg
		display dialog msg buttons {"OK"}
	end try
	close access fref
end if

display dialog MyMessage

This likewise only works when ‘run script’ is applied directly to the script file. If the script’s loaded into another script before running, it’ll error harmlessly unless the parent script has a similar resource of its own. And if the main script should happen to have a resource with the same name in a similar location, the worst that can happen is that the wrong resource will be overwritten. The main script itself won’t be.

A further step would be to require the subsidiary script to take a parameter. This would be used to pass its own alias to it, allowing it to work correctly either from the file or after loading into another script and making it harder to use wrongly. It wouldn’t prevent a determined bonehead from passing it the wrong alias, but if it was for your own use, it might serve the turn.


on run argv
	if ((argv's class is list) and ((count argv) is 1) and (class of item 1 of argv is alias)) then
		set myPath to item 1 of argv
		set messageFile to (path to resource "MyMessage.txt" in directory "Properties" in bundle myPath)
		
		if ((get eof messageFile) > 0) then
			set MyMessage to (read messageFile as «class utf8»)
		else
			display dialog "Create message" default answer ""
			set MyMessage to text returned of result
			set fref to (open for access messageFile with write permission)
			try
				write MyMessage as «class utf8» to fref
			on error msg
				display dialog msg buttons {"OK"}
			end try
			close access fref
		end if
		
		display dialog MyMessage
	else
		display alert "InternalScript 3 invoked with wrong syntax." message "Use:
run script <alias or script> with parameters {<the script's file alias>}" as critical buttons {"Stop"} default button 1 cancel button 1
	end if
end run

It might be invoked from the main script thus:


set MyInternalScriptFile to (path to resource "Scripts/TestInternalScript 3.scptd") -- Substitute the relevant script name.
run script MyInternalScriptFile with parameters {MyInternalScriptFile}

Or:


set MyInternalScriptFile to (path to resource "Scripts/TestInternalScript 3.scptd") -- Substitute the relevant script name.
set MyInternalScript to (load script MyInternalScriptFile)
run script MyInternalScript with parameters {MyInternalScriptFile}

Since the finder, dock, command line util open all uses the reopen event I thought they would wait until the handler is competed, my mistake. But here is the code I tested it with and it worked synchronous.

Export the applet as stay-open application (I have names it “myapplet”) and here is some example code:

on reopen argv
	display dialog "reopen"
	continue quit
end reopen

Then I call the handler like this:

tell application "myapplet" to reopen

Thanks Nigel and DJ Bazzie Wazzie for your generous considered replies. You have supplied me with so much info that I will need to take time to research it all!