Programmatically close an windows app instance from the task manager

941 views
Skip to first unread message

Zoran Sibinovic

unread,
Oct 4, 2017, 2:58:56 AM10/4/17
to Harbour Users
Hi,

there is a way to close instances of various non-harbour apps from the windows task manager through harbour code instead from a windows command line such

c:\Taskkill /IM %ProgramName% /f     - (%ProgramName% - real name of the executable) ?


I was wondering if there is some harbour function that does the same thing.


Sometime I need to start some external apps and sometimes, during testing these app, they can crash. As a consequence, various instances of called external apps can remain alive

and I have to close them manually from the task manager. With an automation, in my code, I can close these tasks if they exists at the beginning of my app so my app can continue normally.


Thanks

Zoran

Zoran Sibinovic

unread,
Oct 4, 2017, 5:30:27 AM10/4/17
to Harbour Users
At the begining of the app

   hb_processRun( "cmd /c Taskkill /IM excel.exe /f" )

or

oQproc := qProcess ( oWnd ) // main window 
oQproc:startDetached("Taskkill /IM excel.exe /f")  

works with a lightning of the cmd window (want to avoid it)

but  wapi_ShellExecute didn't close the opened excel instance, in the above example

Regards
Zoran

Miroslav Georgiev

unread,
Oct 4, 2017, 2:11:10 PM10/4/17
to Harbour Users
Hi Zoran.

Keep in mind that ending Excel forcefully may not be the best solution. Excel may already be started when your app request automation access.
By ending all Excel processes user can lose unsaved changes in other workbooks.

taskkil/tasklist uses WMI internally. Sometimes it's slow to nonresponding (up to 1 minute to end ot list processes). At least this happens frequently in terminal server with multiple users. 

For automation purposes see TCC RT - https://jpsoft.com/products/tcc-rt-runtime.html

Here is my way of cleaning up after Excel error:

function Main()
   
   
?"Excel Data import via Array"
    lSuccess
:= .f.
    IF
( oExcel := win_oleCreateObject( "Excel.Application" ) ) != NIL
       
BEGIN SEQUENCE with { |objErr| errStack( objErr ) }
       
            oExcel
:WorkBooks:Open( cXLS )
            oAS
:= oExcel:ActiveSheet()
       
           
//using these methods: https://www.excelcampus.com/vba/find-last-row-column-cell/
           
            nLastCol
:= oAS:Cells( 1, oAS:Columns:Count ):End( xlToLeft ):Column
            nLastRow
:= oAS:Cells( oAS:Rows:Count, 1 ):End( xlUp ):Row
           
            cRange
:= "A1:" + xlsNum2ColumnStr( nLastCol ) + hb_NtoS( nLastRow )
            aCells
:= oAS:Range( cRange ):Value
           
        RECOVER USING oError
       
       
END SEQUENCE

        oExcel
:Quit() //in case of error - nice cleanup
        IF oError
<> nil
           
?oError:cargo //or --> ErrorMessage( oError )
           
лSuccess := .f.
       
else
            lSuccess
:= .t.
           
?"Data is in aCells"
        ENDIF

    endif
return nil
//---------------------


function xlsNum2ColumnStr( nColumn ) // "A" -> 1, "C" -> 3, "Z" -> 26, "AA" -> 27, "BA" -> 53
local cRet := ""
local nDiv
local nRemainder
local cRemainder


    repeat
        nDiv      
:= Int( nColumn / 27 )
        nRemainder
:= nColumn % 27
       
if nDiv <> 0
            nRemainder
++
        endif
        cRemainder
:= Chr( ASC( "A" ) + nRemainder - 1)
        cRet
:= cRemainder + cRet
        nColumn
:= nDiv
   
until nDiv == 0
   
return cRet


//---------------------


function errStack( oErr ) //error callstack goes to :cargo
local cProc
local nNum := 0, cErr := ""
   
while !empty( cProc := ProcName( ++nNum ) )
        cErr
+= "Called from " + cProc + "(" + hb_NtoS( Procline( nNum ) ) + ") in " + ProcFile( nNum ) + hb_eol()
    enddo
    oErr
:Cargo := cErr
return break( oErr )

Klas Engwall

unread,
Oct 4, 2017, 2:16:33 PM10/4/17
to harbou...@googlegroups.com
Hi Zoran,

> At the begining of the app
>
>    hb_processRun( "cmd /c Taskkill /IM excel.exe /f" )
>
> or
>
> oQproc := qProcess ( oWnd ) // main window
> oQproc:startDetached("Taskkill /IM excel.exe /f")
>
> works with a lightning of the cmd window (want to avoid it)

