Page 1 of 2

Kill-N-Launch: Kill one app then Launch another

Posted: Sun Jul 30, 2006 12:02 am
by Straker
The basis for this program can be found in this thread

The usage is detailed in the header section of the code.

This code is for 3.94 but to make it 4.0 simply remove the PROCESSENTRY32 Structure declaration.

Code: Select all

; Kill-N-Launch by Straker 7/29/2006
; Kills one process (usually the calling process) and launches another
;
; Used mainly to broker an updater process by killing the calling main app so that
; it can be overwritten by the called updater app.  In other words, sometimes the
; main app exe is locked when the updater is a child process of the main app (when 
; launched from the main app) preventing it from being overwritten by its child updater 
; process.  This exe should instead be called by the main app, so as to make sure it
; has been terminated before calling the updater app (which becomes a child process of
; this broker app instead of its parent main app).
;
; Includes code by oryaaaaa and Hi-Toro
; Inspiration by Xombie
;
; Compile as knl.exe
;
; usage:  knl.exe -t apptokill.exe [-d millisecs ] [-l apptolaunch.exe [-a argument [-w working dir]]] [-v]
; ------------------------------------------------------------------------------------
; example:  knl.exe -t mainapp.exe -d 3000 -l updater.exe -v
; 
; This example will kill the passed process mainapp.exe identified by -t argument but 
; will delay for 3 seconds (-d 3000) giving the mainapp.exe time to close itself. It 
; will then launch the updater.exe specified by the -l argument.
;
; This app can be used 4 ways:
; 1) To just kill a process (-t) with no delay;
; 2) kill process with delay (-t and -d);
; 3) kill process then launch another app (-t and -l) with no delay;
; 4) kill process with delay then launch another app (-t and -d and -l).
;
; -a = The argument(s) if any for the app to launch (can use multiple times).
; -w = The working directory for the app to launch.
; -v = Verbose mode: show messageboxes.
; ------------------------------------------------------------------------------------
;

;- Globals and Enums
Global gDelay.l, gPID.l, gArgCount.l
Global gLaunch.b, gVerbose.b
Global gAppToLaunch.s, gAppToKill.s, gCommandParm.s, gAppName.s, gArgs.s, gWorkDir.s

#PROCESS_TERMINATE = $1
#PROCESS_CREATE_THREAD = $2
#PROCESS_VM_OPERATION = $8
#PROCESS_VM_READ = $10
#PROCESS_VM_WRITE = $20
#PROCESS_DUP_HANDLE = $40
#PROCESS_CREATE_PROCESS = $80
#PROCESS_SET_QUOTA = $100
#PROCESS_SET_INFORMATION = $200
#PROCESS_QUERY_INFORMATION = $400
#PROCESS_ALL_ACCESS = #STANDARD_RIGHTS_REQUIRED | #SYNCHRONIZE | $FFF

