Getting names and serial numbers from USB devices

Well screw parsing XML :lol:

This works with a few different usb keys I have that I tested mounting through a variety of methods (keyboard hub, direct, external hub).

It returns a list of lists like so.

USB_Drives {{serial number 1, mount point1}, {serial number 2, mount point2}, etc.}

set USB_Drives to {}
set USB to paragraphs of (do shell script "system_profiler SPUSBDataType -detailLevel basic")
repeat with i from 1 to (count of USB)
	if item i of USB contains "Removable Media: Yes" then
		set {end of USB_Drives, i} to process_usb_device(i, USB)
	end if
end repeat

on process_usb_device(i, USB)
	set current_item to item i of USB
	repeat until current_item = ""
		set i to i + 1
		set current_item to item i of USB
		if current_item contains "Serial Number:" then
			set serial_num to text ((offset of ": " in current_item) + 2) through -1 of current_item
		else if current_item contains "Mount Point:" then
			set mount_point to text ((offset of ": " in current_item) + 2) through -1 of current_item
		end if
	end repeat
	return {{serial_num, mount_point}, i}
end process_usb_device

You are a true master!!!

Many thanks.

:smiley:

Glad to be of help :smiley:

Amen, James. I started to parse the XML and decided it was easier to attack the text, but didn’t know what the OP wanted to find.

Nicely done. :cool:

Is there an alternative shell script that will grab just the Serial Number?

I’m really struggling with this.

Bob Stuart
Think Data Pty Ltd
Noosa, Qld
Australia

This grabs only the serial numbers of removable media


set USB_Drives to {}
set USB to paragraphs of (do shell script "system_profiler SPUSBDataType -detailLevel basic | grep 'Removable\\|Serial'")
set i to 1
repeat until i > (count USB)
	if item i of USB contains "Removable Media: Yes" then
		set serial to item (i + 1) of USB
		set end of USB_Drives to text ((offset of ": " in serial) + 2) through -1 of serial
		set i to i + 1
	end if
	set i to i + 1
end repeat

Thanks so much, StefanK. I couldn’t have figured that out given a millenium to do it :slight_smile:

It gives me an event log that reads:

tell current application
do shell script “system_profiler SPUSBDataType -detailLevel basic | grep ‘Removable\|Serial’”
" USB Serial Adaptor:
Serial Number: 00022285
Serial Number: 7060IK
Removable Media: Yes
Serial Number: 0831410EBF80F12F"
offset of “: " in " Serial Number: 0831410EBF80F12F”
32
end tell

How would I quote that as a result to poke it into a FileMaker Pro global?

tell me to set field “Global” to result

.doesn’t work. What declared variable should I tell it to store?

Forgive me, I’m a total beginner :confused:

Bob Stuart.

Sorry, I don’t use FileMaker.
The result of the script is the variable USB_drives, which contains a list of strings (the serial numbers)
or an empty list, if no match is found

StephanK, thanks very much. That did the trick!

Bob.

Just one more thing:

On the mac, my USB thumbdrive is identified as “USB Serial Number : 0831410EBF80F12F”

On a PC, if I check the “Volume Serial Number” it is “3005-A8CO”

Anyone know why it’s different, or are we looking at two different kinds of Serial Number?

Bob.

On the Mac it’s just the information displayed in System Profiler

I am pretty sure they are different kinds of serial numbers.

“Volume Serial Number” sounds like it is part of the disk boot-record/partition-table or the filesystem itself (I vaguely remember such numbers reported in CHKDSK). Reformating the device will probably cause it to change. Googled turned up several hits, among them one that says the number is based on the date and time that the disk was formatted.

If the number reported by system_profiler is based on the hardware, then there is no reason the two serial numbers should be similar.

I’m just running this in OS X Yosemite, but it skips the serial number.
How can I get this working please?

Thanks

Matt

Hi,

I am running this in macOS Sierra.

Getting Error: “The variable serial_num is not defined.”
Same as post above (I think), has this ever been resolved?

Someone, anyone please help.
Cheers

The script appears to have four main problems:

  1. It only pretends to get the serial numbers and mount points of attached USB drives, whereas the OP asked for the serial numbers and names of devices.
  2. It assumes there’s only one volume/mount point per drive.
  3. The cue used to start parsing the text lines for the drives currently comes after the serial numbers (where they exist), so the handler never finds those lines.
  4. It looks as if the scripter thinks that changing the value of i in the run handler repeat has some influence on the repeat index.

