How to Set the Where-From Metadata of a File with AppleScriptObjC?

Hey Folks,

Does anyone know how to set kMDItemWhereFroms and other metadata from AppleScriptObjC?


You can’t directly set Spotlight metadata — but you can change the information the Spotlight importer imports. This code will give you the existing quarantine information:

set aURL to current application's |NSURL|'s fileURLWithPath:posixPath -- make URL
set {theResult, qDict} to aURL's getResourceValue:(reference) forKey:(current application's NSURLQuarantinePropertiesKey) |error|:(reference) -- get quarantine dictionary

In theory you can probably change the values and replace the old version. But you may end up causing problems if you just change one value — I can see a UUID in there, as well as a date and some other values. Can I ask why you would want to do that?

Hey Shane,

I’m setting the Where From metadata of a file.

Presently I only know how to do this with xattr in the shell:


# -----------------------------------------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2019/06/15 19:01
# dMod: 2019/06/15 19:01 
# Task: Download BBEdit Notes File and Set WhereFroms MetaData
# Tags: @ccstone, @Shell, @Script, @Download, @BBEdit, @Notes, @Set, @WhereFroms, @Metadata
# -----------------------------------------------------------------------------------------

cd /Users/chris/Downloads


fileName=$(basename "$fileURL")

# Download the current BBEdit beta notes file.

curl -Ls --remote-name --user-agent 'Opera/9.70 (Linux ppc64 ; U; en) Presto/2.2.1' "$fileURL"

# Set the Where From Metadata for the downloaded File.

xattr -w '' '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ""><plist version="1.0"><array><string>'"$fileURL"'</string></array></plist>' "$fileName"

# Create new name for file.

bbeditVersionStr=$(egrep '^version' "$fileName" | head -n 1 | awk '{ print substr($0, index($0,$2)) }' | sed -E -e 's!\)[[:blank:]]+\(!) ⇢ Beta Notes ⇢ !' -e 's!\)$!!' -e 's!^!BBEdit !' -e 's!$!.txt!')

# Rename the file.

mv "$fileName" "$bbeditVersionStr"

You probably want something like this:

use framework "Foundation"
use scripting additions

set fileURL to ""
set theURL to current application's NSURL's URLWithString:fileURL
set theName to theURL's lastPathComponent()
set theData to current application's NSData's dataWithContentsOfURL:theURL
set theDate to current application's NSDate's |date|()
set newPath to "/Users/shane/Desktop/Notes.txt" -- change to suit
set newURL to current application's NSURL's fileURLWithPath:newPath
theData's writeToURL:newURL atomically:true
set qDict to current application's NSDictionary's dictionaryWithObjects:{theURL, theDate, current application's kLSQuarantineTypeWebDownload} forKeys:{current application's kLSQuarantineOriginURLKey, current application's kLSQuarantineTimeStampKey, current application's kLSQuarantineTypeKey}
newURL's setResourceValue:qDict forKey:(current application's NSURLQuarantinePropertiesKey) |error|:(missing value)

It will probably add a quarantine agent automatically.

Hey Shane,


That does a nice job downloading the file.

It does.

The script still does not demonstrate how to set the Where From metadata of the downloaded file.

And I’d also like to know how to remove the Quarantine status of the file with AppleScriptObjC if possible.

(I know how to do it with xattr.)

I’m not sure why the Spotlight importer is not updating the Spotlight database from the new info. I don’t see any other place to change it.

Set the resource value for it to missing value. Just don’t do it to a file that has already had it removed.


Maybe I’m missing something here, but why not using the shell once you have downloaded (and renamed)

set zScript to "xattr -w '' " & "'<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"\"><plist version=\"1.0\"><array><string>" & quoted form of theURL & "</string></array></plist>' " & quoted form of theFilePath
do shell script zScript

Hey Andreas,

Pease see my post #13, where I did just that.

The reason is: I want to know if it can be done with with AppleScriptObjC.

When I’m working with AppleScript I prefer not to use do shell script commands, unless they are the best (or only) method available.


1 Like

Hey Folks,

Coming back to this…

I normally review BBEdit beta release notes in a web browser (Brave), so I decided to scrape the text from the browser instead of freshly download the remote file.

In time the script below will be much more comprehensive, but for now it is only handling the job specified above.

Starting with this web page:

The script below will:

  1. Scrape the text from the page.
  2. Parse the file to compile a unique name for it.
  3. Write the text to the named file in the ~/Downloads folder.
  4. Set the kMDItemWhereFroms Metadata appropriately.
  5. Reveal the file in the Finder.


# dNam: New Downloader Script for FRONT BROWSER (instead of browser-specific).
#          - Set for BRAVE for development.
# dCre: 2019/06/21 17:27
# dMod: 2019/06/07 13:04 
 • All Chrome-based browsers use the same syntax!
