Share via


¡ Hola, chicos del scripting! Calcular tiempo de actividad del servidor

The Microsoft Scripting Guys

copia es hacia arriba y abajo está inactivo. Parece bastante obvio, excepto, es decir, cuando hablamos tiempo de actividad del servidor. Para saber el tiempo de actividad, tiene que saber tiempo de inactividad. Casi todos los administradores de red es preocupado de tiempo de actividad del servidor. (Excepto, es decir, si está preocupado de tiempo de inactividad del servidor.) La mayoría de los administradores dispone de los objetivos de tiempo de actividad y necesita proporcionar informes de actividad a la administración superior.

¿Cuál es el problema? Parece que se puede utilizar la clase WMI Win32_OperatingSystem, que tiene dos propiedades que deben una operación de este tipo es bastante fácil: LastBootUpTime y LocalDateTime. Todo lo que necesita hacer, piensa, es restar el LastBootUptime desde el LocalDateTime, todo es correcto con el mundo y puede ir incluso detectar un rápido agujeros nueve antes de la cena.

Por lo que desencadena hasta Windows PowerShell para consultar la clase WMI Win32_OperatingSystem y seleccione las propiedades, tal como se muestra aquí:

PS C:\> $wmi = Get-WmiObject -Class Win32_OperatingSystem
PS C:\> $wmi.LocalDateTime - $wmi.LastBootUpTime

Pero cuando ejecute estos comandos, se muestra no con el tiempo de actividad descriptivo de su servidor, pero por la mensaje de error mournful en su lugar ve en La figura 1 .

fig01.gif

La figura 1 se ha devuelto un error al intentar restar los valores de hora UTC de WMI (haga clic en la imagen de una vista más grande)

El mensaje de error es quizás un poco confuso: " constante numérica." ¿Verdad? ¿Sabe lo que un número es y sabe lo que una constante es, pero lo que esto tiene que hacer con el tiempo?

Cuando se enfrentan con mensajes de error extraño, es mejor consultar directamente los datos que está intentando analizar la secuencia de comandos. Además, con Windows PowerShell, generalmente sentido para ver qué tipo de datos se está utilizando.

Para examinar los datos de que la secuencia de comandos está utilizando, puede simplemente imprimirla en la pantalla. Esto es lo que obtendrá:

PS C:\> $wmi.LocalDateTime
20080905184214.290000-240

El número parece un poco extraño. ¿Qué tipo de una fecha es esto? Para averiguar, utilice el método GetType. El bueno sobre GetType es que casi siempre está disponible. Todo lo que necesita hacer es llamar. Y aquí es el origen del problema, se notifica el valor LocalDateTime como una cadena, no como un valor System.DateTime:

PS C:\> $wmi.LocalDateTime.gettype()

IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True     True     String
System.Object

Si tiene que restar una vez desde otro momento, asegúrese de que está trabajando con los valores de hora y no las cadenas. Esto es fácil hacer mediante el método ConvertToDateTime, que Windows PowerShell agrega a todas las clases WMI:

PS C:\> $wmi = Get-WmiObject -Class Win32_OperatingSystem
PS C:\> $wmi.ConvertToDateTime($wmi.LocalDateTime) –
$wmi.ConvertToDateTime($wmi.LastBootUpTime)

Si se restan un valor de tiempo de otro, quedan con una instancia de un objeto System.TimeSpan. Esto significa que puede elegir cómo mostrar la información de tiempo de actividad sin tener que realizar una gran cantidad de operaciones aritméticas. Sólo es necesario elegir qué propiedad ha de mostrar (y Esperemos que contar el tiempo de actividad en TotalDays y no en TotalMilliseconds). La pantalla predeterminada del objeto System.TimeSpan se muestra aquí:

Days              : 0
Hours             : 0
Minutes           : 40
Seconds           : 55
Milliseconds      : 914
Ticks             : 24559148010
TotalDays         : 0.0284249398263889
TotalHours        : 0.682198555833333
TotalMinutes      : 40.93191335
TotalSeconds      : 2455.914801
TotalMilliseconds : 2455914.801

El problema con este método es que sólo indica el tiempo el servidor ha sido copia desde el último reinicio. No se calcula tiempo de inactividad. Es este donde resulta arriba hacia abajo, para calcular el tiempo de actividad, primero debe conocer el tiempo de inactividad.

¿Cómo averiguar cuánto el servidor ha sido hacia abajo? Para ello, tiene que saber cuando se inicia el servidor y cuando apaga. Puede obtener esta información del sistema de registro de eventos. Uno de los procesos primero que comienza en el servidor o estación de trabajo es el registro de eventos, y uno de las últimos cosas que detienen cuando un servidor está apagado es el registro de eventos. Cada una de estas inicio/detención eventos genera un event­ID, 6005 al iniciar el registro de sucesos y 6006 cuando detiene el registro de eventos. la figura 2 muestra un ejemplo de un registro de sucesos de inicio.

fig02.gif

La Figura 2 el servicio de registro de eventos se iniciará poco después de inicio del equipo (haga clic en la imagen de una vista más grande)

Al recopilar los eventos 6005 y 6006 desde el registro del sistema, ordenarlos y restar el se inicia desde la se detiene, puede determinar la cantidad de tiempo que el servidor era hacia abajo entre reinicios. Si a continuación, se resta esa cantidad desde el número de minutos durante el período de tiempo en cuestión, puede calcular el porcentaje de tiempo de actividad del servidor. Éste es el enfoque tomado en la secuencia de comandos CalculateSystemUpTimeFromEventLog.ps1, que se muestra en la figura 3 .

La figura 3 CalculateSystemUpTimeFromEventLog 3

#---------------------------------------------------------------
# CalculateSystemUpTimeFromEventLog.ps1
# ed wilson, msft, 9/6/2008
# Creates a system.TimeSpan object to subtract date values
# Uses a .NET Framework class, system.collections.sortedlist to sort the events from eventlog.
#---------------------------------------------------------------
#Requires -version 2.0
Param($NumberOfDays = 30, [switch]$debug)

if($debug) { $DebugPreference = " continue" }

[timespan]$uptime = New-TimeSpan -start 0 -end 0
$currentTime = get-Date
$startUpID = 6005
$shutDownID = 6006
$minutesInPeriod = (24*60)*$NumberOfDays
$startingDate = (Get-Date -Hour 00 -Minute 00 -Second 00).adddays(-$numberOfDays)

Write-debug "'$uptime $uptime" ; start-sleep -s 1
write-debug "'$currentTime $currentTime" ; start-sleep -s 1
write-debug "'$startingDate $startingDate" ; start-sleep -s 1

$events = Get-EventLog -LogName system | 
Where-Object { $_.eventID -eq  $startUpID -OR $_.eventID -eq $shutDownID `
  -and $_.TimeGenerated -ge $startingDate } 

write-debug "'$events $($events)" ; start-sleep -s 1

$sortedList = New-object system.collections.sortedlist

ForEach($event in $events)
{
 $sortedList.Add( $event.timeGenerated, $event.eventID )
} #end foreach event
$uptime = $currentTime - $sortedList.keys[$($sortedList.Keys.Count-1)]
Write-Debug "Current uptime $uptime"

For($item = $sortedList.Count-2 ; $item -ge 0 ; $item -- )
{ 
 Write-Debug "$item `t `t $($sortedList.GetByIndex($item)) `t `
   $($sortedList.Keys[$item])" 
 if($sortedList.GetByIndex($item) -eq $startUpID)
 {
  $uptime += ($sortedList.Keys[$item+1] - $sortedList.Keys[$item])
  Write-Debug "adding uptime. `t uptime is now: $uptime"
 } #end if  
} #end for item 

"Total up time on $env:computername since $startingDate is " + "{0:n2}" -f `
  $uptime.TotalMinutes + " minutes."
$UpTimeMinutes = $Uptime.TotalMinutes
$percentDownTime = "{0:n2}" -f (100 - ($UpTimeMinutes/$minutesInPeriod)*100)
$percentUpTime = 100 - $percentDowntime

"$percentDowntime% downtime and $percentUpTime% uptime."

La secuencia comienza con la instrucción de parámetro para definir un par de parámetros de línea de comandos cuyos valores se pueden cambiar cuando se ejecute la secuencia de comandos desde la línea de comandos. El primero, NumberOfDays $, permite especificar un número diferente de días que se utilice en el informe de actividad. (Tenga en cuenta que ha proporciona un valor predeterminado de 30 días de la secuencia de comandos para que pueda ejecutar la secuencia de comandos sin tener que proporcionar un valor para el parámetro. Por supuesto, puede cambiar esto si es necesario.)

El segundo, [modificador] $ depuración, es un parámetro conmutado que permite obtener determinado información de depuración de la secuencia de comandos si se incluye en la línea de comandos al ejecutar la secuencia de comandos. Esta información puede ayudarle a estar más seguros en los resultados que obtiene de la secuencia de comandos. Puede haber veces cuando el mensaje de detención del servicio Registro de sucesos 6006 no es está presente, quizás como resultado de un error catastrófico del servidor que representa no se puede escribir en el registro de sucesos, provocando el script para restar un valor de tiempo de actividad de otro valor de tiempo de actividad y sesgar los resultados.

Después de la variable de depuración $ se proporciona desde la línea de comandos, está presente en la variable: unidad. En ese caso, se establece el valor de la variable de debugPreference $ para continuar, lo que significa que la secuencia de comandos continuará ejecutándose y cualquier valor proporcionado para la depuración de escritura que estarán visible. Tenga en cuenta que, de forma predeterminada, el valor de $ debugPreference es silentlycontinue, para que si no establece el valor de $ debugPreference para continuar, se ejecutará la secuencia de comandos, pero cualquier valor proporcionado para la depuración de escritura será silencioso (es decir, no será visible).

Cuando se ejecuta la secuencia de comandos, la salida resultante muestra una lista cada instancias de las entradas de registro de eventos 6005 y 6006 (como puede ver en la figura 4 ) y muestra el cálculo del tiempo de actividad. Con esta información, puede confirmar la precisión de los resultados.

fig04.gif

La figura 4 el modo de depuración muestra un seguimiento de cada valor de hora que se agrega al cálculo de tiempo de actividad (haga clic en la imagen de una vista más grande)

El paso siguiente consiste en crear una instancia del objeto System.TimeSpan. Puede utilizar el cmdlet New-Object para crear un objeto de timespan predeterminado que se podría utilizar para realizar cálculos de fecha de diferencia:

PS C:\> [timespan]$ts = New-Object system.timespan

Pero Windows PowerShell realmente tiene un cmdlet New-TimeSpan para crear un objeto timespan, por lo que tiene sentido utilizarlo. Mediante este cmdlet facilita la secuencia de comandos la lectura de, y el objeto que se creó es equivalente al objeto timespan creado con objeto de nuevo.

Ahora, se pueden inicializar variables unas, comenzando por $ currentTime, que se utiliza para contener el valor de fecha y hora actual. Obtener esta información a partir del cmdlet Get-Date:

$currentTime = get-Date

A continuación, inicializar las dos variables que contendrá los números de parámetro eventID inicio y cierre. Realmente no necesita hacer esto, pero el código será más fácil lectura y fáciles de solucionar si evitar la incrustación de las dos como valores literales de cadena.

El siguiente paso es crear un minutesInPeriod variable denominada $ para almacenar el resultado del cálculo utilizado para averiguar el número de minutos durante el período de tiempo en cuestión:

$minutesInPeriod = (24*60)*$NumberOfDays

Por último, debe crear la variable $ startingDate, que contendrá un objeto System.DateTime que representa la hora inicial del período de informe. La fecha será la medianoche de la fecha de comienzo para el período:

$startingDate = (Get-Date -Hour 00 -Minute 00 -Second 00).adddays(-$numberOfDays)

Después de se crean las variables, recuperar los eventos del registro de eventos y almacenar los resultados de la consulta en la variable de eventos $. Utilice el cmdlet Get-EventLog para consultar el registro de eventos, si se especifica "sistema" como el nombre del registro. En Windows PowerShell 2.0, puede utilizar un parámetro –source para reducir la cantidad de información que necesita que se ordenen salida en el cmdlet Where-Object. Pero en Windows PowerShell 1.0, no tiene esa opción y, por tanto, debe ordenar a través de todos los eventos sin filtrar devueltos por la consulta. Por lo que canalizar los eventos para el cmdlet Where-Object para filtrar las entradas de correspondiente registro de eventos. Examina el filtro WHERE-Object, verá por qué los chicos del scripting tenía que crear las variables para contener los parámetros.

El comando lee mucho mejor que lo haría si ha utilizado los literales de cadena. Los eventIDs get son iguales a $ startUpID o a shutDownID $. También debe asegurarse de la propiedad timeGenerated de la entrada del registro de eventos es mayor o igual que el startingDate $, así:

$events = Get-EventLog -LogName system |
Where-Object { $_.eventID -eq  $startUpID -OR $_.eventID -eq $shutDownID -and $_.TimeGenerated -ge $startingDate }

Tenga en cuenta que este comando sólo se ejecutan localmente. En Windows Power­Shell 2.0, puede utilizar el parámetro –computerName para que el comando trabajar de forma remota.

El paso siguiente consiste en crear un objeto de lista ordenada. ¿Por qué? Porque al recorra la colección de eventos, no se garantiza el orden en que se notifican las entradas del registro de eventos. Incluso si canalizar los objetos el cmdlet de orden de objeto y almacén de que los resultados de nuevo en una variable, al recorrer en iteración los objetos y almacenar los resultados en una tabla hash, no puede ser determinados que la lista mantiene los resultados del procedimiento ordenar.

Para sidestep estos problemas frustrantes y difícil de depuración, crear una instancia del objeto System.Collections.SortedList, utilizando el constructor predeterminado para el objeto. El constructor predeterminado indica a la lista ordenada para ordenar fechas cronológicamente. Almacenar el objeto lista ordenada vacía en la variable de sortedList $:

$sortedList = New-object system.collections.sortedlist

Después de crear el objeto de lista ordenada, debe rellenarla. Para ello, utilice la instrucción ForEach y recorra la colección de entradas de registro de eventos almacenadas en la variable de entradas de $. Como recorrer la colección, la variable de evento $ realiza un seguimiento de su posición en la colección. Puede utilizar el método add para agregar dos propiedades del objeto System.Collections.SortedList. La lista ordenada le permite agregar una clave y una propiedad de valor (similar a un objeto Dictionary excepto que también permite índice de la colección sólo como una matriz no). Agregar la propiedad timegenerated como la clave y el event­ID como la propiedad de valor:

ForEach($event in $events)
{
 $sortedList.Add( $event.timeGenerated,
 $event.eventID )
} #end foreach event

A continuación, puede calcular el tiempo de actividad actual del servidor. Para ello, utilice la entrada de registro de eventos más reciente en la lista ordenada. Observe que este siempre estará una instancia 6005 porque si la entrada más reciente eran 6006, el servidor sigue sería hacia abajo. Dado que el índice es basada en cero, la entrada más reciente será el – 1 número.

Para recuperar el valor de tiempo, genere, tiene que mirar la propiedad clave de la lista ordenada. Para obtener el valor de índice, utilice la propiedad recuento y restar uno a él. A continuación, resta la hora que se generó el evento de 6005 del valor de hora de fecha almacenado en la variable de currenttime $ anteriormente rellena. Puede imprimir los resultados de este cálculo sólo si la secuencia de comandos se ejecuta en modo de depuración. Este código se muestra aquí:

$uptime = $currentTime -
$sortedList.keys[$($sortedList.Keys.Count-1)]
Write-Debug "Current uptime $uptime"

Ahora es el momento para recorrer en iteración el objeto de lista ordenada y calcular el tiempo de actividad para el servidor. Debido a está utilizando el objeto de lista System.Collections.Sorted, que aprovechar el hecho de que se puede indizar en la lista. Para ello, utilice el de instrucción, comenzando por el número-2 porque se utiliza contar-1 anterior a la figura la cantidad actual de tiempo de actividad.

Vamos a contar hacia atrás para obtener el tiempo de actividad, por lo que la condición especificado en la segunda posición de la instrucción for es cuando el elemento es mayor o igual a 0. En la tercera posición de la instrucción utiliza--, que disminuir el valor del elemento $ en uno. Utilice el cmdlet Write-Debug para imprimir el valor del número de índice si se ejecuta la secuencia de comandos con el modificador –debug. También ficha sobre mediante el ` caracteres e imprimir el valor de tiempo timegenerated. En esta sección de código se muestra aquí:

For($item = $sortedList.Count-2 ; $item -ge 
  0 ; $item--)
{ 
 Write-Debug "$item `t `t $($sortedList.
 GetByIndex($item)) `t `
   $($sortedList.Keys[$item])" 

Si el valor de parámetro eventID es igual a 6005, que es el valor de parámetro eventID inicio, calcular la cantidad de tiempo de actividad restando el tiempo especificado inicio por el valor de tiempo de inactividad anterior. Almacenar este valor en la variable de tiempo de actividad $. Si se encuentra en modo de depuración, puede usar el cmdlet Write-Debug para imprimir estos valores en la pantalla:

 if($sortedList.GetByIndex($item) -eq $startUpID)
 {
  $uptime += ($sortedList.Keys[$item+1] 
  - $sortedList.Keys[$item])
  Write-Debug "adding uptime. `t uptime is now: $uptime"
 } #end if  
} #end for item

Por último, necesita generar el informe. Recoger la nombre de equipo desde el equipo variable de entorno del sistema. Se utiliza la hora actual almacenada en el valor de startingdate $, y se muestran el totales en minutos de tiempo de actividad para el período. Se utiliza el especificador de formato {0:n2} para imprimir el número a dos dígitos. A continuación, calcular el porcentaje de tiempo de inactividad dividiendo el número de minutos de tiempo de actividad por el número de minutos en el período de tiempo cubiertos por el informe. Utilice el mismo especificador de formato para imprimir el valor para dos posiciones decimales. Simplemente por diversión, puede también calcular el porcentaje de tiempo de actividad y, a continuación, se imprimen ambos valores, así:

"Total up time on $env:computername since $startingDate is " + "{0:n2}" -f `
  $uptime.TotalMinutes + " minutes."
$UpTimeMinutes = $Uptime.TotalMinutes
$percentDownTime = "{0:n2}" -f (100 - ($UpTimeMinutes/$minutesInPeriod)*100)
$percentUpTime = 100 - $percentDowntime
"$percentDowntime% downtime and $percentUpTime% uptime."

¿Por lo que ahora los chicos del scripting volver a la pregunta original: cuando está hacia abajo hasta? Ahora verá que no puede hablar actividad sin retirar el tiempo de inactividad en consideración. Si considerar que esto era divertidas, consulte el otro " ¡ Hola, chicos del scripting " columnas en TechNet o vaya a la Centro de secuencias de comandos.

Problemas de versión

Al probar la secuencia de comandos Calculate­System­Up­timeFromEventLog.ps1 en su equipo portátil, Michael Murgolo de editor Colaborador se ejecutó a través de un error más molesto. Asignó la secuencia de comandos a mi amigo JIT, y se ejecutó en el mismo error. ¿Cuál fue ese error? Bueno, aquí es:

PS C:\> C:\fso\CalculateSystemUpTimeFromEventLog.ps1
Cannot index into a null array.
At C:\fso\CalculateSystemUpTimeFromEventLog.ps1:36 char:43 + $uptime = 
$currentTime - $sortedList.keys[$ <<<< ($sortedList.Keys.Count-1)]
Total up time on LISBON since 09/02/2008 00:00:00 is 0.00 minutes.
100.00% downtime and 0% uptime.

El error "No se puede indizar en una matriz null," es una indicación de que la matriz no era creada correctamente. Por lo que dug en el código que crea la matriz:

ForEach($event in $events)
{
 $sortedList.Add( $event.timeGenerated,
 $event.eventID )
} #end foreach event

En al final, que el código ha sido bien. ¿Qué causa el error?

A continuación, decidí ver el objeto SortedList. Para ello, escribí una secuencia de comandos simple que crea una instancia de la clase System.Collections.SortedList y agrega información a esa. En este momento, utiliza la propiedad claves para imprimir la lista de las claves. Éste es que el código:

$aryList = 1,2,3,4,5
$sl = New-Object Collections.SortedList
ForEach($i in $aryList)
{
 $sl.add($i,$i)
}

$sl.keys

En mi equipo, este código funciona correctamente. En equipo de JIT, error. Bummer. Pero al menos señala me en la dirección correcta. El problema, aparentemente, es que hay un error con el System.Collections.SortedList en Windows PowerShell 1.0. Y se está ejecutando la versión más reciente de la aún se liberan Windows PowerShell 2.0 donde ese error se corrigió y, por tanto, el código se ejecuta bien.

¿Por lo que en que deje nuestra secuencia de comandos? Según parece, la clase SortedList tiene un método denominado GetKey y dicho método funciona en Windows Power­Shell 1.0 y 2.0 de Power­Shell de Windows. Por lo que para la versión 1.0 de la secuencia de comandos, nos modificar el código para que utilice GetKey en lugar de recorre mediante iteración la colección de claves. En la versión 2.0 de la secuencia de comandos, nos agregar una etiqueta que requiere la versión 2.0 de Windows PowerShell. Si intenta ejecutar esa secuencia de comandos en un equipo con Windows PowerShell 1.0, simplemente se terminará la secuencia de comandos y no obtendrá el error.

Michael también señalar algo que no es un error pero está relacionado con una consideración de diseño. Tener en cuenta que la secuencia de comandos no correctamente detectará tiempo de actividad si hibernación o suspensión del equipo. Esto es cierto, como no nos detectar o busque estos eventos.

En realidad, sin embargo, no estoy relacionado con el tiempo de actividad en el portátil o un equipo de escritorio. Sólo Estoy interesado en tiempo de actividad en un servidor, y aún tengo que cumplir con el servidor que esté en estado de suspensión o hiberna. Podría suceder, por supuesto y puede ser una forma interesante para conservar la electricidad en el centro de datos, pero no es algo que ha encontrado todavía. Permítanme saber si hibernación de los servidores. Se pueden llegar a mí en Scripter@Microsoft.com.

Dr. Perplexer de secuencias de comandos del Scripto

El desafío mensual que prueba no sólo su capacidad para resolver el rompecabezas sino también sus habilidades de secuencias de comandos.

2008 De diciembre: comandos de PowerShell

La lista siguiente contiene comandos de Windows PowerShell 21. El cuadrado contiene los mismos comandos, pero están ocultos. El trabajo es encontrar los comandos que pueden estar ocultos horizontal, vertical o diagonal (hacia delante o hacia atrás).

EXPORT-CSV FORMAT-LIST FORMAT-TABLE
GET-ACL GET-ALIAS GET-CHILDITEM
GET-UBICACIÓN INVOCACIÓN DE ARTÍCULO MEDIDA DE OBJECT
NUEVO ITEMPROPERTY OUT-HOST OUT-NULL
REMOVE-PSSNAPIN SET-ACL SET-TRACESOURCE
PATH DE DIVIDIR SLEEP DE INICIO DETENCIÓN DE SERVICIO
SUSPENDER-SERVICIO ESCRITURA DE DEBUG ADVERTENCIA DE ESCRITURA

\\msdnmagtst\MTPS\TechNet\issues\en\2008\12\HeyScriptingGuy - 1208\Figures\puzzle.gif

RESPUESTA:

Dr. Perplexer de secuencias de comandos del Scripto

Respuesta: Diciembre de 2008: comandos de PowerShell

fig12.gif

Ed Wilson es un consultor sénior de Microsoft y un experto de secuencias de comandos conocido. Es un Microsoft Certified Trainer que ofrece un taller de Windows PowerShell popular a los clientes de Microsoft Premier en todo el mundo. Ha escrito ocho libros incluidas varias de las secuencias de comandos de Windows y ha contribuido a prácticamente una docena otros libros. Ed contiene más de 20 certificaciones del sector. Craig Liebendorfer es wordsmith y longtime editor Web de Microsoft. Craig aún no se puede creer hay un trabajo que se paga a trabajar con las palabras cada día. Uno de sus cosas favoritas es irreverent humor, para que debe ajustarse derecha en aquí. Considera que su hija magnificent que su accomplishment mayor en la vida.

Craig Liebendorfer es wordsmith y longtime editor Web de Microsoft. Craig aún can’t creer ’s hay un trabajo que se paga a trabajar con las palabras cada día. Uno de sus cosas favoritas es irreverent humor, para que debe ajustarse derecha en aquí. Considera que su hija magnificent que su accomplishment mayor en la vida.