Jump to content

UDF - get a list of USB-attached drives


blindwig
 Share

Recommended Posts

I have a need to get a list of USB-attached drives on my system. I couldn't find any AutoIt functions to do this, and I don't know how to do any system calls or anything fancy, so I ended up writing a script to interface with DISKPART.EXE, a command-line tool for managing disks. This script obviously won't work on a system that does not have access to DISKPART.EXE

This function returns an [x][2] array, where [x][0]=drive number and [x][1]=drive size. The length of the array is in [0][0]

Notice that I return drive numbers, not letters. Letters are too ambigious - a physical drive can have multiple letters (partitions), no letter (if windows doesn't recognise the file system, or if it's letter has been removed), or a letter can represent multiple physical drives (spanned or stripped volumes).

Also note that drive numbers are 0-based.

CODE

Func DriveGetUsbList()

dim $i, $temp

;Step 1 - Get all valid drive numbers

Dim $DriveListNum[100][2], $DriveListNext=1, $DriveListText

;$DriveListNum is a table of valid drive numbers and their sizes, $DriveListNext is an index to the next free entry

;$DriveListText is a temporary variable used for parsing text in and out

$DriveListText=StringSplit(RunGetOutput(@ComSpec & " /c echo list disk | diskpart"), @CRLF)

For $i = 1 to $DriveListText[0]

if StringInStr($DriveListText[$i], "Disk ") Then

$Temp = StringMid($DriveListText[$i],8,3)

if $Temp<>"###" Then

$DriveListNum[$DriveListNext][0] = Int($Temp)

$DriveListNum[$DriveListNext][1] = StringMid($DriveListText[$i],25,7)

$DriveListNext = $DriveListNext + 1

EndIf

If $DriveListNext > Ubound($DriveListNum) Then Redim $DriveListNum[ubound($DriveListNum)+20][2]

EndIf

Next

$DriveListNum[0][0]=$DriveListNext ;Element [0][0] contains the list size (1-based)

;Step 2 - figure out which are attached via USB

Dim $TempFile, $hndFile_Temp, $DriveUsbCur

;Step 2.1 - build a script for DiskPart

$TempFile = _TempFile()

$hndFile_Temp = FileOpen($TempFile, 2)

for $i = 1 to $DriveListNext - 1

FileWriteLine($hndFile_Temp, "Select Disk " & $DriveListNum[$i][0])

FileWriteLine($hndFile_Temp, "Detail Disk")

Next

FileClose($hndFile_Temp)

;Step 2.2 - execute the script for DiskPart

$DriveListText=StringSplit(RunGetOutput(@ComSpec & " /c echo list disk | diskpart < " & $TempFile), @CRLF)

;Step 2.3 - parse the results, build a list of USB drives

Dim $DriveListUsb[$DriveListNext-1][2]

$DriveListNext=1

For $i = 1 to $DriveListText[0]

if StringRight($DriveListText[$i], 26) = ' is now the selected disk.' Then

$DriveUsbCur = StringSection($DriveListText[$i], 2, " ")

EndIf

if $DriveListText[$i] = 'Type : USB' Then

$DriveListUsb[$DriveListNext][0] = $DriveUsbCur

$DriveListUsb[$DriveListNext][1] = TableLookUp($DriveListNum, $DriveUsbCur)

$DriveListNext = $DriveListNext + 1

EndIf

Next

$DriveListUsb[0][0]=$DriveListNext-1

;Step 3 - Return the results table

If $DriveListUsb[0][0] > 0 Then

Redim $DriveListUsb[$DriveListUsb[0][0]+1][2]

Return $DriveListUsb

Else

Return 0

EndIf

EndFunc

Link to comment
Share on other sites

pardon me for asking. but wtf is:

RunGetOutput

<{POST_SNAPBACK}>

Oops, forgot to include that. It's another function I wrote that will run a command (intended for console-mode commands) that will return the output in a string.

Here's the code:

CODE

Func RunGetOutput($CommandLine)

Dim $Tempfile, $Return

;Find a temp file. Could use _TempFile() instead, if file.au3 is included

Do

$TempFile = @TempDir & '\' & Int(Random(65536)) & '.txt'

Until not FileExists($TempFile)

;Run the inputted command, redirect output to our tempfile

RunWait($CommandLine & " > " & $TempFile, '', @SW_HIDE)

;Read the temp file into a string, delete the temp file, return the string

$Return = FileRead($TempFile, FileGetSize($TempFile))

FileDelete($TempFile)

Return $Return

EndFunc

Link to comment
Share on other sites

