How to replace an app's icon with a custom one from an .icns file?

I am selling the app “Find Any File”. It has a “classic” pre-Big Sur app icon.

However, some users rather want to see a Big Sur “squircle” icon in their Dock.

So, I had a BS icon made.

Now, I like to offer that icon as a downloadable option, so that they can simply paste it as a custom icon into the app’s Info window.

I also like to offer an app, preferrably made with AppleScript (or Automator) that performs this operation.

Any idea how to accomplish this without “accessiblity scripting”, i.e. I seek a way that allows me to set the app’s custom icon directly instead of opening the Info window in the Finder and trying to wiggle it in there (too much can go wrong with that).

This only needs to work on Big Sur and later macOS versions.

I found a way through ASObjC in an Apple dev forum post (https://discussions.apple.com/thread/5448060?answerId=23402634022#23402634022).

Here’s the code I’d use:

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

property NSWorkspace : class "NSWorkspace"
property NSImage : class "NSImage"
property imageName : "Find Any File - Orange.icns"

on open of theFiles
	repeat with aFile in theFiles
		my setCustomIcon(aFile)
	end repeat
end open

on run
	set theApp to choose file with prompt "Choose the Find Any File app for which you want to replace its icon:" of type {"APPL"} default location "/Applications"
	my setCustomIcon(theApp)
end run

on setCustomIcon(destFile)
	tell application "Finder"
		-- the icns file is expected to be placed next to this script app, so we use our app's path to locate it.
		set currentDir to container of (path to me)
		set imageFile to item imageName of currentDir
	end tell
	
	set imagePath to POSIX path of (imageFile as alias)
	set filePath to POSIX path of destFile
	
	set image to (NSImage's alloc's initWithContentsOfFile:imagePath)
	if image is null then
		display dialog "Icon could not be loaded." buttons "Quit"
	else
		set res to (NSWorkspace's sharedWorkspace()'s setIcon:image forFile:filePath options:0)
		if res then
			display dialog "Icon has been updated." buttons "Quit"
		else
			display dialog "Icon could not be updated." buttons "Quit"
		end if
	end if
end setCustomIcon

However, there is one problem with it: If the application has been installed via the Mac App Store, then it’s write protected and one needs admin permissions to change its icon. If one changes the icon thru the Finder’s Info dialog, the Finder will automatically prompt the user to enter his credentials for this operation. Unfortunately, this script can’t accomplish this, so it only works for files that are not protected.

I guess I’ll have to instead instruct the Finder through an Applescript command to replace the icon, so that the Finder will then hopefully bring up the Admin prompt if necessary.

For Mac OS X built-in applications you can’t do nothing.

For App Store’s applications you can add yourself to Sharing & Permissions of Get Info, with read/write permissions. Then the script above will work for those apps.

This will work on any application whose current icon file has the default name of “applet.icns”

This following AppleScript code allows the user to choose the app or apps whose icons you want to change and also allows the user to choose the replacement .icns file.

property destinationSuffix : "Contents/Resources/applet.icns"

tell application "Finder"
	activate
	set chosenApp to (choose file with prompt ¬
		"Choose The Application Whose Icon You Would Like To Change" of type ¬
		"APPL" with multiple selections allowed)
end tell
delay 0.1

tell application "Finder"
	activate
	set iconFile to (choose file of type "com.apple.icns" with prompt ¬
		"Choose The Replacement .icns File")
end tell
delay 0.1

repeat with i from 1 to count of chosenApp
	set thisItem to POSIX path of item i of chosenApp
	set replaceThisIcon to thisItem & destinationSuffix as string
	do shell script "cp " & quoted form of POSIX path of iconFile & " " & ¬
		quoted form of replaceThisIcon & " ; touch " & quoted form of thisItem
end repeat

This would replace the icons file directly inside the app. I had thought of this myself before, but had my doubts that this would work, so I hadn’t even considered it further:

  1. It might cause the app’s signature become invalid. Indeed, when I perform the command “spctl --assess” on the modified app, it gives: “a sealed resource is missing or invalid”, meaning the codesignature is indeed no valid any more. I can still launch the app, but I’d not be surprised that later macOS versions or other mechanisms will eventually deem the app broken.

  2. If the app was installed via the Mac App Store, it’ll be protected, meaning that the modification won’t work, for the same reason I mentioned in post #2. However, this may be resolved by appending “with administrator privileges” to the “do shell script” command.

  3. After replacing the icon file in the Resources folder, the app will still show the old icon. Using “Get Info” will update the icon in the Finder window, but then launching it will still show the old icon in the Dock. The only way I found to have it immediately updated in the Dock as well was to change the modification date of the app, e.g. with the “touch” command, like the shown script does it. But then, the Mac App Store app does not see the app as being installed any more and wants to re-download it. So this isn’t a good solution, either, so far.

I think you’re being charitable – it would make it invalid.

Off-topic for your requirements, but given the sample code uses the icon for AppleScript applets, it’s probably worth pointing out that, as of macOS 11, all applets are code-signed, so doing risks effectively destroying any such applet (or at least require you to re-sign it).

This looks like a promising option.
https://github.com/mklement0/fileicon#manual-installation

On my system I installed “fileicon” with Homebrew using this command [format]brew install fileicon[/format]

After successful installation, In Terminal, I ran the command [format]which fileicon[/format]
which returned /usr/local/bin/fileicon . I navigated to that file in Finder. Because that file is actually an alias, I right clicked it and chose “Show Original”.

Next, I created and saved a new AppleScript applet in Script Debugger, so I could add the “fileicon” file to the applet’s Resources folder.

After adding the “fileicon” file (Now that I know where It’s located) to the applet’s Resources folder and saving the applet again, I added this following AppleScript code into the document.

property userName : short user name of (system info)

set iconChanger to POSIX path of (path to resource "fileicon" in bundle (path to me))

tell application "Finder"
	activate
	set chosenApp to POSIX path of (choose file with prompt ¬
		"Choose The Application Whose Icon You Would Like To Change" of type "APPL")
end tell
delay 0.1

tell application "Finder"
	activate
	set iconFile to POSIX path of (choose file of type "com.apple.icns" with prompt ¬
		"Choose The Replacement .icns File")
end tell
delay 0.1

activate
set userPassword to text returned of (display dialog ¬
	"Please Enter User Password" default answer "password" hidden answer true ¬
	buttons {"Cancel", "OK"} default button 2 cancel button 1 ¬
	with title "Please Enter User Password" with icon stop)

do shell script quoted form of iconChanger & " set " & ¬
	quoted form of chosenApp & " " & quoted form of iconFile ¬
	user name userName password userPassword with administrator privileges

I don’t know if this solution will work for you. I considered it to be a worthwhile project to play around with. I hope this is something you can use but if not, maybe others will be interested.