You could try to add the START command to the mix:

"cmd /C start /B ..."
or
"cmd /C start /MIN ..."

Regards,
Klas

Zoran Sibinovic

unread,
Oct 4, 2017, 3:36:29 PM10/4/17
to Harbour Users
Hi,

thanks to both of you,

the excel files on witch the app works are temp copies of predefined templates without data, except columns headers ant texts.
The app copies in the temp dir the original empty excel or word or else, template and opens it, so the app works with temp files and fills them during the running process. The client just see at the end of the process the filled file without interact with it.
There is no lost of data if the app crashes because they are temp files.

On the other hand, if the app crashes, caused by this or that, openoffice/libreoffice closes its instances from the3 task manager, but office apps dont and than we have a problem.
A classic user/client mostly dont know the existance of the task manager or even how to open it and kill the non visible office tasks, but they exist, than it starts the app
again and from that point the app crashes all the time because the app instances already exists. The best thing that the client can do by itself is to restart the comp to get rid of the problem.

I will  try both suggestions
and let you know if they meet my needs

Thanks again,

Zoran






 
 
 the copyuse I some of my work is to start ancopy an excel file
Message has been deleted

Pete

unread,
Oct 4, 2017, 4:04:56 PM10/4/17
to Harbour Users


On Wednesday, 4 October 2017 09:58:56 UTC+3, Zoran Sibinovic wrote:
Hi,

there is a way to close instances of various non-harbour apps from the windows task manager through harbour code instead from a windows command line such

c:\Taskkill /IM %ProgramName% /f     - (%ProgramName% - real name of the executable) ?


I was wondering if there is some harbour function that does the same thing.




Hi,

since you are on windows you could use WMI API to terminate a process. try this:
/*
  source  : killprocess.prg
  compile : hbmk2 killprocess.prg hbwin.hbc -okill
  run     : kill <progname.exe>
  Pete D. 2017
*/


PROCEDURE
Main( cExeName )
   hb_default
( @cExeName , "nonexistent.exe" )
   
? TerminateProcess( cExeName )

FUNCTION
TerminateProcess( cExeName )
   LOCAL cProcess
, oProcessList, cQuery
   LOCAL nResult
:= -1
   LOCAL hResults
:= { 0 => "Success", 2 => "Access Denied", ;
                     
3 => "Insufficient Privilege", 8 => "Unknown Failure", ;
                     
9 => "Path Not Found", 21 => "Invalid Parameter" }
   cQuery
:= 'Select * from Win32_Process where Name = "' + cExeName + '"'
   oProcessList
:= win_oleCreateObject("WbemScripting.SWbemLocator"):ConnectServer( ".", "root\cimv2" ):ExecQuery( cQuery )
   FOR EACH cProcess IN oProcessList
      nResult
:= cProcess:Terminate()
      IF nResult
<> 0
         EXIT
      ENDIF
   NEXT
   RETURN hb_HGetDef
( hResults, nResult, "Couldn't locate process " + cExeName )

Please note that above function will terminate any running instance of given executable,
which in some cases might not be the desired effect.

regards,
Pete

Zoran Sibinovic

unread,
Oct 4, 2017, 4:10:59 PM10/4/17
to Harbour Users
Thanks Pete

I will try it too

Miroslav Georgiev

unread,
Oct 5, 2017, 2:22:51 PM10/5/17
to Harbour Users
Do you know how to get process PID of the launched EXE under Windows?

Zoran Sibinovic

unread,
Oct 5, 2017, 2:46:15 PM10/5/17
to Harbour Users
Hi Miroslave,

sorry I dont.

In the meanwhile I integrated an alterated version of your suggested code and will post it soon.

I have also tried the Pete's code, but from time to time I've got some random winole errors.
Sometimes the functions worked, some other crash the app.
I have not digged deeply for the cause, but always on the cProcess:Terminate() method with Error WINOLE/1007.

Regards 
Zoran

Pete

unread,
Oct 6, 2017, 9:41:30 AM10/6/17
to Harbour Users


On Thursday, 5 October 2017 21:22:51 UTC+3, Miroslav Georgiev wrote:
Do you know how to get process PID of the launched EXE under Windows?


Hi,

yes. in the function I posted above
you can exploit the `ProcessID` property
like this:
FOR EACH cProcess IN oProcessList
   ? cProcess:ProcessId  // --> process ID numeric value
       ...
NEXT


PS. to Zoran: regarding Error WINOLE/1007
I have used a bit extended version of this function (which includes a basic error handling in case of WMI access/query fail)
for quite a long time (like f.e. in this utility I have made some years ago)
without seeing this error. Can't figure what could be the culprit in your case.


regards,
Pete
 

Miroslav Georgiev

unread,
Oct 6, 2017, 10:16:21 AM10/6/17
to Harbour Users
Hi Pete. 

I mean getting PID of executed process via for example hb_ProcessRun() or something...

Dušan D. Majkić

unread,
Oct 6, 2017, 1:01:02 PM10/6/17
to Harbour Users
> Can't figure what could be the culprit in your case.

Process could already be dead. Or user does not have access right to
kill some process.
> --
> --
> You received this message because you are subscribed to the Google
> Groups "Harbour Users" group.
> Unsubscribe: harbour-user...@googlegroups.com
> Web: http://groups.google.com/group/harbour-users
>
> ---
> You received this message because you are subscribed to the Google Groups
> "Harbour Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to harbour-user...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Pete

unread,
Oct 6, 2017, 1:01:55 PM10/6/17
to Harbour Users


On Friday, 6 October 2017 17:16:21 UTC+3, Miroslav Georgiev wrote:
Hi Pete. 

I mean getting PID of executed process via for example hb_ProcessRun() or something...


Hi,
AFAIK, hb_processOpen() supports an optional parameter that, when passed by reference, holds the PID of the launched executable.
syntax:
hProcess := hb_processOpen( <cCommand>, [ @<cStdIn> ], [ @<cStdOut> ], [ @<cStdErr> ], [ <lDetach> ], [@PID] )

it's the last param above: <@PID>

regards,
Pete





Miroslav Georgiev

unread,
Oct 6, 2017, 1:18:02 PM10/6/17
to Harbour Users
Hi Zoran.

The problem you may have with TerminateProcess might be due to missing begin/end sequence - you should process exceptions (errors) correctly - you need to call TerminateProcess the same way as in the Excel example from my first post - with begin/end sequence- that's important. 

If you have problems with begin/end sequence and capturing 

Miroslav Georgiev

unread,
Oct 6, 2017, 1:37:30 PM10/6/17
to Harbour Users
Hi Pete.

Thank youuu, It's working. I'm so happy now ;) Yesterday I tested but it didn't worked ;)

Here is remainder of hb_ProcessOpen/Close/Value:

2009-01-13 14:07 UTC+0100 Przemyslaw Czerpak (druzus/at/priv.onet.pl)
  * harbour/common.mak
  * harbour/source/rtl/Makefile
  * harbour/include/hbapifs.h
  * harbour/include/hbextern.ch
  + harbour/source/rtl/hbproces.c
  + harbour/source/rtl/hbprocfn.c
    + added C functions hb_fsOpenProcess(), hb_fsProcessValue(),
      hb_fsCloseProcess()
    + added .prg functions HB_OPENPROCESS(), HB_PROCESSVALUE(),
      HB_CLOSEPROCESS()
    Based on xHarbour code by Giancarlo Niccolai.
    Warning: it's possible that they will be changed in the future.
    Please test current implementation. Now few notes about it.
    Each handle returned by HB_OPENPROCESS() have to be closed
    by HB_PROCESSVALUE(). Even if process is killed by HB_CLOSEPROCESS()
    its handle is still open and HB_PROCESSVALUE() has to be executed.
    Type of handle depends on OS. In *nixes it's process PID. In MS-Windows
    it's HANDLE. HB_PROCESSVALUE() attach process exit result in *nixes
    cleaning zombie processes and in Windows closing the HANDLE. If you
    do not call this function then you will have resource leak.
    HB_CLOSEPROCESS() only sends quite request to the process but does
    not execute any cleanup code.
    All communication handles returned in parameters by reference by
    HB_OPENPROCESS() function have to be closed using FCLOSE() function.
    If hStdOut and hStdErr are references to the same variables then
    executing process stdout and stderr is redirected to the only one
    pipe. When handles for stdout, stderr and stdin are not given then
    executed process inherits them from parent process unless <lDetach>
    parameter is not given. In such case they are redirected to null
    device (/dev/null or NUL).

Miroslav Georgiev

unread,
Oct 6, 2017, 1:55:09 PM10/6/17
to Harbour Users
Pete, one more question.

Where did you get the full syntax of hb_ProcessOpen()?
hProcess := hb_processOpen( <cCommand>, [ @<cStdIn> ], [ @<cStdOut> ], [ @<cStdErr> ], [ <lDetach> ], [@PID] )

I can't find it in the sources/changelog of both hb32 and hb34.

