Delphi pass string parameter to IDispatch.Invoke - delphi

Using Delphi 11.1, I want to add scripting to my application using IActiveScript. I created a small VBScript to test parameter passing from Delphi to the Script:
function test2(n)
MsgBox n
test2 = n
end function
VBScript code loads OK, passing an integer as parameter works OK, but when passing a string parameter, I found only the first half of the string makes it to the script. Delphi code:
procedure TForm1.Button4Click(Sender: TObject);
var
Disp: IDispatch;
Res: OleVariant;
DispParams: TDispParams;
s: string;
n: Integer;
v: Variant;
Arg: array of TVariantArg;
begin
OleCheck(FScript.GetScriptDispatch(nil, Disp));
s := 'test2';
OleCheck(Disp.GetIDsOfNames(GUID_NULL, #s, 1, 1033, #n));
v := VarArrayOf(['1234567890']);
SetLength(Arg, VarArrayHighBound(v, 1) - VarArrayLowBound(v, 1) + 1);
arg[0].vt := VT_BSTR;
arg[0].bstrVal := PWideChar(VarToStr(v[0])); //passes first half of string only
// arg[0].bstrVal := SysAllocString(PWideChar(VarToStr(v[0]))); //passes complete (copy of) string
end;
DispParams.rgvarg := #Arg[0];
DispParams.rgdispidNamedArgs := nil;
DispParams.cArgs := 1;
DispParams.cNamedArgs := 0;
//at this point, debugger shows no difference, bstrVal holds full string
OleCheck(Disp.Invoke(n, GUID_NULL, 1033, DISPATCH_METHOD, DispParams, #res, nil, nil));
end;
MsgBox shows 12345. Tried other strings, other string lengths, too, always first half only.
Anyone who can shine a light on this?

When interfacing with COM, you need to use WideString (a wrapper for a COM BSTR string) instead of using string (aka UnicodeString), eg:
procedure TForm1.Button4Click(Sender: TObject);
var
Disp: IDispatch;
Res: OleVariant;
DispParams: TDispParams;
s: WideString;
n: Integer;
v: Variant;
Arg: array of TVariantArg;
begin
OleCheck(FScript.GetScriptDispatch(nil, Disp));
s := 'test2';
OleCheck(Disp.GetIDsOfNames(GUID_NULL, #s, 1, 1033, #n));
v := VarArrayOf(['1234567890']);
SetLength(Arg, VarArrayHighBound(v, 1) - VarArrayLowBound(v, 1) + 1);
s := VarToWideStr(v[0]);
arg[0].vt := VT_BSTR;
arg[0].bstrVal := PWideChar(s);
//arg[0].bstrVal := SysAllocString(PWideChar(s));
DispParams.rgvarg := #Arg[0];
DispParams.rgdispidNamedArgs := nil;
DispParams.cArgs := 1;
DispParams.cNamedArgs := 0;
OleCheck(Disp.Invoke(n, GUID_NULL, 1033, DISPATCH_METHOD, DispParams, #res, nil, nil));
//SysFreeString(arg[0].bstrVal);
end;

Related

Delphi pass multiple params to IActiveScript IDispatch.Invoke

Using Delphi 11.1, I want to add scripting to my application using IActiveScript. I created a small VBScript to test passing multiple parameters from Delphi to the Script:
Function TestParams(a, b, c)
TestParams = c
End Function
VB script load OK, but I have trouble passing multiple params. Delphi code:
procedure TForm1.Button14Click(Sender: TObject);
var
v: OleVariant;
Disp: IDispatch;
Arg: TArray<TVariantArg>;
Res: OleVariant;
DispParams: TDispParams;
i,n: Integer;
s: string;
begin
v := VarArrayOf(['Wrong...', 'huh', 'OK!']);
s := 'TestParams';
Memo2.Lines.Text := VarToStr(MyScriptingHost1.Run('TestParams', v));
exit;
OleCheck(MyScriptingHost1.FScript.GetScriptDispatch(nil, Disp));
OleCheck(Disp.GetIDsOfNames(GUID_NULL, #s, 1, 1033, #n));
setlength(arg, 3);
for i := 0 to High(Arg) do
begin
n := High(Arg) - i;
Arg[n].vt := VarType(v[i]);
Arg[n].bstrVal := PWideChar(VarToWideStr(v[i]));
end;
//At this point, my Delphi 11.1 assignes the same value to Arg[]0, Arg[1], arg[2]
//this works
//Arg[0].vt := VT_BSTR;
//Arg[0].bstrVal := 'test3';
//
//Arg[1].vt := VT_BSTR;
//Arg[1].bstrVal := 'test2';
//
//Arg[2].vt := VT_BSTR;
//Arg[2].bstrVal := 'test1';
DispParams.rgvarg := #Arg[0]; //#Arg gives error
DispParams.rgdispidNamedArgs := nil;
DispParams.cArgs := High(Arg) + 1;
DispParams.cNamedArgs := 0;
//passing pointer to DispParams gives errors
OleCheck(Disp.Invoke(n, GUID_NULL, 1033, DISPATCH_METHOD, DispParams, #res, nil, nil));
end;
For some reason, multiple params gives different results for 32/64 bits, and using the code above, All params get usually the same value. Very strange.
Even more strange, running this several times gives sometimes different results.
The above code works without problems in case of only 1 param.
Anyone who knows what is wrong here?
As I explained in reply to your earlier question, you MUST use WideString when interfacing with COM, not string (ie, when calling Disp.GetIDsOfNames()).
Also, your use of VarToWideStr() is producing temporary WideStrings that are no longer valid by the time you pass the Arg array to Disp.Invoke(), so store the WideStrings in another array to keep them in scope until Invoke() exits.
Try this:
procedure TForm1.Button14Click(Sender: TObject);
var
v: OleVariant;
Disp: IDispatch;
Arg: TArray<TVariantArg>;
ArgStrs: TArray<WideString>;
Res: OleVariant;
DispParams: TDispParams;
i, n: Integer;
s: WideString;
begin
v := VarArrayOf(['Wrong...', 'huh', 'OK!']);
s := 'TestParams';
//Memo2.Lines.Text := VarToStr(MyScriptingHost1.Run('TestParams', v));
OleCheck(MyScriptingHost1.FScript.GetScriptDispatch(nil, Disp));
OleCheck(Disp.GetIDsOfNames(GUID_NULL, #s, 1, 1033, #n));
SetLength(Arg, 3);
SetLength(ArgStrs, 3);
for i := 0 to High(Arg) do
begin
ArgStrs[i] := VarToWideStr(v[i]);
n := High(Arg) - i;
Arg[n].vt := VT_BSTR;
Arg[n].bstrVal := PWideChar(ArgStrs[i]);
end;
DispParams.rgvarg := #Arg[0]; //#Arg gives error
DispParams.rgdispidNamedArgs := nil;
DispParams.cArgs := Length(Arg);
DispParams.cNamedArgs := 0;
OleCheck(Disp.Invoke(n, GUID_NULL, 1033, DISPATCH_METHOD, DispParams, #res, nil, nil));
end;

Delphi (2006): how to Split by new line and break at the same time

I have this simple operation in Java, where the string is split by new line and break.
String i= "Holidays
Great.
Bye";
String []linesArray = i.split("\\r?\\n");
I would like to obtain the same result in Delphi 2006.
Is it valid to use the following steps?
charArray[0] := '\\r';
charArray[1] := '\\n';
strArray := strA.Split(charArray);
I interpret your request like this: "Split a string at both CR and LF." which implies that CR+LF gives an empty string element. For instance, 'alpha'#13'beta'#10'gamma'#13#10'delta' yields the five elements 'alpha', 'beta', 'gamma', '', and 'delta'.
If so, and if you are using a non-ancient version of Delphi, this is really simple:
var S := 'alpha'#13'beta'#10'gamma'#13#10'delta';
var Parts := S.Split([#13, #10]);
for var Part in Parts do
ShowMessage(Part);
For old Delphi versions
The code above requires TStringHelper (crucially) and also makes use of inline variable declarations, for in loops, and generics.
For old Delphi versions, you can do it manually:
type
TStringArray = array of string;
function Split(const S: string): TStringArray;
var
Count: Integer;
const
Delta = 512;
procedure Add(const Part: string);
begin
if Length(Result) = Count then
SetLength(Result, Length(Result) + Delta);
Result[Count] := Part;
Inc(Count);
end;
var
p, i: Integer;
begin
Result := nil;
Count := 0;
p := 0; // previous delim
for i := 1 to Length(S) do
if S[i] in [#13, #10] then
begin
Add(Copy(S, Succ(p), i - p - 1));
p := i;
end;
Add(Copy(S, Succ(p)));
SetLength(Result, Count);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
S: string;
Parts: TStringArray;
i: Integer;
begin
S := 'alpha'#13'beta'#10'gamma'#13#10'delta';
Parts := Split(S);
for i := 0 to High(Parts) do
ShowMessage(Parts[i]);
end;

How to get the string representation of a ShortCut Key including the SHIFTSTATE?

In a Delphi 10.4.2 Win32 VCL Application, and based on the question + solution here which provides a way to get the string representation of a Shortcut Key (but presumably with no possibility to also pass a SHIFTSTATE for the Shortcut Key) I wrote this code:
function MyGetSpecialShortcutName(ShortCut: TShortCut): string;
// gets shortcut name for e.g. VK_NUMPAD0 where TMenuItem.Shortcut gets the wrong shortcut name
var
ScanCode: Integer;
KeyName: array[0..255] of Char;
begin
Result := '';
FillChar(KeyName, SizeOf(KeyName), 0);
ScanCode := Winapi.Windows.MapVirtualKey(LoByte(Word(ShortCut)), 0) shl 16;
if ScanCode <> 0 then
begin
if Winapi.Windows.GetKeyNameText(ScanCode, KeyName, Length(KeyName)) <> 0 then
Result := KeyName;
end;
end;
function GetSpecialShortcutNameWithShiftState(const AScanCode: Word; const AShiftState: System.Classes.TShiftState = []): string;
begin
Result := MyGetSpecialShortcutName(Vcl.Menus.ShortCut(AScanCode, AShiftState));
end;
Usage:
Result := GetSpecialShortcutNameWithShiftState(VK_A, [ssCTRL]);
However, the Result is "A" where the expected Result should be "CTRL+A".
How to get the string representation of a ShortCut Key including the SHIFTSTATE?
The OP wants the key names fully localised, but for completeness I first show that the VCL already has a function to obtain a partly unlocalised string, namely, ShortCutToText in the Menus unit:
ShortCutToText(ShortCut(Ord('A'), [ssShift, ssAlt]))
This returns Shift+Alt+A on all systems.
Now, using the Win32 function GetKeyNameText already mentioned in the Q, it is easy to obtain a fully localised shortcut string:
function GetKeyName(AKey: Integer): string;
var
name: array[0..128] of Char;
begin
FillChar(name, SizeOf(name), 0);
GetKeyNameText(MapVirtualKey(AKey, 0) shl 16, #name[0], Length(name));
Result := name;
end;
function ModifierVirtualKey(AModifier: Integer): Integer;
begin
case AModifier of
Ord(ssShift):
Result := VK_SHIFT;
Ord(ssCtrl):
Result := VK_CONTROL;
Ord(ssAlt):
Result := VK_MENU;
else
Result := 0;
end;
end;
function ShortcutToString(AKey: Integer; AShiftState: TShiftState = []): string;
begin
Result := '';
for var Modifier in AShiftState do
begin
var ModifierKey := ModifierVirtualKey(Ord(Modifier));
if ModifierKey <> 0 then
Result := Result + IfThen(not Result.IsEmpty, '+') + GetKeyName(ModifierKey);
end;
Result := Result + IfThen(not Result.IsEmpty, '+') + GetKeyName(AKey);
end;
(Here I use a IfThen overload from StrUtils.)
Now,
ShortcutToString(Ord('A'), [ssShift, ssAlt])
returns SKIFT+ALT+A on my Swedish system. SKIFT is, as you might already have guessed, the Swedish name for the SHIFT key.

Suppress Userproperty printing with outlook mail items

I need to suppress the printing of outlook userproperties programmatically added to a mail item. I had seen the following question that has a solution for dot.net here Suppressing Outlook Field Printing but i'm having trouble translating the code to delphi. My main problem is the invokemember line i'm guessing i need to use userproperty.invoke somehow in delphi but i'm clueless on how i should use the parameters that the invoke methode requires. Can someone help me translate the solution from that question to delphi code ?
Thanks with the help of the people from addin-express i have a working solution... that seems to work for outlook 2016 still have to test other outlook versions. The problem was that i did not know what parameters to use for the invoke function.
I'm posting my function here
function TAddInModule.RemoveUserPropertyPrintFlag(
var aUserProperty: UserProperty): Boolean;
const
propID: integer = 107;
removePrinterFlag: integer = $4;
var
res: OleVariant;
disp : TDispParams;
flags: Integer;
dispIDs: array[0..0] of TDispID;
args: array [0..0] of TVariantArg;
begin
Result := False;
disp.cNamedArgs:= 0;
disp.cArgs:= 0;
if aUserProperty.Invoke(propID, GUID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, disp, #res, nil, nil) = S_OK then
begin
if TVarData(res).VType = varInteger then
begin
flags := TVarData(res).VInteger;
args[0].vt := VT_INT;
args[0].intVal := flags and (not removePrinterFlag);
disp.cArgs := 1;
disp.cNamedArgs := 1;
dispIDs[0]:= DISPID_PROPERTYPUT;
disp.rgdispidNamedArgs := #dispIDs;
disp.rgvarg := #args;
Result:= aUserProperty.Invoke(propID, GUID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, disp, nil, nil, nil) = S_OK;
end;
end;
end;
The translated code to delphi from the answer of the other stackoverflow should be something like this (not tested):
function TAddInModule.SuppressUserPropertyPrinting(mailItem: _MailItem) : HResult;
const
propID: integer = 107;
removePrinterFlag: integer = $4;
var
props: UserProperties;
prop: UserProperty;
i: integer;
res: OleVariant;
disp : TDispParams;
flags: Integer;
dispIDs: array[0..0] of TDispID;
args: array [0..0] of TVariantArg;
begin
props := mailItem.UserProperties;
if props.Count > 0 then begin
for i := 1 to props.Count do begin
prop := props.Item(i);
disp.cNamedArgs:= 0;
disp.cArgs:= 0;
Result:= prop.Invoke(propID, GUID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, disp, #res, nil, nil);
if TVarData(res).VType = varInteger then begin
flags := TVarData(res).VInteger;
args[0].vt := VT_INT;
args[0].intVal := flags and (not removePrinterFlag);
disp.cArgs := 1;
disp.cNamedArgs := 1;
dispIDs[0]:= DISPID_PROPERTYPUT;
disp.rgdispidNamedArgs := #dispIDs;
disp.rgvarg := #args;
Result:= prop.Invoke(propID, GUID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, disp, nil, nil, nil);
end;
prop := nil;
end;
end;
props := nil;
end;
You will need to use IDispatch.Invoke() in Delphi. Disp id is 107 and the value must be a variant of type varInteger and the value of 4. There are quite a few examples of calling IDispatch.Invoke in the VCL source code.
If using Redemption (I am its author) is an option, it explicitly exposes the RDOUserProperty.Printable property.

in Delphi7, How can I retrieve hard disk unique serial number?

Hi
I want to retrieve HDD unique (hardware) serial number.
I use some functions but in Windows Seven or Vista they don't work correctly because of admin right.
Is it possible retrieve it without run as Administrator?
Following the links in the question comments Sertac posted, I came across this interesting C++ question, where Fredou answered with a nice link to a codeproject example showing how to do this in .NET, which in turn was based on a link to Borland C++ code and article.
The cool thing is that this C++ code works as a non-administrator user too!
Now you need someone to help you translate this C++ code to Delphi.
Edit: Found a Delphi unit that does this for you.
I wrote some sample use for it:
program DiskDriveSerialConsoleProject;
{$APPTYPE CONSOLE}
uses
Windows,
SysUtils,
hddinfo in 'hddinfo.pas';
const
// Max number of drives assuming primary/secondary, master/slave topology
MAX_IDE_DRIVES = 16;
procedure ReadPhysicalDriveInNTWithZeroRights ();
var
DriveNumber: Byte;
HDDInfo: THDDInfo;
begin
HDDInfo := THDDInfo.Create();
try
for DriveNumber := 0 to MAX_IDE_DRIVES - 1 do
try
HDDInfo.DriveNumber := DriveNumber;
if HDDInfo.IsInfoAvailable then
begin
Writeln('VendorId: ', HDDInfo.VendorId);
Writeln('ProductId: ', HDDInfo.ProductId);
Writeln('ProductRevision: ', HDDInfo.ProductRevision);
Writeln('SerialNumber: ', HDDInfo.SerialNumber);
Writeln('SerialNumberInt: ', HDDInfo.SerialNumberInt);
Writeln('SerialNumberText: ', HDDInfo.SerialNumberText);
end;
except
on E: Exception do
Writeln(Format('DriveNumber %d, %s: %s', [DriveNumber, E.ClassName, E.Message]));
end;
finally
HDDInfo.Free;
end;
end;
begin
ReadPhysicalDriveInNTWithZeroRights;
Write('Press <Enter>');
Readln;
end.
Unit from http://www.delphipraxis.net/564756-post28.html
// http://www.delphipraxis.net/564756-post28.html
unit hddinfo;
interface
uses Windows, SysUtils, Classes;
const
IOCTL_STORAGE_QUERY_PROPERTY = $2D1400;
type
THDDInfo = class (TObject)
private
FDriveNumber: Byte;
FFileHandle: Cardinal;
FInfoAvailable: Boolean;
FProductRevision: string;
FProductId: string;
FSerialNumber: string;
FVendorId: string;
procedure ReadInfo;
procedure SetDriveNumber(const Value: Byte);
public
constructor Create;
property DriveNumber: Byte read FDriveNumber write SetDriveNumber;
property VendorId: string read FVendorId;
property ProductId: string read FProductId;
property ProductRevision: string read FProductRevision;
property SerialNumber: string read FSerialNumber;
function SerialNumberInt: Cardinal;
function SerialNumberText: string;
function IsInfoAvailable: Boolean;
end;
implementation
type
STORAGE_PROPERTY_QUERY = packed record
PropertyId: DWORD;
QueryType: DWORD;
AdditionalParameters: array[0..3] of Byte;
end;
STORAGE_DEVICE_DESCRIPTOR = packed record
Version: ULONG;
Size: ULONG;
DeviceType: Byte;
DeviceTypeModifier: Byte;
RemovableMedia: Boolean;
CommandQueueing: Boolean;
VendorIdOffset: ULONG;
ProductIdOffset: ULONG;
ProductRevisionOffset: ULONG;
SerialNumberOffset: ULONG;
STORAGE_BUS_TYPE: DWORD;
RawPropertiesLength: ULONG;
RawDeviceProperties: array[0..511] of Byte;
end;
function ByteToChar(const B: Byte): Char;
begin
Result := Chr(B + $30)
end;
function SerialNumberToCardinal (SerNum: String): Cardinal;
begin
HexToBin(PChar(SerNum), PChar(#Result), SizeOf(Cardinal));
end;
function SerialNumberToString(SerNum: String): String;
var
I, StrLen: Integer;
Pair: string;
B: Byte;
Ch: Char absolute B;
begin
Result := '';
StrLen := Length(SerNum);
if Odd(StrLen) then Exit;
I := 1;
while I < StrLen do
begin
Pair := Copy (SerNum, I, 2);
HexToBin(PChar(Pair), PChar(#B), 1);
Result := Result + Chr(B);
Inc(I, 2);
end;
I := 1;
while I < Length(Result) do
begin
Ch := Result[I];
Result[I] := Result[I + 1];
Result[I + 1] := Ch;
Inc(I, 2);
end;
end;
constructor THddInfo.Create;
begin
inherited;
SetDriveNumber(0);
end;
function THDDInfo.IsInfoAvailable: Boolean;
begin
Result := FInfoAvailable
end;
procedure THDDInfo.ReadInfo;
type
PCharArray = ^TCharArray;
TCharArray = array[0..32767] of Char;
var
Returned: Cardinal;
Status: LongBool;
PropQuery: STORAGE_PROPERTY_QUERY;
DeviceDescriptor: STORAGE_DEVICE_DESCRIPTOR;
PCh: PChar;
begin
FInfoAvailable := False;
FProductRevision := '';
FProductId := '';
FSerialNumber := '';
FVendorId := '';
try
FFileHandle := CreateFile(
PChar('\\.\PhysicalDrive' + ByteToChar(FDriveNumber)),
0,
FILE_SHARE_READ or FILE_SHARE_WRITE,
nil,
OPEN_EXISTING,
0,
0
);
if FFileHandle = INVALID_HANDLE_VALUE then
RaiseLastOSError;
ZeroMemory(#PropQuery, SizeOf(PropQuery));
ZeroMemory(#DeviceDescriptor, SizeOf(DeviceDescriptor));
DeviceDescriptor.Size := SizeOf(DeviceDescriptor);
Status := DeviceIoControl(
FFileHandle,
IOCTL_STORAGE_QUERY_PROPERTY,
#PropQuery,
SizeOf(PropQuery),
#DeviceDescriptor,
DeviceDescriptor.Size,
Returned,
nil
);
if not Status then
RaiseLastOSError;
if DeviceDescriptor.VendorIdOffset <> 0 then
begin
PCh := #PCharArray(#DeviceDescriptor)^[DeviceDescriptor.VendorIdOffset];
FVendorId := PCh;
end;
if DeviceDescriptor.ProductIdOffset <> 0 then
begin
PCh := #PCharArray(#DeviceDescriptor)^[DeviceDescriptor.ProductIdOffset];
FProductId := PCh;
end;
if DeviceDescriptor.ProductRevisionOffset <> 0 then
begin
PCh := #PCharArray(#DeviceDescriptor)^[DeviceDescriptor.ProductRevisionOffset];
FProductRevision := PCh;
end;
if DeviceDescriptor.SerialNumberOffset <> 0 then
begin
PCh := #PCharArray(#DeviceDescriptor)^[DeviceDescriptor.SerialNumberOffset];
FSerialNumber := PCh;
end;
FInfoAvailable := True;
finally
if FFileHandle <> INVALID_HANDLE_VALUE then
CloseHandle(FFileHandle);
end;
end;
function THDDInfo.SerialNumberInt: Cardinal;
begin
Result := 0;
if ((IsInfoAvailable = True) and (FSerialNumber <> '')) then Result := SerialNumberToCardinal(FSerialNumber)
end;
function THDDInfo.SerialNumberText: string;
begin
Result := '';
if ((IsInfoAvailable = True) and (FSerialNumber <> '')) then Result := SerialNumberToString(FSerialNumber)
end;
procedure THDDInfo.SetDriveNumber(const Value: Byte);
begin
FDriveNumber := Value;
ReadInfo;
end;
end.
Edit: RAID configurations require special provisions.
For instance, I got a RAID system with multiple RAID 5 array; only the first one displays, and it does not show the drive serial numbers, but the serial number of the RAID array:
VendorId: AMCC
ProductId: 9550SXU-16ML
ProductRevision: 3.08
SerialNumber: 006508296D6A2A00DE82
SerialNumberInt: 688416000
--jeroen
You can use the WMI (Windows Management Instrumentation) to get information related to windows hardware.
Exist two wmi classes wich exposes a property called SerialNumber which store the Number allocated by the manufacturer to identify the physical media. these classes are Win32_DiskDrive and Win32_PhysicalMedia.to access the SerialNumber property of these classes you must know the DeviceId of the Disk which is something like this \\.\PHYSICALDRIVE0. Another way is use a association class which link the Physical drive with the logical drive (C,D,E)
so you must find this link previous to obtain the serial number. the sequence to find this association is like this.
Win32_DiskPartition -> Win32_LogicalDiskToPartition -> Win32_DiskDrive
Note 1 : The SerialNumber property for the Win32_DiskDrive class does not exist in Windows Server 2003, Windows XP, Windows 2000, and Windows NT 4.0, so how you are talking about use Windows Vista or Windows 7, will work ok for you.
Note 2 : The code does not require a administrator account to run.
check this code
{$APPTYPE CONSOLE}
uses
SysUtils,
ActiveX,
ComObj,
Variants;
function GetDiskSerial(const Drive:AnsiChar):string;
var
FSWbemLocator : OLEVariant;
objWMIService : OLEVariant;
colDiskDrives : OLEVariant;
colLogicalDisks: OLEVariant;
colPartitions : OLEVariant;
objDiskDrive : OLEVariant;
objPartition : OLEVariant;
objLogicalDisk : OLEVariant;
oEnumDiskDrive : IEnumvariant;
oEnumPartition : IEnumvariant;
oEnumLogical : IEnumvariant;
iValue : LongWord;
DeviceID : string;
begin;
Result:='';
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
objWMIService := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', ''); //Connect to the WMI
colDiskDrives := objWMIService.ExecQuery('SELECT * FROM Win32_DiskDrive','WQL',0);
oEnumDiskDrive:= IUnknown(colDiskDrives._NewEnum) as IEnumVariant;
while oEnumDiskDrive.Next(1, objDiskDrive, iValue) = 0 do
begin
DeviceID := StringReplace(objDiskDrive.DeviceID,'\','\\',[rfReplaceAll]); //Escape the `\` chars in the DeviceID value because the '\' is a reserved character in WMI.
colPartitions := objWMIService.ExecQuery(Format('ASSOCIATORS OF {Win32_DiskDrive.DeviceID="%s"} WHERE AssocClass = Win32_DiskDriveToDiskPartition',[DeviceID]));//link the Win32_DiskDrive class with the Win32_DiskDriveToDiskPartition class
oEnumPartition := IUnknown(colPartitions._NewEnum) as IEnumVariant;
while oEnumPartition.Next(1, objPartition, iValue) = 0 do
begin
colLogicalDisks := objWMIService.ExecQuery('ASSOCIATORS OF {Win32_DiskPartition.DeviceID="'+objPartition.DeviceID+'"} WHERE AssocClass = Win32_LogicalDiskToPartition'); //link the Win32_DiskPartition class with theWin32_LogicalDiskToPartition class.
oEnumLogical := IUnknown(colLogicalDisks._NewEnum) as IEnumVariant;
while oEnumLogical.Next(1, objLogicalDisk, iValue) = 0 do
begin
if objLogicalDisk.DeviceID=(Drive+':') then //compare the device id
begin
Result:=objDiskDrive.SerialNumber;
Exit;
end;
objLogicalDisk:=Unassigned;
end;
objPartition:=Unassigned;
end;
end;
end;
begin
try
CoInitialize(nil);
try
Writeln(GetDiskSerial('C'));
Readln;
finally
CoUninitialize;
end;
except
on E:Exception do
begin
Writeln(E.Classname, ':', E.Message);
Readln;
end;
end;
end.
Here is another DiskId32 translation from C++ to Delphi by Victor Derevyanko
project:
http://code.google.com/p/dvsrc/
Because the first method (WithZeroRights) doesn't work for me, I wrote another for ReadIdeDriveAsScsiDriveInNT method:
unit HDScsiInfo;
interface
uses
Windows, SysUtils;
const
IDENTIFY_BUFFER_SIZE = 512;
FILE_DEVICE_SCSI = $0000001b;
IOCTL_SCSI_MINIPORT_IDENTIFY = ((FILE_DEVICE_SCSI shl 16) + $0501);
IDE_ATA_IDENTIFY = $EC; // Returns ID sector for ATA.
IOCTL_SCSI_MINIPORT = $0004D008; // see NTDDSCSI.H for definition
type
TDiskData = array [0..256-1] of DWORD;
TDriveInfo = record
ControllerType: Integer; //0 - primary, 1 - secondary, 2 - Tertiary, 3 - Quaternary
DriveMS: Integer; //0 - master, 1 - slave
DriveModelNumber: String;
DriveSerialNumber: String;
DriveControllerRevisionNumber: String;
ControllerBufferSizeOnDrive: Int64;
DriveType: String; //fixed or removable or unknown
DriveSizeBytes: Int64;
end;
THDScsiInfo = class (TObject)
private
FDriveNumber: Byte;
FFileHandle: Cardinal;
FInfoAvailable: Boolean;
FProductRevision: string;
FSerialNumber: string;
FControllerType: Integer;
FDriveMS: Integer;
FDriveModelNumber: string;
FControllerBufferSizeOnDrive: Int64;
FDriveType: string;
FDriveSizeBytes: Int64;
procedure ReadInfo;
procedure SetDriveNumber(const Value: Byte);
procedure PrintIdeInfo(DiskData: TDiskData);
public
constructor Create;
property DriveNumber: Byte read FDriveNumber write SetDriveNumber;
property ProductRevision: string read FProductRevision;
property SerialNumber: string read FSerialNumber;
property ControllerType: Integer read FControllerType;
property DriveMS: Integer read FDriveMS;
property DriveModelNumber: string read FDriveModelNumber;
property ControllerBufferSizeOnDrive: Int64 read FControllerBufferSizeOnDrive;
property DriveType: string read FDriveType;
property DriveSizeBytes: Int64 read FDriveSizeBytes;
function IsInfoAvailable: Boolean;
end;
implementation
type
SRB_IO_CONTROL = record
HeaderLength: Cardinal;
Signature: array [0..8-1] of Byte;
Timeout: Cardinal;
ControlCode: Cardinal;
ReturnCode: Cardinal;
Length: Cardinal;
end;
PSRB_IO_CONTROL = ^SRB_IO_CONTROL;
DRIVERSTATUS = record
bDriverError: Byte;// Error code from driver, or 0 if no error.
bIDEStatus: Byte;// Contents of IDE Error register.
// Only valid when bDriverError is SMART_IDE_ERROR.
bReserved: array [0..1] of Byte;// Reserved for future expansion.
dwReserved: array [0..1] of Longword;// Reserved for future expansion.
end;
SENDCMDOUTPARAMS = record
cBufferSize: Longword;// Size of bBuffer in bytes
DriverStatus: DRIVERSTATUS;// Driver status structure.
bBuffer: array [0..0] of Byte;// Buffer of arbitrary length in which to store the data read from the // drive.
end;
IDEREGS = record
bFeaturesReg: Byte;// Used for specifying SMART "commands".
bSectorCountReg: Byte;// IDE sector count register
bSectorNumberReg: Byte;// IDE sector number register
bCylLowReg: Byte;// IDE low order cylinder value
bCylHighReg: Byte;// IDE high order cylinder value
bDriveHeadReg: Byte;// IDE drive/head register
bCommandReg: Byte;// Actual IDE command.
bReserved: Byte;// reserved for future use. Must be zero.
end;
SENDCMDINPARAMS = record
cBufferSize: Longword;// Buffer size in bytes
irDriveRegs: IDEREGS; // Structure with drive register values.
bDriveNumber: Byte;// Physical drive number to send
// command to (0,1,2,3).
bReserved: array[0..2] of Byte;// Reserved for future expansion.
dwReserved: array [0..3] of Longword;// For future use.
bBuffer: array [0..0] of Byte;// Input buffer. //!TODO: this is array of single element
end;
PSENDCMDINPARAMS = ^SENDCMDINPARAMS;
PSENDCMDOUTPARAMS = ^SENDCMDOUTPARAMS;
IDSECTOR = record
wGenConfig: Word;
wNumCyls: Word;
wReserved: Word;
wNumHeads: Word;
wBytesPerTrack: Word;
wBytesPerSector: Word;
wSectorsPerTrack: Word;
wVendorUnique: array [0..3-1] of Word;
sSerialNumber: array [0..20-1] of AnsiChar;
wBufferType: Word;
wBufferSize: Word;
wECCSize: Word;
sFirmwareRev: array [0..8-1] of AnsiChar;
sModelNumber: array [0..40-1] of AnsiChar;
wMoreVendorUnique: Word;
wDoubleWordIO: Word;
wCapabilities: Word;
wReserved1: Word;
wPIOTiming: Word;
wDMATiming: Word;
wBS: Word;
wNumCurrentCyls: Word;
wNumCurrentHeads: Word;
wNumCurrentSectorsPerTrack: Word;
ulCurrentSectorCapacity: Cardinal;
wMultSectorStuff: Word;
ulTotalAddressableSectors: Cardinal;
wSingleWordDMA: Word;
wMultiWordDMA: Word;
bReserved: array [0..128-1] of Byte;
end;
PIDSECTOR = ^IDSECTOR;
TArrayDriveInfo = array of TDriveInfo;
type
DeviceQuery = record
HeaderLength: Cardinal;
Signature: array [0..8-1] of Byte;
Timeout: Cardinal;
ControlCode: Cardinal;
ReturnCode: Cardinal;
Length: Cardinal;
cBufferSize: Longword;// Buffer size in bytes
irDriveRegs: IDEREGS; // Structure with drive register values.
bDriveNumber: Byte;// Physical drive number to send
bReserved: array[0..2] of Byte;// Reserved for future expansion.
dwReserved: array [0..3] of Longword;// For future use.
bBuffer: array [0..0] of Byte;// Input buffer. //!TODO: this is array of single element
end;
function ConvertToString (diskdata: TDiskData;
firstIndex: Integer;
lastIndex: Integer;
buf: PAnsiChar): PAnsiChar;
var
index: Integer;
position: Integer;
begin
position := 0;
// each integer has two characters stored in it backwards
for index := firstIndex to lastIndex do begin
// get high byte for 1st character
buf[position] := AnsiChar(Chr(diskdata [index] div 256));
inc(position);
// get low byte for 2nd character
buf [position] := AnsiChar(Chr(diskdata [index] mod 256));
inc(position);
end;
// end the string
buf[position] := Chr(0);
// cut off the trailing blanks
index := position - 1;
while (index >0) do begin
// if not IsSpace(AnsiChar(buf[index]))
if (AnsiChar(buf[index]) <> ' ')
then break;
buf [index] := Chr(0);
dec(index);
end;
Result := buf;
end;
constructor THDScsiInfo.Create;
begin
inherited;
SetDriveNumber(0);
end;
function THDScsiInfo.IsInfoAvailable: Boolean;
begin
Result := FInfoAvailable
end;
procedure THDScsiInfo.PrintIdeInfo (DiskData: TDiskData);
var
nSectors: Int64;
serialNumber: array [0..1024-1] of AnsiChar;
modelNumber: array [0..1024-1] of AnsiChar;
revisionNumber: array [0..1024-1] of AnsiChar;
begin
// copy the hard drive serial number to the buffer
ConvertToString (DiskData, 10, 19, #serialNumber);
ConvertToString (DiskData, 27, 46, #modelNumber);
ConvertToString (DiskData, 23, 26, #revisionNumber);
FControllerType := FDriveNumber div 2;
FDriveMS := FDriveNumber mod 2;
FDriveModelNumber := modelNumber;
FSerialNumber := serialNumber;
FProductRevision := revisionNumber;
FControllerBufferSizeOnDrive := DiskData [21] * 512;
if ((DiskData [0] and $0080) <> 0)
then FDriveType := 'Removable'
else if ((DiskData [0] and $0040) <> 0)
then FDriveType := 'Fixed'
else FDriveType := 'Unknown';
// calculate size based on 28 bit or 48 bit addressing
// 48 bit addressing is reflected by bit 10 of word 83
if ((DiskData[83] and $400) <> 0) then begin
nSectors := DiskData[103] * Int64(65536) * Int64(65536) * Int64(65536) +
DiskData[102] * Int64(65536) * Int64(65536) +
DiskData[101] * Int64(65536) +
DiskData[100];
end else begin
nSectors := DiskData [61] * 65536 + DiskData [60];
end;
// there are 512 bytes in a sector
FDriveSizeBytes := nSectors * 512;
end;
procedure THDScsiInfo.ReadInfo;
type
DataArry = array [0..256-1] of WORD;
PDataArray = ^DataArry;
const
SENDIDLENGTH = sizeof (SENDCMDOUTPARAMS) + IDENTIFY_BUFFER_SIZE;
var
I: Integer;
buffer: array [0..sizeof (SRB_IO_CONTROL) + SENDIDLENGTH - 1] of AnsiChar;
dQuery: DeviceQuery;
dummy: DWORD;
pOut: PSENDCMDOUTPARAMS;
pId: PIDSECTOR;
DiskData: TDiskData;
pIdSectorPtr: PWord;
begin
FInfoAvailable := False;
FFileHandle := CreateFile (PChar(Format('\\.\Scsi%d:', [FDriveNumber])),
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil,
OPEN_EXISTING, 0, 0);
if (FFileHandle <> INVALID_HANDLE_VALUE) then begin
ZeroMemory(#dQuery, SizeOf(dQuery));
dQuery.HeaderLength := sizeof (SRB_IO_CONTROL);
dQuery.Timeout := 10000;
dQuery.Length := SENDIDLENGTH;
dQuery.ControlCode := IOCTL_SCSI_MINIPORT_IDENTIFY;
StrLCopy(#dQuery.Signature, 'SCSIDISK', 8);
dQuery.irDriveRegs.bCommandReg := IDE_ATA_IDENTIFY;
dQuery.bDriveNumber := FDriveNumber;
if (DeviceIoControl (FFileHandle, IOCTL_SCSI_MINIPORT,
#dQuery,
SizeOf(dQuery),
#buffer,
sizeof (SRB_IO_CONTROL) + SENDIDLENGTH,
dummy, nil))
then begin
pOut := PSENDCMDOUTPARAMS(buffer + sizeof (SRB_IO_CONTROL)); //!TOCHECK
pId := PIDSECTOR(#pOut^.bBuffer[0]);
if (pId^.sModelNumber[0] <> Chr(0) ) then begin
pIdSectorPtr := PWord(pId);
for I := 0 to 256-1 do
DiskData[I] := PDataArray(pIdSectorPtr)[I];
PrintIdeInfo (DiskData);
FInfoAvailable := True;
end;
end;
CloseHandle(FFileHandle);
end;
end;
procedure THDScsiInfo.SetDriveNumber(const Value: Byte);
begin
FDriveNumber := Value;
ReadInfo;
end;
end.
Sample usage:
procedure ReadIdeDriveAsScsiDriveInNT;
var
DriveNumber: Byte;
HDDInfo: THDScsiInfo;
begin
HDDInfo := THDScsiInfo.Create();
try
for DriveNumber := 0 to MAX_IDE_DRIVES - 1 do
try
HDDInfo.DriveNumber := DriveNumber;
if HDDInfo.IsInfoAvailable then begin
Writeln('Available Drive: ', HDDInfo.DriveNumber);
Writeln('ControllerType: ', HDDInfo.ControllerType);
Writeln('DriveMS: ', HDDInfo.DriveMS);
Writeln('DriveModelNumber: ', HDDInfo.DriveModelNumber);
Writeln('ControllerBufferSizeOnDrive: ', HDDInfo.ControllerBufferSizeOnDrive);
Writeln('DriveType: ', HDDInfo.DriveType);
Writeln('DriveSizeBytes: ', HDDInfo.DriveSizeBytes);
Writeln('ProductRevision: ', HDDInfo.ProductRevision);
Writeln('SerialNumber: ', HDDInfo.SerialNumber);
end;
except
on E: Exception do
Writeln(Format('DriveNumber %d, %s: %s', [DriveNumber, E.ClassName, E.Message]));
end;
finally
HDDInfo.Free;
end;
end;
begin
ReadIdeDriveAsScsiDriveInNT;
Write('Press <Enter>');
end.
This works great with my WD disk.
I found this code, it is fixed one and working fine with me on windows 7 64
https://code.google.com/p/dvsrc/downloads/detail?name=20120116DiskId32Port_fixed.7z&can=2&q=
and this his all work
https://code.google.com/p/dvsrc/downloads/list
Posting this purely for completeness sake, and to possibly satisfy those interested or die hard hardware fanatics.
I do not have a Pascal compiler at my disposal to test these routines on current Windows systems, but I do know this code worked back in the DOS era. Maybe it still works from a command prompt window.
Pascal code:
uses
Dos, Crt;
type
SerNoType = record
case Integer of
0: (SerNo1, SerNo2: Word);
1: (SerNo: Longint);
end;
DiskSerNoInfoType = record
Infolevel: Word;
VolSerNo: SerNoType;
VolLabel: array[1..11] of Char;
FileSys: array[1..8] of Char;
end;
function HexDigit(N: Byte): Char;
begin
if N < 10 then
HexDigit := Chr(Ord('0') + N)
else
HexDigit := Chr(Ord('A') + (N - 10));
end;
function GetVolSerialNo(DriveNo: Byte): String;
var
ReturnArray: DiskSerNoInfoType;
Regs: Registers;
begin
with Regs do
begin
AX := $440d;
BL := DriveNo;
CH := $08;
CL := $66;
DS := Seg(ReturnArray);
DX := Ofs(ReturnArray);
Intr($21, Regs);
if (Flags and FCarry) <> 0 then
GetVolSerialNo := ''
else
with ReturnArray.VolSerNo do
GetVolSerialNo :=
HexDigit(Hi(SerNo2) div 16) + HexDigit(Hi(SerNo2) mod 16) +
HexDigit(Lo(SerNo2) div 16) + HexDigit(Lo(SerNo2) mod 16) +
HexDigit(Hi(SerNo1) div 16) + HexDigit(Hi(SerNo1) mod 16) +
HexDigit(Lo(SerNo1) div 16) + HexDigit(Lo(SerNo1) mod 16);
end;
end;
procedure PutVolSerialNo(DriveNo: Byte; SerialNo: Longint);
var
ReturnArray: DiskSerNoInfoType;
Regs: Registers;
begin
with Regs do
begin
AX := $440d;
BL := DriveNo;
CH := $08;
CL := $66;
DS := Seg(ReturnArray);
DX := Ofs(ReturnArray);
Intr($21, Regs);
if (Flags and FCarry) = 0 then
begin
ReturnArray.VolSerNo.SerNo := SerialNo;
AH := $69;
BL := DriveNo;
AL := $01;
DS := Seg(ReturnArray);
DX := Ofs(ReturnArray);
Intr($21, Regs);
end;
end;
end;
Please feel free to update this answer in order to get it working (if possible at all) in Delphi.

Resources