Identifying subnet mask of the computer in Delphi - delphi

I am looking for a way to be able to retrieve IP Subnet mask of the computer I am currently
running on at run time, in Delphi.
Is there a way in code for me to retrieve the subnet mask and store it so that I may use it in other operations?
Thanks

You can use the Win32_NetworkAdapterConfiguration WMI class and the IPSubnet property.
Try this sample code
{$APPTYPE CONSOLE}
uses
SysUtils,
ActiveX,
ComObj,
Variants;
function VarArrayToStr(const vArray: variant): string;
function _VarToStr(const V: variant): string;
var
Vt: integer;
begin
Vt := VarType(V);
case Vt of
varSmallint,
varInteger : Result := IntToStr(integer(V));
varSingle,
varDouble,
varCurrency : Result := FloatToStr(Double(V));
varDate : Result := VarToStr(V);
varOleStr : Result := WideString(V);
varBoolean : Result := VarToStr(V);
varVariant : Result := VarToStr(Variant(V));
varByte : Result := char(byte(V));
varString : Result := String(V);
varArray : Result := VarArrayToStr(Variant(V));
end;
end;
var
i : integer;
begin
Result := '[';
if (VarType(vArray) and VarArray)=0 then
Result := _VarToStr(vArray)
else
for i := VarArrayLowBound(vArray, 1) to VarArrayHighBound(vArray, 1) do
if i=VarArrayLowBound(vArray, 1) then
Result := Result+_VarToStr(vArray[i])
else
Result := Result+'|'+_VarToStr(vArray[i]);
Result:=Result+']';
end;
procedure GetWin32_NetworkAdapterConfigurationInfo;
const
WbemUser ='';
WbemPassword ='';
WbemComputer ='localhost';
wbemFlagForwardOnly = $00000020;
var
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObjectSet: OLEVariant;
FWbemObject : OLEVariant;
oEnum : IEnumvariant;
iValue : LongWord;
begin;
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
FWMIService := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM Win32_NetworkAdapterConfiguration Where IpEnabled=True','WQL',wbemFlagForwardOnly);
oEnum := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
while oEnum.Next(1, FWbemObject, iValue) = 0 do
begin
Writeln(Format('Caption %s',[String(FWbemObject.Caption)]));// String
if not VarIsNull(FWbemObject.DHCPServer) then
Writeln(Format('DHCPServer %s',[String(FWbemObject.DHCPServer)]));// String
if not VarIsNull(FWbemObject.IPAddress) then
Writeln(Format('IPAddress %s',[VarArrayToStr(FWbemObject.IPAddress)]));// array String
if not VarIsNull(FWbemObject.IPSubnet) then
Writeln(Format('IPSubnet %s',[VarArrayToStr(FWbemObject.IPSubnet)]));// array String
if not VarIsNull(FWbemObject.MACAddress) then
Writeln(Format('MACAddress %s',[VarArrayToStr(FWbemObject.MACAddress)]));// array String
Writeln;
FWbemObject:=Unassigned;
end;
end;
begin
try
CoInitialize(nil);
try
GetWin32_NetworkAdapterConfigurationInfo;
finally
CoUninitialize;
end;
except
on E:EOleException do
Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Writeln('Press Enter to exit');
Readln;
end.

1) computer may be opart of different Nets/Subnets. Each network adapter usually has some. OR even more than one sometimes.
2) Even if physically you only have one network card, ypou also have loopback network - 127.x.x.x subnet. If you have some VPN installed like Hamachi or Comodo - that would give one more network adapter. Same for phone modem when connected. Same for virtual machines like XP Compatibility Mode. So you anyway would have to filter them one some criteria.
3) You can enlist network cards with FindAdaptor function of WMI: http://www.magsys.co.uk/delphi/magwmi.asp
Then you can read the properties of those adaptors.
There are also mentioned functions to set IP's, probably there are also functiones to read them.
Even if not, how to read properties is shown in the demo at the URL.
Which properties to read, you can determine using any WMI explorer out there.
Personally i ended with WMI Explorer of ks-soft.net plus WMI Tools of Microsoft.
You debug WMI request like you would do with SQLite, then pass it to WMI wrapper and read the result.
However, returning back to aforementioned Set IP Address functions, their sources probably already do contain properties names, just to save on exploring.

Related

WMI values to array

Good afternoon guys!
I'm currently developing a small system management tool, very much in a style similar to the Windows 8 Task Manager in Firemonkey (Delphi XE2). At the moment, i'm attempting to create the CPU Core Usage tiles using TPanels inside a TGridLayout. I've added all my panels at designtime until i get the actual functionality sorted.
My issue lies with outputting the data from WMI into an array. I've already had a working version not involving an array, but i'm having trouble creating something more dynamic and less likely to fall over in different configurations. Here's the current code that's not working entirely;
Procedure CoreUsage;
const
WbemUser ='';
WbemPassword ='';
WbemComputer ='localhost';
wbemFlagForwardOnly = $00000020;
var
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObjectSet: OLEVariant;
FWbemObject : OLEVariant;
oEnum : IEnumvariant;
iValue : LongWord;
i : Integer;
begin
try
CoInitialize(nil);
try
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
FWMIService := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM Win32_PerfFormattedData_Counters_ProcessorInformation WHERE NOT Name="_Total" AND NOT Name="0,_Total"',
'WQL',
wbemFlagForwardOnly);
oEnum := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
begin
if oEnum.Next(1, FWbemObject, iValue) = 0 then
begin
for i := 0 to 63 do
begin
CoreName[i] := FWbemObject.Name;
CoreUsage[i] := FWbemObject.PercentProcessorTime;
end;
end;
FWbemObject:=Unassigned;
end;
finally
CoUninitialize;
end;
end;
end;
CoreUsage and CoreName are both global variable arrays of length [0..63] of the relevant types (CoreName is of String, and CoreUsage is of Uint64). The problem with this is that it shows the same value in all the panels as opposed to a per-core basis (which is my intent). It's almost like it's not going through the 0..63, and instead just retrieving the first value only.
I'm essentially wanting to assign each one to a value within the array as that would allow me to easily read core-specific details without needing to hard code. In the code i had working completely on my development machine, i had to manually go through and use something like this;
if AnsiContainsText(FWbemObject.Name,'3') then
begin
CoreName[3] := FWbemObject.Name;
CoreUsage[3] := FWbemObject.PercentProcessorTime;
end
As you can see, it's hard coded to expect something but would be flaky in systems with 3 CPU's (in the above example). Obviously '3' was replaced with the appropriate array id, all the way from 0 to 63 (though 0 was looking for 0,0 specifically). The problem with this is that the CPU format is actually of x,y (where x is the CPU and y is the core). It's also not code that i'd be proud to use on the simple basis of it being unreliable. For example, the core name could potentially be 0,0 .. 0,7 and then 1,0 .. 1,7 for a system with 2 eight-core CPU's (16 logical cores).
Equally important, the code itself would have been a nightmare to maintain, but could have potentially been shortened down to a single for i := clause and using IntToStr. But the problem of making sure it works would still remain.
I'm sure there's a perfectly possible way to do this, but i can't figure it out. I did try using;
for VarArrayLowBound(FWbemObject.Name, 1) to VarArrayHighBound(FWbemObject.Name, 1) do
and
for VarArrayLowBound(FWbemObject.PercentProcessorTime, 1) to VarArrayHighBound(FWbemObject.PercentProcessorTime, 1) do
but alas, no such luck. Any ideas?
The problem is your loop here:
if oEnum.Next(1, FWbemObject, iValue) = 0 then
begin
for i := 0 to 63 do
begin
CoreName[i] := FWbemObject.Name;
CoreUsage[i] := FWbemObject.PercentProcessorTime;
end;
end;
You're simply reusing the same FwbemObject over and over again 64 times.
Try something more like:
var
iCurrObj: Integer;
...
iCurrObj := 0;
while oEnum.Next(1, FWbemObject, iValue) = 0 do
begin
CoreName[iCurrObj] := FWbemObject.Name;
CoreUsage[iCurrObj] := FWbemObject.PercentProcessorTime;
Inc(iCurrObj);
// Sanity check for future protection.
if i > High(CoreName) then
Break;
end;
Better yet, make it a function that actually lets you know how many items in the arrays it populated. Here's a working (tested) example. (Note: Your example in your question won't even compile despite the declaration of the two arrays, because your procedure name CoreUsage is the same name as your array CoreUsage.)
var
CoreName: array[0..63] of string;
CoreUsage: array[0..63] of Extended;
function CoreUse: Integer;
const
WbemUser ='';
WbemPassword ='';
WbemComputer ='localhost';
wbemFlagForwardOnly = $00000020;
var
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObjectSet: OLEVariant;
FWbemObject : OLEVariant;
oEnum : IEnumvariant;
iValue : LongWord;
i : Integer;
begin
i := 0;
CoInitialize(nil);
try
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
FWMIService := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM Win32_PerfFormattedData_Counters_ProcessorInformation WHERE NOT Name="_Total" AND NOT Name="0,_Total"', 'WQL', wbemFlagForwardOnly);
oEnum := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
while oEnum.Next(1, FWbemObject, iValue) = 0 do
begin
CoreName[i] := FWbemObject.Name;
CoreUsage[i] := FWbemObject.PercentProcessorTime;
Inc(i);
FWbemObject:=Unassigned;
end;
Result := i;
finally
CoUninitialize;
end;
end;

How to correctly retrieve battery serial number?

In Delphi 7 I'm working on a library implementing an object encapsulating information about the batteries attached to a system. It's working well, except for retrieving the serial number for the battery.
The code I am using for this call is as follows:
function TBattery.GetSerialNumber(hbat: THandle): boolean;
var
bqi: TBatteryQueryInformation;
Serial: PWideChar;
SerialSize,
dwOut: DWORD;
begin
Result := False;
if hbat <> INVALID_HANDLE_VALUE then
begin
ZeroMemory(#bqi, SizeOf(bqi));
dwOut := 0;
bqi.BatteryTag := FBatteryTag;
bqi.InformationLevel := BatterySerialNumber;
SerialSize := 2048;
GetMem(Serial, SerialSize);
try
ZeroMemory(Serial, SerialSize);
Result := DeviceIoControl(hbat, IOCTL_BATTERY_QUERY_INFORMATION, #bqi,
SizeOf(bqi), Serial, SerialSize, #dwOut, nil);
if Result then
FSerialNumber := Serial;
finally
FreeMem(Serial, SerialSize);
end;
end;
end;
Unfortunately, DeviceIoControl() always returns False and if I check GetLastError() afterwards then it comes back with error 87, "the parameter is incorrect."
This doesn't make much sense, because the code works perfectly well if I simply change the InformationLevel from BatterySerialNumber to BatteryUniqueID, say. Also, I've used the handle to the battery (hbat) in other calls in the code before GetSerialNumber and they all work fine, and I can call others after this one fails as well, so that's not the issue.
Any ideas? I'm really at a loss.
The issue it seems related to the dwOut variable which is passed as #dwOut, this variable represents the var lpBytesReturned parameter of the DeviceIoControl which is defined as
function DeviceIoControl(hDevice: THandle; dwIoControlCode: DWORD; lpInBuffer: Pointer;
nInBufferSize: DWORD; lpOutBuffer: Pointer; nOutBufferSize: DWORD;
var lpBytesReturned: DWORD; lpOverlapped: POverlapped): BOOL; stdcall;
So replacing your code by
Result := DeviceIoControl(hbat, IOCTL_BATTERY_QUERY_INFORMATION, #bqi,
SizeOf(bqi), Serial, SerialSize, dwOut, nil);
Must fix the problem.
WinAPI
Also check this code translated to delphi from this msdn entry Enumerating Battery Devices which can help you to detect any additional issues with your code.
uses
SetupApi,
Windows,
SysUtils;
type
BATTERY_QUERY_INFORMATION_LEVEL = (
BatteryInformation,
BatteryGranularityInformation,
BatteryTemperature,
BatteryEstimatedTime,
BatteryDeviceName,
BatteryManufactureDate,
BatteryManufactureName,
BatteryUniqueID,
BatterySerialNumber);
TBatteryQueryInformationLevel = BATTERY_QUERY_INFORMATION_LEVEL;
_BATTERY_QUERY_INFORMATION = record
BatteryTag: ULONG;
InformationLevel: BATTERY_QUERY_INFORMATION_LEVEL;
AtRate: Longint;
end;
BATTERY_QUERY_INFORMATION = _BATTERY_QUERY_INFORMATION;
PBATTERY_QUERY_INFORMATION = ^BATTERY_QUERY_INFORMATION;
TBatteryQueryInformation = BATTERY_QUERY_INFORMATION;
const
GUID_DEVCLASS_BATTERY:TGUID='{72631E54-78A4-11D0-BCF7-00AA00B7B32A}';
//DEFINE_GUID( GUID_DEVCLASS_BATTERY, 0x72631E54, 0x78A4, 0x11D0, 0xBC, 0xF7, 0x00, 0xAA, 0x00, 0xB7, 0xB3, 0x2A );
METHOD_BUFFERED = 0;
FILE_DEVICE_BATTERY = $00000029;
FILE_READ_ACCESS = $0001; // for files and pipes
IOCTL_BATTERY_QUERY_TAG =
(FILE_DEVICE_BATTERY shl 16) or (FILE_READ_ACCESS shl 14) or ($10 shl 2) or (METHOD_BUFFERED);
IOCTL_BATTERY_QUERY_INFORMATION =
(FILE_DEVICE_BATTERY shl 16) or (FILE_READ_ACCESS shl 14) or ($11 shl 2) or (METHOD_BUFFERED);
function GetBatteryInfo(InformationLevel : BATTERY_QUERY_INFORMATION_LEVEL) : string;
var
cbRequired : DWORD;
hdev : HDEVINFO;
idev : Integer;
did : TSPDeviceInterfaceData;
pdidd : PSPDeviceInterfaceDetailData;
hBattery : THandle;
bqi : TBatteryQueryInformation;
dwWait, dwOut : DWORD;
lpOutBuffer: PWideChar;
begin
// enumerate the batteries
hdev := SetupDiGetClassDevs(#GUID_DEVCLASS_BATTERY, nil, 0, DIGCF_PRESENT OR DIGCF_DEVICEINTERFACE);
if ( INVALID_HANDLE_VALUE <> THandle(hdev) ) then
begin
idev:=0;//first battery
ZeroMemory(#did, SizeOf(did));
did.cbSize := SizeOf(did);
if (SetupDiEnumDeviceInterfaces(hdev, nil, GUID_DEVCLASS_BATTERY, idev, did)) then
begin
try
cbRequired := 0;
SetupDiGetDeviceInterfaceDetail(hdev, #did, nil, 0, cbRequired, nil);
if (ERROR_INSUFFICIENT_BUFFER= GetLastError()) then
begin
pdidd:=AllocMem(cbRequired);
try
pdidd.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
if (SetupDiGetDeviceInterfaceDetail(hdev, #did, pdidd, cbRequired, cbRequired, nil)) then
begin
hBattery :=CreateFile(pdidd.DevicePath, GENERIC_READ OR GENERIC_WRITE, FILE_SHARE_READ OR FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (INVALID_HANDLE_VALUE <> hBattery) then
begin
try
ZeroMemory(#bqi, SizeOf(bqi));
// With the tag, you can query the battery info.
dwWait := 0;
if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_TAG, #dwWait, sizeof(dwWait), #bqi.BatteryTag, sizeof(bqi.BatteryTag), dwOut, nil)) then
begin
lpOutBuffer:=AllocMem(MAX_PATH);
try
ZeroMemory(lpOutBuffer,MAX_PATH);
bqi.InformationLevel:=InformationLevel;
if DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, #bqi, SizeOf(BATTERY_QUERY_INFORMATION), lpOutBuffer, 255, dwOut,nil) then
Result:= WideCharToString(lpOutBuffer);
finally
FreeMem(lpOutBuffer);
end;
end;
finally
CloseHandle(hBattery)
end;
end;
end;
finally
FreeMem(pdidd);
end;
end;
finally
SetupDiDestroyDeviceInfoList(hdev);
end;
end;
end;
end;
begin
try
if not LoadsetupAPI then exit;
Writeln(GetBatteryInfo(BatterySerialNumber));
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
end.
WMI
Finally as aside note, you can use the WMI to retrieve the same info, in this case using the BatteryStaticData WMI class
{$APPTYPE CONSOLE}
uses
SysUtils,
ActiveX,
ComObj,
Variants;
// Battery Static Data
procedure GetBatteryStaticDataInfo;
const
WbemUser ='';
WbemPassword ='';
WbemComputer ='localhost';
wbemFlagForwardOnly = $00000020;
var
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObjectSet: OLEVariant;
FWbemObject : OLEVariant;
oEnum : IEnumvariant;
iValue : LongWord;
begin;
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
FWMIService := FSWbemLocator.ConnectServer(WbemComputer, 'root\WMI', WbemUser, WbemPassword);
FWbemObjectSet:= FWMIService.ExecQuery('SELECT SerialNumber FROM BatteryStaticData','WQL',wbemFlagForwardOnly);
oEnum := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
while oEnum.Next(1, FWbemObject, iValue) = 0 do
begin
Writeln(Format('SerialNumber %s',[String(FWbemObject.SerialNumber)]));// String
Writeln('');
FWbemObject:=Unassigned;
end;
end;
begin
try
CoInitialize(nil);
try
GetBatteryStaticDataInfo;
finally
CoUninitialize;
end;
except
on E:EOleException do
Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Writeln('Press Enter to exit');
Readln;
end.
In summary, the code #RRUZ and I posted work fine under Windows 7, as well as other third-party applications. They do not work for retrieving the serial number under Windows XP. I've also tested under WinXP and 7 with base installs of the OS on the exact same hardware, with identical results (success under Windows 7, not under windows XP).
It appears that under WinXP the value BatterySerialNumber for IOCTL_BATTERY_QUERY_INFORMATION's InformationLevel member is not supported, but this is not documented directly in the Windows SDK docs. It is documented that invalid entries should return error 1 (ERROR_INVALID_FUNCTION) for GetLastError(), but in this case is returning 87 (for an invalid parameter) instead. I posit that this is because that value in the enumeration is not valid, so it makes the parameter invalid, but I'm not exactly sure.
Thanks to all for their help, especially #RRUZ for going way above and beyond!
(As an aside, it appears that one can extract the serial number from the battery's Unique ID (Using BatteryUniqueID as the InformationLevel member) and removing the manufacturer name and device name from the unique ID. That's a terrible hack, but it's a semi-viable workaround for Windows XP.)

How can I detect whether a Garmin GPS device is connected in mass storage mode?

I am trying to figure out how to detect if a drive is a mass storage device. I can get the drive letters but cannot figure out how to detect what sort of device it is. I am trying to detect if a Garmin GPS receiver is connected to a PC in mass storage mode.
You can use the WMI for this task, exist several classes which can help you to determine that information, start with the Win32_USBHub checking for the value Mass Storage Device in the the property Description, also take a look in the Win32_PNPEntity class. If you don't have experience accesing the WMI from Delphi, try using the Wmi delphi code creator
UPDATE
To associate the values returned by the Win32_USBHub or Win32_USBControllerDevice WMI classes with a Disk Drive letter, you must follow the next steps
Query for the Win32_USBControllerDevice class.
Using the DeviceID extracted of the dependent property of each returned instance(record) check the Win32_PnPEntity class loinked to a Win32_DiskDrive using a WQL sentence like so : ASSOCIATORS OF {Win32_PnPEntity.DeviceID="DeviceID"} WHERE ResultClass = Win32_DiskDrive
Now using Win32_DiskDriveToDiskPartition class you can found the link between the Disk Drive and the partition.
Finally using the Win32_LogicalDiskToPartition class you can extract the Drive letter.
Check this sample code
{$APPTYPE CONSOLE}
{$R *.res}
uses
Types,
StrUtils,
SysUtils,
ActiveX,
ComObj,
Variants;
procedure ScanUSBPnpDevices;
const
wbemFlagForwardOnly = $00000020;
var
FSWbemLocator : OLEVariant;
objWMIService : OLEVariant;
USBControllerDevices: OLEVariant;
USBControllerDevice : OLEVariant;
EnumUSBDevice : IEnumvariant;
PnPEntities : OLEVariant;
PnPEntity : OLEVariant;
EnumPnPEntity : IEnumvariant;
DiskDrives : OLEVariant;
DiskDrive : OLEVariant;
EnumDiskDrive : IEnumvariant;
DiskPartitions : OLEVariant;
DiskPartition : OLEVariant;
EnumDiskPartition : IEnumvariant;
iValue : LongWord;
DeviceID : string;
DiskDeviceID : string;
DiskPartID : string;
StringDynArray : TStringDynArray;
begin;
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
objWMIService := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
//This will scan all the usb device, you can filter this WQL sentece using any property of this class, to speed-up the process.
USBControllerDevices := objWMIService.ExecQuery('SELECT Dependent FROM Win32_USBControllerDevice','WQL',wbemFlagForwardOnly);
EnumUSBDEvice := IUnknown(USBControllerDevices._NewEnum) as IEnumVariant;
while EnumUSBDEvice.Next(1, USBControllerDevice, iValue) = 0 do
begin
StringDynArray:=SplitString(USBControllerDevice.Dependent, '=');
DeviceID:=StringDynArray[1];
Writeln(Format('USB Controller Device Device ID %s',[DeviceID]));
PnPEntities := objWMIService.ExecQuery(Format('ASSOCIATORS OF {Win32_PnPEntity.DeviceID=%s} WHERE ResultClass = Win32_DiskDrive',[DeviceID]),'WQL',wbemFlagForwardOnly);
EnumPnPEntity := IUnknown(PnPEntities._NewEnum) as IEnumVariant;
while EnumPnPEntity.Next(1, PnPEntity, iValue) = 0 do
begin
//Escape the `\` chars in the DeviceID value because the '\' is a reserved character in WMI.
DiskDeviceID := PnPEntity.DeviceId;
Writeln(Format(' Disk Drive Device ID %s',[DiskDeviceID]));
DiskDeviceID := StringReplace(DiskDeviceID,'\','\\',[rfReplaceAll]);;
DiskDrives := objWMIService.ExecQuery(Format('ASSOCIATORS OF {Win32_DiskDrive.DeviceID="%s"} WHERE AssocClass = Win32_DiskDriveToDiskPartition',[DiskDeviceID]),'WQL',wbemFlagForwardOnly);
EnumDiskDrive := IUnknown(DiskDrives._NewEnum) as IEnumVariant;
while EnumDiskDrive.Next(1, DiskDrive, iValue) = 0 do
begin
DiskPartID:=DiskDrive.deviceID;
Writeln(Format(' Disk Partition ID %s',[DiskPartID]));
DiskPartitions:=objWMIService.ExecQuery(Format('ASSOCIATORS OF {Win32_DiskPartition.DeviceID="%s"} WHERE AssocClass = Win32_LogicalDiskToPartition',[DiskPartID]),'WQL',wbemFlagForwardOnly);
EnumDiskPartition := IUnknown(DiskPartitions._NewEnum) as IEnumVariant;
while EnumDiskPartition.Next(1, DiskPartition, iValue) = 0 do
begin
Writeln(Format(' Drive Letter %s',[String(DiskPartition.DeviceID)]));
DiskPartition:=Unassigned;
end;
DiskDrive:=Unassigned;
end;
PnPEntity:=Unassigned;
end;
USBControllerDevice:=Unassigned;
end;
end;
begin
try
CoInitialize(nil);
try
ScanUSBPnpDevices;
finally
CoUninitialize;
end;
except
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Readln;
end.
Which will return some thing like this.
USB Controller Device Device ID "USBSTOR\\DISK&VEN_HP&PROD_V100W&REV_1.00\\3S980
62800DD&0"
Disk Drive Device ID \\.\PHYSICALDRIVE1
Disk Partition ID Disk #1, Partition #0
Drive Letter F:

Serial port enumeration in Delphi using SetupDiGetClassDevs

I'm trying to enumerate "friendly names" for COM ports. The ports may dynamically change as USB-serial devices are connected and disconnected at runtime.
Based on the possible methods described in this question, I am attempting to use the SetupDiGetClassDevs method.
I found this example code, but it is written for an older version of the setupapi unit (the original link to homepages.borland.com doesn't work of course).
I tried using the setupapi unit from the current JVCL(JVCL340CompleteJCL221-Build3845), but it doesn't seem to be compatible with Delphi 7. I get compiler errors:
if SetupDiGetDeviceRegistryProperty(DevInfoHandle,DeviceInfoData,
RegProperty,
#PropertyRegDataType,
#S1[1],RequiredSize,#RequiredSize) then begin
In the call to function SetupDiGetDeviceRegistryProperty,
I get the error "Types of actual and formal parameters must be identical" on the parameters #PropertyRegDataType, and #RequiredSize.
The Delphi3000 site says the code was written in 2004 and is intended for Delphi 7, so I'm not sure why it doesn't work with Delphi 7 now, unless setupapi has changed. Is anyone familiar with the changes to setupapi that could cause these problems?
I'm testing with a simple console program. The uses statement is " windows,
sysutils,
classes,
setupAPI,
Registry;"
The main program is:
begin
ComPortStringList := SetupEnumAvailableComPorts;
for Index := 0 to ComPortStringList.Count - 1 do
writeln(ComPortStringList[Index]);
end;
end.
The following procedure is working correctly for me (in Windows 8.1). It is important to use the parameter KEY_READ in the TRegistry.Constructor.
procedure EnumComPorts(const Ports: TStringList);
var
nInd: Integer;
begin { EnumComPorts }
with TRegistry.Create(KEY_READ) do
try
RootKey := HKEY_LOCAL_MACHINE;
if OpenKey('hardware\devicemap\serialcomm', False) then
try
Ports.BeginUpdate();
try
GetValueNames(Ports);
for nInd := Ports.Count - 1 downto 0 do
Ports.Strings[nInd] := ReadString(Ports.Strings[nInd]);
Ports.Sort()
finally
Ports.EndUpdate()
end { try-finally }
finally
CloseKey()
end { try-finally }
else
Ports.Clear()
finally
Free()
end { try-finally }
end { EnumComPorts };
I was able to get some more specific suggestions by asking the question a different way with different tags.
It turns out there were errors in the delphi3000.com example code, and possibly errors in the JVCL code. After fixing the example code errors, I got it to work. I have not addressed the potential JVCL errors.
Here is the working code (as a simple console app) for enumerating the names of com ports:
{$APPTYPE CONSOLE}
program EnumComPortsTest;
uses
windows,
sysutils,
classes,
setupAPI,
Registry;
{$R *.RES}
var
ComPortStringList : TStringList;
(*
The function below returns a list of available COM-ports
(not open by this or an other process), with friendly names. The list is formatted as follows:
COM1: = Communications Port (COM1)
COM5: = NI Serial Port (Com5)
COM6: = NI Serial Port (Com6)
COM7: = USB Serial Port (COM7)
COM8: = Bluetooth Communications Port (COM8)
COM9: = Bluetooth Communications Port (COM9)
This code originally posted at http://www.delphi3000.com/articles/article_4001.asp?SK=
errors have been fixed so it will work with Delphi 7 and SetupAPI from JVCL
*)
function SetupEnumAvailableComPorts:TstringList;
// Enumerates all serial communications ports that are available and ready to
// be used.
// For the setupapi unit see
// http://homepages.borland.com/jedi/cms/modules/apilib/visit.php?cid=4&lid=3
var
RequiredSize: Cardinal;
GUIDSize: DWORD;
Guid: TGUID;
DevInfoHandle: HDEVINFO;
DeviceInfoData: TSPDevInfoData;
MemberIndex: Cardinal;
PropertyRegDataType: DWord;
RegProperty: Cardinal;
RegTyp: Cardinal;
Key: Hkey;
Info: TRegKeyInfo;
S1,S2: string;
hc: THandle;
begin
Result:=Nil;
//If we cannot access the setupapi.dll then we return a nil pointer.
if not LoadsetupAPI then exit;
try
// get 'Ports' class guid from name
GUIDSize := 1; // missing from original code - need to tell function that the Guid structure contains a single GUID
if SetupDiClassGuidsFromName('Ports',#Guid,GUIDSize,RequiredSize) then begin
//get object handle of 'Ports' class to interate all devices
DevInfoHandle:=SetupDiGetClassDevs(#Guid,Nil,0,DIGCF_PRESENT);
if Cardinal(DevInfoHandle)<>Invalid_Handle_Value then begin
try
MemberIndex:=0;
result:=TStringList.Create;
//iterate device list
repeat
FillChar(DeviceInfoData,SizeOf(DeviceInfoData),0);
DeviceInfoData.cbSize:=SizeOf(DeviceInfoData);
//get device info that corresponds to the next memberindex
if Not SetupDiEnumDeviceInfo(DevInfoHandle,MemberIndex,DeviceInfoData) then
break;
//query friendly device name LIKE 'BlueTooth Communication Port (COM8)' etc
RegProperty:=SPDRP_FriendlyName;{SPDRP_Driver, SPDRP_SERVICE, SPDRP_ENUMERATOR_NAME,SPDRP_PHYSICAL_DEVICE_OBJECT_NAME,SPDRP_FRIENDLYNAME,}
SetupDiGetDeviceRegistryProperty(DevInfoHandle,
DeviceInfoData,
RegProperty,
PropertyRegDataType,
NIL,0,RequiredSize);
SetLength(S1,RequiredSize);
if SetupDiGetDeviceRegistryProperty(DevInfoHandle,DeviceInfoData,
RegProperty,
PropertyRegDataType,
#S1[1],RequiredSize,RequiredSize) then begin
KEY:=SetupDiOpenDevRegKey(DevInfoHandle,DeviceInfoData,DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ);
if key<>INValid_Handle_Value then begin
FillChar(Info, SizeOf(Info), 0);
//query the real port name from the registry value 'PortName'
if RegQueryInfoKey(Key, nil, nil, nil, #Info.NumSubKeys,#Info.MaxSubKeyLen, nil, #Info.NumValues, #Info.MaxValueLen,
#Info.MaxDataLen, nil, #Info.FileTime) = ERROR_SUCCESS then begin
RequiredSize:= Info.MaxValueLen + 1;
SetLength(S2,RequiredSize);
if RegQueryValueEx(KEY,'PortName',Nil,#Regtyp,#s2[1],#RequiredSize)=Error_Success then begin
If (Pos('COM',S2)=1) then begin
//Test if the device can be used
hc:=CreateFile(pchar('\\.\'+S2+#0),
GENERIC_READ or GENERIC_WRITE,
0,
nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if hc<> INVALID_HANDLE_VALUE then begin
Result.Add(Strpas(PChar(S2))+': = '+StrPas(PChar(S1)));
CloseHandle(hc);
end;
end;
end;
end;
RegCloseKey(key);
end;
end;
Inc(MemberIndex);
until False;
//If we did not found any free com. port we return a NIL pointer.
if Result.Count=0 then begin
Result.Free;
Result:=NIL;
end
finally
SetupDiDestroyDeviceInfoList(DevInfoHandle);
end;
end;
end;
finally
UnloadSetupApi;
end;
end;
var
index : integer;
begin
ComPortStringList := SetupEnumAvailableComPorts;
if (ComPortStringList <> nil) and (ComPortStringList.Count > 0) then
for Index := 0 to ComPortStringList.Count - 1 do
writeln(ComPortStringList[Index]);
end.
Looks like some arguments of type PDWord were replaced by var DWord in SetupApi.pas. All you need is to remove '#' from these arguments in your code like that:
if SetupDiGetDeviceRegistryProperty(DevInfoHandle,DeviceInfoData,
RegProperty,
PropertyRegDataType,
#S1[1],RequiredSize,RequiredSize) then begin
Do you have "typed # operator" turned on? Project options, Compiler tab under "Syntax options". A lot of third party code breaks if that option is enabled.
For easier operation you might consider simply using the registry where those names are listed eg:
ErrCode := RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
'HARDWARE\DEVICEMAP\SERIALCOMM',
0,
KEY_READ,
KeyHandle);
(I've ommited the hand-waving stuff).
You might also consider using WMI - see this example from Magenta Systems - you can get a pretty much everything hardware-related now.
I adapted below code from RRUZ answer for Serial Port class. Works fine under Win10 20H2.
{$APPTYPE CONSOLE}
uses
SysUtils,
ActiveX,
ComObj,
Variants;
procedure GetWin32_SerialPortInfo;
const
WbemUser ='';
WbemPassword ='';
WbemComputer ='localhost';
wbemFlagForwardOnly = $00000020;
var
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObjectSet: OLEVariant;
FWbemObject : OLEVariant;
oEnum : IEnumvariant;
iValue : LongWord;
begin;
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
FWMIService := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM Win32_SerialPort','WQL',wbemFlagForwardOnly);
oEnum := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
while oEnum.Next(1, FWbemObject, iValue) = 0 do
begin
// for other fields: https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-serialport
Writeln(Format('DeviceID %s',[String(FWbemObject.DeviceID)]));// String
Writeln(Format('Name %s',[String(FWbemObject.Name)]));// String
Writeln(Format('Description %s',[String(FWbemObject.Description)]));// String
FWbemObject:=Unassigned;
end;
end;
begin
try
CoInitialize(nil);
try
GetWin32_SerialPortInfo;
finally
CoUninitialize;
end;
except
on E:EOleException do
Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Writeln('Press Enter to exit');
Readln;
end.
Output:
DeviceID COM7
Name Silicon Labs CP210x USB to UART Bridge (COM7)
Description Silicon Labs CP210x USB to UART Bridge
Press Enter to exit

How can I get other processes' information with Delphi?

I want to make a Task Manager program that displays this information:
Image name
memory usage
PID
How can I do this?
You don't need the J(WS)CL therefore, there is a simple WinAPI call that does almost all you want, and this is CreateToolhelp32Snapshot. To get a snapshot of all running processes, you have to call it as follows:
var
snapshot: THandle;
begin
snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
Now you have a list of all running processes. You can navigate through this list with the Process32First and Process32Next functions, the list entries are PROCESSENTRY32-structures (which contain, amongst others, the process ID and image name).
uses
Windows, TLHelp32, SysUtils;
var
snapshot: THandle;
ProcEntry: TProcessEntry32;
s: String;
begin
snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot <> INVALID_HANDLE_VALUE) then begin
ProcEntry.dwSize := SizeOf(ProcessEntry32);
if (Process32First(snapshot, ProcEntry)) then begin
s := ProcEntry.szExeFile;
// s contains image name of the first process
while Process32Next(snapshot, ProcEntry) do begin
s := ProcEntry.szExeFile;
// s contains image name of the current process
end;
end;
end;
CloseHandle(snapshot);
However, memory consumption information doesn't seem to be included, but you can get this via another simple API call, GetProcessMemoryInfo
uses
psAPI;
var
pmc: TProcessMemoryCounters;
begin
pmc.cb := SizeOf(pmc) ;
if GetProcessMemoryInfo(processID, #pmc, SizeOf(pmc)) then
// Usage in Bytes: pmc.WorkingSetSize
else
// fail
You just have to call this function with the process IDs retrieved from the snapshot.
Use the PSAPI (Process Status API).
The open source JCL has a Delphi wrapper for the PSAPI.
There are some more good stackoverflow Delphi PSAPI questions you can check for answers.
--jeroen
you can use the WMI Win32_Process class to get all the running process info. addtionally you can check the Win32_PerfFormattedData_PerfProc_Process class to get the performance counters related to CPU and memory usage.
Check this sample
program WMIProcessInfo;
{$APPTYPE CONSOLE}
uses
SysUtils
,ActiveX
,ComObj
,Variants;
procedure GetWin32_Process;
var
objWMIService : OLEVariant;
colItems : OLEVariant;
colItem : OLEVariant;
oEnum : IEnumvariant;
iValue : LongWord;
User : OLEVariant;
Domain : OLEVariant;
function GetWMIObject(const objectName: String): IDispatch;
var
chEaten: Integer;
BindCtx: IBindCtx;
Moniker: IMoniker;
begin
OleCheck(CreateBindCtx(0, bindCtx));
OleCheck(MkParseDisplayName(BindCtx, StringToOleStr(objectName), chEaten, Moniker));
OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));
end;
begin
objWMIService := GetWMIObject('winmgmts:\\localhost\root\cimv2');
colItems := objWMIService.ExecQuery('SELECT * FROM Win32_Process','WQL',0);
oEnum := IUnknown(colItems._NewEnum) as IEnumVariant;
WriteLn(Format('%-20s %6s %10s %10s %10s',['Caption','PID','User','Domain','Working Set ( Kb Memory)']));
while oEnum.Next(1, colItem, iValue) = 0 do
begin
colItem.GetOwner(User,Domain);
if colItem.GetOwner( User, Domain ) =0 then //get the user and domain
WriteLn(Format('%-20s %6s %10s %10s %10s',[colItem.Caption,colItem.ProcessId,User,Domain,colItem.WorkingSetSize / 1024]))
else
WriteLn(Format('%-20s %6s %10s %10s %10s',[colItem.Caption,colItem.ProcessId,'','',colItem.WorkingSetSize / 1024]));
end;
end;
begin
try
CoInitialize(nil);
try
GetWin32_Process;
Readln;
finally
CoUninitialize;
end;
except
on E:Exception do
Begin
Writeln(E.Classname, ': ', E.Message);
Readln;
End;
end;
end.
In Jwscl there is a class that can do this for you (JwsclTerminalServer):
var
ATerminalServer: TJwTerminalServer;
i: Integer;
begin
// Create Terminal Server instance and allocate memory for it
ATerminalServer := TjwTerminalServer.Create;
// Set servername (only in case of remote server)
ATerminalServer.Server := 'TS001';
// Remember that EnumerateProcesses will automatically connect to the
// Terminal Server for you. The connect function raises an Exception
// if the connection attempt was unsuccessfull, so better use try..except
try
if ATerminalServer.EnumerateProcesses then
begin
// Now loop through the list
for i := 0 to ATerminalServer.Processes.Count - 1 do
begin
Memo1.Lines.Add(ATerminalServer.Processes[i].ProcessName);
end;
end;
except
on E: EJwsclWinCallFailedException do
begin
// Handle Exception here
end;
end;
// Free Memory
ATerminalServer.Free;
end;
Although the unit is aimed at Terminal Server this part works both with and without and as a bonus you can use it on remote systems as well.
For each process detailed information is returned, check the docs for details.
For memory usage you can use the ProcessMemUsage and ProcessVirtualSize properties, for the Pid there is the ProcessId property
ProcessInfo provides basic information about running processes in Windows. It is open-source, and contains a demo of a task manager.

Resources