Phoenix - Inicio, apagado y reinicio de equipos centralizado

Phoenix es una aplicación que inicia, apaga o reinicia equipos de forma remota y centralizada a través de un archivo xml dónde se asignar estas tareas.


Lo primero que debemos realizar es identificar los equipos con los que deseamos operar. En estos equipos debe estar activo WON (Wake On LAN) activado y es conveniente que posean una dirección IP fija (para evitar sustos por cambios de IP), Además necesitaremos un usuario administrador para poder lanzar las operaciones en remoto. 

Se presenta Phoenix en modo aplicación consola aunque su mayor potencial es convirtiéndolo en servicio Microsoft Windows.

Empezamos por el archivo xml, este es un ejemplo del mismo:
<general>
  <!--Periodo de ejecucion de la aplicacion en segundos-->
  <frecuencia>5</frecuencia>
  <!--Ejecucion de la aplicacion los fines de semana 1 = Si, 0 =  No-->
  <finsemana>0</finsemana>
  <!--Escritura de log 1 = Si,  0 =  No-->
  <escrituralog>1</escrituralog>
  <rutalog>C:\Phoenix\Phoenix_Log.log</rutalog>
 </general>
 <equipos>
   <!--Apagar, Reiniciar, Encender Y = Si, N =  No-->
   <equipo>
   <nombreequipo>nomEquipo1</nombreequipo>
   <direccionip>10.10.10.10</direccionip>
   <mac>SU:JA:D7:30:99:34</mac>
   <descripcion>Financieros_1</descripcion>
   <encender>Y</encender>
   <horaencender>11:08</horaencender>
   <apagar>N</apagar>
   <horaapagar>13:15</horaapagar>
   <reiniciar>N</reiniciar>
   <horareiniciar>10:25</horareiniciar>
  </equipo> 
  <equipo>
   <nombreequipo>nomEquipo2</nombreequipo>
   <direccionip>10.10.10.11</direccionip>
   <mac>SU:JA:d7:30:33:2H</mac>
   <descripcion>Financieros_2</descripcion>
   <encender>Y</encender>
   <horaencender>11:08</horaencender>
   <apagar>N</apagar>
   <horaapagar>13:15</horaapagar>
   <reiniciar>Y</reiniciar>
   <horareiniciar>08:52</horareiniciar>
  </equipo>
  <equipo>
   <nombreequipo>nomEquipon</nombreequipo>
   <direccionip>10.10.10.11</direccionip>
   <mac>SU:JA:e7:30:77:2H</mac>
   <descripcion>Financieros_n</descripcion>
   <encender>Y</encender>
   <horaencender>11:08</horaencender>
   <apagar>N</apagar>
   <horaapagar>13:15</horaapagar>
   <reiniciar>Y</reiniciar>
   <horareiniciar>08:52</horareiniciar>
  </equipo>
 </equipos>
</configuracion>

Como podemos el archivo tiene dos partes, la primera con unos parámetros generales -que se cargarán al inicio de la aplicación o servicio- donde se indica con qué frecuencia lanzamos la aplicación, después si queremos que se realice los fines de semana (no implementado en el código) y los dos últimos por si deseamos crear un log de actividad.

La segunda parte del archivo xml será dónde se identifiquen los equipos, la acción que deseamos que se realice y a qué hora la queremos efectuar. Esta configuración se leerá según el valor de frecuencia indicado en la parte general, se podrán crear tantos equipos como se deseen y cambiar los valores de ellos en caliente (si necesidad de parar y arrancar la aplicación).

En la aplicación de consola, realizada con Microsoft Visual Basic .NET (2013), tenemos las siguientes clases:

Clase ReadConfig, en esta clase realizamos la lectura de los valores del xml que es la configuración de la aplicación y el modo que actúa con los equipos.

Imports System.Xml
Imports Microsoft.VisualBasic.FileIO

