7

I need to get the volume serial number for a drive letter during an installation created with Inno Setup. I know that DLL functions can be imported into Inno, but I'm fairly new to it and having some problems getting it to work. I know that the GetVolumeInformation function in kernel32 can do what I need. Could someone show me how to import and use that functionality in an Inno script to retrieve the volume serial number?

Thanks!

TLama
  • 75,147
  • 17
  • 214
  • 392
user1208402
  • 117
  • 2
  • 10

2 Answers2

8

Inno-Setup code::

[Code]
function GetVolumeInformation(
  lpRootPathName: PChar;
  lpVolumeNameBuffer: PChar;
  nVolumeNameSize: DWORD;
  var lpVolumeSerialNumber: DWORD;
  var lpMaximumComponentLength: DWORD;
  var lpFileSystemFlags: DWORD;
  lpFileSystemNameBuffer: PChar;
  nFileSystemNameSize: DWORD
  ): BOOL;
  external 'GetVolumeInformationA@kernel32.dll stdcall';


function LoWord(dw: DWORD): WORD;
begin
  Result := WORD(dw);
end;

function HiWord(dw: DWORD): WORD;
begin
  Result := WORD((dw shr 16) and $FFFF);
end;

function WordToHex(w: WORD): string;
begin
  Result := Format('%.4x', [w]);
end;

function FindVolumeSerial(const Drive: string): string;
var
  FileSystemFlags: DWORD;
  VolumeSerialNumber: DWORD;
  MaximumComponentLength: DWORD;
begin
  Result := '';
  // Note on passing PChars using RemObjects Pascal Script:
  // '' pass a nil PChar  
  // #0 pass an empty PChar    
  if GetVolumeInformation(
    PChar(Drive), 
    '', // nil
    0,
    VolumeSerialNumber,
    MaximumComponentLength,
    FileSystemFlags,
    '', // nil
    0)
  then
    Result := WordToHex(HiWord(VolumeSerialNumber)) + '-' + WordToHex(LoWord(VolumeSerialNumber));
end;

