MACRO: Time Machine Assistant, v4.0

If Time Machine is not running when the macro is triggered, a dialog like to the following will appear.

Note that if an encrypted volume is ejected, it must be disconnected and reconnected before it can be used again. When it is reconnected, the encryption key can be entered manually or retrieved from Keychain.


If the Start Time Machine button is selected, a dialog like to the following will appear.

Note the mounted and ejected volumes can be used; the latter will be automatically mounted.


If Time Machine is running when the macro is triggered, a dialog like to the following will appear.

If Interrupt & Eject or Wait (up to X min) & Eject is selected, the macro will provide a notification when Time Machine is interrupted or completes. A second notification will appear when the volume is ejected.


PURPOSE

This macro provides Time Machine information and control.

Minimal and only optional configuration is necessary. All Time Machine information (e.g., the status and volumes) is retrieved from tmutil and diskutil.

I created Time Machine Assistant for myself and others that I help with Time Machine. For those of us that periodically connect an external backup drive to a MacBook Pro or MacBook Air, it is important to check the status of Time Machine before disconnecting the external drive.

Also, in some cases, itโ€™s nice to run Time Machine immediately before disconnecting the drive.

IMPLEMENTATION NOTE

This macro includes an Execute an AppleScript that contains the script the reports and controls Time Machine. Since the vast majority of the macro logic is contained in this script, it can be run like any other AppleScript. I use FastScripts 3 because it provides some nice AppleScript-related enhancements.

TESTED WITH

โ€ข Keyboard Maestro 11.0.3
โ€ข Sequoia 15.2 (24C5089c)/MacBookPro18,2
โ€ข Mojave 10.14.16/Macmini6,2
โ€ข High Sierra 10.13.6/iMac11,1445

VERSION HISTORY

( expand / collapse )

1.0 - initial version

1.1
a) Modified the method to determine myName so that the name is successfully returned when the script runs within Keyboard Maestro.
b) Updated the Purpose.

2.0
a) Added the variable local_EjectTimeoutMin (set to 10) and added it to the 'Wait & Eject' dialog button.
b) Revised the 'Wait & Eject' logic to incorporate local_EjectTimeoutMin.

3.0
a) After each 'diskutil unmountDisk' added a 'diskutil eject'.
b) Revised the 'Wait & Eject' logic to incorporate local_EjectTimeoutMin.
b) Added green and red indicators before each volume.

4.0
Macro revisions:
a) If triggered, the macro now cancels the previous instance of itself.
b) If triggered by a USB Device, the macro will display a progress indicator and wait a configured number of seconds. This provides time for the HD or SSD to mount before the first dialog is rendered. The setting local_UsbDevicesInfo was added to support this new feature.
c) Changed setting local_EjectTimeoutMin to local_IfTimeMachineRunningTimeoutBeforeEject.

AppleScript revisions:
a) Added yellow indicator before each ejected volume.
b) Changed ejectTimeoutMin to IfTimeMachineRunningTimeoutBeforeEject.
c) Improved error checking.
d) Expanded comments.
e) Bug fix: Incorrect Time Machine volume was reported when Time Machine was being started.


Download: Time Machine Assistant.kmmacros (41 KB)

Macro-Image


Macro-Notes
  • Macros are always disabled when imported into the Keyboard Maestro Editor.
    • The user must ensure the macro is enabled.
    • The user must also ensure the macro's parent macro-group is enabled.

System Information
  • macOS 15.2 (24C5089c) PRE-RELEASE SEED SOFTWARE
  • Keyboard Maestro v11.0.3

If running the AppleScript independently, i.e., outside Keyboard Maestro, here's the code that should be used:

( expand / collapse )
(*
ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
Title			: Time Machine Assistant, v4.0
Modified		: 2024-12-05
Author			: Jim Sauer, [@_jims](https://forum.keyboardmaestro.com/u/_jims/summary)

Purpose
This script provides Time Machine information and control. 

No configurations is required as all information is retrieved from tmutil 
and diskutil.

This macro provides Time Machine information and control.

I created 'Time Machine Assistant' for myself and others that I help 
with Time Machine. For those of us that periodically connect an external 
backup drive to a MacBook Pro or MacBook Air, it is important to check 
the status of Time Machine before disconnecting the external drive.

Also, in some cases, itโ€™s nice to run Time Machine immediately before 
disconnecting the drive.

Tested With. : Sonoma 14.4.1 (23E224)/MacBookPro18,2
ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
Version History
1.0 - initial version

1.1 
a) Modified the method to determine myName so that the name is
   isuccessfully returned when the script runs within Keyboard Maestro.
b) Updated the Purpose.

2.0
a) Added the variable ejectTimeoutMin (set to 10) and added it to the 
    'Wait & Eject' dialog button.
b) Revised the 'Wait & Eject' logic to incorporate ejectTimeoutMin.

3.0
a) After each `diskutil unmountDisk` added a `diskutil eject`.
b) Added green and red indicators before each volume.

4.0
a) Added yellow indicator before each ejected volume.
b) Changed ejectTimeoutMin to IfTimeMachineRunningTimeoutBeforeEject.
c) Improved error checking.
d) Expanded comments.
e) Bug fix: Incorrect Time Machine volume was reported when
    Time Machine was being started.
ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
*)

set IfTimeMachineRunningTimeoutBeforeEject to 20

(* For standalone AppleScript of this script
set IfTimeMachineRunningTimeoutBeforeEject to 20
*)

(* For Keyboard Maestro application of this script
set kmInst to system attribute "KMINSTANCE"
tell application "Keyboard Maestro Engine"
	set IfTimeMachineRunningTimeoutBeforeEject to getvariable ยฌ
	"local_IfTimeMachineRunningTimeoutBeforeEject" instance kmInst
end tell
*)

set myName to getMyName()

set tmDestinationsInfo to getTmDestinationsInfo()

set tmDestinationsDiskutilInfo to getTmDestinationsDiskutilInfo(tmDestinationsInfo)

set tmStatus to getTmStatus(tmDestinationsInfo, tmDestinationsDiskutilInfo)

set buttonList to {}

if tmStatus's backup_phase is not "" then
	
	-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
	-- = = = = Time Machine Running = = = = = = = = = = = = = = = = = = = = =
	-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
	
	set tmLabel to tmStatus's label
	set tmBackupPhase to tmStatus's backup_phase
	set tmMountPoint to tmStatus's mount_point
	set tmDeviceIndentifier to tmStatus's device_identifier
	
	set btnInteruptAndEject to "Interrupt & Eject"
	set btnWaitAndEdject to "Wait (up to " & IfTimeMachineRunningTimeoutBeforeEject & " min) & Eject"
	
	if tmStatus's encryption then
		set encryptionStr to "Yes"
	else
		set encryptionStr to "No"
	end if
	
	set thePrompt to "Time Machine Status : " & tmBackupPhase & return & return & ยฌ
		"Backup Volume : " & tmLabel & return & ยฌ
		"Encryption : " & encryptionStr
	
	set dialogResult to display dialog thePrompt ยฌ
		with title myName buttons {btnInteruptAndEject, btnWaitAndEdject, "Cancel"} ยฌ
		default button {"Cancel"}
	
	if button returned of dialogResult is btnInteruptAndEject then
		
		-- = Interrupt & Eject = = = = = = = = = = = = = = = = = = = = = = = = =
		-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
		
		try
			do shell script "tmutil stopbackup"
		on error errMsg number errNum
			display dialog "Time Machine to '" & tmLabel & "' could not be stopped." & return & return & ยฌ
				errNum & ": " & errMsg with title myName
			return "Time Machine Not Interrupted"
		end try
		
		display notification "Time Machine to '" & tmLabel & "' interrupted." with title myName
		
	else if button returned of dialogResult is btnWaitAndEdject then
		
		-- = Wait & Eject = = = = = = = = = = = = = = = = = = = = = = = = = = = 
		-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
		
		set startTime to current date
		
		repeat
			set tmStatus to getTmStatus(tmDestinationsInfo, tmDestinationsDiskutilInfo)
			if tmStatus's backup_phase is "" then
				exit repeat
			end if
			delay 1
			if ((current date) - startTime) > IfTimeMachineRunningTimeoutBeforeEject * 60 then
				display dialog "Time Machine to '" & tmLabel & "' is still running. " & return & return & ยฌ
					"The timeout of " & IfTimeMachineRunningTimeoutBeforeEject & " minutes was exceeded, " & ยฌ
					"thus the volume will not be automatically ejected when it " & ยฌ
					"completes." & return & return & ยฌ
					"You can start '" & myName & "' again, " & ยฌ
					"to resume waiting." with title myName buttons {"OK"} default button {"OK"}
				return "Timeout"
			end if
		end repeat
		
		display notification "Time Machine to '" & tmLabel & "' completed." with title myName
		
	end if
	
	try
		do shell script "diskutil unmountDisk " & quoted form of tmMountPoint
	on error errMsg number errNum
		display dialog tmMountPoint & "' could not be unmounted." & return & return & ยฌ
			errNum & ": " & errMsg with title myName
		return "Volume Not Unmounted"
	end try
	
	try
		do shell script "diskutil eject " & quoted form of tmDeviceIndentifier
	on error errMsg number errNum
		display dialog "'" & tmMountPoint & "' (" & ยฌ
			tmDeviceIndentifier & ") could not be ejected." & return & return & ยฌ
			errNum & ": " & errMsg with title myName
		return "Volume Not Ejected"
	end try
	
	display notification "'" & tmMountPoint & "' (" & ยฌ
		tmDeviceIndentifier & ") has been ejected." with title myName
	
	return
	
end if

-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
-- = = = = Time Machine NOT Running = = = = = = = = = = = = = = = = =
-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

set connected_cnt to 0
set mounted_cnt to 0
set ejected_cnt to 0
set toMountMustReconnect_cnt to 0

set vListString to ""

repeat with volume in tmDestinationsDiskutilInfo
	
	set vStatus to volume's status
	
	if volume's connected then
		set connected_cnt to connected_cnt + 1
		if vStatus begins with "mounted" then
			set mounted_cnt to mounted_cnt + 1
		else if vStatus begins with "ejected" then
			set ejected_cnt to ejected_cnt + 1
		else if vStatus begins with "to mount, must reconnect" then
			set toMountMustReconnect_cnt to toMountMustReconnect_cnt + 1
		end if
	end if
	
	if vStatus starts with "mounted" then
		set vListString to vListString & "๐ŸŸข"
	else if vStatus starts with "ejected" then
		set vListString to vListString & "๐ŸŸก"
	else
		set vListString to vListString & "๐Ÿ”ด"
	end if
	
	set vListString to vListString & " " & volume's label & " (" & vStatus & ")" & return & return
	
end repeat

set vListString to text 1 thru -3 of vListString

if (mounted_cnt + ejected_cnt) < 1 then
	
	set thePrompt to "Time Machine volumes:" & return & return & ยฌ
		vListString & return & return & ยฌ
		"๐Ÿ‘‰๐Ÿฟ Time Machine is not running, no volumes are available, and there are no volumes to eject."
	
	set dialogResult to display dialog thePrompt with title myName buttons {"OK"} ยฌ
		default button {"OK"}
	
	return
	