Public Class ReadConfig

    Private xmlDoc As XmlDocument
    Private nodeList As XmlNodeList
    Private node As XmlNode
    Private pathConfig As String = "C:\Phoenix\Config_Phoenix.xml"
    Private writeLog As New WriteLog

    Public frecuencia As Integer
    Public finSemana As Boolean
    Public escrituraLog As Boolean
    Public rutaLog As String

    Public Sub ConfigGeneral()
        Try
            If FileSystem.FileExists(pathConfig) Then
                xmlDoc = New XmlDocument()
                xmlDoc.Load(pathConfig)

                nodeList = xmlDoc.SelectNodes("configuracion/general/frecuencia")
                For Each node In nodeList
                    frecuencia = (node.InnerXml)
                Next
                nodeList = xmlDoc.SelectNodes("configuracion/general/finSemana")
                For Each node In nodeList
                    finSemana = (node.InnerXml)
                Next
                nodeList = xmlDoc.SelectNodes("configuracion/general/escrituraLog")
                For Each node In nodeList
                    escrituraLog = (node.InnerXml)
                Next
                nodeList = xmlDoc.SelectNodes("configuracion/general/rutaLog")
                For Each node In nodeList
                    rutaLog = (node.InnerXml)
                Next
                writeLog.EscribeLog(Now.ToString & " , In , Lectura de Configuracion General.")
            Else
                'controlar si no exite el xml de configuracion
            End If
        Catch ex As Exception
            writeLog.EscribeLog(Now.ToString & " , Er , Se ha producido un error en la lectura de Configuracion General. " & ex.Message)
        End Try
        
    End Sub

    Public Sub ConfigEquipos()
        Dim nombreEquipo As String
        Dim direccionIP As String
        Dim direccionMac As String
        Dim descripcion As String
        Dim encender As String
        Dim apagar As String
        Dim reiniciar As String
        Dim horaEncender As Date
        Dim horaApagar As Date
        Dim horaReiniciar As Date

        Try
            xmlDoc = New XmlDocument
            xmlDoc.Load(pathConfig)
            nodeList = xmlDoc.SelectNodes("configuracion/equipos/equipo")
            For Each node In nodeList
                nombreEquipo = node.ChildNodes.Item(0).InnerText
                direccionIP = node.ChildNodes.Item(1).InnerText
                direccionMac = node.ChildNodes.Item(2).InnerText
                descripcion = node.ChildNodes.Item(3).InnerText
                encender = node.ChildNodes.Item(4).InnerText
                horaEncender = node.ChildNodes.Item(5).InnerText
                apagar = node.ChildNodes.Item(6).InnerText
                horaApagar = node.ChildNodes.Item(7).InnerText
                reiniciar = node.ChildNodes.Item(8).InnerText
                horaReiniciar = node.ChildNodes.Item(9).InnerText

                Dim accion As New Decisiones

                accion.decisionEquipo(nombreEquipo, direccionIP, direccionMac, descripcion, _
                            encender, horaEncender, apagar, horaApagar, reiniciar, horaReiniciar)

            Next
        Catch ex As Exception
            writeLog.EscribeLog(Now.ToString & " , Er , Se ha producido un error en la lectura de Configuracion de Equipos. " & ex.Message)
        End Try
    End Sub

Clase OnOff, en esta clase definimos como operar dependiendo si es un inicio, apagado o reinicio.

Imports System.Diagnostics
Imports System.Net
Imports System.Net.NetworkInformation
Imports System.ServiceProcess

