Ejecutar un archivo externo y esperar a que termine para eliminarlo

Iniciado por ThunderCls, 4 Marzo 2011, 16:31 PM

0 Miembros y 1 Visitante están viendo este tema.

ThunderCls

Un saludo a todos los amigos del foro y ti que lees este post.
Vamos a ver, tengo esta aplicacion cuyo objetivo es el de ejecutar un archivo externo (.txt, .pdf, etc) con ShellExecute, ahora, lo que quiero es que, una vez ejecutado y cerrado dicho archivo, se borre del HD sin mas. Lo que quiero es algo asi como un fichero temporal, suponiendo que sea un txt, pues que una vez mi aplicacion lo abra con el bloc de notas y luego el usuario lo cierre, que este se elimine. La cuestion esta en que, como muchos pensaran, podria programar un DeleteFile justo antes de salir de mi aplicacion y listo, pero lo que quiero hacer es lo siguiente:

Variante#1
========

1- Ejecuto mi aplicacion
2- Mi aplicacion ejecuta el archivo (txt, pdf, doc, etc)
3- Mi aplicacion se cierra
4- Se continua abriendo el archivo con su programa predeterminado
5- Se termina de trabajar con el archivo abierto y se cierra
6- El archivo se elimina automaticamente

Ahora, otra variante que pudiera hacer seria moviendo el punto 3:

Variante#2
========

1- Ejecuto mi aplicacion
2- Mi aplicacion ejecuta el archivo (txt, pdf, doc, etc)
3- Se continua abriendo el archivo con su programa predeterminado
4- Se termina de trabajar con el archivo abierto y se cierra
5- Mi aplicacion sabe que se cerro el archivo y lo elimina
6- Mi aplicacion se cierra

Ahora, la cosa se complica al no ser un .exe el que se ejecuta, sino cualquier tipo de archivo que depende de una segunda aplicacion para abrirse, por lo que no puedo hacer nada con WaitForSingleObject o cualquier API para el trabajo con procesos y su monitoreo (corriganme si estoy equivocado).

Variante#1
========

Para esto que quiero he estado investigando un poco las API's CreateFile y ShellExecuteEx. En cuanto a la primera, he visto un parametro muy interesante, me refiero a:

Código (cpp) [Seleccionar]
DWORD dwFlagsAndAttributes

especificamente en la bandera:

FILE_FLAG_DELETE_ON_CLOSE: The system deletes a file immediately after all of its handles are closed, which includes the specified handle and any other open or duplicated handles. If there are existing open handles to a file, the call fails unless they were all opened with the FILE_SHARE_DELETE share mode. Subsequent open requests for the file fail, unless the FILE_SHARE_DELETE share mode is specified.

Y yo tengo esto:

Código (cpp) [Seleccionar]
SECURITY_ATTRIBUTES secAttr;
ZeroMemory(&secAttr, sizeof(SECURITY_ATTRIBUTES));
secAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
secAttr.bInheritHandle = true; // Para que sea el mismo handle para todos los procesos

HANDLE hFile = CreateFile(filename,
                          GENERIC_ALL, // GENERIC_READ
                          FILE_SHARE_DELETE + FILE_SHARE_READ,
                          &secAttr, // 0
                          OPEN_EXISTING,
                          FILE_FLAG_DELETE_ON_CLOSE, // FILE_ATTRIBUTE_NORMAL
                          NULL);


Como ven eso es justo lo que necesito, pero aqui viene el otro problema, y es que una vez tomado el handle del archivo con estos valores, no tengo acceso permitido a el por una segunda aplicacion al no ser para eliminarlo, por lo que las llamadas con ShellExecute sobre el mismo daran error de acceso al intentar abrirlo.

Variante#2
========

Para la segunda variante, he investigado sobre la API ShellExecuteEx, exactamente hay un valor interesante en la estructura de parametro SHELLEXECUTEINFO, me refiero al valor:

Código (cpp) [Seleccionar]
HANDLE hProcess;

