Get valid drive letter and is occupied
Asked Answered
T

2

5

I want scan all available drive letter that exist on my computer, and get detail with it (chk if occupied, chk for type and size).

I have no problem about how to get size by using the codes bellow

var
  FreeAvail, totalSpace: Int64;
begin
  if SysUtils.GetDiskFreeSpaceEx(PChar('F:\'), FreeAvail, totalSpace, nil) = True
  then
  begin
    F1.Liner('Drive F total space ');
    F1.pBold(IntToStr(totalSpace div (1024 * 1024 * 1024)) + ' GB ,');
    F1.Liner(' available free space ');
    F1.pBold(IntToStr(FreeAvail div (1024 * 1024 * 1024)) + ' GB.');
  end;
end;

But if the drive is unoccupied, i don't like this situation.

error message if no media

Question: How to get available ALL drive(s) - CDROM, USB Stick, etc. To be more specific, i want the display result like this example;

Drive E [Local Disk] - TotalSpace 500 GB - FreeSpace 200 GB

Drive F [CD Drive] - Unoccupied - FreeSpace 0

Drive G [Removable] - TotalSpace 8 GB - FreeSpace 2 GB

Thallus answered 18/10, 2014 at 20:14 Comment(2)
Do GetVolumeInformation and GetDriveType help you?Intransigent
Thank you, will digging more on those two things.Thallus
C
11

I've provided a couple of functions that might help. The first uses the Win32 API function GetLogicalDriveStrings to retrieve a list of the assigned drive letters on the computer. The second queries a drive to see if it's ready for use (has a disk in it). (There's also a utility function that converts a drive letter to the integer value needed for DiskSize, the old Pascal I/O function.)

The code has worked since Win95 days, and was just tested on Win7 64-bit in a Delphi 2007 console application. There's a console test application included below.

program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils, Windows, Types;

// Returns an array filled wit the assigned
// drive letters on the current computer.
function  GetDriveList: TStringDynArray;
var
  Buff: array[0..128] of Char;
  ptr: PChar;
  Idx: Integer;
begin
  if (GetLogicalDriveStrings(Length(Buff), Buff) = 0) then
    RaiseLastOSError;
  // There can't be more than 26 lettered drives (A..Z).
  SetLength(Result, 26);      

  Idx := 0;
  ptr := @Buff;
  while StrLen(ptr) > 0 do
  begin
    Result[Idx] := ptr;
    ptr := StrEnd(ptr);
    Inc(ptr);
    Inc(Idx);
  end;
  SetLength(Result, Idx);
end;

// Converts a drive letter into the integer drive #
// required by DiskSize().
function DOSDrive( const sDrive: String ): Integer;
begin
  if (Length(sDrive) < 1) then
    Result := -1
  else
    Result := (Ord(UpCase(sDrive[1])) - 64);
end;

// Tests the status of a drive to see if it's ready
// to access. 
function DriveReady(const sDrive: String): Boolean;
var
  ErrMode: Word;
begin
  ErrMode := SetErrorMode(0);
  SetErrorMode(ErrMode or SEM_FAILCRITICALERRORS);
  try
    Result := (DiskSize(DOSDrive(sDrive)) > -1);
  finally
    SetErrorMode(ErrMode);
  end;
end;

// Demonstrates using the above functions.
var
  DrivesArray: TStringDynArray;
  Drive: string;
const
  StatusStr = 'Drive %s is ready: %s';
begin
  DrivesArray := GetDriveList;
  for Drive in  DrivesArray do
    WriteLn(Format(StatusStr, [Drive, BoolToStr(DriveReady(Drive), True)]));
  ReadLn;
end.

Sample output when run on my system (Win7 64, two physical hard drives (C: and D:), an ISO device with no image mounted (E:), and a DVD drive (Z:).

Drive C:\ is ready: True 
Drive D:\ is ready: True 
Drive E:\ is ready: False
Drive Z:\ is ready: True
Convexoconcave answered 18/10, 2014 at 21:20 Comment(23)
This is good, but the calls to SetLastError are a little off. It's a very quirky API. As you have written it, you will remove any flags that are already set. I think it needs two calls as per my answer.Humanitarian
@David: No, it doesn't. It saves the previous value (which is returned by the first call to SetErrorMode) and restores it afterward. (There are two calls - see the finally block.)Convexoconcave
No. Between the first and second calls, you've lost whatever flags were there to begin with. You'd need three calls to add a flag and then restore. Best practice is to add them all and startup and leave it at that.Humanitarian
@David: No. SetErrorMode returns the previous flags, according to the docs. I store those flags, set it to only SEM_FAILCRITICALERRORS, and then restore the previous flag value in the finally block.Convexoconcave
Careful, ErrorMode is global for the process and thus vulnerable if threads are changing mode.Ingvar
@LURD: Yes, it is. Win7 adds SetThreadErrorMode to address this, but that isn't available in earlier OS versions. It's why I change and restore it as quickly as possible rather than set it on a process-wide basis. It is possible that it could be changed by a thread during that short span, though.Convexoconcave
I'll try one more time. Consider that SetErrorMode(SEM_FAILCRITICALERRORS) can remove the other flags, if they were set. Yes you add them back a little later, but what if you need them all the time? Ideally you want to add SEM_FAILCRITICALERRORS to what was there already. Even then, it's such a dangerous api, process wide, that a single call at startup is the way to go.Humanitarian
@David: Sorry, but I disagree. A single call at startup changes the flags for your entire process for the duration of the process, and there's no need to do so here. A very quick change to the flags has little impact, and my code saves and restores the previous flags quickly. The only risk (which is only applicable if your app is multi-threaded, as LURD mentioned in a comment previously) is during that short duration of the single function call. Do you have a citation that indicates my code is incorrect? The API documentation doesn't.Convexoconcave
There are two risks. Threading, and for the code in between the two calls to SetErrorMode. blogs.msdn.com/b/oldnewthing/archive/2004/07/27/198410.aspxHumanitarian
@David: Your citation (from Raymond's blog) is a decade old, and things have changed. For instance, the blog post specifically states that there is no GetErrorMode function. I'm not sure it still applies, although it may. I've never experienced an issue with the code posted here. I'm opposed in principle to turning off critical errors at process startup.Convexoconcave
So use GetErrorMode to augment then. That makes code cleaner. But don't remove all the other flags the way you do. Your code removes SEM_NOOPENFILEERRORBOX. The only robust way to use SetErrorMode is once at the start of the process when there is only one thread running.Humanitarian
And nobody is talking about turning off critical errors. The point is that we control how those errors are handled. Either by the system with its dialog, or by the app through an api function failing.Humanitarian
And there's this msdn.microsoft.com/en-us/library/windows/desktop/… Best practice is that all applications call the process-wide SetErrorMode function with a parameter of SEM_FAILCRITICALERRORS at startup. This is to prevent error mode dialogs from hanging the application.Humanitarian
@David: Wow. A little obsessed? :-) I clearly meant "critical error dialogs". Thanks for that last link, though; I hadn't seen it. I'll modify my answer accordingly tomorrow morning, when I can test it - it's getting late here, and unlike you most of us need to sleep occasionally. :-)Convexoconcave
Not obsessed. But certainly insomniac.Humanitarian
@David: Didn't want to add to the reasons you couldn't sleep. Do the code changes work for you?Convexoconcave
Yes. Better. However I really do believe that these flags should be set once at startup. That they aren't is really a back compat thing. MSDN tells you to add them at process start. Anyway, we've expressed our opinions. Readers can form their own views. It's all good here.Humanitarian
FWIW GetLogicalDriveStrings takes the length in characters (rather than bytes). So this code is semantically wrong for Unicode Delphi. And it's easier to store the return value which is the length of the string.Humanitarian
@David: Just tested it in XE6, exactly as written here, and it works fine. What is semantically wrong? The buffer allocated is sufficient (128 characters), GetLogicalDriveStrings can never return more than 26 * 3 * SizeOf(Char), and the question regarding whether using the return value is easier or not is a matter of opinion.Convexoconcave
@Ken You are lying to the api about the size of the buffer you say it is twice as large as it is. 26 letters in alphabet means it doesn't cause an error. It's still wrong though. That cannot be denied. The documentation could not be more clear. Very common mistake. Best not lead novices astray by setting bad example. If you use D2007 habitually then you won't be so alive to this issue. Leave the answer as is. Or not. I honestly don't care what you do. Up to you. Pointless testing anything. It's all in the docs.Humanitarian
@David: Now you're just being silly. All the values of `A..Z, :, \` will always be a single byte, and Windows does not localize drive letters. Therefore, the max length of each assigned drive letter is 4 byets (drive letter + ':\' + null terminator. There are a max of 26 drive letters, which means the maximum buffer ever needed would be 26 * 4 bytes plus one for the final double terminator, or 105 bytes. As long as what is supplied is larger in size than that, it is perfectly safe, and allocating a fixed char array simplifies the code.Convexoconcave
For Unicode Delphi you are passing the size of the buffer measured in bytes, but the api wants the length in characters. For UTF16, one character is two bytes. Pass Length() rather than SizeOf(). I can edit the answer if that helps you understand.Humanitarian
@David: I've made the change from SizeOf to Length.Convexoconcave
H
6

The error dialog is a backwards compatibility issue. Older versions (much older) of Windows showed such dialogs. The designers realised that they were often undesirable. Applications need to be able to handle these conditions themselves.

But changing wholesale would have affected those apps that wanted to have the dialogs. So a mechanism was introduced to allow applications to control certain aspects of error handling.

You can suppress such error dialogs by calling SetErrorMode. This allows you to suppress the dialog and instead have the failing API call return an error.

Call the following function once at startup:

procedure SetProcessErrorMode;
var
  CurrentMode: DWORD;
begin
  CurrentMode := SetErrorMode(0);
  SetErrorMode(CurrentMode or SEM_FAILCRITICALERRORS
    or SEM_NOOPENFILEERRORBOX);
end;

This call should be made once at startup. The error mode is a process wide property and modifications after startup can lead to undesirable and unpredictable side effects. MSDN says:

Best practice is that all applications call the process-wide SetErrorMode function with a parameter of SEM_FAILCRITICALERRORS at startup. This is to prevent error mode dialogs from hanging the application.

I personally recommend adding SEM_NOOPENFILEERRORBOX too.

I've attempted here to address part of your question, but not all of it. I think that's reasonable when you ask multiple questions at once.

Humanitarian answered 18/10, 2014 at 20:38 Comment(1)
Thank you sir, this code is working. The code above is also obtained from you. You helped me out and over again. Much appreciate it.Thallus

© 2022 - 2024 — McMap. All rights reserved.