ANNOUNCE: SD Notary 2.0 (101) BETA

I’ve been pre-flighting and code signing my apps using SD Notary via a script, and I have a question. First, here’s the pre-flighting script. Question to follow

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use script "filemanagerlib"

on open folderList
	set dmgList to {}
	set submittedDMGs to {}
	repeat with thisFolder in folderList
		set thisDMG to PreFlightFolderMakeDMG(thisFolder)
		set the end of dmgList to thisDMG
		--	set submittedDMG to SubmitDMG(thisDMG)
		
	end repeat
end open

on PreFlightFolderMakeDMG(thisFolder)
	set sourceFolder to POSIX path of thisFolder
	set searchString to ".app"
	
	set appList to AppsInFolder(sourceFolder)
	set appletFilesFixed to FixAppletFiles(appList)
	
	set signingErrors to SignApps(appList)
	
	if signingErrors is {} then
		set folderInfo to (parse object sourceFolder)
		set folderName to name_stub of folderInfo
		set folderLocation to parent_folder_path of folderInfo
		set dmgPath to folderLocation & "/" & folderName & ".dmg"
		set newDMG to MakeDMGFromFolder(thisFolder, dmgPath, folderName)
		return newDMG
	else
		set AppleScript's text item delimiters to {return}
		set signingErrors to signingErrors as text
		--display dialog
		set buttonList to {¬
			("Cancel"), ¬
			("Send to BBEdit"), ¬
			("Okay") ¬
				}
		tell me to activate
		set DialogReply to display dialog ("An error occured. No DMG will be created") & return & ("Full error text is below") ¬
			with title ("Pre-flight Error") ¬
			default answer (signingErrors) ¬
			buttons buttonList ¬
			default button 2 ¬
			cancel button 1 ¬
			hidden answer false ¬
			giving up after 10 ¬
			with icon 2
		if button returned of DialogReply is item 2 of buttonList then
			
			tell application "BBEdit"
				activate
				make new window at beginning
				set text of window 1 to signingErrors
			end tell
			
		end if
	end if
end PreFlightFolderMakeDMG

on SubmitDMG(aDMG)
	tell application "SD Notary 2"
		set submittingDocument to make new document with properties {allow library loading:true, create disk image:true}
		tell submittingDocument
			
			try
				set notarizedDocument to submit app at aDMG
			on error errMsg number errNum
				--DisplayDialog Full
				set dialogText to "Notary Submission Error"
				set defaultAnswer to "Notary Submission Error number: " & errNum & return & return & errMsg
				set dialogButtons to {"Cancel", "Skip", "OK"}
				set defaultButton to 3
				set cancelButton to 1
				set dialogTitle to "Notary Submission Error"
				set dialogIcon to caution --caution --note	--stop
				set givingUpAfter to 60
				set DialogReply to ¬
					display dialog dialogText ¬
						default answer defaultAnswer ¬
						buttons dialogButtons ¬
						default button defaultButton ¬
						cancel button cancelButton ¬
						with title dialogTitle ¬
						with icon dialogIcon ¬
						giving up after givingUpAfter
				
				return errorMsg
				
			end try
			
		end tell
	end tell
	return notarizedDocument
end SubmitDMG

on FixAppletFiles(appList)
	local appPath
	set fixedAppletNames to {}
	repeat with thisApp in appList
		set appPath to thisApp as text
		set appPath to appPath & "/Contents/MacOS/"
		--Contents of
		set FilesInMacOS to objects of appPath ¬
			include folders false ¬
			include files true ¬
			result type files list
		if the (count of FilesInMacOS) > 1 then
			set appName to full_name of (parse object thisApp)
			repeat with thisFile in FilesInMacOS
				set fileName to full_name of (parse object thisFile)
				if fileName is not in {"applet", "fancyDroplet"} then
					set the end of fixedAppletNames to appName
					set fileRemoved to remove object thisFile
				end if
			end repeat
		end if
	end repeat
	return fixedAppletNames
end FixAppletFiles

on AppBundleExportsSettings(thisApp)
	
	tell application "Script Debugger"
		set thisDoc to open thisApp
		
		tell thisDoc
			set its properties to {codesign bundles too:true, autoincrement build number:true, codesign on export only:false, embed used frameworks:true, embed used libraries:true, copyright:"Copyright © 2022, 2023 Ed Stockly, All Rights Reserved", codesign identity:"Developer ID Application: Ed Stockly (2JJNKVL7R4)"}
			close saving yes
		end tell
		
	end tell