else
	
	set end of buttonList to "Cancel"
	
	if mounted_cnt > 0 then
		set beginning of buttonList to "Eject Volume"
	end if
	
	set mountNote to ""
	
	if (mounted_cnt + ejected_cnt) > 0 then
		set beginning of buttonList to "Start Time Machine"
		if ejected_cnt > 0 then
			set mountNote to return & return & "๐Ÿ‘‰๐Ÿฟ When starting Time Machine, ejected volumes will be automatically mounted."
		end if
	end if
	
	set defaultButton to "Cancel"
	
	set thePrompt to "Time Machine volumes:" & return & return & ยฌ
		vListString & mountNote
	
	set dialogResult to display dialog thePrompt with title myName buttons buttonList default button defaultButton
	
	if button returned of dialogResult is "Eject Volume" then
		
		-- = Eject Volume = = = = = = = = = = = = = = = = = = = = = = = = = = =
		-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
		
		set connected_cnt to 0
		set mounted_cnt to 0
		set ejected_cnt to 0
		set toMountMustReconnect_cnt to 0
		
		set mvListString to ""
		
		-- The state of one or more of the volumes could have potentially have changed,
		-- thus regenerate tmDestinationsDiskutilInfo (e.g., by the user)
		set tmDestinationsDiskutilInfo to getTmDestinationsDiskutilInfo(tmDestinationsInfo)
		
		repeat with volume in tmDestinationsDiskutilInfo
			
			set vStatus to volume's status
			
			if volume's connected then
				set connected_cnt to connected_cnt + 1
				if vStatus starts with "mounted" then
					
					set mounted_cnt to mounted_cnt + 1
					set mvLabel to volume's label
					set mvId to volume's id
					set mvMountPoint to volume's mount_point
					set mvDeviceIndentifier to volume's device_identifier
					
					set mvListString to mvListString & mvLabel & " (" & vStatus & ")" & return
					
				else if vStatus starts with "ejected" then
					set ejected_cnt to ejected_cnt + 1
				else if vStatus starts with "to mount, must reconnect" then
					set toMountMustReconnect_cnt to toMountMustReconnect_cnt + 1
				end if
			end if
			
		end repeat
		
		if mounted_cnt > 1 then
			
			set mvList to paragraphs of mvListString
			set mvString to listToString(mvList)
			set thePrompt to "Select a volume to eject:"
			set mvSelected to do shell script "osascript -e 'return choose from list {" & mvString & "} with prompt \"" & thePrompt & "\" default items {\"Cancel\"} with title \"" & myName & "\"'"
			
			if mvSelected is "false" then
				
				return "User Cancelled"
				
			else
				
				set vLabel to do shell script "echo " & quoted form of mvSelected & " | awk 'BEGIN{FS=\" \\\\(\"}{print $1}'"
				
				repeat with volume in tmDestinationsDiskutilInfo
					if vLabel is equal to volume's label then
						set mvMountPoint to volume's mount_point
						set mvDeviceIndentifier to volume's device_identifier
					end if
				end repeat
				
			end if
			
		end if
		
		try
			do shell script "diskutil unmountDisk " & quoted form of mvMountPoint
		on error errMsg number errNum
			display dialog mvMountPoint & "' could not be unmounted." & return & return & ยฌ
				errNum & ": " & errMsg with title myName
			return "Volume Not Unmounted"
		end try
		
		try
			do shell script "diskutil eject " & quoted form of mvDeviceIndentifier
		on error errMsg number errNum
			display dialog "'" & mvMountPoint & "' (" & ยฌ
				mvDeviceIndentifier & ") could not be ejected." & return & return & ยฌ
				errNum & ": " & errMsg with title myName
			return "Volume Not Ejected"
		end try
		
		display notification "'" & mvMountPoint & "' (" & ยฌ
			mvDeviceIndentifier & ") has been ejected." with title myName
		
	else if button returned of dialogResult is "Start Time Machine" then
		
		-- = Start Time Machine = = = = = = = = = = = = = = = = = = = = = = = =
		-- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
		
		set connected_cnt to 0
		set mounted_cnt to 0
		set ejected_cnt to 0
		set toMountMustReconnect_cnt to 0
		
		set tmvListString to ""
		
		-- The state of one or more of the volumes could have potentially have changed,
		-- thus regenerate tmDestinationsDiskutilInfo (e.g., by the user)
		set tmDestinationsDiskutilInfo to getTmDestinationsDiskutilInfo(tmDestinationsInfo)
		
		repeat with volume in tmDestinationsDiskutilInfo
			
			set vStatus to volume's status
			
			if volume's connected then
				set connected_cnt to connected_cnt + 1
				if vStatus starts with "mounted" then
					set mounted_cnt to mounted_cnt + 1
				else if vStatus starts with "ejected" then
					set ejected_cnt to ejected_cnt + 1
				else if vStatus starts with "to mount, must reconnect" then
					set toMountMustReconnect_cnt to toMountMustReconnect_cnt + 1
				end if
				
				if vStatus starts with "mounted" or vStatus starts with "ejected" then
					
					set tmvLabel to volume's label
					set tmvStatus to volume's status
					set tmvId to volume's id
					set tmvMountPoint to volume's mount_point
					set tmvDeviceIndentifier to volume's device_identifier
					
					set tmvListString to tmvListString & tmvLabel & " (" & tmvStatus & ")" & return
					
				end if
				
			end if
			
		end repeat
		
		if (mounted_cnt + ejected_cnt) > 1 then
			
			set tmvList to paragraphs of tmvListString
			set tmvString to listToString(tmvList)
			if ejected_cnt > 0 then
				set thePrompt to "Select a Time Machine volume (ejected volumes will be automatically mounted):"
			else
				set thePrompt to "Select a Time Machine volume:"
			end if
			set tmvSelected to do shell script "osascript -e 'return choose from list {" & tmvString & "} with prompt \"" & thePrompt & "\" default items {\"Cancel\"} with title \"" & myName & "\"'"
			
			if tmvSelected is "false" then
				
				return "User Cancelled"
				
			else
				
				set vLabel to do shell script "echo " & quoted form of tmvSelected & " | awk 'BEGIN{FS=\" \\\\(\"}{print $1}'"
				
				repeat with volume in tmDestinationsDiskutilInfo
					if vLabel is equal to volume's label then
						set tmvLabel to volume's label
						set tmvStatus to volume's status
						set tmvId to volume's id
						set tmvMountPoint to volume's mount_point
						set tmvDeviceIndentifier to volume's device_identifier
					end if
				end repeat
				
			end if
			
		end if
		
		if tmvStatus does not start with "mounted" then
			
			try
				do shell script "diskutil mountDisk " & quoted form of tmvDeviceIndentifier
			on error
				display dialog "'" & tmvMountPoint & "' (" & ยฌ
					tmvDeviceIndentifier & ") could not be ejected." with title myName
				return "Error Mounting"
			end try
			
		end if
		
		-- It's possible that Time Machine automatically started during the period
		-- that the above dialogs were open. If it automatically started for the volume
		-- that was selected, let it continue. If it was another volume, stop it before
		-- starting Time Machine for the selected volume.
		
		set tmStatus to getTmStatus(tmDestinationsInfo, tmDestinationsDiskutilInfo)
		
		if tmStatus's destination_id is tmvId then
			
			display notification "Time Machine to " & quoted form of tmvLabel & ยฌ
				" is already running." with title myName
			
		else if tmStatus's backup_phase is not "" then
			
			set tmrvLabel to tmStatus's label
			
			try
				do shell script "tmutil stopbackup"
			on error errMsg number errNum
				display dialog "Time Machine to '" & tmrvLabel & "' was already running and could not be stopped." & return & return & ยฌ
					errNum & ": " & errMsg with title myName
				return "Time Machine Not Stopped"
			end try
			
			set timeoutLimit to 20
			set startTime to current date
			
			repeat
				set tmStatus to getTmStatus(tmDestinationsInfo, tmDestinationsDiskutilInfo)
				if tmStatus's backup_phase is "" then
					exit repeat
				end if
				delay 1
				if ((current date) - startTime) > timeoutLimit then
					display dialog "Time Machine to '" & vLabel & ยฌ
						"' was already running. An attempt to stop it failed after " & timeoutLimit & ยฌ
						" seconds." with title myName buttons {"OK"} default button {"OK"}
					return "Timeout Stopping Time Machine"
				end if
			end repeat
			
			display notification "Time Machine to '" & tmrvLabel & ยฌ
				"' was running and was stopped." with title myName
			
			delay 2.0
			
			try
				do shell script "tmutil startbackup --destination " & quoted form of tmvId
			on error errMsg number errNum
				display dialog "Time Machine to '" & tmvLabel & "' could not be started." & return & return & ยฌ
					errNum & ": " & errMsg with title myName
				return "Time Machine Not Started"
			end try
			
			display notification "Time Machine to '" & tmvLabel & ยฌ
				"' started." with title myName
			
		else
			
			try
				do shell script "tmutil startbackup --destination " & quoted form of tmvId
			on error errMsg number errNum
				display dialog "Time Machine to '" & tmvLabel & "' could not be started." & return & return & ยฌ
					errNum & ": " & errMsg with title myName
				return "Time Machine Not Started"
			end try
			
			display notification "Time Machine to '" & tmvLabel & ยฌ
				"' started." with title myName
			
		end if
		
	end if
	