;- Structures
Structure PROCESSENTRY32
  dwSize.l
  cntUsage.l
  th32ProcessID.l
  th32DefaultHeapID.l
  th32ModuleID.l
  cntThreads.l
  th32ParentProcessID.l
  pcPriClassBase.l
  dwFlags.l
  szExeFile.b [#MAX_PATH]
EndStructure

;- Procedures
Procedure.l CheckRunningExe(FileName.s)
  ; Original procedure by oryaaaaa
  ; modified by Straker to return PID instead of True/False
  ; returns 0 if process is not found running.
  lReturnPid.l = 0
  Protected snap.l , Proc32.PROCESSENTRY32 , dll_kernel32.l
  FileName = GetFilePart( FileName )
  dll_kernel32 = OpenLibrary (#PB_Any, "kernel32.dll")
  If dll_kernel32
    snap = CallFunction (dll_kernel32, "CreateToolhelp32Snapshot",$2, 0)
    If snap
      Proc32\dwSize = SizeOf (PROCESSENTRY32)
      If CallFunction (dll_kernel32, "Process32First", snap, @Proc32)
        While CallFunction (dll_kernel32, "Process32Next", snap, @Proc32)
          If (UCase(PeekS (@Proc32\szExeFile))=UCase(FileName))
            lReturnPid.l = PeekL (@Proc32\th32ProcessID)
            Break
          EndIf
        Wend
      EndIf   
      CloseHandle_ (snap)
    EndIf
    CloseLibrary (dll_kernel32)
  EndIf
  ProcedureReturn lReturnPid.l
EndProcedure

Procedure.b KillProcess(pid)
  ; Original procedure by Hi-Toro
  result = #False
  phandle = OpenProcess_(#PROCESS_TERMINATE, #False, pid)
  If phandle <> #Null
    If TerminateProcess_(phandle, 1)
      result = #True
    EndIf
    CloseHandle_(phandle)
  EndIf
  ProcedureReturn result
EndProcedure

;- Program Initialization
gDelay.l = 0
gLaunch.b = #False
gVerbose.b = #False
gAppToLaunch.s = ""
gAppToKill.s = ""
gAppName.s = "Kill-N-Launch"
gArgCount.l = 0
gArgs.s = ""
gWorkDir.s = ""
lRetVal.l = 0

gCommandParm.s = ProgramParameter()

If (gCommandParm.s = "")
  MessageRequester(gAppName.s,"Usage:"+Chr(13)+Chr(13)+"knl.exe -t apptokill.exe [-d millisecs ] [-l apptolaunch.exe [-a argument [-w working dir]]] [-v]")
  Goto TheEnd
Else
  While (gCommandParm.s <> "")
    If (LCase(gCommandParm.s) = "-t")
      ; next argument is the app to kill
      gCommandParm.s = ProgramParameter()
      gAppToKill.s = Trim(gCommandParm.s)
    EndIf
    If (LCase(gCommandParm.s) = "-d")
      ; next argument is the delay milliseconds
      gCommandParm.s = ProgramParameter()
      gDelay.l = Val(gCommandParm.s)
    EndIf
    If (LCase(gCommandParm.s) = "-l")
      ; next argument is the app to launch
      gCommandParm.s = ProgramParameter()
      gAppToLaunch.s = Trim(gCommandParm.s)
      gLaunch.b = #True
    EndIf
    If (LCase(gCommandParm.s) = "-v")
      ; set verbose flag
      gVerbose.b = #True
    EndIf
    If (LCase(gCommandParm.s) = "-a")
      ; next argument is an argument for the App to Launch
      gCommandParm.s = ProgramParameter()
      gArgCount.l = (gArgCount.l + 1)
      gArgs.s = (gArgs.s + " " + gCommandParm.s)
    EndIf
    If (LCase(gCommandParm.s) = "-w")
      ; next argument is the working dirctory for the App to Launch
      gCommandParm.s = ProgramParameter()
      gWorkDir.s = Trim(gCommandParm.s)
    EndIf
    gCommandParm.s = ProgramParameter()
  Wend
EndIf

;- Main Loop

; --- Delay section...
If (gDelay.l > 0)
  ; user has specified a delay...
  Delay(gDelay.l)
EndIf

; --- Kill process section...
gPID.l = CheckRunningExe(gAppToKill.s)
; Is the process still running?
If (gPID.l > 0)
  ; yep still running...hunt it down and kill it...
  If (KillProcess(gPID.l)=#False)
    ; could not kill it...
    If (gVerbose.b = #True)
      MessageRequester(gAppName.s,"Unable to terminate process:"+Chr(13)+Chr(13)+Str(gPID.l)+" : " + gAppToKill.s)
    EndIf
    Goto TheEnd
  EndIf
EndIf

; --- Launch app section...
If (gLaunch.b = #True)
  If (FileSize(gAppToLaunch.s) > -1)
    ; found the file...try to launch it...
    If (gArgCount.l > 0)
      lRetVal.l = RunProgram(gAppToLaunch.s, gArgs.s, gWorkDir.s)
    Else
      lRetVal.l = RunProgram(gAppToLaunch.s)
    EndIf
    If (lRetVal.l = 0)
      ; could not launch
      If (gVerbose.b = #True)
        MessageRequester(gAppName.s,"Unable to launch application:"+Chr(13)+Chr(13)+gAppToLaunch.s)
      EndIf
      Goto TheEnd
    EndIf
  Else
    If (gVerbose.b = #True)
      MessageRequester(gAppName.s,"Unable to locate file to launch:"+Chr(13)+Chr(13)+gAppToLaunch.s)
    EndIf
    Goto TheEnd
  EndIf
EndIf

Goto TheEnd

;- Cleanup and Exit
TheEnd:
End
; --- ### ---


Posted: Sun Jul 30, 2006 12:21 am
by mike74
Straker,

I can think of a couple reasons why you might not want to use a .bat file to launch the second process, but I'm curious about whether you had different ones. The reasons I can think of are:

1. You don't want users modifying or losing the .bat file.

2. It is simpler to use Kill-N-Launch (now that it exists!)

Did you have other reasons for not using a .bat? Thanks, and thanks for the code!

Mike

Posted: Sun Jul 30, 2006 12:35 am
by Dare
Thank you, Straker.

Posted: Sun Jul 30, 2006 12:44 am
by Straker
mike74 wrote:Did you have other reasons for not using a .bat?
Whether a BAT, CMD, or EXE, the main process could still be running (or the file locked), thus preventing the overwriting of the file, plus the COMMAND/CMD to call the bat would be a child process of the main app.

So, the best solution is one where the child process commits parenticide before launching its own child process which intends to overwrite the parent EXE.

Cheers

Posted: Sun Jul 30, 2006 12:57 am
by freak
This is overkill imho. There is no need to kill the parent process. It can just end normally.
All the child has to do is wait a few milliseconds for that to happen.
A simple wait loop until the delete/copy succeeds would be enough.

The PureBasic SmartUpdater uses that since the beginning, and it works fine.

Posted: Sun Jul 30, 2006 1:36 am
by Straker
I appreciate your comments freak. My updater is being called by an EXE which is not written in PureBasic, and it doesn't always release itself when it Runs an external program (although it should). Now, if everything was written in PureBasic...

Posted: Sun Jul 30, 2006 7:17 am
by KarLKoX
ShellExecute + WaitForSingleObject do the same thing ;)

Posted: Sun Jul 30, 2006 4:17 pm
by Straker
Thanks for the tip. But what is the object that is passed to WaitForSingleObject()? The Process ID? And what should I expect for return if the process does not terminate or if it does?

Thanks

Posted: Sun Jul 30, 2006 5:44 pm
by KarLKoX
Fill the SHELLEXECUTEINFO structure, call ShellExecuteEx(shellexecinfostruct) then call WaitForSingleObject with the hProcess member of the SHELLEXECUTEINFO structure.

Posted: Sun Jul 30, 2006 9:52 pm
by utopiomania
Straker, first, thanks for the contribution, but is there a reason for not using a simple script to do the same?
I'm using one here http://www.purebasic.fr/english/viewtopic.php?t=18158 (Procedure DeleteThisPath(Path.s, All) ).

It's written to disk then called to kill the calling program, it's folders and then the delete itself to get rid of
everything. I've also used the same tecnique in an update example where the script kills the calling app,
renames the update, calls it and then commit binary suicide.

It works very well so far, but if there's a good reason for avoiding a script for this, I would appreciate hearing
about it. :?:

Posted: Mon Jul 31, 2006 12:30 am
by Straker
@utopiomania

Well, my main reason for doing it this way, is that the Main Application is not written in PureBasic and does not always exit cleanly, so I needed to make sure that calling process was killed before calling the updater process.

I didn't use a script because if the main app is still running, it won't delete or overwrite because the file is locked. By using Kill-N-Launch, I know that the calling app is dead otherwise, the secondary app won't launch.

Is this overkill, yes. But is usable and callable from other languages, or you just use it to kill processes by name.

BTW, I noticed in your DeleteThisPath() procedure you mentioned this:

Code: Select all

;Do NOT use RunProgram() here:
ShellExecute_(0, "", Script, "", Left(Path, 3), #PB_Program_Hide)
Why not just use RunProgram?

Posted: Mon Jul 31, 2006 9:39 am
by Trond
RunProgram specifies the "open" verb.

Posted: Mon Jul 31, 2006 12:56 pm
by utopiomania
Straker wrote:
Why not just use RunProgram?
I ran into some problems using RunProgram where the empty path and the script itself from time to time
wasn't deleted. The files were visible in Exolorer, but Windows reported them to be non-existent when I
tried to delete them, and I had to reboot to get rid of them.

This has never happened using ShellExecute_(), so I assumed there was something funny going on
with RunProgram() in this particular case and stopped using it.

Posted: Mon Jul 31, 2006 3:54 pm
by Straker
Trond wrote:RunProgram specifies the "open" verb.
utopiomania wrote:This has never happened using ShellExecute_(), so I assumed there was something funny going on
with RunProgram() in this particular case and stopped using it.
Thanks guys. I think this may be the source of my problem. PowerBuilder (the language of my main application) must also be issuing the "open" verb in its Run() command.

Could it be that Windows is interpreting the its ShellExecute with the "open" verb as a somewhat synchronous call, thus preventing full deletion of the calling parent program?

Posted: Mon Jul 31, 2006 4:49 pm
by mike74
Straker wrote: Could it be that Windows is interpreting the its ShellExecute with the "open" verb as a somewhat synchronous call, thus preventing full deletion of the calling parent program?
Straker: Did you mean RunProgram? If you meant ShellExecute, that seems to contradict what utopiomania said about it working for him.