VMware et Powershell : rapport des versions d’hôtes vSphere

Voici un script permettant de lister les versions des hôtes vSphere présents dans un vCenter. Le premier mode liste tous les hôtes tandis que le deuxième permet de lister une version spécifique, majeure ou mineure. Par exemple, en saisissant « 6 », alors le script cherchera les serveurs en 6.x.x tandis qu’en envoyant « 5.5 », il cherchera les serveurs en 5.5.x. Dans tous les cas, un export CSV exploitable est réalisé. Naturellement, il est nécessaire que les modules Powershell de VMware soient déployés sur la machine exécutant le script (si ils ne le sont pas, il suffit simplement de télécharger PowerCli).

Import-Module VMware.VimAutomation.Core

function allhosts{
Write-Host "Retrieving all hosts on vCenter $vcenter"
$spheres = Get-VMHost
process
}

function spechosts{
Write-Host "Retrieving specific hosts on vCenter $vcenter."
$reqver = Read-Host "Please input the desired version in one of those formats : 6, 6.0 or 6.0.0"
if($reqver.Length -lt 5) { $reqver = "$reqver*" }
$spheres = Get-VMHost | Where-Object {$_.Version -like $reqver}
process
}

function process{
$path = "$env:temp\vmware-hostver-$vcenter.csv"
$header = "Hostname,PowerState,Version"
Add-Content -Value $header -Path $path
foreach($vsphere in $spheres) {
$row = $vsphere.Name+","+$vsphere.PowerState+","+$vsphere.Version
Add-Content -Value $row -Path $path
}
Write-Host "Processing is done. Results are located in $path."
explorer $env:temp
}

Write-Host "vSphere host version listing script"
Write-Host "===================================`r`n"
$vcenter = Read-Host "vCenter to connect to ?"
Connect-VIServer $vcenter | Out-Null
Write-Host "1: List all hosts and versions on this vCenter`r`n2: List only hosts running under a specific version"
$mode = Read-Host "Choice"
switch ($mode){
1 { allhosts }
2 { spechosts }
}
Disconnect-VIServer $vcenter -confirm:$false | Out-Null
Write-Host "Bye."

Powershell : listing d’enregistrements DNS

J’ai développé un (plutôt deux, à vrai dire) script(s) Powershell permettant de remplacer la fonction de filtre présente sur la console DNS. En effet, celle-ci est un peu pénible à utiliser car il faut attendre que toute la zone soit chargée dans la console si le filtre n’est pas encore actif, puis il faut rafraîchir… et il est persistant après fermeture, ce qui fait qu’il faut réinitialiser le filtre et actualiser lorsqu’on souhaite revenir à une vision d’ensemble.

A l’exécution, le script demande le nom ou l’IP du serveur DNS à interroger, ainsi que la chaîne de caractères à trouver dans le nom de l’enregistrement. Le joker * est utilisable, ce qui permet à la fois de rechercher une expression ou une chaîne stricte.

La version basique du script renvoie ses résultats dans la console avec uniquement le nom de l’enregistrement :

Write-Host "=========================================="
Write-Host "DNS records listing script - Basic Version"
Write-Host "=========================================="
$dnsnode = Read-Host "DNS Server to query"
$lfseq = Read-Host "Character sequence to look for ('*' tokens accepted)"
Write-Host "Connecting to DNS Server and retrieving DNS zones"
$dnszones = Get-DNSServerZone -ComputerName $dnsnode | Where-Object {$_.IsReverseLookupZone -eq $false}
foreach($dnszone in $dnszones){
Write-Host "Zone currently parsed:" $dnszone.ZoneName
$dnsrecords = Get-DnsServerResourceRecord -ComputerName $dnsnode -ZoneName $dnszone.ZoneName | Where-Object {$_.HostName -like "$lfseq"} | ft HostName
if($dnsrecords.length -gt 0) { $dnsrecords }
else { Write-Host "No matching records found." }
Write-Host "`r"
}
Write-Host "Parsing over."
Write-Host "Bye."

La version améliorée renvoie les résultats dans la console, mais génère un fichier CSV offrant plus d’informations (type d’enregistrement et données de l’enregistrement) et effectue une suppression des doublons : nécessaire car la commande Powershell peut renvoyer plusieurs fois le même enregistrement si il est dans une sous-zone. Par exemple, en considérant la zone infra.local, si un enregistrement s’appelle sql dans la sous-zone prod, il apparaîtra alors deux fois : une fois en tant que sql.prod et une fois en tant que sql.prod.infra.local.

Write-Host "============================================="
Write-Host "DNS records listing script - Enhanced Version"
Write-Host "============================================="
$dnsnode = Read-Host "DNS Server to query"
$lfseq = Read-Host "Character sequence to look for ('*' tokens accepted)"
$today = Get-Date -f yyyyMMdd
$path = "dns-$today.csv"
$csvheader = "Hostname,Zone,RecordType,RecordData"
Add-Content -Value $csvheader -Path $path
Write-Host "Connecting to DNS Server and retrieving DNS zones"
$dnszones = Get-DNSServerZone -ComputerName $dnsnode | Where-Object {$_.IsReverseLookupZone -eq $false}
foreach($dnszone in $dnszones){
Write-Host "Zone currently parsed:" $dnszone.ZoneName
$dnsrecords = Get-DnsServerResourceRecord -ComputerName $dnsnode -ZoneName $dnszone.ZoneName | Where-Object {$_.HostName -like "$lfseq"}
if($dnsrecords.length -gt 0){
foreach($dnsrecord in $dnsrecords){
Write-Host "Record: "$dnsrecord.Hostname
switch($dnsrecord.RecordType){
"A" {$recorddata = $dnsrecord.RecordData.IPv4Address.IpAddressToString}
"CNAME" {$recorddata = $dnsrecord.RecordData.HostNameAlias}
}
if($dnsrecord.HostName.SubString($dnsrecord.HostName.Length-$dnszone.ZoneName.Length,$dnszone.ZoneName.Length) -ne $dnszone.ZoneName) {
$row = $dnsrecord.Hostname+","+$dnszone.ZoneName+","+$dnsrecord.RecordType+","+$recorddata
Add-Content -Value $row -Path $path
}
}
Write-Host "DNS records found:"$dnsrecords.length
}
else { Write-Host "No matching records found." }
Write-Host "`r"
}
Write-Host "Parsing over, cleaned duplicates, hence the difference between the number of lines in the CSV file and the number displayed in console. Please find the output CSV file named $path in $pwd."
Write-Host "Bye."
explorer $pwd

