Applescript USB Hardware Validation

I want to be sure that the USB stick at ‘usbMountPoint’ is also the one with the matching ‘usbSerialNumber’


-- Get Mount Point of script's USB host
tell application "Finder"
	try
		set usbMountPoint to text 1 thru -2 of POSIX path of (disk of (path to me) as alias)
		log usbMountPoint --> Output: /Volumes/usb-drive-name
	on error
		set usbMountPoint to POSIX path of (disk of (path to me) as alias) --> if file is on desktop, path: '/', can't have ' ' hence no removing
		log usbMountPoint --> Output: /Volumes/usb-drive-name
	end try
end tell

-- Set Serial Num of USB Stick
set usbSerialNumber to "C86000BDB9F2BFA04A31E8A1"

-- Check if usbMountPoint and usbSerialNumber exist
set sysProfileUSB to (do shell script "system_profiler SPUSBDataType") as text
if sysProfileUSB contains "Serial Number: " & usbSerialNumber and sysProfileUSB contains "Mount Point: " & usbMountPoint then
	log "USB Stick Found!"
else
	log "USB Stick Not found."
end if

When validating to check if usbMountPoint and usbSerialNumber exist in system_profiler, the code should check if the USB device at ‘usbMountPoint’ has the serial number of ‘usbSerialNumber’.
Without this “matching/pairing”, the user could simply have two USBs connected - one with the right serial number, the other with the script; while the code will return “Found” becuase ‘contains’ reads the entire system_profiler without validating that both variables come from the same device.

Any help would be greatly appreciated. Cheers.

Hi,

the handler checkDisk written in AppleScriptObjC code returns the mount point of a given serial number or missing value

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

use framework "Foundation"

on checkDisk(serialNumber)
	set usbData to do shell script "system_profiler SPUSBDataType -detailLevel basic"
	if usbData does not contain serialNumber then return missing value
	set scanner to current application's NSScanner's scannerWithString:usbData
	set mountPoint to ""
	scanner's scanUpToString:serialNumber intoString:(missing value)
	scanner's scanUpToString:"Mount Point: " intoString:(missing value)
	scanner's scanString:"Mount Point: " intoString:(missing value)
	return (item 2 of (scanner's scanUpToString:return intoString:(reference))) as text
end checkDisk

and call it

set serialNumber to "C86000BDB9F2BFA04A31E8A1"
set mountPoint to checkDisk(serialNumber)

However this works only if there is only one volume partition on the USB device.

StefanK, your code is very helpful but since it only works if there is only one volume partition on the USB device, I was wondering if there is a way to reverse it so that instead of feeding a serial number into this function, I could feed it the mount point and have it return the serial number of the drive of the mount point.

That way, all mount points / partitions (of the same drive) should return the same serial number but not all serial numbers return all mount points / partitions because your code only works if there is only one volume partition on the USB device. I hope it makes sense.

Example:
Basically, say my Seagate Expansion Drive (for example - this could be anything) has 2 partitions (partition1 and partition2), as these are on the same drive (Seagate Expansion Drive) they have the same serial number.
I want to be able to feed the mount point of partition1 or partition2 into the function and have it return the serial number of it’s host (ie. that of the Seagate Expansion Drive), this way, no matter if I feed partion1 or partition2, it always finds the serial number (Unlike the other way around where parsing the serial number only returns one mount point / partition).

This script creates a list of all USB devices in the variable devices containing mount point and serial number with format

[format]{{mountpoint, serial}, {mountpoint, serial}, … }[/format]


set devices to {}
set dataList to paragraphs of (do shell script "system_profiler SPUSBDataType | grep 'Mount Point\\|Serial Number'")
set numberOfLines to count dataList
set idx to 1

set {TID, text item delimiters} to {text item delimiters, ": "}
repeat while idx < numberOfLines
	set currentItem to item idx of dataList
	if currentItem contains "Serial" then
		set serialNumber to text item 2 of currentItem
		set idx to idx + 1
		repeat
			if idx < numberOfLines and item idx of dataList contains "Mount" then
				set end of devices to {text item 2 of item idx of dataList, serialNumber}
				set idx to idx + 1
			else
				exit repeat
			end if
		end repeat
	end if
end repeat
set text item delimiters to TID

Hi Stefan,

On my machine, dataList comes out as:

{"          Mount Point: /Volumes/USB_DISK", "          Serial Number: 8J9B81CBK6V13A00", "          Serial Number: 070D4691CB477D74", "              Mount Point: /Volumes/USB_DISK", "          Mount Point: /Volumes/CANON_DC", "          Serial Number: 000000009833", "              Mount Point: /Volumes/CANON_DC", "              Serial Number: 60FB427E7CCB"}

That is, my USB stick’s one mount point, the serial number of the computer’s built-in iSight camera, the serial number of the the USB stick, the stick’s mount point again, the mount point of a card in the card reader, the card reader’s serial number (same as Yvan’s!), the card’s mount point again, and the serial number of the Bluetooth USB Host Controller.

Given that ProGrammer wants to ascertain the serial number associated with a given mount point, it may be safer and more appropriate to work backwards through the list. There are still a few assumptions involved though:

-- Given a mount point for a connected USB device, find the device's serial number.
-- It's assumed that the mount point is unique, that the device does have a serial number, and that this comes before the mount point in the listing.

set mountPoint to "/Volumes/USB_DISK"

set dataList to paragraphs of (do shell script "system_profiler SPUSBDataType | grep 'Mount Point\\|Serial Number'")
set idx to (count dataList)

repeat while (idx > 0)
	set currentItem to item idx of dataList
	set idx to idx - 1
	if (currentItem contains mountPoint) then
		repeat while (idx > 0)
			set thisLine to item idx of dataList
			if (thisLine contains "Serial Number") then
				set {TID, text item delimiters} to {text item delimiters, ": "}
				set serialNumber to text item 2 of thisLine
				set text item delimiters to TID
				set idx to 0
			else
				set idx to idx - 1
			end if
		end repeat
	end if
end repeat

serialNumber

Fair enough, thanks! I tested only one connected USB flash drive with two partitions.

Here, under 10.12.2, StefanK’s script returns an empty list

Nigel’s version issue an error : error “La variable serialNumber n’est pas définie.” number -2753 from “serialNumber”
If I take care to add one instruction:

set idx to (count dataList)
set serialNumber to "00" # ADDED

the script returns : “00”.

The explanation is quite simple. Under 10.12.2, the instruction:

set dataList to paragraphs of (do shell script "system_profiler SPUSBDataType | grep 'Mount Point\\|Serial Number'")

return :
Résultat :

{"          Serial Number: CC2B26051GDGGDFP", "              Serial Number: 07981808B2F1361F", "                      Mount Point: /Volumes/Yosemite Install Disk - 10.10.4", "              Serial Number: 000000000000", "                  Serial Number: 442A60CDF2EE", "                      Mount Point: /Volumes/Transcend", "              Serial Number: 000000009833"}

Yes, the searched string doesn’t appear.

Yvan KOENIG running Sierra 10.12.2 in French (VALLAURIS, France) lundi 19 décembre 2016 12:59:12

I think my script in the other thread may be usable in Sierra now.

http://macscripter.net/viewtopic.php?pid=188347#p188347