Public Class OnOff
    Private servicio As ServiceController '= New ServiceController(nombreServicio)
    Private pingSender As New Ping
    Private pingReply As PingReply
    Private writeLog As New WriteLog

    Public Sub WakeOnLAN(ByVal direccionMac As String, ByVal direccionIP As String, ByVal nombreEquipo As String)
        'http://www.planetsourcecode.com/vb/scripts/ShowCode.asp?txtCodeId=2367&lngWId=10
        '// Name: A Wake-On-LAN sample from VB.NET
        '// Description:Wake-On-LAN standard is based on a Magic Packet and is used to wake up PCs from remote. This VC#.NET sample shows how to power up PCs using Wake-On-LAN, also called WOL or wake-up-on-LAN
        '// By: Roby Kott
        '//This code is copyrighted and has// limited warranties.Please see http://www.Planet-Source-Code.com/vb/scripts/ShowCode.asp?txtCodeId=2367&lngWId=10//for details.//**************************************
        ' ********************************************************************
        ' Wake-On-LAN sample - power on a remote PC, based on its MAC address
        'Requires freeware ASocket.dll to run the sample. Download it from http://www.vahland.com/asocket/asocket.dll
        'and register it (REGSVR32 asocket.dll) on your machine.
        ' IMPORTANT: Online manual: http://www.vahland.com/asocket/manual.htm

        Dim objWol As ASOCKETLib.WOLClass = New ASOCKETLib.WOL()
        Try
            ' Se envia un paquete ICMP a la dirección IP para ejecutar si está apagado el equipo
            pingReply = pingSender.Send(direccionIP)
            If pingReply.Status <> 0 Then
                'Console.WriteLine("WakeUp " & direccionMac & " ...")
                objWol.WakeUp(direccionMac)
                ' Console.WriteLine("Resultado: " & objWol.GetErrorDescription(objWol.LastError))
                System.Threading.Thread.Sleep(5000)
                writeLog.EscribeLog(Now.ToString & " , In , Se ha Encendido el equipo " & nombreEquipo)
            Else
                writeLog.EscribeLog(Now.ToString & " , In , No se ha podido encender el equipo " & nombreEquipo & " porque no tiene conexion a la red o el soporte WOL está deshabilitado.")
                Exit Sub
            End If
        Catch ex As Exception
            writeLog.EscribeLog(Now.ToString & " , Er , Se ha producido un error en el Encendido del equipo " & nombreEquipo & " Mensaje de error: " & ex.Message)
        End Try
    End Sub

    Public Sub RestartComputer(ByVal direccionIP As String, ByVal nombreEquipo As String)
        Try
            ' Se envia un paquete ICMP a la dirección IP para ejecutar si está encendido el equipo
            ' pingReply.Status = 0 => succesful = equipo encendido
            pingReply = pingSender.Send(direccionIP)
            If pingReply.Status = 0 Then
                Try
                    'System.Threading.Thread.Sleep(20000)
                    Dim process As New Process
                    process.Start("shutdown.exe", " -m \\" & direccionIP & " -r -t 1 -f")
                    System.Threading.Thread.Sleep(30000)
                    writeLog.EscribeLog(Now.ToString & " , In , Se ha Reinicio el equipo " & nombreEquipo)
                Catch ex As Exception
                    writeLog.EscribeLog(Now.ToString & " , Er , No se ha podido reiniciar el equipo " & nombreEquipo)
                End Try
            Else
                writeLog.EscribeLog(Now.ToString & " , In , No se ha podido reiniciar el equipo " & nombreEquipo & " porque está apagado o sin conexion")
                Exit Sub
            End If
        Catch ex As Exception
            writeLog.EscribeLog(Now.ToString & " , Er , Se ha producido un error en el Reinicio del equipo " & nombreEquipo & " Mensaje de error: " & ex.Message)
        End Try
    End Sub

    Public Sub ShutdownComputer(ByVal direccionIP As String, ByVal nombreEquipo As String)
        Try
            ' Se envia un paquete ICMP a la dirección IP para ejecutar si está encendido el equipo
            pingReply = pingSender.Send(direccionIP)
            If pingReply.Status = 0 Then
                Try

                    Dim process As New Process
                    process.Start("shutdown.exe", " -m \\" & direccionIP & " -s -t 1 -f")
                    writeLog.EscribeLog(Now.ToString & " , In , Se ha Apagado el equipo " & nombreEquipo)
                Catch ex As Exception
                    writeLog.EscribeLog(Now.ToString & " , Er , No se ha podido apagar el equipo " & nombreEquipo)
                End Try
            Else
                writeLog.EscribeLog(Now.ToString & " , In , No se ha podido reiniciar el equipo " & nombreEquipo & " porque está apagado o sin conexion")
                Exit Sub
            End If
        Catch ex As Exception
            writeLog.EscribeLog(Now.ToString & " , Er , Se ha producido un error en el Apagado del equipo " & nombreEquipo & " Mensaje de error: " & ex.Message)
        End Try
    End Sub

