Missing value returned for some version numbers

Hi. I’ve written a script to gather all the version numbers of Library>Audio>Plug-ins>Components. The script works fine except with a number of plug-ins that contain extra text in the version field. So, instead of the version being “4.31” it’s “BC Chorus 4 AU(Mono) 4.31 by Blue Cat Audio (www.bluecataudio.com).” The script returns that field as “missing value.” Is there a way to force it to pull whatever has been entered in that field by the manufacturer? Script is:

set inputFolder to alias "Macintosh HD:Library:Audio:Plug-Ins:Components:"
tell application "Finder" to set foundComponents to files of entire contents of folder inputFolder
set masterList to ""
repeat with i in foundComponents
	set allText to i as alias
	set infoRecord to info for allText
	set verRecord to short version of infoRecord
	set fileName to name of infoRecord
	set filePlusVersion to fileName & ":" & tab & verRecord
	set masterList to masterList & "
" & filePlusVersion
end repeat
set the clipboard to masterList

Thanks!

Hello,

I believe plugins are mini-applications. Just like traditional apps they’re bundles and should normally contain the Info.plist file storing the version number. the short version property isn’t reliable so you have to reference the corresponding plist property. For that, you use the Mac shell command defaults.


try
do shell script "defaults read " & quoted form of POSIX path of [an object of class "alias", "file" or "POSIX file" which is the path to Info.plist inside the plugin's bundle] & " CFBundleShortVersionString"
end try

Replace the text in the brackets with an actual object reference to Info.plist.

Thank you scrutinizer82. I didn’t realize components were bundles. I’ve been playing with your script for a couple of days but haven’t been able to get it to work (I am a beginner with AppleScript). However, in trying to figure it out I did get this to work:

set thePListPath to POSIX path of ("Macintosh HD:Library:Audio:Plug-Ins:Components:BC Chorus 4 AU(Mono).component:Contents:Info.plist")
tell application "System Events"
    tell property list file thePListPath
        tell contents
            value of property list item "CFBundleVersion"
        end tell
    end tell
end tell[/AppleScript]

This returns the correct version number from the Info.plist file. I have been trying to integrate it into my repeat script. This is as close as I've got.


set theFolder to (“Macintosh HD:Library:Audio:Plug-Ins:Components:”)
tell application “Finder” to set foundComponents to files of entire contents of folder theFolder
set masterList to “”
repeat with i in foundComponents
set allText to i as alias
set infoRecord to info for allText
set thePath to allText & “Contents:Info.plist” as string
set thePListPath to POSIX path of (thePath)
set verRecord to the result of
tell application “System Events”
tell property list file thePListPath
tell contents
value of property list item “CFBundleVersion”
end tell
end tell
set fileName to name of infoRecord
set filePlusVersion to fileName & “:” & tab & verRecord
set masterList to masterList & "
" & filePlusVersion
end tell
end repeat



Of course, it doesn't work because setting a variable to a tell statement is incorrect. Do you know the correct way to do that?

Thank you!

property _Library:path to library folder from local domain
property AUComponentExt: "component"

tell application "Finder"
 set _Folder to alias (_Library as text & "Audio:Plug-Ins:Components:")
set locatedComponents to (items of entire contents of _Folder whose name extension is AUComponentExt) as alias list
end tell


set masterList to {}
repeat with _file in locatedComponents
set _PsxFile to  _file's contents's Posix path
set _Basename to do shell script "basename -s " & "." & AUComponentExt & space & quoted form of _PsxFile
set _ShortVersion to do shell script "/usr/libexec/PlistBuddy -c 'Print  :CFBundleShortVersionString'" & space & quoted form of (_PsxFile & "Contents/Info.plist")
set NewName to _BaseName & tab & "ver." & tab & _ShortVersion & "." & AUComponentExt

set end of masterList to NewName 

end repeat
return masterList

Is this what you wanted?

On a side note, why do you place a tabulator after the name? Generally, its recommended to follow the convention of avoiding spaces in file-names. I’d put a dash unless it’s on purpose you deem necessary.

Couple of notes. In script-writing, always seek brevity and clarity. This comes with experience. Variables are to be set to the result of an expression or explicitly declared values (numbers, texts, lists, records). If you want to set a variable to the result of a tell block then the result must be the value of the last expression of the tell block. Besides that, in this script I used my knowledge of some Unix commands that greatly simplify and reduce amount of the boring writing of supplemental expressions (sub-expressions called handlers in AppleScript jargon or routines, subroutines and functions in other programming languages). Read their manual pages by issuing commands

and

in Terminal to grasp the essence.

@Mchael007, I actually prefer (and admire) your approach in taking the effort to use System Events’s property list suite. It’s a small learning curve, but it allows you to manipulate property lists as AppleScript objects, which gives you the option of handling collections of property list items in a single command, or casting the entire file or any subset of its contents as a record—and this can also be manipulated, then used to the file if needed.

I made corrections to your script that hopefully will work towards what you were aiming for. I don’t actually have that particular folder or any of those components on my system, so there’s a chance my script may have a mistake in it somewhere that throws an error. If so, just describe what the error is, and it’ll likely just be a simple tweak. I would normally test before posting, but I’m reasonably familiar enough with property lists and System Events to be cavalier:

set componentsFolder to "Macintosh HD:Library:Audio:Plug-Ins:Components:"

tell application "System Events"
				set foundComponents to a reference to (items in the ¬
								folder componentsFolder whose name extension ¬
								is "component")
				
				set plistPaths to the path of file "Info.plist" in the ¬
								folder named "Contents" of the foundComponents
				
				set fileNamesAndVersions to the name of foundComponents
				
				repeat with plistPath in plistPaths
								tell the property list file named plistPath to get the ¬
												value of property list item "CFBundleVersion"
								
								tell fileNamesAndVersions to set string 1 ¬
												to [string 1, ":", tab, result]
				end repeat
end tell

set my text item delimiters to linefeed
fileNamesAndVersions as text

Some notes:

  1. You already brought in Finder to do some file operations, so it seems a bit left-field to then employ info for. It’s technically deprecated, but that means very little in the AppleScript world, however its performance is hit-and-miss.

  2. Since property lists are involved, I’ve used System Events for all the file operations as well. In fact, even if property lists weren’t involved, I’d still use System Events. In a strictly-vanilla AppleScript context, System Events is by far the most performant with the least impact on resources. Finder is slow, it blocks, and it’s very easy to script in a suboptimal way…

  3. …such as using entire contents. If you genuinely need to recurse through nested subfolders in /Library/Audio/Plug-Ins/Components/, let me know because I have assumed it’s a single-level directory containing *.component packages. If this isn’t the case, then the script above will not work correctly. I would avoid Finder’s entire contents like the plague if I were you, but if you do end up using it, do it sensibly: don’t let it return object references; grab the file info you need at the point of enumeration, so it returns what will mostly be lists of strings, e.g.

tell application "Finder" to tell the folder componentsFolder ¬
				to tell (a reference to its entire contents) ¬
				to tell (a reference to the file "Info.plist") ¬
				to return [its name, it as alias list]

That avoids horrid Finder references, and also leverages the fact that entire contents is a fully referenced collection that can, itself, be filtered before we evaluate. Hence, you can use it in this way to grab all the “Info.plist” files and their alias path specifiers in a single attack. But a slight misstep in the way one scripts entire contents, particularly in this example, will leave you beachballing and relaunching Finder—.component files are packaged folders, so it recurses into these, and if any of them contain a shortcut that leads elsewhere, it used to be the case that you could end up enumerating your entire hard drive (though I believe there are provisions that prevent this now).

  1. How the script above works :
    i) Grabs files in the componentsFolder by file extension (“component”);
    ii) Similar to the example with Finder, it grabs all “Info.plist” files from all “.component” packages at once;
    iii) It also lists the names of all “.component” packages as a starting point for what your script labelled as masterList (this script called fileNamesAndVersions
    iv) Sadly, couldn’t avoid a loop altogether, but it’s quick with two operations: read the property list item value for “CFBundleVersion”; then combine this with the associated filename in the manner you were doing, i.e. filename, colon, tab, version. Note the square brackets here are important, as these shield the contents of this list from the effects of text item delimiters.
    v) The list that encloses each filename and version set is, however, a regular list, so by setting text item delimiters to a linefeed character, and coercing the whole thing to a string, the result will (hopefully) be a formatted list of filename and version pairs.

