未提升的程序启动提升的更新程序,更新程序应等待程序完成
unelevated program starts an elevated updater, updater should wait for finishing of program
我有 2 个应用程序,program.exe 和 updater.exe,都是用 Delphi5 编写的。
程序 运行s 没有管理员权限(也没有清单),更新程序有一个带有 "requireAdministrator" 的清单,因为他必须能够在程序文件夹中写入以更新 program.exe.
问题是启动更新程序并让他等到程序关闭。
我在网上找到了不同的方法,但 none 有效(在大多数情况下,第一个应用程序启动第二个应用程序并等待第二个应用程序结束,在我的情况下,第二个应用程序应该等待第一个应用程序结束)。
更新程序应该等待,这很容易
updater.exe
{$R manifest.res}
label.caption:='Wait for program.exe closing';
repeat
sleep(1000);
until File is not open
ProgramHandle := Read Handle from File
WaitForSingleObject(ProgramHandle,INFINITE);
label.caption:='program.exe CLOSED';
Do updates
方式一
使用 CreateProcess 启动更新程序:
program.exe
FillChar(siInfo, SizeOf(siInfo), 0);
siInfo.cb := SizeOf(siInfo);
saProcessAttributes.nLength := SizeOf(saProcessAttributes);
saProcessAttributes.lpSecurityDescriptor := nil;
saProcessAttributes.bInheritHandle := TRUE;
saThreadAttributes.nLength := SizeOf(saThreadAttributes);
saThreadAttributes.lpSecurityDescriptor := nil;
saThreadAttributes.bInheritHandle := True;
if CreateProcess(nil,
PChar('updater.exe'),
@saProcessAttributes,
@saThreadAttributes,
TRUE, NORMAL_PRIORITY_CLASS, nil,
PChar(ExtractFilePath(Application.ExeName)),
siInfo, piInfo) then
begin
DuplicateHandle(GetCurrentProcess, GetCurrentProcess,
piInfo.hProcess, @MyHandle,
PROCESS_QUERY_INFORMATION, TRUE,
DUPLICATE_SAME_ACCESS) then
Write MyHandle in a File
end;
Close program
不执行任何操作,仅在更新程序没有包含 requireAdministrator 的清单时才起作用。
如果我 运行 具有 explizit 管理员权限的程序,它也可以工作。
方式二
使用 ShellExecuteEx 启动更新程序:
program.exe
FillChar(Info, SizeOf(Info), Chr(0));
Info.cbSize := SizeOf(Info);
Info.fMask := SEE_MASK_NOCLOSEPROCESS;
Info.lpVerb := PChar('runas');
Info.lpFile := PChar('update.exe');
Info.lpDirectory := nil;
Info.nShow := SW_RESTORE;
ShellExecuteEx(@Info);
MyHandle:=OpenProcess(PROCESS_ALL_ACCESS, False, GetCurrentProcessId())));
Write MyHandle in a File
Close program
不工作,MyHandle 每次我 运行 这个过程都有不同的值(没有重新启动程序),所以更新程序不能使用它。
所以我不知道如何开始 updater.exe 并在文件中写入 program.exe 的句柄。
我对编程的这些部分不是很熟悉...有人知道我的问题吗?
我在两个事件的不确定顺序中看到了主要问题:程序的关闭和更新程序主代码的启动。
一种可能的修复方法是使用事件 - https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686670(v=vs.85).aspx
程序 创建事件(带有供 children 继承的选项),然后启动 更新程序 (通过命令行将事件和程序进程的句柄作为整数传递给它!),然后在事件的 WaitForSingleObject 中冻结。
这确保程序不会在更新程序准备好监视它之前退出,因此PID不会无效。
更新程序然后对从命令行获得的程序 PID 调用 OpenProcess,然后调用 SignalAndWait 敲击事件(从命令行获得)并冻结句柄(从 OpenProcess 获得)- https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686293(v=vs.85).aspx
程序, 现在正在从等待事件中释放出来,终止。
进程的终止发出信号,所以现在 updater 依次被释放并可以开始执行主要工作。
C++, How to determine if a Windows Process is running? 中建议的另一种方法是查询 程序 ProcessID 的退出代码 - 据说虽然 程序 仍然是 运行 会有一个特定的错误代码,您可以 Sleep(100) 然后再试一次。任何其他结果意味着 程序 已经完成。
程序在启动更新器后立即退出,而不等待它开始监控。
这看起来不错,只是我现在不保证 PID 值不会被重用。机会是无限小的,但仍然不为零。
就我个人而言,我可能会使用标志文件。 CreateFile API 有一个非常有趣的标志 - temporary-file 模式。这意味着,Windows 会在进程结束后自动删除该文件。那么
- 程序 使用 Windows API(或使用加密 API 的新随机值)创建新的 GUID。
- 程序在临时文件夹中创建temporary-mode文件,其名称基于GUID或随机价值。如果运气好这样的文件已经存在 - 您只需获得一个新的 GUID 或随机值。
- 程序启动更新程序并通过命令行
将文件名传递给它
- 程序在启动更新程序后立即退出,而不等待它开始监控。
- 更新程序 不断检查文件是否存在(在尝试之间暂停)。当文件不再存在时 - 这意味着 程序 已经完成并且 Windows auto-deleted 它。
同样,其他进程创建具有完全相同名称的标志文件的可能性极小 in-between 程序 终止并且 updater 再次检查,但这在实践中几乎是不可能的
您的代码无法正常工作,因为句柄 table 是每个进程的,这意味着第二个进程可能具有指向另一个内核对象的相同句柄。下面是许多可能的解决方案之一:
创建进程2时,传入进程1的PID作为参数:
procedure CreateUpdater;
var
Info: TShellExecuteInfo;
begin
FillChar(Info, SizeOf(TShellExecuteInfo), 0);
Info.cbSize := SizeOf(TShellExecuteInfo);
Info.fMask := SEE_MASK_NOCLOSEPROCESS;
Info.lpVerb := PChar('runas');
Info.lpFile := PChar('Update.exe');
Info.lpParameters := PChar(IntToStr(GetCurrentProcessId));
Info.lpDirectory := nil;
Info.nShow := SW_RESTORE;
ShellExecuteEx(@Info);
//NOTE: MISSING ERROR CHECKING!
end;
在 Updater 内部,等待 process1 终止:
procedure WaitForAndClose;
var
PID: String;
AHandle: Cardinal;
Ret: longbool;
ExitNumber: DWORD;
begin
PID:= ParamStr(1);
if PID <> '' then
begin
AHandle:= OpenProcess(PROCESS_QUERY_INFORMATION, False, StrToInt(PID));
//NOTE: MISSING ERROR CHECKING!
try
repeat
Ret:= GetExitCodeProcess(AHandle, ExitNumber);
//NOTE: MISSING ERROR CHECKING!
Sleep(1000); //define a time to poolling
until (ExitNumber <> STILL_ACTIVE);
finally
CloseHandle(AHandle);
end;
//Terminate the process;
Application.Terminate;
end;
end;
你也可以使用WaitForSingleObject
来避免轮询:
WaitForSingleObject(AHandle, INFINITE);
//NOTE: MISSING ERROR CHECKING!
但是您需要 SYNCHRONIZE 权限才能打开进程:
AHandle:= OpenProcess(SYNCHRONIZE, False, StrToInt(PID));
//NOTE: MISSING ERROR CHECKING!
注意:这里没有错误检查。您应该阅读文档并正确检查错误。
注意 2:我想让您注意您正在泄漏句柄这一事实。
当您使用 SEE_MASK_NOCLOSEPROCESS
时,调用者负责关闭 calee 的句柄。就您而言,我认为您根本不需要那个面具。我会删除它。
以下是如何使用事件实现此目的的基本示例:
program.exe
:
// manual-reset event, non-signaled
Event := CreateEvent(nil, True, False, 'MyUniqueName');
ExecuteUpdater; // via ShellExecuteEx with runas
// synchronize - wait for the event to be signaled
WaitForSingleObject(Event, INFINITE);
// WAIT_OBJECT_0 = The state of the specified object is signaled.
CloseHandle(Event);
updater.exe
:
Event := CreateEvent(nil, True, False, 'MyUniqueName');
if Event = 0 then RaiseLastWin32Error;
SetEvent(Event); // sets the event object to the signaled state
CloseHandle(Event);
您还应该向 program.exe
添加清单(requestedExecutionLevel
应该是 level="asInvoker"
)以避免虚拟化。
我有 2 个应用程序,program.exe 和 updater.exe,都是用 Delphi5 编写的。 程序 运行s 没有管理员权限(也没有清单),更新程序有一个带有 "requireAdministrator" 的清单,因为他必须能够在程序文件夹中写入以更新 program.exe.
问题是启动更新程序并让他等到程序关闭。 我在网上找到了不同的方法,但 none 有效(在大多数情况下,第一个应用程序启动第二个应用程序并等待第二个应用程序结束,在我的情况下,第二个应用程序应该等待第一个应用程序结束)。
更新程序应该等待,这很容易
updater.exe
{$R manifest.res}
label.caption:='Wait for program.exe closing';
repeat
sleep(1000);
until File is not open
ProgramHandle := Read Handle from File
WaitForSingleObject(ProgramHandle,INFINITE);
label.caption:='program.exe CLOSED';
Do updates
方式一
使用 CreateProcess 启动更新程序:
program.exe
FillChar(siInfo, SizeOf(siInfo), 0);
siInfo.cb := SizeOf(siInfo);
saProcessAttributes.nLength := SizeOf(saProcessAttributes);
saProcessAttributes.lpSecurityDescriptor := nil;
saProcessAttributes.bInheritHandle := TRUE;
saThreadAttributes.nLength := SizeOf(saThreadAttributes);
saThreadAttributes.lpSecurityDescriptor := nil;
saThreadAttributes.bInheritHandle := True;
if CreateProcess(nil,
PChar('updater.exe'),
@saProcessAttributes,
@saThreadAttributes,
TRUE, NORMAL_PRIORITY_CLASS, nil,
PChar(ExtractFilePath(Application.ExeName)),
siInfo, piInfo) then
begin
DuplicateHandle(GetCurrentProcess, GetCurrentProcess,
piInfo.hProcess, @MyHandle,
PROCESS_QUERY_INFORMATION, TRUE,
DUPLICATE_SAME_ACCESS) then
Write MyHandle in a File
end;
Close program
不执行任何操作,仅在更新程序没有包含 requireAdministrator 的清单时才起作用。 如果我 运行 具有 explizit 管理员权限的程序,它也可以工作。
方式二
使用 ShellExecuteEx 启动更新程序:
program.exe
FillChar(Info, SizeOf(Info), Chr(0));
Info.cbSize := SizeOf(Info);
Info.fMask := SEE_MASK_NOCLOSEPROCESS;
Info.lpVerb := PChar('runas');
Info.lpFile := PChar('update.exe');
Info.lpDirectory := nil;
Info.nShow := SW_RESTORE;
ShellExecuteEx(@Info);
MyHandle:=OpenProcess(PROCESS_ALL_ACCESS, False, GetCurrentProcessId())));
Write MyHandle in a File
Close program
不工作,MyHandle 每次我 运行 这个过程都有不同的值(没有重新启动程序),所以更新程序不能使用它。
所以我不知道如何开始 updater.exe 并在文件中写入 program.exe 的句柄。
我对编程的这些部分不是很熟悉...有人知道我的问题吗?
我在两个事件的不确定顺序中看到了主要问题:程序的关闭和更新程序主代码的启动。
一种可能的修复方法是使用事件 - https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686670(v=vs.85).aspx
程序 创建事件(带有供 children 继承的选项),然后启动 更新程序 (通过命令行将事件和程序进程的句柄作为整数传递给它!),然后在事件的 WaitForSingleObject 中冻结。
这确保程序不会在更新程序准备好监视它之前退出,因此PID不会无效。
更新程序然后对从命令行获得的程序 PID 调用 OpenProcess,然后调用 SignalAndWait 敲击事件(从命令行获得)并冻结句柄(从 OpenProcess 获得)- https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686293(v=vs.85).aspx
程序, 现在正在从等待事件中释放出来,终止。 进程的终止发出信号,所以现在 updater 依次被释放并可以开始执行主要工作。
C++, How to determine if a Windows Process is running? 中建议的另一种方法是查询 程序 ProcessID 的退出代码 - 据说虽然 程序 仍然是 运行 会有一个特定的错误代码,您可以 Sleep(100) 然后再试一次。任何其他结果意味着 程序 已经完成。
程序在启动更新器后立即退出,而不等待它开始监控。
这看起来不错,只是我现在不保证 PID 值不会被重用。机会是无限小的,但仍然不为零。
就我个人而言,我可能会使用标志文件。 CreateFile API 有一个非常有趣的标志 - temporary-file 模式。这意味着,Windows 会在进程结束后自动删除该文件。那么
- 程序 使用 Windows API(或使用加密 API 的新随机值)创建新的 GUID。
- 程序在临时文件夹中创建temporary-mode文件,其名称基于GUID或随机价值。如果运气好这样的文件已经存在 - 您只需获得一个新的 GUID 或随机值。
- 程序启动更新程序并通过命令行 将文件名传递给它
- 程序在启动更新程序后立即退出,而不等待它开始监控。
- 更新程序 不断检查文件是否存在(在尝试之间暂停)。当文件不再存在时 - 这意味着 程序 已经完成并且 Windows auto-deleted 它。
同样,其他进程创建具有完全相同名称的标志文件的可能性极小 in-between 程序 终止并且 updater 再次检查,但这在实践中几乎是不可能的
您的代码无法正常工作,因为句柄 table 是每个进程的,这意味着第二个进程可能具有指向另一个内核对象的相同句柄。下面是许多可能的解决方案之一:
创建进程2时,传入进程1的PID作为参数:
procedure CreateUpdater;
var
Info: TShellExecuteInfo;
begin
FillChar(Info, SizeOf(TShellExecuteInfo), 0);
Info.cbSize := SizeOf(TShellExecuteInfo);
Info.fMask := SEE_MASK_NOCLOSEPROCESS;
Info.lpVerb := PChar('runas');
Info.lpFile := PChar('Update.exe');
Info.lpParameters := PChar(IntToStr(GetCurrentProcessId));
Info.lpDirectory := nil;
Info.nShow := SW_RESTORE;
ShellExecuteEx(@Info);
//NOTE: MISSING ERROR CHECKING!
end;
在 Updater 内部,等待 process1 终止:
procedure WaitForAndClose;
var
PID: String;
AHandle: Cardinal;
Ret: longbool;
ExitNumber: DWORD;
begin
PID:= ParamStr(1);
if PID <> '' then
begin
AHandle:= OpenProcess(PROCESS_QUERY_INFORMATION, False, StrToInt(PID));
//NOTE: MISSING ERROR CHECKING!
try
repeat
Ret:= GetExitCodeProcess(AHandle, ExitNumber);
//NOTE: MISSING ERROR CHECKING!
Sleep(1000); //define a time to poolling
until (ExitNumber <> STILL_ACTIVE);
finally
CloseHandle(AHandle);
end;
//Terminate the process;
Application.Terminate;
end;
end;
你也可以使用WaitForSingleObject
来避免轮询:
WaitForSingleObject(AHandle, INFINITE);
//NOTE: MISSING ERROR CHECKING!
但是您需要 SYNCHRONIZE 权限才能打开进程:
AHandle:= OpenProcess(SYNCHRONIZE, False, StrToInt(PID));
//NOTE: MISSING ERROR CHECKING!
注意:这里没有错误检查。您应该阅读文档并正确检查错误。
注意 2:我想让您注意您正在泄漏句柄这一事实。
当您使用 SEE_MASK_NOCLOSEPROCESS
时,调用者负责关闭 calee 的句柄。就您而言,我认为您根本不需要那个面具。我会删除它。
以下是如何使用事件实现此目的的基本示例:
program.exe
:
// manual-reset event, non-signaled
Event := CreateEvent(nil, True, False, 'MyUniqueName');
ExecuteUpdater; // via ShellExecuteEx with runas
// synchronize - wait for the event to be signaled
WaitForSingleObject(Event, INFINITE);
// WAIT_OBJECT_0 = The state of the specified object is signaled.
CloseHandle(Event);
updater.exe
:
Event := CreateEvent(nil, True, False, 'MyUniqueName');
if Event = 0 then RaiseLastWin32Error;
SetEvent(Event); // sets the event object to the signaled state
CloseHandle(Event);
您还应该向 program.exe
添加清单(requestedExecutionLevel
应该是 level="asInvoker"
)以避免虚拟化。