LinkedIn Google+

Objetivo

En este post me gustaría revisar con vosotros cómo usar Powershell y las tareas programadas de Windows, conjuntamente, para montar un pequeño sistema de alertas artesanal. El sistema, a pesar de su sencillez, creo que puede resultarle interesante a muchos administradores de sistemas que quieran estar informados de cuándo se producen determinados eventos en servidores particulares.

Antes de entrar en los detalles de cada una de las partes que componen este sistema de alertas, creo que es interesante que definamos claramente qué es lo que queremos conseguir, y cómo vamos a intentar conseguirlo, para después entrar en el detalle que nos interesa.

El objetivo de este pequeño experimento es sencillo: enviar un correo electrónico a un buzón determinado cuando se produzca un evento determinado en el sistema que vamos a monitorizar.

¿Cómo lo vamos a conseguir? Pues tal y como indica el título de este post, usando Powershell y las tareas programadas de Windows. El procedimiento es sencillo:

  1. Crear un script que capture el último evento de un origen que se le indique como parámetro y lo envíe por correo.
  2. Crear una tarea programada que se dispare cuando se cumpla una condición que definiremos.

El script en Powershell

El script que os pongo a continuación es mi aproximación personal y, por descontado, no es la única forma, ni tan siquiera tiene por que ser la más correcta, tan solo mi forma de programar scripts. Si os parece bien, os pongo el código completo, para posteriormente ir analizándolo parte por parte.

<#
.SYNOPSIS
    Script para el envío por correo del último evento producido en un Registro de Eventos determinado
.DESCRIPTION
    Este script permite el envío del último evento producido en el registro de eventos que se le pasa como parámetro
.PARAMETER emailFrom
    Indica el remitente del correo. Dependiendo de la configuración del servidor de correo esta debe ser una cuenta existente
.PARAMETER emailTo
    Indica el destinatario del correo.
.PARAMETER smtpServer
    Dirección IP o nombre DNS del servidor de correo electrónico que se va a utilizar para el envío del mensaje. Si no se especifica este parámetro se usara localhost como servidor de correo
.PARAMETER smtpServerAuthUsername
    En caso del que el servidor de correo requiera autenticación, indicar el usuario mediante este parámetro
.PARAMETER smtpServerAuthPassword
    Contraseña para autenticar al usuario utilizado para establecer la conexión con el servidor de correo.
.PARAMETER nombreLog
    Registro de eventos del que se va a obtener el último evento producido para enviarlo por correo. Este parámetro debe coincidir con uno de los registros de eventos existentes en el sistema. Por defecto se usará "Application"
.EXAMPLE

    Para enviar por correo el último evento del registro de eventos "Security"

    Send-EventByEmail -emailFrom alertas@cursohispano.com -emailTo santiago.fernandez@cursohispano.com -smtpServer "correo.cursohispano.com" -nombreLog "Security"
#>

param(
    [ Parameter(Position=1,Mandatory=$true)] [ValidatePattern("^([a-z0-9_.-]+)@([da-z.-]+).([a-z.]{2,6})$")] [String]$emailFrom,
    [ Parameter(Position=2,Mandatory=$true)] [ValidatePattern("^([a-z0-9_.-]+)@([da-z.-]+).([a-z.]{2,6})$")] [String]$emailTo,
    [ Parameter(Position=3,Mandatory=$false)] [String]$smtpServer = "localhost",
    [ Parameter(Position=4,Mandatory=$false)] [String]$smtpServerAuthUsername,
    [ Parameter(Position=5,Mandatory=$false)] [String]$smtpServerAuthPassword,
    [ Parameter(Position=6,Mandatory=$false)] [String]$nombreLog ="Application"
)

#region Inicialización de objetos

$mailMessage = New-Object System.Net.Mail.MailMessage($emailFrom, $emailTo)
$mailmessage.IsBodyHTML = $true

$smtpClient = New-Object System.Net.Mail.SmtpClient($smtpServer, 25)
If ( $smtpServerAuthUsername -ne "" -and $smtpServerAuthPassword -ne "") {
	$smtpClient.Credentials = New-Object System.Net.NetworkCredential($smtpServerAuthUsername, $smtpServerAuthPassword)
}

