For me, as script or as application is better. No need osascript:
tell application "Finder"
if not (exists folder "Backups" of folder "Dropbox" of home) then
make new folder in (folder "Dropbox" of home) with properties {name:"Backups"}
end if
if not (exists folder "vCards" of folder "Backups" of folder "Dropbox" of home) then
make new folder in (folder "Backups" of folder "Dropbox" of home) with properties {name:"vCards"}
end if
set vPath to (folder "vCards" of folder "Backups" of folder "Dropbox" of home) as string
end tell
tell application "Contacts"
repeat with cardPerson in people
set nameOfvCard to name of cardPerson & ".vcf"
set outFile to (open for access file (vPath & nameOfvCard) with write permission)
write (vcard of cardPerson as text) to outFile
close access outFile
end repeat
quit
end tell
set vPath to (path to home folder as text) & "Dropbox:Backups:vCards:"
do shell script "mkdir -p " & quoted form of POSIX path of vPath
tell application "Contacts"
set {contactNames, vCards} to {name, vcard} of people
quit
end tell
repeat with i from 1 to (count contactNames)
set nameOfvCard to (item i of contactNames) & ".vcf"
set outFile to (open for access file (vPath & nameOfvCard) with write permission)
try
set eof outFile to 0
write (item i of vCards) to outFile as «class utf8»
end try
close access outFile
end repeat
The first part in each script, specifically the lines beginning “set vPath to …”. KniaizidisR uses the Finder’s ‘home’ keyword, which represents the home folder of the user in which the script’s run. This is the “bocciaman” folder in your case. Mine uses the Standard Additions function ‘path to home folder as text’, which does pretty much the same thing. The paths produced in each case are HFS paths: colon-separated and with the name of the disk on the front. The second line of my script uses the POSIX version of the path (slash-separated) in a shell function which creates the “Backups” and “vCards” folders if they don’t already exist. KniazidisR uses the Finder and its specifiers to do the same thing.
Both scripts above exports to Dropbox contact’s info. Sure you have 682 registered contacts?
Maybe, those is not contacts (that is, persons), but 682 messages from those contacts in your mail application?
The only reasons I can think of for this are either:
• Contacts is only returning 55 of your 682 contacts’ names to the script, or:
• 627 of the names are duplicates, or:
• There’s something about writing to a Dropbox folder which is causing 627 of the vcfs to be lost.
I don’t know of any reason why the first should be true, I although I don’t rule it out.
The second is easy to understand technically. Since the vcf files are named after the contacts, each duplicated name simply causes an existing file to be overwritten. (This does in fact happen with two of my own contacts, which are for different divisions of the same company.) But it’s hard to believe you only have 55 different names among 682 contacts.
I haven’t tested the scripts on my Dropbox folder as I’m loathe to upload my contacts’ details to an unknown destination. But there’s no problem writing them to folders on my Desktop.
set targetFile to (path to desktop as text) & "vCards_sdraCv.txt"
my writeto(targetFile, "", «class utf8», false) # create an empty log file
set vPath to (path to home folder as text) & "Dropbox:Backups:vCards:"
do shell script "mkdir -p " & quoted form of POSIX path of vPath
tell application "Contacts"
set {contactNames, vCards} to {name, vcard} of people
quit
end tell
set treatedNames to {}
repeat with i from 1 to (count contactNames)
set bareName to (item i of contactNames)
if bareName is in treatedNames then
set nameOfvCard to bareName & "_" & i & ".vcf"
else
set end of treatedNames to bareName
set nameOfvCard to bareName & ".vcf"
end if
my writeto(targetFile, nameOfvCard & linefeed, «class utf8», true) # report name of every files
set outFile to (open for access file (vPath & nameOfvCard) with write permission)
try
set eof outFile to 0
write (item i of vCards) to outFile as «class utf8»
on error errmsg number nbr
my writeto(targetFile, errmsg & tab & nameOfvCard & linefeed, «class utf8», true) # report error message and name of the offending file
end try
close access outFile
end repeat
#=====
(*
Handler borrowed to Regulus6633 - http://macscripter.net/viewtopic.php?id=36861
*)
on writeto(targetFile, theData, dataType, apendData)
-- targetFile is the path to the file you want to write
-- theData is the data you want in the file.
-- dataType is the data type of theData and it can be text, list, record etc.
-- apendData is true to append theData to the end of the current contents of the file or false to overwrite it
try
set targetFile to targetFile as «class furl»
set openFile to open for access targetFile with write permission
if not apendData then set eof of openFile to 0
write theData to openFile starting at eof as dataType
close access openFile
return true
on error
try
close access file targetFile
end try
return false
end try
end writeto
#=====
This way vcard with identical name will generate files with different names.
So you will have a track to explain what is at work.
Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) samedi 2 novembre 2019 14:06:36
Added instructions creating a log report so you will be able to see what was done and what possibly failed.
The AppleScript above this post worked! I was still getting an error about 2 H&R block vcf cards not being found. Therefore, I manually backed up those 2 records and deleted them as contacts. Then the script ran with no errors and I got 1038 individual .vcf cards in my Dropbox folder!
Thank you!
set vPath to (path to home folder as text) & "Dropbox:Backups:vCards:"
do shell script "mkdir -p " & quoted form of POSIX path of vPath
tell application "Contacts"
set {contactNames, vCards} to {name, vcard} of people
quit
end tell
set treatedNames to {}
repeat with i from 1 to (count contactNames)
set bareName to (item i of contactNames)
if bareName is in treatedNames then
set nameOfvCard to bareName & "_" & i & ".vcf"
else
set end of treatedNames to bareName
set nameOfvCard to bareName & ".vcf"
end if
set outFile to (open for access file (vPath & nameOfvCard) with write permission)
try
set eof outFile to 0
write (item i of vCards) to outFile as «class utf8»
end try
close access outFile
end repeat
I think that just means that AppleScript was being stopped once it got to the contacts with the “&” in the name. See the screenshot for the number of contacts.
Here for fun is an ASObjC version using the Contacts framework. It’s pretty fast. It assumes a title-forename-surname-honours name order, but I think there’s a setting which can be queried to determine this. I’ll look into it later. (Edit: Now duly looked into and incorporated into the script.)
use AppleScript version "2.5" -- El Capitan (10.11) or later
use framework "Foundation"
use framework "Contacts"
use scripting additions
property vPath : "~/Dropbox/Backups/vCards/"
property logPath : "~/Desktop/vCards_sdraCv.txt"
main()
on main()
set |⌘| to current application
-- Get all the "containers" in the user's Contacts database. (Possibly only one.)
set contactStore to |⌘|'s class "CNContactStore"'s new()
set allContainers to contactStore's containersMatchingPredicate:(missing value) |error|:(missing value)
-- Get all the contacts from all the containers, including the data required for their vCards.
set allContacts to |⌘|'s class "NSMutableArray"'s new()
set requiredKeyDescriptor to |⌘|'s class "CNContactVCardSerialization"'s descriptorForRequiredKeys()
repeat with thisContainer in allContainers
set containerID to thisContainer's identifier()
set contactPredicate to (|⌘|'s class "CNContact"'s predicateForContactsInContainerWithIdentifier:(containerID))
set theseContacts to (contactStore's unifiedContactsMatchingPredicate:(contactPredicate) keysToFetch:({requiredKeyDescriptor}) |error|:(missing value))
tell allContacts to addObjectsFromArray:(theseContacts)
end repeat
-- Create the destination folder if it doesn't already exist.
set expandedVPath to (|⌘|'s class "NSString"'s stringWithString:(vPath))'s stringByExpandingTildeInPath()
tell |⌘|'s class "NSFileManager"'s defaultManager() to createDirectoryAtPath:(expandedVPath) withIntermediateDirectories:(true) attributes:(missing value) |error|:(missing value)
-- Work through the contacts individually, writing their vCards to the destination folder.
set person to |⌘|'s CNContactTypePerson
set regexSearch to |⌘|'s NSRegularExpressionSearch
set usedNames to |⌘|'s class "NSMutableArray"'s new()
set duplicatedNamesLog to |⌘|'s class "NSMutableString"'s new()
set nameFormatter to |⌘|'s class "CNContactFormatter"'s new()
repeat with i from 1 to (count allContacts)
set thisContact to item i of allContacts
-- Create this contact's vCard.
set vCardData to (|⌘|'s class "CNContactVCardSerialization"'s dataWithContacts:{thisContact} |error|:(missing value))
-- Get the contact's personal or organisation name, formatted as set for that contact.
set contactName to (nameFormatter's stringFromContact:(thisContact))
-- If the name's a duplicate of one already handled, modify it for the file name and add a note to the log text.
if (usedNames's containsObject:(contactName)) then
tell duplicatedNamesLog to appendFormat_("%@ -> ", contactName)
set contactName to (contactName's stringByAppendingString:("_" & i))
tell duplicatedNamesLog to appendFormat_("%@.vcf%@", contactName, linefeed)
else
tell usedNames to addObject:(contactName)
end if
-- Construct the file name and the full path and write the vCard data to it.
set fileName to (contactName's stringByAppendingPathExtension:("vcf"))
set savePath to (expandedVPath's stringByAppendingPathComponent:(fileName))
tell vCardData to writeToFile:(savePath) atomically:(true)
end repeat
-- If there's anything in the log text, write it to file and inform the user.
if (duplicatedNamesLog's |length|() > 0) then
set expandedLogPath to (|⌘|'s class "NSString"'s stringWithString:(logPath))'s stringByExpandingTildeInPath()
tell duplicatedNamesLog to writeToFile:(expandedLogPath) atomically:(true) encoding:(|⌘|'s NSUTF8StringEncoding) |error|:(missing value)
set {button returned:buttonReturned} to (display alert "PLEASE NOTE" message "One or more of your contacts' names were duplicates and their vCards were saved under modified names. A log of the modified names has been saved to the file \"" & expandedLogPath's lastPathComponent() & "\" on your desktop." as critical buttons {"Open the log file", "OK"} default button "OK")
if (buttonReturned is "Open the log file") then
set logFile to expandedLogPath as text as POSIX file
tell application "Finder" to open logFile
end if
end if
end main
This is my first post here.
The ASObjC script is great and I wanted to use it for my purposes. As I found out, “CNContactVCardSerialisation” does not write the → notes and → images to the vCards. There are also articles on this topic (e.g. here), but they don’t help me because of my lack of knowledge. However, you can add the notes and images to the end of the vCards.
I have tried that. It works with the notes, but I can’t get any further with the images. How can I read the data and get a base64-encoded string?
I have also downloaded the ScriptDebugger. There is an error when executing the script. But it works in the Scripteditor.
set thisContactsNote to thisContact's {|note|()} as string
--> Error: A property was not requested when contact was fetched.
Here is my script with the marked changes (and a workaround to save vCards with slashes in the name):
use AppleScript version "2.5"
use framework "Foundation"
use framework "Contacts"
use scripting additions
set vPath to do shell script "currDate=$(date \"+%Y-%m-%d %H.%M.%S\"); echo \"$HOME/Desktop/vCards/vCards Backup ($currDate)\""
set logPath to vPath & "/_log.txt"
set |⌘| to current application
-- Get all the "containers" in the user's Contacts database. (Possibly only one.)
set contactStore to |⌘|'s class "CNContactStore"'s new()
set allContainers to contactStore's containersMatchingPredicate:(missing value) |error|:(missing value)
-- Get all the contacts from all the containers, including the data required for their vCards.
set allContacts to |⌘|'s class "NSMutableArray"'s new()
set requiredKeyDescriptor to |⌘|'s class "CNContactVCardSerialization"'s descriptorForRequiredKeys()
repeat with thisContainer in allContainers
set containerID to thisContainer's identifier()
set contactPredicate to (|⌘|'s class "CNContact"'s predicateForContactsInContainerWithIdentifier:(containerID))
set theseContacts to (contactStore's unifiedContactsMatchingPredicate:(contactPredicate) keysToFetch:({requiredKeyDescriptor}) |error|:(missing value))
tell allContacts to addObjectsFromArray:(theseContacts)
end repeat
-- Create the destination folder if it doesn't already exist.
set expandedVPath to (|⌘|'s class "NSString"'s stringWithString:(vPath))'s stringByExpandingTildeInPath()
tell |⌘|'s class "NSFileManager"'s defaultManager() to createDirectoryAtPath:(expandedVPath) withIntermediateDirectories:(true) attributes:(missing value) |error|:(missing value)
-- Work through the contacts individually, writing their vCards to the destination folder.
set person to |⌘|'s CNContactTypePerson
set regexSearch to |⌘|'s NSRegularExpressionSearch
set usedNames to |⌘|'s class "NSMutableArray"'s new()
set errorLog to |⌘|'s class "NSMutableString"'s new()
set nameFormatter to |⌘|'s class "CNContactFormatter"'s new()
--set organizationnameFormatter to |⌘|'s class "CNContact.organizationName"'s new()
set c to 2
repeat with i from 1 to (count allContacts)
set thisContact to item i of allContacts
-- Create this contact's vCard.
set vCardData to (|⌘|'s class "CNContactVCardSerialization"'s dataWithContacts:{thisContact} |error|:(missing value))
-- Get the contact's personal or organisation name, formatted as set for that contact.
set contactName to (nameFormatter's stringFromContact:(thisContact))
##-- If the name's a duplicate of one already handled, modify it for the file name
##----- with sequential numbering
if (usedNames's containsObject:(contactName)) then
set newcontactName to (contactName's stringByAppendingString:("_" & c))
set contactName to newcontactName
set c to c + 1
else
tell usedNames to addObject:(contactName)
set c to 2
end if
-- Construct the file name and the full path and write the vCard data to it.
set fileName to (contactName's stringByAppendingPathExtension:("vcf"))
-----------------------------------
##-----replace "/" in path
if (fileName as string) contains "/" then
tell me
try
set fileName to (do shell script "echo '" & fileName & "' |sed 's/\\//_⊕⊖⊗⊘_/g'")
on error err
tell errorLog to appendFormat_(err, linefeed)
end try
end tell
end if
set savePath to (expandedVPath's stringByAppendingPathComponent:(fileName))
tell vCardData to writeToFile:(savePath) atomically:(true)
-----------------------------------
##--- add image to vCard
set thisContactsImage to thisContact's imageData()
--?
##------ add note to vCard
###### working in Scripteditor. Error in Script Debugger: A property was not requested when contact was fetched
set thisContactsNote to thisContact's |note|() as string
if thisContactsNote is not "" then
try
set NoteList to {}
if (count of paragraphs of thisContactsNote) > 1 then
repeat with aPara in paragraphs of thisContactsNote
set NoteList to NoteList & aPara & "\\\\n"
end repeat
else
set NoteList to thisContactsNote
end if
tell me to do shell script "sed -i '' -e '$ d' " & quoted form of (savePath as string) & ";echo 'NOTE:" & (NoteList as string) & "
END:VCARD' >> " & quoted form of (savePath as string)
on error err
try
set exist to savePath as string
exist as POSIX file as alias
set err to "Notes could not be added to vCard"
on error
set err to ""
end try
set err to err & "
"
tell errorLog to appendFormat_(err, linefeed)
end try
end if
end repeat
-----------------------------------------------------
##---- use Finder to insert "/" back into file name
set slash to do shell script "find " & quoted form of vPath & " -mindepth 1 -maxdepth 1 -name '*_⊕⊖⊗⊘_*' -type f"
set slashes to paragraphs of slash
repeat with i from 1 to count of items of slashes
set a_slash to (item i of slashes as POSIX file)
tell application "Finder"
set n to name of (a_slash as alias)
tell me
set nn to replace_chars(n, "_⊕⊖⊗⊘_", "/")
end tell
set name of (a_slash as alias) to nn
end tell
end repeat
-- If there's anything in the log text, write it to file and inform the user.
if (errorLog's |length|() > 0) then
set expandedLogPath to (|⌘|'s class "NSString"'s stringWithString:(logPath))'s stringByExpandingTildeInPath()
tell errorLog to writeToFile:(expandedLogPath) atomically:(true) encoding:(|⌘|'s NSUTF8StringEncoding) |error|:(missing value)
set {button returned:buttonReturned} to (display alert "PLEASE NOTE" message "One or more of your contacts or contact's notes maybe not saved! A log has been saved to the file \"_log.txt\" in vCards_Backup folder" as critical buttons {"Open the log file", "OK"} default button "OK")
if (buttonReturned is "Open the log file") then
tell application "Finder" to open logPath as POSIX file
end if
end if
on replace_chars(this_text, search_string, replacement_string)
set AppleScript's text item delimiters to the search_string
set the item_list to every text item of this_text
set AppleScript's text item delimiters to the replacement_string
set this_text to the item_list as string
set AppleScript's text item delimiters to ""
return this_text
end replace_chars
The script works perfectly, I even duplicated one of my contacts just to see how the log file works. And that’s where I ran into an issue. When the log window pops up to let you know that there were errors, and you click “Open the log file”, I get another window saying I don’t have permission to view the log file. Yet, I can double-click the log file on my desktop and it opens in TextEdit. Any ideas?
Issue resolved. I was playing with different ideas and changed the name of the log file from “vCards_sdraCv.txt” to “vCards Export Errors” and now when I click “Open the log file” it magically opens. I haven’t a clue as to why (anyone care to offer up an explanation?), but it works now for some reason.
Sounds like a permissions issue on your machine. Possibly the application running the script needed permission to tell the Finder to open the file. But I’ve no idea how changing the file name has fixed it!
Hi @Keita. Welcome to MacScripter! Apologies for this delayed response.
After looking over my script to remind myself how it works, and trying a few things before looking at your modifications or the link you’ve provided, here are my observations about notes not being written to the vCards. I’m afraid I don’t keep images with my own contacts, so I haven’t been able to test with images too.
The input for the unifiedContactsMatchingPredicate:keysToFetch:error: method, which fetches the contacts from the database, has to include a list of keys indicating which properties are wanted with the returned “CNContact” objects. Alternatively, the list can contain a “descriptor” indicating a range of properties. My script uses the “CNContactVCardSerialization” descriptor descriptorForRequiredKeys, which represents the properties that are absolutely required in a contact’s vCard. Apparently, these don’t include any note there may be. So the returned “NSContact” objects already don’t don’t have note properties before the vCard data are extracted from them.
According to the documentation, the fetching method’s keysToFetch: parameter can be a mixture of keys and descriptors. But when I add |⌘|'s CNContactNoteKey to the list, the returned objects for my contacts with notes only gain a property called [b]iOSLegacyIdentifier[b], which has a numeric value and isn’t written to the vCards either. It turns out that in macOS 13 (or iOS 13) and later, the app running the code needs special permission in a plist file to be able to access contacts’ notes.
So to sum up briefly for now: it seems easy enough to add the keys for notes, images, and/or thumbnails to the keysToFetch: list, but actually obtaining the notes may require the script to be saved in a particular way. I’ll look into this this afternoon (GMT) and report back.