Parsing the System Profiler text isn’t currently as simple as it appears to have been back in 2009. :confused:

Nigel, I have worked on something similar…
Check it out at: http://macscripter.net/viewtopic.php?id=45403 and see if you could help me out.
Thanks in advance

Try :

set USB_Drives to {}
set USB to paragraphs of (do shell script "system_profiler SPUSBDataType -detailLevel basic")
set i to 0
set iMax to count USB
repeat
	set i to i + 1
	if i > iMax then exit repeat
	--if item i of USB contains "Removable Media: Yes" then
	if item i of USB contains "Product ID:" then
		set {maybe, i} to process_usb_device(i, USB)
		if class of maybe is list then set end of USB_Drives to maybe
	end if
end repeat
USB_Drives
on process_usb_device(i, USB)
	set current_item to item i of USB
	set serial_num to "?" # default value
	set mount_point to "?" # default value
	set isRemovable to false
	repeat --until current_item = ""
		set i to i + 1
		if i > (count USB) then exit repeat
		set current_item to item i of USB
		log current_item
		if current_item contains "Serial Number:" then
			set serial_num to text ((offset of ": " in current_item) + 2) through -1 of current_item
		else if current_item contains "Removable Media: Yes" then
			set isRemovable to true
		else if current_item contains "Mount Point:" then
			set mount_point to text ((offset of ": " in current_item) + 2) through -1 of current_item
			exit repeat
		end if
	end repeat
	if isRemovable then
		return {{serial_num, mount_point}, i}
	else
		return {false, i}
	end if
end process_usb_device

One of the problems was that as designed, the original code failed to define the variables serial_num or mount_point.
In my case,it’s serial_num which wasn’t defined.

Here is a part of the contents of the variable USB:
[format]
Patriot Memory:

          Product ID: 0x3100
          Vendor ID: 0x13fe  (Phison Electronics Corp.)
          Version: 1.10
          Serial Number: 07981808B2F1361F
          Speed: Up to 480 Mb/sec
          Manufacturer:         
          Location ID: 0xfa130000 / 5
          Current Available (mA): 500
          Current Required (mA): 300
          Extra Operating Current (mA): 0
          Media:
            Patriot Memory:
              Capacity: 8,01 GB (8 011 120 640 bytes)
              Removable Media: Yes
              BSD Name: disk2
              Logical Unit: 0
              Partition Map Type: GPT (GUID Partition Table)
              USB Interface: 0
              Volumes:
                EFI:
                  Capacity: 209,7 MB (209 715 200 bytes)
                  BSD Name: disk2s1
                  Content: EFI
                  Volume UUID: 0E239BC6-F960-3107-89CF-1C97F78BB46B
                Yosemite Install Disk - 10.10.4:
                  Capacity: 7,67 GB (7 667 146 752 bytes)
                  Available: 582,6 MB (582 569 984 bytes)
                  Writable: Yes
                  File System: HFS+
                  BSD Name: disk2s2
                  Mount Point: /Volumes/Yosemite Install Disk - 10.10.4
                  Content: Apple_HFS
                  Volume UUID: 72B11AB5-2D53-3564-A5CF-683019424F49

[/format]
As you may see, the first occurrence of the string “Removable Media: Yes” is 11 lines after the one containing the device’s Serial Number so the handler was unable to see it.

Yvan KOENIG running Sierra 10.12.2 in French (VALLAURIS, France) dimanche 18 décembre 2016 13:29:24

That’s a bit of an understatement :slight_smile: Even parsing it as a property list is laborious. But this works here, at least:

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

