Monitorear cola de impresión en Windows

En este artículo te voy a enseñar a monitorear la cola de impresión de una impresora de Windows para saber cuando un trabajo ha sido agregado, básicamente vas a escuchar el evento de cuando alguien envía un trabajo de impresión a una impresora.

Esto va a servir para obtener una notificación cada vez que un documento es enviado para ser impreso en cualquier impresora, y a partir de ahí poder realizar otras acciones, sin importar el lugar desde donde el trabajo de impresión fue enviado.

Vamos a usar las APIs de Windows, específicamente OpenPrinter, FindFirstPrinterChangeNotification y FindNextPrinterChangeNotification usando C++, pero me imagino que esto que haremos puede hacerse desde cualquier lenguaje de programación como C#, Python, etcétera.

Abrir impresora

Comencemos viendo el modo de obtener un handler para la impresora, para ello invocamos a la función OpenPrinter. Toma en cuenta que en este caso el nombre de la impresora se está leyendo desde la línea de comandos.

char nombreDeImpresora[MAX_PATH];
std::cout << "Nombre de la impresora a monitorear, tal y como aparece en el panel de control: ";
std::cin.getline(nombreDeImpresora, MAX_PATH);
HANDLE manejadorDeImpresora = NULL;
PRINTER_DEFAULTS pd;
ZeroMemory(&pd, sizeof(pd));
pd.DesiredAccess = PRINTER_ACCESS_USE;
if (!OpenPrinter(nombreDeImpresora, &manejadorDeImpresora, &pd))
{
    std::cerr << "Error al abrir la impresora." << std::endl;
    return 1;
}

Debido a que solo vamos a revisar la cola de impresión desde C++, solo necesitamos el permiso PRINTER_ACCESS_USE, si quieres puedes revisar la documentación oficial para revisar otro tipo de derechos.

Obtener identificador de objeto para monitoreo

Una vez que tenemos el handle de la impresora podemos invocar a FindFirstPrinterChangeNotification, aquí pasamos una bandera indicando las condiciones bajo las cuales queremos ser notificados.

En este caso solo se necesita saber cuando alguien envía un trabajo de impresión a una impresora, así que con PRINTER_CHANGE_ADD_JOB basta.

HANDLE identificadorDeObjetoParaMonitoreo = FindFirstPrinterChangeNotification(
    manejadorDeImpresora, PRINTER_CHANGE_ADD_JOB, 0, NULL);

if (identificadorDeObjetoParaMonitoreo == NULL || identificadorDeObjetoParaMonitoreo == INVALID_HANDLE_VALUE)
{
    std::cerr << "Error al iniciar la notificacion de cambio de impresora." << std::endl;
    return 1;
}

std::cout << "Monitoreando. Cada vez que se mande una impresion a la impresora seleccionada, se deberia mostrar un mensaje.\n";

Es importante mencionar que FindFirstPrinterChangeNotification puede devolver INVALID_HANDLE_VALUE en caso de no poder obtener el acceso al manejador, esto puede ocurrir por permisos o cosas similares, por ello es que debemos manejar esa opción.

Monitorear cola de impresión

Llegamos al punto más importante: saber cuando alguien envía un trabajo de impresión a una impresora en Windows.

Para ello encerramos el comportamiento en un ciclo infinito y esperamos con WaitForSingleObject para esperar la señal, que en otras palabras será la notificación de que se ha agregado un trabajo de impresión a la cola.

while (true)
{
    if (WaitForSingleObject(identificadorDeObjetoParaMonitoreo, INFINITE) == WAIT_OBJECT_0)
    {
        std::cout << "Se ha agregado un trabajo a la cola de impresion" << std::endl;

        if (FindNextPrinterChangeNotification(identificadorDeObjetoParaMonitoreo, NULL, 0, NULL) == FALSE)
        {
            std::cerr << "Error al esperar mas notificaciones" << std::endl;
            break;
        }
    }
    else
    {
        std::cerr << "Error al esperar la notificacion" << std::endl;
        break;
    }
}

La línea más importante está dentro del if, que es en donde imprimimos que se ha agregado un trabajo a la cola de impresión. Aquí puedes hacer más cosas además de imprimir, pues puedes estar seguro de que alguien envió una impresión a la impresora previamente indicada.

Finalmente basta con cerrar los manejadores y objetos:

if (identificadorDeObjetoParaMonitoreo != NULL)
{
    FindClosePrinterChangeNotification(identificadorDeObjetoParaMonitoreo);
}
if (manejadorDeImpresora != NULL)
{
    ClosePrinter(manejadorDeImpresora);
}

Código completo

Ya he explicado cómo ser notificado cuando alguien envía una impresión a una impresora, ahora veremos el código completo. Recuerda que estamos trabajando con la Windows API así que debes incluir el encabezado windows.h.

El código completo queda así:

#include <windows.h>
#include <iostream>

int main()
{
    char nombreDeImpresora[MAX_PATH];
    std::cout << "Nombre de la impresora a monitorear, tal y como aparece en el panel de control: ";
    std::cin.getline(nombreDeImpresora, MAX_PATH);
    HANDLE manejadorDeImpresora = NULL;
    PRINTER_DEFAULTS pd;
    ZeroMemory(&pd, sizeof(pd));
    pd.DesiredAccess = PRINTER_ACCESS_USE;
    if (!OpenPrinter(nombreDeImpresora, &manejadorDeImpresora, &pd))
    {
        std::cerr << "Error al abrir la impresora." << std::endl;
        return 1;
    }

    HANDLE identificadorDeObjetoParaMonitoreo = FindFirstPrinterChangeNotification(
        manejadorDeImpresora, PRINTER_CHANGE_ADD_JOB, 0, NULL);

    if (identificadorDeObjetoParaMonitoreo == NULL || identificadorDeObjetoParaMonitoreo == INVALID_HANDLE_VALUE)
    {
        std::cerr << "Error al iniciar la notificacion de cambio de impresora." << std::endl;
        return 1;
    }

    std::cout << "Monitoreando. Cada vez que se mande una impresion a la impresora seleccionada, se deberia mostrar un mensaje.\n";
    while (true)
    {
        if (WaitForSingleObject(identificadorDeObjetoParaMonitoreo, INFINITE) == WAIT_OBJECT_0)
        {
            std::cout << "Se ha agregado un trabajo a la cola de impresion" << std::endl;

            if (FindNextPrinterChangeNotification(identificadorDeObjetoParaMonitoreo, NULL, 0, NULL) == FALSE)
            {
                std::cerr << "Error al esperar mas notificaciones" << std::endl;
                break;
            }
        }
        else
        {
            std::cerr << "Error al esperar la notificacion" << std::endl;
            break;
        }
    }

    if (identificadorDeObjetoParaMonitoreo != NULL)
    {
        FindClosePrinterChangeNotification(identificadorDeObjetoParaMonitoreo);
    }
    if (manejadorDeImpresora != NULL)
    {
        ClosePrinter(manejadorDeImpresora);
    }

    return 0;
}

Personalmente lo he compilado con g++ así: g++ monitorear.cpp -lwinspool -o monitorear.exe

Me parece que también se puede compilar con Visual Studio, y también recuerdo que puedes invocar a estas API desde otros lenguajes de programación.

El código es sencillo, pero me ha llevado un tiempo el investigar, hacer pruebas y colocar todo en una prueba de concepto.

Encantado de ayudarte


Estoy disponible para trabajar en tu proyecto, modificar el programa del post o realizar tu tarea pendiente, no dudes en ponerte en contacto conmigo.

No te pierdas ninguno de mis posts

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *