miércoles, 10 de septiembre de 2014

Cambiar la hora en Windows con NTP por consola: PowerShell

¡¡Hola!!

Ultimamente estoy viendo que la hora de mi equipo se va retrasando poco a poco. Además, me doy cuenta cuando ya lleva unas cuantas horas de retraso. Y me da que es cuestión de la pila de la BIOS, cosa que me da una pereza enorme cambiarla.

Ya sé que desde la interfaz gráfica de Windows se puede cambiar la hora, e, incluso, forzar a que se sincronice contra un servidor NTP para que esté perfecta.

Cambiar la hora en Windows utilizando NTP - GUI
Cambiar la hora en Windows utilizando NTP - GUI
Ahora bien, quería ir más allá. Quería conseguir cambiar la hora utilizando powershell y tirando, a su vez, de un servidor NTP.

En mi búsqueda localicé algún enlace que otro que permitía obtener un montón de datos de comparativas de la hora del sistema contra el servidor indicado. Es el cmlet llamado Get-NtpTime. Una vez importado, podríamos obtener los datos que se muestran por pantalla:

Cmlet Get-NtpTime, por Chris J. Warwick, @cjwarwickps
Aún así, le faltaba algo muy, muy importante. ¿De qué me sirve ver todos estos datos si no puedo ponerle al sistema la hora que me ha indicado el servidor? Por lo tanto, busqué la forma de cambiarla. ¿Cómo lo he hecho? Fusilando el script para adecuarlo a mis necesidades. Algunos datos no sé para qué los quiere, o si supondrán una gran diferencia. Como por ejemplo, el tiempo que tarda en conectarse y desconectarse utiliza dos variables de tiempo inicial y final. 

Por lo tanto, después de hacer varias pruebas, algunas con más éxito que otras, ya tengo un cmlet que permite cambiarte la hora utilizando la que te devuelve el servidor NTP pasado como parámetro. El script se llama Set-NtpTimeV2 (por las distintas pruebas y esas cosas), y tiene un resultado como el siguiente:

Cmlet Set-NtpTimeV2, por Agux

Y el código fuente, tal cual lo he dejado, eliminando las cosas que no hacían falta del original y poniendo las necesarias (las que menos, la verdad):

Function Set-NtpTimeV2 {
#Parametros
[CmdletBinding()]
Param (
[String]$Server = 'pool.ntp.org',
[Int]$MaxOffset = 10000     # (Milliseconds) Throw if network time offset is larger
)
# Construct a 48-byte client NTP time packet to send to the specified server
# (Request Header: [00=No Leap Warning; 011=Version 3; 011=Client Mode]; 00011011 = 0x1B)
[Byte[]]$NtpData = ,0 * 48
$NtpData[0] = 0x1B    # NTP Request header in first byte
# NTP Transaction -------------------------------------------------------
$Socket = New-Object Net.Sockets.Socket([Net.Sockets.AddressFamily]::InterNetwork,
[Net.Sockets.SocketType]::Dgram,
[Net.Sockets.ProtocolType]::Udp)
Try {
$Socket.Connect($Server,123)
}
Catch {
Write-Error $_
Throw "Failed to connect to server $Server"
}
$t1 = Get-Date    # Start of transaction... the clock is ticking...
Try {
[Void]$Socket.Send($NtpData)
[Void]$Socket.Receive($NtpData)  
}
Catch {
Write-Error $_
Throw "Failed to communicate with server $Server"
}
$t4 = Get-Date    # End of transaction time
$Socket.Close()
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# Check the Leap Indicator (LI) flag for an alarm condition - extract the flag
# from the first byte in the packet by masking and shifting (dividing)
$LI = ($NtpData[0] -band 0xC0)/64    # Leap Second indicator
If ($LI -eq 3) {
Throw 'Alarm condition from server (clock not synchronized)'
}
# We now have the 64-bit NTP times, t3 is in the last 8 bytes of the received data.
# The NTP time is the number of seconds since 1/1/1900 and is split into an 
# integer part (top 32 bits) and a fractional part, multipled by 2^32, in the 
# bottom 32 bits.
# Convert Integer and Fractional parts of the (64-bit) t3 NTP time from the byte array
# $IntPart=0;  Foreach ($Byte in $NtpData[40..43]) {$IntPart  = $IntPart  * 256 + $Byte} 
# $FracPart=0; Foreach ($Byte in $NtpData[44..47]) {$FracPart = $FracPart * 256 + $Byte} 
$IntPart = [BitConverter]::ToUInt32($NtpData[43..40],0)
$FracPart = [BitConverter]::ToUInt32($NtpData[47..44],0)
# Convert to Millseconds (convert fractional part by dividing value by 2^32)
$t3ms = $IntPart * 1000 + ($FracPart * 1000 / 0x100000000)
# Make sure the result looks sane...
If ([Math]::Abs($Offset) -gt $MaxOffset) {
# Network time is too different from server time
Throw "Network time offset exceeds maximum ($($MaxOffset)ms)"
}
# Create Output object and return
echo "Cogiendo hora y fecha presente en el equipo..."
$currentDate = Get-Date
echo "La fecha y hora actual del sistema es: $currentDate"
echo "Obteniedo la fecha y hora real del servidor NTP..."
echo "Configurando la fecha y hora real..."
$nuevaFechaHora=New-Object DateTime(1900,1,1,0,0,0,[DateTimeKind]::Utc)   
$nuevaFechaHora = $nuevaFechaHora.AddMilliseconds($t3ms).ToLocalTime()
$nuevaFecha = Set-Date -Date $nuevaFechaHora
echo "La hora y fecha configurada es... $nuevaFecha"
}

No hay comentarios:

Publicar un comentario