Dans un domaine Windows, l'heure est quelque chose d'extrêmement important. La grande majorité des interactions entre machines, invisibles pour l'utilisateur, se basent sur un système d'horodatage précis, et un décallage d'heure entre deux machines peut les amener à ne plus pouvoir communiquer entre elles.
Pour autant que l'heure (et le temps en général) soient des paramètres extrêmements importants, les pannes qui en sont issues sont parfois difficilement cernables. Il ne suffit que de 300 secondes (5 minutes) d'écart entre deux machines pour causer des problèmes... et entre votre heure de réference, celle d'un serveur A qui recule de 2 minutes et celle d'un serveur B qui avance de 3 minutes, l'écart est assez subtil pour ne pas sauter directement aux yeux.
Dans cet article, je vais vous montrer comment faire pour récuperer les heures de chaque serveur en prenant en compte le temps d'exécution du script pour avoir une heure en cours à un instant précis.
Bien évidemment, c'est Powershell qui nous sera utile pour récolter l'intégralité des heures en cours sur chaque serveur. La solution la plus "simple" à première vue, serait de faire un invoke-command + get-date sur chaque serveur. Mais la présence d'un residuel windows 2000 et de quelques 2003 poserait problème. Nous allons donc nous tourner vers le WMI, qui est compatible avec ces versions, pour obtenir les informations d'heure sur chaque serveur ($serveur étant bien sur le nom du serveur).
$temps = Get-WmiObject win32_localtime -ComputerName $serveur
Et le résultat est le suivant :
Nous avons donc dans cet objet le jour (Day) le mois (Month) l'année (Year), l'heure (Hour) les minutes (Minute) et les secondes (Second). Il ne nous reste plus qu'a formater cela pour créer uniformiser le tout. Nous utiliserons l'opérateur -f pour créer modifier les chiffres à notre guise.
Nous précisons un format (spécifié entre {}) pour chaque partie, et créons une date / heure valide ainsi.
$resultat = $(("{0:D2}" -f $temps.day) + "/" + ("{0:D2}" -f $temps.month) + "/" + [string]$temps.year + " " + ("{0:D2}" -f $temps.hour) +":"+ ("{0:D2}" -f $temps.minute)+":" + ("{0:D2}" -f $temps.second))
Comment lire cela? Prenons pour exemple : "{0:D2}" -f $temps.day
Pour traduire :
"{En partant du point 0:Je veux un nombre à 2 chiffres}" -Formate_la_valeur $temps.day
Ici, le nombre est 18, il ne changera rien. Mais si c'était un chiffre tel que 6, il aurait renvoyé à la place 06. Cela nous permettra de modifier la date en un format utilisable dans une commande Get-Date.
Mais à cela s'ajoute une autre problématique. Si nous calculons l'heure en même temps sur tous les serveurs, alors le temps d'exécution de notre script va forcément fausser le retour. Pour être plus clair : Si nous lancons notre script à 10:00 précise, que l'analyse d'un serveur nous prends 2 secondes, au bout du centième serveur nous aurons déja 200 secondes d'écart, et nos résultats seront faussés.
Pour cela, nous allons mettre en place un petit calcul simple. Nous allons tout d'abord définir avant de rentrer dans la boucle une heure de réference. C'est cette heure ci qui servira de base de calcul au temps d'execution du script. Puis après chaque récupération de temps sur un serveur, nous enregistrerons une nouvelle fois l'heure actuelle. A chaque passage de boucle, nous retirerons la différence de ticks (un tick est 1/10.000.000e de seconde) entre l'heure dans la boucle et l'heure de réference. Et cette différence, ce nombre de ticks représente le temps de fonctionnement du script. Nous la retirerons ensuite de la date/heure renvoyée par le serveur pour avoir le temps exact qu'il était sur le serveur au moment ou vous avez lancé le script.
$heurereference = get-date #Notre heure avant boucle
#notre script ici avec le temps d'exécution lié
$heurecompare = get-date
($heurereference - $heurecompare).ticks #nombre de ticks à retirer de l'heure renvoyée par le serveur
Vous pouvez télécharger mon script depuis mon répertoire git personnel.
#Liste des serveurs à analyser
$serveurs = @("DC1","DC2","DC3","hyperv1","hyperv2","hyperv3","hyperv4","lotus1","lotus2")
#création de la variable qui stockera les résultats
$table = New-Object System.Collections.ArrayList
#L'heure de réference. C'est l'instant T qui définira l'heure exacte sur les serveurs en retirant le temps de fonctionnement du script
$heurereference = get-date
#Pour chaque serveur dans la liste...
foreach ($serveur in $serveurs)
{
write-host "Récupération de $serveur"
$resultat = $null
#Nous obtenons l'heure du serveur
$temps = Get-WmiObject win32_localtime -ComputerName $serveur
#Nous reprenons l'heure actuelle
$heurecompare = Get-Date
#Enregistrement d'un message d'erreur s'il en est
if (!$temps)
{
$resultat = "Erreur $($error[0].exception.message)"
}
else
{
#Nous formatons la valeur pour lui donner le format JJ/MM/AAAA hh:mm:ss
$resultat = $(("{0:D2}" -f $temps.day) + "/" + ("{0:D2}" -f $temps.month) + "/" + [string]$temps.year + " " + ("{0:D2}" -f $temps.hour) +":"+ ("{0:D2}" -f $temps.minute)+":" + ("{0:D2}" -f $temps.second))
#Nous la définissons en format date, et nous retirons le nombre de ticks passés. La méthode Addticks() est utilisée, mais en précisant une valeur négative elle retirera des ticks, comme c'est le cas ici
$resultat = (get-date $resultat).addticks(($heurereference - $heurecompare).Ticks))
}
#Nous ajoutons dans la table un objet qui correspond au nom du serveur et à l'heure recalculée
$table.Add([PSCUSTOMOBJECT]@{
"Serveur" = $serveur
"Heure" = $resultat
})
}
#Une fois tout ceci fini, nous avons un joli fichier CSV sur le bureau avec les informations récoltées
$table | Export-Csv -Path $(([environment]::GetFolderPath("Desktop") + "\"+$(get-date -Format yyyyMMdd_HHmm)+"_date_heure_serveurs.csv")) -NoTypeInformation -Delimiter ";" -Force
if ($?)
{
"fichier exporté sur le bureau"
}
else
{
"Erreur lors de la création du fichier"
$table
}
$table
Et nous avons donc le résultat, ici visible, qui nous renvoie l'intégralité des heures "temps réel", avec un affichage d'erreurs sur les serveurs qui ne répondent pas.
Bien entendu, le résultat étant une Arraylist, vous pouvez très bien trier avec un pipe et un Sort-Object sur la propriété qui vous interesse.
BONUS : Ajouter les baies NetApp dans ce test
Pour ce faire, ajoutons d'abord le module DataOnTap de Netapp dans le script, qui permet de commander les baies via Powershell.
Import-Module Dataontap
Ajoutons ensuite une variable spéciale contenant nos diverses baies Netapp à controler
$netapp = @("baie1","baie2","baie3","baie4")
Ensuite, dans une boucle Foreach, nous nous connecterons contrôleur par contrleur, et obtiendrons le temps à l'aide de la commande Get-NaTime
foreach ($baie in $netapp)
{
Connect-NaController $baie #nous nous connectons à une baie
$tempsNA = $null
$tempsNA = Get-NaTime | Select-Object -ExpandProperty localtimedt #nous en obtenons l'heure, en ne conservant que la propriété localtimedt
$heurecompare = Get-Date
$resultat = get-date $tempsNA -Format "dd/MM/yyyy HH:mm:ss" #nous formatons l'heure au format attendu
$resultat = (get-date $resultat).addticks(($heurereference - $heurecompare).Ticks)
$table.Add([PSCUSTOMOBJECT]@{
"Serveur" = $baie
"Heure" = $resultat
})
}