end AppBundleExportsSettings
 
 on SignApps(appList)
	set errorLog to {}
	local appPath
	repeat with thisApp in appList
		AppBundleExportsSettings(thisApp)
		set notaryError to false
		set appLocation to parent_folder_path of (parse object thisApp)
		--do shell script "codesign -dv " & quoted form of thisApp
		tell application "SD Notary 2"
			set codeSignDocument to make new document
			tell codeSignDocument
				try
					set appPath to codesign only at thisApp without showing dialogs
				on error errMsg number errNum
					tell me to activate
					set alertMessage to ("Error message: ") & return & return & ("The file:") & return & ¬
						thisApp & return & return & ("Could not be signed.") & ¬
						return & return & ("Error message:") & errMsg & return & return & "Error number: " & errNum
					set the end of errorLog to alertMessage
					display alert alertMessage giving up after 5
					set notaryError to true
					tell application "Finder" to activate
				end try
			end tell
			delay 1
			close codeSignDocument
		end tell
		if not notaryError then
			set signedAppInfo to (parse object appPath without HFS results)
			set appName to full_name of signedAppInfo
			set signedAppLocation to parent_folder_path of signedAppInfo
			set signedApp to move object appPath ¬
				to folder appLocation ¬
				with replacing and return path
			set folderRemoved to remove object signedAppLocation ¬
				with folders included
		end if
	end repeat
	return errorLog
end SignApps

on MakeDMGFromFolder(folderPath, dmgPath, folderName)
	local folderPath, dmgPath, folderName, dmgShellScript
	set folderPath to quoted form of POSIX path of folderPath
	set dmgPath to quoted form of POSIX path of dmgPath
	set folderName to quoted form of folderName
	
	set dmgShellScript to "hdiutil create -srcfolder " & folderPath & " -format UDZO -volname " & folderName & " " & dmgPath
	
	do shell script dmgShellScript
	return dmgPath
end MakeDMGFromFolder

on AppsInFolder(sourceFolder)
	set allFiles to objects of sourceFolder ¬
		searching subfolders true ¬
		include invisible items false ¬
		include folders false ¬
		include files true ¬
		result type paths list
	set foundApps to {}
	repeat with thisFile in allFiles
		set thisFile to thisFile as item
		set nameExtension to name_extension of (parse object thisFile)
		if nameExtension is "app" then set the end of foundApps to thisFile
	end repeat
	return foundApps
end AppsInFolder

The question comes with this handler. Basically, I want to automate the Application Bundle & Export settings.

This handler opens the applet in Script Debugger, and sets the Bundle and exports settings to the desired values, then saves the app.

The problem is after doing this, none of the script libraries are included in the bundle. (Don’t know about frameworks, because none of the ones I use would need to be bundled).

Any suggestions?

→ Script Debugger 8.0.5 (8A61)
→ MacOS 12.6.6
→ SD Notary 2 2.0 (102)

Note, I have stopped trying to submit the dmg via the script until some issues are resolved, so that command is noted out.

But this is pretty smooth. I just drop the folder containing the apps I want to sign and put in a DMG. After the preflight is done, SD Notary is left open and I just click the submit button and the navigation window is already in the directory containing the DMG.

Where are the libraries stored?

Mostly in the user script libraries folder, but a couple are in an app (your app!) in that folder.

I think I figured out what the issue is.

When I preflight, I’m not saving the document as run only, those only get bundled if it’s run-only. I’m not sure where in the code signing submission process they become run-only, but I think I need to do that myself before code signing, right?

If so, I may need to rethink my workflow. I’d hate to accidentally make an app run-only.

Yep. Note the subheading in the Bundle & Export dialog that says Export Only Options.

Saving as run-only doesn’t seem to work. But sometimes it displays a dialog that says it did work.

direct export command does work (bundles libraries, results in a read only app) but it does not return a result, and puts the exported file in a new folder in the same directory as the file with a different name.

