Delphi - get what files are opened by an application - delphi

How can I get the list of opened files by an application, using Delphi?
For example what files are opened by winword.exe

Using the Native API function NtQuerySystemInformation you can list all open handles from all processes.
try this example
program ListAllHandles;
{$APPTYPE CONSOLE}
uses
PSApi,
Windows,
SysUtils;
const
SystemHandleInformation = $10;
STATUS_SUCCESS = $00000000;
STATUS_BUFFER_OVERFLOW = $80000005;
STATUS_INFO_LENGTH_MISMATCH = $C0000004;
DefaulBUFFERSIZE = $100000;
type
OBJECT_INFORMATION_CLASS = (ObjectBasicInformation,ObjectNameInformation,ObjectTypeInformation,ObjectAllTypesInformation,ObjectHandleInformation );
SYSTEM_HANDLE=packed record
uIdProcess:ULONG;
ObjectType:UCHAR;
Flags :UCHAR;
Handle :Word;
pObject :Pointer;
GrantedAccess:ACCESS_MASK;
end;
PSYSTEM_HANDLE = ^SYSTEM_HANDLE;
SYSTEM_HANDLE_ARRAY = Array[0..0] of SYSTEM_HANDLE;
PSYSTEM_HANDLE_ARRAY= ^SYSTEM_HANDLE_ARRAY;
SYSTEM_HANDLE_INFORMATION=packed record
uCount:ULONG;
Handles:SYSTEM_HANDLE_ARRAY;
end;
PSYSTEM_HANDLE_INFORMATION=^SYSTEM_HANDLE_INFORMATION;
TNtQuerySystemInformation=function (SystemInformationClass:DWORD; SystemInformation:pointer; SystemInformationLength:DWORD; ReturnLength:PDWORD):THandle; stdcall;
TNtQueryObject =function (ObjectHandle:cardinal; ObjectInformationClass:OBJECT_INFORMATION_CLASS; ObjectInformation:pointer; Length:ULONG;ResultLength:PDWORD):THandle;stdcall;
UNICODE_STRING=packed record
Length :Word;
MaximumLength:Word;
Buffer :PWideChar;
end;
OBJECT_NAME_INFORMATION=UNICODE_STRING;
POBJECT_NAME_INFORMATION=^OBJECT_NAME_INFORMATION;
Var
NTQueryObject :TNtQueryObject;
NTQuerySystemInformation:TNTQuerySystemInformation;
function GetObjectInfo(hObject:cardinal; objInfoClass:OBJECT_INFORMATION_CLASS):LPWSTR;
var
pObjectInfo:POBJECT_NAME_INFORMATION;
HDummy :THandle;
dwSize :DWORD;
begin
Result:=nil;
dwSize := sizeof(OBJECT_NAME_INFORMATION);
pObjectInfo := AllocMem(dwSize);
HDummy := NTQueryObject(hObject, objInfoClass, pObjectInfo,dwSize, #dwSize);
if((HDummy = STATUS_BUFFER_OVERFLOW) or (HDummy = STATUS_INFO_LENGTH_MISMATCH)) then
begin
FreeMem(pObjectInfo);
pObjectInfo := AllocMem(dwSize);
HDummy := NTQueryObject(hObject, objInfoClass, pObjectInfo,dwSize, #dwSize);
end;
if((HDummy >= STATUS_SUCCESS) and (pObjectInfo.Buffer <> nil)) then
begin
Result := AllocMem(pObjectInfo.Length + sizeof(WCHAR));
CopyMemory(result, pObjectInfo.Buffer, pObjectInfo.Length);
end;
FreeMem(pObjectInfo);
end;
Procedure EnumerateOpenFiles();
var
sDummy : string;
hProcess : THandle;
hObject : THandle;
ResultLength: DWORD;
aBufferSize : DWORD;
aIndex : Integer;
pHandleInfo : PSYSTEM_HANDLE_INFORMATION;
HDummy : THandle;
lpwsName : PWideChar;
lpwsType : PWideChar;
lpszProcess : PAnsiChar;
begin
AbufferSize := DefaulBUFFERSIZE;
pHandleInfo := AllocMem(AbufferSize);
HDummy := NTQuerySystemInformation(DWORD(SystemHandleInformation), pHandleInfo,AbufferSize, #ResultLength); //Get the list of handles
if(HDummy = STATUS_SUCCESS) then //If no error continue
begin
for aIndex:=0 to pHandleInfo^.uCount-1 do //iterate the list
begin
hProcess := OpenProcess(PROCESS_DUP_HANDLE or PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, FALSE, pHandleInfo.Handles[aIndex].uIdProcess); //open the process to get aditional info
if(hProcess <> INVALID_HANDLE_VALUE) then //Check valid handle
begin
hObject := 0;
if DuplicateHandle(hProcess, pHandleInfo.Handles[aIndex].Handle,GetCurrentProcess(), #hObject, STANDARD_RIGHTS_REQUIRED,FALSE, 0) then //Get a copy of the original handle
begin
lpwsName := GetObjectInfo(hObject, ObjectNameInformation); //Get the filename linked to the handle
if (lpwsName <> nil) then
begin
lpwsType := GetObjectInfo(hObject, ObjectTypeInformation);
lpszProcess := AllocMem(MAX_PATH);
if GetModuleFileNameEx(hProcess, 0,lpszProcess, MAX_PATH)<>0 then //get the name of the process
sDummy:=ExtractFileName(lpszProcess)
else
sDummy:= 'System Process';
Writeln('PID ',pHandleInfo.Handles[aIndex].uIdProcess);
Writeln('Handle ',pHandleInfo.Handles[aIndex].Handle);
Writeln('Process ',sDummy);
Writeln('FileName ',string(lpwsName));
Writeln;
FreeMem(lpwsName);
FreeMem(lpwsType);
FreeMem(lpszProcess);
end;
CloseHandle(hObject);
end;
CloseHandle(hProcess);
end;
end;
end;
FreeMem(pHandleInfo);
end;
begin
try
NTQueryObject := GetProcAddress(GetModuleHandle('NTDLL.DLL'), 'NtQueryObject');
NTQuerySystemInformation := GetProcAddress(GetModuleHandle('NTDLL.DLL'), 'NtQuerySystemInformation');
if (#NTQuerySystemInformation<>nil) and (#NTQuerySystemInformation<>nil) then
EnumerateOpenFiles();
Readln;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.

You could port walkobjects.cpp or run a command line process that does it for you and parse it's output.

I've looked at the MSDN page...
it said NtQuerySystemInformation() is an OS internal proc,
and that we're not recommended to use it:
The NtQuerySystemInformation function
and the structures that it returns are
internal to the operating system and
subject to change from one release of
Windows to another. To maintain the
compatibility of your application, it
is better to use the alternate
functions previously mentioned
instead.

Related

WlanAPI WlanGetNetworkBssList returning invalid data

I'm stuck in my debugging efforts with a call to WlanGetNetworkBssList and would appreciate some pointers. My end objective is to construct a Wifi scanner/profiler tool to help me troubleshoot networking issues on remote sites.
I am using the Windows Native Wifi API (link) and Delphi/Pascal interface found here using Delphi Berlin 10.1 Update 2 under Windows 10 (VCL).
I started with a simple and crude test app (VCL) to get a feel for the API and ran into a problem calling WlanGetNetworkBssList so I created a small console app focused on that problem. The issue is that it works in a console app running in a command prompt but not in my VCL test app. The functions are pretty much copy-paste equivalent and stepping through the code side-by-side shows that the data is identical except for the return data from WlanGetNetworkBssList call (pWlanBssList)
Question: Since the call is to an external DLL what steps can I do to further debug this and understand the difference between the VCL and the console app.
Note: The WlanGetNetworkBssList has two modes of operation where an SSID can be supplied to get the BSSID (MAC of the access point) for that specific SSID. By passing NULL instead of an SSID the API will return the BSSIDs of all APs. Passing NULL works on both the VLC and console app. What breaks is when a specific SSID is requested. After verification, the SSID data structure is identical in both apps but the data buffer returned is invalid with the VCL app. How can this be?
Console app:
program CWifiScan;
{$APPTYPE CONSOLE}
uses
Windows,
System.SysUtils,
nduWlanAPI,
nduWlanTypes;
const
WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_ADHOC_PROFILES = $00000001;
var
hWlan: THandle;
guid : TGUID;
dwSupportedVersion: DWORD = 0;
dwClientVersion: DWORD = 1;
i,j : integer;
pInterfaceInfo : Pndu_WLAN_INTERFACE_INFO;
pInterfaceList : Pndu_WLAN_INTERFACE_INFO_LIST;
pAvailableNetworkList : Pndu_WLAN_AVAILABLE_NETWORK_LIST;
procedure GetBSSIDList(clientHandle : THandle;
interfaceGUID : TGUID;
pSSID : Pndu_DOT11_SSID = nil;
SSID_Type : Tndu_DOT11_BSS_Type = dot11_BSS_type_any;
SecurityEnabled : BOOL = True);
var
//to check if interface is connected
pData : Pndu_WLAN_INTERFACE_STATE;
pdwDataSize : DWORD;
isConnected : Boolean;
//to get list of BSSids from available APs
pWlanBssList : Pndu_WLAN_BSS_LIST;
items : integer;
itemIndex : integer;
SSID : string;
MAC : string;
begin
//check if interface is connected
isConnected := False;
if WlanQueryInterface(clientHandle,
#interfaceGUID,
wlan_intf_opcode_interface_state,
nil,
#pdwDataSize,
#pData,
nil) = ERROR_SUCCESS then
begin
isConnected := (pData^ = Tndu_WLAN_INTERFACE_STATE.wlan_interface_state_connected);
end;
//get the list of BSSids for the provided interface
if isConnected then
begin
if WlanGetNetworkBssList(clientHandle,
#interfaceGUID,
pSSID,
SSID_Type,
SecurityEnabled,
nil,
#pWlanBssList) = ERROR_SUCCESS then
begin
items := pWlanBssList^.dwNumberOfItems;
for itemIndex := 0 to items - 1 do
begin
SSID := String(PAnsiChar(#pWlanBssList^.wlanBssEntries[itemIndex].dot11Ssid.ucSSID));
MAC := Format('%.2x:%.2x:%.2x:%.2x:%.2x:%.2x', [
pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[0],
pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[1],
pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[2],
pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[3],
pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[4],
pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[5]]);
Writeln('');
Writeln('SSID: ................ '+SSID);
Writeln('Physical Address: .... '+MAC);
end; {for itemIndex}
Writeln(#10+#13+'Done.');
end; {WlanGetNetworkBssList succeeds}
end; {isConnected}
end;
begin
hWlan := 0;
if WlanOpenHandle(2, nil,#dwSupportedVersion, #hWlan)= ERROR_SUCCESS then
begin
if WlanEnumInterfaces(hWlan, nil, #pInterfaceList) = ERROR_SUCCESS then
begin
try
for i := 0 to pInterfaceList^.dwNumberOfItems-1 do
begin
Writeln('Wifi Adapter - '+GUIDToString( pInterfaceList^.InterfaceInfo[i].InterfaceGuid ) );
Writeln('Scanning: .... '+pInterfaceList^.InterfaceInfo[i].strInterfaceDescription);
guid := pInterfaceList^.InterfaceInfo[i].InterfaceGuid;
//Get all BSSids for this interface
GetBSSIDList(hWlan, guid);
if WlanGetAvailableNetworkList(hWlan,
#guid,
WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_ADHOC_PROFILES,
nil,
pAvailableNetworkList) = ERROR_SUCCESS then
begin
try
for j := 0 to pAvailableNetworkList^.dwNumberOfItems - 1 do
begin
//Get BSSid for this specific SSID
GetBSSIDList(hWlan,
guid,
#pAvailableNetworkList^.Network[j].dot11Ssid,
pAvailableNetworkList^.Network[j].dot11BssType,
pAvailableNetworkList^.Network[j].bSecurityEnabled);
end;
finally
if pAvailableNetworkList<>nil then
WlanFreeMemory(pAvailableNetworkList);
end;
end;
end;
finally
if pInterfaceList<>nil then
WlanFreeMemory(pInterfaceList);
end;
end;
WlanCloseHandle(hWlan, nil);
readln;
end;
end.
The relevant parts of the VCL app are:
uses
... nduWlanAPI, nduWlanTypes, nduWinDot11;
function TForm1.GetBSSID(clientHandle: THandle;
interfaceGuid: TGUID;
pSSID: Pndu_DOT11_SSID = nil;
SSID_Type : Tndu_DOT11_BSS_TYPE = dot11_BSS_type_any;
SecurityEnabled: boolean = true): string;
var
//used to determin if the interface is connected
pData : Pndu_WLAN_INTERFACE_STATE;
isConnected : boolean;
//used to extract a list of BSSIDs for a given interface
pWlanBssList : Pndu_WLAN_BSS_LIST;
lastError : DWORD;
pdwDataSize : DWORD;
items,
itemIndex: Integer;
begin
pData := nil;
pdwDataSize := 0;
isConnected := False;
//check if the interface is connected
lastError := WlanQueryInterface(clientHandle,
#interfaceGuid,
wlan_intf_opcode_interface_state,
nil,
#pdwDataSize,
#pData,
nil);
if (lastError = ERROR_SUCCESS) then
begin
//isConnected := (Tndu_WLAN_INTERFACE_STATE(pData^.isState) = Tndu_WLAN_INTERFACE_STATE.wlan_interface_state_connected);
isConnected := (pData^ = Tndu_WLAN_INTERFACE_STATE.wlan_interface_state_connected);
end
else
DisplayError('Error in WlanQueryInterface() function', lastError);
if isConnected then
begin
pWlanBssList := nil;
lastError := WlanGetNetworkBssList(clientHandle,
#interfaceGuid,
pSSID,
SSID_Type,
SecurityEnabled,
nil,
#pWlanBssList);
try
if (lastError = ERROR_SUCCESS) then
begin
items := pWlanBssList^.dwNumberOfItems;
for itemIndex := 0 to items-1 do
begin
Result := (Format('%.2x:%.2x:%.2x:%.2x:%.2x:%.2x', [
pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[0],
pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[1],
pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[2],
pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[3],
pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[4],
pWlanBssList^.wlanBssEntries[itemIndex].dot11Bssid[5]]));
end;
end
else
DisplayError('Error in the WlanGetNetworkBssList() function call', lastError);
finally
if pData<>nil then
WlanFreeMemory(pData);
if pWlanBssList<>nil then
WlanFreeMemory(pWlanBssList);
end;
end;
end;
Which is called as follows:
function TForm1.ScanWifi(): THandle;
const
WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_ADHOC_PROFILES = $00000001;
var
hClient : THandle;
dwVersion : DWORD;
lastError : DWORD;
pInterface : Pndu_WLAN_INTERFACE_INFO_LIST;
i : Integer;
j : Integer;
pAvailableNetworkList: Pndu_WLAN_AVAILABLE_NETWORK_LIST;
interfaceGuid : TGUID;
BSSID : string;
begin
lastError:=WlanOpenHandle(NDU_WLAN_API_VERSION, nil, #dwVersion, #hClient);
if lastError<> ERROR_SUCCESS then
begin
//DisplayError('Error in the WlanOpenHandle() function call', lastError);
Result := 0;
Exit;
end;
//L(Format('Requested WLAN interface version [%d], negotiated version [%d]', [NDU_WLAN_API_VERSION, dwVersion]));
Result := hClient;
try
lastError:=WlanEnumInterfaces(hClient, nil, #pInterface);
try
if lastError<> ERROR_SUCCESS then
begin
//DisplayError('Errorin the WlanEnumInterfaces() function call', lastError);
Exit;
end;
for i := 0 to pInterface^.dwNumberOfItems - 1 do
begin
interfaceGuid:= pInterface^.InterfaceInfo[i].InterfaceGuid;
lastError:=WlanGetAvailableNetworkList(hClient,
#interfaceGuid,
WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_ADHOC_PROFILES,
nil,
pAvailableNetworkList);
try
if lastError<> ERROR_SUCCESS then
begin
//DisplayError('Error WlanGetAvailableNetworkList', lastError);
Exit;
end
else
begin
for j := 0 to pAvailableNetworkList^.dwNumberOfItems - 1 do
Begin
BSSID := GetBssid(hClient,
interfaceGuid,
#pAvailableNetworkList^.Network[j].dot11Ssid,
pAvailableNetworkList^.Network[j].dot11BssType,
pAvailableNetworkList^.Network[j].bSecurityEnabled
);
//FAPList.AddOrSetValue(BSSID,J);
end;
end;
finally
if pAvailableNetworkList <> nil then
WlanFreeMemory(pAvailableNetworkList);
end;
end;
finally
if pInterface <> nil then
WlanFreeMemory(pInterface);
end;
finally
WlanCloseHandle(FhClient, nil);
end;
end;
Comparing the data between the two apps the only difference is the result (pWlanBssList) as seen here (left=console, right=VCL):
Looks like compiler bug in boolean conversion, problem is following line in VCL code
SecurityEnabled: boolean = true
you need to change it to
SecurityEnabled: bool = true

How lock CTRL+ALT+DEL using SetWindowHookEx api?

Good afternoon,
I need lock CTRL+ALT+DEL combination using SetWindowsHookEx and today i have done a code and don't is working until now.
This code is executing in a dll ( this dll is my software ) that is injected in other process.
So, how i can adapt this code below to work?
const
WH_KEYBOARD_LL = 13;
LLKHF_ALTDOWN = $20;
type
KBDLLHOOKSTRUCT = record
vkCode: DWORD;
scanCode: DWORD;
flags: DWORD;
time: DWORD;
dwExtraInfo: Longint ;
end;
var
hhkLowLevelKybd : HHOOK;
FoldProc : LongInt;
hSASWnd : HWND;
hThread : Cardinal;
{$R *.dfm}
Function LowLevelKeyboardProc(nCode : Integer; wParam : Longint; var LParam: KBDLLHOOKSTRUCT) : Longint; stdcall;
var
fEatKeystroke : Boolean;
dwThreadId : Cardinal;
begin
If (nCode = HC_ACTION) Then
begin
If (wParam = WM_KEYDOWN) Or
(wParam = WM_SYSKEYDOWN) Or
(wParam = WM_KEYUP) Or
(wParam = WM_SYSKEYUP) Then
begin
fEatKeystroke :=
(((GetKeyState(VK_CONTROL) And $8000) <> 0) And
((LParam.flags And LLKHF_ALTDOWN ) <> 0) And
(LParam.vkCode = VK_DELETE));
End;
If fEatKeystroke Then
Result := -1
Else
Result := CallNextHookEx(0, nCode, wParam, LongInt(#LParam));
End;
end;
////////// FormCreate event here ///////////
hhkLowLevelKybd := 0;
hhkLowLevelKybd := SetWindowsHookEx(WH_KEYBOARD_LL, #LowLevelKeyboardProc,
HInstance, 0);
end.
Windows does not allow you to intercept Ctrl+Alt+Del for security reasons. Earlier versions (pre-Vista?) used to allow it by replacing the GINA DLL, but it's not been allowed for years.
That key combination is known as a secure attention sequence which is guaranteed to be trustworthy as part of the login process.
If your goal is to only allow your application to be run, you can configure it to act in kiosk mode if you're running a suitable version of Windows, as shown in Set up a device for anyone to use (kiosk mode) at TechNet which #LURD kindly provided.
By design it's impossible to trap or block Ctrl+Alt+Del (The Secure Attention Sequence). There is however a commercial library available (disclaimer: I am the author), SasLibEx.
SasLibEx: a library that can simulate or block the Secure Attention
Sequence (Ctrl+Alt+Del) but it can even unlock a
workstation or session without entering or needing the user’s
credentials (and many more things)
See this screencast for a demo.
Impossible. The Ctl-Alt-Del gets trapped in the Kernel and never makes it to the user mode space where your app is running.
I have had to do this on kiosks systems (using Win XP and Vista) and I did it with a keyboard filter driver (which runs in the kernel) that swaps out the scan codes when the key are pressed.
Not is impossible, see the following code:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils,
Windows,
Registry,
vcl.Dialogs;
procedure DisableCtrAltDel(boolState: Boolean);
var
SystemReg: TRegistry;
Data: Array [1 .. 48] of Byte;
i: Byte;
begin
try
for i := 1 to 48 do
Data[i] := $00;
Data[9] := $09;
Data[15] := $5B;
Data[16] := $E0;
Data[19] := $5C;
Data[20] := $E0;
Data[23] := $5D;
Data[24] := $E0;
Data[27] := $44;
Data[31] := $1D;
Data[35] := $38;
Data[39] := $1D;
Data[40] := $E0;
Data[43] := $38;
Data[44] := $E0;
try
SystemReg := TRegistry.Create;
with SystemReg do
begin
RootKey := HKEY_LOCAL_MACHINE;
OpenKey('\System\CurrentControlSet\Control\Keyboard Layout', True);
if boolState then
WriteBinaryData('Scancode Map', Data, SizeOf(Data))
else
DeleteValue('Scancode Map');
MessageDlg('Restart Windows in order the changes to take effect!',
mtInformation, [mbOK], 0);
CloseKey;
end;
finally
SystemReg.Free;
end;
except
MessageDlg
('Error occurred while trying to disable ctrl+alt+del and Task Manager',
mtWarning, [mbOK], 0);
end;
end;
begin
try
DisableCtrAltDel(True);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Reference

Copying files to clipboard and then pasting them into their original folder does not work

I've got a puzzling situation. I am using the following code in Delphi to copy a list of files to the clipboard;
procedure TfMain.CopyFilesToClipboard(FileList: string);
const
C_UNABLE_TO_ALLOCATE_MEMORY = 'Unable to allocate memory.';
C_UNABLE_TO_ACCESS_MEMORY = 'Unable to access allocated memory.';
var
DropFiles: PDropFiles;
hGlobal: THandle;
iLen: Integer;
begin
iLen := Length(FileList);
hGlobal := GlobalAlloc(GMEM_SHARE or GMEM_MOVEABLE or
GMEM_ZEROINIT, SizeOf(TDropFiles) + ((iLen + 2) * SizeOf(Char)));
if (hGlobal = 0) then
raise Exception.Create(C_UNABLE_TO_ALLOCATE_MEMORY);
try DropFiles := GlobalLock(hGlobal);
if (DropFiles = nil) then raise Exception.Create(C_UNABLE_TO_ACCESS_MEMORY);
try
DropFiles^.pFiles := SizeOf(TDropFiles);
DropFiles^.fWide := True;
if FileList <> '' then
Move(FileList[1], (PByte(DropFiles) + SizeOf(TDropFiles))^,
iLen * SizeOf(Char));
finally
GlobalUnlock(hGlobal);
end;
Clipboard.SetAsHandle(CF_HDROP, hGlobal);
except
GlobalFree(hGlobal);
end;
end;
(This seems to be a popular piece of code on the internet)
Using my application, once the files are copied to the clipboard, I can use Windows Explorer to paste them into every other folder, EXCEPT the folder where the file originally came from! I was expecting it to behave just like a normal Windows copy (i.e. on paste it should create a file with postfix of '-Copy') but this doesn't seem to work. Any clues?
I am not able to get Windows Explorer to paste into the source folder when the only clipboard format available is CF_HDROP. However, if the filenames are provided in an IDataObject instead, it works fine.
If all of the files are from the same source folder, you can retrieve the IShellFolder of the source folder and query it for child PIDLs for the individual files, then use IShellFolder.GetUIObjectOf() to get an IDataObject that represents the files. Then use OleSetClipboard() to put that object on the clipboard. For example:
uses
System.Classes, Winapi.Windows, Winapi.ActiveX, Winapi.Shlobj, Winapi.ShellAPI, System.Win.ComObj;
procedure CopyFilesToClipboard(const Folder: string; FileNames: TStrings);
var
SF: IShellFolder;
PidlFolder: PItemIDList;
PidlChildren: array of PItemIDList;
Eaten: UINT;
Attrs: DWORD;
Obj: IDataObject;
I: Integer;
begin
if (Folder = '') or (FileNames = nil) or (FileNames.Count = 0) then Exit;
OleCheck(SHParseDisplayName(PChar(Folder), nil, PidlFolder, 0, Attrs));
try
OleCheck(SHBindToObject(nil, PidlFolder, nil, IShellFolder, Pointer(SF)));
finally
CoTaskMemFree(PidlFolder);
end;
SetLength(PidlChildren, FileNames.Count);
for I := Low(PidlChildren) to High(PidlChildren) do
PidlChildren[i] := nil;
try
for I := 0 to FileNames.Count-1 do
OleCheck(SF.ParseDisplayName(0, nil, PChar(FileNames[i]), Eaten, PidlChildren[i], Attrs));
OleCheck(SF.GetUIObjectOf(0, FileNames.Count, PIdlChildren[0], IDataObject, nil, obj));
finally
for I := Low(PidlChildren) to High(PidlChildren) do
begin
if PidlChildren[i] <> nil then
CoTaskMemFree(PidlChildren[i]);
end;
end;
OleCheck(OleSetClipboard(obj));
OleCheck(OleFlushClipboard);
end;
Update: If the files are in different source folders, you can use the CFSTR_SHELLIDLIST format:
uses
System.Classes, System.SysUtils, Winapi.Windows, Winapi.ActiveX, Winapi.Shlobj, Winapi.ShellAPI, System.Win.ComObj, Vcl.Clipbrd;
{$POINTERMATH ON}
function HIDA_GetPIDLFolder(pida: PIDA): LPITEMIDLIST;
begin
Result := LPITEMIDLIST(LPBYTE(pida) + pida.aoffset[0]);
end;
function HIDA_GetPIDLItem(pida: PIDA; idx: Integer): LPITEMIDLIST;
begin
Result := LPITEMIDLIST(LPBYTE(pida) + (PUINT(#pida.aoffset[0])+(1+idx))^);
end;
var
CF_SHELLIDLIST: UINT = 0;
type
CidaPidlInfo = record
Pidl: PItemIDList;
PidlOffset: UINT;
PidlSize: UINT;
end;
procedure CopyFilesToClipboard(FileNames: TStrings);
var
PidlInfo: array of CidaPidlInfo;
Attrs, AllocSize: DWORD;
gmem: THandle;
ida: PIDA;
I: Integer;
begin
if (FileNames = nil) or (FileNames.Count = 0) or (CF_SHELLIDLIST = 0) then Exit;
SetLength(PidlInfo, FileNames.Count);
for I := Low(PidlInfo) to High(PidlInfo) do
PidlInfo[I].Pidl := nil;
try
AllocSize := SizeOf(CIDA)+(SizeOf(UINT)*FileNames.Count)+SizeOf(Word);
for I := 0 to FileNames.Count-1 do
begin
OleCheck(SHParseDisplayName(PChar(FileNames[I]), nil, PidlInfo[I].Pidl, 0, Attrs));
PidlInfo[I].PidlOffset := AllocSize;
PidlInfo[I].PidlSize := ILGetSize(PidlInfo[I].Pidl);
Inc(AllocSize, PidlInfo[I].PidlSize);
end;
gmem := GlobalAlloc(GMEM_MOVEABLE, AllocSize);
if gmem = 0 then RaiseLastOSError;
try
ida := PIDA(GlobalLock(gmem));
if ida = nil then RaiseLastOSError;
try
ida.cidl := FileNames.Count;
ida.aoffset[0] := SizeOf(CIDA)+(SizeOf(UINT)*FileNames.Count);
HIDA_GetPIDLFolder(ida).mkid.cb := 0;
for I := 0 to FileNames.Count-1 do
begin
ida.aoffset[1+I] := PidlInfo[I].PidlOffset;
Move(PidlInfo[I].Pidl^, HIDA_GetPIDLItem(ida, I)^, PidlInfo[I].PidlSize);
end;
finally
GlobalUnlock(gmem);
end;
Clipboard.SetAsHandle(CF_SHELLIDLIST, gmem);
except
GlobalFree(gmem);
raise;
end;
finally
for I := Low(PidlInfo) to High(PidlInfo) do
CoTaskMemFree(PidlInfo[I].Pidl);
end;
end;
initialization
CF_SHELLIDLIST := RegisterClipboardFormat(CFSTR_SHELLIDLIST);
Alternatively:
procedure CopyFilesToClipboard(FileNames: TStrings);
var
Pidls: array of PItemIdList;
Attrs: DWORD;
I: Integer;
obj: IDataObject;
begin
if (FileNames = nil) or (FileNames.Count = 0) then Exit;
SetLength(Pidls, FileNames.Count);
for I := Low(Pidls) to High(Pidls) do
Pidls[I] := nil;
try
for I := 0 to FileNames.Count-1 do
OleCheck(SHParseDisplayName(PChar(FileNames[I]), nil, Pidls[I], 0, Attrs));
OleCheck(CIDLData_CreateFromIDArray(nil, FileNames.Count, PItemIDList(Pidls), obj));
finally
for I := Low(Pidls) to High(Pidls) do
CoTaskMemFree(Pidls[I]);
end;
OleCheck(OleSetClipboard(obj));
OleCheck(OleFlushClipboard);
end;
However, I found that Windows Explorer will sometimes but not always allow CFSTR_SHELLIDLIST to be pasted into the source folder of a referenced file. I don't know what criteria is preventing Windows Explorer from pasting. Maybe some kind of permissions issue?
You should take Microsoft's advice:
Handling Shell Data Transfer Scenarios
Include as many formats as you can support. You generally do not know where the data object will be dropped. This practice improves the odds that the data object will contain a format that the drop target can accept.

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.

LsaOpenPolicy is throwing exception in my code. Why?

I got the following code from a newsgroup posting. Strangely, it isn't working for me in Delphi 2010; An exception is being thrown at the LsaOpenPolicy function call:
function AddLogonAsAService(ID: pchar): boolean;
const
Right: PChar = 'SeServiceLogonRight';
var
FResult: NTSTATUS;
//szSystemName: LPTSTR;
FObjectAttributes: TLSAObjectAttributes;
FPolicyHandle: LSA_HANDLE;
Server, Privilege: TLSAUnicodeString;
FSID: PSID;
cbSid: DWORD;
ReferencedDomain: LPTSTR;
cchReferencedDomain: DWORD;
peUse: SID_NAME_USE;
PrivilegeString: String;
begin
Result := false;
try
ZeroMemory(#FObjectAttributes, sizeof(FObjectAttributes));
Server.Buffer := nil;
Server.Length := 0;
Server.MaximumLength := 256;
PrivilegeString := Right; //or some other privilege
Privilege.Buffer := PChar(PrivilegeString);
Privilege.Length := 38;
Privilege.MaximumLength := 256;
FResult := LsaOpenPolicy(
#Server, //this machine, because the Buffer is NIL
#FObjectAttributes,
POLICY_ALL_ACCESS,
FPolicyHandle);
if FResult = STATUS_SUCCESS then begin
cbSid := 128;
cchReferencedDomain := 16;
GetMem(FSID, cbSid);
//FSID:=PSID(HeapAlloc(GetProcessHeap(), 0, cbSid));
GetMem(ReferencedDomain, cchReferencedDomain);
//ReferencedDomain := LPTSTR(HeapAlloc(GetProcessHeap(), 0, cchReferencedDomain * sizeof(ReferencedDomain^)));
if LookupAccountName(nil, ID, FSID, cbSid, ReferencedDomain,
cchReferencedDomain, peUse) then begin
FResult := LsaAddAccountRights(FPolicyHandle, FSID, #Privilege, 1);
Result := FResult = STATUS_SUCCESS;
end;
FreeMem(FSID, cbSid);
FreeMem(ReferencedDomain, cchReferencedDomain);
end;
except
Result := false;
end;
end;
Original posting may be found at Google Groups archive:
From: "andrew"
Newsgroups:
borland.public.delphi.winapi
Subject: NetUserAdd and assigning user
rights
Date: Tue, 25 Sep 2001 10:08:35 +1000
Thanks in advance for any answers.
According to the MSDN docs you should not use an LSA_UNICODE_STRING with the Buffer set to nil but pass nil instead: LsaOpenPolicy(nil, ...
/EDIT:
The code below works fine for me using Jedi Apilib so I think something might be wrong with your definition (maybe calling convention?), so please add this to your code.
Also you are specifying maximum buffer size of 256 in the LSA_UNICODE_STRING's which is incorrect, in the first case the maximum buffer is 0.
uses
JwaWinType, JwaNtSecApi;
procedure TForm1.Button1Click(Sender: TObject);
var
ObjectAttribs: LSA_OBJECT_ATTRIBUTES;
PolicyHandle: LSA_HANDLE;
nts: NTSTATUS;
begin
ZeroMemory(#ObjectAttribs, SizeOf(ObjectAttribs));
nts := LsaOpenPolicy(nil, ObjectAttribs, POLICY_ALL_ACCESS, PolicyHandle);
Memo1.Lines.Add(Format('nts=%.8x', [nts]));
end;
Fixed/changed function, tested on Win7 under D2009 (but should work on older/newer too). Of course app. must be running with admin rights.
uses
JwaWinNT, JwaWinType, JwaNtStatus, JwaNtSecApi, JwaLmCons;
function AddPrivilegeToAccount(AAccountName, APrivilege: String): DWORD;
var
lStatus: TNTStatus;
lObjectAttributes: TLsaObjectAttributes;
lPolicyHandle: TLsaHandle;
lPrivilege: TLsaUnicodeString;
lSid: PSID;
lSidLen: DWORD;
lTmpDomain: String;
lTmpDomainLen: DWORD;
lTmpSidNameUse: TSidNameUse;
{$IFDEF UNICODE}
lPrivilegeWStr: String;
{$ELSE}
lPrivilegeWStr: WideString;
{$ENDIF}
begin
ZeroMemory(#lObjectAttributes, SizeOf(lObjectAttributes));
lStatus := LsaOpenPolicy(nil, lObjectAttributes, POLICY_LOOKUP_NAMES, lPolicyHandle);
if lStatus <> STATUS_SUCCESS then
begin
Result := LsaNtStatusToWinError(lStatus);
Exit;
end;
try
lTmpDomainLen := JwaLmCons.DNLEN; // In 'clear code' this should be get by LookupAccountName
SetLength(lTmpDomain, lTmpDomainLen);
lSidLen := SECURITY_MAX_SID_SIZE;
GetMem(lSid, lSidLen);
try
if LookupAccountName(nil, PChar(AAccountName), lSid, lSidLen, PChar(lTmpDomain),
lTmpDomainLen, lTmpSidNameUse) then
begin
lPrivilegeWStr := APrivilege;
lPrivilege.Buffer := PWideChar(lPrivilegeWStr);
lPrivilege.Length := Length(lPrivilegeWStr) * SizeOf(Char);
lPrivilege.MaximumLength := lPrivilege.Length;
lStatus := LsaAddAccountRights(lPolicyHandle, lSid, #lPrivilege, 1);
Result := LsaNtStatusToWinError(lStatus);
end else
Result := GetLastError;
finally
FreeMem(lSid);
end;
finally
LsaClose(lPolicyHandle);
end;
end;
procedure TForm2.Button1Click(Sender: TObject);
var
lStatus: DWORD;
begin
lStatus := AddPrivilegeToAccount('Administrators'{or any account/group name}, 'SeServiceLogonRight');
if lStatus = ERROR_SUCCESS then
Caption := 'OK'
else
Caption := SysErrorMessage(lStatus);
end;

Resources