function InitializeSetup(): Boolean;
begin
  MsgBox(FindVolumeSerial('c:\'), mbInformation, mb_Ok);
end;

Tested with Inno-setup version 5.2.3
In Unicode versions of Inno-Setup replace PChar with PAnsiChar

kobik
  • 21,001
  • 4
  • 61
  • 121
  • Thank you over and over again. Forgive me for being an idiot, but how do I interpret that? For example, my C drive is labeled as 449D-0C30 at the command prompt, but when I run your Inno code the msgbox shows 17565-3120. I know I'm on the right track, but I'm new to importing functions into Inno! Thank you so much for your help! – user1208402 Feb 16 '12 at 09:30
  • I've had several people lend solid advice. I'm new the stack, so how can I give them credit for their contributions? – user1208402 Feb 16 '12 at 09:42
4

Since the InnoSetup doesn't support pointers you will have to create the external library for the call of the GetVolumeInformation function. The following code samples should work for all combinations of the Delphi and InnoSetup (from the Unicode support point of view).

Here's the Delphi library code:

library VolumeInformation;

uses
  Types, Classes, SysUtils, Windows;

var
  SerialNumber: AnsiString;

function GetVolumeSerial(Drive: PAnsiChar): PAnsiChar; stdcall;
var
  FileSystemFlags: DWORD;
  VolumeSerialNumber: DWORD;
  MaximumComponentLength: DWORD;
begin
  SerialNumber := '';
  GetVolumeInformationA(Drive, nil, 0, @VolumeSerialNumber,
    MaximumComponentLength, FileSystemFlags, nil, 0);
  SerialNumber := IntToHex(HiWord(VolumeSerialNumber), 4) + ' - ' +
    IntToHex(LoWord(VolumeSerialNumber), 4);
  Result := PAnsiChar(SerialNumber);
end;

exports
  GetVolumeSerial;

end.

And here's the InnoSetup code:

[Files]
Source: "VolumeInformation.dll"; Flags: dontcopy

[Code]

function GetVolumeSerial(Drive: PAnsiChar): PAnsiChar;
  external 'GetVolumeSerial@files:VolumeInformation.dll stdcall setuponly';

procedure ButtonOnClick(Sender: TObject);
var
  S: string;
begin
  S := GetVolumeSerial('c:\');
  MsgBox(S, mbInformation, mb_Ok);
end;
TLama
  • 75,147
  • 17
  • 214
  • 392
  • I had tried things very similar to those, in fact I looked at that same delphi article trying to get it to work. But I keep getting errors. Using the code you provided, I get "Unknown type: PDWORD" in the function import. If I change that to something else it complies farther until I get "Unknown identifier VolumeSerialNumber" when it reaches @VolumeSerialNumber. I tried removing the '@' just to see what happens, and it compiles all the way to the call to GetVolumeInformation() and I receive a "type mismatch" error at the end of the function call. Any thoughts? – user1208402 Feb 15 '12 at 01:53
  • 1
    @TLama, You can pass variable by reference ("pointers"). you simply need to declare the parameters as `var lpVolumeSerialNumber: DWORD` etc. NO need to specify `@` in the caller. Another issue is passing `nil` to the function. this can be done by crating a `PChar` variable such as `_NIL` and assigning `#0` to it. `HiWord` and `LoWord` are simple macros. I got stuck with `IntToHex` :) but I was able to implement your initial code inside Inno and get the serial number. BUT, I think your final solution to keep helper functions in a separate DLL is much better! +1 – kobik Feb 15 '12 at 21:17
  • Seems like newer versions of Inno supports `IntToHex`, but it's also easy to implement :) – kobik Feb 15 '12 at 21:29
  • @kobik, good point about `lpVolumeSerialNumber` declaration. About `IntToHex` I'm not sure (even RemObject's reference doesn't say anything about it). However I think OP is doing some registration process because I don't know any sensible reason why to get the volume serial number, so I think the library might be useful also for the other purposes. But post what you have, maybe your solution will fit better to OP ;) – TLama Feb 15 '12 at 21:40
  • @TLama Thanks for the input, and no apologies necessary! I always appreciate everyone's help, even if it isn't 100% correct. At least we can all learn something that way! I'm still somewhat confused about importing functions into Inno based on your latest example. Do I have to compile a DLL to store external functions for Inno? Or a better questions is how CAN I do that? It sounds like a good idea for future reference, but I was hoping to simply use the native Windows DLL for function importing. Thanks a million for you help! – user1208402 Feb 16 '12 at 06:37
  • @kobik Many thanks to you for your input as well! You are correct in assuming that I am trying to do some registration. I need to get the volume serial for a specified partition and compare it to a downloaded hash value (which will be downloaded using Inno) that was created during an online registration. Any chance that you could post an example of how to get the volume serial using only kernel.dll? Thanks to everyone for the help so far! – user1208402 Feb 16 '12 at 06:44
  • I didn't get the `GetVolumeInformation` to work from InnoSetup (@kobik, it's your turn :) user1208402, to your question about external custom functions; yes if you want custom functions, you have to create a DLL (in my example I've used Delphi for it, but you can use any programming framework which can do that). And you don't need to worry, those DLLs will only be part of the setup - if you specify in the `[Files]` section for your DLL `Flags: dontcopy` and at the function import `setuponly` flag then they won't be copied anywhere and will remain hidden to user. – TLama Feb 16 '12 at 08:42
  • @TLama Don't feel bad. You have given me tons of information. If nothing else, I've decided to learn Delphi for my own benefit. It seems like a straightforward language that I need to, and should've learned before! Thank you a million times over! With that said, I really need to learn how to get that volume information! Thank you to everyone that has contributed so far! – user1208402 Feb 16 '12 at 09:13