Veeam et Powershell : état des jobs de sauvegardes sur les dernières 24 heures

Afin de me faciliter la tâche sur certains contrôles quotidiens, j’ai développé un script Powershell pour Veeam Backup & Replication me permettant d’afficher rapidement si il y a eu des échecs de sauvegarde durant les dernières 24 heures.

Ce script est plutôt simple et contrôle les sauvegardes de machines virtuelles VMware (supporte également Hyper-V il me semble) ainsi que les agents Veeam déployés sur des machines Windows. Veeam possède diverses commandes Powershell pour les divers types de sauvegarde qu’il propose (sauvegarde sur bande, par exemple) ; si il y a besoin de contrôler d’autres types de sauvegarde, alors il suffit simplement de réaliser une requête supplémentaire Get-VBRxSessionx sera évidemment remplacé par le type de sauvegarde.

J’ai testé ce script sur Veeam B&R version 9.5.

Add-PSSnapin VeeamPSSnapIn
Disconnect-VBRServer
$SrvList = @("backupsrv1","backupsrv2","veeamdev1","veeamdev2")
Write-Host "Veeam B&R Daily Checks Jobs Script"

function process{
param($type)
if($session.Result -eq "Failed"){
switch ($type){
"std" { $name = $session.Name ; $RetryList = $sessionList | Where { $_.Name -eq $name } }
"agent" { $job = Get-VBREPJob -ID $session.JobId ; $name = $job.Name ; $RetryList = $sessionAgentList | Where { $_.JobId -eq $session.JobId } }
}
$status = $session.Result
$st = $session.CreationTime
$et = $session.EndTime
$global:count+=1
echo "Job: $name"
echo "Status: $status"
echo "Start Time: $st"
echo "End Time: $et"
foreach($Retry in $RetryList){
if ($Retry.Result -ne "Failed"){
if ($Retry.State -ne "Running"){
$st = $Retry.CreationTime
$et = $Retry.EndTime
echo "Job $name finally succeeded."
echo "Start Time: $st"
echo "End Time: $et"
}
if ($Retry.State -eq "Running"){
echo "Job $name currently running."
}
}
}
}
}

foreach($UniqueSrv in $SrvList){
Write-Host "Processing server $UniqueSrv"
Connect-VBRServer -Server $UniqueSrv
$SessionList = Get-VBRBackupSession | ?{($_.EndTime -ge (Get-Date).AddHours(-24) -or $_.CreationTime -ge (Get-Date).AddHours(-24))}
$SessionAgentList = Get-VBREPSession | ?{($_.EndTime -ge (Get-Date).AddHours(-24) -or $_.CreationTime -ge (Get-Date).AddHours(-24))}
$global:count = 0
foreach($session in $SessionList){
process("std")
}
foreach($session in $SessionAgentList){
process("agent")
}
if ($global:count -eq 0){ Write-Host "No errors detected in the jobs for this server." }
Disconnect-VBRServer
}

Merci à smasterson d’avoir partagé un script complet de reporting Veeam en HTML, dont j’ai repris le pipe pour la condition des 24 dernières heures, mon premier développement de script fonctionnant autrement, cette syntaxe est plus propre. La documentation de Veeam étant ni claire ni complète et l’interface avec Powershell particulièrement catastrophique (propriétés, variables et retours différents entre deux objets pour rien, d’où l’usage de switch et gestion des états de fin de traitement anarchique : certaines sauvegardes s’arrêtent sans retour, d’autres en succès en fonction de leur type), j’ai donc dû m’y prendre à plusieurs fois avant de proposer un script fonctionnel.

550 Cannot create a file when that file already exists sur un FTP porté par IIS

J’ai rencontré une erreur 550 ce jour en tentant d’accéder à un répertoire situé à la racine d’un serveur FTP porté par IIS sur 2008 R2 (IIS 7).

Après vérifications dans le IIS Manager, je n’ai rien trouvé de particulier au niveau des autorisations, le compte que j’utilisais avait bien les droits en lecture et en écriture ; les autorisations NTFS sur le répertoire physique étaient également valides. De plus, ce répertoire n’est qu’une variante de deux autres répertoires identiques, créés en même temps, gérés par les mêmes comptes et accédés par les mêmes applications. Il n’y a donc aucune raison d’avoir une différence de configuration.

J’ai donc été vérifier le fichier de configuration des sites IIS dans C:\Windows\System32\inetsrv\config\applicationHost.config. A la toute fin de ce fichier, la première entrée concerne la racine du FTP, la deuxième concerne spécifiquement le répertoire auquel je n’ai pas accès.

En supprimant cette seconde balise <location> et en redémarrant mon site FTP, tout est revenu dans l’ordre.

Il n’y avait aucune raison d’avoir cette balise pour un seul des 3 répertoires ; par ailleurs ajouter une balise <add accessType= »Allow » users= »MonUser » permissions= »Read, Write » /> n’avait rien changé.