Thank you very much, @scrutinizer82 and @CK. I appreciate both the scripting solutions and your very detailed and helpful explantations. I cobble together about one AppleScript per year and as soon as I feel like I’m getting it, it’s lost again.

@CK, this solution worked perfectly for me. Thank you! You’re correct that the Components folder is a single-level directory. I used “entire contents” only because that was what worked first for me when trying to build the script, and not knowing that the potential to never end existed. Same goes for combing Finder and System Events. I could get part of the process to work with one and another part with the other so just tried combining them. The only unusual result of your script is that the resulting list is in a seemingly random order - at least not one I could replicate in the folder itself in the Finder. Changing your last line to and adding:

set theList to fileNamesAndVersions
sortList(theList)

on sortList(theList)
	set theIndexList to {}
	set theSortedList to {}
	repeat (length of theList) times
		set theLowItem to ""
		repeat with a from 1 to (length of theList)
			if a is not in theIndexList then
				set theCurrentItem to item a of theList as text
				if theLowItem is "" then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				else if theCurrentItem comes before theLowItem then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				end if
			end if
		end repeat
		set end of theSortedList to theLowItem
		set end of theIndexList to theLowItemIndex
	end repeat
	return theSortedList
end sortList[/AppleScript]

worked (though now the list is comma delimited instead of return delimited - I still have to figure that out).