Handle to the newly started application. This member is set on return and is always NULL unless fMask is set to SEE_MASK_NOCLOSEPROCESS. Even if fMask is set to SEE_MASK_NOCLOSEPROCESS, hProcess will be NULL if no process was launched. For example, if a document to be launched is a URL and an instance of Microsoft Internet Explorer is already running, it will display the document. No new process is launched, and hProcess will be NULL.
Note ShellExecuteEx does not always return an hProcess, even if a process is launched as the result of the call. For example, an hProcess does not return when you use SEE_MASK_INVOKEIDLIST to invoke IContextMenu.


Y yo tengo esto:

Código (cpp) [Seleccionar]
SHELLEXECUTEINFO execInfo;

ZeroMemory(&execInfo, sizeof(SHELLEXECUTEINFO));
execInfo.cbSize = sizeof(SHELLEXECUTEINFO);
execInfo.fMask = SEE_MASK_FLAG_DDEWAIT + SEE_MASK_FLAG_NO_UI + SEE_MASK_NOCLOSEPROCESS;
execInfo.lpVerb = "open";
execInfo.lpFile = filename;
execInfo.nShow = SW_SHOWNORMAL;

ShellExecuteEx(&execInfo);


Pero por lo que dicen de el, vemos que no seria confiable para esto, ademas que lo he intentado con el si exito alguno.

Perdon por este post tan largo, y gracias por el simple hecho de haber llegado hasta aqui (si es que llegaste...jeje), pero aqui es donde estoy detenido, y es que no se de que forma lograr lo que quiero,...si es con alguna combinacion exacta de valores en los parametros de CreateFile o en ShellExecuteEx que me permitan alguna de las dos variantes que expuse al principio.
Bueno, no doy mas lata...ya esta...alguien tiene alguna idea o me puede guiar a lo que quiero???
Saludos y gracias de antemano
-[ "...I can only show you the door. You're the one that has to walk through it." – Morpheus (The Matrix) ]-
http://reversec0de.wordpress.com
https://github.com/ThunderCls/

ThunderCls

Bueno, ya que nadie aporta, me respondo yo mismo y de paso aporto tambien para alguien con el mismo problema que encuentre este post en la red. Por ahora lo he solucionado por la variante#2, aunque no estoy 100% conforme, pero bueno, es lo que hay, aun asi seguire investigando en la variante#1 y si logro algo lo posteo aqui tambien.

Variante#2 - Metodo1
================

Esta variante la he sacado de dos formas distintas. La primera es de un post parecido a este que encontre en el foro de VB el codigo lo pueden ver aqui: http://foro.elhacker.net/programacion_visual_basic/problemas_con_ejecutar_cualquier_archivo_y_esperar_a_que_termine-t205423.0.html
Asi que lo que hice fue pasarlo a CBuilder6 y aqui esta el codigo resultante:

Código (cpp) [Seleccionar]
//---------------------------------------------------------------------------
#include <vcl.h>
#include <psapi.h>
#pragma hdrstop
#pragma comment(lib,"C:\Archivos de programa\Borland\CBuilder6\Lib\Psdk\Psapi.lib")

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

bool TForm1::ShellWait(AnsiString sShell, int eFocus)
{
    HANDLE lhProcess = NULL;
    DWORD lRet = 0;
    DWORD lProc = 0;
    TList *cProcs = new TList();

    // Se ejecuta el archivo especificado
    if ((INT)ShellExecute(0, "open", sShell.c_str(), NULL, NULL, eFocus) > 32)
    {
        // Se enumeran todos los procesos y se devuelve
        // un listado de ellos
cProcs = (TList*)EnumProcs();
        // Se toma el ultimo proceso que se creo de la lista
lProc = (*(DWORD*)cProcs->Items[cProcs->Count - 1]);
    }

    // Se abre toma la informacion del proceso
    lhProcess = OpenProcess(PROCESS_QUERY_INFORMATION, false, lProc);

    if (lhProcess == 0)
        return false;

    do
    {  // Se iterara mientras el proceso se mantenga activo
       GetExitCodeProcess(lhProcess, &lRet);
       Application->ProcessMessages();
       Sleep(100);
    }while(lRet == STILL_ACTIVE);

    // Liberamos memoria
    CloseHandle(lhProcess);
    delete cProcs;
   
    return true;
}
//---------------------------------------------------------------------------