$emailSubject = "Notificaciondel servidor: $($env:computername)"

#capturamos el último evento que se ha generado
$log = Get-EventLog -LogName $nombreLog -Newest 1

#endregion

#Construimos el cuerpo del mensaje en base al último evento capturado
$emailBody = @"
El monitor de registros encontró eventos a revisar.
Por favor, verificar el siguiente evento que se encontró en: $($log.MachineName)
Evento: $($log.EventId)
Origen: $($log.Source)
Tipo de Evento: $($log.EntryType)
Hora del evento: $($log.TimeGenerated)
Mensaje: $($log.Message)
$(Get-Date)
"@

#envio del correo

$mailMessage.Subject = $emailSubject
$mailmessage.Body = $emailBody
$smtpClient.Send($mailMessage)

Analizamos el Script

Antes de comenzar el análisis del script, es importante que para que esto funcione, cojáis ese código y lo copiéis a un archivo en blanco con el nombre Send-EventByEmail.ps1 para poder usarlo después en nuestra tarea programada.

Ahora sí, comencemos con el análisis del script.

La primera sección enmarcada en entre los símbolos <# y #> corresponde con la parte de ayuda de nuestro script. No hay mucho que explicar aquí, salvo que las diferentes secciones de la ayuda está diferenciadas por las etiquetas (.SYNOPSIS, .DESCRIPTION, .PARAMETER y .EXAMPLE) y que, para cada parámetro del script , lo recomendable es crear su correspondiente sección PARAMETER en la ayuda. Evidentemente, la ayuda no es obligatoria, aunque siempre es recomendable.

El siguiente trozo de código quizá sí que resulte bastante más interesante de analizar:

param (
	[ Parameter(Position=1,Mandatory=$true)] [ValidatePattern("^([a-z0-9_.-]+)@([da-z.-]+).([a-z.]{2,6})$")] [String]$emailFrom,
	[ Parameter(Position=2,Mandatory=$true)] [ValidatePattern("^([a-z0-9_.-]+)@([da-z.-]+).([a-z.]{2,6})$")] [String]$emailTo,
	[ Parameter(Position=3,Mandatory=$false)] [String]$smtpServer = "localhost",
	[ Parameter(Position=4,Mandatory=$false)] [String]$smtpServerAuthUsername,
	[ Parameter(Position=5,Mandatory=$false)] [String]$smtpServerAuthPassword,
	[ Parameter(Position=6,Mandatory=$false)] [String]$nombreLog="Application"
)

La sección param de un script define el listado de argumentos o parámetros que toma una función o, como en nuestro caso, un script. Aunque el uso de la sección param no es la única forma de gestionar los parámetros de un script, quizá sí sea la más potente y recomendable.

Fijaros que cada parámetro viene definido por una variable. El nombre de la variable que usemos aquí será el que debamos indicar en la invocación del script. Eso significa que, si hemos definido la variable emailFrom en esta sección, el script tendrá un parámetro que se llamará emailFrom.

La única parte obligatoria de esta sección es la variable que se asocia al parámetro, el resto corresponde a ajustes y controles del mismo. Quizá resulte más sencillo revisar un parámetro y explicar cada una de sus secciones:

[ Parameter(Position=1,Mandatory=$true)] [ValidatePattern(“^([a-z0-9_.-]+)@([da-z.-]+).([a-z.]{2,6})$”)] [String]$emailFrom

La palabra clave Parameter establece atributos del parámetro. En nuestro caso, para este, indica que en caso de que en la invocación del script no se indique el nombre parámetro, el primer parámetro sin nombre se le asignará a la variable $emailFrom. Además, es obligatorio especificarlo en la invocación del script.

ValidatePattern corresponde con el conjunto de atributos de un parámetro que nos permiten validar el valor que se le puede asignar a un parámetro. En este caso, usamos una expresión regular para asegurarnos que el valor que nos pasan es una dirección de correo con una estructura válida, y no cualquier otra cosa. En cristiano,… que si no pones un email, esto casca.