Here’s a suggestion: If direct export returned the new path it would be easy to put the run-only file where I want, and remove the new folder. Instead I need to write a routine to get the name of the folder, to get the path of the run-only file inside the folder.

	tell application "Script Debugger"
		set thisDoc to open thisApp		
		set codesignBundlesToo to true
		set autoincrementBuildNumber to true
		set codesignOnExportOnly to false
		set embedUsedFrameworks to true
		set embedUsedLibraries to true
		set theCopyRight to "Copyright © 2022, 2023 Ed Stockly, All Rights Reserved"
		set codesignIdentity to "Developer ID Application: Ed Stockly (2JJNKVL7R4)"		
		set bundleExportProperties to {}
		set bundleExportProperties to bundleExportProperties & {codesign bundles too:codesignBundlesToo}
		set bundleExportProperties to bundleExportProperties & {codesign bundles too:codesignBundlesToo}
		set bundleExportProperties to bundleExportProperties & {autoincrement build number:autoincrementBuildNumber}
		set bundleExportProperties to bundleExportProperties & {codesign on export only:codesignOnExportOnly}
		set bundleExportProperties to bundleExportProperties & {embed used frameworks:embedUsedFrameworks}
		set bundleExportProperties to bundleExportProperties & {embed used libraries:embedUsedLibraries}
		set bundleExportProperties to bundleExportProperties & {copyright:theCopyRight}
		set bundleExportProperties to bundleExportProperties & {codesign identity:codesignIdentity}
		tell thisDoc
			set its properties to bundleExportProperties
			save in thisApp ¬
				with run only and bundle run only
			close saving no
		end tell
	end tell

Here’s a new version of the applet that Preflights, code signs and makes DMGs (ready to submit to Apple) from folders dropped on it. (Once the scripting of submissions using SD Notary is resolved it can be modified to do that too).

The makes a copy of the folder, then works only on the copy so no worry of overwriting your work.

Since the Script Debugger export command does not return a result, a script could keep going before the export is complete. So I added a repeat loop that waits until the file is found then continues.

I would suggest that command be modified to return a result (shouldn’t break pre-existing scripts, even this one).

Same thing with SD Notary. When it does it’s thing it leaves folders that we need to clean up after which makes it tricky to automate the process.

But, all that said, this is far easier to automate than earlier versions of SD Notary would have been. In fact I wanted to but didn’t even try.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use script "filemanagerlib"
property exportSlug : ""
on open folderList
	set dmgList to {}
	set submittedDMGs to {}
	set notarizeFolder to NotarizeFolderPath()
	repeat with thisFolder in folderList
		set folderName to full_name of (parse object thisFolder)
		set notarizeLocation to create folder at notarizeFolder ¬
			use name folderName & "(Run only - notarized)"
		
		set dupeFolder to copy object thisFolder ¬
			to folder notarizeLocation ¬
			replacing true ¬
			with return path
		set thisDMG to PreFlightFolderMakeDMG(dupeFolder)
		
		set the end of dmgList to thisDMG
		--	set submittedDMG to SubmitDMG(thisDMG)
		
	end repeat
end open

on NotarizeFolderPath()
	set myPath to path to me
	set notaryLocation to ¬
		parent_folder_path of (parse object myPath ¬
		with HFS results)
	set rightNow to current date
	set yearString to year of rightNow
	set monthString to TwoDigit(month of rightNow as integer)
	set dayString to TwoDigit(day of rightNow as integer)
	set exportSlug to " Export " & yearString & "-" & monthString & "-" & dayString
	return notaryLocation
end NotarizeFolderPath

on TwoDigit(aNumberString)
	if aNumberString < 10 then
		return "0" & (aNumberString as text)
	else
		return (aNumberString as text)
	end if
end TwoDigit

on PreFlightFolderMakeDMG(sourceFolder)
	--set sourceFolder to POSIX path of sourceFolder
	set searchString to ".app"
	
	set appList to AppsInFolder(sourceFolder)
	set appletFilesFixed to FixAppletFiles(appList)
	
	set signingErrors to SignApps(appList)
	
	if signingErrors is {} then
		set folderInfo to (parse object sourceFolder)
		set folderName to name_stub of folderInfo
		set folderLocation to parent_folder_path of folderInfo
		set dmgPath to folderLocation & "/" & folderName & ".dmg"
		set newDMG to MakeDMGFromFolder(sourceFolder, dmgPath, folderName)
		return newDMG
	else
		set AppleScript's text item delimiters to {return}
		set signingErrors to signingErrors as text
		--display dialog
		set buttonList to {¬
			("Cancel"), ¬
			("Send to BBEdit"), ¬
			("Okay") ¬
				}
		tell me to activate
		set DialogReply to display dialog ("An error occured. No DMG will be created") & return & ("Full error text is below") ¬
			with title ("Pre-flight Error") ¬
			default answer (signingErrors) ¬
			buttons buttonList ¬
			default button 2 ¬
			cancel button 1 ¬
			hidden answer false ¬
			giving up after 10 ¬
			with icon 2
		if button returned of DialogReply is item 2 of buttonList then
			
			tell application "BBEdit"
				activate
				make new window at beginning
				set text of window 1 to signingErrors
			end tell
			
		end if
	end if
