HyperV script de copie de snapshot
De BlaxWiki
Révision datée du 26 octobre 2012 à 12:35 par 217.174.199.129 (discussion)
Ceci est un script windows permettant de faire un snapshot des vm sur un hyperV. Il est appelé en crontab. Ce script fait appel à 2 sous script
Description
Bien que le log d'execution soit remonté dans la supervision, il est possible de consulter le fichier de log localement sur infra-hv1.fbd : C:\Agarik\Scripts\HyperVBackup\HyperVBackup.log
Configuration (à effectuer dans le bloc "CONFIGURATION" présent dans le script) :
# Which cluster shared volume (CSV) to backup
$strVolumePath = "C:\ClusterStorage\Volume1"
# Where to mount the snapshot ($strSnapshotDrive) and where to find the VM
($strSourcePath)
$strSnapshotDrive = "Z:"
$strSourcePath = $strSnapshotDrive
# Where to backup
$strBackupPath = "\\infra-backup\f$\VirtualMachines"
# What to backup
$arrVirtualMachines = @("wsus.fbd","txt-iis.fbd","txt-app.fbd","txt-sql1.fbd","ba-iis.fbd","ba-sql1.fbd","ba-app.fbd","ad2.fbd","ad1.fbd")
# Retention
$intRetention = 7
Le script effectue les actions suivantes :
- ouverture fichier de log C:\Agarik\Scripts\HyperVBackup\HyperVBackup.log - verification de l'état de $strVolumePath, (BackupState = 0 (None) sinon on quitte avec une erreur) - preparation de $strVolumePath a être snapshoté via l'appel au binaire $strExePCSV (qui appelle la fonctione ClusterPrepareSharedVolumeForBackup, voir David Couderc si le binaire a un problème, la création d'un binaire a été nécessaire car aucun langage "non compilé" ne permet l'appel à cette fonction) - verification de l'état de $strVolumePath (BackupState = 1 (InProgress) sinon on quitte avec une erreur) - creation du snapshot et montage en tant que Z: (avec diskshadow.exe) - archivage des precedents backups dans la limite de $intRetention - pour chaque VM dans la liste $arrVirtualMachines, backup vers $strBackupPath - demontage du lecteur Z: et suppression du snapshot (avec diskshadow.exe) - envoi du resultat à la supervision (avec vc_send)
Scripts
HyperVBackup.ps1
# ------------------------------------------------------------------
# ABOUT
# ABOUT::Author
# Yoann Moriot
# ABOUT::Version
# 1.5 2012/09/12 Suppression du bloc de backup des VMs ad1 et ad2 (pas de consistance du backup effectué de cette manière)
# 1.4 2012/07/26 Ajout d'un bloc pour le backup des VMs ad1 et ad2
# 1.3 2012/04/25 Improved output display
# 1.2 2012/04/24 Added $script:arrErrLog to store errors
# and output them on top of the log
# 1.1 2012/04/23 Added Operia integration with vc_send
# 1.0 2012/04/19
# /ABOUT
# ------------------------------------------------------------------
# ------------------------------------------------------------------
# CONFIGURATION
# Monitoring
$strVCSend = "C:\Agarik\Vision\bin\vc_send.exe"
$strVCDest = "10.252.15.235"
$strVCHost = "infra-backup.fbd"
$strVCService = "backup"
$strVCColor = "red"
$intVCValidity = 1560
$strVCMessage = ""
# Which cluster shared volume (CSV) to backup
$strVolumePath = "C:\ClusterStorage\Volume2"
# Where to mount the snapshot ($strSnapshotDrive) and where to find the VM ($strSourcePath)
$strSnapshotDrive = "Z:"
$strSourcePath = $strSnapshotDrive
# Where to backup
$strBackupPath = "\\infra-backup\f$\VirtualMachines"
# What to backup
$arrVirtualMachines = @("wsus.fbd","txt-iis.fbd","txt-app.fbd","txt-sql1.fbd","ba-iis.fbd","ba-sql1.fbd","ba-app.fbd","ad2.fbd","ad1.fbd")
# Retention
$intRetention = 5
# /CONFIGURATION
# ------------------------------------------------------------------
# ------------------------------------------------------------------
# SYSTEM
$bDEBUG = 0 # 0 = disabled, 1 = enabled
# DONT EDIT BELOW THIS LINE
$ERROR_CSV_BACKUPSTATE_CANNOTPREPARE = 1
$ERROR_CSV_BACKUPSTATE_NOTREADY = 2
$ERROR_DS_CREATESNAPSHOT_FAILED = 3
$ERROR_DS_REMOVESNAPSHOT_FAILED = 4
$ERROR_CSV_PREPAREBACKUP_FAILED = 5
$intError = 0
$strScriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition
$strExePCSV = $strScriptDir + "\bin\PCSV.exe" # path to ClusterPrepareSharedVolumeForBackup binary
$strExeDS = "C:\Windows\system32\diskshadow.exe"
$colVMBackup = @{} # will hold the backup result per VM
$strLogFile = $strScriptDir + "\HyperVBackup.log"
$script:arrLog = @()
$script:arrErrLog = @()
# Temp files
$strDSMetadata = $strScriptDir + "\metadata.cab"
$strCreateSnapshot = $strScriptDir + "\scripts\CreateSnapshot.script"
$strRemoveSnapshot = $strScriptDir + "\scripts\RemoveSnapshot.script"
# /SYSTEM
# ------------------------------------------------------------------
# ------------------------------------------------------------------
# FUNCTIONS
# Get-SharedVolume : returns an object containing properties for the $strPath shared volume
# See http://msdn.microsoft.com/en-us/library/windows/desktop/ee342510(v=vs.85).aspx
function Get-SharedVolume( [string]$strPath )
{
$strPath = $strPath.Replace("\", "\\")
$strQuery = "select * from MSCluster_ClusterSharedVolume where Name='" + $strPath + "'"
get-wmiobject -namespace root\MSCluster -query $strQuery
}
function log( [string]$strString )
{
$strLine = (Get-CurrentTimestamp) + " > " + $strString + "`r`n"
$script:arrLog += $strLine
If ( $strString.StartsWith("ERROR") )
{
$script:arrErrLog += $strLine
}
write-host $strLine
}
function Get-CurrentTimestamp
{
$objDate = Get-Date
$strCurrentTimestamp = $objDate.ToShortDateString() + " " + $objDate.ToLongTimeString()
$strCurrentTimestamp
}
function vc_send
{
If ( $script:arrErrLog.Length -gt 0 )
{
$strVCMessage += "ERRORS : `r`n`r`n"
ForEach($strLogLine in $script:arrErrLog)
{
$strVCMessage += $strLogLine
}
$strVCMessage += "`r`n"
$strVCMessage += "================================================="
$strVCMessage += "`r`n"
$strVCMessage += "`r`n"
}
ForEach($strLogLine In $script:arrLog)
{
$strVCMessage += $strLogLine
}
& $strVCSend $strVCDest $strVCHost $strVCService $strVCColor $intVCValidity $strVCMessage
Stop-Transcript
}
# /FUNCTIONS
# ------------------------------------------------------------------
Start-Transcript $strLogFile
# ------------------------------------------------------------------
# PREFLIGHT
# PREFLIGHT::Check
log "-------------------------------------------------"
log "INFO: Checking for $strVolumePath status"
$objSharedVolume = Get-SharedVolume $strVolumePath
If ( $bDEBUG ) { log "DEBUG: objSharedVolume.BackupState = $($objSharedVolume.BackupState)" } # expect : 0 (None)
If ( $bDEBUG ) { log "DEBUG: objSharedVolume.FaultState = $($objSharedVolume.FaultState)" } # expect : 0 (NoFaults)
If ( $objSharedVolume.BackupState -ne 0 ) {
log "ERROR ($ERROR_CSV_BACKUPSTATE_CANNOTPREPARE): $strVolumePath cannot be prepared for shadow copy (objSharedVolume.BackupState -ne 0)"
vc_send
exit $ERROR_CSV_BACKUPSTATE_CANNOTPREPARE
}
log "INFO: $strVolumePath ready to be prepared for snapshot"
# PREFLIGHT::Prepare
log "-------------------------------------------------"
log "INFO: Preparing $strVolumePath for snapshot with $strExePCSV"
# ClusterPrepareSharedVolumeForBackup
& $strExePCSV $strVolumePath
If (! ($?) ) {
log "ERROR ($ERROR_CSV_PREPAREBACKUP_FAILED): Failed to run $strExePCSV, exit code : $lastexitcode"
vc_send
exit $ERROR_CSV_PREPAREFAILED
}
log "INFO: $strVolumePath prepared for snapshot"
log "-------------------------------------------------"
log "INFO: Checking for $strVolumePath status"
$objSharedVolume = Get-SharedVolume $strVolumePath
If ( $bDEBUG ) { log "DEBUG: objSharedVolume.BackupState = $($objSharedVolume.BackupState)" } # expect : 1 (InProgress)
If ( $bDEBUG ) { log "DEBUG: objSharedVolume.FaultState = $($objSharedVolume.FaultState)" } # expect : 1 (NoDirectIO)
If ( $objSharedVolume.BackupState -ne 1 ) {
log "ERROR ($ERROR_CSV_BACKUPSTATE_NOTREADY): $strVolumePath not ready for shadow copy (objSharedVolume.BackupState -ne 1)"
vc_send
exit $ERROR_CSV_BACKUPSTATE_NOTREADY
}
log "INFO: $strVolumePath ready for snapshot"
# PREFLIGHT::BUILD_SCRIPTS
# PREFLIGHT::BUILD_SCRIPTS::CreateSnapshot
$arrCreateSnapshot = @()
$arrCreateSnapshot += "set context persistent nowriters"
$arrCreateSnapshot += "set verbose on"
$arrCreateSnapshot += "set metadata " + $strDSMetadata
$arrCreateSnapshot += "begin backup"
$arrCreateSnapshot += "add volume " + $objSharedVolume.VolumeName + " alias vm"
$arrCreateSnapshot += "create"
$arrCreateSnapshot += "expose %vm% " + $strSnapshotDrive
$arrCreateSnapshot | out-file -Encoding ASCII $strCreateSnapshot
# PREFLIGHT::BUILD_SCRIPTS::RemoveSnapshot
$arrRemoveSnapshot = @()
$arrRemoveSnapshot += "set context persistent nowriters"
$arrRemoveSnapshot += "delete shadows exposed " + $strSnapshotDrive
$arrRemoveSnapshot += "end backup"
$arrRemoveSnapshot | out-file -Encoding ASCII $strRemoveSnapshot
# /PREFLIGHT::BUILD_SCRIPTS
# /PREFLIGHT
# ------------------------------------------------------------------
# ------------------------------------------------------------------
# MAIN
# MAIN::ROTATE_BACKUP
log "-------------------------------------------------"
log "INFO: Rotating backups"
# Delete the backup that reached $intRetention
$strPrevBackupPath = $strBackupPath + "." + $intRetention
If ( Test-Path -Path $strPrevBackupPath )
{
log "INFO: Deleting $strPrevBackupPath"
Remove-Item -Recurse -Force $strPrevBackupPath
sleep 5
}
# Rotate the backups
For ($i = $intRetention; $i -gt 0; $i--)
{
If ( $i -eq 1 )
{
$strPrevBackupPath = $strBackupPath
}
Else
{
$strPrevBackupPath = $strBackupPath + "." + ($i - 1)
}
$strNewBackupPath = $strBackupPath + "." + $i
If ( Test-Path -Path $strPrevBackupPath ) {
log "INFO: Moving $strPrevBackupPath to $strNewBackupPath"
Rename-Item $strPrevBackupPath $strNewBackupPath
sleep 1
}
}
# /MAIN::ROTATE_BACKUP
# MAIN::CREATE_SNAPSHOT::run_diskshadow
log "-------------------------------------------------"
log "INFO: Creating snapshot of $strVolumePath and mounting it as $strSnapshotDrive"
& $strExeDS -s $strCreateSnapshot
If (! ($?) ) {
log "ERROR ($ERROR_DS_CREATESNAPSHOT_FAILED): Failed to run $strExeDS /s $strCreateSnapshot, exit code : $lastexitcode"
vc_send
exit $ERROR_DS_CREATESNAPSHOT_FAILED
}
log "INFO: $strExeDS -s $strCreateSnapshot ended with exit code $lastexitcode"
# /MAIN::CREATE_SNAPSHOT
# MAIN::BACKUP_VIRTUALMACHINES
ForEach($strVM In $arrVirtualMachines) {
$strVMSourcePath = $strSourcePath + "\" + $strVM
$strVMBackupPath = $strBackupPath + "\" + $strVM
If ( $bDEBUG ) { log "DEBUG: strVMSourcePath = $strVMSourcePath" }
If ( $bDEBUG ) { log "DEBUG: strVMBackupPath = $strVMBackupPath" }
# Create $colVMBackup entry for $strVM
$colVM = @{}
$colVMBackup.Add($strVM, $colVM)
$colVMBackup[$strVM]["Start"] = Get-CurrentTimestamp
$colVMBackup[$strVM]["SourceDir"] = $strVMSourcePath
$colVMBackup[$strVM]["BackupDir"] = $strVMBackupPath
$colVMBackup[$strVM]["CurrentSize"] = 0
$colVMBackup[$strVM]["BackupSize"] = 0
$colVMBackup[$strVM]["End"] = 0
$colVMBackup[$strVM]["BackupDuration"] = 0
$colVMBackup[$strVM]["Result"] = 0
log "-------------------------------------------------"
log "INFO: Starting backup of $strVM"
# Backup the VM
If ( ! ( Test-Path -path $strVMSourcePath ) ) {
$colVMBackup[$strVM]["Result"] = "ERROR: Cannot find " + $strVMSourcePath
log "ERROR (0): Cannot find $strVMSourcePath, cannot backup $strVM"
$intError++
}
Else
{
# Get the current VM size
$objVMSourcePath = (Get-ChildItem $strVMSourcePath -recurse | Measure-Object -property length -sum)
$colVMBackup[$strVM]["CurrentSize"] = "{0:N2}" -f ($objVMSourcePath.sum / 1MB) + " MB"
# Copy the VM, copy-item automatically creates destination dir if required
log "INFO: Copying $strVMSourcePath to $strVMBackupPath"
Copy-Item $strVMSourcePath -destination $strVMBackupPath -recurse
If ( $? )
{
$colVMBackup[$strVM]["Result"] = "Success"
}
Else
{
$colVMBackup[$strVM]["Result"] = "ERROR (0): Failed to copy $strVMSourcePath to $strVMBackupPath"
log "ERROR (0): Failed to copy $strVMSourcePath to $strVMBackupPath"
$intError++
}
# Get the backup size
$objVMBackupPath = (Get-ChildItem $strVMBackupPath -recurse | Measure-Object -property length -sum)
$colVMBackup[$strVM]["BackupSize"] = "{0:N2}" -f ($objVMBackupPath.sum / 1MB) + " MB"
$colVMBackup[$strVM]["End"] = Get-CurrentTimestamp
$colVMBackup[$strVM]["BackupDuration"] = New-Timespan $colVMBackup[$strVM]["Start"] $colVMBackup[$strVM]["End"]
}
log "INFO: Finished backup of $strVM, BackupSize = $($colVMBackup[$strVM][`"BackupSize`"]), SourceSize = $($colVMBackup[$strVM][`"CurrentSize`"]), BackupDuration = $($colVMBackup[$strVM][`"BackupDuration`"])"
log "INFO: Result = $($colVMBackup[$strVM][`"Result`"])"
}
# /MAIN::BACKUP_VIRTUALMACHINES
# MAIN::REMOVE_SNAPSHOT::run_diskshadow
log "-------------------------------------------------"
log "INFO: Unmounting $strSnapshotDrive and removing snapshot of $strVolumePath"
& $strExeDS /s $strRemoveSnapshot
If (! ($?) ) {
log "ERROR ($ERROR_DS_REMOVESNAPSHOT_FAILED): Failed to run $strExeDS /s $strRemoveSnapshot, exit code : $lastexitcode"
vc_send
exit $ERROR_DS_REMOVESNAPSHOT_FAILED
}
log "INFO: $strExeDS -s $strRemoveSnapshot ended with exit code $lastexitcode"
# /MAIN::REMOVE_SNAPSHOT
# MAIN::MONITORING
If ( $intError -eq 0 ) { $strVCColor = "green" }
vc_send
exit 0
CreateSnapshot.script
set context persistent nowriters
set verbose on
set metadata C:\Agarik\Scripts\HyperVBackup\metadata.cab
begin backup
add volume \\?\Volume{41bf2ab9-d5fd-4cb7-ae13-a5a6291e3596} alias vm
create
expose %vm% Z:
RemoveSnapshot.script
set context persistent nowriters delete shadows exposed Z: end backup
Resolution des problèmes
RAPPELS
* Le script de backup '''C:\Agarik\Scripts\HyperVBackup\HyperVBackup.ps1''' s'execute toujours les jours à 22h00 sur '''infra-hv1.fbd''' et remonte un point de supervision
'''backup''' sur '''infra-backup.fbd'''
* AVANT TOUTE CHOSE, vérifier que le script ne soit deja pas en cours d'éxecution via le Task Scheduler
* La commande Powershell {{{ get-wmiobject -namespace root\MSCluster -query "select * from MSCluster_ClusterSharedVolume where Name='C:\\ClusterStorage\\Volume1'" }}} retourne l'état
du volume partagé
* BackupState : 0 = None, 1 = InProgress
* FaultState : 0 = NoFaults, 1 = NoDirectIO (état normal pendant le backup), 2 = NoAccess, 4 = Maintenance
Erreurs
* '''ERROR (1): C:\ClusterStorage\Volume1 cannot be prepared for shadow copy (objSharedVolume.BackupState -ne 0)'''
/!\ Cette erreur est bloquante et interrompt l'execution du script
1. L'état actuel de C:\ClusterStorage\Volume1 ne permet de le préparer pour faire ensuite un snapshot.
2. Se connecter sur l'équipement '''infra-hv1''' avec le compte '''PLUS-INT-PRV\Agarik'''
1. Ouvrir une fenêtre Powershell en tant qu'administrateur (clic droit > executer en tant qu'administrateur)
2. Exécuter la commande suivante : {{{
get-wmiobject -namespace root\MSCluster -query "select * from MSCluster_ClusterSharedVolume where Name='C:\\ClusterStorage\\Volume1'"}}}
3. Verifier l'état des valeurs "BackupState" et "FaultState", elles devraient être à 0 pour que le backup se lance.
1. Si ce n'est pas le cas, il faut réinitialiser l'état du volume partagé qui doit être bloqué dans un état de backup
2. Lancer une fenetre Powershell en tant qu'administrateur et effectuer les commande suivantes : {{{
diskshadow.exe
DISKSHADOW> delete shadows all
DISKSHADOW> end backup }}}
3. Vérifier l'état du volume avec la commande Powershell evoquée dans la partie == RAPPELS == que "BackupState" et "FaultState" sont à 0, sinon escalader aux ISR
3. Relancer le script de backup via l'entrée présente dans le Task Scheduler
* '''ERROR (5): Failed to run C:\Agarik\Scripts\HyperVBackup\bin\PCSV.exe, exit code : $lastexitcode'''
/!\ Cette erreur est bloquante et interrompt l'execution du script
1. L'execution de "C:\Agarik\Scripts\HyperVBackup\bin\PCSV.exe" ne s'est pas déroulée correctement et de ce fait le volume partagé n'a pas pu être préparé pour un snapshot
2. Le code d'erreur $lastexitcode est le code retour de "C:\Agarik\Scripts\HyperVBackup\bin\PCSV.exe".
3. Une erreur indique probablement une erreur sur le volume partagée, effectuer les étapes de résolution mentionnées ci dessus pour "ERROR (1):"
* '''ERROR (2): $strVolumePath not ready for shadow copy (objSharedVolume.BackupState -ne 1)'''
/!\ Cette erreur est bloquante et interrompt l'execution du script
1. L'état de "C:\ClusterStorage\Volume1" n'est pas compatible avec la réalisation d'un snapshot, "BackupState" est différent de 1
2. Vérifier l'état du volume avec la commande Powershell evoquée dans la partie == RAPPELS ==
3. Si "BackupState" et "FaultState" sont à 0, relancer le script de backup via le Task Scheduler : si l'erreur persiste, escalader aux ISR.
4. Si les valeurs ne sont pas 0 et 0, lancer Powershell en tant qu'administrateur et effectuer les commandes suivantes : {{{
diskshadow.exe
DISKSHADOW> delete shadows all
DISKSHADOW> end backup }}}
* '''ERROR (3): Failed to run C:\Windows\system32\diskshadow.exe /s C:\Agarik\Scripts\HyperVBackup\scripts\CreateSnapshot.script, exit code : $lastexitcode'''
/!\ Cette erreur est bloquante et interrompt l'execution du script
1. diskshadow.exe n'a pas pu s'executer correctement, $lastexitcode correspond au code de retour de diskshadow.exe
2. Valider que le fichier de script fourni à diskshadow soit valide en executant manuellement la commande {{{ C:\Windows\system32\diskshadow.exe /s C:\Agarik\Scripts\HyperVBackup
\scripts\CreateSnapshot.script }}}, cela doit faire apparaitre le snapshot sous le lecteur Z: sur '''infra-hv1.fbd'''. Si ce n'est pas le cas, escalader auprès des ISR.
3. Exécuter ensuite {{{ C:\Windows\system32\diskshadow.exe /s C:\Agarik\Scripts\HyperVBackup\scripts\RemoveSnapshot.script }}} pour supprimer le snapshot et le lecteur Z:
* '''ERROR (0): Cannot find $strVMSourcePath, cannot backup $strVM'''
1. Le chemin $strVMSourcePath n'est pas disponible, la VM $strVM ne sera pas backupée
2. Verifier l'existence de $strVMSourcePath, s'assurer que $strVM est bien mentionnée dans le tableau $arrVirtualMachines dans le script de backup
* '''ERROR (0): Failed to copy $strVMSourcePath to $strVMBackupPath'''
1. Echec de la copie des données de la VM depuis $strVMSourcePath vers $strVMBackupPath
2. Verifier l'espace disque disponible et la disponibilité du répertoire source, le répertoire de destination est créé automatiquement donc il ne devrait pas y avoir de soucis de ce
coté là.
* '''ERROR (4): Failed to run C:\Windows\system32\diskshadow.exe /s C:\Agarik\Scripts\HyperVBackup\scripts\RemoveSnapshot.script, exit code : $lastexitcode'''
/!\ Cette erreur est bloquante et interrompt l'execution du script (bien qu'à cette étape, le backup des VMs soit terminé)
1. diskshadow.exe n'a pas pu s'executer correctement, $lastexitcode correspond au code de retour de diskshadow.exe
2. Valider que le fichier de script fourni à diskshadow soit valide en executant manuellement la commande {{{ C:\Windows\system32\diskshadow.exe /s C:\Agarik\Scripts\HyperVBackup
\scripts\RemoveSnapshot.script }}}. Cela doit faire disparaitre le lecteur Z: sur '''infra-hv1.fbd'''
3. Si ce n'est pas le cas, lancer Powershell en tant qu'administrateur et effectuer les commandes suivantes : {{{
diskshadow.exe
DISKSHADOW> delete shadows all
DISKSHADOW> end backup }}}
4. Si le lecteur ne disparait toujours pas, escalader aux ISR