end if

-- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
-- + HANDLERS + + + + + + + + + + + + + + + + + + + + + + + + + + + +
-- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

on getMyName()
	tell application "Finder"
		set myPath to (path to me) as alias
		set myFileName to name of myPath
		set myName to (text 1 thru ((offset of "." in myFileName) - 1) of myFileName)
	end tell
	return myName
end getMyName

(* For standalone AppleScript of this script
on getMyName()
	tell application "Finder"
		set myPath to (path to me) as alias
		set myFileName to name of myPath
		set myName to (text 1 thru ((offset of "." in myFileName) - 1) of myFileName)
	end tell
	return myName
end getMyName
*)

(* For Keyboard Maestro application of this script
on getMyName()
	set kmInst to system attribute "KMINSTANCE"
	tell application "Keyboard Maestro Engine"
		set myName to getvariable "local_myName" instance kmInst
	end tell
	return myName
end getMyName
*)

on listToString(theList)
	-- Convert the AppleScript list to a string
	set str to ""
	repeat with i from 1 to count of theList
		set str to str & "\"" & item i of theList & "\"" & ", "
	end repeat
	-- Remove the trailing comma
	set str to text 1 thru -3 of str
	return str
end listToString

-- Information gathered by this handler, will not change during the execution of this script
on getTmDestinationsInfo()
	
	set tmudi_raw to do shell script "tmutil destinationinfo"
	set tmutilDestinationinfo to do shell script "echo " & quoted form of tmudi_raw & " | sed 's/> ===/=====/g'"
	
	set AppleScript's text item delimiters to "===================================================="
	set theItms to text items of tmutilDestinationinfo
	
	set tmDestinationsInfo to {}
	
	repeat with cItm in theItms
		set AppleScript's text item delimiters to return
		set cItmLines to text items of cItm
		set cItmRec to {label:"", id:""}
		
		repeat with cLine in cItmLines
			if cLine starts with "Name" then
				set label of cItmRec to do shell script "echo " & quoted form of cLine & " | awk -F': ' '{print $2}'"
			else if cLine starts with "ID" then
				set id of cItmRec to do shell script "echo " & quoted form of cLine & " | awk -F': ' '{print $2}'"
			end if
		end repeat
		
		if label of cItmRec is not "" then
			set end of tmDestinationsInfo to cItmRec
		end if
	end repeat
	
	return tmDestinationsInfo
	