end PreFlightFolderMakeDMG

on SubmitDMG(aDMG)
	tell application "SD Notary 2"
		set submittingDocument to make new document with properties {allow library loading:true, create disk image:true}
		tell submittingDocument
			
			try
				set notarizedDocument to submit app at aDMG
			on error errMsg number errNum
				--DisplayDialog Full
				set dialogText to "Notary Submission Error"
				set defaultAnswer to "Notary Submission Error number: " & errNum & return & return & errMsg
				set dialogButtons to {"Cancel", "Skip", "OK"}
				set defaultButton to 3
				set cancelButton to 1
				set dialogTitle to "Notary Submission Error"
				set dialogIcon to caution --caution --note	--stop
				set givingUpAfter to 60
				set DialogReply to ¬
					display dialog dialogText ¬
						default answer defaultAnswer ¬
						buttons dialogButtons ¬
						default button defaultButton ¬
						cancel button cancelButton ¬
						with title dialogTitle ¬
						with icon dialogIcon ¬
						giving up after givingUpAfter
				
				return errorMsg
				
			end try
			
		end tell
	end tell
	return notarizedDocument
end SubmitDMG

on FixAppletFiles(appList)
	local appPath
	set fixedAppletNames to {}
	repeat with thisApp in appList
		set appPath to thisApp as text
		set appPath to appPath & "/Contents/MacOS/"
		--Contents of
		set FilesInMacOS to objects of appPath ¬
			include folders false ¬
			include files true ¬
			result type files list
		if the (count of FilesInMacOS) > 1 then
			set appName to full_name of (parse object thisApp)
			repeat with thisFile in FilesInMacOS
				set fileName to full_name of (parse object thisFile)
				if fileName is not in {"applet", "fancyDroplet"} then
					set the end of fixedAppletNames to appName
					set fileRemoved to remove object thisFile
				end if
			end repeat
		end if
	end repeat
	return fixedAppletNames
end FixAppletFiles

on AppBundleExportsSettings(thisApp)
	local exportFolderPath, exportedAppPath
	
	--nameStub
	
	set appInfo to (parse object thisApp)
	set appName to name_stub of appInfo
	set appFullName to full_name of appInfo
	set appLocation to parent_folder_path of appInfo
	tell application "Script Debugger"
		set thisDoc to open thisApp
		set exportFolderName to appName & exportSlug
		set exportFolderPath to appLocation & "/" & exportFolderName & "/"
		set exportedAppPath to exportFolderPath & appFullName
		set codesignBundlesToo to true
		set autoincrementBuildNumber to true
		set codesignOnExportOnly to false
		set embedUsedFrameworks to true
		set embedUsedLibraries to true
		--set theCopyRight to ""
		--set codesignIdentity to ""
		
		set bundleExportProperties to {}
		set bundleExportProperties to bundleExportProperties & {codesign bundles too:codesignBundlesToo}
		set bundleExportProperties to bundleExportProperties & {codesign bundles too:codesignBundlesToo}
		set bundleExportProperties to bundleExportProperties & {autoincrement build number:autoincrementBuildNumber}
		set bundleExportProperties to bundleExportProperties & {codesign on export only:codesignOnExportOnly}
		set bundleExportProperties to bundleExportProperties & {embed used frameworks:embedUsedFrameworks}
		set bundleExportProperties to bundleExportProperties & {embed used libraries:embedUsedLibraries}
		--set bundleExportProperties to bundleExportProperties & {copyright:theCopyRight}
		--set bundleExportProperties to bundleExportProperties & {codesign identity:codesignIdentity}
		tell thisDoc
			set its properties to bundleExportProperties
			direct export
			close saving no
			repeat 10 times
				set folderIsThere to exists object exportFolderPath
				set runOnlyAppIsThere to exists object exportedAppPath
				if runOnlyAppIsThere then exit repeat
				delay 1
			end repeat
			if runOnlyAppIsThere then
				set finalApp to move object exportedAppPath ¬
					to folder appLocation ¬
					replacing true ¬
					with return path
				
				set folderDeleted to remove object exportFolderPath ¬
					with folders included
			end if
		end tell
	end tell
	return appLocation