Where are StringSection and TableLookUp defined?

<{POST_SNAPBACK}>

StringSection is like StringSplit(), except that instead of returning the entire array, it just returns the section that you want. Here's the code:

CODE

Func StringSection($Input, $Number, $Seperator="|")

;Returns a substring of $Input, between $Number and $Number+1 occurance of $Seperator

$Number=Int($Number)

If $Number>0 And $Number<=StringInCount($Input, $Seperator)+1 Then

If $Number=1 Then

$Begin=0

$End=StringInStr($Input, $Seperator, 0, 1)

Else

$Begin=StringInStr($Input, $Seperator, 0, $Number-1)

$End=StringInStr($Input, $Seperator, 0, $Number)

If @error Then $End=StringLen($Input)+1

EndIf

Return StringMid($Input, $Begin+1, $End-$Begin-1)

Else

Return ""

EndIf

EndFunc

TableLookUp looks at an array[x][2] and returns the value of [x][1] such that [x][0] matches the given key.

This is the predecessor to my table library:

http://www.autoitscript.com/forum/index.php?showtopic=12136

Here's the old code:

CODE

Func TableLookUp($Table, $Key)

;$Table is an array of [x][1]. Function finds a record such that $Key=$Table[x][0] and returns $Table[x][1]

If UBound($Table, 0)<2 Then Return ""

For $i=0 to UBound($Table, 1)-1

If $Table[$i][0]=$Key Then Return $Table[$i][1]

Next

Return ""

EndFunc

I'm glad you think my code is clean. This is probably one of the first things I wrote with AutoIt, and looking at it now I see plenty of room for improvement :(
Link to comment
Share on other sites

OK, I totally re-wrote the routine. Now it returns a table containing a list of all drives, keyed by drive number, and containing a CSV field with all the drive details. Again, this relies on shelling out to run DISKPART, so it is dependant on that.

Function _DriveGetList() takes no input, and if successful, returns a table containing the drive info, keyed by drive number

CODE

Func _DriveGetList()

Dim $EOF

Dim $DriveNum, $DriveDetails, $Detail

Dim $ReadLine, $RetVal

Dim $OutputFileName, $hndFile_Output

Dim $InputFileName, $hndFile_Input

Dim $tblDriveList

Dim $i, $DriveNums

;Find names for temporary output files

Do

$OutputFileName = @TempDir & '\' & Int(Random(65536)) & '.txt'

Until not FileExists($OutputFileName)

Do

$InputFileName = @TempDir & '\' & Int(Random(65536)) & '.txt'

Until not FileExists($InputFileName)

;Write drive list to output file

$RetVal=RunWait(@COMSPEC & ' /c echo list disk | diskpart>' & $OutputFileName,'',@SW_HIDE)

If $RetVal Then Return ''

;Read output, parse to find drive list

_TableCreate($tblDriveList)

$hndFile_Output=FileOpen($OutputFileName,0)

$ReadLine = FileReadLine($hndFile_Output)

$EOF = @error

While Not $EOF

While (StringMid($ReadLine, 3, 5) <> 'Disk ' Or StringMid($ReadLine, 8, 3)='###') And Not $EOF

$ReadLine = FileReadLine($hndFile_Output)

$EOF = @error

WEnd

If Not $EOF Then

$DriveNum=StringStripWS(StringMid($ReadLine, 8, 3), 3)

$DriveDetails=StringStripWS(StringMid($ReadLine, 13, 10), 3)

$DriveDetails=$DriveDetails & ',' & StringStripWS(StringMid($ReadLine, 25, 7), 3)

$DriveDetails=$DriveDetails & ',' & StringStripWS(StringMid($ReadLine, 34, 7), 3)

$DriveDetails=$DriveDetails & ',' & StringStripWS(StringMid($ReadLine, 43, 3), 3)

$DriveDetails=$DriveDetails & ',' & StringStripWS(StringMid($ReadLine, 43, 3), 3)

_TableSetValue($tblDriveList, $DriveNum, $DriveDetails)

$ReadLine = FileReadLine($hndFile_Output)

EndIf

WEnd

FileClose($hndFile_Output)

;Build a script to get extended drive info

$DriveNums=_TableGetKeys($tblDriveList)

$hndFile_Input = FileOpen($InputFileName,2)

For $i = 1 To $DriveNums[0]

FileWriteLine($hndFile_Input, 'select disk ' & $DriveNums[$i])

FileWriteLine($hndFile_Input, 'detail disk')

Next

FileClose($hndFile_Input)

;Run script, save output