end getTmDestinationsInfo

-- Information gathered by this handler, will change as Time Machine progresses
on getTmDestinationsDiskutilInfo(tmDestinationsInfo)
	
	set tmDestinationsDiskutilInfo to {}
	
	repeat with i in tmDestinationsInfo
		
		set iRecord to {label:"", id:"", device_identifier:"", mount_point:"", encryption:"", connected:"", status:""}
		
		set device_identifier to ""
		set mount_point to ""
		set encryption to ""
		set status to ""
		set connected to true
		
		try
			set du to do shell script "diskutil info -plist " & quoted form of i's label
			
			set device_identifier to do shell script "echo " & quoted form of du & " | tr '\\015' '\\012' | awk -F'<|>' '/<key>DeviceIdentifier<\\/key>/{getline; print $3}'"
			set mount_point to do shell script "echo " & quoted form of du & " | tr '\\015' '\\012' | awk -F'<|>' '/<key>MountPoint<\\/key>/{getline; print $3}'"
			set encryption to do shell script "echo " & quoted form of du & " | tr '\\015' '\\012' | awk -F'<|>' '/<key>Encryption<\\/key>/{getline; print}'"
		on error
			set connected to false
		end try
		
		set label of iRecord to i's label
		set id of iRecord to i's id
		set device_identifier of iRecord to device_identifier
		set mount_point of iRecord to mount_point
		
		set encryption to (encryption contains "true")
		set encryption of iRecord to encryption
		
		if connected then
			set connected of iRecord to true
			if mount_point is not "" then
				if encryption then
					set status of iRecord to "mounted; encrypted"
				else
					set status of iRecord to "mounted; unencrypted"
				end if
			else
				if encryption then
					set status of iRecord to "to mount, must reconnect; encrypted"
				else
					set status of iRecord to "ejected; unencrypted"
				end if
			end if
		else
			set connected of iRecord to false
			set status of iRecord to "unavailable"
		end if
		
		if label of iRecord is not "" then
			set end of tmDestinationsDiskutilInfo to iRecord
		end if
		
	end repeat
	
	return tmDestinationsDiskutilInfo
	
