Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Getting a USB drive letter programmatically

Status
Not open for further replies.

Bryan - Gendev

Programmer
Jan 9, 2011
408
AU
I am using this code
Declare Integer WNetGetConnection In win32api ;
STRING lpszLocalName,;
STRING lpszRemoteName,;
INTEGER @ lpchBuffer && Declare the external WNetGetConnection ;
API Function

Works fine, however I would like to be able to list any USB drives that may be connected to the PC - as in Windows Disk Management.
Anyone know how?

GenDev
 
You can check what type is the drive with DRIVETYPE() function

Borislav Borissov
VFP9 SP2, SQL Server
 
Thanks Borislav,

The problem is that the above code does not find the USB storage drive. I put the drive letters in a dropdown list for the user to choose which drive to copy to.

GenDev
 
Yes, DRIVETYPE() would work. According to the Help, it returns 2 for a floppy-disk drive, but it also returns 2 for a USB drive. Since you are highly unlikely to have a floppy drive installed, it's reasonable to assume that a value of 2 means that it is USB drive.

You need to pass the drive letter. If you don't know the letter, you can call the function in a loop, passing all the letters from A to Z. It will return 1 if the letter is not a valid drive.

Here's some code:

Code:
LOCAL lnType, lcDrive, lnI

FOR lnI = 0 TO 25
  lcDrive = CHR(65 + lnI)
  lnType = DriveType(lcDrive)
  IF lnType <> 1
    ? lcDrive + SPACE(5) + ICASE( ;
       lnType = 2, "USB / Floppy", ;
       lnType = 3, "Fixed", ;
       lnType = 4, "Network", ;
       lnType = 5, "CD-ROM", ;
       lnType = 6, "RAM Disk", "Unknown")
  ENDIF
ENDFOR

Mike



__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Thanks again Mike
I had not realised that Type 2 was "USB / Floppy" and had neglected to record it in my table.
Thanks once again

GenDev
 
As slight modification of thread184-1828754:

Code:
Function GetUSBDrives(tcAliasname)
   tcAliasname = EVL(m.tcAliasname,'USBDrives')
   Local lnDrive, lcDrive
   Create Cursor (m.tcAliasname) (Driveletter C(1))
   For lnDrive= 3 To 26 && avoid 'A:\ and 'B:\'
      lcDrive = Chr(64+m.lnDrive)
      If DriveType(m.lcDrive)=2 && floppy disk , USB drive
         Insert into (m.tcAliasname) values (m.lcDrive)
      EndIf
   EndFor

The result of the function is a newly created cursor with the aliasname passed in or 'USBDrives' by default, which has a record per USB stick drive letter.

The code avoids checking A and B, which are the only drive letters usually associated with floppy drives. You can pretty much rely on this, as I never saw anyone using other letters than A and B for floppy drives, also drive letters automatically assigned to a USB stick you plug in will be after 'C' and never 'A' or 'B'. Both of these ciorcumstances together make any further checks and verifications uunnecessary.

You can add to it, by getting the volume names, that makes it easier to pick the right drive, as the volume name of USB sticks usually is the vendor of the drive or even a more specifc brand name of the vendor for that stick.
Code:
Function GetUSBDrives(tcAliasname)
   tcAliasname = EVL(m.tcAliasname,'USBDrives')
   Local lnDrive, lcDrive
   Create Cursor (m.tcAliasname) (Driveletter C(1), volumename V(128))
   For lnDrive= 3 To 26 && avoid 'A:\ and 'B:\'
      lcDrive = Chr(64+m.lnDrive)
      If DriveType(m.lcDrive)=2 && floppy disk , USB drive
         ADIR(laVolumename,m.lcDrive+':\','V')
         Insert into (m.tcAliasname) values (m.lcDrive,laVolumename[1])
      EndIf
   EndFor

USB hard drives don't show up as drivetype 2, though. If you want to find all USB attached drives, no matter if stick or a larger drive, that would require getting more information than Drivetype does. Because DriveType then returns 3 (hard drive) just like for internal local hard drives , not 4 as you could expect from the documentation listing 4 as "Removable drive or network drive". So "removable drive" means something else, it would show up for the drive letters you can get when you have a USB card reader.

So if you do this for deciding to where a backup should go, I'd usually not only want USB sticks listed, but also USB drives with usually much larger capacities. I have to look into that and if you like, would add to that code to differentiate between internal drives and USB attached drives, which both have DRIVETYPE() 3. I guess that needs a bit more info from an API call about volume information.



Chriss
 
GenDev, you mentioned that you have the drive letters in a combo box for the user to choose from.

That's fine. But you might prefer to create a combo box (or some other control) which displays the volume labels of all the mounted USB drives rather than just the drive letters. I think that would be more meaningful to the users. The code I showed above will tell you the drive letters corresponding to USB drives, but it does not let you retrieve the volume labels. Nor is there a native function to do that, as far as I know .

However, there is an API function called GetVolumeInformation() which can provide this information. Combined with the DriveType() code I showed above, it should do what you want.

There is an example of how to use GetVolumeInformation() on GitHub. See:

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Mike Lewis said:
but it does not let you retrieve the volume labels. Nor is there a native function to do that, as far as I know .
Well, I just posteed it within my sample code:
Code:
ADIR(laVolumename,m.lcDrive+':\','V')
The ADIR function can return the volume name/label if you use 'V' as flag parameter.

Chriss
 
You're right, Chris. I missed that. I did check the Help before posting. It doesn't mention the "V" attribute in the table of attribute values, but later on it says, "You can include V in cAttribute to return the volume name of the current drive."

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Don't worry, and I said flag while it's the cAttribute parameter, which name can easily be confused with the file attrtibutes, which the ADIR function also outputs. Sometimes the naming of the parameters itself already is confuing and getting the volume name that way is something I also found here in an older post, not in the help.

I was thinking about Win API GetVolumeInformation function myself as I thought it would also tell more about the drive and hardware interface (USB/IDE/SCSI etc.). It just get's much more complicated, when you want to get just a llittle better distinction between the drive types. The Windows API function GetDriveType() is having the same results as VFPs DriveType() function, I guess the VFP funciton is just mapping to the Windows API function.

Never mind, in the end you could already provide a helpful list to the user, if you provide all drives no matter which drivetype and add the volume name, usually that will be what users recognize with their USB stick, as it's also displayed by Windows Explorer.

So the simpler function listing all drives (C:\ or higher) would be my choice with a little bit of responsibility taken by the user to decide where to store something. There are reasons, even when the picked drive is a fixed local drive, to first make a backup there as it's a fast drive and then later manually copy the target zip or folder to another external drive manually.

Code:
Function GetUSBDrives(tcAliasname)
   tcAliasname = EVL(m.tcAliasname,'USBDrives')
   Local lnDrive, lcDrive
   Create Cursor (m.tcAliasname) (Driveletter C(1), volumename V(128))
   For lnDrive= 3 To 26 && avoid 'A:\ and 'B:\'
      lcDrive = Chr(64+m.lnDrive)
      If DriveType(m.lcDrive)>1, any drive type, except "no drive"
         ADIR(laVolumename,m.lcDrive+':\','V')
         Insert into (m.tcAliasname) values (m.lcDrive,laVolumename[1])
      EndIf
   EndFor

Otherwise, you can get into details using WMI, but the informations needed to match drive letters with drive types needs to come from Win32_DiskDrive, Win32_DiskPartition, Win32_LogicalDrive, Win32_Volume and Win32_MappedLogicalDisk or Win32_LogicalDiskToPartition to get the relations between hardware and partitions and drive letters, not sure yet, how to make the connections.

Chriss
 
Hello Chris, you can get all the disk information ( and any other info wmi has to offer )
in one line call using _WMI.PRG , a short utility I shared. You can get it at


just save it and try
Code:
oDisk = _wmi('Win32_diskDrive')

you'll get something like this:

usb_hijsej.png


also try "do testme in _wmi" to see this and some other useful querys.



Marco Plaza
 
Thanks, mplaza!

Always useful, what you provide.

I found this: InterfaceType USB is not definitive, My Seagate Expansion drive is shown with InterfaceType SCSI.

The Diskdrive also only is about the drive, some drives can have multiple partitions and thus logical drives are not equal to physical drives.
Besides, that raw _wmi.prg fails on not finding _newempty.prg, _error.prg and maybe more.

Chriss
 
Yes, I recognized that mediatype is a good property to look into. You still marked the InterfaceType spot in your screenshot.

And you still miss a point, physical drive is not equal to drive letter. And while all simple usb thum drives will come up as just one drive letter,... Well, just read a bit about the codeproject article, please, so we're discussing on an equalized level.

Let me quote one sentence:
stevenmcohn said:
...We now need a way to associate Win32_DiskDrive and Win32_LogicalDisk so we can marry these bits of information together. You might think there would be some shared field that allows you join the two classes. No such luck. And that's exactly where the Web came to the rescue and I discovered a bit of code tucked away on MSDN that demonstrates how to associate these classes. We can use the associators operator to discover associations between various classes. Given a Win32_DiskDrive instance, we can use its DeviceID property to determine the Win32_DiskPartition instance associated via Win32_DiskDriveToDiskPartition...

Chriss
 
You are right.. no drive letter.. but I just shared a tool to ease the tasks you mentioned above!
See how easy is to examine any wmi class. Check Win32_logicalDisk and see if driveType / deviceId helps:

win32_logicaldisk_vlennw.png


Marco Plaza
 
Not trivial indeed, but here is the vfp code using _wmi.prg:

Code:
*----------------------------------------------------------------
* Marco Plaza, 2024
* sample using nfox _wmi.prg ( [URL unfurl="true"]https://github.com/nfoxdev/_wmi[/URL] )
* get drive type, letter
*----------------------------------------------------------------

clear
local dd2p,ld2p,dependent,antecedent,odisk,rel,opartition,driveletter

dd2p = _wmi('win32_DiskDriveToDiskPartition')
ld2p = _wmi('Win32_LogicalDiskToPartition')

for each rel in dd2p.items

   dependent  = strextract(rel.dependent,'="','"')
   antecedent = strextract(rel.antecedent,'="','"')

   odisk = _wmi('win32_DiskDrive where deviceId="'+m.antecedent+'"')

   driveletter = '* no drive letter *'
   for each opartition in ld2p.items
      if strextract( opartition.antecedent,'="','"') = m.dependent
         driveletter = strextract(opartition.dependent,'="','"')
         exit
      endif
   endfor

   ? '*---------------------------'
   ? m.antecedent,m.dependent
   ? 'Drive letter:',m.driveletter

   with odisk.items(1)
      ? 'Caption:',.caption
      ? 'Interface Type:',.interfacetype
      ? 'Media Type:',.mediatype
   endwith

endfor

Marco Plaza
 
Well done, mplaza, thank you!

For usage of this, we're still missing dependent PRGs, so _wmi.prg can run.
myself said:
that raw _wmi.prg fails on not finding _newempty.prg, _error.prg and maybe more.

mplaza said:
Not trivial indeed

You see why I said
myself said:
the simpler function listing all drives (C:\ or higher) would be my choice with a little bit of responsibility taken by the user to decide where to store something.
And
myself said:
add the volume name, usually that will be what users recognize with their USB stick, as it's also displayed by Windows Explorer.

Not that I was lazy to do a little bit of WMI:
Code:
oWMI = GETOBJECT("winmgmts:\\.\root\cimv2")
oQueryResult = oWMI.ExecQuery("select * from Win32_DiskDrive")
nIndex = 0
For each oItem in oQueryResult
  nIndex = nIndex + 1 
  ? 'drive',nIndex
  For each oProperty in oItem.Properties_
     If oProperty.Name $ "Caption,InterfaceType,MediaType"
        ? oProperty.Name, oProperty.Value
     EndIf 
  EndFor 
  ? '-----'
EndFor

I just stopped at this stage, simply because I also have other things to do. It#s not hard to do the same code with Win32_DiskDriveToDiskPartition and Win32_LogicalDiskToPartition and pick out the necessary properties to bring all informations together.

Chriss
 
Whoops.. Certainly I did not notice I uploaded a _wmi.prg that has some new dependencies.
The repo is now updated with the standalone version, wich I also post below to save the trip:

PS:
the codeproject article you shared was very useful, it saved a lot of time.

Code:
*-----------------------------------------------------------------------------------
* Marco Plaza, 2022
* [URL unfurl="true"]https://github.com/nfoxdev/_wmi[/URL]
* this program should be saved as _WMI.PRG
*-----------------------------------------------------------------------------------
*
* simple usage: 
* _wmi( wmiClass [where <filter condition>] [, wmiNameSpace] )
* where: optional query filter
* wmiNameSpace defaults to "CIMV2"
*
* ie: 
*
* oDisks = _wmi('Win32_diskDrive') 
* oMonitors = _wmi('Win32_PNPEntity where service = "monitor"')
*
* Test: save this program as _wmi.prg and do "testme in _wmi"
*
*------------------------------------------------------------------------------------
lparameters wmiquery,wmiclass

local oerr,emessage,objwmiservice,oquery,owmi

wmiclass = Evl(m.wmiclass,'CIMV2')
wmiquery = Evl(m.wmiquery,'')

emessage = ''

Try
   objwmiservice = Getobject("winmgmts:\\.\root\"+m.wmiclass)
   oquery = objwmiservice.execquery( 'SELECT * FROM '+m.wmiquery,,48)
   owmi = processobject( oquery )
Catch To oerr
   emessage = m.oerr.Message
Endtry

If !Empty(m.emessage)
   Error ' Invalid Query/WmiClass '
   Return .Null.
Else
   Return m.owmi
Endif

*-------------------------------------------------
Procedure processobject( oquery )
*-------------------------------------------------
local owmi,nitem,oitem

owmi = Createobject('empty')
AddProperty(owmi,'items(1)',.Null.)
nitem = 0

Try

   For Each oitem In m.oquery

      nitem = m.nitem + 1
      Dimension owmi.items(m.nitem)
      owmi.items(m.nitem) = Createobject('empty')
      setproperties( m.oitem, owmi.items(m.nitem) )

   Endfor

Catch

Endtry

AddProperty(owmi,'count',m.nitem)

Return m.owmi

*--------------------------------------------------------
Procedure setproperties( oitem , otarget  )
*--------------------------------------------------------

local oerr,thisproperty,thisarray,nitem,thisitem,property,item

For Each property In m.oitem.properties_
   Try
      Do Case
      Case Vartype( m.property.Value ) = 'O'
         thisproperty = Createobject('empty')
         setproperties(m.property.Value, m.thisproperty )
         AddProperty( otarget ,m.property.Name,m.thisproperty)

      Case m.property.isarray

         AddProperty( otarget ,property.Name+'(1)',.Null.)
         thisarray = 'otarget.'+m.property.Name

         nitem = 0

         If !Isnull(m.property.Value)

            For Each Item In m.property.Value

               nitem = m.nitem+1
               Dimension &thisarray(m.nitem)

               If Vartype( m.item) = 'O'
                  thisitem = Createobject('empty')
                  setproperties( m.item, m.thisitem )
                  &thisarray(m.nitem) = m.thisitem
               Else
                  &thisarray(m.nitem) = m.item
               Endif

            Endfor

         Endif

      Otherwise
         AddProperty( otarget ,m.property.Name,m.property.Value)
      Endcase

   Catch To oerr
      Messagebox(Message(),0)
   Endtry
Endfor


*----------------------------------
procedure testme
*----------------------------------
Public oinfo

oinfo = Create('empty')

Wait 'Running WMI Query....please wait.. ' Window Nowait At Wrows()/2,Wcols()/2

addproperty( oinfo, "monitors"  , wmiquery('Win32_PNPEntity where service = "monitor"') )
addproperty( oInfo, "diskdrive" , wmiquery('Win32_diskDrive') )
addproperty( oInfo, "startup" ,   wmiquery('Win32_startupCommand'))
addproperty( oInfo, "BaseBoard" , wmiquery('Win32_baseBoard') )
addproperty( oInfo, "netAdaptersConfig",  wmiquery('Win32_NetworkAdapterConfiguration') )


Messagebox( 'Please explore "oInfo" in debugger watch window or command line ',0)






Marco Plaza
 
Chriss,

I would take any misidentification between Marco and me as a badge of honour, but you should edit your posts to refer to Marco appropriately :)
 
Oops, I did it again.

I can't say I'm sure the posts appeared under the username atlopes, it happened Filip Brnic about me and Mike Lewis, unless that also just was personal confusion.
Anyway, in this case, even if it would have been the forum displaying the wrong username I could have recognzied Marco by the nf prefix of the Github links.

Chriss
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top