1 // This
file contains code paths
for Windows
95, Windows
2000 and Windows Vista
2 // to get a list
of processes that use a given
module (DLL
). For the processes
3 // that lock the
file, the process ID
, the
full path
to the executable
, and file
4 // description
is returned
. This information can
then used
to present the user a
5 // list
of processes
/ applications that she needs
to close before a module can
6 // be replaced
/ a replacement will take effect
.
8 // Since Windows Vista
, processes that
register with the
Restart Manager can be
9 // asked
to be restarted without any user interaction
. The
"Restartable" flag
in
10 // the
"ProcessEntry" indicates whether this
is supported
or not.
12 // Please note that this code only works
for modules
, not for files that are
13 // locked by processes
in other ways
, e
.g
. by opening them
for exclusive read /
16 // In contrast
to existing solutions like
[1] or [2], this one has the advantages
17 // of not requiring an
external DLLs
, being Open Source
and having support
for
18 // the Windows Vista
Restart Manager API
.
20 // [1] http
://www
.vincenzo
.net
/isxkb
/index
.php?title
=PSVince
21 // [2] http
://raz
-soft
.com
/display
-english
-posts
-only
/files-in-use
-extension
-for-inno
-setup
/
30 MAX_MODULE_NAME32
= 255;
34 ERROR_MORE_DATA
= 234;
35 INVALID_HANDLE_VALUE
= -1;
38 PROCESS_VM_READ
= $0010;
39 PROCESS_QUERY_INFORMATION
= $0400;
48 IdList
=array of DWORD
;
55 ProcessList
=array of ProcessEntry
;
57 function CloseHandle(hObject
:THandle
):Boolean;
58 external 'CloseHandle@Kernel32.dll';
60 // We need
to always use ANSI version
of this
function, see the bottom note
in GetFileDescription().
61 function GetFileVersionInfoSize(lptstrFilename
:AnsiString
;var lpdwHandle
:DWORD
):DWORD
;
62 external 'GetFileVersionInfoSizeA@Version.dll';
64 // We need
to always use ANSI version
of this
function, see the bottom note
in GetFileDescription().
65 function GetFileVersionInfo(lptstrFilename
:AnsiString
;dwHandle
,dwLen
:DWORD
;lpData
:array of Byte):Boolean;
66 external 'GetFileVersionInfoA@Version.dll';
68 // We need
to always use ANSI version
of this
function, see the bottom note
in GetFileDescription().
69 function VerQueryValue(pBlock
:array of Byte;lpSubBlock
:AnsiString
;var lplpBuffer
:PAnsiChar
;var puLen
:UINT
):Boolean;
70 external 'VerQueryValueA@Version.dll';
72 // Returns the
file description
as stored
in the VS_VERSION_INFO resource
. This
73 // is used
as the process name rather than using the window title
, as e
.g
. editors
74 // might display the current
file rather than the application name
in the title bar
.
75 function GetFileDescription(FileName
:String):String;
83 Language
,Codepage
,LanguageFB
,CodepageFB
:WORD;
85 Size
:=GetFileVersionInfoSize(Filename
,Dummy
);
90 SetArrayLength(Info
,Size
);
91 if not GetFileVersionInfo(FileName
,0,Size
,Info
) then begin
95 // Query the language
and codepage
in order
to query locale specific strings
.
96 if not VerQueryValue(Info
,'\VarFileInfo\Translation',Buffer
,BufLen
) then begin
100 // This will fail
if "Buffer" contains inner #
0 characters
, in which
case
101 // the
"else" branch below
is taken
, and we are guessing some values
.
104 if Length(BufStr
)>=BufLen
then begin
107 // Decode the WORDs from the
string.
108 Language
:=Ord(BufStr
[Offset
+1]);
109 Language
:=(Language
shl 8)+Ord(BufStr
[Offset
]);
111 Codepage
:=Ord(BufStr
[Offset
+3]);
112 Codepage
:=(Codepage
shl 8)+Ord(BufStr
[Offset
+2]);
114 // Use the first entry
or English
as a fallback
.
115 if (Offset
=1) or (Language
=$0409) then begin
116 LanguageFB
:=Language
;
117 CodepageFB
:=Codepage
;
121 until (Language
=GetUILanguage
) or (Offset
>BufLen
);
123 // If we did
not find the UI language
, use the fallback
.
124 if Language
<>GetUILanguage
then begin
125 Language
:=LanguageFB
;
126 Codepage
:=CodepageFB
;
129 Language
:=$0000; // Process Default Language
130 Codepage
:=$04b0; // 1200 (UTF
-16, Little
-Endian
)
131 LanguageFB
:=$0000; // Process Default Language
132 CodepageFB
:=$04e4; // 1252 (West European
, Latin
)
135 // Query the
file description
.
136 BufStr
:=Format('\StringFileInfo\%.4x%.4x\FileDescription',[Language
,Codepage
]);
137 if not VerQueryValue(Info
,BufStr
,Buffer
,BufLen
) then begin
138 // Try the fallback
if the first choice failed
.
139 BufStr
:=Format('\StringFileInfo\%.4x%.4x\FileDescription',[LanguageFB
,CodepageFB
]);
140 if not VerQueryValue(Info
,BufStr
,Buffer
,BufLen
) then begin
145 // As we cannot cast PAnsiChar
to a Unicode
string here
, we always
146 // need
to use the ANSI functions
for VerQueryValue etc
.
151 Code for Windows 95 and above
155 TH32CS_SNAPPROCESS
= $0002;
156 TH32CS_SNAPMODULE
= $0008;
157 TH32CS_SNAPMODULE32
= $0010;
160 PROCESSENTRY32
=record
161 dwSize
,cntUsage
,th32ProcessID
:DWORD
;
162 th32DefaultHeapID
:ULONG_PTR
;
163 th32ModuleID
,cntThreads
,th32ParentProcessID
:DWORD
;
166 szExeFile
:array[1..MAX_PATH
] of Char;
169 dwSize
,th32ModuleID
,th32ProcessID
,GlblcntUsage
,ProccntUsage
:DWORD
;
170 modBaseAddr
:BYTE_PTR
;
173 szModule
:array[1..MAX_MODULE_NAME32
+1] of Char;
174 szExePath
:array[1..MAX_PATH
] of Char;
177 function CreateToolhelp32Snapshot(dwFlags
,th32ProcessID
:DWORD
):THandle
;
178 external 'CreateToolhelp32Snapshot@Kernel32.dll stdcall delayload';
180 function Process32First(hSnapshot
:THandle
;var lppe
:PROCESSENTRY32
):Boolean;
182 external 'Process32FirstW@Kernel32.dll stdcall delayload';
184 external 'Process32FirstA@Kernel32.dll stdcall delayload';
187 function Process32Next(hSnapshot
:THandle
;var lppe
:PROCESSENTRY32
):Boolean;
189 external 'Process32NextW@Kernel32.dll stdcall delayload';
191 external 'Process32NextA@Kernel32.dll stdcall delayload';
194 function Module32First(hSnapshot
:THandle
;var lpme
:MODULEENTRY32
):Boolean;
196 external 'Module32FirstW@Kernel32.dll stdcall delayload';
198 external 'Module32FirstA@Kernel32.dll stdcall delayload';
201 function Module32Next(hSnapshot
:THandle
;var lpme
:MODULEENTRY32
):Boolean;
203 external 'Module32NextW@Kernel32.dll stdcall delayload';
205 external 'Module32NextA@Kernel32.dll stdcall delayload';
208 // Returns a list
of running processes that currectly use the specified module
.
209 // The module may be a filename
to a DLL
with or without path
.
210 function FindProcessesUsingModule_Win95(Module
:String;var Processes
:ProcessList
):Boolean;
214 ProcEntry
:PROCESSENTRY32
;
216 ModEntry
:MODULEENTRY32
;
217 ModPath
,ProcPath
:String;
220 SetArrayLength(Processes
,0);
223 ProcSnap
:=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS
,0);
224 if ProcSnap
=INVALID_HANDLE_VALUE
then begin
228 // Compare strings
case-insensitively
.
229 Module
:=Lowercase(Module
);
231 // Loop over the processes
in the
system.
232 ProcEntry
.dwSize
:=SizeOf(ProcEntry
);
233 Success
:=Process32First(ProcSnap
,ProcEntry
);
235 while Success
do begin
236 if ProcEntry
.th32ProcessID
>0 then begin
237 ModSnap
:=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE
or TH32CS_SNAPMODULE32
,ProcEntry
.th32ProcessID
);
238 if ModSnap
<>INVALID_HANDLE_VALUE
then begin
239 // Loop over the modules
in the process
.
240 ModEntry
.dwSize
:=SizeOf(ModEntry
);
241 Success
:=Module32First(ModSnap
,ModEntry
);
243 // Assume the first module always
is the executable itself
.
244 ProcPath
:=ArrayToString(ModEntry
.szExePath
);
245 Success
:=Module32Next(ModSnap
,ModEntry
);
247 while Success
do begin
248 ModPath
:=ArrayToString(ModEntry
.szExePath
);
249 if Pos(Module
,Lowercase(ModPath
))>0 then begin
250 i
:=GetArrayLength(Processes
);
251 SetArrayLength(Processes
,i
+1);
252 Processes
[i
].ID
:=ProcEntry
.th32ProcessID
;
253 Processes
[i
].Path
:=ProcPath
;
254 Processes
[i
].Name
:=GetFileDescription(ProcPath
);
255 if Length(Processes
[i
].Name
)=0 then begin
256 Processes
[i
].Name
:=ExtractFileName(ProcPath
);
258 Processes
[i
].Restartable
:=False;
261 Success
:=Module32Next(ModSnap
,ModEntry
);
264 CloseHandle(ModSnap
);
268 Success
:=Process32Next(ProcSnap
,ProcEntry
);
271 CloseHandle(ProcSnap
);
277 Code for Windows 2000 and above
280 function EnumProcesses(pProcessIds
:IdList
;cb
:DWORD
;var pBytesReturned
:DWORD
):Boolean;
281 external 'EnumProcesses@Psapi.dll stdcall delayload';
283 function EnumProcessModules(hProcess
:THandle
;lphModule
:IdList
;cb
:DWORD
;var lpcbNeeded
:DWORD
):Boolean;
284 external 'EnumProcessModules@Psapi.dll stdcall delayload';
286 // Wrapper
for EnumProcesses() that returns process IDs
as a list
.
287 function GetProcessList(var List
:IdList
):Boolean;
292 // Start
with space
for 64 processes
.
293 Bytes
:=32*SizeOf(Bytes
);
297 SetArrayLength(List
,Size
/SizeOf(Bytes
));
298 Result
:=EnumProcesses(List
,Size
,Bytes
);
299 until (Bytes
<Size
) or (not Result
);
302 SetArrayLength(List
,Bytes
/SizeOf(Bytes
));
304 SetArrayLength(List
,0);
308 // Wrapper
for EnumProcessModules() that returns module IDs
as a list
.
309 function GetModuleList(Process
:THandle
;var List
:IdList
):Boolean;
314 // Start
with space
for 64 modules
.
315 Bytes
:=32*SizeOf(Bytes
);
319 SetArrayLength(List
,Size
/SizeOf(Bytes
));
320 Result
:=EnumProcessModules(Process
,List
,Size
,Bytes
);
321 until (Bytes
<Size
) or (not Result
);
324 SetArrayLength(List
,Bytes
/SizeOf(Bytes
));
326 SetArrayLength(List
,0);
330 function OpenProcess(dwDesiredAccess
:DWORD
;bInheritHandle
:BOOL
;dwProcessId
:DWORD
):THandle
;
331 external 'OpenProcess@Kernel32.dll stdcall delayload';
333 function GetModuleFileNameEx(hProcess
:THandle
;hModule
:HMODULE
;lpFilename
:String;nSize
:DWORD
):DWORD
;
335 external 'GetModuleFileNameExW@Psapi.dll stdcall delayload';
337 external 'GetModuleFileNameExA@Psapi.dll stdcall delayload';
340 // Returns a list
of running processes that currectly use the specified module
.
341 // The module may be a filename
to a DLL
with or without path
.
342 function FindProcessesUsingModule_Win2000(Module
:String;var Processes
:ProcessList
):Boolean;
344 ProcList
,ModList
:IdList
;
350 SetArrayLength(Processes
,0);
353 if not GetProcessList(ProcList
) then begin
357 // Compare strings
case-insensitively
.
358 Module
:=Lowercase(Module
);
360 for p
:=0 to GetArraylength(ProcList
)-1 do begin
361 Process
:=OpenProcess(PROCESS_QUERY_INFORMATION
or PROCESS_VM_READ
,False,ProcList
[p
]);
362 if Process
<>0 then begin
363 if GetModuleList(Process
,ModList
) then begin
364 for m
:=0 to GetArraylength(ModList
)-1 do begin
365 SetLength(Path
,MAX_PATH
);
366 PathLength
:=GetModuleFileNameEx(Process
,ModList
[m
],Path
,MAX_PATH
);
367 SetLength(Path
,PathLength
);
369 if Pos(Module
,Lowercase(Path
))>0 then begin
370 SetLength(Path
,MAX_PATH
);
371 PathLength
:=GetModuleFileNameEx(Process
,0,Path
,MAX_PATH
);
372 SetLength(Path
,PathLength
);
374 i
:=GetArrayLength(Processes
);
375 SetArrayLength(Processes
,i
+1);
376 Processes
[i
].ID
:=ProcList
[p
];
377 Processes
[i
].Path
:=Path
;
378 Processes
[i
].Name
:=GetFileDescription(Path
);
379 if Length(Processes
[i
].Name
)=0 then begin
380 Processes
[i
].Name
:=ExtractFileName(Path
);
382 Processes
[i
].Restartable
:=False;
386 CloseHandle(Process
);
394 Code for Windows Vista and above
398 CCH_RM_SESSION_KEY
= 32;
399 CCH_RM_MAX_APP_NAME
= 255;
400 CCH_RM_MAX_SVC_NAME
= 63;
410 RmStatusUnknown
= $0000;
411 RmStatusRunning
= $0001;
412 RmStatusStopped
= $0002;
413 RmStatusStoppedOther
= $0004;
414 RmStatusRestarted
= $0008;
415 RmStatusErrorOnStop
= $0010;
416 RmStatusErrorOnRestart
= $0020;
417 RmStatusShutdownMasked
= $0040;
418 RmStatusRestartMasked
= $0080;
421 SessionKey
=array[1..CCH_RM_SESSION_KEY
+1] of Char;
424 dwLowDateTime
,dwHighDateTime
:DWORD
;
426 RM_UNIQUE_PROCESS
=record
428 ProcessStartTime
:FILETIME
;
431 RM_PROCESS_INFO
=record
432 Process
:RM_UNIQUE_PROCESS
;
433 strAppName
:array[1..CCH_RM_MAX_APP_NAME
+1] of Char;
434 strServiceShortName
:array[1..CCH_RM_MAX_SVC_NAME
+1] of Char;
435 ApplicationType
:RM_APP_TYPE
;
441 function RmStartSession(var pSessionHandle
:DWORD
;dwSessionFlags
:DWORD
;strSessionKey
:SessionKey
):DWORD
;
442 external 'RmStartSession@Rstrtmgr.dll stdcall delayload';
444 function RmEndSession(dwSessionHandle
:DWORD
):DWORD
;
445 external 'RmEndSession@Rstrtmgr.dll stdcall delayload';
447 function RmRegisterResources(dwSessionHandle
:DWORD
;hFiles
:UINT
;rgsFilenames
:TArrayOfString
;nApplications
:UINT
;rgApplications
:array of RM_UNIQUE_PROCESS
;nServices
:UINT
;rgsServiceNames
:TArrayOfString
):DWORD
;
448 external 'RmRegisterResources@Rstrtmgr.dll stdcall delayload';
450 function RmGetList(dwSessionHandle
:DWORD
;var pnProcInfoNeeded
:UINT
;var pnProcInfo
:UINT
;rgAffectedApps
:array of RM_PROCESS_INFO
;lpdwRebootReasons
:IdList
):DWORD
;
451 external 'RmGetList@Rstrtmgr.dll stdcall delayload';
453 // Returns a list
of running processes that currectly use the specified module
.
454 // The module has
to be a
full path
and filename
to a DLL
.
455 function FindProcessesUsingModule_WinVista(Module
:String;var Processes
:ProcessList
):Boolean;
459 Files:TArrayOfString
;
460 Apps
:array of RM_UNIQUE_PROCESS
;
461 Services
:TArrayOfString
;
466 AppList
:array of RM_PROCESS_INFO
;
470 SetArrayLength(Processes
,0);
473 // We require the
full path
to the module here
.
474 if not FileExists(Module
) then begin
478 // NULL
-terminate the
array of chars
.
479 Name
[CCH_RM_SESSION_KEY
+1]:=#
0;
480 if RmStartSession(Handle
,0,Name
)<>ERROR_SUCCESS
then begin
484 SetArrayLength(Files,1);
486 if RmRegisterResources(Handle
,GetArrayLength(Files),Files,0,Apps
,0,Services
)=ERROR_SUCCESS
then begin
487 // Reallocate the arrays
until they are large enough
to hold the process information
.
491 SetArrayLength(AppList
,Have
);
492 SetArrayLength(ReasonList
,Have
);
493 Success
:=RmGetList(Handle
,Needed
,Have
,AppList
,ReasonList
);
494 until (Have
>=Needed
) and (Success
<>ERROR_MORE_DATA
);
496 if (Success
=ERROR_SUCCESS
) and (Needed
>0) then begin
497 for i
:=0 to Needed
-1 do begin
498 // Optionally
, only list non
-critical stand
-alone processes that
do not require a forced shutdown
.
499 //if (AppList
[i
].ApplicationType
=RmMainWindow
) or (AppList
[i
].ApplicationType
=RmExplorer
) or (AppList
[i
].ApplicationType
=RmConsole
) then begin
500 Process
:=OpenProcess(PROCESS_QUERY_INFORMATION
or PROCESS_VM_READ
,False,AppList
[i
].Process
.dwProcessId
);
501 if Process
<>0 then begin
502 SetLength(Path
,MAX_PATH
);
503 PathLength
:=GetModuleFileNameEx(Process
,0,Path
,MAX_PATH
);
504 SetLength(Path
,PathLength
);
506 Have
:=GetArrayLength(Processes
);
507 SetArrayLength(Processes
,Have
+1);
508 Processes
[Have
].ID
:=AppList
[i
].Process
.dwProcessId
;
509 Processes
[Have
].Path
:=Path
;
510 Processes
[Have
].Name
:=ArrayToString(AppList
[i
].strAppName
);
511 Processes
[Have
].Restartable
:=AppList
[i
].bRestartable
;
513 CloseHandle(Process
);
521 RmEndSession(Handle
);
528 function FindProcessesUsingModule(Module
:String;var Processes
:ProcessList
):Boolean;
530 Version
:TWindowsVersion
;
532 GetWindowsVersionEx(Version
);
534 if (Version
.Major
<5) or (not Version
.NTPlatform
) then begin
535 Result
:=FindProcessesUsingModule_Win95(Module
,Processes
);
536 end else if Version
.Major
<6 then begin
537 Result
:=FindProcessesUsingModule_Win2000(Module
,Processes
);
539 Result
:=FindProcessesUsingModule_WinVista(Module
,Processes
);