On Friday, October 6, 2017 at 8:01:55 PM UTC+3, Pete wrote:

Zoran Sibinovic

unread,
Oct 6, 2017, 1:55:11 PM10/6/17
to Harbour Users
Hi to all,

first of all, thanks to all,

In the meanwhile I attached the altered printing class from the top of the topic.

the :new(), at this moment handle the Kill all the doc tasks with hb_processRun( "cmd /c Taskkill /IM winword.exe /f" ) and then recreate the wanted document, but, introducing the HB_RandomInt(1,99999999) to handle the temporary document name and a timer to retry in the BEGIN/END SEQUENCE I think the need to kill all the Word of Openoffice instances will never happends.

Regarding the winole 1007 error, it refers to me for something not found. I wasn't able to pay more attention in the last few days couse some working deadlinesy. I will investigate later on this according to the previous posts, but at the first look seems it not found some process in the oProcessList to execute cProcess:Terminate().

Till to the next info,

Regards
Zoran
hbqps.prg

Miroslav Georgiev

unread,
Oct 7, 2017, 2:49:05 AM10/7/17
to Harbour Users
Zoran, I see a problem in your code...

RECOVER USING ... is your error handler.

It isn't wise to do more and more things in this error handler because error already happened, and you are doing more error-prone things, and if an error happens again - you get a fatal error and are kicked out of application.
My suggestions are:
1. Leave RECOVER USING empty
2. All code that calls win_oleCreateObject() should be within BEGIN/END SEQUENCE scope.
3. This code should be moved after END SEQUENCE
 IF QTime():currentTime():tostring("hh.mm.ss") >= dieTime:tostring("hh.mm.ss") .AND. oError <> nil
      DO CASE
         CASE
::docType = ".doc" ; ::oNewWordDoc:quit()
         CASE
::docType = ".odt" ; ::oServiceManager:terminate()
      ENDCASE
 
      IF
QtMsg( ,"There is some problem with the document. You can try again but first save all your Word or Openoffice documents and then pres yes.",,{ "Yes","No"} ) = 0

Pete

unread,
Oct 7, 2017, 5:07:52 AM10/7/17
to Harbour Users


On Friday, 6 October 2017 20:55:09 UTC+3, Miroslav Georgiev wrote:
Pete, one more question.

Where did you get the full syntax of hb_ProcessOpen()?
hProcess := hb_processOpen( <cCommand>, [ @<cStdIn> ], [ @<cStdOut> ], [ @<cStdErr> ], [ <lDetach> ], [@PID] )

Hi Miroslav,
Actually, I don't remember where/how I have found it, but if you carefully look into \harbour\src\rtl\hbprocfn.c i'm sure you'll find it yourself (hint: check out line 88)
To generalize a bit my answer, I 'd say that obtaining harbour documentation, is always a "disquiet story". :-)
Usually, I look firstly at changelog and any other supplemental doc file (like  xhb-diff.txt for example); then I probe into sources (something which greatly helps to gradually improve my limited C-knowledge, as well); and then the "try and error" fun, comes into play. When in doubt, I (re)take a look at some nice harbour-doc site/pages that I have spotted during the time. BTW, all this thing have motivated the creation of a harbour wiki on GitHub.

regards,
Pete

Miroslav Georgiev

unread,
Oct 7, 2017, 9:32:10 AM10/7/17
to Harbour Users
Thanks Pete, I regularly look into your wiki - that's where i first saw hb_processOpen (...@PID) but i was curious how/where did you get it for the first time... C code - OK, but this is when you look for a particular info, not just general search. I scratched my head why it's not included in changelog. Since hb_processOpen() first appeared in oldnews.txt, the reason may be because oldnews.txt isn't so thorough like current changelogs.

Zoran Sibinovic

unread,
Oct 7, 2017, 12:47:09 PM10/7/17
to Harbour Users
Hi Miroslav,

In the BEGIN SEQUENCE / END SEQUENCE I kept RECOVERY USING oError and only the line that can cause the BREAK and implemented the original Pete's process terminator functions. Everything works very well without errors.

The recursion, if an error shows, works too without consequences.

/*-------------------------------------------------------------------------*/
METHOD HbDocPrintSystem:new(docName)  // path+doc name without extension

