Making a shell script survive quit

I need to start a shell script that survives quit of the calling application. I can do this on Mac/Linux with the nohup command like this:

nohup myscript &

That works perfectly, and I understand that this is the natural way things work on Windows anyway, but need to test that. What I’m looking for is a simple script that will take some time to run (say 10 seconds) and will do something trivial but measurable like write to a log file. Can someone give me a suggestion?

(Note: I never, ever create Windows shell scripts so I am learning the language for the first time.)

In windows, you can write a .BAT file to %tmp% and then use ShellExecuteEX with the SEE_MASK_ASYNCOK flag set. This works for me.

http://msdn.microsoft.com/en-us/library/windows/desktop/bb759784(v=vs.85).aspx

These are two functions (ShellWait and ShellDontWait) I use frequently (in my RB2007, so may need some changes for Xojo). Never know it helps somehow :slight_smile:

Function ShellDontWait(sInCommandline as String, Visible as Boolean) As Boolean
  #if targetWin32 then
        
    Declare Function CreateProcessA Lib "kernel32" (lpApplicationName As integer, lpCommandLine As cString, lpProcessAttributes As integer, lpThreadAttributes As integer, bInheritHandles As integer, dwCreationFlags As integer, lpEnvironment As integer, lpCurrentDirectory As cstring, lpStartupInfo As ptr, lpProcessInformation As ptr) As integer
           
    Const SW_HIDE = 0
    
    Const SW_SHOWNORMAL = 1
    Const NORMAL_PRIORITY_CLASS = &H20
    
    Dim Ret As integer
    
    Dim StartInf As memoryblock
    Dim Proc as MemoryBlock
    
    Startinf = newmemoryBlock(76)
    
    Startinf.long(44) = 1 /// set flags to use showwindow
    If Visible then
      Startinf.long(48) = SW_SHOWNORMAL
    Else
      Startinf.long(48) = SW_HIDE
    End
    Proc = newmemoryBlock(16)
    StartInf.long(0) = StartInf.size
    
    Ret = CreateProcessA(0,sInCommandline, 0, 0, 1,NORMAL_PRIORITY_CLASS, 0, "C:\", StartInf, Proc) //replace C:\\ with your desired working directory
    
    Return True
  #endif
End Function
Function ShellWait(sInCommandline as String, Visible as Boolean) As Boolean
  #if targetWin32 then
    
    Declare Function CreateProcessA Lib "kernel32" (lpApplicationName As integer, lpCommandLine As cString, lpProcessAttributes As integer, lpThreadAttributes As integer, bInheritHandles As integer, dwCreationFlags As integer, lpEnvironment As integer, lpCurrentDirectory As cstring, lpStartupInfo As ptr, lpProcessInformation As ptr) As integer
    
    Declare Sub Sleep Lib "kernel32" Alias "Sleep" (dwMilliseconds As integer)
    Declare Function WaitForSingleObject Lib "kernel32" (hHandle As integer, dwMilliseconds As integer) As integer
    Declare Function CloseHandle Lib "kernel32" (hObject As integer) As integer
    
    Const SW_HIDE = 0
    Const SW_SHOWNORMAL = 1
    Const WAIT_OBJECT_0 = 0
    Const WAIT_TIMEOUT = &H102
    Const NORMAL_PRIORITY_CLASS = &H20
    
    Dim Ret As integer
    Dim StartInf As memoryblock
    Dim Proc as MemoryBlock
    
    Startinf = newmemoryBlock(76)
    
    Startinf.long(44) = 1 /// set flags to use showwindow
    If Visible then
      Startinf.long(48) = SW_SHOWNORMAL
    Else
      Startinf.long(48) = SW_HIDE
    End
    Proc = newmemoryBlock(16)
    StartInf.long(0) = StartInf.size
    
    Ret = CreateProcessA(0,sInCommandline, 0, 0, 1,NORMAL_PRIORITY_CLASS, 0, "C:\", StartInf, Proc) //replace C:\\ with your desired working directory
    
    If Ret <> 0 then
      Do
        Ret = WaitForSingleObject(proc.long(0), 300) /// longer than 5000 will cause app to show as 'not responding' in the task list
        
        if Ret = WAIT_OBJECT_0 then
          /// Application has terminated
          Ret = CloseHandle(proc.long(0))
          return true
        elseif Ret = WAIT_TIMEOUT then
          /// keep waiting
        else
          Ret = CloseHandle(proc.long(0))
          
          return false
        end
        app.DoEvents
        
        Sleep(300)
      Loop
    Else
      /// bad command line
      return false
    End
  #endif
End Function

Thanks to both of you, I will review these both.

I still need a simple script that I can use to make sure that it’s running after my app quits though.

windows 7 and 8 batch files have a TIMEOUT command, but earlier versions did not, so folks would make do with a PING command to localhost as a way to fake a delay.

You could use something like this:

echo "here we go, waiting 10 seconds"
TIMEOUT 10
echo "hit any key to exit"
PAUSE
CLS
EXIT

See http://www.robvanderwoude.com/wait.php

OH, I never thought of this. So, unlike in Mac/Linux, I can create a BAT file, tell it to open, then let me app quit. I’ve seen other apps on Windows use the shell this way, so this is acceptable on that platform, yes?

Yes :slight_smile:

Fantastic. Thanks for your help.

Now I just have to figure out how to write the actual script. :slight_smile:

[quote=150408:@Alain Bailleul]These are two functions (ShellWait and ShellDontWait) I use frequently (in my RB2007, so may need some changes for Xojo). Never know it helps somehow :slight_smile:

Function ShellDontWait(sInCommandline as String, Visible as Boolean) As Boolean
  #if targetWin32 then
        
    Declare Function CreateProcessA Lib "kernel32" (lpApplicationName As integer, lpCommandLine As cString, lpProcessAttributes As integer, lpThreadAttributes As integer, bInheritHandles As integer, dwCreationFlags As integer, lpEnvironment As integer, lpCurrentDirectory As cstring, lpStartupInfo As ptr, lpProcessInformation As ptr) As integer
           
    Const SW_HIDE = 0
    
    Const SW_SHOWNORMAL = 1
    Const NORMAL_PRIORITY_CLASS = &H20
    
    Dim Ret As integer
    
    Dim StartInf As memoryblock
    Dim Proc as MemoryBlock
    
    Startinf = newmemoryBlock(76)
    
    Startinf.long(44) = 1 /// set flags to use showwindow
    If Visible then
      Startinf.long(48) = SW_SHOWNORMAL
    Else
      Startinf.long(48) = SW_HIDE
    End
    Proc = newmemoryBlock(16)
    StartInf.long(0) = StartInf.size
    
    Ret = CreateProcessA(0,sInCommandline, 0, 0, 1,NORMAL_PRIORITY_CLASS, 0, "C:\", StartInf, Proc) //replace C:\\ with your desired working directory
    
    Return True
  #endif
End Function

[code]
Function ShellWait(sInCommandline as String, Visible as Boolean) As Boolean
#if targetWin32 then

Declare Function CreateProcessA Lib "kernel32" (lpApplicationName As integer, lpCommandLine As cString, lpProcessAttributes As integer, lpThreadAttributes As integer, bInheritHandles As integer, dwCreationFlags As integer, lpEnvironment As integer, lpCurrentDirectory As cstring, lpStartupInfo As ptr, lpProcessInformation As ptr) As integer

Declare Sub Sleep Lib "kernel32" Alias "Sleep" (dwMilliseconds As integer)
Declare Function WaitForSingleObject Lib "kernel32" (hHandle As integer, dwMilliseconds As integer) As integer
Declare Function CloseHandle Lib "kernel32" (hObject As integer) As integer

Const SW_HIDE = 0
Const SW_SHOWNORMAL = 1
Const WAIT_OBJECT_0 = 0
Const WAIT_TIMEOUT = &H102
Const NORMAL_PRIORITY_CLASS = &H20

Dim Ret As integer
Dim StartInf As memoryblock
Dim Proc as MemoryBlock

Startinf = newmemoryBlock(76)

Startinf.long(44) = 1 /// set flags to use showwindow
If Visible then
  Startinf.long(48) = SW_SHOWNORMAL
Else
  Startinf.long(48) = SW_HIDE
End
Proc = newmemoryBlock(16)
StartInf.long(0) = StartInf.size

Ret = CreateProcessA(0,sInCommandline, 0, 0, 1,NORMAL_PRIORITY_CLASS, 0, "C:\", StartInf, Proc) //replace C:\\ with your desired working directory

If Ret <> 0 then
  Do
    Ret = WaitForSingleObject(proc.long(0), 300) /// longer than 5000 will cause app to show as 'not responding' in the task list
    
    if Ret = WAIT_OBJECT_0 then
      /// Application has terminated
      Ret = CloseHandle(proc.long(0))
      return true
    elseif Ret = WAIT_TIMEOUT then
      /// keep waiting
    else
      Ret = CloseHandle(proc.long(0))
      
      return false
    end
    app.DoEvents
    
    Sleep(300)
  Loop
Else
  /// bad command line
  return false
End

#endif
End Function
[/code][/quote]

I guess the code below will do the same.

  Dim oWshShell As OleObject
  Dim intRun As Integer
  
  // oWshShell.Run(strCommand, [intWindowStyle], [bWaitOnReturn])
  // When bWaitOnReturn is True wait till notepad is closed 
  // When bWaitOnReturn is False continue the code and display msgbox
  // More informations see: http://msdn.microsoft.com/en-us/library/d5fk67ky%28v=vs.84%29.aspx
  
  // Opens notepad and first Display Msgbox when notepad is closed
  
  oWshShell = New Oleobject("WScript.Shell")
  intRun = oWshShell.Run("notepad ", 1, true)  
  
  Msgbox("Return Value: "+str(intRun))
  
  exception err as oleexception
  msgbox err.message
  oWshShell = Nil

Just to follow up, I didn’t have to do anything fancy on Windows. I just save the BAT file and call it from a a Shell. It survives the application quit.

Mmm or just use “start” - then you won’t need any batch file.

Example:

start notepad.exe

You should be able to use windows scheduler at command invoked from a shell. http://support.microsoft.com/KB/313565

Dim oWshShell As OleObject
  Dim intRun As Integer
  
  // oWshShell.Run(strCommand, [intWindowStyle], [bWaitOnReturn])
  // When bWaitOnReturn is True wait till notepad is closed 
  // When bWaitOnReturn is False continue the code and display msgbox
  // More informations see: http://msdn.microsoft.com/en-us/library/d5fk67ky%28v=vs.84%29.aspx
  
  // Runs MyProgram from Windows Scheduler
  // at command see: http://support.microsoft.com/KB/313565

  oWshShell = New Oleobject("WScript.Shell")
  // Schedule job
 // 
  intRun = oWshShell.Run("at 20:00 /every:M,T,W,Th,F cmd /c  MyProgram.exe", 0, true)  
  
  Msgbox("Return Value: "+str(intRun))
  
  exception err as oleexception
  msgbox err.message
  oWshShell = Nil

[quote=156848:@John Hansen]You should be able to use windows scheduler at command invoked from a shell. http://support.microsoft.com/KB/313565

Dim oWshShell As OleObject
Dim intRun As Integer

// oWshShell.Run(strCommand, [intWindowStyle], [bWaitOnReturn])
// When bWaitOnReturn is True wait till notepad is closed
// When bWaitOnReturn is False continue the code and display msgbox
// More informations see: http://msdn.microsoft.com/en-us/library/d5fk67ky(v=vs.84).aspx

// Runs MyProgram from Windows Scheduler
// at command see: http://support.microsoft.com/KB/313565

oWshShell = New Oleobject(“WScript.Shell”)
// Schedule job
//
intRun = oWshShell.Run(“at 20:00 /every:M,T,W,Th,F cmd /c MyProgram.exe”, 0, true)

Msgbox("Return Value: "+str(intRun))

exception err as oleexception
msgbox err.message
oWshShell = Nil[/quote]

Oops… Too fast to submit, and edit mode did not work. Only the comments in code should have been changed. attached new one:

  // Runs MyProgram from Windows Scheduler
  // Windows Scheduler 'at' command see: http://support.microsoft.com/KB/313565

  Dim oWshShell As OleObject
  Dim intRun As Integer
  
  oWshShell = New Oleobject("WScript.Shell")
  // Schedule job
  intRun = oWshShell.Run("at 20:00 /every:M,T,W,Th,F cmd /c  MyProgram.exe", 0, true)  
  
  Msgbox("Return Value: "+str(intRun))
  
  oWshShell = Nil

  exception err as oleexception
  msgbox err.message

I remember Norman explained in some thread the difference between the shell in Mac OS X and Windows, which resulted in the process being tied to the parent in Unix, but not in Windows.

Coming from the future, if anyone else finds this question, an easy way is to run “cmd /c EXE”.

Example:

Dim s As Shell
s = New Shell
s.Execute("cmd /c my_executable_file)

or

Dim s As Shell
s = New Shell
s.Execute("cmd /c """ + exefile.NativePath + """")

On windows there is no actual concept of parenting. Say you have desktop application with a shell class running a console application.

Once the shell is running the console app, and the desktop app quits, the console app remains running.
So you could even use a console app to do what you want or a batch script…

On other platforms (linux, mac) you need to deamonize to get this to work.

1 Like

FYI, this thread was created while I was trying to figure out how to make Kaju work. Kaju does this, i.e., creates a shell script that survives quit, on all platforms.

1 Like

Copy Mike’s code into a text editor and save it as a .bat file.

Edit: actually, that doesn’t seem to be working in a Shell.

If you use the START command with your .bat file or .exe, it separates the command from your app, so it will survive your app quitting.

2 Likes

Please explain the benefit to creating a separate BAT file to launch your shell script using the START command rather than launching the shell script directly using the method I gave above, specifically

s.Execute("cmd /c """ + exefile.NativePath + """")

I use the above, and if it’s going to cause problems I’d like to know.

Thanks,

Jason