Getting Installed programs and version number

I’m attempting to retrieve the list of installed programs along with their version numbers, but my current approach has been unsuccessful. If anyone has a working script for this task, I would greatly appreciate your help.

System: macOS 13.2.1

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

set appList to {}
set appVersionList to {}

set appData to do shell script "system_profiler SPApplicationsDataType"
set appLines to paragraphs of appData

repeat with appLine in appLines
	if appLine starts with "      " then
		set {appName, appVersion} to my parseAppLine(appLine)
		set end of appList to appName
		set end of appVersionList to appVersion
	end if
end repeat

set text item delimiters to {", ", return}
set appInfoList to {}

repeat with i from 1 to count of appList
	set appName to item i of appList
	set appVersion to item i of appVersionList
	set end of appInfoList to appName & " (" & appVersion & ")"
end repeat

display dialog "Installed Applications:" & return & return & appInfoList as text

set inputString to "et Info String: 24.1.0 (20221206.r.166 be4691b)"
set versionStart to offset of "(" in inputString
set versionEnd to offset of ")" in inputString
if versionStart > 0 and versionEnd > versionStart then
	set versionNumber to text (versionStart + 1) thru (versionEnd - 1) of inputString
else
	set versionNumber to "unknown"
end if
1 Like

A more efficient way than parsing the data with text item delimiters is to read the data as property list and parse it with NSPropertyListSerialization.

Then it’s pretty easy to extract the values for name and version with the key value coding API dictionaryWithValuesForKeys

use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