LOCAL oError:=" ", dieTime

   DO CASE
      CASE ( ::oNewWordDoc := win_oleCreateObject( "Word.Application" ) ) != NIL                ; ::docType:=".doc"
      CASE ( ::oServiceManager := win_oleCreateObject( "com.sun.star.ServiceManager" ) ) != NIL ; ::docType:=".odt"
      OTHERWISE
           QTMSG(,"Error, you have not installed MS Word, Openoffice or Libreoffice.")
           RETURN nil

   ENDCASE

   ::origDoc := (docName+::docType())

   dieTime:= QTime():currentTime():addMSecs( 3000 )
   DO WHILE QTime():currentTime():tostring("hh.mm.ss") < dieTime:tostring("hh.mm.ss") .AND. oError <> nil
      oError:=nil
      ::tempDoc := cLongTmp+ALLTRIM(STR(HB_RandomInt(1,99999999)))+::docType()    
      
      BEGIN SEQUENCE WITH { |oError| ERRSTACK( oError ) }
  
            COPY FILE (::origDoc) TO (::tempDoc)

        RECOVER USING oError

  END SEQUENCE

      IF QTime():currentTime():tostring("hh.mm.ss") >= dieTime:tostring("hh.mm.ss") .AND. oError <> nil
         DO CASE
            CASE ::docType = ".doc" ; ::oNewWordDoc:quit()
            CASE ::docType = ".odt" ; ::oServiceManager:terminate()
         ENDCASE
         IF QtMsg( ,"There is some problem with the document. You can try again but first save all your Word or Openoffice documents and then pres yes.",,{ "Yes","No"} ) = 0


            DO CASE
               CASE ::docType = ".doc" 
                   TMAPPTERMINATOR("winword.exe")                // task manager app terminator (Pete's PROCEDURE Main)
       ::oNewWordDoc := win_oleCreateObject( "Word.Application" )
  
               CASE ::docType = ".odt" 
                    TMAPPTERMINATOR("soffice.bin")                  // task manager app terminator (Pete's PROCEDURE Main)
    ::oServiceManager := win_oleCreateObject( "com.sun.star.ServiceManager" ) 
        ENDCASE

            dieTime:= QTime():currentTime():addMSecs( 3000 )   
    LOOP

         ELSE
      
            RETURN nil
     ENDIF
      ENDIF  
 
      LOOP
   ENDDO   

...

Thanks to all for helping in this topic.

Regards
Zoran

Miroslav Georgiev

unread,
Oct 8, 2017, 2:41:55 PM10/8/17
to Harbour Users
Zoran, 

1. Isn't it too complicated to use BEGIN/END SEQUENCE just for a single copy?
Why you don't use instead:
hb_vfCOPYFILE( <cFileSrc>, <cFileDst> ) -> <nResult>

I think word isn't locking files exclusively because word can open them in READ ONLY mode witch is in fact a copy of original file.

if cFileSrc is somehow "locked" by Word (if I understood your problem, and above code can't copy) you have more control with following code by opening in SHARED mode:
nHandle := hb_vfOpen( <cFileSrc>, FO_READ + FO_SHARED )
if nHandle == nil
     
?"error opening file: " + <cFileSrc> + "; error: " + Str_( FERROR() ) + " " + DOSErr2Str( FERROR() ) )
endif
//if you need size - nFileSize := hb_vfSize( nHandle )
cContent
:= hb_vfREAD( nHandle, @cBuffer )

and write contents to new file.

2.From my experience It's risky to not enclose win_oleCreateObject( "Word.Application" ) with BEGIN/END SEQUENCE. Can anyone comment on this? 

3. Why you generate more and more temp file names inside the loop. Isn't one enough?:
::tempDoc := cLongTmp+ALLTRIM(STR(HB_RandomInt(1,99999999)))+::docType()    

Can you generate one name outside loop and try copy/open inside loop multiple times (if needed). Isn't it better to copy file first, then call Word.

4. You are doing 3 things at once. why?
create word object
copy file 
forcefully terminate word

Can't you do them one by one, check result and act appropriately

4.  CASE ::docType = ".doc" ; ::oNewWordDoc:quit()
Isn't it  better to use
if  ::oNewWordDoc <> nil //if object is not created for some reason, whis would be NIL
   
::oNewWordDoc:quit()
endif


5. time elapsed.

I still not use QT but QTime():currentTime():addMSecs( 3000 ) seems unnecessary complicated to me. Can't you just use something like:


nStart
:= hb_milliseconds()
while hb_milliseconds() - nStart > 3000


enddo

6. following code
 
              dieTime:= QTime():currentTime():addMSecs( 3000 )
                               
              LOOP    
<---
          ELSE
              RETURN
nil
          ENDIF
      ENDIF  
 
      LOOP
<---
   ENDDO  

both LOOPs aren't needed. Try use loop-s where it's absolutely required to make code more clear.
Reply all
Reply to author
Forward
0 new messages