【问题标题】:NSIS - See if a given process has shown a windowNSIS - 查看给定进程是否显示了一个窗口
【发布时间】:2014-12-17 16:45:51
【问题描述】:

我通过 NSIS 安装程序中的 ExecShell 运行某个进程。该过程需要一段时间才能开始(可能需要 5 到 40 秒),在此期间我希望 NSIS 窗口保持可见。

问题是,虽然进程本身几乎立即启动,但在用户看到任何东西之前的某个时间,因此我希望 NSIS 安装程序窗口保持可见,直到启动进程的主窗口(或任何与此相关的窗口显示)。

因此,我需要知道的是如何从 ExecShell 获取 processid(由于其他原因不能使用 Exec 或 ExecWait),然后如何使用该进程 id 来查看窗口是否已显示(我知道我可以通过简单的 sleep、check、goto 循环来完成,所以基本上我是想弄清楚它的 check 部分)?

那么,我怎么知道我使用 ShellExec 生成的进程是否显示了 GUI。这需要在 Windows XP SP3 及更高版本中工作。

谢谢。

【问题讨论】:

  • 为什么需要 ExecShell 而不是普通的 Exec?您是在启动 .exe 还是不可执行的文件类型?
  • 不同的用户权限。默认情况下,我的安装程序是非管理员的。而要启动的程序可能需要也可能不需要管理员权限。

标签: windows user-interface process nsis


【解决方案1】:
RequestExecutionLevel user
Page InstFiles

!include LogicLib.nsh
!include WinMessages.nsh ; For SW_*

Var IsSlowFakeApp ; We can also pretend to be a silly little app that is slow to start up, this is just so the example code has no external dependencies

Function .onInit
StrCpy $0 $CMDLINE 1 -1
${If} $0 == "?"
    StrCpy $IsSlowFakeApp 1
    Sleep 3333
${EndIf}
FunctionEnd


Function StartMyAppAndWaitForWindow
StrCpy $0 "$ExePath" ; Application
StrCpy $1 "?" ; Parameters
!define SEE_MASK_NOCLOSEPROCESS 0x40
DetailPrint 'Starting "$0" $1'
System::Store S
System::Call '*(i60,i${SEE_MASK_NOCLOSEPROCESS},i$hwndparent,i0,tr0,tr1,i0,i${SW_SHOW},i,i,i,i,i,i,i)i.r0'
System::Call 'SHELL32::ShellExecuteEx(ir0)i.r1'
${If} $1 <> 0
    System::Call '*$0(i,i,i,i,i,i,i,i,i,i,i,i,i,i,i.r1)'
    System::Call 'USER32::WaitForInputIdle(ir1,2000)i'

    System::Call 'KERNEL32::GetProcessId(ir1)i.r2' ; MSDN says this function is XP.SP1+
    StrCpy $3 $2 ; Not found a window yet, keep looping

    ; Call EnumWindows until we find a window matching our target process id in $2
    System::Get '(i.r5, i) iss'
    Pop $R0
    callEnumWindows:
        System::Call 'USER32::EnumWindows(k R0, i) i.s'
        loopEnumWindows:
            Pop $4
            StrCmp $4 "callback1" 0 doneEnumWindows
            System::Call 'USER32::GetWindowThreadProcessId(ir5,*i0r4)'
            ${If} $4 = $2
                System::Call 'USER32::IsWindowVisible(ir5)i.r4'
                ${IfThen} $4 <> 0 ${|} StrCpy $3 0 ${|} ; Found a visible Window
            ${EndIf}
            Push $3 ; EnumWindows callback's return value
            System::Call "$R0"
            Goto loopEnumWindows
        doneEnumWindows:
    ${If} $3 <> 0
        Sleep 1000
        Goto callEnumWindows
    ${EndIf}
    System::Free $R0

    ; Hide installer while app runs
    /*HideWindow
    System::Call 'KERNEL32::WaitForSingleObject(ir1,i-1)'
    BringToFront*/
    System::Call 'KERNEL32::CloseHandle(ir1)'
${EndIf}
System::Free $0
System::Store L
FunctionEnd

Section
${If} $IsSlowFakeApp <> 0
    SetCtlColors $HWNDPARENT 0xffffff 0xdd0000
    FindWindow $0 "#32770" "" $HWNDPARENT
    SetCtlColors $0 0xffffff 0xdd0000
    DetailPrint "This is a fake slow app and it will close soon..."
    Sleep 5555
    Quit
${Else}
    Call StartMyAppAndWaitForWindow
${EndIf}
SectionEnd

【讨论】:

  • 如果您使用 UAC 提升子进程,我不确定这是否会起作用。我不相信 MSDN 记录了您对使用 UAC 提升的子进程的访问权限。
  • 感谢您这么快的回答。它似乎有点工作,除了安装程序隐藏自身(因为它找到了一个窗口)和应用程序的实际窗口出现在屏幕上之间仍有 5-10 秒的延迟。知道为什么吗?是否可以添加一个过滤器,例如 window.classname.startswith("WinForms...")?
  • 另外,我看到您正在有效地使用类似于 ExecWait 的东西,但使用的是 Shell。如果我只想启动应用程序,执行窗口操作,然后继续执行其他一些安装程序逻辑而不等待应用程序退出(即,没有 WaitForSingleObject 内容)怎么办?
  • 您不需要 HideWindow+WaitForSingleObject+BringToFront 但您必须直接调用 ShellExecuteEx 才能获取进程句柄。我将通过调用来更新循环以检查窗口是否可见...
  • 感谢您的更新。虽然它仍然对我不起作用,因为在屏幕上实际可见窗口和隐藏安装程序之间几乎有 10 秒的延迟。我意识到这很棘手,所以我需要考虑其他方法来获取窗口的实际状态。
猜你喜欢
  • 2017-03-30
  • 1970-01-01
  • 1970-01-01
  • 2021-04-20
  • 2020-06-20
  • 1970-01-01
  • 2012-08-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多