end AppBundleExportsSettings

on SignApps(appList)
	set errorLog to {}
	local appPath
	repeat with thisApp in appList
		set notaryError to false
		set appLocation to AppBundleExportsSettings(thisApp)
		--do shell script "codesign -dv " & quoted form of thisApp
		tell application "SD Notary 2"
			set codeSignDocument to make new document
			tell codeSignDocument
				try
					set appPath to codesign only at thisApp without showing dialogs
				on error errMsg number errNum
					tell me to activate
					set alertMessage to ("Error message: ") & return & return & ("The file:") & return & ¬
						thisApp & return & return & ("Could not be signed.") & ¬
						return & return & ("Error message:") & errMsg & return & return & "Error number: " & errNum
					set the end of errorLog to alertMessage
					display alert alertMessage giving up after 5
					set notaryError to true
					tell application "Finder" to activate
				end try
			end tell
			delay 1
			close codeSignDocument
		end tell
		if not notaryError then
			set signedAppInfo to (parse object appPath without HFS results)
			set appName to full_name of signedAppInfo
			set signedAppLocation to parent_folder_path of signedAppInfo
			set signedApp to move object appPath ¬
				to folder appLocation ¬
				with replacing and return path
			set folderRemoved to remove object signedAppLocation ¬
				with folders included
		end if
	end repeat
	return errorLog
end SignApps

on MakeDMGFromFolder(folderPath, dmgPath, folderName)
	local folderPath, dmgPath, folderName, dmgShellScript
	if (exists object dmgPath) then
		remove object dmgPath
	end if
	set folderPath to quoted form of POSIX path of folderPath
	set dmgPath to quoted form of POSIX path of dmgPath
	set folderName to quoted form of folderName
	
	set dmgShellScript to "hdiutil create -srcfolder " & folderPath & " -format UDZO -volname " & folderName & " " & dmgPath
	
	do shell script dmgShellScript
	return dmgPath
end MakeDMGFromFolder

on AppsInFolder(sourceFolder)
	set allFiles to objects of sourceFolder ¬
		searching subfolders true ¬
		include invisible items false ¬
		include folders false ¬
		include files true ¬
		result type paths list
	set foundApps to {}
	repeat with thisFile in allFiles
		set thisFile to thisFile as item
		set nameExtension to name_extension of (parse object thisFile)
		if nameExtension is "app" then set the end of foundApps to thisFile
	end repeat
	return foundApps
end AppsInFolder

And here’s the applet in a DMG created by the applet, signed, notarized and stapled:
Notarizing Folder.dmg.zip (399.1 KB)

(Hey, can we ad “dmg” to files we can post here? Makes sense to me.

Thanks for picking that up – I’m logging it as a bug.

Also, when scripting an export, Script Debugger displays a dialog, which is fine in the UI but a pain when scripting.

Would it be possible to export without UI?

Typically what happens I’m preflighting a folder with a bunch of apps and each one opens and makes a few changes, then exports (bundling libraries). When it’s done that dialog appears. When the process is done there’s one app window left open with one dialog.

Also, I just had an annoying experience.

After preflighting, signing and submitting several apps (some repeatedly), I closed all SD windows and quit. When I launched SD later, all the windows from my previous session reopened, including all of the run-only export windows. And the run-only export windows opened in one window in tabs (probably over 100 of them).

When I tried to close each one I got that same dialog again (Buttons: Reveal and OKAY). If I tried to close the window the mac would make this buzzing screaching sound (I think trying to beep for each tab), and only one tab would show the close dialog.

So I had to individually close each tab (which required two clicks because command-w tried to close the window, and the OK button wasn’t activated.

After closing them all I quit script debugger and opened it again and did not get the windows.

I just prefilighted and submitted a couple more and quit and relaunched SD and it didn’t happen.

Anyone else get failures with notarytool (SD Notary 2) which have meaningless errors… but if I submit using the old SD Notary with altool it gives me a meaningful error which I can address (in this case I needed to sign with --timestamp the plugins in the DMG I was submitting).

Yes, unfortunately notarytool is not very helpful when something goes wrong.