$RetVal=RunWait(@COMSPEC & ' /c echo list disk | diskpart /s ' & $InputFileName & '>' & $OutputFileName,'',@SW_HIDE)

FileDelete($InputFileName)

If $RetVal Then Return ''

;Parse output, look for extended info

$hndFile_Output=FileOpen($OutputFileName,0)

$DriveNum=-1

$ReadLine = FileReadLine($hndFile_Output)

$EOF = @error

While Not $EOF

$Detail = StringLeft($ReadLine, 9)

;look for a new drive number

If StringInStr($ReadLine, ' is now the selected disk.') Then

;if switching drives, then need to write details from the last drive to the table

If $DriveNum>=0 Then

_TableSetValue($tblDriveList, $DriveNum, $DriveDetails)

EndIf

;Get a new drive number, get details for that drive

$DriveNum = StringStripWS(StringMid($ReadLine, 6, 2), 3)

$DriveDetails = _TableGetValue($tblDriveList, $DriveNum)

;skip a line

$ReadLine = FileReadLine($hndFile_Output)

$EOF = @error

;next line should be the drive name

If Not $EOF Then

$ReadLine = FileReadLine($hndFile_Output)

$EOF = @error

$DriveDetails = $DriveDetails & ',' & $ReadLine

EndIf

EndIf

;look for details about the current drive

If _StringInSet($Detail,'Disk ID: ,Type : ,Bus : ,Target : ,LUN ID : ') Then

$DriveDetails = $DriveDetails & ',' & StringMid($ReadLine, 10)

EndIf

$ReadLine = FileReadLine($hndFile_Output)

$EOF = @error

WEnd

;Write remaining details to the table

If $DriveNum>=0 Then

_TableSetValue($tblDriveList, $DriveNum, $DriveDetails)

EndIf

FileClose($hndFile_Output)

FileDelete($OutputFileName)

Return $tblDriveList

EndFunc

Func _DriveGetListUSB($tblDrives)

Dim $tblDrives, $DriveNums, $DriveDetails, $i

If Not _TableIsValid($tblDrives) Then $tblDrives = _DriveGetList()

$DriveNums = _TableGetKeys($tblDrives)

Dim $UsbList[$DriveNums[0]+1]

$UsbList[0]=1

For $i = 1 to $DriveNums[0]

$DriveDetails = StringSplit(_TableGetValue($tblDrives,$DriveNums[$i]), ',')

If UBound($DriveDetails)>=7 Then

If $DriveDetails[8]='USB' Then

$UsbList[$UsbList[0]]=$DriveNums[$i]

$UsbList[0] = $UsbList[0] + 1

EndIf

EndIf

Next

Redim $UsbList[$UsbList[0]]

$UsbList[0] = $UsbList[0] - 1

Return $UsbList

EndFunc

Function _DriveGetListUSB will return an array containing the numbers of USB drives. It will take a table returned by _DriveGetList(), or will generate a fresh one.

CODE

Func _DriveGetListUSB($tblDrives = -1)

Dim $tblDrives, $DriveNums, $DriveDetails, $i

If Not _TableIsValid($tblDrives) Then $tblDrives = _DriveGetList()

$DriveNums = _TableGetKeys($tblDrives)

Dim $UsbList[$DriveNums[0]+1]

$UsbList[0]=1

For $i = 1 to $DriveNums[0]

$DriveDetails = StringSplit(_TableGetValue($tblDrives,$DriveNums[$i]), ',')

If UBound($DriveDetails)>=7 Then

If $DriveDetails[8]='USB' Then

$UsbList[$UsbList[0]]=$DriveNums[$i]

$UsbList[0] = $UsbList[0] + 1

EndIf

EndIf

Next

Redim $UsbList[$UsbList[0]]

$UsbList[0] = $UsbList[0] - 1

Return $UsbList

EndFunc

Both of these functions require my tables UDF and my _StringInSet() function.
Link to comment
Share on other sites

i dont suppose youd be willing top make a zip of all of your udf's and pst em? some look very useful...

perty plz :(

<{POST_SNAPBACK}>

Right now they're mostly works in progress. As I complete a function I post it here for some peer review. Maybe once I have a good collection of functions that form a logical module I'll post it up here. But if you like my work, just do a search for my name and UDF in the title.
Link to comment
Share on other sites

  • 1 year later...

could you also retrieve a drive letter or perhaps a list of drive letters?or can an optical drive be called by the drive number? as i do here:

CDTray("F:", "close")

Edit: Also, I am asuming that Func _DriveGetList() is not dependent upon Func _DriveGetListUSB and visa versa is that correct?

Edited by jzn2
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...