set USB to do shell script "system_profiler -xml SPUSBDataType"
set aString to current application's NSString's stringWithString:USB
set theData to aString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
set {theThing, theError} to current application's NSPropertyListSerialization's propertyListWithData:theData options:0 |format|:(missing value) |error|:(reference)
set theItems to (theThing's firstObject()'s valueForKeyPath:"_items")'s firstObject()'s valueForKeyPath:"_items._items"
set theInfo to {}
repeat with anItem in theItems
	if (anItem's isKindOfClass:(current application's NSArray)) then
		repeat with subItem in anItem
			set sn to (subItem's valueForKey:"serial_num")
			if (sn is not missing value) then
				set theMedia to (subItem's valueForKey:"Media")
				if (theMedia is not missing value) then
					repeat with aMedium in theMedia
						if aMedium is not missing value and (aMedium's valueForKey:"removable_media") as text = "yes" then
							set theVolumes to (aMedium's valueForKey:"volumes")
							repeat with aVolume in theVolumes
								set end of theInfo to {mountPoint:((aVolume's valueForKey:"mount_point") as text), serialNo:(sn as text)}
							end repeat
						end if
						
					end repeat
				end if
			end if
		end repeat
	end if
end repeat
return theInfo

I’m puzzled by your code Shane.

With a single USB device connected, it returns a list of two lists :
[format]{{mountPoint:“missing value”, serialNo:“07981808B2F1361F”}, {mountPoint:“/Volumes/Yosemite Install Disk - 10.10.4”, serialNo:“07981808B2F1361F”}}[/format]

As you may see,the same device is returned twice:
1 - with no mountPoint
2 - with its true mountPoint

My understanding is that a complementary test upon mountPoint is required.

repeat with aMedium in theMedia
	if aMedium is not missing value and (aMedium's valueForKey:"removable_media") as text = "yes" then
		set theVolumes to (aMedium's valueForKey:"volumes")
		repeat with aVolume in theVolumes
			set maybe to (aVolume's valueForKey:"mount_point")
			if maybe is not missing value then
				set end of theInfo to {mountPoint:maybe as text, serialNo:(sn as text)}
			end if
		end repeat
	end if
end repeat

Yvan KOENIG running Sierra 10.12.2 in French (VALLAURIS, France) dimanche 18 décembre 2016 14:50:25

In a nod to the original question in this thread, this returns (on my mid-2009 MBP running El Capitan) a list of records containing the name, serial number (where available) and mount points (where appropriate) of connected USB devices. To keep the result simple, the structure doesn’t include the buses, but it could if desired:

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

set USB to (do shell script "system_profiler -xml SPUSBDataType" without altering line endings)
-- set USB to (read (choose file) as «class utf8») -- For testing with Yvan's XML results.

set aString to current application's class "NSString"'s stringWithString:(USB)
set theData to aString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
set {theThing, theError} to current application's class "NSPropertyListSerialization"'s propertyListWithData:(theData) options:(0) |format|:(missing value) |error|:(reference)
set deviceDetails to current application's class "NSMutableArray"'s new()
parseRecursively(theThing's firstObject(), deviceDetails)
return deviceDetails as list

on parseRecursively(thisThing, deviceDetails)
	set subthings to thisThing's valueForKey:("_items")
	repeat with thisSubthing in subthings
		if (thisSubthing's allKeys()'s containsObject:("_items")) then
			parseRecursively(thisSubthing, deviceDetails)
		else
			-- In El Capitan, 'volumes', if it exists, is a direct property of a device. In Sierra, it's apparently a property of individual 'media' which a device may have.
			if (thisSubthing's allKeys()'s containsObject:("Media")) then -- Sierra.
				set theMedia to (thisSubthing's valueForKey:("Media"))
				set mediaDetails to current application's class "NSMutableArray"'s new()
				repeat with thisMedium in theMedia
					set theseDetails to (current application's class "NSDictionary"'s dictionaryWithObjects:({thisMedium's valueForKey:("_name"), thisMedium's valueForKeyPath:("volumes.mount_point")}) forKeys:({"name", "mount points"}))
					tell mediaDetails to addObject:(theseDetails)
				end repeat
				set thisEntry to (current application's class "NSDictionary"'s dictionaryWithObjects:({thisSubthing's valueForKey:("_name"), thisSubthing's valueForKey:("serial_num"), mediaDetails}) forKeys:({"name", "serial number", "media"}))
			else -- El Capitan.
				set mountPoints to (thisSubthing's valueForKeyPath:("volumes.mount_point")) -- List of mount points.
				set thisEntry to (current application's class "NSDictionary"'s dictionaryWithObjects:({thisSubthing's valueForKey:("_name"), thisSubthing's valueForKey:("serial_num"), mountPoints}) forKeys:({"name", "serial number", "mount points"}))
			end if
			tell deviceDetails to addObject:(thisEntry)
		end if
	end repeat
end parseRecursively

Edits:
1. Script replaced with a recursive version which burrows down through the “_items” of any objects which have them. This should remove the previous assumption about the hierarchy depth.
2. Another revision based on a couple of XML inputs which Yvan helpfully sent me. In his El Capitan XML, the ‘volumes’ are direct properties of the devices. In Sierra, they’re properties of individual ‘Media’ possessed by the devices.