set plistString to do shell script "system_profiler -xml SPApplicationsDataType"
set dataString to current application's NSString's stringWithString:plistString
set plistData to dataString's dataUsingEncoding:4
set propertyList to current application's NSPropertyListSerialization's propertyListWithData:plistData options:0 format:(missing value) |error|:(missing value)
set allItems to (propertyList's objectAtIndex:0)'s objectForKey:"_items"
set nameAndVersion to allItems's dictionaryWithValuesForKeys:{"_name", "version"}
set appList to (nameAndVersion's objectForKey:"_name") as list
set appVersionList to (nameAndVersion's objectForKey:"version") as list
2 Likes

Thanks Stefan for the above script, I added few things to your script. This works for my needs

Script
-------------------------------------------------------------------------------------------
# Author	:  Jack
# Created	:  2023/03/29 21:29
# Modified	:  
# System	:  macOS 13.3
# Purpose	:  Exports the installed Apps with the option to avoid Apple Apps
# Url		: https://www.macscripter.net/t/getting-installed-programs-and-version-number/74399
-------------------------------------------------------------------------------------------


use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

set plistString to do shell script "system_profiler -xml SPApplicationsDataType"
set dataString to current application's NSString's stringWithString:plistString
set plistData to dataString's dataUsingEncoding:4
set propertyList to current application's NSPropertyListSerialization's propertyListWithData:plistData options:0 format:(missing value) |error|:(missing value)
set allItems to (propertyList's objectAtIndex:0)'s objectForKey:"_items"
set nameAndVersion to allItems's dictionaryWithValuesForKeys:{"_name", "version", "path"}
set appList to (nameAndVersion's objectForKey:"_name") as list
set appVersionList to (nameAndVersion's objectForKey:"version") as list
set appPathList to (nameAndVersion's objectForKey:"path") as list
set dateStamp to (do shell script "date \"+%Y-%m-%d-%H-%M\"")
set desktopPath to (path to desktop as text)

-- List
set myList to {"All Installed Apps", "Avoid Apple Apps"}
set chosenItem to choose from list myList with prompt "Options"
if chosenItem is false then
	display dialog "No item selected."
else
	if chosenItem's first item = "All Installed Apps" then
		
		set theFilePath to desktopPath & "All Installed Apps " & dateStamp & ".txt"
		set theFile to open for access theFilePath with write permission
		
		repeat with i from 1 to count of appList
			set appName to item i of appList
			set appVersion to item i of appVersionList
			log appName & " " & appVersion
			write (appName & " " & appVersion & return) to theFile starting at eof
		end repeat
		close access theFile
		
		display dialog "All installed apps saved to Desktop"
		
	else if chosenItem's first item = "Avoid Apple Apps" then
		
		set theFilePath to desktopPath & "All Installed Apps without Apple Apps " & dateStamp & ".txt"
		set theFile to (open for access theFilePath with write permission)
		
		repeat with i from 1 to count of appList
			set appName to item i of appList
			set appVersion to item i of appVersionList
			set appPath to item i of appPathList
			set bundleID to do shell script "mdls -name kMDItemCFBundleIdentifier -r \"" & appPath & "\""
			
			if bundleID does not start with "com.apple." then -- Exclude com.apple. bundle IDs
				log appName & " " & appVersion & " " & bundleID
				-- write (appName & " " & appVersion & " " & bundleID & return) to theFile starting at eof
				write (appName & " " & appVersion & return) to theFile starting at eof
				
			end if
		end repeat
		close access theFile
		display dialog "Avoided System Apps and saved to Desktop"
		
	end if
end if

use framework "Foundation"
use script "FileManagerLib" version "2.3.5"
use script "List" version "1.0"
use script "Myriad Tables Lib" version "1.0.12"
use script "PrefsStorageLib" version "1.1.0"
use scripting additions
property lastAppPosition : {30, 30, 600, 600}
property lastLibPosition : {30, 30, 600, 600}

property appsInfo : {}
property libsInfo : {}
property allLibs : true
property allApps : true

property selectedLibs : {}
property selectedApps : {}
property selectedAppRows : {}
property selectedLibRows : {}
PersistentVariables()
--StorePersistentValues()
--RetreiveStoredValues()
set AppleScript's text item delimiters to {" ", ")"}
set sysVer to (current application's NSProcessInfo's processInfo()'s operatingSystemVersionString()) as text
set sysVer to text items of (sysVer as text)
set {sysVer, buildNum} to ({item 2 of sysVer, item 4 of sysVer})
set sysVer to {"-->  Mac OS Version " & sysVer & " (" & buildNum & ")" as text}
set AppleScript's text item delimiters to {""}

repeat with x from 1 to count of appsInfo
	set {appName, appVersion, appBuild, appBundleID, appScriptable} to item x of appsInfo
	set {appName, appVersion, appBuild} to GetappsInfo(appBundleID)
	set item x of appsInfo to {appName, appVersion, appBuild, appBundleID, appScriptable}
end repeat

set tableData to appsInfo
set tableTitle to "Application Info"
set addAppButton to "Add Applications"
set tablePrompt to "Select which apps to copy version build info"
set columnHeadings to {"Name", "Version", "Build", "Bundle ID", "Scriptable?"}
set selectedAppRows to GenerateSelectedRowNumbers(tableData, selectedApps)
set {lastAppPosition, selectedApps, selectedAppRows, appsInfo} to DisplayTable(tableData, tableTitle, tablePrompt, columnHeadings, lastAppPosition, selectedAppRows, addAppButton)
StorePersistentValues()
set versionExportText to {sysVer}
if selectedApps is not {} then
	set the end of versionExportText to {"", "--Applications"}
	repeat with thisApp in selectedApps
		set {appName, appVersion, appBuild} to thisApp
		set appString to " --> " & appName & " " & appVersion & " (" & appBuild & ")"
		set the end of versionExportText to appString
	end repeat
end if

set libsInfo to AllLibVersions()
set tableData to libsInfo
set addLibButton to "Add Script Libararies"
set tableTitle to "Script Library Versions"
set tablePrompt to "Select which libraries to copy version info"
set columnHeadings to {"Script Library Folder", "Name", "Version"}
set selectedLibRows to GenerateSelectedRowNumbers(tableData, selectedLibs)
set {lastLibPosition, selectedLibs, selectedLibRows, libsInfo} to DisplayTable(tableData, tableTitle, tablePrompt, columnHeadings, lastLibPosition, selectedLibRows, addLibButton)
StorePersistentValues()

if selectedLibs is not {} then
	set the end of versionExportText to return & "--Script Libraries"
	
	repeat with thisLib in selectedLibs
		set {libFolder, libName, libVersion} to thisLib
		set libString to " --> " & libName & " " & libVersion
		set the end of versionExportText to libString
	end repeat
end if
set AppleScript's text item delimiters to {return}
set versionExportText to versionExportText as text
--display dialog
set buttonList to {¬
	("Done"), ¬
	("Change"), ¬
	("Copy") ¬
		}
set DialogReply to display dialog ("Select Copy to send versions to the clipboard" & return & return & versionExportText) ¬
	with title ("App and Script Library versions") ¬
	buttons buttonList ¬
	default button 3 ¬
	cancel button 1 ¬
	giving up after 60 ¬
	with icon note

if button returned of DialogReply is "Copy" then set the clipboard to versionExportText

on AllLibVersions()
	
	set saveTID to AppleScript's text item delimiters
	set userDLibraryFolder to path to library folder ¬
		from user domain ¬
		as text
	set systemDLibraryFolder to path to library folder ¬
		from system domain ¬
		as text
	--tell application "Finder" to open item systemDLibraryFolder
	set libVersions to LibFolderVersions(userDLibraryFolder, "Scripts Library - User")
	set libVersions to libVersions & LibFolderVersions(systemDLibraryFolder, "Scripts Library - System")
	--set libVersions to my SortLibVersions(libVersions)
	return libVersions
end AllLibVersions

on DisplayTable(tableData, tableTitle, tablePrompt, columnHeadings, lastTablePosition, selectedRows, extraButtonName)
	set multipleSelectionsAllowed to true
	set canAddAndDelete to false
	set editableColumns to missing value -- Number List
	set rowNumbering to true
	set emptySelectionAllowed to true
	set rowTemplate to {item 1 of appsInfo}
	set multipleLinesAllowed to true
	set doubleclickMeansOK to true
	
	set newTable to make new table with data tableData ¬
		with title tableTitle ¬
		with prompt tablePrompt ¬
		multiple selections allowed multipleSelectionsAllowed ¬
		can add and delete canAddAndDelete ¬
		editable columns editableColumns ¬
		column headings columnHeadings ¬
		row numbering rowNumbering ¬
		initially selected rows selectedRows ¬
		empty selection allowed emptySelectionAllowed ¬
		multiple lines allowed multipleLinesAllowed ¬
		double click means OK doubleclickMeansOK
	
	
	set OKButtonName to "OKAY"
	set OKButtonIsDefault to true
	set cancelButtonName to "Cancel"
	set gridStyle to grid both --grid between columns -- grid between rows; grid between rows dashed;; grid both dashed between rows; between rows and columns grid none
	
	
	set highlightedRows to {}
	set alternateBackgrounds to true
	set rowDragging to false
	set columnReordering to false
	set hiddenCancelButton to false
	
	--modify columns in table newTable ¬
	--	sort method sort none ¬
	
	modify table newTable ¬
		OK button name OKButtonName ¬
		OK button is default OKButtonIsDefault ¬
		cancel button name cancelButtonName ¬
		extra button name extraButtonName ¬
		grid style gridStyle ¬
		highlighted rows highlightedRows ¬
		alternate backgrounds alternateBackgrounds ¬
		row dragging rowDragging ¬
		column reordering columnReordering ¬
		hidden cancel button hiddenCancelButton ¬
		initial position lastTablePosition
	
	set extendedResults to true
	
	set tableResult to display table newTable ¬
		extended results extendedResults
	tell tableResult
		set lastPosition to its final position
		set valuesSelected to its values selected
		set selectedRows to its rows selected
		set valuesReturned to its values returned
	end tell
	
	return {lastPosition, valuesSelected, selectedRows, valuesReturned}
end DisplayTable

on GetApps(appNames)
	set infoForApps to {}
	tell application "System Events" to set allProcesses to processes whose background only is false
	repeat with thisProcess in allProcesses
		tell application "System Events"
			tell thisProcess
				
				properties
				set appBundleID to bundle identifier
				--	set appFile to path of application file
				set appScriptable to has scripting terminology
			end tell
		end tell
		set {fullAppName, appVer, appBundleVer} to GetappsInfo(appBundleID)
		if fullAppName is in appNames then
			set the end of infoForApps to {¬
				fullAppName, ¬
				appVer, ¬
				appBundleVer, ¬
				appBundleID, ¬
				appScriptable ¬
					}
		end if
	end repeat
	return infoForApps
end GetApps

on GetappsInfo(appBundleID)
	set appNSURL to current application's NSWorkspace's sharedWorkspace()'s URLForApplicationWithBundleIdentifier:appBundleID
	set appBundle to current application's NSBundle's bundleWithURL:appNSURL
	set fullAppName to (appBundle's infoDictionary()'s objectForKey:"CFBundleName") as text
	set appVer to (appBundle's infoDictionary()'s objectForKey:"CFBundleShortVersionString") as text
	set appBundleVer to (appBundle's infoDictionary()'s objectForKey:"CFBundleVersion") as text
	return {fullAppName, appVer, appBundleVer}
end GetappsInfo

on getLibFilesFromFolder(aFolder, source)
	--set LibApp to (aFolder as text) & "Shane's Script Library Pack.app:Contents:Library:Script Libraries:"
	aFolder
	try
		
		set foundFiles to objects of aFolder as text ¬
			searching subfolders false ¬
			include invisible items false ¬
			include folders false ¬
			include files true ¬
			result type files list
	on error errText number errNum
		set errorString to "Error Message: " & errText & return & return & "Error number: " & errNum as text
		set buttonList to {¬
			("Cancel"), ¬
			("Okay") ¬
				}
		set errDialogReply to display dialog errorString ¬
			with title ("Error: " & errNum) ¬
			buttons buttonList ¬
			default button 2 ¬
			cancel button 1 ¬
			giving up after 60 ¬
			with icon note
		if button returned of errDialogReply contains "Copy" then
			set the clipboard to errorString
		else if button returned of errDialogReply contains "Continue Error" then
			error errText number errNum
		end if
	end try
	set libFiles to {}
	
	repeat with thisFile in foundFiles
		set thisFile to thisFile as alias
		--tell application "System Events" to set libVersion to version of thisLib
		set fileInfo to (parse object thisFile)
		
		set {fileName, fileExtension} to fileInfo's {name_stub, name_extension}
		if fileName is not in libsInfo then set the end of libsInfo to fileName
		if fileExtension is in {"scpt", "scptd"} then
			set the end of libFiles to {source, fileName, thisFile}
		else if fileExtension is "app" then
			set appLibFolder to (thisFile as text) & "Contents:Library:Script Libraries:"
			try
				appLibFolder as alias
				set appLibFiles to my getLibFilesFromFolder(appLibFolder, fileName)
				set libFiles to libFiles & appLibFiles
			end try
		end if
	end repeat
	
	return libFiles
end getLibFilesFromFolder

on LibFolderVersions(libraryFolder, sourceDirectory)
	
	try
		set scriptLibraryFolder to libraryFolder & "Script Libraries" as alias
	on error
		return {}
	end try
	set libFiles to my getLibFilesFromFolder(scriptLibraryFolder, sourceDirectory)
	
	--set the end of libFiles to "--> Script Libaries:"
	set AppleScript's text item delimiters to {""}
	set libVersions to {}
	repeat with thisLibFile in libFiles
		set thisLibFile to thisLibFile as item
		if the class of thisLibFile is text then
			set the end of libVersions to {libSource, thisLibFile, ""}
		else
			set {libSource, libName, libPath} to thisLibFile as list
			
			tell application "System Events" to set libVersionNum to version of libPath
			set the end of libVersions to {libSource, libName, libVersionNum}
		end if
	end repeat
	return libVersions
end LibFolderVersions

on SortLibVersions(libVersions)
	set libVersions to sort list libVersions
	set sortedList to {}
	set saveTID to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {""}
	
	repeat with thisItem in libVersions
		set AppleScript's text item delimiters to {"-->"}
		set {libLocation, libInfo} to text items of thisItem
		if libLocation is not in sortedList then
			set the end of sortedList to libLocation
		end if
		set the end of sortedList to {"", libInfo} as text
		sortedList
		
	end repeat
	set AppleScript's text item delimiters to saveTID
	
	return sortedList
end SortLibVersions

on PersistentVariables()
	--set myPath to path to me
	--prepare storage for (myPath) 
	
	prepare storage for domain "com.edstockly.AppLibVersionApp"
	--default values {appsInfo:{}, libsInfo:{}, selectedLibs:{}, selectedApps:{}, lastPosition:{}}
	my RetreiveStoredValues()
end PersistentVariables

on StorePersistentValues()
	assign value appsInfo to key "appsInfo"
	assign value libsInfo to key "libsInfo"
	assign value selectedLibs to key "selectedLibs"
	assign value selectedApps to key "selectedApps"
	assign value lastAppPosition to key "lastAppPosition"
	assign value selectedAppRows to key "selectedAppRows"
	assign value selectedLibRows to key "selectedLibRows"
	assign value lastLibPosition to key "lastLibPosition"
end StorePersistentValues

on RetreiveStoredValues()
	set my appsInfo to value for key "appsInfo"
	set my libsInfo to value for key "libsInfo"
	set my selectedLibs to value for key "selectedLibs"
	set my selectedApps to value for key "selectedApps"
	set my selectedLibRows to value for key "selectedLibRows"
	set my selectedAppRows to value for key "selectedAppRows"
	set my lastAppPosition to value for key "lastAppPosition"
	set my lastLibPosition to value for key "lastLibPosition"
end RetreiveStoredValues

on GenerateSelectedRowNumbers(tableList, selectedValues)
	set selectedRowNumbers to {}
	repeat with x from 1 to count of tableList
		if item x of tableList is in selectedValues then set the end of selectedRowNumbers to x
	end repeat
	return selectedRowNumbers
end GenerateSelectedRowNumbers

Where can i find the the Library

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

set appData to do shell script "system_profiler SPApplicationsDataType"
set text item delimiters to return & return
set appLines to rest of (text items of appData)
set text item delimiters to {"Version: ", return}
set appInfoList to {}
repeat with i from 1 to (count appLines) by 2
	--set end of appList to item i of appLines
	--set end of appVersionList to text item 2 of item (i + 1) of appLines
	set end of appInfoList to (text 1 thru -2 of item i of appLines) & " (" & (text item 2 of item (i + 1) of appLines) & ")"
end repeat
set text item delimiters to {return}
display dialog "Installed Applications:" & return & return & (appInfoList as text)

That’s here. It’s best to install the entire collection here/

1 Like

I’m getting an error on line 38:

error "Can’t make some data into the expected type." number -1700 to item

Version number is ununified data. Some maker describe build number or its product name or blank…

My Mac app “Uni Detector” on Mac App Store is full written in AppleScript and reports version number very strange.

1 Like

I’ll check this out.

@Jacck,

Very interesting question, answered by @StefanK in post #2.

If I were you, I’d mark post #2 of the thread as the solution because it’s brilliant. You don’t need any additional installations, libraries or third-party utilities, as it is fast + handles any oddities in the versions laid down by the developers.

I decided to only slightly improve his solution, returning the result as a full-fledged AppleSript record. Made it useful handler, as well.
.

use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

appVersionsInfo()

on appVersionsInfo()
	set plistString to do shell script "system_profiler -xml SPApplicationsDataType"
	set dataString to current application's NSString's stringWithString:plistString
	set plistData to dataString's dataUsingEncoding:4
	set propertyList to current application's NSPropertyListSerialization's propertyListWithData:plistData options:0 format:(missing value) |error|:(missing value)
	set allItems to (propertyList's objectAtIndex:0)'s objectForKey:"_items"
	set info to allItems's dictionaryWithValuesForKeys:{"_name", "version"}
	(current application's NSDictionary's dictionaryWithObjects:(info's |version|) forKeys:(info's _name)) as record
end appVersionsInfo
1 Like

Hey Ed,

appsInfo is not populated before it is called, so the script is dying at that first repeat.

-Chris

My original goal was to generate a list of installed applications on my system while excluding the default apps from Apple. I wanted to use this list to install the same apps on another computer later on. However, I discovered that there are many different approaches to achieve this task from this post :exploding_head:

I’ve tried a number of things but can’t seem to get your script to output to any type of document. Possible?

We haven’t heard from KniazidisR for a few days. The following will write the record returned by his script to a text file on the desktop.

use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

set theDictionary to appVersionsInfo()
set theKeys to theDictionary's allKeys()'s sortedArrayUsingSelector:"localizedStandardCompare:"
set theText to ""
repeat with anItem in theKeys
	set theText to theText & (anItem as text) & ": " & (theDictionary's valueForKey:anItem) & linefeed
end repeat
set theFile to POSIX path of (path to desktop as text) & "App Data.txt"
(current application's NSString's stringWithString:theText)'s writeToFile:theFile atomically:true encoding:(current application's NSUTF8StringEncoding) |error|:(missing value)

on appVersionsInfo()
	set plistString to do shell script "system_profiler -xml SPApplicationsDataType"
	set dataString to current application's NSString's stringWithString:plistString
	set plistData to dataString's dataUsingEncoding:4
	set propertyList to current application's NSPropertyListSerialization's propertyListWithData:plistData options:0 format:(missing value) |error|:(missing value)
	set allItems to (propertyList's objectAtIndex:0)'s objectForKey:"_items"
	set info to allItems's dictionaryWithValuesForKeys:{"_name", "version"}
	(current application's NSDictionary's dictionaryWithObjects:(info's |version|) forKeys:(info's _name))
end appVersionsInfo

Thank you, that does things nicely. Now I’ll be trying to have the output go to an Excel spreadsheet with the app name in the first column and the version of the app in the second column. I’ll post when I figure that out.

Here is my script again with sorting and whitespace removal.

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

set appData to do shell script "system_profiler SPApplicationsDataType"
set text item delimiters to return & return
set appLines to rest of (text items of appData)
set text item delimiters to {"Version: ", return}
set appInfoList to {}
repeat with i from 1 to (count appLines) by 2
	set end of appInfoList to trimSpace(text 1 thru -2 of item i of appLines) & " (" & trimSpace(text item 2 of item (i + 1) of appLines) & ")"
end repeat
combSort for appInfoList
choose from list appInfoList with title "Installed Applications…"

on combSort for alist given shrinkfactor:sf : 1.7 -- fastest by a third, interleaved
	local i, j, cc, ns, js, gap, pgap, sw -- ns means No Swap
	script m
		property nList : alist
	end script
	set cc to count m's nList
	set gap to cc div sf
	repeat until gap = 0 --with j from (lc - 1) to 1 by -1
		repeat with i from 1 to gap
			set js to cc - gap
			repeat until js < 1 -- do each gap till nor more swaps
				set ns to gap
				repeat with j from i to js by gap
					if (item j of m's nList) > (item (j + gap) of m's nList) then
						set sw to (item j of m's nList)
						set (item j of m's nList) to (item (j + gap) of m's nList)
						set (item (j + gap) of m's nList) to sw
						set ns to j
					end if
				end repeat
				set js to ns - gap
			end repeat
		end repeat
		set pgap to gap
		set gap to gap div sf
		if gap = 0 then
			if pgap ≠ 1 then set gap to 1
		end if
	end repeat
end combSort

on trimSpace(aString)
	local i, j
	repeat with i from length of aString to 1 by -1
		if text i of aString ≠ " " then
			exit repeat
		end if
	end repeat
	repeat with j from 1 to length of aString
		if text j of aString ≠ " " then
			exit repeat
		end if
	end repeat
	return text j thru i of aString
end trimSpace

This works incredibly well, although I’m not sure I understand the reasoning behind the “Please make a selection” window only allowing you to select one application, rather than allowing you to produce a list of all installed applications. :thinking:

I used the choose list command so it would let me see the very long list.
Just remove that line

Tried many combinations of removing the “choose” text. Nothing worked as the result was either no applications, or one application.