TList* TForm1::EnumProcs()
{
    DWORD lvProcesses[1024] = {0};
    DWORD lNedded = 0;
    TList *cTemp = new TList();

    // Enumeramos todos los procesos en ejecucion
    if (EnumProcesses(lvProcesses, sizeof(lvProcesses) * 4, &lNedded) != 0)
    {
        for(UINT i = 0; i < (lNedded / sizeof(DWORD)); i++)
        {
           // Almacenamos en una lista cada proceso
           // Nota: Se almacenan por orden de ejecucion
           if (lvProcesses[i] != 0)
              cTemp->Add(&lvProcesses[i]);
        }     
    }

    return cTemp;
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    if(ShellWait("C:\\test.doc", SW_SHOWNORMAL))
        Application->Terminate();
}
//---------------------------------------------------------------------------


Ahora, con respecto a este metodo, es de aclarar que no siempre es 100% efectivo, y me explico. Supongamos que vamos a probar el codigo que he puesto, el cual abre el archivo "test.doc", pero antes de hacerlo ya teniamos con anterioridad una instancia del "WORD.EXE" abierta, y luego de haber ejecutado una que otra aplicacion mas (Winrar, AdobeReader, etc), probamos nuestro codigo y...veremos que abre el "test.doc", pero aun al cerrarlo, nuestra aplicacion no se termina.
Esto es debido a que el codigo anterior se basa en hacer un listado de todos los procesos que se ejecutan en la PC, tomando como referencia el ultimo proceso creado, como el proceso de nuestro archivo, y por este sera por el que esperara a que termine. Ahora en el ejemplo que puse anteriormente, ya teniamos una instancia del "WORD.EXE" abierta, por lo que al lanzar el documento no se crea ningun nuevo proceso y se tomara como referencia el ultimo proceso de la lista, no siendo este el correcto.

Variante#2 - Metodo2
================

El segundo metodo que utilizo es el que comentaba en el primer post, y es haciendo uso de las API's ShellExecuteEx y WaitForSingleObject, aqui esta:

Código (cpp) [Seleccionar]
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
   SHELLEXECUTEINFO execInfo;
   char filename[MAX_PATH] = "C:\\test.doc";

   ZeroMemory(&execInfo, sizeof(SHELLEXECUTEINFO));

   // Llenamos la estructura
   execInfo.cbSize = sizeof(SHELLEXECUTEINFO);
   execInfo.fMask = SEE_MASK_FLAG_DDEWAIT + SEE_MASK_FLAG_NO_UI + SEE_MASK_NOCLOSEPROCESS;
   execInfo.lpVerb = "open";
   execInfo.lpFile = filename;
   execInfo.nShow = SW_SHOWNORMAL;

   // Lanzamos el archivo
   ShellExecuteEx(&execInfo);

   // Verificamos que no hayan errores
   if ((int)execInfo.hInstApp <= 32)
   {
      if ((int)execInfo.hInstApp == SE_ERR_NOASSOC)
         MessageBox(NULL,
                          "No existe una aplicación asociada \na la extensión del archivo",
                          "Error",
                          MB_ICONERROR);

      MessageBox(NULL,
                       "No se ha podido ejecutar el archivo",
                       "Error",
                       MB_ICONERROR);
   }

   // Esperamos a que termine el proceso
   WaitForSingleObject(execInfo.hProcess, INFINITE);
   Application->Terminate();
}
//---------------------------------------------------------------------------


Este metodo al igual que el primero, tiene sus "contras", algo bien parecido a lo que sucede con el primer codigo.

A pesar de estos inconvenientes que he expuesto para cada metodo, el que mejores resultados da de los dos es el primero, asi que gracias a cobein del foro de VB por ese codigo que posteo hace unos años.
Como dije al principio, seguire investigando en la primera variante que expuse, de seguro sale algo interesante.
-[ "...I can only show you the door. You're the one that has to walk through it." – Morpheus (The Matrix) ]-
http://reversec0de.wordpress.com
https://github.com/ThunderCls/