[String] indica que solo se le pueden asignar valores de tipo String (cadena).

$emailFrom es el nombre del parámetro.

Es interesante que observéis que algunos parámetros tiene un valor asignado, como por ejemplo $smtpServer = “localhost”. Esto significa que, en caso de que el usuario no indique este, se le asignará el valor por defecto “localhost”.

Una vez que ha quedado razonablemente clara la sección de parámetros, vamos a seguir con la construcción de los objetos que vamos a necesitar para realizar lo que nos hemos propuesto. ¿Y qué necesitamos? Pues si queremos mandar un mensaje que contenga un evento, necesitaremos:

  • Un objeto que represente un mensaje de correo.
  • Un objeto que represente un cliente de correo.
  • Un objeto que represente un evento del sistema.

Todo esto lo tenemos en las siguientes líneas de código:

$mailMessage = New-Object System.Net.Mail.MailMessage($emailFrom, $emailTo)
$smtpClient = New-Object System.Net.Mail.SmtpClient($smtpServer, 25)
$log = Get-EventLog -LogName $nombreLog -Newest 1

Donde $mailMessage es el mensaje de correo, $smtpClient es el cliente de correo y $log representará el evento que queremos enviar.

Ahora toca personalizar estos objetos con las propiedades oportunas para que hagan el trabajo como queremos que lo hagan.

Como creo que es más interesante ver cada objeto por separado que seguir estrictamente el orden del código, a partir de ahora veréis referencia al código que no tienen por qué estar necesariamente ordenadas.

Veamos primero el mensaje de correo. Ya que un mensaje debe provenir de alguien y debe estar dirigido a alguien, esos “álguienes” se guardan en los atributos from (remitente) y to (destinatario). Hemos creado un objeto que representa un mensaje de correo haciendo uso de la clase System.Net.Mail.MailMessage. Si os fijáis, cuando hemos creado el objeto $emailMessage entre paréntesis, detrás del tipo de objeto hemos puesto los parámetros $emailFrom y $emailTo. Lo que estamos diciéndole al constructor de la clase (pa que nos entendamos, el currito que se encarga de crear el objeto en memoria) es que nos cree un objeto de tipo MailMessage y que el remitente va a ser $emailFrom y el destinatario $emailTo.

Evidentemente, un mensaje de correo tiene que tener un asunto y un cuerpo del mensaje. Esto viene representado por los atributos Subject y Body del objeto $emailMessage. Sin embargo, como este mensaje dependerá de datos externos, es conveniente construir el texto que se asociará tanto al asunto como al cuerpo de forma separada para luego asignarlo al mensaje justo antes de enviarlo.

Para realizar la construcción del asunto, asignaremos el mensaje que queremos que aparezca en el asunto a una cadena de texto donde introduciremos, además, el nombre de equipo donde se ejecuta el script. Esto lo conseguimos simplemente con la línea:

$emailSubject = "Notificacion&nbsp;del servidor: $($env:computername)"

En el caso del cuerpo del mensaje la composición es un poco más compleja. Veamos el código y luego comentamos…

$emailBody = @"
El monitor de registros encontró eventos a revisar.
Por favor, verificar el siguiente evento que se encontró en: $($log.MachineName)
Evento: $($log.EventId)
Origen: $($log.Source)
Tipo de Evento: $($log.EntryType)
Hora del evento: $($log.TimeGenerated)
Mensaje: $($log.Message)
$(Get-Date)
"@

Lo primero que tenemos que ver es que estamos usando una forma un poco “rara” de construir la cadena llamada HereString. Esta notación nos permite que todo aquello que escribamos entre los símbolos @” y “@ se represente exactamente como lo escribimos en el código, espacios, tabuladores y retornos de carro incluidos. Luego construimos el mensaje, usando la sustitución de variables y haciendo uso de los atributos del objeto $log que, si recordamos, representa un evento del visor de eventos.

Ahora que tenemos nuestro asunto y cuerpo del mensaje, simplemente tenemos que asociarlo al objeto $emailMessage asignando las variables a su atributo correspondiente:

$mailMessage.Subject = $emailSubject
$mailmessage.Body = $emailBody

Una vez que hemos acabado de analizar el objeto que representa al mensaje de correo, ahora toca analizar los dos restantes, cliente de correo y evento.

La construcción del objeto que representa el cliente de correo es bastante sencilla, simplemente creamos un objeto del tipo System.Net.Mail.SmtpClient con los parámetros del servidor de correo ($smtpServer) y puerto (25):

$smtpClient&nbsp;= New-Object System.Net.Mail.SmtpClient($smtpServer, 25)

Sin embargo, hay un detalle que tenemos que tener en cuenta, el servidor puede requerir autenticación de usuario. Para solventar este problema hicimos nuestra previsión y definimos los parámetros smtpServerAuthUsernamey smtpServerAuthPassword. Con estos parámetros construimos un objeto de credenciales de red (System.Net.NetworkCredential) para autenticarnos en el servidor. Pero ¿qué ocurre si el servidor no requiere autenticación o el usuario no la ha suministrado? Controlamos esta circunstancia en el siguiente trozo de código:

If ( $smtpServerAuthUsername -ne "" -and $smtpServerAuthPassword -ne "") {
	$smtpClient.Credentials = New-Object System.Net.NetworkCredential($smtpServerAuthUsername, $smtpServerAuthPassword)
}

De forma que, si el usuario no proporciona un usuario y una contraseña, no construimos el objeto de credencial de red.

Por último, la obtención del último evento producido:

$log = Get-EventLog -LogName $nombreLog -Newest 1

No hay mucho que decir aquí, salvo que capturamos el último evento que se ha producido en el registro de eventos indicado por el parámetro $nombreLog

Una vez que tenemos todos nuestro objetos construidos y listos para la acción, enviamos el mensaje:

$smtpClient.Send($mailMessage)

Le decimos al objeto $smtpClient que envíe el mensaje $mailMessage.

Cómo crear una tarea programada

A estas alturas de la película ya supongo que, el que más o el que menos, sabe crear una tarea programada básica. Sin embargo, vamos a crear una tarea programada haciendo uso de algunas opciones avanzadas que nos ayuden a nuestro propósito. En principio, asumo que estamos trabajando con Windows 7 o Windows 2008 como mínimo.

El primer paso será irnos al programador de tareas para crear una nueva tarea programada. Ojo, que necesitamos una tarea programada normal y no una básica.

Programador de tareas

Ahora que tenemos la ventana de nuestra nueva tarea programada, vamos a ir configurándola paso a paso:

Crear una tarea programada

En la imagen anterior tenéis marcados en rojo los elementos que yo he modificado. Hay aspectos importantes a tener en cuenta. Por ejemplo, si queremos enviar eventos provenientes del registro de eventos “Security”, debemos ejecutar la tarea con las credenciales de un usuario que tenga privilegios de administrador local, para que el script pueda acceder a este registro y leer el último evento. Para eso marcamos la opción “Run with highest privileges”. Evidentemente, también querremos que el script se ejecute sin tener que tener una sesión iniciada, para ello marcamos la opción “Run whether user is logged on or not”.

Una vez que hemos configurado las opciones de la pestaña General, pasamos a configurar nuestro disparador en la pestaña Triggers. Aquí es donde está la mitad del turrón, la otra mitad está en la pestaña Actions, pero como decía mi amigo Jack the Ripper (o Jack el Destripador)…, “vayamos por partes”

Configurar disparador para la tarea programada

Nuevo disparador

Lo que nos interesa a nosotros es que el script se lance cuando se produzca un evento en unas determinadas condiciones. Ahora veremos como configurar las condiciones, pero para decirle a la tarea programada que se dispare cuando se produzca un event (el que sea) lo hacemos modificando la opción Begin the task y la establecemos al valor On an event. Como a lo mejor no nos interesa que se dispare la tarea para TODOS los eventos que se produzcan en el sistema, tendremos que ajustar las opciones de filtrado para conseguir esto.

Filtro de eventos para el disparador

