Below is what appears to be a working script.
This is my first time writing any substantial amount of Applescript, so any feedback would be extremely welcome. You'll notice that I had to "kludge" some things (like, I couldn't figure out how to get both the backup location and VMs location to both be POSIX references).
No one has challenged nor stated that they've submitted the bug that I seem to have found regarding VMware's failure to correctly report suspended VMs as suspended if the VM has no windows open, so I'll go ahead and submit that.
Again, I would love code improvements!
Thanks!
-----
(*
Applescript application to auto back up virtual machines after certain period of system idle
Requirements:
[ ] "Power off the virtual machine" must be selected in VMWare Fusion "Preferences…" -> "General" -> "When closing a virtual machine:" -- this is needed because if a VM window is closed, then VMware reports the power state of a VM as "powered off" even if the VM is actually "suspended"; and from a document object there is no way to access its window
[ ] In "System Preferences…" -> "Energy Saver" -> "Power adapter" -> "Computer sleep" must be set to a greater value than backup_after + ( check_every * 2 )
http://culturedcode.com/forums/read.php?7,30174,30597
To make this runable, save as application.
To not show in Dock, set LSBackgroundOnly in Info.plist of created app bundle, or other ways in
http://groups.google.com/group/macenterprise/browse_thread/thread/be7db35451e1dc70
*)
property afterHours : 8 --23 -- hour of the day to start monitoring idle
property workDayStarts : 5 -- hour of the day to stop monitoring idle
property backupDuration : 2 -- number of hours needed to back up virtual machines
global backup_after, check_every -- TODO: do these really need to be global ?
set backup_after to 10 --(40 * 60) -- in seconds
set check_every to 5 --(10 * 60)
property cancelTimer : 60 -- how many seconds the user has to cancel backup
property virtualizationApp : "VMware Fusion" -- variable can't be used in 'tell application' statements or won't compile, so search & replace other instances of "VMware Fusion"
property resumeDelay : 120 --(4 * 60) -- in seconds; usually takes < 30s, but if multiple VM running, then can be ~2min
property powerOffDelay : 60 -- in seconds (Win 7 waits about 40 seconds before forcing unsaved docs to close)
property backupLocation : "/Users/mikeong/Documents/Backups/Fusion VMs"
property numBackupsToKeep : 7
--property virtualMachinesLocation : "/Users/mikeong/Documents/Virtual Machines"
property virtualMachinesLocation : "Macintosh HD:Users:mikeong:Documents:Virtual Machines"
onreopen
display dialog "After " & (backup_after / 60) & " minutes of system inactivity this AppleScript powers down any running virtual machines and backs them up. A check for inactivity is performed every " & (check_every / 60) & " minutes."
endreopen
onidle
tellapplication "System Events"
-- verify that are in allowed timeframe for backups
set currentHour to (time of (current date)) div hours
if currentHour isless than (workDayStarts - backupDuration) or currentHour isgreater than afterHours then
-- check that user has been idle / inactive for sufficient time
set idletime todo shell script "echo $((`ioreg -c IOHIDSystem | sed -e '/HIDIdleTime/ !{ d' -e 't' -e '}' -e 's/.* = //g' -e 'q'` / 1000000000))"
if (idletime asinteger) > backup_after then
-- test to see if backup has been done today
ifmy hasNotBackedUpToday(backupLocation) then
-- warn the user that VM application will force close VMs
repeatwith secondsLeft from cancelTimer to 1 by -1
set dialogResult todisplay alert virtualizationApp & " is about to force quit and back up virtual machines " message "This AppleScript is set to force close " & virtualizationApp & ", if it's open, and its virtual machines, and then to back up the virtual machines." & return & return & "Click \"Cancel\" to delay these actions until later, or press \"OK\" to quit and back up." & return & return & secondsLeft & " seconds left." buttons {"OK", "Cancel"} default button "OK" as warning giving up after 1
if secondsLeft = 1 or (button returned of dialogResult) is "OK" then
-- force VMs to power off, quit VM app
my powerOffVMsQuitFusion()
-- back up VMs
ifmy backupVMs(backupLocation, virtualMachinesLocation) then
display dialog "Successfully backed up virtual machines."
else
display dialog "Failed to back up virtual machines."
endif
exitrepeat
elseif (button returned of dialogResult) is "Cancel" then
display dialog "Backup has been delayed until later."
exitrepeat
endif
endrepeat
else
return (8 * 60 * 60) -- have backed up today, so try again in 8 hrs
endif
endif
return check_every
endif
endtell
endidle
(*
* Check to see if have backed up in last 16 hrs
*)
on hasNotBackedUpToday(theLocation)
tellapplication "Finder"
set bLocation toPOSIX file theLocation asalias
sort (getfoldersof bLocation) by creation date
set myResult to result
set myResultCount tocountof myResult
-- this raises an error if the folder doesn't contain any files
if (myResultCount is notequal to 0) then
set theFile to (item 1 of myResult)
if creation date of theFile isgreater than ((current date) - (16 * 60 * 60)) then
display dialog "Have backed up today."
return false
endif
endif
-- delete a backup if have reached max
if myResultCount ≥ numBackupsToKeep then
deleteitem myResultCount of myResult
endif
set currDate tocurrent date
set folderName to ("" & (year of currDate) & "-" & (monthof currDate) & "-" & (day of currDate))
make new folder at bLocation with properties {name:folderName}
return true
endtell
end hasNotBackedUpToday
(*
* force VMs to power off, quit VM app
*)
on powerOffVMsQuitFusion()
tellapplication "VMware Fusion"
activate -- can't test to see if a VM is suspended unless VMware is running
delay 10 -- give the application time to open
repeatwith currVM indocuments
set powerState to power state of currVM
-- power off doesn't work on suspended VMs
if powerState is suspended then -- VM reports powered off even when actually suspended, reports correctly if VM's own window open (VM Library, right-click on VM, then select "Show Windows")
--display dialog "VM " & (name of currVM) & " is suspended"
resume currVM -- only want to resume if actually suspended, otherwise it starts powered off VMs
delay resumeDelay -- give VM time to resume
endif
if powerState is powered on then
--display dialog "Name: " & (name of currVM)
--display dialog "Name: " & (OS name of currVM)
-- XP unsaved docs prevent power off unless with force, Win7 works without force
try
(*if (OS name of currVM) contains "7" then
--display dialog "Win 7: would power off withOUT force"
power off currVM
delay powerOffDelay
else*)
--display dialog "Not Win 7 (XP, etc.): would power off with force"
power off currVM with force
--end if
onerror errMsg number errNum
tellapplication "Finder"
display dialog ("errMsg: " & errMsg & ", errNum: " & errNum)
endtell
endtry
endif
endrepeat
quit
delay 5
endtell
end powerOffVMsQuitFusion
(*
* Back up VMs, deleting oldest backup if necessary
*)
on backupVMs(thisLocation, thisVMsLocation)
tellapplication "Finder"
set thisBackupLocation toPOSIX file thisLocation asalias
sort (getfoldersof thisBackupLocation) by creation date
set myFinderResult to result
-- This raises an error if the folder doesn't contain any files
if ((countof myFinderResult) = 0) then
display dialog "Error. What happened to the folder that was just created?"
return false
else
set backupFolder to (item 1 of myFinderResult)
--display dialog "Folder name: " & (name of backupFolder)
-- check to see if folder is empty
if ((countfilesof backupFolder) is notequal to 0) then
display dialog "Error. Backup folder wasn't empty."
return false
endif
-- copy virtual machines to backup folder
--set vmsLocation to alias "Macintosh HD:Users:mikeong:Documents:Virtual Machines"
set vmsLocation toalias thisVMsLocation
duplicateeveryfileof vmsLocation to backupFolder
return true
endif
endtell
end backupVMs