WMI values to array - delphi

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;

Related

Identifying subnet mask of the computer in 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.

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:

How can I set a file's compression attribute in Delphi?

How can I compact files (set the 'c' attribute) from Delphi? I am speaking about the "compress contents to save disk space" function available under NTFS.
It seems that FileSetAttr does not allow me to set the 'c' attribute for a file.
you can also use the CIM_DataFile and CIM_Directory WMI classes, both had two methods called Compress and UnCompress which can be used to set the NTFS compression in a file or folder.
Check these samples (if the )
Compress (NTFS) or UnCompress a File
function CompressFile(const FileName:string;Compress:Boolean):integer;
var
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObject : OLEVariant;
begin;
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
FWMIService := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
FWbemObject := FWMIService.Get(Format('CIM_DataFile.Name="%s"',[StringReplace(FileName,'\','\\',[rfReplaceAll])]));
if Compress then
Result:=FWbemObject.Compress()
else
Result:=FWbemObject.UnCompress();
end;
Compress (NTFS) or UnCompress a Folder
function CompressFolder(const FolderName:string;Recursive, Compress:Boolean):integer;
var
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObject : OLEVariant;
StopFileName : OLEVariant;
begin;
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
FWMIService := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
FWbemObject := FWMIService.Get(Format('CIM_Directory.Name="%s"',[StringReplace(FolderName,'\','\\',[rfReplaceAll])]));
if Compress then
if Recursive then
Result:=FWbemObject.CompressEx(StopFileName, Null, Recursive)
else
Result:=FWbemObject.Compress()
else
if Recursive then
Result:=FWbemObject.UnCompressEx(StopFileName, Null, Recursive)
else
Result:=FWbemObject.UnCompress();
end;
The documentation for SetFileAttributes() explains that the FILE_ATTRIBUTE_COMPRESSED flag is not accepted by that function (although it is for GetFileAttributes). Instead it states:
To set a file's compression state, use the DeviceIoControl function with the FSCTL_SET_COMPRESSION operation.
The FSCTL_SET_COMPRESSION link in particular explains precisely how to do it. It goes something like this:
const
COMPRESSION_FORMAT_NONE = 0;
COMPRESSION_FORMAT_DEFAULT = 1;
COMPRESSION_FORMAT_LZNT1 = 2;
procedure SetCompressionAttribute(const FileName: string; const CompressionFormat: USHORT);
const
FSCTL_SET_COMPRESSION = $9C040;
var
Handle: THandle;
Flags: DWORD;
BytesReturned: DWORD;
begin
if DirectoryExists(FileName) then
Flags := FILE_FLAG_BACKUP_SEMANTICS
else if FileExists(FileName) then
Flags := 0
else
raise Exception.CreateFmt('%s does not exist', [FileName]);
Handle := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, Flags, 0);
Win32Check(Handle <> INVALID_HANDLE_VALUE);
try
if not DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, #CompressionFormat, SizeOf(Comp), nil, 0, BytesReturned, nil) then
RaiseLastOSError;
finally
CloseHandle(Handle);
end;
end;
Here you go. Call this against a file or a folder and it should do the job for you. State=true makes it compressed, State=false undoes the compression. Remember, though, that if you run it against a folder it only changes the attribute and makes it so future files created in that folder are compressed. To compress the ones already in there, you have to iterate and call this on each file (FindFirst/FindNext/FindClose). HTH.
function CompressFile(filepath: string; state: boolean): boolean;
const
COMPRESSION_FORMAT_DEFAULT = 1;
COMPRESSION_FORMAT_NONE = 0;
FSCTL_SET_COMPRESSION: DWord = $9C040;
var
compsetting: Word;
bytesreturned: DWord;
FHandle: THandle;
begin
//if not os_is_nt then
// raise Exception.Create('A Windows NT based OS is required for this function.');
FHandle := CreateFile(PChar(filepath), GENERIC_READ or GENERIC_WRITE,
0, nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if FHandle = INVALID_HANDLE_VALUE then
raise Exception.Create('CompressFile Message: ' + SysErrorMessage(GetLastError));
if state = true then
compsetting := COMPRESSION_FORMAT_DEFAULT
else
compsetting := COMPRESSION_FORMAT_NONE;
try
Result := DeviceIOControl(FHandle, FSCTL_SET_COMPRESSION, #compsetting,
sizeof(compsetting), nil, 0, bytesreturned, nil);
finally
CloseHandle(FHandle);
end;
end;

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