use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

--» Discover what browser is Frontmost.

set jsCmdStr to "document.URL"
# set browserURL to doJavaScriptInFrontBrowser(jsCmdStr)
set browserURL to doJavaScriptInBrave(jsCmdStr)

--» BBEdit Beta Notes
if (its reMatch:"\\d+\\.\\d+\\.\\d+_Notes.txt" inText:browserURL) ≠ "" then
   set jsCmdStr to "
   set browserText to doJavaScriptInBrave(jsCmdStr)
   set thePattern to "(?m)^(version)\\h+(\\d+[\\d.]+)\\h+(\\(\\d+\\))\\h+\\((\\d+[\\d-]+)\\)"
   set templateStr to "BBEdit $2 $3 Beta Notes ⇢ $4.txt"
   set findResult to its regexFindWithCapture:thePattern fromString:browserText resultTemplate:templateStr
   if length of findResult ≠ 0 then
      set findResult to item 1 of findResult
      set newFilePath to (POSIX path of (path to downloads folder as text)) & findResult
      its writeString:browserText toPath:newFilePath
      set newBBEditVersionFile to alias POSIX file newFilePath
      --» Set kMDItemWhereFroms of the newly created file.		
      set shCmdStr to "xattr -w '' '<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"\"><plist version=\"1.0\"><array><string>'" & quoted form of browserURL & "'</string></array></plist>' " & quoted form of newFilePath
      do shell script shCmdStr
      tell application "Finder"
         reveal newBBEditVersionFile
      end tell
      # "" --» •••••
   end if
end if

on doJavaScriptInBrave(jsCmdStr)
      tell application "Brave Browser" to tell front window's active tab to execute javascript jsCmdStr
   on error e
      error "Error in handler doJavaScriptInBrave() of library NLb!" & return & return & e
   end try
end doJavaScriptInBrave
on doJavaScriptInFrontBrowser(jsCmdStr)
   set frontBrowserName to getFrontBrowserName() of me
   if frontBrowserName = "Brave Browser" then
      tell application "Brave Browser" to tell front window's active tab to execute javascript jsCmdStr
   else if frontBrowserName = "Google Chrome" then
      tell application "Google Chrome" to tell front window's active tab to execute javascript jsCmdStr
   else if frontBrowserName = "Safari" then
      tell application "Safari" to do JavaScript jsCMD in front document
   end if
end doJavaScriptInFrontBrowser
on getFrontBrowserName()
   set frontBrowser to path to frontmost application as text
   tell application frontBrowser to set frontBrowserName to its name
   return frontBrowserName
end getFrontBrowserName
on cngStr:findString intoString:replaceString inString:dataString
   set anNSString to current application's NSString's stringWithString:dataString
   set dataString to (anNSString's stringByReplacingOccurrencesOfString:findString withString:replaceString ¬
      options:(current application's NSRegularExpressionSearch) range:{0, length of dataString}) as text
end cngStr:intoString:inString:
on reMatch:findPattern inText:theText
   set theNSString to current application's NSString's stringWithString:theText
   set theOptions to ((current application's NSRegularExpressionDotMatchesLineSeparators) as integer) + ((current application's NSRegularExpressionAnchorsMatchLines) as integer)
   set theRegEx to current application's NSRegularExpression's regularExpressionWithPattern:findPattern options:theOptions |error|:(missing value)
   set theFinds to theRegEx's matchesInString:theNSString options:0 range:{location:0, |length|:theNSString's |length|()}
   set theFinds to theFinds as list -- so we can loop through
   set theResult to {} -- we will add to this
   repeat with i from 1 to count of items of theFinds
      set theRange to (item i of theFinds)'s range()
      set end of theResult to (theNSString's substringWithRange:theRange) as string
   end repeat
   return theResult
end reMatch:inText:
on regexFindWithCapture:thePattern fromString:theString resultTemplate:templateStr
   set theString to current application's NSString's stringWithString:theString
   set theRegEx to current application's NSRegularExpression's regularExpressionWithPattern:thePattern options:0 |error|:(missing value)
   set theFinds to theRegEx's matchesInString:theString options:0 range:{0, theString's |length|()}
   set theResult to current application's NSMutableArray's array()
   repeat with aFind in theFinds
      set foundString to (theRegEx's replacementStringForResult:aFind inString:theString |offset|:0 template:templateStr)
      (theResult's addObject:foundString)
   end repeat
   return theResult as list
end regexFindWithCapture:fromString:resultTemplate:
on writeString:aString toPath:posixPath
   set anNSString to current application's NSString's stringWithString:aString
   anNSString's writeToFile:posixPath atomically:true encoding:(current application's NSUTF8StringEncoding) |error|:(missing value)
end writeString:toPath: