🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

VBA Shell en Excel — ejecuta un programa externo, y por qué tu código no espera

|

VBA Shell en Excel — ejecuta un programa externo, y por qué tu código no espera

TL;DRShell inicia un programa y devuelve de inmediato, antes de que ese programa haya hecho nada. Te entrega un ID de tarea (un Double), no un código de salida, ni salida de texto, ni una señal de éxito, y la siguiente línea de tu macro se ejecuta mientras el programa todavía está arrancando. Si necesitas esperarlo o leer lo que produjo, Shell es la herramienta equivocada — usa WScript.Shell (.Run con espera, o .Exec).

Sub ShellDemo()
    Dim taskId As Double
    taskId = Shell("notepad.exe", vbNormalFocus)   ' devuelve un ID de tarea, luego sigue
    Debug.Print taskId                             ' p.ej. 4180 - NO un codigo de salida
    ' la linea de abajo se ejecuta AHORA, mientras el Bloc de notas aun se abre
End Sub

Shell es el único verbo de VBA que se estira fuera de Excel para iniciar otro programa de Windows. El problema es que se comporta de forma muy distinta al resto de tu macro. En todos los demás sitios, una línea termina su trabajo antes de que se ejecute la siguiente, y un fallo lanza un error a voces. Shell rompe ambas costumbres: lanza el programa y devuelve al momento, y no te cuenta casi nada sobre si el programa funcionó. En cuanto lo imaginas como fire-and-forget —dispara y olvida—, los bugs desconcertantes —«mi código abrió el archivo antes de que el conversor terminara de escribirlo»— dejan de ser un misterio.

Lo que aprenderás

  • El modelo mental — Shell dispara y olvida: lanza el programa y devuelve de inmediato
  • Por qué el valor de retorno es un ID de tarea, no un código de salida ni la salida del programa
  • Por qué la línea posterior a Shell se ejecuta demasiado pronto, y cómo esperar de verdad al programa
  • Cómo entrecomillar una ruta que contiene espacios, y por qué Shell por sí solo no puede abrir un PDF ni una URL
  • Cuándo abandonar Shell por WScript.Shell .Run (espera + código de salida) o .Exec (captura la salida)
  • Los errores que Shell lanza cuando el programa no existe, y cómo protegerte de ellos

El modelo mental: Shell dispara y olvida

La única idea que explica todas las sorpresas de Shell: Shell no ejecuta un programa — lo inicia y se marcha. Le pide a Windows que lance el ejecutable, recibe de vuelta un número que identifica la nueva tarea, y regresa a tu macro de inmediato. El programa ahora se ejecuta en paralelo con tu código, no dentro de él.

Shell "calc.exe"          ' Windows inicia la Calculadora...
MsgBox "done"             ' ...y este MsgBox aparece al instante, calc aun cargando

Compáralo con una llamada a método normal como Range("A1").Copy, que ha terminado en el instante en que se ejecuta la línea siguiente. Shell es lo contrario: es asíncrono. Tu macro y el programa lanzado siguen caminos separados. Cada regla de abajo es una consecuencia de este único hecho.

El segundo argumento es el estilo de ventana — vbNormalFocus, vbMinimizedNoFocus, vbHide, etc. Controla cómo aparece la nueva ventana; no hace que Shell espere. No existe ningún estilo de ventana que convierta Shell en síncrono.

Shell devuelve un ID de tarea, no un resultado

Shell devuelve un Double — el ID de proceso (tarea) que Windows asignó al nuevo programa. La gente echa mano de ese número esperando un código de salida o una señal de «funcionó», y no es ninguna de las dos:

Dim result As Double
result = Shell("robocopy.exe C:\a C:\b", vbHide)
' result es un ID de tarea como 7820 - no dice nada sobre si robocopy tuvo exito

Como la llamada devuelve antes de que el programa haya hecho su trabajo, todavía no hay nada significativo que informar. Shell no puede darte el código de salida del programa, no puede capturar su salida de consola, y no te avisa si el programa se cae más tarde. El ID de tarea solo sirve como manejador — por ejemplo, para pasarlo a una API de Windows como OpenProcess/WaitForSingleObject si decides esperar. Si tu lógica depende de lo que el programa devolvió, Shell estructuralmente no puede suministrarlo, y ninguna cantidad de código extra alrededor de Shell cambiará eso.

Por qué tu siguiente línea se ejecuta demasiado pronto

Este es el bug estrella, y cae directamente del dispara-y-olvida. Lanzas una herramienta que produce un archivo, y luego usas ese archivo de inmediato:

Shell "C:\Tools\convert.exe report.docx report.pdf"
Workbooks.Open "C:\Tools\report.pdf"   ' FALLA - el PDF aun no existe

convert.exe necesita un segundo o dos para ejecutarse, pero Workbooks.Open se ejecuta ahora, mientras el conversor apenas está arrancando. El archivo no está, y obtienes un error de «archivo no encontrado» que apunta a la línea equivocada — la causa real está tres caracteres más arriba, en el Shell que no esperó.

El arreglo no es un Application.Wait o un Sleep fijo puesto a ojo — pausa muy poco y sigue fallando, pausa demasiado y desperdicias el tiempo del usuario, y en cualquier caso una máquina lenta lo rompe. Para esperar de verdad a que el programa termine, necesitas algo distinto de Shell. La respuesta limpia es WScript.Shell.Run con su flag de espera (más abajo); la respuesta de bajo nivel es la API WaitForSingleObject contra el ID de tarea. Lo que deberías dejar de hacer es esparcir Sleep 2000 y cruzar los dedos.

Rutas con espacios, y abrir un documento en lugar de un exe

Dos trampas prácticas hacen tropezar a casi todo el mundo.

Espacios en la ruta. Shell toma una única cadena de línea de comandos, y un espacio separa el programa de sus argumentos. Así que una ruta como C:\Program Files\... se parte en el sitio equivocado:

Shell "C:\Program Files\App\app.exe"        ' error 53 - Windows busca "C:\Program"
Shell """C:\Program Files\App\app.exe"""    ' correcto - entrecomilla la ruta del exe

Dentro de una cadena de VBA, cada "" es una comilla literal, así que """...""" pone comillas reales alrededor de la ruta. Entrecomilla el ejecutable siempre que su carpeta pueda contener un espacio (y la mayoría lo tiene).

Abrir un documento, no un programa. Shell lanza ejecutables — no sabe que un .pdf debería abrirse en tu lector de PDF ni que un .xlsx debería abrirse en Excel. Apuntarlo a un documento falla:

Shell "C:\Reports\Q1.pdf"                    ' error 53 - no es un ejecutable
Shell "cmd /c start """" ""C:\Reports\Q1.pdf"""   ' funciona - deja que el shell resuelva la app por defecto

El comando start (vía cmd /c) le pide a Windows que abra el archivo con el programa registrado para él — lo mismo que hace un doble clic. El "" vacío después de start es el marcador del título de la ventana, que start requiere cuando la ruta va entrecomillada. Abrir documentos y URLs de esta forma es habitual; la alternativa más limpia es la API ShellExecute o WScript.Shell, que veremos a continuación.

Cuándo necesitas de verdad el resultado: WScript.Shell

En el momento en que necesitas esperar al programa, leer su código de salida o capturar su salida, sube de Shell al objeto WScript.Shell, creado con CreateObject:

Dim sh As Object
Set sh = CreateObject("WScript.Shell")

' .Run - el tercer argumento True significa ESPERAR hasta que el programa termine;
' el valor de retorno es entonces el codigo de salida real.
Dim exitCode As Long
exitCode = sh.Run("robocopy.exe C:\a C:\b", 0, True)   ' 0 = ventana oculta
If exitCode >= 8 Then MsgBox "robocopy failed: " & exitCode

' .Exec - ejecuta y lee el texto de stdout del programa
Dim p As Object, output As String
Set p = sh.Exec("cmd /c dir C:\")
output = p.StdOut.ReadAll

.Run(command, windowStyle, waitOnReturn) es la mejora directa cuando quieres esperar y obtener un código de salida. .Exec va más allá y te da un objeto de proceso vivo cuyo StdOut puedes leer — la única forma de traer el texto de un programa de consola de vuelta a Excel. Construye las rutas del comando con Environ para que funcionen en cualquier máquina, y envuelve todo en manejo de errores para que un programa que falta se informe con limpieza en lugar de estrellarse.

El veredicto honesto: Shell para dispara-y-olvida, WScript.Shell para el resto

Shell es un verbo de un solo truco, y el truco es estrecho: inicia un programa y olvídate de él. Es genuino y útil para exactamente eso — arrancar un visor, abrir una carpeta, lanzar una herramienta de larga duración que no necesitas seguir. Los bugs vienen de pedirle cosas que nunca prometió. Cuatro reglas cubren toda la superficie:

  • Dispara y olvidaShell devuelve de inmediato; el programa se ejecuta en paralelo. No hay estilo de ventana que lo haga esperar.
  • El valor de retorno es un ID de tarea, no un resultado → sin código de salida, sin salida de texto, sin señal de éxito. Si tu lógica necesita el resultado del programa, Shell es la herramienta equivocada.
  • Entrecomilla las rutas con espacios; los documentos no son ejecutables → envuelve el exe en ""..."" ; abre un documento con cmd /c start o ShellExecute, no con Shell directamente.
  • ¿Necesitas esperar o leer la salida? Usa WScript.Shell.Run(..., True) espera y devuelve el código de salida; .Exec captura StdOut. Échale mano en cuanto el silencio de Shell sea un problema.

Ajusta la herramienta al trabajo y la clase de bug «abrió el archivo antes de que el programa terminara» desaparece — estabas usando un lanzador de dispara-y-olvida para hacer un trabajo de esperar-y-comprobar.

Cómo ayuda ExcelMaster

Los bugs de Shell que cuestan tiempo de verdad son los de temporización: una macro que abre un archivo que el programa lanzado todavía no ha escrito, un Sleep 2000 que funciona en tu máquina y falla en una más lenta, un conversor cuyo fallo es invisible porque Shell no informó de nada. Cada uno viene de usar un verbo de dispara-y-olvida donde en realidad necesitabas esperar y comprobar.

ExcelMaster escribe la lógica de lanzar-y-esperar correctamente. Describe el trabajo — «ejecuta este conversor y luego abre su salida» o «llama a esta herramienta de línea de comandos y dime si falló» — y elige el mecanismo correcto: Shell cuando dispara-y-olvida está de verdad bien, o WScript.Shell.Run/.Exec cuando necesitas esperar a que termine, leer un código de salida o capturar la salida. Tú describes el programa que quieres ejecutar; él escribe el código que lo ejecuta y sabe cuándo ha terminado.

Preguntas frecuentes

¿Cómo hago que VBA Shell espere a que el programa termine?

Shell por sí mismo no puede esperar — siempre devuelve de inmediato. Para esperar, usa el objeto WScript.Shell en su lugar: CreateObject("WScript.Shell").Run(command, windowStyle, True). El tercer argumento True (waitOnReturn) hace que se bloquee hasta que el programa termine, y el valor de retorno es entonces el código de salida real del programa. La alternativa es un enfoque con la API de Windows — pasa el ID de tarea que devuelve Shell a WaitForSingleObject — pero WScript.Shell.Run es mucho más sencillo para el uso diario.

¿Qué devuelve la función VBA Shell?

Shell devuelve un Double — el ID de tarea (proceso) que Windows asignó al programa recién iniciado. No es un código de salida, ni la salida del programa, ni una señal de éxito. Como Shell devuelve antes de que el programa haya terminado, todavía no hay ningún resultado que informar. El ID de tarea es solo un manejador que podrías pasar a una API de Windows. Si necesitas el código de salida del programa, usa WScript.Shell.Run(..., True) en su lugar.

¿Por qué VBA Shell da el error 53 (archivo no encontrado)?

Dos causas comunes. Primera, la ruta contiene un espacio y no está entrecomillada — Shell "C:\Program Files\..." se parte en el espacio, así que Windows busca C:\Program; envuelve la ruta del ejecutable en comillas dobladas: Shell """C:\Program Files\App\app.exe""". Segunda, apuntaste Shell a un documento (un .pdf, .xlsx) en lugar de un ejecutable — Shell lanza programas, no archivos. Para abrir un documento con su app por defecto, usa Shell "cmd /c start """" ""C:\file.pdf""" o la API ShellExecute.

¿Cómo ejecuto un archivo por lotes o un comando de línea de comandos desde VBA?

Para un archivo .bat, Shell puede lanzarlo directamente: Shell "C:\scripts\build.bat" (entrecomilla la ruta si tiene espacios). Para un comando en crudo que no es un ejecutable — un dir, un copy, una tubería — ejecútalo a través del intérprete de comandos: Shell "cmd /c copy a.txt b.txt". Si necesitas esperar a que termine y leer su código de salida o su salida, usa CreateObject("WScript.Shell").Run("cmd /c ...", 0, True) o .Exec en lugar de Shell.

¿Cómo puedo capturar la salida de un programa ejecutado desde VBA?

Shell no puede capturar la salida en absoluto. Usa el método .Exec del objeto WScript.Shell, que devuelve un objeto de proceso con un flujo StdOut legible: Set p = CreateObject("WScript.Shell").Exec("cmd /c dir C:\") : output = p.StdOut.ReadAll. Esta es la forma estándar de traer el texto de un programa de consola de vuelta a VBA. Para programas que solo señalan el éxito mediante un código de salida, usa .Run(command, 0, True) y comprueba su valor de retorno en su lugar.

Probado en

Probado en: Excel 365 (Windows 11), VBA 7.1 — última verificación el 30/08/2026.

Guías relacionadas: VBA CreateObject y GetObject · VBA Environ · VBA Wait y Sleep · VBA On Error · VBA FreeFile y Open