Putting a password on zip files with a simple dialog

Compressing files is easy enough on macOS. You select files, right-click on them and select "Compress ... items". macOS creates a zip file that either has the name of the item that was compressed, or is named "Archive.zip" if more than one item was selected.

When you want to add a password to the zip, just to make sure that the file cannot be opened by people who should not access it, it is a different story. You must either use a third party utility, or the Terminal.

Terminal has everything you need, but even people who often use the command line can forget the syntax, the options and a number of other things. The ideal would be to be able to leverage the command line with a minimum set of parameters, directly from a simple graphical user interface.

And that's where AppleScript shines...

I thought about putting something like this together a while ago, but it's only a recent client who always sends me password locked zipped files that really made me want to scratch this itch. Basically I want something that behaves almost exactly as when the Finder does it:

1) select the files in Finder
2) launch the compress command
3) the zipped file is created at the shortest common directory
4) Finder opens a window for that directory with the zipped file selected

What we need to add is

2b) propose a random password to lock the opening of the archive

That seems simple enough when you write it like this and in fact there are a number of solutions posted online that make use of AppleScript and the command line, but I could not find one that replicated the Finder's behavior. The closest (and shortest) was an AppleScript that created one zip file for each selected file, but not respecting 3) and 4) above, I was not satisfied with it.

The problem here is in fact finding the shortest common directory. It would probably be a piece of AppleScript cake for experienced programmers, but I'm not there yet, if I'll ever be...

I tested a number of approaches. The first one being, sort the files by length of path (number of "container"), find the shortest path, use that as a base to find the shortest common path. Then I realized that 2 files could have a similarly short path but have little in common, so I'd have to choose one arbitrarily to use as the base for the shortest common path. Then it occurred to me that even if a file had a shorter path than another, it did not mean that the shortest path included the longest one.

So, I was running in all sorts of directions and wrote a lot of really fun code (including a nice recursive function) when I decided to bite the bullet and just take any first file that Finder would hand me and use it to start the comparison.

When I restarted coding pretty much from scratch, I ended up at a point where I had all the path items for a give file handled as strings: "Mabinogion" "Users" "suzume" "Document", etc. and I had to compare all the nth items of each file simultaneously to see whether they were all equal (in which case, that folder was a common folder) or if one or more were not identical (in which case the considered folders were not common and I could stop the search).

Once I had that common folder, I had to "subtract" it from the path of each considered file to obtain a relative path that I could use in my "behind the AppleScript scene" zip command. I have to thank Shane Stanley for the hint he gave me, and I really wish there was a logical way in AppleScript to handle paths and file system items that does not involve string manipulation...

Anyway, the following code took me about 3 days to complete, and even though I've tested it fairly well, there are still possibly a few issues with it. The password is the first 12 characters of the md5 hash of "date", which changes every second so it should be good enough as a relatively strong password. It is also generated every time you use the script.

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

# We initialize a number of variables that will be used throughout the script, including the password

set savedTextDelimiters to AppleScript's text item delimiters
set AppleScript's text item delimiters to {":"}
set selectionList to {}
set commonPath to {}
set zipcommand to ""
set zippassword to (do shell script "date | md5 | head -c12")

tell application "Finder"
set mySelection to selection
# If no files are selected the script simply stops.
if length of mySelection = 0 then
display alert "Select files to compress"
# If just one file is selected, the process is straightforward.
else if length of mySelection = 1 then
set fileName to name of item 1 of mySelection
set targetfile to quoted form of (fileName)
set fileContainer to (POSIX path of (container of item 1 of mySelection as alias))
set commonPath to quoted form of fileContainer
set zipFile to quoted form of (fileContainer & fileName & ".zip")
# If more than one file is selected, we have to first split the file paths into their Finder elements (a list of folders that starts at the disk and ends at the selection, which can be a folder or a file).
repeat with myItem in mySelection
copy text items of (contents of myItem as text) to end of selectionList
end repeat
# We now have a list of paths that are stored as lists of Finder elements and we need to compare all the Finder elements one by one and only keep the ones that are common to all the files.
repeat with i from 1 to length of item 1 of selectionList
set referenceItem to item i of item 1 of selectionList
repeat with selectedFile in selectionList
if (item i of contents of selectedFile is not equal to referenceItem) then
set sameItem to false
exit repeat
set sameItem to true
end if
end repeat
if sameItem is true then
copy referenceItem to end of commonPath
set sameItem to false
exit repeat
end if
end repeat
# We now have the common path, which leads to a folder that all the files have in common. When we'll zip the files, we'll first move to that folder and will call all the files to be zipped by their path relative to that folder, so now we need to subtract the common path from the path of all the selected files.
set commonPath to POSIX path of ((commonPath as text) & ":" as alias)
repeat with myItem in mySelection
set contents of myItem to quoted form of ("." & text (length of commonPath) thru -1 of (POSIX path of (contents of myItem as alias)))
end repeat
# Ok, all the file paths are now ready. We now need to prepare the zip command and make sure that all the paths that we'll use are in "quoted form" since we'll be using the "do shell script" command and "quoted form of" allows us to manipulate paths that include spaces.
set zipFile to quoted form of (commonPath & "Archive.zip")
set AppleScript's text item delimiters to {" "}
set targetfile to mySelection as text
set commonPath to quoted form of commonPath
end try
end if
# Now we let the user validate the password that was generated at the beginning of the script, or eventually change it to something she prefers, we generate the command that will be launched by "do shell script", we launch the command and we ask Finder the open a window on the common folder where the archive was created and to select it.
set zippassword to quoted form of (text returned of (display dialog "Password:" default answer zippassword buttons {"OK", "Cancel"} default button "OK" cancel button "Cancel"))
set zipcommand to "cd " & commonPath & "; " & "zip -r " & zipFile & " " & targetfile & " --password " & zippassword # & " ; open " & commonPath
do shell script zipcommand
set AppleScript's text item delimiters to {""}
set archiveFile to (((items 2 thru ((length of zipFile) - 1)) of zipFile) as text) as POSIX file
select archiveFile
end try
end tell

# Just to make sure, but there should be no reason to worry, we reinitialize all the important information

set AppleScript's text item delimiters to savedTextDelimiters
set selectionList to {}
set commonPath to {}
set zipcommand to ""
set zippassword to ""

# Et voilĂ  !

If you save this script as an application, you can call it from Spotlight every time you need to password-compress files. You can also put it into an Automator service and assign a shortcut to it. Or put it into the dock and click on it when files are selected in the Finder. Whatever the method is, the result will be the same: the selected files in Finder will be compressed and locked with the password you provided and Finder will open a window where the compressed file is selected. You can then copy it, paste it into a mail, or copy it into a different folder, etc.

In any case, don't forget to store the password somewhere, either to send it to your client so that the archive can be opened there, or to keep it somewhere on your machine if the archive is for local use. Just like with any password locked file, don't erase the original file before you are sure the password is safely stored somewhere, otherwise you would not be able to access the archived files anymore...

Ok, that's this end for 2017. I'm glad I resumed writing this year. And I'm hoping you've found some use in the things I wrote.