3 // This
file contains code paths
for Windows
95, Windows
2000 and Windows Vista
4 // to get a list
of processes that use a given
module (DLL
). For the processes
5 // that lock the
file, the process ID
, the
full path
to the executable
, and file
6 // description
is returned
. This information can
then used
to present the user a
7 // list
of processes
/ applications that she needs
to close before a module can
8 // be replaced
/ a replacement will take effect
.
10 // Since Windows Vista
, processes that
register with the
Restart Manager can be
11 // asked
to be restarted without any user interaction
. The
"Restartable" flag
in
12 // the
"ProcessEntry" indicates whether this
is supported
or not.
14 // Please note that this code only works
for modules
, not for files that are
15 // locked by processes
in other ways
, e
.g
. by opening them
for exclusive read /
18 // In contrast
to existing solutions like
[1] or [2], this one has the advantages
19 // of not requiring an
external DLLs
, being Open Source
and having support
for
20 // the Windows Vista
Restart Manager API
.
22 // [1] http
://www
.vincenzo
.net
/isxkb
/index
.php?title
=PSVince
23 // [2] http
://raz
-soft
.com
/display
-english
-posts
-only
/files-in-use
-extension
-for-inno
-setup
/
32 MAX_MODULE_NAME32
= 255;
36 ERROR_MORE_DATA
= 234;
37 INVALID_HANDLE_VALUE
= -1;
40 PROCESS_VM_READ
= $0010;
41 PROCESS_QUERY_INFORMATION
= $0400;
50 IdList
=array of DWORD
;
57 ProcessList
=array of ProcessEntry
;
59 function CloseHandle(hObject
:THandle
):Boolean;
60 external 'CloseHandle@Kernel32.dll';
62 // We need
to always use ANSI version
of this
function, see the bottom note
in GetFileDescription().
63 function GetFileVersionInfoSize(lptstrFilename
:AnsiString
;var lpdwHandle
:DWORD
):DWORD
;
64 external 'GetFileVersionInfoSizeA@Version.dll';
66 // We need
to always use ANSI version
of this
function, see the bottom note
in GetFileDescription().
67 function GetFileVersionInfo(lptstrFilename
:AnsiString
;dwHandle
,dwLen
:DWORD
;lpData
:array of Byte):Boolean;
68 external 'GetFileVersionInfoA@Version.dll';
70 // We need
to always use ANSI version
of this
function, see the bottom note
in GetFileDescription().
71 function VerQueryValue(pBlock
:array of Byte;lpSubBlock
:AnsiString
;var lplpBuffer
:PAnsiChar
;var puLen
:UINT
):Boolean;
72 external 'VerQueryValueA@Version.dll';
74 // Returns the
file description
as stored
in the VS_VERSION_INFO resource
. This
75 // is used
as the process name rather than using the window title
, as e
.g
. editors
76 // might display the current
file rather than the application name
in the title bar
.
77 function GetFileDescription(FileName
:String):String;
85 Language
,Codepage
,LanguageFB
,CodepageFB
:WORD;
87 Size
:=GetFileVersionInfoSize(Filename
,Dummy
);
92 SetArrayLength(Info
,Size
);
93 if not GetFileVersionInfo(FileName
,0,Size
,Info
) then begin
97 // Query the language
and codepage
in order
to query locale specific strings
.
98 if not VerQueryValue(Info
,'\VarFileInfo\Translation',Buffer
,BufLen
) then begin
102 // This will fail
if "Buffer" contains inner #
0 characters
, in which
case
103 // the
"else" branch below
is taken
, and we are guessing some values
.
106 if Length(BufStr
)>=BufLen
then begin
109 // Decode the WORDs from the
string.
110 Language
:=Ord(BufStr
[Offset
+1]);
111 Language
:=(Language
shl 8)+Ord(BufStr
[Offset
]);
113 Codepage
:=Ord(BufStr
[Offset
+3]);
114 Codepage
:=(Codepage
shl 8)+Ord(BufStr
[Offset
+2]);
116 // Use the first entry
or English
as a fallback
.
117 if (Offset
=1) or (Language
=$0409) then begin
118 LanguageFB
:=Language
;
119 CodepageFB
:=Codepage
;
123 until (Language
=GetUILanguage
) or (Offset
>BufLen
);
125 // If we did
not find the UI language
, use the fallback
.
126 if Language
<>GetUILanguage
then begin
127 Language
:=LanguageFB
;
128 Codepage
:=CodepageFB
;
131 Language
:=$0000; // Process Default Language
132 Codepage
:=$04b0; // 1200 (UTF
-16, Little
-Endian
)
133 LanguageFB
:=$0000; // Process Default Language
134 CodepageFB
:=$04e4; // 1252 (West European
, Latin
)
137 // Query the
file description
.
138 BufStr
:=Format('\StringFileInfo\%.4x%.4x\FileDescription',[Language
,Codepage
]);
139 if not VerQueryValue(Info
,BufStr
,Buffer
,BufLen
) then begin
140 // Try the fallback
if the first choice failed
.
141 BufStr
:=Format('\StringFileInfo\%.4x%.4x\FileDescription',[LanguageFB
,CodepageFB
]);
142 if not VerQueryValue(Info
,BufStr
,Buffer
,BufLen
) then begin
147 // As we cannot cast PAnsiChar
to a Unicode
string here
, we always
148 // need
to use the ANSI functions
for VerQueryValue etc
.
153 Code for Windows 95 and above
157 TH32CS_SNAPPROCESS
= $0002;
158 TH32CS_SNAPMODULE
= $0008;
159 TH32CS_SNAPMODULE32
= $0010;
162 PROCESSENTRY32
=record
163 dwSize
,cntUsage
,th32ProcessID
:DWORD
;
164 th32DefaultHeapID
:ULONG_PTR
;
165 th32ModuleID
,cntThreads
,th32ParentProcessID
:DWORD
;
168 szExeFile
:array[1..MAX_PATH
] of Char;
171 dwSize
,th32ModuleID
,th32ProcessID
,GlblcntUsage
,ProccntUsage
:DWORD
;
172 modBaseAddr
:BYTE_PTR
;
175 szModule
:array[1..MAX_MODULE_NAME32
+1] of Char;
176 szExePath
:array[1..MAX_PATH
] of Char;
179 function CreateToolhelp32Snapshot(dwFlags
,th32ProcessID
:DWORD
):THandle
;
180 external 'CreateToolhelp32Snapshot@Kernel32.dll stdcall delayload';
182 function Process32First(hSnapshot
:THandle
;var lppe
:PROCESSENTRY32
):Boolean;
184 external 'Process32FirstW@Kernel32.dll stdcall delayload';
186 external 'Process32FirstA@Kernel32.dll stdcall delayload';
189 function Process32Next(hSnapshot
:THandle
;var lppe
:PROCESSENTRY32
):Boolean;
191 external 'Process32NextW@Kernel32.dll stdcall delayload';
193 external 'Process32NextA@Kernel32.dll stdcall delayload';
196 function Module32First(hSnapshot
:THandle
;var lpme
:MODULEENTRY32
):Boolean;
198 external 'Module32FirstW@Kernel32.dll stdcall delayload';
200 external 'Module32FirstA@Kernel32.dll stdcall delayload';
203 function Module32Next(hSnapshot
:THandle
;var lpme
:MODULEENTRY32
):Boolean;
205 external 'Module32NextW@Kernel32.dll stdcall delayload';
207 external 'Module32NextA@Kernel32.dll stdcall delayload';
210 // Returns a list
of running processes that currectly use the specified module
.
211 // The module may be a filename
to a DLL
with or without path
.
212 function FindProcessesUsingModules_Win95(Modules
:TArrayOfString
;var Processes
:ProcessList
):DWORD
;
216 ProcEntry
:PROCESSENTRY32
;
218 ModEntry
:MODULEENTRY32
;
219 ModPath
,ProcPath
:String;
222 SetArrayLength(Processes
,0);
225 ProcSnap
:=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS
,0);
226 if ProcSnap
=INVALID_HANDLE_VALUE
then begin
230 // Compare strings
case-insensitively
.
231 for i
:=0 to GetArraylength(Modules
)-1 do begin
232 Modules
[i
]:=Lowercase(Modules
[i
]);
235 // Loop over the processes
in the
system.
236 ProcEntry
.dwSize
:=SizeOf(ProcEntry
);
237 Success
:=Process32First(ProcSnap
,ProcEntry
);
239 while Success
do begin
240 if ProcEntry
.th32ProcessID
>0 then begin
241 ModSnap
:=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE
or TH32CS_SNAPMODULE32
,ProcEntry
.th32ProcessID
);
242 if ModSnap
<>INVALID_HANDLE_VALUE
then begin
243 // Loop over the modules
in the process
.
244 ModEntry
.dwSize
:=SizeOf(ModEntry
);
245 Success
:=Module32First(ModSnap
,ModEntry
);
247 // Assume the first module always
is the executable itself
.
248 ProcPath
:=ArrayToString(ModEntry
.szExePath
);
249 Success
:=Module32Next(ModSnap
,ModEntry
);
251 while Success
do begin
252 ModPath
:=ArrayToString(ModEntry
.szExePath
);
254 for i
:=0 to GetArraylength(Modules
)-1 do begin
255 if Pos(Modules
[i
],Lowercase(ModPath
))>0 then begin
256 i
:=GetArrayLength(Processes
);
257 SetArrayLength(Processes
,i
+1);
258 Processes
[i
].ID
:=ProcEntry
.th32ProcessID
;
259 Processes
[i
].Path
:=ProcPath
;
260 Processes
[i
].Name
:=GetFileDescription(ProcPath
);
261 if Length(Processes
[i
].Name
)=0 then begin
262 Processes
[i
].Name
:=ExtractFileName(ProcPath
);
264 Processes
[i
].Restartable
:=False;
268 Success
:=Module32Next(ModSnap
,ModEntry
);
271 CloseHandle(ModSnap
);
275 Success
:=Process32Next(ProcSnap
,ProcEntry
);
278 CloseHandle(ProcSnap
);
283 // Returns a list
of running processes that currectly use the specified module
.
284 // The module may be a filename
to a DLL
with or without path
.
285 function FindProcessesUsingModule_Win95(Module
:String;var Processes
:ProcessList
):DWORD
;
287 Modules
:TArrayOfString
;
289 SetArrayLength(Modules
,1);
291 Result
:=FindProcessesUsingModules_Win95(Modules
,Processes
);
295 Code for Windows 2000 and above
298 function EnumProcesses(pProcessIds
:IdList
;cb
:DWORD
;var pBytesReturned
:DWORD
):Boolean;
299 external 'EnumProcesses@Psapi.dll stdcall delayload';
301 function EnumProcessModules(hProcess
:THandle
;lphModule
:IdList
;cb
:DWORD
;var lpcbNeeded
:DWORD
):Boolean;
302 external 'EnumProcessModules@Psapi.dll stdcall delayload';
304 // Wrapper
for EnumProcesses() that returns process IDs
as a list
.
305 function GetProcessList(var List
:IdList
):Boolean;
310 // Start
with space
for 64 processes
.
311 Bytes
:=32*SizeOf(Bytes
);
315 SetArrayLength(List
,Size
/SizeOf(Bytes
));
316 Result
:=EnumProcesses(List
,Size
,Bytes
);
317 until (Bytes
<Size
) or (not Result
);
320 SetArrayLength(List
,Bytes
/SizeOf(Bytes
));
322 SetArrayLength(List
,0);
326 // Wrapper
for EnumProcessModules() that returns module IDs
as a list
.
327 function GetModuleList(Process
:THandle
;var List
:IdList
):Boolean;
332 // Start
with space
for 64 modules
.
333 Bytes
:=32*SizeOf(Bytes
);
337 SetArrayLength(List
,Size
/SizeOf(Bytes
));
338 Result
:=EnumProcessModules(Process
,List
,Size
,Bytes
);
339 until (Bytes
<Size
) or (not Result
);
342 SetArrayLength(List
,Bytes
/SizeOf(Bytes
));
344 SetArrayLength(List
,0);
348 function OpenProcess(dwDesiredAccess
:DWORD
;bInheritHandle
:BOOL
;dwProcessId
:DWORD
):THandle
;
349 external 'OpenProcess@Kernel32.dll stdcall delayload';
351 function GetModuleFileNameEx(hProcess
:THandle
;hModule
:HMODULE
;lpFilename
:String;nSize
:DWORD
):DWORD
;
353 external 'GetModuleFileNameExW@Psapi.dll stdcall delayload';
355 external 'GetModuleFileNameExA@Psapi.dll stdcall delayload';
358 // Returns a list
of running processes that currectly use one
of the specified modules
.
359 // Each module may be a filename
to a DLL
with or without path
.
360 function FindProcessesUsingModules_Win2000(Modules
:TArrayOfString
;var Processes
:ProcessList
):DWORD
;
362 ProcList
,ModList
:IdList
;
368 SetArrayLength(Processes
,0);
371 if not GetProcessList(ProcList
) then begin
375 // Compare strings
case-insensitively
.
376 for i
:=0 to GetArraylength(Modules
)-1 do begin
377 Modules
[i
]:=Lowercase(Modules
[i
]);
380 for p
:=0 to GetArraylength(ProcList
)-1 do begin
381 Process
:=OpenProcess(PROCESS_QUERY_INFORMATION
or PROCESS_VM_READ
,False,ProcList
[p
]);
382 if Process
<>0 then begin
383 if GetModuleList(Process
,ModList
) then begin
384 for m
:=0 to GetArraylength(ModList
)-1 do begin
385 SetLength(Path
,MAX_PATH
);
386 PathLength
:=GetModuleFileNameEx(Process
,ModList
[m
],Path
,MAX_PATH
);
387 SetLength(Path
,PathLength
);
389 for i
:=0 to GetArraylength(Modules
)-1 do begin
390 if Pos(Modules
[i
],Lowercase(Path
))>0 then begin
391 SetLength(Path
,MAX_PATH
);
392 PathLength
:=GetModuleFileNameEx(Process
,0,Path
,MAX_PATH
);
393 SetLength(Path
,PathLength
);
395 i
:=GetArrayLength(Processes
);
396 SetArrayLength(Processes
,i
+1);
397 Processes
[i
].ID
:=ProcList
[p
];
398 Processes
[i
].Path
:=Path
;
399 Processes
[i
].Name
:=GetFileDescription(Path
);
400 if Length(Processes
[i
].Name
)=0 then begin
401 Processes
[i
].Name
:=ExtractFileName(Path
);
403 Processes
[i
].Restartable
:=False;
408 CloseHandle(Process
);
415 // Returns a list
of running processes that currectly use the specified module
.
416 // The module may be a filename
to a DLL
with or without path
.
417 function FindProcessesUsingModule_Win2000(Module
:String;var Processes
:ProcessList
):DWORD
;
419 Modules
:TArrayOfString
;
421 SetArrayLength(Modules
,1);
423 Result
:=FindProcessesUsingModules_Win2000(Modules
,Processes
);
427 Code for Windows Vista and above
431 CCH_RM_SESSION_KEY
= 32;
432 CCH_RM_MAX_APP_NAME
= 255;
433 CCH_RM_MAX_SVC_NAME
= 63;
435 RmUnknownApp
= 0; // The application cannot be classified
as any other
type. An application
of this
type can only be shut down by a forced shutdown
.
436 RmMainWindow
= 1; // A Windows application run
as a stand
-alone process that displays a top
-level window
.
437 RmOtherWindow
= 2; // A Windows application that does
not run
as a stand
-alone process
and does
not display a top
-level window
.
438 RmService
= 3; // The application
is a Windows service
.
439 RmExplorer
= 4; // The application
is Windows Explorer
.
440 RmConsole
= 5; // The application
is a stand
-alone console application
.
441 RmCritical
= 1000; // A
system restart is required
to complete the installation because a process cannot be shut down
.
443 RmStatusUnknown
= $0000;
444 RmStatusRunning
= $0001;
445 RmStatusStopped
= $0002;
446 RmStatusStoppedOther
= $0004;
447 RmStatusRestarted
= $0008;
448 RmStatusErrorOnStop
= $0010;
449 RmStatusErrorOnRestart
= $0020;
450 RmStatusShutdownMasked
= $0040;
451 RmStatusRestartMasked
= $0080;
453 RmForceShutdown
= $0001;
454 RmShutdownOnlyRegistered
= $0010;
457 SessionKey
=array[1..CCH_RM_SESSION_KEY
+1] of Char;
460 dwLowDateTime
,dwHighDateTime
:DWORD
;
462 RM_UNIQUE_PROCESS
=record
464 ProcessStartTime
:FILETIME
;
467 RM_PROCESS_INFO
=record
468 Process
:RM_UNIQUE_PROCESS
;
469 strAppName
:array[1..CCH_RM_MAX_APP_NAME
+1] of Char;
470 strServiceShortName
:array[1..CCH_RM_MAX_SVC_NAME
+1] of Char;
471 ApplicationType
:RM_APP_TYPE
;
476 RM_WRITE_STATUS_CALLBACK
=DWORD
;
478 function RmStartSession(var pSessionHandle
:DWORD
;dwSessionFlags
:DWORD
;strSessionKey
:SessionKey
):DWORD
;
479 external 'RmStartSession@Rstrtmgr.dll stdcall delayload';
481 function RmEndSession(dwSessionHandle
:DWORD
):DWORD
;
482 external 'RmEndSession@Rstrtmgr.dll stdcall delayload';
484 function RmRegisterResources(dwSessionHandle
:DWORD
;hFiles
:UINT
;rgsFilenames
:TArrayOfString
;nApplications
:UINT
;rgApplications
:array of RM_UNIQUE_PROCESS
;nServices
:UINT
;rgsServiceNames
:TArrayOfString
):DWORD
;
485 external 'RmRegisterResources@Rstrtmgr.dll stdcall delayload';
487 function RmGetList(dwSessionHandle
:DWORD
;var pnProcInfoNeeded
,pnProcInfo
:UINT
;rgAffectedApps
:array of RM_PROCESS_INFO
;lpdwRebootReasons
:IdList
):DWORD
;
488 external 'RmGetList@Rstrtmgr.dll stdcall delayload';
490 function RmShutdown(dwSessionHandle
:DWORD
;lActionFlags
:ULONG
;fnStatus
:RM_WRITE_STATUS_CALLBACK
):DWORD
;
491 external 'RmShutdown@Rstrtmgr.dll stdcall delayload';
493 function RmRestart(dwSessionHandle
:DWORD
;dwRestartFlags
:DWORD
;fnStatus
:RM_WRITE_STATUS_CALLBACK
):DWORD
;
494 external 'RmRestart@Rstrtmgr.dll stdcall delayload';
496 // Returns a list
of running processes that currectly use one
of the specified modules
.
497 // Each module has
to be a
full path
and filename
to a DLL
.
498 function FindProcessesUsingModules_WinVista(Modules
:TArrayOfString
;var Processes
:ProcessList
):DWORD
;
502 Apps
:array of RM_UNIQUE_PROCESS
;
503 Services
:TArrayOfString
;
508 AppList
:array of RM_PROCESS_INFO
;
512 SetArrayLength(Processes
,0);
515 // NULL
-terminate the
array of chars
.
516 Name
[CCH_RM_SESSION_KEY
+1]:=#
0;
517 if RmStartSession(Handle
,0,Name
)<>ERROR_SUCCESS
then begin
521 if RmRegisterResources(Handle
,GetArrayLength(Modules
),Modules
,0,Apps
,0,Services
)=ERROR_SUCCESS
then begin
522 // Reallocate the arrays
until they are large enough
to hold the process information
.
526 SetArrayLength(AppList
,Have
);
527 SetArrayLength(ReasonList
,Have
);
528 Success
:=RmGetList(Handle
,Needed
,Have
,AppList
,ReasonList
);
529 until (Have
>=Needed
) and (Success
<>ERROR_MORE_DATA
);
531 if (Success
=ERROR_SUCCESS
) and (Needed
>0) then begin
532 for i
:=0 to Needed
-1 do begin
533 Process
:=OpenProcess(PROCESS_QUERY_INFORMATION
or PROCESS_VM_READ
,False,AppList
[i
].Process
.dwProcessId
);
534 if Process
<>0 then begin
535 SetLength(Path
,MAX_PATH
);
536 PathLength
:=GetModuleFileNameEx(Process
,0,Path
,MAX_PATH
);
537 SetLength(Path
,PathLength
);
539 Have
:=GetArrayLength(Processes
);
540 SetArrayLength(Processes
,Have
+1);
541 Processes
[Have
].ID
:=AppList
[i
].Process
.dwProcessId
;
542 Processes
[Have
].Path
:=Path
;
543 Processes
[Have
].Name
:=ArrayToString(AppList
[i
].strAppName
);
544 Processes
[Have
].Restartable
:=AppList
[i
].bRestartable
;
546 CloseHandle(Process
);
554 // Returns a list
of running processes that currectly use the specified module
.
555 // The module has
to be a
full path
and filename
to a DLL
.
556 function FindProcessesUsingModule_WinVista(Module
:String;var Processes
:ProcessList
):DWORD
;
558 Modules
:TArrayOfString
;
560 SetArrayLength(Modules
,1);
562 Result
:=FindProcessesUsingModules_WinVista(Modules
,Processes
);
569 // Returns a list
of running processes that currectly use one
of the specified modules
.
570 // Automatically calls the best
implementation for the running OS
. The return value
is
571 // non
-zero on success
, and equals the
Restart Manager session handle on Vista
and above
.
572 function FindProcessesUsingModules(Modules
:TArrayOfString
;var Processes
:ProcessList
):DWORD
;
574 Version
:TWindowsVersion
;
576 GetWindowsVersionEx(Version
);
578 if (Version
.Major
<5) or (not Version
.NTPlatform
) then begin
579 Result
:=FindProcessesUsingModules_Win95(Modules
,Processes
);
580 end else if Version
.Major
<6 then begin
581 Result
:=FindProcessesUsingModules_Win2000(Modules
,Processes
);
583 Result
:=FindProcessesUsingModules_WinVista(Modules
,Processes
);
587 // Returns a list
of running processes that currectly use the specified module
.
588 // Automatically calls the best
implementation for the running OS
. The return value
is
589 // non
-zero on success
, and equals the
Restart Manager session handle on Vista
and above
.
590 function FindProcessesUsingModule(Module
:String;var Processes
:ProcessList
):DWORD
;
592 Version
:TWindowsVersion
;
594 GetWindowsVersionEx(Version
);
596 if (Version
.Major
<5) or (not Version
.NTPlatform
) then begin
597 Result
:=FindProcessesUsingModule_Win95(Module
,Processes
);
598 end else if Version
.Major
<6 then begin
599 Result
:=FindProcessesUsingModule_Win2000(Module
,Processes
);
601 Result
:=FindProcessesUsingModule_WinVista(Module
,Processes
);
609 // Tries
to replace an
in-use
file, e
.g
. a registered shell extension
, by
610 // renaming it
and then renaming the new
file to the original name
. Optionally
,
611 // performs (un
-)registering via regsvr32
.
612 function ReplaceInUseFile(CurFile
,NewFile
:String;Register:Boolean;var ErrorMsg
:String):Boolean;
614 CurFilePath
,CurFileName
,NewFileName
:String;
615 CurFileStem
,CurFileTemp
:String;
616 UnregisterFailed
,RenameFailed
:Boolean;
620 // Note that CurFile may
not exist
, in which
case NewFile
is just renamed
.
621 if not FileExists(NewFile
) then begin
625 CurFilePath
:=ExtractFilePath(CurFile
);
626 CurFileName
:=ExtractFileName(CurFile
);
627 NewFileName
:=ExtractFileName(NewFile
);
629 // Get the
file name without extension
or period
and use that
as a suffix
630 // for the temporary
file.
631 CurFileStem
:=ChangeFileExt(CurFileName
,'');
632 CurFileTemp
:=GenerateUniqueName(CurFilePath
,'.'+CurFileStem
);
634 // Clean
-up by trying
to delete any previously renamed temporary
files.
635 DelTree(CurFilePath
+'\*.'+CurFileStem
,False,True,False);
637 UnregisterFailed
:=False;
640 if FileExists(CurFile
) then begin
641 if Register and (not UnregisterServer(Is64BitInstallMode
,CurFile
,False)) then begin
642 UnregisterFailed
:=True;
645 if (not DeleteFile(CurFile
)) and (not RenameFile(CurFile
,CurFileTemp
)) then begin
650 if not RenameFile(NewFile
,CurFile
) then begin
651 ErrorMsg
:='Unable to install a new version of "'+CurFileName
+'". ' +
652 'Please finish the installation manually by following theses steps on the command line:' + #
13 + #
13;
653 if FileExists(CurFile
) then begin
654 if UnregisterFailed
then begin
655 ErrorMsg
:= ErrorMsg
+ '- run "regsvr32 /u ' + CurFileName
+ '",' + #
13;
657 if RenameFailed
then begin
658 ErrorMsg
:= ErrorMsg
+ '- rename "' + CurFileName
+ '" to something else,' + #
13;
661 ErrorMsg
:= ErrorMsg
+ '- rename "' + NewFileName
+ '" to "' + CurFileName
+ '",' + #
13;
662 ErrorMsg
:= ErrorMsg
+ '- run "regsvr32 ' + CurFileName
+ '".';
664 if Register then begin
665 RegisterServer(Is64BitInstallMode
,CurFile
,False);