Installer: Add code to detect which process uses a given module
[msysgit/mtrensch.git] / share / WinGit / modules.inc.iss
blob6dc90cbd27ea6dec3e00ab0e9c4782247f74d5e0
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.
7 //
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 /
14 // write access.
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/
24 Common code
27 const
28 // General constants.
29 MAX_PATH = 260;
30 MAX_MODULE_NAME32 = 255;
32 // Return codes.
33 ERROR_SUCCESS = 0;
34 ERROR_MORE_DATA = 234;
35 INVALID_HANDLE_VALUE = -1;
37 // For OpenProcess().
38 PROCESS_VM_READ = $0010;
39 PROCESS_QUERY_INFORMATION = $0400;
41 type
42 HMODULE = DWORD;
43 LONG = Longint;
44 ULONG = Cardinal;
45 BYTE_PTR = DWORD;
46 ULONG_PTR = DWORD;
48 IdList=array of DWORD;
50 ProcessEntry=record
51 ID:DWORD;
52 Path,Name:String;
53 Restartable:Boolean;
54 end;
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;
76 var
77 Dummy,Size:DWORD;
78 Info:array of Byte;
79 Buffer:PAnsiChar;
80 BufLen:UINT;
81 BufStr:String;
82 Offset:Integer;
83 Language,Codepage,LanguageFB,CodepageFB:WORD;
84 begin
85 Size:=GetFileVersionInfoSize(Filename,Dummy);
86 if Size=0 then begin
87 Exit;
88 end;
90 SetArrayLength(Info,Size);
91 if not GetFileVersionInfo(FileName,0,Size,Info) then begin
92 Exit;
93 end;
95 // Query the language and codepage in order to query locale specific strings.
96 if not VerQueryValue(Info,'\VarFileInfo\Translation',Buffer,BufLen) then begin
97 Exit;
98 end;
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.
102 BufStr:=Buffer;
104 if Length(BufStr)>=BufLen then begin
105 Offset:=1;
106 repeat
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;
118 end;
120 Offset:=Offset+4;
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;
127 end;
128 end else begin
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)
133 end;
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
141 Exit;
142 end;
143 end;
145 // As we cannot cast PAnsiChar to a Unicode string here, we always
146 // need to use the ANSI functions for VerQueryValue etc.
147 Result:=Buffer;
148 end;
151 Code for Windows 95 and above
154 const
155 TH32CS_SNAPPROCESS = $0002;
156 TH32CS_SNAPMODULE = $0008;
157 TH32CS_SNAPMODULE32 = $0010;
159 type
160 PROCESSENTRY32=record
161 dwSize,cntUsage,th32ProcessID:DWORD;
162 th32DefaultHeapID:ULONG_PTR;
163 th32ModuleID,cntThreads,th32ParentProcessID:DWORD;
164 pcPriClassBase:LONG;
165 dwFlags:DWORD;
166 szExeFile:array[1..MAX_PATH] of Char;
167 end;
168 MODULEENTRY32=record
169 dwSize,th32ModuleID,th32ProcessID,GlblcntUsage,ProccntUsage:DWORD;
170 modBaseAddr:BYTE_PTR;
171 modBaseSize:DWORD;
172 hModule:HMODULE;
173 szModule:array[1..MAX_MODULE_NAME32+1] of Char;
174 szExePath:array[1..MAX_PATH] of Char;
175 end;
177 function CreateToolhelp32Snapshot(dwFlags,th32ProcessID:DWORD):THandle;
178 external 'CreateToolhelp32Snapshot@Kernel32.dll stdcall delayload';
180 function Process32First(hSnapshot:THandle;var lppe:PROCESSENTRY32):Boolean;
181 #ifdef UNICODE
182 external 'Process32FirstW@Kernel32.dll stdcall delayload';
183 #else
184 external 'Process32FirstA@Kernel32.dll stdcall delayload';
185 #endif
187 function Process32Next(hSnapshot:THandle;var lppe:PROCESSENTRY32):Boolean;
188 #ifdef UNICODE
189 external 'Process32NextW@Kernel32.dll stdcall delayload';
190 #else
191 external 'Process32NextA@Kernel32.dll stdcall delayload';
192 #endif
194 function Module32First(hSnapshot:THandle;var lpme:MODULEENTRY32):Boolean;
195 #ifdef UNICODE
196 external 'Module32FirstW@Kernel32.dll stdcall delayload';
197 #else
198 external 'Module32FirstA@Kernel32.dll stdcall delayload';
199 #endif
201 function Module32Next(hSnapshot:THandle;var lpme:MODULEENTRY32):Boolean;
202 #ifdef UNICODE
203 external 'Module32NextW@Kernel32.dll stdcall delayload';
204 #else
205 external 'Module32NextA@Kernel32.dll stdcall delayload';
206 #endif
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;
212 Success:Boolean;
213 ProcSnap:THandle;
214 ProcEntry:PROCESSENTRY32;
215 ModSnap:THandle;
216 ModEntry:MODULEENTRY32;
217 ModPath,ProcPath:String;
218 i:Longint;
219 begin
220 SetArrayLength(Processes,0);
221 Result:=False;
223 ProcSnap:=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
224 if ProcSnap=INVALID_HANDLE_VALUE then begin
225 Exit;
226 end;
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);
257 end;
258 Processes[i].Restartable:=False;
259 end;
261 Success:=Module32Next(ModSnap,ModEntry);
262 end;
264 CloseHandle(ModSnap);
265 end;
266 end;
268 Success:=Process32Next(ProcSnap,ProcEntry);
269 end;
271 CloseHandle(ProcSnap);
273 Result:=True;
274 end;
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;
289 Size:Longint;
290 Bytes:DWORD;
291 begin
292 // Start with space for 64 processes.
293 Bytes:=32*SizeOf(Bytes);
295 repeat
296 Size:=Bytes*2;
297 SetArrayLength(List,Size/SizeOf(Bytes));
298 Result:=EnumProcesses(List,Size,Bytes);
299 until (Bytes<Size) or (not Result);
301 if Result then begin
302 SetArrayLength(List,Bytes/SizeOf(Bytes));
303 end else begin
304 SetArrayLength(List,0);
305 end;
306 end;
308 // Wrapper for EnumProcessModules() that returns module IDs as a list.
309 function GetModuleList(Process:THandle;var List:IdList):Boolean;
311 Size:Longint;
312 Bytes:DWORD;
313 begin
314 // Start with space for 64 modules.
315 Bytes:=32*SizeOf(Bytes);
317 repeat
318 Size:=Bytes*2;
319 SetArrayLength(List,Size/SizeOf(Bytes));
320 Result:=EnumProcessModules(Process,List,Size,Bytes);
321 until (Bytes<Size) or (not Result);
323 if Result then begin
324 SetArrayLength(List,Bytes/SizeOf(Bytes));
325 end else begin
326 SetArrayLength(List,0);
327 end;
328 end;
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;
334 #ifdef UNICODE
335 external 'GetModuleFileNameExW@Psapi.dll stdcall delayload';
336 #else
337 external 'GetModuleFileNameExA@Psapi.dll stdcall delayload';
338 #endif
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;
345 p,m,i:Longint;
346 Process:THandle;
347 Path:String;
348 PathLength:DWORD;
349 begin
350 SetArrayLength(Processes,0);
351 Result:=False;
353 if not GetProcessList(ProcList) then begin
354 Exit;
355 end;
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);
381 end;
382 Processes[i].Restartable:=False;
383 end;
384 end;
385 end;
386 CloseHandle(Process);
387 end;
388 end;
390 Result:=True;
391 end;
394 Code for Windows Vista and above
397 const
398 CCH_RM_SESSION_KEY = 32;
399 CCH_RM_MAX_APP_NAME = 255;
400 CCH_RM_MAX_SVC_NAME = 63;
402 RmUnknownApp = 0;
403 RmMainWindow = 1;
404 RmOtherWindow = 2;
405 RmService = 3;
406 RmExplorer = 4;
407 RmConsole = 5;
408 RmCritical = 1000;
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;
420 type
421 SessionKey=array[1..CCH_RM_SESSION_KEY+1] of Char;
423 FILETIME=record
424 dwLowDateTime,dwHighDateTime:DWORD;
425 end;
426 RM_UNIQUE_PROCESS=record
427 dwProcessId:DWORD;
428 ProcessStartTime:FILETIME;
429 end;
430 RM_APP_TYPE=DWORD;
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;
436 AppStatus:ULONG;
437 TSSessionId:DWORD;
438 bRestartable:BOOL;
439 end;
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;
457 Handle:DWORD;
458 Name:SessionKey;
459 Files:TArrayOfString;
460 Apps:array of RM_UNIQUE_PROCESS;
461 Services:TArrayOfString;
462 Process:THandle;
463 Path:String;
464 PathLength:DWORD;
465 Needed,Have,i:UINT;
466 AppList:array of RM_PROCESS_INFO;
467 ReasonList:IdList;
468 Success:DWORD;
469 begin
470 SetArrayLength(Processes,0);
471 Result:=False;
473 // We require the full path to the module here.
474 if not FileExists(Module) then begin
475 Exit;
476 end;
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
481 Exit;
482 end;
484 SetArrayLength(Files,1);
485 Files[0]:=Module;
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.
488 Needed:=1;
489 repeat
490 Have:=Needed;
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);
514 end;
515 //end;
516 end;
517 Result:=True;
518 end;
519 end;
521 RmEndSession(Handle);
522 end;
525 Wrapper code
528 function FindProcessesUsingModule(Module:String;var Processes:ProcessList):Boolean;
530 Version:TWindowsVersion;
531 begin
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);
538 end else begin
539 Result:=FindProcessesUsingModule_WinVista(Module,Processes);
540 end;
541 end;