Skip to content

Instantly share code, notes, and snippets.

@jerrykrinock
Last active July 4, 2018 21:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jerrykrinock/e453a94c598c1b6f4e6c to your computer and use it in GitHub Desktop.
Save jerrykrinock/e453a94c598c1b6f4e6c to your computer and use it in GitHub Desktop.
Demonstrates a boatload of AppleScript techniques, primarily focused on gathering data from a user's Mac account for troubleshooting.
(* Actually running this script requires that several other tools be included into
the script's package. The entire package, which should include the latest version of
this script, may be downloaded from here:
http://sheepsystems.com/files/SSYTroubleZipper.zip *)
property scriptVersion : "0255 published 2014-12-04"
property companyUTI : "com.sheepsystems"
property ourProcessNames : {"Bookdog", "Bookwatchdog", "BookMacster", "Markster", "Smarky", "Synkmark", "Sheep-Sys-Worker", "Sheep-Sys-Quatch", "Sheep-Sys-Thomas", "Sheep-Sys-UrlHandler", "FileAliasWorker"}
-- The above are all acted upon in 'try' blocks, so it's OK if some don't exist in certain context(s).
property companyName : "Sheep Systems"
property limitExidBytes : 1.0E+7
property limitSyncLogBytes : 1.0E+7
property limitEmailAttachment : 1.0E+7
property fileUploaderUrl : "http://www.sheepsystems.com/support/file-uploader/"
property ourChromeExtensionId : "dopbhkfmgllfbimacbeopgifmflgelni"
global bookmarksPaths
global namedOfLocalClientsToGetBookmarksFrom
global genericTitle
global logText
global progress
global gotAuthorization
-- Do not do the following, because it will *compile* the path to *my* home folder into the script, causing it to fail when run in other Mac accounts!
--property posixHome : POSIX path of (path to home folder) <-- Do not do this
set posixHome to POSIX path of (path to home folder)
set shoeboxDocDirPath to posixHome & "Library/Application Support/BookMacster"
set bookmarkshelfDocsDirPath to shoeboxDocDirPath & "/Bookmarkshelf Documents"
set appInfos to {{appName:"Bookdog", hasDocuments:false, shoeboxFilename:missing value, docUTI:"com.sheepsystems.bdogmigr", docType:"bdogmigr", defaultDocDirPath:posixHome, localClientApps:{"Camino", "Firefox", "iCab", "OmniWeb", "Opera", "Safari"}, appAppSupportName:"Bookdog", canSync:true}, {appName:"BookMacster", hasDocuments:"many", shoeboxFilename:missing value, docUTI:"com.sheepsystems.bkmslf", docType:"bkmslf", defaultDocDirPath:bookmarkshelfDocsDirPath, localClientApps:{"Camino", "Firefox", "Google Chrome", "iCab", "OmniWeb", "Opera", "Safari"}, appAppSupportName:"BookMacster", canSync:true}, {appName:"Markster", shoeboxFilename:"Markster.bkmslf", hasDocuments:"one", docUTI:"com.sheepsystems.bkmslf", docType:"bkmslf", defaultDocDirPath:shoeboxDocDirPath, localClientApps:{"Camino", "Firefox", "Google Chrome", "iCab", "OmniWeb", "Opera", "Safari"}, appAppSupportName:"BookMacster", canSync:true}, {appName:"Smarky", shoeboxFilename:"Smarky.bkmslf", hasDocuments:"one", docUTI:"com.sheepsystems.bkmslf", docType:"bkmslf", defaultDocDirPath:shoeboxDocDirPath, localClientApps:{"Safari"}, appAppSupportName:"BookMacster", canSync:true}, {appName:"Synkmark", shoeboxFilename:"Synkmark.bkmslf", hasDocuments:"one", docUTI:"com.sheepsystems.bkmslf", docType:"bkmslf", defaultDocDirPath:shoeboxDocDirPath, localClientApps:{"Camino", "Firefox", "Google Chrome", "iCab", "OmniWeb", "Opera", "Safari"}, appAppSupportName:"BookMacster", canSync:true}}
set bookmarksPaths to {}
set namedOfLocalClientsToGetBookmarksFrom to {}
set cachesNeeded to {}
set genericTitle to "Trouble Zipper" & " ver " & scriptVersion
set logText to ""
set gotAuthorization to false
set quitButton to "Quit"
set aLooseFile to "A \"loose\" file (Advanced)"
set posixLibrary to (POSIX path of (path to home folder)) & "/Library"
set appNames to {}
repeat with appInfo in appInfos
set end of appNames to appName of appInfo
end repeat
set pathToMe to ((POSIX path of (path to me)) as string)
-- See Note 20131005
-- for testing
--set zipName to "Bkmx_Trouble"
--set tempDirPath to posixHome & "Desktop/" & zipName & "/"
--set cmd to "cd ~/Desktop ; /bin/rm -Rf " & zipName & " ; /bin/mkdir " & zipName
--do shell script cmd
tryShellScript("This command should fail!")
catLog("Produced by Sheep Systems Trouble Zipper version " & scriptVersion)
set aPrompt to "Sheep Systems Trouble Zipper will zip up an archive of data and place it on your Desktop. Sending us this archive helps us to more quickly resolve uncommon problems, with fewer emails.
Which application are you having trouble with?"
choose from list appNames with title genericTitle with prompt aPrompt cancel button name quitButton
set aAnswer to result
if aAnswer is false then
return
end if
catLog("Which app? ANSWER: " & aAnswer)
set appName to first item of aAnswer
set appInfo to missing value
repeat with anAppInfo in appInfos
if ((appName of anAppInfo) as string) is (appName as string) then
set appInfo to anAppInfo
exit repeat
end if
end repeat
if appName is false then return
set zipName to appName & "_Trouble"
tell application "Finder"
set appAppSupportFolder to folder (appAppSupportName of appInfo) of folder "Application Support" of folder "Library" of home
end tell
set aPrompt to "The information gathered by this script will be:
• Your answers to a few questions
• Versions of Mac OS, web browsers and
" & companyName & " apps
• Computer's Processor and Model
• User LaunchAgent and LaunchDaemon plist files
• Messages which " & companyName & " apps wrote to
your System Log during the last week
• Data created by " & companyName & " apps:
• Crash Reports
• Preferences
• Local Settings
• Command Logs
• Info about your Firefox and Chrome extensions and preferences
• Bookmarks (optional, later)
We'll shall destroy this data as soon as you direct, or indicate that the issue is resolved, and treat it securely as stated in sections 2.3 and 3.0 of our Privacy Policy."
repeat
display dialog aPrompt with title genericTitle buttons {quitButton, "View Policy", "Just Continue"} default button "Just Continue"
set buttonReturned to button returned of result
if buttonReturned is quitButton then
return
else if buttonReturned is "View Policy" then
open location "http://sheepsystems.com/privacy/"
delay 5
else
exit repeat
end if
end repeat
tell me to activate
set AppleScript's text item delimiters to {}
-- Clean out anything from a previous run of this tool, and make a new working directory
set cmd to "cd ~/Desktop ; /bin/rm -f " & zipName & ".zip"
set aResult to tryShellScriptAndVerifySilentResult(cmd)
if aResult is not missing value then
set aMsg to "Could not remove an older zip file because " & toString(aResult) & "Please remove the old " & zipName & ".zip from your Desktop yourself, then retry this script."
display dialog aMsg
catLog(aMsg)
end if
set cmd to "cd ~/Desktop ; /bin/rm -Rf " & zipName
set aResult to tryShellScriptAndVerifySilentResult(cmd)
if aResult is not missing value then
set aMsg to "Could not remove an older output directory because " & toString(aResult) & "Please remove the old " & zipName & " directory from your Desktop yourself, then retry this script."
display dialog aMsg
catLog(aMsg)
end if
set cmd to "cd ~/Desktop ; /bin/mkdir " & (quoted form of zipName)
set aResult to tryShellScriptAndVerifySilentResult(cmd)
if aResult is not missing value then
set aMsg to "Could not create new output directory because " & toString(aResult)
display dialog aMsg
catLog(aMsg)
end if
-- See what Local Caches and Sync Snapshots are available
set filesFound to {}
try
tell application "Finder"
log ("aasfolder = " & appAppSupportFolder)
set filesFound to files of appAppSupportFolder
end tell
end try
-- Search for local extore caches…
set sqlFilenames to {}
tell application "Finder"
repeat with aFile in filesFound
set filename to name of aFile
log ("aas item : " & filename)
tell me to set doesContain to verifyTextContains(filename, "|")
if doesContain then
tell me to set doesContain to verifyTextContains(filename, ".sql")
if doesContain then
set sqlFilenames to sqlFilenames & filename
end if
end if
end repeat
end tell
-- Search for the raw downloaded data from web apps (webdata files)…
try
tell application "Finder"
set foldersFound to folders of folder "SyncSnapshots" of appAppSupportFolder
end tell
end try
set webdataFilenames to {}
try
tell application "Finder"
repeat with aFolder in foldersFound
set folderName to name of aFolder
tell me to set doesContain to verifyTextContains(filename, ".webdata")
if doesContain then
set webdataFilenames to webdataFilenames & filename
end if
end repeat
end tell
end try
-- Summarize
set candidateClients to displayizeFilenames(sqlFilenames, ".sql") & displayizeFilenames(webdataFilenames, ".webdata")
set candidateClients to candidateClients & (localClientApps of appInfo)
set candidateClients to candidateClients & aLooseFile
set candidateClients to deleteDuplicates(candidateClients)
set aPrompt to "If the problem appears when working with bookmarks from a particular web browser or Client, it is often very helpful to include the current bookmarks data, recent snapshots of it, and preferences from the Client web browser.
The bookmarks data contains bookmark names, URLs, tags or labels, possibly times visited, and also any comments or descriptions you have entered. It does not include any site passwords, unless you have entered these into bookmarks names, comments or descriptions (which is not a good idea in any case!)."
display dialog aPrompt with title (genericTitle & " : Question B") buttons {quitButton, "Don't Include Bookmarks", "Include Bookmarks"} default button "Include Bookmarks"
set aAnswer to button returned of result
catLog("Include Bookmarks? ANSWER: " & aAnswer)
if aAnswer is "Include Bookmarks" then
set aPrompt to "Please select the browsers and other Clients whose bookmarks may be involved. Note that if you imported from Browser A to Browser B, and then had trouble when exporting to Browser B, the trigger may actually be in Browser A.
Hold down the ⌘ key to select more than one."
choose from list candidateClients with title genericTitle with prompt aPrompt OK button name "Done" cancel button name "Quit" with multiple selections allowed and empty selection allowed
set namedOfLocalClientsToGetBookmarksFrom to result
if namedOfLocalClientsToGetBookmarksFrom is false then return
catLog("Choose Clients? ANSWER: " & namedOfLocalClientsToGetBookmarksFrom)
else if aAnswer is quitButton then
return
end if
set getFirefox to false
repeat with clientName in namedOfLocalClientsToGetBookmarksFrom
set clientName to clientName as string -- I don't know why this is needed, but it is!
if clientName is aLooseFile then
set aPrompt to "Select " & aLooseFile & " you wish to include."
try -- So that "cancel" will not abort script or result in nonsense POSIX path
choose file with prompt aPrompt
set end of bookmarksPaths to POSIX path of result
end try
else
addInfoForBrowser(clientName, pathToMe)
if clientName is "Firefox" then
set getFirefox to true
end if
end if
end repeat
set getDiaries to false
if canSync of appInfo is true then
set aPrompt to appName & " writes Sync Log entries when it imports or exports any bookmarks.
Sync Logs include bookmark and folder names, and any changed attributes, possibly including URLs (i.e., http://… addresses)."
display dialog aPrompt with title (genericTitle & " : Question L") buttons {quitButton, "Don't Include Sync Logs", "Include Sync Logs"} default button "Include Sync Logs"
set aAnswer to button returned of result
catLog("Include Sync Logs? ANSWER: " & aAnswer)
if aAnswer is "Include Sync Logs" then
set getDiaries to true
else if aAnswer is quitButton then
return
end if
end if
set getBkmslfs to false
set bkmslfPaths to {}
if hasDocuments of appInfo is "many" then
set aPrompt to "Question F. (Last Question)
May we include your Bookmarkshelf Document(s)? (.bkmslf files)
Answer \"Manually\" to pick troublesome .bkmslf file(s) yourself. You should do this if you know the name of your document, or are a new user who has only created one. Trouble Zipper will start you out in the default location where BookMacster creates .bkmslf files.
Answer \"Search\" to search your hard drive for .bkmslf files. This may take a few minutes but is recommended if you think you have created multiple .bkmslf files and are not sure which one you're having trouble with. We'll list the paths of the documents we find."
display dialog aPrompt with title (genericTitle & " : Question F") buttons {"Don't Include", "Manually", "Search"} default button "Search"
set aAnswer to button returned of result
catLog("Include bkmslf files? ANSWER: " & aAnswer)
set getBkmslfs to aAnswer
set anyMore to true
if getBkmslfs is "Manually" then
set docType to docType of appInfo
set defaultDocDirPath to defaultDocDirPath of appInfo
set didDoDefaultDocDirPath to false
repeat while anyMore is true
set aPrompt to "Please select the troublesome ." & docType & " file(s) to include."
-- We 'try' in case "Bookmarkshelf Documents" subfolder does not exist
try
if didDoDefaultDocDirPath is false then
try
set docDirPath to (POSIX file defaultDocDirPath) as alias
on error
-- This will happen if defaultDocDirPath does not exist.
set docDirPath to (POSIX file posixLibrary) as alias
end try
set didDoDefaultDocDirPath to true
else
set docDirPath to (POSIX file posixLibrary) as alias
end if
set docAliases to {}
try
-- We 'try' so that script does not exit if user chooses no files and clicks 'cancel'. Unfortunately, 'choose file' does not allow me to change the name of the 'Cancel" button to something more appropriate like 'None'.
set docAliases to choose file with prompt aPrompt default location docDirPath with multiple selections allowed
end try
repeat with docAlias in docAliases
set docPath to POSIX path of docAlias
set end of bkmslfPaths to docPath
end repeat
end try
set aPrompt to "Any more, maybe in other folders?"
display dialog aPrompt with title genericTitle buttons {"More", "Done"} default button "Done"
set aAnswer to button returned of result
if aAnswer is "Done" then
set anyMore to false
end if
end repeat
end if
else if hasDocuments of appInfo is "one" then
set aPrompt to "Question F. (Last Question)
May we include your document (.bkmslf) file, which contains your bookmarks in " & appName & "?"
display dialog aPrompt with title (genericTitle & " : Question F") buttons {"Don't Include", "Include Document"} default button "Include Document"
set aAnswer to button returned of result
catLog(aPrompt & " ANSWER: " & aAnswer)
set getBkmslfs to aAnswer
if getBkmslfs is "Include Document" then
set docPath to (defaultDocDirPath of appInfo) & "/" & (shoeboxFilename of appInfo)
set end of bkmslfPaths to docPath
end if
end if
set tempDirPath to posixHome & "Desktop/" & zipName & "/"
set browserBookmarksOutDir to tempDirPath & "ExtoreBookmarks/"
set cmd to "/bin/mkdir " & browserBookmarksOutDir
do shell script cmd
startScriptView(pathToMe)
set logText to logText & "User's answer's are complete.
Beginning to gather data at " & (current date) & "
Time Zone is " & timeZone() & " hours from GMT
" as string
set bookmarksPathLog to (count of bookmarksPaths) & " bookmarksPaths…" & return
if (count of bookmarksPaths) is greater than 0 then
appendScriptView("Copying Clients' Bookmarks Files and Snapshots")
set textFilePath to browserBookmarksOutDir & "_OriginalPaths.txt"
set i to 1
set pathsString to ""
repeat with aPath in bookmarksPaths
set bookmarksPathLog to bookmarksPathLog & i & ": " & aPath & return
set filename to getLastPathComponent(aPath)
set cmd to "/bin/cp -Rp " & (quoted form of aPath) & " " & quoted form of (browserBookmarksOutDir & "[" & i & "]" & filename)
set cmdResult to 9876 -- was 9999
try
set cmdResult to do shell script cmd
end try
-- If successful, cmdResult will be an empty string
if cmdResult is greater than 0 then
set bookmarksPathLog to bookmarksPathLog & " Failed with exit code " & cmdResult & " for command: " & cmd & return
else
set pathsString to pathsString & " " & aPath & return
set aLine to "[" & i & "] " & aPath as string
writeTextToPosixPath(textFilePath, aLine & return, true)
set bookmarksPathLog to bookmarksPathLog & " Succeeded." & return
end if
set i to i + 1
end repeat
appendScriptView(pathsString)
end if
catLog(return & bookmarksPathLog)
if getBkmslfs is "Search" then
appendScriptView("Searching for BookMacster Bookmarkshelf Documents. This may take a few minutes. You can leave this running and go do something else.")
set toolPath to quoted form of (pathToMe & "Contents/MacOS/CarbonSearch")
set cmd to toolPath & " -f .bkmslf"
set bkmslfPathsString to do shell script cmd
set bkmslfPathCandidates to split(bkmslfPathsString, return)
set bkmslfPaths to {}
-- SSYCarbonSearch found files with ".bkmslf" anywhere in the name. We now filter in only those which have it as a filename extension.
repeat with candidatePath in bkmslfPathCandidates
set aLength to length of candidatePath
set docType to docType of appInfo
set extension to "." & docType
if (hasSuffix(candidatePath, extension)) then
if (verifyTextContains(candidatePath, "/.Trash/") is false) then
set end of bkmslfPaths to candidatePath
end if
end if
end repeat
end if
set bkmslfsOut to tempDirPath & "Bkmslfs/"
set cmd to "/bin/mkdir " & bkmslfsOut
do shell script cmd
if (count of bkmslfPaths) is greater than 0 then
appendScriptView("Copying " & (count of bkmslfPaths) & " Document Files")
set textFilePath to bkmslfsOut & "_OriginalPaths.txt"
set i to 1
set pathsString to ""
repeat with bkmslfPath in bkmslfPaths
set pathsString to pathsString & " " & bkmslfPath & return
set filename to getLastPathComponent(bkmslfPath)
set cmd to "/bin/cp -pX " & (quoted form of bkmslfPath) & " " & quoted form of (bkmslfsOut & "[" & i & "]" & filename)
set errNumber to 0
try
do shell script cmd
on error errStr number errNum
catLog("/bin/cp failed and returned error " & errNum & " : " & errStr & " when attempting to copy " & (quoted form of bkmslfPath))
if (errNum is 0) or (errNum is missing value) then
set errNumber to 1
end if
end try
if errNumber is 0 then
set aLine to "[" & i & "] " & bkmslfPath as string
writeTextToPosixPath(textFilePath, aLine & return, true)
end if
set i to i + 1
end repeat
appendScriptView(pathsString)
end if
appendScriptView("Getting preferences")
-- Add prefs to zip directory
repeat with aProcessName in ourProcessNames
set aBundleID to bundleIdentifier(companyUTI, aProcessName)
set aPath to posixHome & "Library/Preferences/" & aBundleID & ".plist"
set cmd to "/bin/cp -pX " & quoted form of aPath & " " & tempDirPath
tryShellScript(cmd)
end repeat
set unescapedAppSupportDir to posixHome & "Library/Application Support/" & (appAppSupportName of appInfo) & "/"
-- We're going to escape the spaces so that we do not need to quote the path. We'll need an unquoted path in order to use file globbing to get Setting*.sql, Exids*.sql, and Diaries*.sql.
set appSupportDir to searchAndReplace(unescapedAppSupportDir, " ", "\\ ")
set appSupportOut to tempDirPath & "AppSupport-not-incl-SyncSnapshots/"
set cmd to "/bin/mkdir " & appSupportOut
tryShellScript(cmd)
if (appAppSupportName of appInfo) is "BookMacster" then
appendScriptView("Listing " & appSupportDir)
set cmd to "/bin/ls -alww " & appSupportDir
appendScriptView(" cmd: " & cmd)
set appSupportCatalog to "Sorry, couldn't even try"
try
set appSupportCatalog to tryShellScript(cmd)
-- tryShellScript() may return 'missing value'
on error errString number errNumber
catLog("AppleScript error: " & errNumber & " : " & errString)
set appSupportCatalog to "Sorry, ls failed"
end try
if appSupportCatalog is missing value then set appSupportCatalog to "Sorry, ls command failed"
catLog("Contents of " & appSupportDir & return & appSupportCatalog)
end if
if (appAppSupportName of appInfo) is "BookMacster" then
appendScriptView("Getting Logs from " & appSupportDir)
-- Add Logs database to zip directory
try
set aPath to appSupportDir & "Logs.sql"
set cmd to "/bin/cp -pX " & aPath & " " & appSupportOut
tryShellScript(cmd)
on error errorString number aCode
catLog("Getting Logs.sql, error occurred: " & aCode & " occurred. Details: " & errorString)
end try
end if
if (appAppSupportName of appInfo) is "BookMacster" then
appendScriptView("Getting Local Settings from " & appSupportDir)
-- Add Settings databases to zip directory
set aPath to appSupportDir & "Settings*.sql"
set cmd to "/bin/cp -pX " & aPath & " " & appSupportOut
tryShellScript(cmd)
end if
if (appAppSupportName of appInfo) is "BookMacster" then
appendScriptView("Getting Exids from " & appSupportDir)
-- Add Exids databases to zip directory
try
set aPath to appSupportDir & "Exids*.sql"
set latestFilenamez to latestFilenames(aPath, limitExidBytes)
repeat with filename in latestFilenamez
set aPath to appSupportDir & filename
set cmd to "/bin/cp -pX " & aPath & " " & appSupportOut
tryShellScript(cmd)
end repeat
on error errorString number aCode
catLog("Getting Exids, error occurred: " & aCode & " occurred. Details: " & errorString)
end try
end if
if getDiaries is true then
appendScriptView("Getting Sync Logs from " & appSupportDir)
-- Add Diaries database to zip directory
try
set aPath to appSupportDir & "Diaries*.sql"
set latestFilenamez to latestFilenames(aPath, limitSyncLogBytes)
repeat with filename in latestFilenamez
set aPath to appSupportDir & filename
set cmd to "/bin/cp -pX " & aPath & " " & appSupportOut
tryShellScript(cmd)
end repeat
on error errorString number aCode
catLog("Getting Sync Logs, error occurred: " & aCode & " occurred. Details: " & errorString)
end try
end if
if getFirefox is true then
appendScriptView("Getting Firefox Backups from " & appSupportDir)
try
-- Add Firefox backups folder to zip directory
set aPath to appSupportDir & "Firefox\\ Backups"
set cmd to "/bin/cp -pXR " & aPath & " " & appSupportOut
tryShellScript(cmd)
on error errorString number aCode
catLog("Getting Firefox, error occurred: " & aCode & " occurred. Details: " & errorString)
end try
end if
if getBkmslfs is not false then
appendScriptView("Getting document files")
try
repeat with bkmslfPath in bkmslfPaths
set cmd to "/bin/" & quoted form of bkmslfPath & " " & bkmslfsOut
tryShellScript(cmd)
end repeat
on error errorString number aCode
catLog("Getting Bookmarkshelf document files, error occurred: " & aCode & " occurred. Details: " & errorString)
end try
end if
appendScriptView("Getting Permissions on LaunchAgents")
set userShortName to short user name of (system info)
set testResult to "The following LaunchAgent permissions probes will print Access Control Lists (ACLs), and also flags, if any. Beware of any 'uchg' in flags. This user is " & userShortName & "."
catLog(testResult)
set aPath to "~"
set cmd to "ls -lde@O " & aPath
set aResult to tryShellScript(cmd)
catLog("Result of " & cmd & ":" & return & aResult)
set aPath to "~/Library"
set cmd to "ls -lde@O " & aPath
set aResult to tryShellScript(cmd)
catLog("Result of " & cmd & ":" & return & aResult)
set aPath to "~/Library/LaunchAgents"
set cmd to "ls -lde@O " & aPath
set aResult to tryShellScript(cmd)
catLog("Result of " & cmd & ":" & return & aResult)
set aPath to "~/Library/Safari"
set cmd to "ls -le@O " & aPath
set aResult to tryShellScript(cmd)
catLog("Result of " & cmd & ":" & return & aResult)
set aPath to "~/Library/Application\\ Support/Firefox/Profiles"
set cmd to "ls -le@O " & aPath
set aResult to tryShellScript(cmd)
catLog("Result of " & cmd & ":" & return & aResult)
set aPath to "~/Library/Application\\ Support/Firefox/Profiles/*.default"
set cmd to "ls -le@O " & aPath
set aResult to tryShellScript(cmd)
catLog("Result of " & cmd & ":" & return & aResult)
set aPath to "~/Library/Application\\ Support/Google/Chrome/Default"
set cmd to "ls -le@O " & aPath
set aResult to tryShellScript(cmd)
catLog("Result of " & cmd & ":" & return & aResult)
set aPath to "~/Library/Application\\ Support/No/Such/Directory"
set cmd to "ls -le@O " & aPath
set aResult to tryShellScript(cmd)
catLog("Result of " & cmd & ":" & return & aResult)
catLog("End of LaunchAgent permissions probes")
appendScriptView("Getting LaunchAgents and LaunchDaemons")
try
set destinPath to tempDirPath & "LaunchStuff-AllUsers"
set cmd to "/bin/rm -Rf " & destinPath
tryShellScript(cmd)
set cmd to "/bin/mkdir " & destinPath
tryShellScript(cmd)
set sourcePath to "/Library/Launch*"
set cmd to "/bin/cp -Rp " & sourcePath & " " & destinPath
tryShellScript(cmd)
end try
try
set destinPath to tempDirPath & "LaunchStuff-ThisUser"
set cmd to "/bin/rm -Rf " & destinPath
tryShellScript(cmd)
set cmd to "/bin/mkdir " & destinPath
tryShellScript(cmd)
set sourcePath to posixHome & "Library/Launch*"
set cmd to "/bin/cp -Rp " & sourcePath & " " & destinPath
tryShellScript(cmd)
end try
appendScriptView("Testing access to launchd agents in ~/Library/LaunchAgents")
set testResult to testAccessToDirectory("~")
catLog(testResult)
set testResult to testAccessToDirectory("~/Library")
catLog(testResult)
set testResult to testAccessToDirectory("~/Library/LaunchAgents")
catLog(testResult)
set crashReportsOut to tempDirPath & "CrashReports/"
set cmd to "/bin/mkdir " & crashReportsOut
tryShellScript(cmd)
appendScriptView("Getting info on Chrome Extensions and Preferences")
-- TODO: Get all Chrome profiles instead of this…
set chromeProfiles to {"Default"}
try -- Because we'll take whatever we can get
set destinDir to tempDirPath & "Chrome-App-Support/"
set cmd to "/bin/rm -Rf " & destinDir
tryShellScript(cmd)
set cmd to "/bin/mkdir " & destinDir
tryShellScript(cmd)
set chromeSource to posixHome & "Library/Application Support/Google/Chrome/"
set wantedFilename to "Local State"
set sourcePath to chromeSource & wantedFilename
set destinPath to destinDir & wantedFilename
set ok to copyPath(sourcePath, destinPath)
set wantedFilename to "External Extensions.json"
set sourcePath to chromeSource & wantedFilename
set destinPath to destinDir & wantedFilename
copyPath(sourcePath, destinPath)
repeat with profile in chromeProfiles
set destinDir to tempDirPath & "Chrome-App-Support/" & profile & "/"
set cmd to "/bin/rm -Rf " & destinDir
tryShellScript(cmd)
set cmd to "/bin/mkdir " & destinDir
tryShellScript(cmd)
set chromeSource to posixHome & "Library/Application Support/Google/Chrome/" & profile & "/"
-- Starting with verison 244, we only copy *our* Chrome extension, because of one user who had about 100 Chrome extensions totalling 70 MB.
set wantedFilename to "Extensions/" & ourChromeExtensionId
set sourcePath to chromeSource & wantedFilename
set destinPath to destinDir & wantedFilename
copyPath(sourcePath, destinPath)
set wantedFilename to "Preferences"
set sourcePath to chromeSource & wantedFilename
set destinPath to destinDir & wantedFilename
copyPath(sourcePath, destinPath)
set cmd to "/bin/ls -alww " & quoted form of chromeSource & " | grep \"Local Storage\""
set someInfo to tryShellScript(cmd)
set someInfo to "ls (list) of Local Storage itself for Chrome profile " & profile & ":" & return & someInfo
catLog(someInfo)
set localStoragePath to posixHome & "Library/Application Support/Google/Chrome/" & profile & "/Local Storage/"
set cmd to "/bin/ls -alww " & quoted form of localStoragePath
set someInfo to tryShellScript(cmd)
set someInfo to "ls (list) of Local Storage contents for Chrome profile " & profile & ":" & return & someInfo
catLog(someInfo)
set ourLocalStorageFilename to "chrome-extension_" & ourChromeExtensionId & "_0.localstorage"
set ourLocalStoragePath to localStoragePath & ourLocalStorageFilename
set cmd to "/bin/ls -alww " & quoted form of (ourLocalStoragePath)
set someInfo to tryShellScript(cmd)
catLog("ls (list) of *our* Local Storage file for Chrome profile " & profile & ":" & return & someInfo)
set destinDir to tempDirPath & "Chrome-App-Support/" & profile & "/LocalStorage/"
set cmd to "/bin/rm -Rf " & destinDir
tryShellScript(cmd)
set cmd to "/bin/mkdir " & destinDir
tryShellScript(cmd)
set destinPath to destinDir & ourLocalStorageFilename
copyPath(ourLocalStoragePath, destinPath)
end repeat
end try
appendScriptView("Getting any crash reports")
try -- Because we'll take whatever we can get
-- Add user and system crash reports to zip directory
repeat with aProcessName in ourProcessNames
addCrashReports(crashReportsOut, aProcessName, "~", aProcessName & "_User_Crashes")
addCrashReports(crashReportsOut, aProcessName, "", aProcessName & "_System_Crashes")
end repeat
end try
appendScriptView("Getting " & companyName & " app versions")
set daVersions to ""
repeat with aAppName in appNames
set daVersions to daVersions & (reportVersionOfApplicationId(companyUTI & "." & aAppName)) & return
end repeat
set daVersions to trim(daVersions, "
", 1)
catLog(daVersions)
appendScriptView("Getting web browser versions")
set daVersions to ""
set daVersions to daVersions & (reportVersionOfApplicationId("org.mozilla.Camino")) & return
set daVersions to daVersions & (reportVersionOfApplicationId("com.google.Chrome")) & return
set daVersions to daVersions & (reportVersionOfApplicationId("org.chromium.Chromium")) & return
set daVersions to daVersions & (reportVersionOfApplicationId("org.mozilla.Firefox")) & return
set daVersions to daVersions & (reportVersionOfApplicationId("de.icab.iCab")) & return
set daVersions to daVersions & (reportVersionOfApplicationId("com.omnigroup.OmniWeb5")) & return
set daVersions to daVersions & (reportVersionOfApplicationId("com.operasoftware.Opera")) & return
set daVersions to daVersions & (reportVersionOfApplicationId("com.apple.Safari")) & return
catLog(daVersions)
appendScriptView("Getting info on Firefox extensions installed (carefully)")
-- Added this 'try' block for G. Zwaenepoel error -1728
try
set toolPath to quoted form of (pathToMe & "Contents/MacOS/FirefoxProfileFinder")
set profileRecordsString to tryShellScript(toolPath)
set profileRecords to split(profileRecordsString, return)
set foxtensionInfo to ""
set foxWorkingName to "foxworks"
set foxWorkingDir to tempDirPath & foxWorkingName & "/"
set tempZipPath to foxWorkingDir & "zippedExtension.zip"
set tempUnzipPath to foxWorkingDir & "unzippedExtension"
set cmd to "cd " & tempDirPath & " ; /bin/mkdir " & foxWorkingName
tryShellScript(cmd)
repeat with profileRecord in profileRecords
set profileRecordColumns to split(profileRecord, " ")
set profileName to item 1 of profileRecordColumns
set profilePath to item 2 of profileRecordColumns
set extensionPath to profilePath & "/extensions/"
set cmd to "/bin/ls -1wwwww " & quoted form of extensionPath
-- try
set extensionDirsString to tryShellScript(cmd)
set extensionDirs to split(extensionDirsString, return)
set nExts to count of extensionDirs
set foxtensionInfo to foxtensionInfo & return & return & "***** PROFILE NAME: " & profileName & return & "***** PATH: " & profilePath & return & "***** Found " & nExts & " extensions:" & return
repeat with extensionDir in extensionDirs
set fileExtension to getFilenameExtension(extensionDir)
if fileExtension is "xpi" then
-- This extension is zipped. Copy to temporary directory and unzip.
-- try
set cmd to "/bin/cp -f " & quoted form of (extensionPath & extensionDir) & " " & tempZipPath
tryShellScript(cmd)
set cmd to "/usr/bin/unzip -o " & tempZipPath & " -d " & tempUnzipPath
tryShellScript(cmd)
set installrdfPath to tempUnzipPath & "/install.rdf"
-- end try
else
-- This extension is already unzipped
set installrdfPath to extensionPath & extensionDir & "/install.rdf"
end if
set rdfText to readTextFile(installrdfPath)
if rdfText is missing value then
set rdfText to "File not found!"
else
-- Change "\n" from the rdf file text to "\r"
set rdfText to searchAndReplace(rdfText, "
", return)
end if
set foxtensionInfo to foxtensionInfo & return & "*** install.rdf for " & extensionDir & "***" & return & rdfText & return
end repeat
end repeat
-- Remove the temporary directory into which we unzipped zipped extensions
set cmd to "/bin/rm -Rf " & foxWorkingDir
tryShellScript(cmd)
set fxInfoFilename to "Firefox-Extension-Info.txt"
writeTextToPosixPath(tempDirPath & fxInfoFilename, foxtensionInfo, false)
catLog("Succeeded getting " & fxInfoFilename)
on error errorString number aCode
catLog("Getting Firefox extension info, error occurred: " & aCode & " occurred. Details: " & errorString)
end try
appendScriptView("Getting filtered list of loaded agents")
repeat with anAppName in appNames
try
set filteredList to {}
set cmd to "launchctl list"
set launchctlResult to do shell script cmd
set agentLines to split(launchctlResult, ASCII character 13)
if (count of agentLines) is less than 2 then
set agentLines to split(launchctlResult, ASCII character 10)
end if
repeat with agentLine in agentLines
repeat with aAppName in appNames
if (verifyTextContains(agentLine, aAppName)) then
set end of filteredList to agentLine
end if
end repeat
end repeat
set filteredResult to join(ASCII character 10, filteredList)
set launchctlLoadsFilename to "launchctlLoads.txt"
writeTextToPosixPath(tempDirPath & launchctlLoadsFilename, filteredResult, false)
on error errString number errNum
log "errNum=" & errNum & " errString: " & errString
end try
log filteredList
end repeat
appendScriptView("Getting system info.")
try
-- Add system hardware and software to answers Log
set sysProfile to nanoSystemProfile()
catLog(sysProfile)
end try
try
-- Add answers Log to zip directory
set aPath to tempDirPath & "/answers.txt"
writeTextToPosixPath(aPath, logText, false)
end try
delay 2
-- Add filtered console log to zip directory
-- We use separate 'try' blocks because, if an error occurs, it will jump to the end of the block, and we don't want to jump too far.
appendScriptView("Unarchiving system logs")
set logsOutDir to tempDirPath & "ConsoleLogs/"
set cmd to "/bin/mkdir " & logsOutDir
tryShellScript(cmd)
-- We unarchive the previous week's logs first, then filter them for each application in turn
tryUnarchiveWeeksLogsToDirPath(logsOutDir)
set msg to "Filtering system logs to get only entries logged by "
repeat with aProcessName in ourProcessNames
set msg to msg & " " & aProcessName
end repeat
appendScriptView(msg)
-- See Note 20131005
filterMessagesToNewFile(ourProcessNames, logsOutDir, pathToMe)
-- Done with the unarchived previous week's logs.
tryRemoveWeeksConsoleLogsInDirPath(logsOutDir)
-- Zip the zip directory
appendScriptView("Zipping everything up.")
set cmd to "cd ~/Desktop ; /usr/bin/zip -r " & zipName & " " & zipName & " ; /bin/rm -Rf " & zipName
tryShellScript(cmd)
appendScriptView("All done.")
endScriptViewAsking(true)
set zipFilename to zipName & ".zip"
set cmd to "/usr/bin/stat -f %z ~/Desktop/" & zipFilename
set fileSize to tryShellScript(cmd) as integer
set doneButton to "Done"
if fileSize is greater than limitEmailAttachment then
set sendingAdvice to "When you click '" & doneButton & "', two windows will open: An email message to us in your email app, and a window to our File Uploader in your web browser. Please fill in and send the email to us, and use the File Uploader to upload " & zipFilename & " to us."
set needsFileUploader to true
else
set sendingAdvice to "When you click '" & doneButton & "', an email message to us will open in your email app. Please fill in the information, attach the file " & zipFilename & " and send the email to us"
set needsFileUploader to false
end if
set aPrompt to "All done.
The information archive is in a file on your Desktop named:" & return & return & " " & zipName & ".zip" & return & return & sendingAdvice & return & return & "Thank you for helping us to support " & appName & ".
Jerry Krinock"
display dialog aPrompt with title genericTitle buttons {"Done"} default button "Done"
-- Actually, we should percent-escape encode appName and zipName. But they're all ASCII at this time, so it's todo. Maybe could call out to Perl or something. Here's a crude start:
set encodedAppName to searchAndReplace(appName, " ", "%20")
set encodedZipName to searchAndReplace(zipFilename, " ", "%20")
set mailtoThing to "mailto:support@sheepsystems.com?subject=%20" & encodedAppName & "%20Trouble%20Zip&body=%0A%0ASuggested%20Format%3A%0A%0A*%20Here%20is%20what%20I%20did%3A%0A%0A%0A%0A*%20Here%20is%20what%20I%20expected%20to%20happen%3A%0A%0A%0A%0A*%20Here%20is%20what%20happened%20instead%3A"
if needsFileUploader is true then
set mailtoThing to mailtoThing & "%0A%0A%0A%0A**%20Please%20upload%20" & encodedZipName & "%20from%20your%20Desktop%20to%20our%20File%20Uploader."
else
set mailtoThing to mailtoThing & "%0A%0A%0A%0A**%20Please%20attach%20" & encodedZipName & "%20from%20your%20Desktop."
end if
open location mailtoThing
if needsFileUploader is true then
open location fileUploaderUrl
end if
-------------- END OF MAIN PROGRAM
-------------- BEGINNING OF HANDLERS
on displayizeFilenames(filenames, extension)
set displayNames to {}
repeat with filename in filenames
set displayName to searchAndReplace(filename, "|", " ")
set displayName to searchAndReplace(displayName, "thisUser", " ")
set displayName to searchAndReplace(displayName, extension, " ")
-- Do this several times to get rid of multiple consecutive spaces
set displayName to searchAndReplace(displayName, " ", " ")
set displayName to searchAndReplace(displayName, " ", " ")
set displayName to searchAndReplace(displayName, " ", " ")
set displayName to trim(displayName, " ", 2)
set displayNames to displayNames & displayName
end repeat
return displayNames
end displayizeFilenames
-- When done with the files produced by this handler, call tryRemoveWeeksConsoleLogsFromDesktop() to delete them
on tryUnarchiveWeeksLogsToDirPath(dirPath)
repeat with logFile from 8 to 0 by -1 -- in AppleScript, "7 to 0" means "7 thru 0"
appendScriptView("Unarchiving Log " & (logFile as Unicode text))
try
-- If the computer was running each morning at 12:30, log files are "rotated" by the newsyslog program such that
-- logFile =8 means 8 days ago, which is in the file named system.log.7.bz2
-- logFile =7 means 7 days ago, which is in the file named system.log.6.bz2
-- logFile =6 means 6 days ago, which is in the file named system.log.5.bz2
-- …
-- logFile =2 means 2 days ago, which is in the file named system.log.1.bz2
-- logFile =1 means yesterday, which is the file named system.log.0.bz2
-- logFile =0 means today, which is in the file named system.log and has not been compressed yet.
-- However if the computer is not awake at 12:30, then the newsyslog program will not run, and a single log file will contain logs for multiple consecutive days.
if (logFile is greater than 0) then
-- Logs are compressed
-- First, we copy the file from /var/log/ to our temporary directory. One reason to do this is because, if user does not have required admin privileges, any action upon /var/log/xxx.log may fail.
set filename to "system.log." & (logFile - 1)
set sourceLogArchivePath to "/var/log/" & filename & ".bz2"
-- However, we need to make room for the uncompressed log for today at index 0, so we don't use the -1 offset for the destination filename
set filename to "system.log." & logFile
set destinLogArchivePath to dirPath & filename & ".bz2"
set cmd to "/bin/cp -pX " & quoted form of sourceLogArchivePath & " " & quoted form of destinLogArchivePath
try
do shell script cmd
on error errorString number aCode
-- Unfortunately, aCode is useless since cp returns the same exit value, 1, for both inadequate permissions and file not found. So we'll have to parse errorString for the word "Permission", as in "Permission denied".
set badPermissions to verifyTextContains(errorString, "Permission")
if badPermissions then
-- Retry with auth services dialog
if gotAuthorization is false then
-- Prepare the user for the Authentication dialog which is about to appear.
display dialog "We need administrator access in order to read your system logs and filter out relevant entries."
end if
do shell script cmd with administrator privileges
set gotAuthorization to true
-- But wait there's more! Because we used admin privileges, the copy that we just made is owned by root, so filterMessagesToNewFile() won't be able to open it unless we chown so that we own it...
set myShortName to short user name of (system info)
-- The authorization we just received should be good for 5 minutes
set cmd to "/usr/sbin/chown " & quoted form of myShortName & " " & quoted form of destinLogArchivePath
try
if gotAuthorization is true then
do shell script cmd with administrator privileges
else
do shell script cmd
end if
end try
end if
end try
-- Unarchive the copy of this day's .bz2 log file.
set cmd to "usr/bin/bzcat " & destinLogArchivePath & " > " & dirPath & filename
try
if gotAuthorization is true then
do shell script cmd with administrator privileges
else
do shell script cmd
end if
on error errorString number aCode
catLog("Executing: " & cmd & return & ". Error: " & errorString)
end try
-- Now delete the copy of the bz2 file, since we don't need it any more
set cmd to "/bin/rm " & destinLogArchivePath
try
if gotAuthorization is true then
do shell script cmd with administrator privileges
else
do shell script cmd
end if
on error errorString number aCode
catLog("Executing: " & cmd & return & ". Error: " & errorString)
end try
else
-- Get log for today. This is not compressed.
set filename to "system.log"
set sourceLogPath to "/var/log/" & filename
set destinLogPath to dirPath & filename & ".0"
set cmd to "/bin/cp -pX " & quoted form of sourceLogPath & " " & quoted form of destinLogPath
try
do shell script cmd
on error errorString number aCode
-- Unfortunately, aCode is useless since cp returns the same exit value, 1, for both inadequate permissions and file not found. So we'll have to parse errorString for the word "Permission", as in "Permission denied".
set badPermissions to verifyTextContains(errorString, "Permission")
if badPermissions then
-- Retry with auth services dialog
if gotAuthorization is false then
-- Prepare the user for the Authentication dialog which is about to appear.
display dialog "We need administrator access in order to read your system logs and filter out relevant entries."
end if
do shell script cmd with administrator privileges
set gotAuthorization to true
-- But wait there's more! Because we used admin privileges, the copy that we just made is owned by root, so filterMessagesToNewFile() won't be able to open it unless we chown so that we own it...
set myShortName to short user name of (system info)
-- The authorization we just received should be good for 5 minutes
set cmd to "/usr/sbin/chown " & quoted form of myShortName & " " & quoted form of destinLogPath
try
if gotAuthorization is true then
do shell script cmd with administrator privileges
else
do shell script cmd
end if
end try
end if
end try
end if
end try
end repeat
end tryUnarchiveWeeksLogsToDirPath
-- See Note 20131005
on filterConsoleMessages(logPath, appNames, logFile, outDir, pathToMe)
set logFilePartOfName to "tiger"
if logFile is not missing value then
set logFilePartOfName to logFile
end if
set logPath to quoted form of logPath
set destinPath to quoted form of (outDir & "system." & logFilePartOfName & ".log")
set perlFilterToolPath to quoted form of (pathToMe & "Contents/Resources/Scripts/filterConsoleLog.pl")
set cmd to perlFilterToolPath & " " & logPath & " " & destinPath
repeat with aAppName in appNames
set cmd to cmd & " " & quoted form of aAppName
end repeat
appendScriptView(" From " & logFilePartOfName & " logs ago")
tryShellScript(cmd)
end filterConsoleMessages
-- See Note 20131005
on filterMessagesToNewFile(appNames, dirPath, pathToMe)
-- The log file numbered 8 is the oldest. We want oldest first.
repeat with logFile from 8 to 0 by -1
-- Assumes that we have previously run tryUnarchiveWeeksLogsToDirPath()
set unarchivedLogPath to dirPath & "system.log." & logFile
-- Filter and add this day's log to leopardMessages
set dayMessages to missing value
filterConsoleMessages(unarchivedLogPath, appNames, logFile, dirPath, pathToMe)
end repeat
-- Note: Tiger logs are in one big file instead of separate daily files.
-- Todo: Make this work for non-admin users. It probably won't.
set tigerConsoleLogDirs to {}
tell application "Finder"
try
set tigerConsoleLogDir to folder "Console" of folder "Logs" of folder "Library" of startup disk
set tigerConsoleLogDirs to folders of tigerConsoleLogDir
end try
end tell
set tigerMessages to {}
repeat with aFolder in tigerConsoleLogDirs
set user50x to (name of aFolder as string)
set aLogPath to (POSIX path of (tigerConsoleLogDir as string)) & user50x & "/console.log"
filterConsoleMessages(aLogPath, appNames, missing value, dirPath, pathToMe)
end repeat
end filterMessagesToNewFile
on tryRemoveWeeksConsoleLogsInDirPath(dirPath)
try
set leopardMessages to ""
-- The log file numbered 8 is from 8 days ago. We want oldest first.
repeat with day from 8 to 0 by -1
try
set filename to "system.log." & day
set unarchivedLogPath to dirPath & filename
-- Delete the file
set cmd to "/bin/rm " & unarchivedLogPath
appendScriptView("Removing unfiltered log:" & return & " " & unarchivedLogPath)
do shell script cmd
end try
end repeat
end try
end tryRemoveWeeksConsoleLogsInDirPath
on bundleIdentifier(uti, name)
return uti & "." & name
end bundleIdentifier
on searchAndReplace(this_text, search_string, replacement_string)
set oldastid to AppleScript's text item delimiters
set AppleScript's text item delimiters to search_string
-- We're going to split this_text at each searchString. However, this will miss search_string if it is at the beginning or ending of this_text, since no split will be made there. We fix that by temporarily appending a prefix and suffix "X".
set this_text to "X" & this_text & "X"
set the item_list to every text item of this_text
set AppleScript's text item delimiters to replacement_string
set this_text to item_list as string
set AppleScript's text item delimiters to oldastid
-- Remove the temporary prefix and suffix
set aLength to ((count of characters of this_text) - 1)
set this_text to characters 2 thru aLength of this_text as string
return this_text
end searchAndReplace
on verifyTextContains(aText, aTarget)
set oldastid to AppleScript's text item delimiters
set AppleScript's text item delimiters to aTarget
set answer to false
try
set textItems to text items of aText
set nParts to count of textItems
if nParts is greater than 1 then
set answer to true
end if
end try
set AppleScript's text item delimiters to oldastid
return answer
end verifyTextContains
on catLog(addition)
set newLogText to (logText & addition & return & return)
try
set logText to newLogText
end try
end catLog
on addInfoForBrowser(browserName, pathToMe)
local bookmarksPath
set posixHome to POSIX path of (path to home folder)
set SnapshotsDir to posixHome & "Library/Application Support/BookMacster/SyncSnapshots/"
if browserName is "Safari" then
set end of bookmarksPaths to posixHome & "Library/Safari/Bookmarks.plist"
set end of bookmarksPaths to SnapshotsDir & "Safari||thisUser|||"
else if browserName is "Camino" then
set end of bookmarksPaths to posixHome & "Library/Application Support/Camino/bookmarks.plist"
set end of bookmarksPaths to SnapshotsDir & "Camino||thisUser|||"
else if browserName is "Google Chrome" then
set end of bookmarksPaths to posixHome & "Library/Application Support/Google/Chrome/Default/Bookmarks"
set end of bookmarksPaths to posixHome & "Library/Application Support/Camino/bookmarks.plist"
set end of bookmarksPaths to SnapshotsDir & "Chrome||thisUser|||"
else if browserName is "iCab" then
set end of bookmarksPaths to posixHome & "Library/Preferences/iCab/iCab 4 Bookmarks"
set end of bookmarksPaths to SnapshotsDir & "ICab||thisUser|||"
else if browserName is "OmniWeb" then
set end of bookmarksPaths to posixHome & "Library/Application Support/OmniWeb 5/Bookmarks.html"
set end of bookmarksPaths to posixHome & "Library/Application Support/OmniWeb 5/Published.html"
set end of bookmarksPaths to posixHome & "Library/Application Support/OmniWeb 5/Favorites.html"
set end of bookmarksPaths to posixHome & "Library/Application Support/OmniWeb 5/ServerBookmarks"
set end of bookmarksPaths to SnapshotsDir & "OmniWeb||thisUser|||"
else if browserName is "Opera" then
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences/bookmarks.adr"
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences/operaprefs.ini"
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences 1050/bookmarks.adr"
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences 1050/operaprefs.ini"
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences 1150/bookmarks.adr"
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences 1150/operaprefs.ini"
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences 1200/bookmarks.adr"
set end of bookmarksPaths to posixHome & "Library/Preferences/Opera Preferences 1200/operaprefs.ini"
set end of bookmarksPaths to SnapshotsDir & "Opera||thisUser|||"
else if browserName is "Firefox" then
set toolPath to quoted form of (pathToMe & "Contents/MacOS/FirefoxProfileFinder")
set profileRecordsString to do shell script toolPath
set profileRecords to split(profileRecordsString, return)
set profileNames to {}
set profilePaths to {}
set profileNeeded to missing value
repeat with profileRecord in profileRecords
set profileRecordColumns to split(profileRecord, " ")
set end of profileNames to item 1 of profileRecordColumns
set end of profilePaths to item 2 of profileRecordColumns
end repeat
if ((count of profileRecords) is greater than 1) then
set aPrompt to "You have more than one Firefox profile on your Mac account. Please select the Firefox profile you are having (the most) trouble with."
set profileNeeded to choose from list profileNames with title genericTitle with prompt aPrompt OK button name "Done"
catLog(aPrompt & " ANSWER: " & profileNeeded)
if profileNeeded is false then
return
end if
else
set profileNeeded to item 1 of profileNames
end if
set i to 1
repeat with profileName in profileNames
if profileName as string is profileNeeded as string then
set profilePath to item i of profilePaths
set end of bookmarksPaths to profilePath & "/bookmarks.html"
set end of bookmarksPaths to profilePath & "/places.sqlite"
set end of bookmarksPaths to profilePath & "/places.sqlite-shm"
set end of bookmarksPaths to profilePath & "/places.sqlite-wal"
set end of bookmarksPaths to profilePath & "/places.sqlite.corrupt"
set end of bookmarksPaths to profilePath & "/prefs.js"
set end of bookmarksPaths to profilePath & "/bookmarkbackups"
set end of bookmarksPaths to SnapshotsDir & "Firefox|" & profileName & "|thisUser|||"
exit repeat
end if
set i to i + 1
end repeat
else
-- browserName is of the form "Diggo jerrykrinock". Replace the " " with a "|"
set theSplit to split(browserName, " ")
set extoreName to item 1 of theSplit
set profileName to item 2 of theSplit
catLog("Split \"" & browserName & "\" into extoreName=" & extoreName & " and profileName=" & profileName)
set end of bookmarksPaths to SnapshotsDir & extoreName & "|" & profileName & "|thisUser|||"
end if
end addInfoForBrowser
-- If file is not found, returns 'missing value'
-- If file exists but is empty, returns an empty string
on readTextFile(path)
try
-- 'info for' could return a value, but we ignore it
info for path
on error
return missing value
end try
set theFile to open for access (path as POSIX file)
try
set theData to read theFile as text
on error
return ""
end try
close access theFile
return theData
end readTextFile
-- NEWER AND BETTER. Handles UTF8-encoded files, with or without BOM (byte order mark)
--open for access theFile
--set fileContents to (read theFile for (get eof theFile) as «class utf8»)
--close access theFile
on testAccessToDirectory(testDir)
set testText to "This is a test!"
set failedStep to missing value
set aResult to testWriteTextToDir(testText, testDir)
set testFilePath to aPath of result
set success to succeeded of aResult
if success is true then
set aResult to testReadTextFile(testText, testFilePath)
set success to succeeded of aResult
if success is true then
set aResult to testDeleteFilePath(testFilePath)
set success to succeeded of aResult
if success is false then
set failedStep to "deleting"
end if
else
set failedStep to "reading"
end if
else
set failedStep to "writing"
end if
if success is true then
set narrative to "Write, Read and Delete: All succeeded for " & testFilePath
else
set narrative to "Failed " & failedStep & " of " & testFilePath & " with errNum:" & (errNum of aResult) & ": " & (errMsg of aResult)
end if
return narrative
end testAccessToDirectory
on testWriteTextToDir(testText, aDir)
set aDir to addTrailingSlashIfMissing(aDir)
set aPath to aDir & "Test.txt"
set succeeded to true
set errMsg to missing value
set errNum to missing value
try
writeTextToPosixPath(aPath, testText, false)
on error errMsg number errNum
set succeeded to false
end try
set aResult to {aPath:aPath, succeeded:succeeded, errNum:errNum, errMsg:errMsg}
return aResult
end testWriteTextToDir
on testReadTextFile(expectedText, posixPath)
set posixPath to expandHomeTildePathPrefix(posixPath)
set succeeded to true
set errMsg to missing value
set errNum to missing value
try
-- 'info for' could return a value, but we ignore it
info for posixPath
on error errMsg number errNum
set succeeded to false
end try
if succeeded is true then
try
open for access (posixPath as POSIX file)
on error errMsg number errNum
set succeeded to false
end try
end if
if succeeded is true then
set foundText to ""
try
set foundText to (read posixPath for (get eof posixPath) as «class utf8»)
on error errMsg number errNum
set succeeded to false
end try
end if
set compareResult to false
if succeeded is true then
try
if (characters of (foundText)) is equal to (characters of expectedText) then
set compareResult to true
else
set errNum to 444444
set errMsg to "Found: " & foundText & " Expected: " & expectedText
end if
on error errMsg number errNum
set succeeded to false
end try
end if
close access posixPath
set aResult to {aPath:posixPath, succeeded:succeeded, errNum:errNum, errMsg:errMsg}
end testReadTextFile
on testDeleteFilePath(posixPath)
set posixPath to expandHomeTildePathPrefix(posixPath)
set succeeded to true
set errNum to missing value
set errMsg to missing value
set cmd to "rm " & quoted form of posixPath
try
do shell script cmd
on error errMsg number errNum
set succeeded to false
end try
set aResult to {aPath:posixPath, succeeded:succeeded, errNum:errNum, errMsg:errMsg}
end testDeleteFilePath
on writeTextToPosixPath(aPath, newText, doAppend)
-- filename must not end in ".log" or else a "file already open" error will result.
-- Maybe some system logging daemon takes over all .log files??
-- Uses File Read/Write suite of the Standard Additions scripting addition
set oldastid to AppleScript's text item delimiters
set AppleScript's text item delimiters to {""}
set aPath to expandHomeTildePathPrefix(aPath)
set filePath to (POSIX file aPath as text)
set fileRef to open for access filePath with write permission
if doAppend is true then
write newText to fileRef starting at eof
else
-- Wipe out all existing text first
set eof of fileRef to 0
write newText to fileRef
end if
close access fileRef
set AppleScript's text item delimiters to oldastid
end writeTextToPosixPath
on addTrailingSlashIfMissing(aString)
set chars to characters of aString
if ((count of chars) is greater than 1) then
if (last item of chars) is not "/" then
set aString to aString & "/"
end if
else if chars is {"~"} then
set aString to "~/"
else
set aString to "/"
end if
return aString
end addTrailingSlashIfMissing
(* Replaces the "~" POSIX shorthand for home folder with its actual path and returns the resulting path. *)
on expandHomeTildePathPrefix(aPath)
if ((characters of aPath) is equal to (characters of "~")) then
set aPath to (POSIX path of (path to home folder))
-- Remove the trailing /
set aPath to characters 1 thru ((count of characters of aPath) - 1) of aPath as string
else if ((characters of aPath) is equal to (characters of "~/")) then
set aPath to (POSIX path of (path to home folder))
else if aPath begins with "~" then
set home_posix_path to POSIX path of (path to home folder)
set aPath to home_posix_path & characters 3 thru end of aPath
end if
return aPath
end expandHomeTildePathPrefix
on nanoSystemProfile()
return do shell script "/usr/sbin/system_profiler SPHardwareDataType SPSoftwareDataType SPUniversalAccessDataType"
end nanoSystemProfile
on microSystemProfile()
return do shell script "/usr/sbin/system_profiler SPHardwareDataType SPMemoryDataType SPSoftwareDataType SPUniversalAccessDataType SPFrameworksDataType SPApplicationsDataType"
end microSystemProfile
-- trim_chars: A string of chars to trim.
-- Use " \t\n" to trim spaces, tabs and newlines
-- trim_indicator: trim from 0 = beginning, 1 = end, 2 = both
-- returns the trimmed string
on trim(this_text, trim_chars, trim_indicator)
set x to the length of the trim_chars
-- TRIM BEGINNING
if the trim_indicator is in {0, 2} then
repeat while this_text begins with the trim_chars
try
set this_text to characters (x + 1) thru -1 of this_text as string
on error
-- the text contains nothing but the trim characters
return ""
end try
end repeat
end if
-- TRIM ENDING
if the trim_indicator is in {1, 2} then
repeat while this_text ends with the trim_chars
try
set this_text to characters 1 thru -(x + 1) of this_text as string
on error
-- the text contains nothing but the trim characters
return ""
end try
end repeat
end if
return this_text
end trim
on split(someText, delimiter)
if someText is missing value then
return {}
else
set oldastid to AppleScript's text item delimiters
set AppleScript's text item delimiters to delimiter
set someText to someText's text items
set AppleScript's text item delimiters to oldastid
if someText is missing value then
set someText to {}
end if
return someText
end if
end split
on join(joiner, aList)
set nJoiners to ((count of aList) - 1)
set answer to ""
set i to 0
repeat with aItem in aList
set answer to answer & aItem
if i is less than nJoiners then
set answer to answer & joiner
end if
set i to i + 1
end repeat
return answer
end join
on reportVersionOfApplicationId(appId)
set v to "not found"
try
set v to get version of application id appId
end try
set comps to split(appId, ".")
try
set aName to item 3 of comps
on error
set aName to appId
end try
return aName & " version is " & v
end reportVersionOfApplicationId
on addCrashReports(tempDirPath, appName, sourcePrefix, destinName)
-- Make directory for user crash reports in zip directory
set userCrashesZipDir to tempDirPath & destinName
set userCrashesZipDir to searchAndReplace(userCrashesZipDir, " ", "\\ ")
set cmd to "/bin/rm -Rf " & userCrashesZipDir & " ; /bin/mkdir " & userCrashesZipDir
tryShellScript(cmd)
-- Add any user crash reports to zip directory
-- from earlier versions
set aPath to sourcePrefix & "/Library/Logs/CrashReporter/" & appName & "_*"
set bashSafePath to searchAndReplace(aPath, " ", "\\ ")
set cmd to "cp -pX " & bashSafePath & " " & userCrashesZipDir
tryShellScript(cmd)
-- from 10.8, maybe 10.7?
set aPath to sourcePrefix & "/Library/Logs/DiagnosticReports/." & appName & "*"
set bashSafePath to searchAndReplace(aPath, " ", "\\ ")
set cmd to "cp -pX " & bashSafePath & " " & userCrashesZipDir
tryShellScript(cmd)
-- Add user crash history to zip directory
-- (Probably not too interesting, just seems to show path to latest crash report, but what the hell. Leopard only.)
set aPath to sourcePrefix & "/Library/Logs/CrashReporter/." & appName & "*.plist"
set bashSafePath to searchAndReplace(aPath, " ", "\\ ")
set cmd to "cp -pX " & bashSafePath & " " & userCrashesZipDir
tryShellScript(cmd)
end addCrashReports
to geekDateDaysAgo(daysAgo) -- Old_date is text, not a date.
set old_date to ((current date) - 24 * 3600 * daysAgo) as string
set {year:y, month:m, day:d} to date old_date
tell (y * 10000 + m * 100 + d) as string to text 1 thru 4 & text 5 thru 6 & text 7 thru 8
end geekDateDaysAgo
(* removeTrailingSlash indicates whether or not trailing slash should be removed too *)
on removeLastPathComponent(aPath, removeTrailingSlash)
if aPath is missing value then
return missing value
end if
set oldastid to AppleScript's text item delimiters
set AppleScript's text item delimiters to the "/"
set the item_list to every text item of aPath
set lastItem to last item of item_list
if lastItem is not missing value then
set cutLength to (count of lastItem) as string
set wholeLength to length of aPath
set newLength to wholeLength - cutLength
if removeTrailingSlash is true then
set newLength to newLength - 1
end if
set AppleScript's text item delimiters to oldastid
set answer to characters 1 thru newLength of aPath as string
else
set answer to aPath
end if
return answer
end removeLastPathComponent
on deleteDuplicates(aList)
if (aList is missing value) then
return {}
end if
set list2 to {}
repeat with x from 1 to count of items of aList
set n to item x of aList
if n is not in list2 then set end of list2 to n
end repeat
return list2
end deleteDuplicates
-- Returns 'true' or 'false'. Case-insensitive. If aSuffix is an empty string, returns 'true'.
on hasSuffix(aString, aSuffix)
set stringLength to length of aString
set suffixLength to length of aSuffix
--log (stringLength & " -- " & suffixLength)
if stringLength is less than suffixLength then
return false
end if
if stringLength is 0 then
if suffixLength is 0 then
return true
else
return false
end if
end if
if suffixLength is 0 then
return true
end if
set theEnd to characters (stringLength - suffixLength + 1) thru stringLength of aString as string
-- It appears that AppleScript does a case-insensitive comparison in the following:
if theEnd is aSuffix then
return true
end if
return false
end hasSuffix
(* Gets the part of a filename after the last slash *)
on getLastPathComponent(aPath)
set oldastid to AppleScript's text item delimiters
set AppleScript's text item delimiters to the "/"
set the item_list to every text item of aPath
set answer to last item of item_list as string
set AppleScript's text item delimiters to oldastid
return answer
end getLastPathComponent
on removeFilenameExtension(this_name)
if this_name contains "." then
set this_name to ¬
(the reverse of every character of this_name) as string
set x to the offset of "." in this_name
set this_name to (text (x + 1) thru -1 of this_name)
set this_name to (the reverse of every character of this_name) as string
end if
return this_name
end removeFilenameExtension
-- Returns the number of hours relative to GMT. California is -7 or -8.
on timeZone()
return (time to GMT) / hours -- hours is 3600, a built-in constant
end timeZone
-- Returns 'missing value' if no string is equal to targetString
-- Case-sensitive
on indexOfStringInList(targetString, aList)
set i to 1
repeat with aItem in aList
considering case
set compareResult to (aItem = targetString)
end considering
if compareResult is true then
return i
end if
set i to i + 1
end repeat
return missing value
end indexOfStringInList
(*
# Converts the specified object - which may be of any type - into a string representation for logging/debugging.
# Tries hard to find a readable representation - sadly, simple conversion with `as text` mostly doesn't work with non-primitive types.
# An attempt is made to list the properties of non-primitive types (does not always work), and the result is prefixed with the type (class) name
# and, if present, the object's name and ID.
# EXAMPLE
# toString(path to desktop) # -> "[alias] Macintosh HD:Users:mklement:Desktop:"
# To test this subroutine and see the various representations, use the following:
# repeat with elem in {42, 3.14, "two", true, (current date), {"one", "two", "three"}, {one:1, two:"deux", three:false}, missing value, me, path to desktop, front window of application (path to frontmost application as text)}
# log my toString(contents of elem)
# end repeat
Source: mklement0, in http://stackoverflow.com/questions/13653358/how-to-log-objects-to-a-console-with-applescript
*)
on toString(anyObj)
local i, txt, errMsg, orgTids, oName, oId, prefix
set txt to ""
repeat with i from 1 to 2
try
if i is 1 then
if class of anyObj is list then
set {orgTids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {", "}}
set txt to ("{" & anyObj as string) & "}"
set AppleScript's text item delimiters to orgTids # '
else
set txt to anyObj as string
end if
else
set txt to properties of anyObj as string
end if
on error errMsg
# Trick for records and record-*like* objects:
# We exploit the fact that the error message contains the desired string representation of the record, so we extract it from there. This (still) works as of AS 2.3 (OS X 10.9).
try
set txt to do shell script "egrep -o '\\{.*\\}' <<< " & quoted form of errMsg
end try
end try
if txt is not "" then exit repeat
end repeat
set prefix to ""
if class of anyObj is not in {text, integer, real, boolean, date, list, record} and anyObj is not missing value then
set prefix to "[" & class of anyObj
set oName to ""
set oId to ""
try
set oName to name of anyObj
if oName is not missing value then set prefix to prefix & " name=\"" & oName & "\""
end try
try
set oId to id of anyObj
if oId is not missing value then set prefix to prefix & " id=" & oId
end try
set prefix to prefix & "] "
end if
return prefix & txt
end toString
on getFilenameExtension(aPath)
if aPath contains "." then
set aPath to ¬
(the reverse of every character of aPath) as string
set x to the offset of "." in aPath
set aPath to (text 1 thru (x - 1) of aPath)
set aPath to (the reverse of every character of aPath) as string
end if
return aPath
end getFilenameExtension
on tryShellScript(cmd)
set answer to missing value
try
set answer to do shell script cmd
end try
return answer
end tryShellScript
on tryShellScriptAndVerifySilentResult(cmd)
set errNum to missing value
set errMsg to missing value
set cmdResult to missing value
try
set cmdResult to do shell script cmd
on error errMsg number errNum
end try
set didFail to false
if (cmdResult is not missing value) then
if (length of cmdResult is greater than 0) then
set didFail to true
end if
end if
if errMsg is not missing value then
set didFail to true
end if
if errNum is not missing value then
set didFail to true
end if
if didFail is true then
set aResult to {cmdResult:cmdResult, errMsg:errMsg, errNum:errNum}
else
set aResult to missing value
end if
return aResult
end tryShellScriptAndVerifySilentResult
(* Given a bash filesystem glob, for example "/Users/jk/Desktop/*.txt", searches the directory and returns a list of filenames which match the glob, sorted from last modified to first modified, and truncated so that the total bytes of the files in the list does not exceed a given limit. This is useful if you want to gather files from a user, but limit the size of the result. Limitation: Consecutive spaces in filenames will be coalesced into a single space in the returned result. (Friends don't give friends filenames with consecutive spaces in them!) *)
on latestFilenames(glob, limit)
set byteCount to 0
set cmd to "/bin/ls -ltww " & glob
set lsResult to missing value
try
set lsResults to do shell script cmd
set fileStrings to split(lsResults, return)
set results to {}
repeat with fileString in fileStrings
set fileString to coalesceTabsAndSpaces(fileString)
set fields to split(fileString, " ")
set fileSize to item 5 of fields
set byteCount to (byteCount + fileSize)
if byteCount is less than limit then
set filePath to last item of fields
set pathComps to split(filePath, "/")
set filename to last item of pathComps
set end of results to filename
end if
end repeat
on error
set results to missing value
end try
return results
end latestFilenames
(* Collapses all runs of consecutive tab characters, consecutive space characters, or consecutive combinations of tab characters and space characters each into a single space character, and returns the result *)
on coalesceTabsAndSpaces(aString)
if aString is missing value then
return aString
end if
set oldLength to count of aString
set newLength to 0
repeat while oldLength is not equal to newLength
set oldLength to count of aString
set aString to searchAndReplace(aString, " ", " ")
set aString to searchAndReplace(aString, " ", " ")
set newLength to count of aString
end repeat
return aString
end coalesceTabsAndSpaces
(* Copies a file using /bin/cp, returns true if successful, otherwise false *)
on copyPath(src, dst)
set ok to true
try
set cmd to "/bin/cp -Rp \"" & src & "\" \"" & dst & "\""
do shell script cmd
on error
set ok to false
end try
return ok
end copyPath
-------- Reuseable Handlers for ScriptView --------
on startScriptView(pathToMe)
set scriptViewPath to quoted form of (pathToMe & "Contents/Resources/ScriptView.app")
set cmd to "open " & scriptViewPath
set ls to do shell script cmd
-- You probably want your script to be the active application, and ScriptView to be directly under it. The following two lines do that
tell application id "com.sheepsystems.ScriptView" to activate
tell me to activate
end startScriptView
on appendScriptView(aLine)
tell application id "com.sheepsystems.ScriptView" to add line aLine
end appendScriptView
on endScriptViewAsking(doAsk)
if (isAppNameRunning("ScriptView")) then
if (doAsk) then
tell me to activate
display dialog "Click 'OK' to close the Script Log window." buttons {"OK"} default button "OK"
end if
-- In case user has already closed the ScriptView window, which will cause it to quit, we 'try'…
try
tell application id "com.sheepsystems.ScriptView" to quit
end try
end if
end endScriptViewAsking
on isAppNameRunning(targetAppName)
set targetAppAlias to missing value
tell application "System Events"
set runningAppAliases to application file of every application process
repeat with appAlias in runningAppAliases
try
if appAlias is not missing value then
if name of appAlias is (targetAppName & ".app") then
return true
end if
end if
end try
end repeat
end tell
return false
end isAppNameRunning
-------- end Reuseable Handlers for ScriptView ----
(* Note 20131005. On 20131005, I found that, all of a sudden, the statement:
set pathToMe to ((POSIX path of (path to me)) as string)
would raise an error when it was inside of a function (handler). So now, I pass pathToMe to functions (handlers) that need it as a parameter.
*)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment