LB Booster
Programming >> Liberty BASIC language >> Windows disk volume name and serial number?
http://lbb.conforums.com/index.cgi?board=lblang&action=display&num=1499665631

Windows disk volume name and serial number?
Post by CirothUngol on Jul 10th, 2017, 05:47am

I've written a fully recursive version of the FILES statement and was looking to write a simple application to test and showcase it's use, so I've settled on a replacement for the windows console program TREE.EXE. So far, it works like a charm and is just like the original but with several more options. However, to completely reproduce the output of the original executable (so that it could be used as a literal replacement), I need a way to discover the name and serial number of the disk volume that lbbTree.exe is examining. Is there a simple way of retrieving this information?

I'll clean-up, comment, and post the code in the Showcase regardless, but it would be really nice if I could make the replacement completely indistinguishable from the original before doing so.
Re: Windows disk volume name and serial number?
Post by tsh73 on Jul 10th, 2017, 06:16am

I'm not versed in API so I didn't try that
But Google search shows
GetVolumeInformation
(also command VOL returns just that).
Re: Windows disk volume name and serial number?
Post by CirothUngol on Jul 11th, 2017, 02:25am

Thanks for the info. That page seems to be C++ functions for Windows API, but using that as a starting point I finally found similar functions in a list for kernel32.dll. Seems to be 3 of them, GetVolumeInformationA, W, and ByHandleW. None of them seem to work, but I probably need better documentation (plus this is the 1st time I've tried to use CALLDLL). Here's what I've tried, comments at the top are from the site you linked, but they seem perfectly in-line with several code samples from different languages that I've found online.
Here's my non-working sample Code:
'BOOL WINAPI GetVolumeInformation(
'  _In_opt_  LPCTSTR lpRootPathName,
'  _Out_opt_ LPTSTR  lpVolumeNameBuffer,
'  _In_      DWORD   nVolumeNameSize,
'  _Out_opt_ LPDWORD lpVolumeSerialNumber,
'  _Out_opt_ LPDWORD lpMaximumComponentLength,
'  _Out_opt_ LPDWORD lpFileSystemFlags,
'  _Out_opt_ LPTSTR  lpFileSystemNameBuffer,
'  _In_      DWORD   nFileSystemNameSize
');
RootPathName$ = StartupDir$; "\"
VolumeNameBuffer$ = SPACE$(_Max_Path); CHR$(0)
VolumeNameSize = _Max_Path + 1
FileSystemNameBuffer$ = SPACE$(_Max_Path); CHR$(0)
FileSystemNameSize = _Max_Path + 1

OPEN "kernel32.dll" for dll as #kernel
CALLDLL #kernel, "GetVolumeInformationW",_
        RootPathName$ as ptr,_
        VolumeNameBuffer$ as ptr,_
        VolumeNameSize as ulong,_
        VolumeSerialNumber as ulong,_
        MaximumComponentLength as ulong,_
        FileSystemFlags as ulong,_
        FileSystemNameBuffer$ as ptr,_
        FileSystemNameSize as ulong,_
        Result as boolean
CLOSE #kernel

PRINT RootPathName$
PRINT LEFT$(VolumeNameBuffer$, INSTR(VolumeNameBuffer$,CHR$(0))-1)
PRINT VolumeNameSize
PRINT VolumeSerialNumber
PRINT MaximumComponentLength
PRINT FileSystemFlags
PRINT LEFT$(FileSystemNameBuffer$, INSTR(FileSystemNameBuffer$,CHR$(0))-1)
PRINT FileSystemNameSize
PRINT Result
END 

This brings me to another question... is there a way to directly receive the output from a console program (stdErr and stdOut) without writing to a file first? If so, I could cheat and just use VOL. ^_^
Re: Windows disk volume name and serial number?
Post by Richard Russell on Jul 11th, 2017, 08:57am

on Jul 11th, 2017, 02:25am, CirothUngol wrote:
Seems to be 3 of them, GetVolumeInformationA, W, and ByHandleW. None of them seem to work, but I probably need better documentation

This program retrieves the (hexadecimal) volume serial number for the current drive:

Code:
    struct volumeserial, value as ulong
    calldll #kernel32, "GetVolumeInformationA", 0 as long, _
      0 as long, 0 as long, volumeserial as struct, 0 as long, _
      0 as long, 0 as long, 0 as long, ret as ulong

    print dechex$(volumeserial.value.struct) 

It could no doubt be very easily modified to retrieve the volume name as well, and/or to return the information for a drive other than the currently selected one. Just ask if you need help doing that.

Richard.

Re: Windows disk volume name and serial number?
Post by Rod on Jul 11th, 2017, 08:59am

Here is some working code for the API call. It does not get a volume name for me but that is probably because I have none set. The number it returns is a number created when the disc is formatted. There is a manufacturers serial number but I am not sure how to get that.

You don't need to open kernel32.dll it is already open, you need to use structs when it says LP, that implies a pointer, somewhere to store the result. And you need to use the A Ansi versions of the API calls.

Code:
RootPathName$ = "C:\"
VolumeNameBuffer$ = SPACE$(_MAX_PATH); CHR$(0)
VolumeNameSize = _MAX_PATH + 1
FileSystemNameBuffer$ = SPACE$(_MAX_PATH); CHR$(0)
FileSystemNameSize = _MAX_PATH + 1
struct VolumeSerialNumber,_
    sn as ulong
struct MaximumComponentLength,_
    mcl as ulong
struct FileSystemFlags,_
    fsf as ulong


CALLDLL #kernel32, "GetVolumeInformationA",_
        RootPathName$ as ptr,_
        VolumeNameBuffer$ as ptr,_
        VolumeNameSize as long,_
        VolumeSerialNumber as struct,_
        MaximumComponentLength as struct,_
        FileSystemFlags as ptr,_
        FileSystemNameBuffer$ as ptr,_
        FileSystemNameSize as ulong,_
        Result as long

print "Call result ";Result
PRINT "VolumeName ";VolumeNameBuffer$
PRINT "Serial Number ";VolumeSerialNumber.sn.struct
PRINT "Component length ";MaximumComponentLength.mcl.struct
PRINT "System Flags ";FileSystemFlags.fsf.struct
PRINT "File System Name ";FileSystemNameBuffer$


END
 

Re: Windows disk volume name and serial number?
Post by Richard Russell on Jul 11th, 2017, 10:49am

on Jul 11th, 2017, 08:59am, Rod wrote:
There is a manufacturers serial number but I am not sure how to get that.

It's tricky to get the 'drive serial number' using API calls (it needs COM) so the most practical way is to shell out:

Code:
    run "cmd.exe /c wmic path win32_physicalmedia get SerialNumber | more > %temp%\serial.txt", wait
    temp$ = space$(_MAX_PATH) + chr$(0)
    calldll #kernel32, "GetTempPathA", _MAX_PATH as long, temp$ as ptr, num as long
    open left$(temp$, num) + "\serial.txt" for input as #f
    a$ = input$(#f, lof(#f))
    close #f
    print "Drive serial number: "; word$(a$,2) 

This is LBB-specific as shown because it uses the WAIT qualifier to RUN.

Richard.

Re: Windows disk volume name and serial number?
Post by tsh73 on Jul 11th, 2017, 7:12pm

For me (Win XP Home SP3)
VOL command returns serial number in form BCD1-2F6D
Code from Reply #3 says "BCD12F6D"
and Reply #4 says
Quote:
Serial Number 3.16782577E9

though dechex$(.that.) gives "BCD12F6D" again.
As for code in Reply#5 obviously Win XP Home have no wmic.
(not that I need it anyway)
Re: Windows disk volume name and serial number?
Post by Richard Russell on Jul 11th, 2017, 9:14pm

on Jul 11th, 2017, 7:12pm, tsh73 wrote:
For me (Win XP Home SP3) VOL command returns serial number in form BCD1-2F6D

It is common to display the Volume Serial Number in that split hexadecimal format, but it is just a convention for 'human consumption'. The serial number is, after all, just a (32-bit) integer which you can choose to display in many different ways.

It is trivial to format it in the conventional fashion if desired. For example the program I listed can be amended as follows:

Code:
    struct volumeserial, value as ulong
    calldll #kernel32, "GetVolumeInformationA", 0 as long, _
      0 as long, 0 as long, volumeserial as struct, 0 as long, _
      0 as long, 0 as long, 0 as long, ret as ulong

    volhex$ = right$("0000000" + dechex$(volumeserial.value.struct), 8)
    print left$(volhex$,4); "-"; mid$(volhex$,5) 

A similar adaptation can of course be made to Rod's program.

Richard.

Re: Windows disk volume name and serial number?
Post by CirothUngol on Jul 11th, 2017, 11:33pm

Thanks for the help guys! CALLDLL and the WinAPI are still a little mysterious to me, lets see if I got this:
* numerical input can be variables or constants
* numerical output is always in the form of a STRUCT
* string input must be in the form of a variable with no null terminator
* string output must be in the form of a variable with a null terminator

Questions:
Is string output ever in the form of a STRUCT?
The docs identify the numerical values as DWORDs, which are supposed to be 32-bit unsigned integers which are equivalent to ulong, right? Yet when long is substituted in the CALLDLL it seems to work fine. Same thing with boolean. Actually, the code below creates the STRUCT vSerial.sn as a ulong, calls it as a long, and all seems well. I'm quite certain that those devilish details would matter at some point, but when might that be?

Anyhow, it's working great using the following Code:
''''''''''''''''''''''''''''''''''
' check for invalid drive in path$
FILES "C:\", x$()
path$ = LOWER$(LEFT$(x$(0,2),2))
DO
    X += 1
    D$ = WORD$(Drives$,X)
LOOP UNTIL (D$ = "") OR (D$ = path$)
IF D$ = "" THEN
    PRINT "Invalid drive specification"
    END
END IF
'''''''''''''''''''''''''''''''''''
' get volume name and serial number
path$ = UPPER$(x$(0,2))
vName$ = SPACE$(_MAX_PATH); CHR$(0)
vNameSize = _MAX_PATH + 1
STRUCT vSerial, sn AS ulong
CALLDLL #kernel32, "GetVolumeInformationA",_
    path$ as ptr,_
    vName$ as ptr,_
    vNameSize as long,_
    vSerial as struct,_
    0 as long, 0 as long, 0 as long, 0 as long,_
    result as long
IF result = 0 THEN
    PRINT "Invalid volume specification"
    END
END IF
vSerial$ = RIGHT$("0000000"; dechex$(vSerial.sn.struct),8)
PRINT "Folder PATH listing for volume "; LEFT$(vName$,INSTR(vName$,CHR$(0))-1)
PRINT "Volume serial number is "; LEFT$(vSerial$,4); "-"; RIGHT$(vSerial$,4) 

The last thing to deal with is relative path specification. FILES assumes DefaultDir$ (ie %~dp0; where the executable resides) and I need it to assume StartupDir$ (ie %cd%; the current or calling directory). I don't suppose anyone has already written a function to handle all of the relative ".\.." path nonsense?
If not, no biggie. Intermediary functions are my specialty. ^_^
Re: Windows disk volume name and serial number?
Post by Rod on Jul 12th, 2017, 06:47am

There is a lot to it, most of your assumptions are wrong. The clues are in the api docs. There are good tutorials to be found on the LBPE.
Re: Windows disk volume name and serial number?
Post by Richard Russell on Jul 12th, 2017, 08:39am

on Jul 11th, 2017, 11:33pm, CirothUngol wrote:
* numerical input can be variables or constants
* numerical output is always in the form of a STRUCT
* string input must be in the form of a variable with no null terminator
* string output must be in the form of a variable with a null terminator

That's pretty close, yes. There's not generally any harm in adding a NUL terminator to an 'input' string, but it is unnecessary (one is added automatically by LB/LBB). According to the LB docs it is essential to add a NUL to an 'output' string, but in practice that requirement was relaxed along the line. However I still prefer to do it because it's what the docs say.

Quote:
Is string output ever in the form of a STRUCT?

It can be, yes - it's your choice. Here's an example of exactly that:

Code:
    struct temp, path as char[260]
    calldll #kernel32, "GetTempPathA", _MAX_PATH as long, temp as struct, ret as long
    print temp.path.struct 

In some ways I think this is actually nicer than the more common form, using a NUL-terminated string. For example it eliminates the need to truncate the output using LEFT$().

Quote:
Yet when long is substituted in the CALLDLL it seems to work fine.

You should not be surprised. A long can contain any integer value between -2147483648 and +2147483647. A ulong can contain any integer value between 0 and +4294967295. Therefore if the value concerned is in the range 0 to +2147483647 it makes not the slightest difference whether you use a long or ulong. Very commonly values will be in this range.

Quote:
I'm quite certain that those devilish details would matter at some point, but when might that be?

When the value concerned is negative you should use a long and when it is greater than +2147483647 you should use a ulong. However, having said that, LBB will automatically substitute the correct parameter type in a CALLDLL statement if it notices that you have made a mistake (that may not necessarily be true of LB 4).

Of course if you are using long or ulong in a STRUCT, which doesn't necessarily have anything to do with CALLDLL or the Windows API (despite what you may read elsewhere, structs are really useful in all sorts of other circumstances) then it may well matter which you use.

Richard.

Re: Windows disk volume name and serial number?
Post by Richard Russell on Jul 13th, 2017, 09:43am

on Jul 12th, 2017, 08:39am, Richard Russell wrote:
In some ways I think this is actually nicer than the more common form

I'm rapidly convincing myself of this. Returning string values in STRUCTs is more consistent with the way numeric values are returned, it does not require the returned string to be truncated using LEFT$, and it's (arguably) easier to understand.

Here is Rod's program adapted accordingly:

Code:
    RootPathName$ = "C:\"
    struct VolumeNameBuffer, vnb as char[261]
    VolumeNameSize = len(VolumeNameBuffer.struct)
    struct FileSystemNameBuffer, fsnb as char[261]
    FileSystemNameSize = len(FileSystemNameBuffer.struct)
    struct VolumeSerialNumber, vsn as ulong
    struct MaximumComponentLength, mcl as ulong
    struct FileSystemFlags, fsf as ulong

    calldll #kernel32, "GetVolumeInformationA",_
        RootPathName$ as ptr,_
        VolumeNameBuffer as struct,_
        VolumeNameSize as long,_
        VolumeSerialNumber as struct,_
        MaximumComponentLength as struct,_
        FileSystemFlags as struct,_
        FileSystemNameBuffer as struct,_
        FileSystemNameSize as long,_
        Result as long

    print "Call result ";Result
    print "VolumeName ";VolumeNameBuffer.vnb.struct
    print "Serial Number ";dechex$(VolumeSerialNumber.vsn.struct)
    print "Component length ";MaximumComponentLength.mcl.struct
    print "System Flags ";dechex$(FileSystemFlags.fsf.struct)
    print "File System Name ";FileSystemNameBuffer.fsnb.struct

    end 

Richard.
Re: Windows disk volume name and serial number?
Post by CirothUngol on Jul 14th, 2017, 12:41am

Thanks again for the replies. I like the idea of using STRUCT for all output from API/DLLs, as it implies consistency and the code looks cleaner. I may never have known it to be an option had you not said, since char as a type doesn't seem to be mentioned anywhere in the LB 4.5 docs (I see that it's mentioned in LBB docs under STRUCT).

My only concern is that one seems unable to use a variable in the STRUCT definition for char members (eg. char[_MAX_PATH]), forcing the use of a constant instead (eg. char[260]). I receive a 'missing ] in struct' error unless I use a constant value. Is this avoidable? It's really the only reason I'm hesitant to use it instead of the more cumbersome string-buffer-with-added-null/left$-characters-before-the-null combo. Also, does the constant value include the null (259+null), or is the length actually 261 (260+null)?

...and as for the relative paths issue, I discovered that even though FILES gives no drive:\path info if none is provided (ie. if path$ is empty then x$(0,2) and x$(0,3) are empty), it will handle the relative stuff just fine if you provide it with a fully qualified path. So, I was able to correct the issue with the following 3 lines of Code:
' resolve relative paths
IF path$ = "" THEN path$ = StartupDir$
IF INSTR(path$,"\") =  1 THEN path$ = LEFT$(StartupDir$,2); path$
IF INSTR(path$,":") <> 2 THEN path$ = StartupDir$; "\"; path$ 
Then when you feed path$ into FILES all relativity is automatically resolved. Seems to work like a charm.
Re: Windows disk volume name and serial number?
Post by Richard Russell on Jul 14th, 2017, 1:30pm

on Jul 14th, 2017, 12:41am, CirothUngol wrote:
Is this avoidable? It's really the only reason I'm hesitant to use it

It's understandable that the buffer size cannot be a variable (it's declaring a static buffer rather than a dynamic object such as a string) but it would have been desirable for a Windows Constant to be accepted as well as a literal number. For whatever reason - you'd have to ask Carl why - only the latter is accepted in practice.

So it's not currently avoidable (LBB could in principle be adapted to accept a Windows Constant, as a language extension, but it won't at present). However I don't share your hesitancy; a constant is a constant so having to enter the numeric value rather than its symbolic form doesn't concern me other than from a readability standpoint.

If you want to avoid a mistake being made you could always do this:

Code:
    struct temp, path as char[260]
    if len(temp.struct) <> _MAX_PATH then print "Incorrect constant" : end 

Richard.