yqueseyo

Hola
Es muy probable que este solucionado pero quizas te sirva mejor que el otro metodo. No lo pude hacer fallar por el momento ;)

Estaba haciendo un programa para que ejecute cualquier cosa: Accesos directos, .bat, .msu(actualizaciones del seven) etc. etc .
y que espere que finalice el programa para continuar. Al final lo pude resolver
Buscando desesperado encontre este post; no es lo que buscaba pero lo tome como un desafio.

En base a lo que tenia lo modifique y lo adapte a la Variante#1

Salio esto y funciona tambien aunque el word este abierto lo borra sin problemas
(el archivo que queremos abrir tiene que estar cerrado ;D)

Bueno vamos con el codigo

Agregar al form:
un Command1 y un Text1(en este va la ruta completa de lo que se quiere abrir y borrar)


Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" _
    (ByVal lpszLongPath As String, ByVal lpszShortPath As String, _
    ByVal cchBuffer As Long) As Long

Private Sub Command1_Click()
    Comando = Text1
    ejecutar (Comando)
End Sub

Private Sub Form_Load()
    Form1.ScaleMode = vbCharacters
    Command1.Caption = "Ejecutar y borrar al cerrar"
    Command1.Width = Len(Command1.Caption)
End Sub


Sub ejecutar(Comando As String)
    Dim hShell As Long, hProc As Long, codExit As Long, i As Long
    Dim sBuf As String * 280
    Dim archivo, elbat
   'convierte a ruta a nombre corto
    i = GetShortPathName(Comando, sBuf, Len(sBuf))
    Comando = Left$(sBuf, i) 'ponemos en Comando el nomb corto
    If Comando = "" Then Exit Sub
    archivo = Right(Comando, Len(Comando) - InStrRev(Comando, "\"))
    elbat = creatxt(Comando)
    hShell = Shell(Environ$("Comspec") & " /c " & elbat, vbHide)
End Sub


Function creatxt(archivo As String)
    Dim arch
    Dim NumArch
    Randomize
    arch = Int(10000 * Rnd) + 1 & ".bat"
    ChDir Environ$("temp")
    NumArch = freefile
    Open arch For Output As NumArch
    Print #NumArch, "echo off"
    Print #NumArch, "Cls"
    Print #NumArch, "start " & archivo
    Print #NumArch, ":comienzo"
    Print #NumArch, "IF EXIST " & archivo & " (del " & archivo & ") else goto fin"
    Print #NumArch, "ping localhost -n 3 >nul"
    Print #NumArch, "goto comienzo"
    Print #NumArch, ":fin"
    Print #NumArch, "del %0"
    Close #NumArch
    creatxt = arch
End Function



Lo que hace esto es crear un bat en la carpeta temp.
Este bat queda haciendo un bucle para tratar de borrar el archivo que acaba de abrir,
como le da error sigue intentando eliminarlo.
Cuando se cierra la aplicacion que lo utiliza ej el word lo logra eliminar y se borra así mismo

usa el comando ping como temporizador

Saludos


ThunderCls

Hola, pues al final lo que hice fue lo siguiente...

1- Ejecutar el archivo con su aplicacion predeterminada (ShellExecuteEx)
2- Verificar el campo hProcess: if (hProcess != NULL) tenemos un handle ;-)
2.1 - Si no hay handle es que ya habia una instancia de la aplicacion encargada de abrir el archivo u otra razon, por lo que se marca el fichero para que sea borrado al reinicio del sistema (MoveFileEx con la bandera MOVEFILE_DELAY_UNTIL_REBOOT)
3- Esperar a que termine el hilo que abrio el archivo (WaitForSingleObject)
4- Borrar el archivo

La variante del uso del batch ya la habia visto por ahi, pero no me gusta mucho (aunque resuelve sin dudas el problema  :silbar:)
Saludos  ;)
-[ "...I can only show you the door. You're the one that has to walk through it." – Morpheus (The Matrix) ]-
http://reversec0de.wordpress.com
https://github.com/ThunderCls/