end getTmDestinationsDiskutilInfo

-- Information gathered by this handler, will change as Time Machine progresses
on getTmStatus(tmDestinationsInfo, tmDestinationsDiskutilInfo)
	
	set tmStatus to {backup_phase:"", destination_id:"", label:"", mount_point:"", device_identifier:"", encryption:""}
	
	set tms to do shell script "tmutil status"
	
	set backup_phase of tmStatus ยฌ
		to do shell script "echo " & quoted form of tms & " | tr '\\015' '\\012' | grep BackupPhase | awk -F' = ' '{print $2}' | tr -d ';'"
	
	set destination_id of tmStatus ยฌ
		to do shell script "echo " & quoted form of tms & " | tr '\\015' '\\012' | grep DestinationID | awk -F' = ' '{print $2}' | tr -d ';' | tr -d '\"'"
	
	repeat with i in tmDestinationsInfo
		if i's id is equal to destination_id of tmStatus then
			set label of tmStatus to i's label
			exit repeat
		end if
	end repeat
	
	repeat with i in tmDestinationsDiskutilInfo
		if i's label is equal to label of tmStatus then
			set mount_point of tmStatus to i's mount_point
			set device_identifier of tmStatus to i's device_identifier
			set encryption of tmStatus to i's encryption
			exit repeat
		end if
	end repeat
	
	return tmStatus
	
end getTmStatus
3 Likes

Hi,

Thank you very much for this macro, it is 90% of what I was looking for.
I'm pretty new to KeyboardMaestro & AppleScript. Would it be possible to automatically unmount the external drive so that I can just hear / see from it's LED, that TimeMachine is done and I can unplug the cable?

Thank you very much in advance.

1 Like

Hello @BernhardHu :wave:

No โ€ฆ since this Macro has no routine to watch after the TimeMachine Process & also has no Funktion to eject the TimeMachine Volume.

I canโ€™t speak for Jim (@_jims) if he wants to integrate these featuresโ€ฆ

But if you do a little searching here on the Forum youโ€™ll find macros from others which have the ability to watch the Backup going and possibly unmount the Volume afterwardsโ€ฆ

Greetings from Germany :de:

Tobias

Hi, @BernhardHu. Sorry, I don't understand your objective.

If you provide more details, maybe listing your required sequence of events and specifically mention the ones that you feel are missing, I may be able to help.


FYI, when I connect my disk named M1 Time Machine, here's the chronological progression of information that is reported via the main dialog of the macro. (Note that each screenshot represents a separate execution of the macro.)

( expand / collapse )







Hi @_jims

Thanks for your reply.
The screenshots match what is happening on my Mac.

I set up the macro to run when my backup volume is mounted.

  1. I plug in my volume.
  2. MacOS mounts my volume after a few seconds.
  3. KM launches Time Machine Assistant
  4. I click start Time Machine
  5. The window closes and my backup starts
  6. I wait for the backup to finnish
  7. I run the macro manually
  8. I click wait & eject (only works at the end, because of the 10s timeout)
  9. My drive keeps spinning & the light stays on because it is only unmounted and not ejected (like with Command-E in Disk Utility).

I usually do a backup at the end of the day and was hoping to configure the macro to start TM, wait and eject my drive so that I can see from the LED / hear from the spinning when the backup is done, so I can unplug the drive and close the lid of my Macbook.

