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 FindProcessesUsingModules_Win95(Modules
:TArrayOfString
;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 for i
:=0 to GetArraylength(Modules
)-1 do begin
230 Modules
[i
]:=Lowercase(Modules
[i
]);
233 // Loop over the processes
in the
system.
234 ProcEntry
.dwSize
:=SizeOf(ProcEntry
);
235 Success
:=Process32First(ProcSnap
,ProcEntry
);
237 while Success
do begin
238 if ProcEntry
.th32ProcessID
>0 then begin
239 ModSnap
:=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE
or TH32CS_SNAPMODULE32
,ProcEntry
.th32ProcessID
);
240 if ModSnap
<>INVALID_HANDLE_VALUE
then begin
241 // Loop over the modules
in the process
.
242 ModEntry
.dwSize
:=SizeOf(ModEntry
);
243 Success
:=Module32First(ModSnap
,ModEntry
);
245 // Assume the first module always
is the executable itself
.
246 ProcPath
:=ArrayToString(ModEntry
.szExePath
);
247 Success
:=Module32Next(ModSnap
,ModEntry
);
249 while Success
do begin
250 ModPath
:=ArrayToString(ModEntry
.szExePath
);
252 for i
:=0 to GetArraylength(Modules
)-1 do begin
253 if Pos(Modules
[i
],Lowercase(ModPath
))>0 then begin
254 i
:=GetArrayLength(Processes
);
255 SetArrayLength(Processes
,i
+1);
256 Processes
[i
].ID
:=ProcEntry
.th32ProcessID
;
257 Processes
[i
].Path
:=ProcPath
;
258 Processes
[i
].Name
:=GetFileDescription(ProcPath
);
259 if Length(Processes
[i
].Name
)=0 then begin
260 Processes
[i
].Name
:=ExtractFileName(ProcPath
);
262 Processes
[i
].Restartable
:=False;
266 Success
:=Module32Next(ModSnap
,ModEntry
);
269 CloseHandle(ModSnap
);
273 Success
:=Process32Next(ProcSnap
,ProcEntry
);
276 CloseHandle(ProcSnap
);
281 // Returns a list
of running processes that currectly use the specified module
.
282 // The module may be a filename
to a DLL
with or without path
.
283 function FindProcessesUsingModule_Win95(Module
:String;var Processes
:ProcessList
):Boolean;
285 Modules
:TArrayOfString
;
287 SetArrayLength(Modules
,1);
289 Result
:=FindProcessesUsingModules_Win95(Modules
,Processes
);
293 Code for Windows 2000 and above
296 function EnumProcesses(pProcessIds
:IdList
;cb
:DWORD
;var pBytesReturned
:DWORD
):Boolean;
297 external 'EnumProcesses@Psapi.dll stdcall delayload';
299 function EnumProcessModules(hProcess
:THandle
;lphModule
:IdList
;cb
:DWORD
;var lpcbNeeded
:DWORD
):Boolean;
300 external 'EnumProcessModules@Psapi.dll stdcall delayload';
302 // Wrapper
for EnumProcesses() that returns process IDs
as a list
.
303 function GetProcessList(var List
:IdList
):Boolean;
308 // Start
with space
for 64 processes
.
309 Bytes
:=32*SizeOf(Bytes
);
313 SetArrayLength(List
,Size
/SizeOf(Bytes
));
314 Result
:=EnumProcesses(List
,Size
,Bytes
);
315 until (Bytes
<Size
) or (not Result
);
318 SetArrayLength(List
,Bytes
/SizeOf(Bytes
));
320 SetArrayLength(List
,0);
324 // Wrapper
for EnumProcessModules() that returns module IDs
as a list
.
325 function GetModuleList(Process
:THandle
;var List
:IdList
):Boolean;
330 // Start
with space
for 64 modules
.
331 Bytes
:=32*SizeOf(Bytes
);
335 SetArrayLength(List
,Size
/SizeOf(Bytes
));
336 Result
:=EnumProcessModules(Process
,List
,Size
,Bytes
);
337 until (Bytes
<Size
) or (not Result
);
340 SetArrayLength(List
,Bytes
/SizeOf(Bytes
));
342 SetArrayLength(List
,0);
346 function OpenProcess(dwDesiredAccess
:DWORD
;bInheritHandle
:BOOL
;dwProcessId
:DWORD
):THandle
;
347 external 'OpenProcess@Kernel32.dll stdcall delayload';
349 function GetModuleFileNameEx(hProcess
:THandle
;hModule
:HMODULE
;lpFilename
:String;nSize
:DWORD
):DWORD
;
351 external 'GetModuleFileNameExW@Psapi.dll stdcall delayload';
353 external 'GetModuleFileNameExA@Psapi.dll stdcall delayload';
356 // Returns a list
of running processes that currectly use one
of the specified modules
.
357 // Each module may be a filename
to a DLL
with or without path
.
358 function FindProcessesUsingModules_Win2000(Modules
:TArrayOfString
;var Processes
:ProcessList
):Boolean;
360 ProcList
,ModList
:IdList
;
366 SetArrayLength(Processes
,0);
369 if not GetProcessList(ProcList
) then begin
373 // Compare strings
case-insensitively
.
374 for i
:=0 to GetArraylength(Modules
)-1 do begin
375 Modules
[i
]:=Lowercase(Modules
[i
]);
378 for p
:=0 to GetArraylength(ProcList
)-1 do begin
379 Process
:=OpenProcess(PROCESS_QUERY_INFORMATION
or PROCESS_VM_READ
,False,ProcList
[p
]);
380 if Process
<>0 then begin
381 if GetModuleList(Process
,ModList
) then begin
382 for m
:=0 to GetArraylength(ModList
)-1 do begin
383 SetLength(Path
,MAX_PATH
);
384 PathLength
:=GetModuleFileNameEx(Process
,ModList
[m
],Path
,MAX_PATH
);
385 SetLength(Path
,PathLength
);
387 for i
:=0 to GetArraylength(Modules
)-1 do begin
388 if Pos(Modules
[i
],Lowercase(Path
))>0 then begin
389 SetLength(Path
,MAX_PATH
);
390 PathLength
:=GetModuleFileNameEx(Process
,0,Path
,MAX_PATH
);
391 SetLength(Path
,PathLength
);
393 i
:=GetArrayLength(Processes
);
394 SetArrayLength(Processes
,i
+1);
395 Processes
[i
].ID
:=ProcList
[p
];
396 Processes
[i
].Path
:=Path
;
397 Processes
[i
].Name
:=GetFileDescription(Path
);
398 if Length(Processes
[i
].Name
)=0 then begin
399 Processes
[i
].Name
:=ExtractFileName(Path
);
401 Processes
[i
].Restartable
:=False;
406 CloseHandle(Process
);
413 // Returns a list
of running processes that currectly use the specified module
.
414 // The module may be a filename
to a DLL
with or without path
.
415 function FindProcessesUsingModule_Win2000(Module
:String;var Processes
:ProcessList
):Boolean;
417 Modules
:TArrayOfString
;
419 SetArrayLength(Modules
,1);
421 Result
:=FindProcessesUsingModules_Win2000(Modules
,Processes
);
425 Code for Windows Vista and above
429 CCH_RM_SESSION_KEY
= 32;
430 CCH_RM_MAX_APP_NAME
= 255;
431 CCH_RM_MAX_SVC_NAME
= 63;
441 RmStatusUnknown
= $0000;
442 RmStatusRunning
= $0001;
443 RmStatusStopped
= $0002;
444 RmStatusStoppedOther
= $0004;
445 RmStatusRestarted
= $0008;
446 RmStatusErrorOnStop
= $0010;
447 RmStatusErrorOnRestart
= $0020;
448 RmStatusShutdownMasked
= $0040;
449 RmStatusRestartMasked
= $0080;
452 SessionKey
=array[1..CCH_RM_SESSION_KEY
+1] of Char;
455 dwLowDateTime
,dwHighDateTime
:DWORD
;
457 RM_UNIQUE_PROCESS
=record
459 ProcessStartTime
:FILETIME
;
462 RM_PROCESS_INFO
=record
463 Process
:RM_UNIQUE_PROCESS
;
464 strAppName
:array[1..CCH_RM_MAX_APP_NAME
+1] of Char;
465 strServiceShortName
:array[1..CCH_RM_MAX_SVC_NAME
+1] of Char;
466 ApplicationType
:RM_APP_TYPE
;
472 function RmStartSession(var pSessionHandle
:DWORD
;dwSessionFlags
:DWORD
;strSessionKey
:SessionKey
):DWORD
;
473 external 'RmStartSession@Rstrtmgr.dll stdcall delayload';
475 function RmEndSession(dwSessionHandle
:DWORD
):DWORD
;
476 external 'RmEndSession@Rstrtmgr.dll stdcall delayload';
478 function RmRegisterResources(dwSessionHandle
:DWORD
;hFiles
:UINT
;rgsFilenames
:TArrayOfString
;nApplications
:UINT
;rgApplications
:array of RM_UNIQUE_PROCESS
;nServices
:UINT
;rgsServiceNames
:TArrayOfString
):DWORD
;
479 external 'RmRegisterResources@Rstrtmgr.dll stdcall delayload';
481 function RmGetList(dwSessionHandle
:DWORD
;var pnProcInfoNeeded
:UINT
;var pnProcInfo
:UINT
;rgAffectedApps
:array of RM_PROCESS_INFO
;lpdwRebootReasons
:IdList
):DWORD
;
482 external 'RmGetList@Rstrtmgr.dll stdcall delayload';
484 // Returns a list
of running processes that currectly use one
of the specified modules
.
485 // Each module has
to be a
full path
and filename
to a DLL
.
486 function FindProcessesUsingModules_WinVista(Modules
:TArrayOfString
;var Processes
:ProcessList
):Boolean;
490 Apps
:array of RM_UNIQUE_PROCESS
;
491 Services
:TArrayOfString
;
496 AppList
:array of RM_PROCESS_INFO
;
500 SetArrayLength(Processes
,0);
503 // NULL
-terminate the
array of chars
.
504 Name
[CCH_RM_SESSION_KEY
+1]:=#
0;
505 if RmStartSession(Handle
,0,Name
)<>ERROR_SUCCESS
then begin
509 if RmRegisterResources(Handle
,GetArrayLength(Modules
),Modules
,0,Apps
,0,Services
)=ERROR_SUCCESS
then begin
510 // Reallocate the arrays
until they are large enough
to hold the process information
.
514 SetArrayLength(AppList
,Have
);
515 SetArrayLength(ReasonList
,Have
);
516 Success
:=RmGetList(Handle
,Needed
,Have
,AppList
,ReasonList
);
517 until (Have
>=Needed
) and (Success
<>ERROR_MORE_DATA
);
519 if (Success
=ERROR_SUCCESS
) and (Needed
>0) then begin
520 for i
:=0 to Needed
-1 do begin
521 // Optionally
, only list non
-critical stand
-alone processes that
do not require a forced shutdown
.
522 //if (AppList
[i
].ApplicationType
=RmMainWindow
) or (AppList
[i
].ApplicationType
=RmExplorer
) or (AppList
[i
].ApplicationType
=RmConsole
) then begin
523 Process
:=OpenProcess(PROCESS_QUERY_INFORMATION
or PROCESS_VM_READ
,False,AppList
[i
].Process
.dwProcessId
);
524 if Process
<>0 then begin
525 SetLength(Path
,MAX_PATH
);
526 PathLength
:=GetModuleFileNameEx(Process
,0,Path
,MAX_PATH
);
527 SetLength(Path
,PathLength
);
529 Have
:=GetArrayLength(Processes
);
530 SetArrayLength(Processes
,Have
+1);
531 Processes
[Have
].ID
:=AppList
[i
].Process
.dwProcessId
;
532 Processes
[Have
].Path
:=Path
;
533 Processes
[Have
].Name
:=ArrayToString(AppList
[i
].strAppName
);
534 Processes
[Have
].Restartable
:=AppList
[i
].bRestartable
;
536 CloseHandle(Process
);
544 RmEndSession(Handle
);
547 // Returns a list
of running processes that currectly use the specified module
.
548 // The module has
to be a
full path
and filename
to a DLL
.
549 function FindProcessesUsingModule_WinVista(Module
:String;var Processes
:ProcessList
):Boolean;
551 Modules
:TArrayOfString
;
553 SetArrayLength(Modules
,1);
555 Result
:=FindProcessesUsingModules_WinVista(Modules
,Processes
);
562 // Returns a list
of running processes that currectly use one
of the specified modules
.
563 // Automatically calls the best
implementation for the running OS
.
564 function FindProcessesUsingModules(Modules
:TArrayOfString
;var Processes
:ProcessList
):Boolean;
566 Version
:TWindowsVersion
;
568 GetWindowsVersionEx(Version
);
570 if (Version
.Major
<5) or (not Version
.NTPlatform
) then begin
571 Result
:=FindProcessesUsingModules_Win95(Modules
,Processes
);
572 end else if Version
.Major
<6 then begin
573 Result
:=FindProcessesUsingModules_Win2000(Modules
,Processes
);
575 Result
:=FindProcessesUsingModules_WinVista(Modules
,Processes
);
579 // Returns a list
of running processes that currectly use the specified module
.
580 // Automatically calls the best
implementation for the running OS
.
581 function FindProcessesUsingModule(Module
:String;var Processes
:ProcessList
):Boolean;
583 Version
:TWindowsVersion
;
585 GetWindowsVersionEx(Version
);
587 if (Version
.Major
<5) or (not Version
.NTPlatform
) then begin
588 Result
:=FindProcessesUsingModule_Win95(Module
,Processes
);
589 end else if Version
.Major
<6 then begin
590 Result
:=FindProcessesUsingModule_Win2000(Module
,Processes
);
592 Result
:=FindProcessesUsingModule_WinVista(Module
,Processes
);
600 // Tries
to replace an
in-use
file, e
.g
. a registered shell extension
, by
601 // renaming it
and then renaming the new
file to the original name
. Optionally
,
602 // performs (un
-)registering via regsvr32
.
603 function ReplaceInUseFile(CurFile
,NewFile
:String;Register:Boolean):Boolean;
605 CurFilePath
,CurFileName
,NewFileName
:String;
606 CurFileStem
,CurFileTemp
:String;
607 UnregisterFailed
,RenameFailed
:Boolean;
612 // Note that CurFile may
not exist
, in which
case NewFile
is just renamed
.
613 if not FileExists(NewFile
) then begin
617 CurFilePath
:=ExtractFilePath(CurFile
);
618 CurFileName
:=ExtractFileName(CurFile
);
619 NewFileName
:=ExtractFileName(NewFile
);
621 // Get the
file name without extension
or period
and use that
as a suffix
622 // for the temporary
file.
623 CurFileStem
:=ChangeFileExt(CurFileName
,'');
624 CurFileTemp
:=GenerateUniqueName(CurFilePath
,'.'+CurFileStem
);
626 // Clean
-up by trying
to delete any previously renamed temporary
files.
627 DelTree(CurFilePath
+'\*.'+CurFileStem
,False,True,False);
629 UnregisterFailed
:=False;
632 if FileExists(CurFile
) then begin
633 if Register and (not UnregisterServer(Is64BitInstallMode
,CurFile
,False)) then begin
634 UnregisterFailed
:=True;
637 if (not DeleteFile(CurFile
)) and (not RenameFile(CurFile
,CurFileTemp
)) then begin
642 if not RenameFile(NewFile
,CurFile
) then begin
643 Msg
:='Unable to install a new version of "'+CurFileName
+'". ' +
644 'Please finish the installation manually by following theses steps on the command line:' + #
13 + #
13;
645 if FileExists(CurFile
) then begin
646 if UnregisterFailed
then begin
647 Msg
:= Msg
+ '- run "regsvr32 /u ' + CurFileName
+ '",' + #
13;
649 if RenameFailed
then begin
650 Msg
:= Msg
+ '- rename "' + CurFileName
+ '" to something else,' + #
13;
653 Msg
:= Msg
+ '- rename "' + NewFileName
+ '" to "' + CurFileName
+ '",' + #
13;
654 Msg
:= Msg
+ '- run "regsvr32 ' + CurFileName
+ '".';
656 MsgBox(Msg
,mbError
,MB_OK
);
658 if Register then begin
659 RegisterServer(Is64BitInstallMode
,CurFile
,False);