Clase Decisiones, es quien activa lógica, según el archivo xml, de acciones.
Public Class Decisiones

    Public Sub decisionEquipo(ByVal nombreEquipo As String, ByVal direccionIP As String, _
                        ByVal direccionMac As String, ByVal descripcion As String, _
                        ByVal encender As String, ByVal horaEncender As Date, ByVal apagar As String, _
                        ByVal horaApagar As Date, ByVal reiniciar As String, ByVal horaReiniciar As Date)
        Dim onOff As New OnOff

        Select Case encender
            Case "Y"
                If horaEncender.Hour = Now.Hour And ((horaEncender.Minute - 3) <= Now.Minute) _
                    And ((horaEncender.Minute + 5) > Now.Minute) Then
                    onOff.WakeOnLAN(direccionMac, direccionIP, nombreEquipo)
                    Console.WriteLine(Now & " Encender: => " & nombreEquipo)
                End If
            Case "N"
                Exit Select
        End Select

        Select Case apagar
            Case "Y"
                If horaApagar.Hour = Now.Hour And ((horaApagar.Minute + 3) >= Now.Minute) Then
                    onOff.ShutdownComputer(direccionIP, nombreEquipo)
                    Console.WriteLine(Now & " Apagar: " & nombreEquipo)
                End If
            Case "N"
                Exit Select
        End Select

        Select Case reiniciar
            Case "Y"
                If horaReiniciar.Hour = Now.Hour And ((horaReiniciar.Minute + 1) > Now.Minute) Then
                    onOff.RestartComputer(direccionIP, nombreEquipo)
                    Console.WriteLine(Now & "Reiniciar: " & nombreEquipo)
                End If
            Case "N"
                Exit Select
        End Select
    End Sub

La clase Main es la inicial y la encargada de llamar a las clases anteriores cuando se indique.
Imports System.Timers

Module Main

    Sub Main()
        Dim writeLog As New WriteLog
        Dim temporizador As New Timer()

        writeLog.EscribeLog(Now.ToString & " , In , Iniciado Phoenix.")

        Console.WriteLine("Phoenix iniciado .....................")
        Console.WriteLine("Pulsa q + intro para salir")
        Console.WriteLine(vbCrLf)

        ' Lectura de configuración general
        Dim configGeneral As New ReadConfig
        configGeneral.ConfigGeneral()

        ' Hook up the Elapsed event for the timer.
        AddHandler temporizador.Elapsed, AddressOf OnTimedEvent

        ' Establecer el intervalo = frecuencia en segundos
        temporizador.Interval = (configGeneral.frecuencia * 1000)
        temporizador.Enabled = True

        ' Mantener activo el timer hasta el fin del Main
        GC.KeepAlive(temporizador)

        While Chr(Console.Read()) <> "q"c
        End While

        Console.WriteLine("Phoenix finalizado .....................")
    End Sub

    Private Sub OnTimedEvent(ByVal source As Object, ByVal e As ElapsedEventArgs)
        Dim configuracion As New ReadConfig()

        configuracion.ConfigEquipos()
    End Sub
End Module

La clase que efectúa la escritura del log no está reflejada en esta entrada, tampoco la conversión a servicio ni una clase especial de impersonación.

No hay comentarios:

Publicar un comentario