@scrutinizer82, yes that seems to be exactly what I was looking for, but for some reason I was unable to get your script to work again. I did try changing CFBundleShortVersionString to CFBundleVersion, which was what it appeared to be called when looking in the plist file, but that didn't help. It's okay because the other script did work for my situation. The tabulator after the name (do you mean "fileName & ":" & tab & verRecord"?) was just for display purposes.

Thanks!!
Michael

I updated it to correct a typo. Perhaps you ran an earlier version, that is, before my edit. Try running the script again. I tested it and it returned the required value in a matter of a second. True, I have only one object that’s an AU component in my system. As for the naming, I suppose “CFBundleShortVersionString” coexists with “CFBundleVersion” because it is read when queried.

I tested the script from @CK. It works well. To get the info as record:


set localLibraryPath to (path to library folder from local domain) as text
set audioComponentsPath to localLibraryPath & "Audio:Plug-Ins:Components"

set rawList to a reference to {}

tell application "System Events"
	set componentObjects to a reference to ¬
		(items of folder audioComponentsPath whose name extension is "component")
	repeat with component in componentObjects
		set theName to name of component
		set plistPath to path of file "Info.plist" of folder "Contents" of component
		set theVersion to value of property list item "CFBundleVersion" of property list file plistPath
		tell rawList to set {end, end} to {theName, theVersion}
	end repeat
end tell

set infoRecord to «class seld» of (record {«class usrf»:rawList} as record)

@scrutinizer82 - I tried the script again but get the error:

error “Finder got an error: AppleEvent timed out.” number -1712

@KniazidisR - I also received an error with your version of the script:

error “System Events got an error: Can’t get item 634 of folder "Macintosh HD:Library:Audio:Plug-Ins:Components" whose name extension = "component". Invalid index.” number -1719

I have almost 700 components in there, so maybe that’s the cause?

In the end, this is what gave me the list as needed:

set componentsFolder to "Macintosh HD:Library:Audio:Plug-Ins:Components:"

tell application "System Events"
	set foundComponents to a reference to (items in the ¬
		folder componentsFolder whose name extension ¬
		is "component")
	
	set plistPaths to the path of file "Info.plist" in the ¬
		folder named "Contents" of the foundComponents
	
	set fileNamesAndVersions to the name of foundComponents
	
	repeat with plistPath in plistPaths
		tell the property list file named plistPath to get the ¬
			value of property list item "CFBundleVersion"
		
		tell fileNamesAndVersions to set string 1 ¬
			to [string 1, ":", tab, result]
	end repeat
end tell

set my text item delimiters to linefeed
set theList to fileNamesAndVersions
sortList(theList)

on sortList(theList)
	set theIndexList to {}
	set theSortedList to {}
	repeat (length of theList) times
		set theLowItem to ""
		repeat with a from 1 to (length of theList)
			if a is not in theIndexList then
				set theCurrentItem to item a of theList as text
				if theLowItem is "" then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				else if theCurrentItem comes before theLowItem then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				end if
			end if
		end repeat
		set end of theSortedList to theLowItem
		set end of theIndexList to theLowItemIndex
	end repeat
	set {TID, text item delimiters} to {text item delimiters, return & return}
	set theResult to theSortedList as text
	set text item delimiters to TID
	return theResult
end sortList