I hope I could explain what I'm trying to do.
Thanks for your help.

1 Like

Hi, @BernhardHu. Yes, thanks for the information.

I've just uploaded Version 2.0. Please download it and see if it meets your needs. If your Time Machine backups typically take longer than 10 minutes, you can change the variable local_EjectTimeoutMin to a larger number. Alternatively, you can just restart Time Machine Assistant after the time-out to restart the waiting process.

I've updated the OP with Version 2.0 of Time Machine Assistant:

  • Added the variable local_EjectTimeoutMin (set to 10) and added it to the 'Wait & Eject' dialog button.

  • Revised the 'Wait & Eject' logic to incorporate local_EjectTimeoutMin.

Hi @_jims,

like I said, I don't have much experience with AppleScript, so please take everything with a grain of salt:

currently in your script:

do shell script "diskutil unmountDisk "

This works for me in the terminal:

diskutil eject "TimeMachineTranscend"

so this in KeyBoard Maestro?

do shell script "diskutil eject "TimeMachineTranscend" "

(TimeMachineTranscend is the name of my disk, but I'm unsure about the variable)

Do you think implementing this would work in your script?

1 Like

No, because you need to escape the quotes around the disk name:

do shell script "diskutil eject \"TimeMachineTranscend\" "

I'm a bit leery of leaping straight in with the eject command when backup disks are concerned, so try it after the unmountDisk command. That would make the whole try block:

		try
			do shell script "diskutil unmountDisk " & quoted form of mountedMountPoint
			do shell script "diskutil eject " & quoted form of mountedMountPoint
			display notification quoted form of theLabel & ยฌ
				" has been ejected." with title myName
		on error
			display dialog quoted form of theLabel & " could not be ejected." with title myName
		end try
1 Like

Hi, @BernhardHu and @Nige_S.

Among other minor changes, I added the following to the AppleScript. See Version 3.0.

try
	do shell script "tmutil stopbackup"
	display notification "Time Machine backup has been interrupted." with title myName
	do shell script "diskutil unmountDisk " & quoted form of tmMountPoint
	do shell script "diskutil eject " & quoted form of tmDestinationIndentifier
	display notification "'" & tmMountPoint & " (" & ยฌ
		tmDestinationIndentifier & ")' has been ejected." with title myName
on error
	display dialog "'" & tmMountPoint & " (" & ยฌ
		tmDestinationIndentifier & ")' could not be ejected." with title myName
end try

In my cases (and for others that originally used this script) SSD's were being used, therefore unmounts were sufficient.

1 Like

I've updated the OP with Version 3.0 of Time Machine Assistant:

  • After each diskutil unmountDisk added a diskutil eject.

  • Added green and red indicators before each volume.

1 Like

Hi @_jims,
thank you so much.

I was able to set it up to start with my TimeMachine-volume, Execute the script, wait 1 second and execute it again.

That enabled my workflow

  1. connect drive
  2. KM triggers Time Machine Assistant after a few seconds
  3. click Start Time Machine
  4. KM triggers Time Machine Assistant after 1 second again
  5. click Wait (up to 10 min) & Eject
  6. wait... :slight_smile:
  7. unplug the drive when it's light is off
1 Like

I've updated the OP with Version 4.0 of Time Machine Assistant:

Macro revisions:

  • If triggered, the macro now cancels the previous instance of itself.

  • If triggered by a USB Device, the macro will display a progress indicator and wait a configured number of seconds. This provides time for the HD or SSD to mount before the first dialog is rendered. The setting local_UsbDevicesInfo was added to support this new feature.

  • Changed setting local_EjectTimeoutMin to local_IfTimeMachineRunningTimeoutBeforeEject.

AppleScript revisions:

  • Added yellow indicator before each ejected volume.

  • Changed ejectTimeoutMin to IfTimeMachineRunningTimeoutBeforeEject.

  • Improved error checking.

  • Expanded comments.

  • Bug fix: Incorrect Time Machine volume was reported when Time Machine was being started.

2 Likes