Search code examples
windowsuser-interfaceprocessnsis

NSIS - See if a given process has shown a window


I run a certain process via ExecShell from my NSIS installer. That process takes sometime to start (5 - 40 seconds maybe), during which I want the NSIS window to remain visible.

The problem is that while the process itself starts almost instantly, it is sometime before a user would see anything, thus I want the NSIS installer window to remain visible UNTIL the started process's main window (or any window for that matter is shown).

What I need to know thus is how do I get processid from ExecShell (can't use Exec or ExecWait for other reasons), and then how do I use that process id to see if the window has been shown (I know I can do it via a simple sleep, check, goto loop, so basically I am trying to figure out the check part of it)?

So, how exactly can I know if the process I spawned using ShellExec has shown a GUI. This needs to work in Windows XP SP3 and up.

Thank you.


Solution

  • 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