En nuestro caso, como veis en la captura anterior, queremos monitorizar todos los eventos que se producen en el registro de eventos “Security” y con nivel de criticidad Critical, Error o Warning. Otro detalle importante que debéis tener en cuenta es que, si en el filtro hemos configurado como registro de eventos Security , este será el mismo que tendremos que indicarle al script en el parámetro nombreLog. Con este paso podemos dar por finalizado nuestro filtro, aunque por supuesto vosotros podéis personalizarlo como deseéis.

Una vez que hemos terminado de configurar nuestro disparador, con su correspondiente filtro, llega el momento de configurar la acción que va a realizar esta tarea programada cuando se dé la condición que hemos programado en el disparador. ¿Y qué acción se va a realizar? Pues evidentemente lanzar nuestro script de Powershell. Vamos a verlo:

Nueva acción

Acción configurada para invocar un script Powershell

Esta sencillita ventana es la fuente de muchos dolores de cabeza con las tareas programadas, si no se configura correctamente. Lo primero que vamos a configurar es el programa que queremos que ejecute nuestra acción. No es por ser repetitivo, pero el programa es evidente, ¿no? powershell. No hace falta que pongamos la ruta completa, ya que el ejecutable powershell.exe ya se encuentra en el Path y el sistema es capaz de encontrarlo sin problemas.

La otra parte importante, y con la que tenemos que tener mucho cuidado, es con la parte de argumentos. Aquí es donde le vamos a decir a Powershell qué script y con qué argumentos/parámetros se debe ejecutar. Como en la captura de pantalla no cabe el listado completo de argumentos, os lo pongo a continuación:

-File c:scriptsSend-EventByEmail.ps1 -emailFrom alertas@cursohispano.com -emailTo santiago.fernandez@cursohispano.com -smtpServer correo.cursohispano.com -nombreLog "Security"

Del listado completo de parámetros las partes más importantes son el parámetro -File, que le indica a Powershell que script tiene que ejecutar y -nombreLog, que recordemos que le dice al script de dónde coger el último evento. Todos los demás parámetros, evidentemente, también son importantes, pero ya lo hemos visto durante el análisis del script, por lo que no hay mucho que añadir aquí.

En este punto es importante hacer un inciso. Powershell cuenta con mecanismos de seguridad para evitar que código malicioso pueda dañar el equipo, o al menos lo haga sin vuestro consentimiento. Esto se hace mediante lo que se conoce como Políticas de ejecución. Sin entrar en mucho detalle, deciros que os debéis asegurar de que la política de ejecución os permita lanzar este script sin problemas. Por no andarme con mucho misterio, os pongo los pasos para comprobarlo. Asumiendo que habéis generado vuestro propio script (aunque sea con un Copy/Paste):

  • Ejecutad Get-ExecutionPolicy en una PowerShell.
  • Os debe aparecer Restricted, lo que evita que se puedan ejecutar scripts (malo).
  • Cambiad la política de ejecución. Para hacerlo lanzar una Powershell con privilegios de administrador (botón derecho, ejecutar como administrador) y ejecutad el siguiente cmdlet: Set-ExecutionPolicy RemoteSigned lo que os permitirá ejecutar vuestros scripts y los que os descarguéis de Internet siempre que estén firmados digitalmente.

Si queréis saber más sobre las políticas de ejecución consultad la ayuda de Microsoft.

Una vez que hemos terminado de configurar la acción, hemos terminado con la configuración de nuestra tarea programada y solo nos queda esperar a que se produzca el evento de seguridad para recibir nuestro correo.

Como información adicional, es aconsejable probar tanto el script como la parametrización en la acción de nuestra tarea programada en una powershell aparte, a fin de asegurarnos de que no hay ningún error que pueda esta afectando a la tarea programada.

Artículo publicado originalmente por Curso Hispano.
Artículo anterior

INSTALACIÓN DE SYSTEM CENTER 2012 SERVICE MANAGER

Artículo siguiente

KERBEROS V5 NETWORK AUTHENTICATION PROTOCOL

1 Comentario

  1. Santos
    28 Julio 2015 en 10:22 — Responder

    Hola Santigo, quería preguntarte como se podría hacer para que en vez de obtenernos la información del ultimo evento producido, me obtuviera la información del evento 4740.

Responder

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