<# DESCRIPTION: Script de sauvegardes des bases de données FONCTIONNMENT: Le principe ce sont les états "BACKUP ...". Un état correspond à une action, bien précicse. Si dans cet état tout se déroule correcement, la variable NEXT_STATE est configuré Si malheureusement une erreur se produit, NEXT_STATE est configuré vers BACKUP_ERROR, ou d'autre pour réparé avant de relancer Dans l'ordre, nous procedons comme ceci - Controle des prérequis BACKUP_REQUIREMENT - Découverte des services EASY - Pour chaque service - BACKUP_EASY_PROPERTIES: Découverte de la configuration EASY - BACKUP_DBINFO: Découverte de la base de données associée - BACKUP_DBCHECK: Test existance fichier de base de données - BACKUP_STOPSERVICE: Arret du service - BACKUP_DBADDPREFIX: Renommage du fichier de base de données (ajout d'un préfice '_') - BACKUP_CONFIG: Sauvegarde de la configuration du service (fichier engine) A partir de la deux options : - BACKUP_DBCOPY: Copie du fichier vers le serveur de sauvegarde - BACKUP_DBGBKSAVE: Création de la sauvegarde avec gbak - BACKUP_DBGBKRESTORE: Restauration de la sauvegarde avec gbak - BACKUP_DBREMOVEOLD: Suppresion de l'ancienne sauvegarde (_GINKOIA.IB) Ou ca : - BACKUP_DBCOPY: Copie du fichier vers le serveur de sauvegarde - BACKUP_DBREMOVEPREFIX: Renommage du fichier de base de données (ajout d'un préfice '_') - BACKUP_SARTSERVICE: Démarrage du service - BACKUP_ERROR : Erreur et récupération des erreurs - BACKUP_END Tache de récupération: - BACKUP_DBREMOVEPREFIX VERSION: 0.3 AUTEUR: Benoit MENARD ============== HISTORIQUE DES VERSIONS 0.3: Ajout du paramètre de script RunGbak pour activer ou désactiver le processus de "Backup / Restore" Ajout d'un fichier permettant de définir des services à exclure. Il s'agit d'un simple fichier texte, avec un service par ligne. Les REGEX sont supportées Ajout d'un fichier de metadonnées, permettant de facilement récupérer les infos de sauvegardes entre programme. Ce fichier est au format JSON et permet de connaitre des informations sur la sauvegarde d'un service EASY Ajout de la variable $LAST_STATE permettant de savoir quel est dernier etat Ajout des paramètres -force sur les cmdlet Copy-Item Création du dossier ${BACKUP_DIR}\${SVC_NAME} si il n'existe pas 0.2: Changement du chemin vers le serveur de stockage -> storage.easy.local Correction Write-LogWarning existe pas Creation du dossier BACKUP_DIR si il n'existe pas ============== #> [CmdletBinding()] [OutputType([psobject])] Param ( [Parameter(Mandatory=$false, Position=0)] [String] $Dossier = "", [Parameter( Mandatory = $False) ] [Switch]$RunGbak=$False ) . ${PSScriptRoot}\pslib.ps1 $EASY_ROOT = "E:\EASY" $HOSTNAME = $ENV:COMPUTERNAME $BACKUP_DIR = "\\storage.easy.local\exports\backup\${HOSTNAME}" $BACKUP_DIR_TEST_WRITE_FILE = $BACKUP_DIR + "\touch.txt" $MAX_RETRY = 5 $GBAK_BIN = "C:\Embarcadero\InterBase\bin\gbak.exe" $GBAK_ARGS_SAVE = "-b -user SYSDBA -password masterkey " $GBAK_ARGS_REST = "-r -user SYSDBA -password masterkey " $EASY_SVC_PATTERN = "EASY*" $EXCLUDE_FILE = "backup.exclude.txt" $BACKUPS_METADATA_FILE = "backup.json" Start-Log -Path "${PSScriptRoot}\backup.log" Write-LogInfo "Début" $InformationPreference = "Continue" #$VerbosePreference = "Continue" #$DebugPreference = "Continue" # Si pas de fichier existant, $EXCLUDE_SERVICES est vide If (Test-Path "${PSScriptRoot}\${EXCLUDE_FILE}") { Write-LogInfo "Chargement des règles d'exclusion" $EXCLUDE_SERVICES = Get-Content -Path "${PSScriptRoot}\${EXCLUDE_FILE}" $EXCLUDE_SERVICES | ForEach-Object { Write-LogWarn ("Service exclu : {0}" -f $_) } } Else { $EXCLUDE_SERVICES = "" } # Si pas de fichier existant, $BACKUPS_METADATA est un dictionnaire vide If (Test-Path "${PSScriptRoot}\${BACKUPS_METADATA_FILE}" ) { Write-LogInfo "Chargement des anciennes données de sauvegarde" $BACKUPS_METADATA = Get-Content -Path "${PSScriptRoot}\${BACKUPS_METADATA_FILE}" | ConvertFrom-Json } Else { $BACKUPS_METADATA = @{} } Function Test-Write ($Path) { Try { "SUCCESS" | Set-Content -Path $Path -Force -ErrorAction Stop Remove-Item -Path $Path -Force -ErrorAction Stop Write-Output $True } Catch { Write-LogException $_ Write-Output $False } } If (-not (Test-Path -Path $BACKUP_DIR)) { try { New-Item -ItemType Directory -Path $BACKUP_DIR -Force -ErrorAction Stop | Out-Null } catch { Write-LogException $_ } } Write-LogVerbose "CHECKS" $CHECKS = @{} # Vérifie que GBAK.EXE est bien présent $CHECKS["GBAK_BIN"] = Test-Path -Path $GBAK_BIN # Test écriture sur le serveur de sauvegarde $CHECKS["BACKUP_SERVER_WRITE"] = Test-Write $BACKUP_DIR_TEST_WRITE_FILE ForEach ($CHECK in $CHECKS.GetEnumerator()) { If ($CHECK.Value -eq $True) { Write-LogVerbose ("Prérequi {0} validé" -f $CHECK.Name) $NEXT_STATE = "SERVICES" } Else { Write-LogError ("Prérequi {0} non validé - Arret du programme" -f $CHECK.Name) Exit 1 } } <# ETAT : SERVICES Récupération des services EASY installée Sur succès, je vais à BACKUP Sur erreur, je m'arrete la #> If ($NEXT_STATE -eq "SERVICES") { Write-LogVerbose "SERVICES" $Params = @{} If ($Dossier -ne "") { $Params["Name"] = $Dossier $Services = Get-Service -Name $Dossier -Exclude $EXCLUDE_SERVICES } Else { $Params["Name"] = $EASY_SVC_PATTERN } If ($EXCLUDE_SERVICES -ne "") { $Params["Exclude"] = $EXCLUDE_SERVICES } $Services = Get-Service @Params If ($Services.Count -eq 0) { Write-LogWarn "Aucun services EASY trouvés" $NEXT_STATE = "END" } Else { Write-LogInfo ("{0} services trouvés" -f $Services.Count) $NEXT_STATE = "BACKUP" } } Function Get-EasyProperties{ <# .SYNOPSIS Renvois le fichier engines\.properties sous forme clé=valeur .DESCRIPTION Renvoie $Null si impossible à trouver .INPUTS String .OUTPUTS [Hash] .PARAMETERS ServiceName Nom du service EASY #> [CmdletBinding()] [OutputType([Hashtable])] Param( [Parameter( Position = 0, Mandatory = $True) ] [String]$ServiceName ) Process { $Path = "{0}\{1}\engines\{2}.properties" -f ($EASY_ROOT, $ServiceName, $ServiceName.ToLower()) If (Test-Path -Path $Path) { $Output = @{} Get-Content -Path $Path | ForEach-Object { $Param, $Value = $_ -split "=" $Output[$Param] = $Value } } Else { $Output = $Null } Write-Output $Output } } $STATE = @{} $STATE["BACKUP_REQUIREMENT"] = { <# ETAT : BACKUP_REQUIREMENT Controle de l'existance du dossier EASY Sur succès, je vais à BACKUP_EASY_PROPERTIES Sur erreur, je m'arrete la #> Write-LogInfo "BACKUP_REQUIREMENT" $SCRIPT:NEXT_STATE = "BACKUP_EASY_PROPERTIES" If (-not (Test-Path -Path $SVC_EASY_DIR )) { $SCRIPT:NEXT_STATE = "BACKUP_ERROR" Write-LogError ("Le dossier {0} n'existe pas" -f $SVC_EASY_DIR) } If (-not (Test-Path -Path "${BACKUP_DIR}\${SVC_NAME}" )) { Try { New-Item -Path "${BACKUP_DIR}\${SVC_NAME}" -ItemType Directory -ErrorAction Stop -Force | Out-Null } Catch { $SCRIPT:NEXT_STATE = "BACKUP_ERROR" Write-LogException $_ } } } $STATE["BACKUP_EASY_PROPERTIES"] = { <# ETAT : BACKUP_EASY_PROPERTIES Récupération des informations de configuration pour le service EASY Les données sont dans le fichier engine\.properties C'est un fichier qui a le format = Je parse le fichier, et crée un dictionnaire SVC_EASY_PROPERTIES C'est la fonction Get-EasyProperties qui en charge de parser le fichier. Sur succès, je vais à BACKUP_DBINFO Sur erreur, je m'arrete la #> Write-LogInfo "BACKUP_EASY_PROPERTIES" $SCRIPT:SVC_EASY_PROPERTIES = Get-EasyProperties -ServiceName $SVC_NAME If ($Null -eq $SVC_EASY_PROPERTIES) { $SCRIPT:NEXT_STATE = "BACKUP_ERROR" #Write-LogError "Erreur pendant la récupération des informations du service - Impossible de continuer" } Else { $SCRIPT:NEXT_STATE = "BACKUP_DBINFO" } } $STATE["BACKUP_DBINFO"] = { <# ETAT : BACKUP_DBINFO Je prends la clé db.url du dictionnaire SVC_EASY_PROPERTIES La valeur ressemble à ceci : db.url=jdbc\:interbase\://localhost\:3050/F\:/ALGOL/TESTBENOIT02/GINKOIA.IB - Je controle que la ligne contient bien Interbase (autrement, je la considère comme non correcte) - Je remplace les élements inutiles, nottameent les \ - Je cherche un chemin de fichier, juste après le 3050 - J'obtient un chemin Sur succès, je vais à BACKUP_DBCHECK Sur erreur, je m'arrete la #> Write-LogInfo "BACKUP_DBINFO" If ($SVC_EASY_PROPERTIES["db.url"] -match "interbase") { If ($SVC_EASY_PROPERTIES['db.url'] -replace "\\","" -match "3050\/([A-Za-z]:.*IB)$") { $SCRIPT:EASY_DB_PATH = $Matches[1] -replace "/","\" $SCRIPT:EASY_DB_PARENT_DIR = Split-Path -Path $EASY_DB_PATH -Parent $SCRIPT:EASY_DB_FILE_NAME = Split-Path -Path $EASY_DB_PATH -Leaf Write-LogDebug ("EASY_DB_PARENT_DIR={0};EASY_DB_FILE_NAME={1}" -f $EASY_DB_PARENT_DIR,$EASY_DB_FILE_NAME) $SCRIPT:NEXT_STATE = "BACKUP_DBCHECK" } Else { $SCRIPT:NEXT_STATE = "BACKUP_ERROR" Write-LogError "Erreur dans la recherche du chemin de la base - Impossible de continuer" } } Else { $SCRIPT:NEXT_STATE = "BACKUP_ERROR" Write-LogError "L'entrée db.url du fichier de configuration semble incorrect - Impossible de continuer" } } $STATE["BACKUP_DBCHECK"] = { <# ETAT : BACKUP_DBCHECK Je vérifie que le fichier EASY_DB_PATH existe Sur succès, je vais à BACKUP_STOPSERVICE Sur erreur, je m'arrete la #> Write-LogInfo "BACKUP_DBCHECK" If (Test-Path $EASY_DB_PATH) { $SCRIPT:NEXT_STATE = "BACKUP_STOPSERVICE" } Else { Write-LogError "Le fichier de base de données n'existe pas - Impossible de continuer" $SCRIPT:NEXT_STATE = "BACKUP_ERROR" } } $STATE["BACKUP_STOPSERVICE"] = { # ETAT : BACKUP_STOPSERVICE Write-LogInfo "BACKUP_STOPSERVICE" Try { Stop-Service $Service -ErrorAction Stop $SCRIPT:NEXT_STATE = "BACKUP_DBADDPREFIX" } Catch { $SCRIPT:NEXT_STATE = "BACKUP_ERROR" } } $STATE["BACKUP_STARTSERVICE"] = { # ETAT : BACKUP_STARTSERVICE Write-LogInfo "BACKUP_STARTSERVICE" Try { Start-Service $Service -ErrorAction Stop $SCRIPT:NEXT_STATE = "BACKUP_END" } Catch { $SCRIPT:NEXT_STATE = "BACKUP_ERROR" } } $STATE["BACKUP_DBADDPREFIX"] = { <# ETAT : BACKUP_DBADDPREFIX Je controle que aucun programme accède a la base en la renommant J'essaye plusieurs fois, au cas ou Sur succès, je vais à BACKUP_CONFIG Sur erreur, je m'arrete la #> Write-LogInfo "BACKUP_DBADDPREFIX" $END = $False $SCRIPT:NEW_DB_NAME = Join-Path -Path ${EASY_DB_PARENT_DIR} -ChildPath "_${EASY_DB_FILE_NAME}" If (Test-Path $NEW_DB_NAME) { Write-LogError "Impossible de renommer le fichier GINKOIA.IB - Le fichier _GINKOIA.IB existe déja" $SCRIPT:NEXT_STATE = "BACKUP_ERROR" $END = $True } $Cpt = 0 While ($END -eq $False -and $Cpt -lt $MAX_RETRY) { try { Write-LogDebug ("Renommage du fichier {0} vers {1} {2}/5" -f $EASY_DB_PATH, $NEW_DB_NAME, $Cpt) Rename-Item -Path $EASY_DB_PATH -NewName $NEW_DB_NAME -ErrorAction Stop $SCRIPT:NEXT_STATE = "BACKUP_CONFIG" $END = $True } catch { Write-LogException $_ Start-Sleep -Seconds 3 $Cpt ++ $SCRIPT:NEXT_STATE = "BACKUP_ERROR" } } } $STATE["BACKUP_CONFIG"] = { <# ETAT : BACKUP_CONFIG Sauvegarde du fichier engine pour ce dossier Sur succès, je vais à BACKUP_DBCOPY Sur erreur, je m'arrete la #> Write-LogInfo "BACKUP_CONFIG" $DATE = Get-Date -Format yyyyMMddhhss Try { $easy_config_file_name = "{0}.properties" -f ($SVC_NAME.ToLower()) $easy_config_file_path = "{0}\{1}\engines\{2}" -f ($EASY_ROOT, $SVC_NAME, $easy_config_file_name) Write-LogDebug ("Copie du fichier {0} vers {1}" -f $easy_config_file_path, "${BACKUP_DIR}\${SVC_NAME}\${DATE}_${easy_config_file_name}") If (Test-Path "${BACKUP_DIR}\${SVC_NAME}\${DATE}_${easy_config_file_name}") { Remove-Item -Path "${BACKUP_DIR}\${SVC_NAME}\${DATE}_${easy_config_file_name}" -Force } Copy-Item -Path $easy_config_file_path -Destination "${BACKUP_DIR}\${SVC_NAME}\${DATE}_${easy_config_file_name}" -ErrorAction Stop -Force $SCRIPT:NEXT_STATE = "BACKUP_DBCOPY" } Catch { $SCRIPT:NEXT_STATE = "BACKUP_ERROR" Write-LogException $_ } } $STATE["BACKUP_DBCOPY"] = { <# ETAT : BACKUP_DBCOPY Copie du fichier GINKOIA.IB de ce dossier vers le serveur de sauvegarde J'ajoute la date en prefix Sur succès, et si $RunGbak est actif: je vais à BACKUP_DBGBKSAVE sinon je vais BACKUP_DBREMOVEPREFIX Sur erreur, je m'arrete la #> Write-LogInfo "BACKUP_DBCOPY" $DATE = Get-Date -Format yyyyMMddhhss If ($RunGbak) { $SCRIPT:NEXT_STATE = "BACKUP_DBGBKSAVE" } Else { $SCRIPT:NEXT_STATE = "BACKUP_DBREMOVEPREFIX" } Try { Write-LogDebug ("Copie du fichier {0} vers {1}" -f $NEW_DB_NAME, "${BACKUP_DIR}\${SVC_NAME}\GINKOIA.IB") If (Test-Path "${BACKUP_DIR}\${SVC_NAME}\${DATE}_GINKOIA.IB") { Remove-Item -Path "${BACKUP_DIR}\${SVC_NAME}\${DATE}_GINKOIA.IB" -Force } Copy-Item -Path $NEW_DB_NAME -Destination "${BACKUP_DIR}\${SVC_NAME}\${DATE}_GINKOIA.IB" -ErrorAction Stop -Force } Catch { $SCRIPT:NEXT_STATE = "BACKUP_ERROR" Write-LogException $_ } } $STATE["BACKUP_DBGBKSAVE"] = { <# ETAT : BACKUP_DBGBKSAVE Création de la sauvegarde avec gbak.exe J'ajoute la date en prefix Sur succès, je vais à BACKUP_DBGBKRESTORE Sur erreur, je vais BACKUP_DBREMOVEPREFIX #> Write-LogInfo "BACKUP_DBGBKSAVE" $DATE = Get-Date -Format yyyyMMddhhss $SCRIPT:DBGBK_DEST = "${BACKUP_DIR}\${SVC_NAME}\${DATE}_GINKOIA.GBK" Write-LogDebug "$GBAK_ARGS_SAVE $DBGBK_DEST" $GBAK_PROCESS = Start-Process -FilePath $GBAK_BIN -ArgumentList "$GBAK_ARGS_SAVE $NEW_DB_NAME $DBGBK_DEST" -PassThru -Wait If ($GBAK_PROCESS.ExitCode -eq 0) { $SCRIPT:NEXT_STATE = "BACKUP_DBGBKRESTORE" } Else { Write-LogWarn "GBK PROCESS FAILED" $SCRIPT:NEXT_STATE = "BACKUP_DBREMOVEPREFIX" } } $STATE["BACKUP_DBGBKRESTORE"] = { <# ETAT : BACKUP_DBGBKRESTORE Restauration de la sauvegarde Sur succès, je vais à BACKUP_DBREMOVEOLD Sur erreur, je vais BACKUP_DBREMOVEPREFIX #> Write-LogInfo "BACKUP_DBGBKRESTORE" $GBAK_PROCESS = Start-Process -FilePath $GBAK_BIN -ArgumentList "$GBAK_ARGS_REST $DBGBK_DEST $EASY_DB_PATH" -PassThru -Wait If ($GBAK_PROCESS.ExitCode -eq 0) { $SCRIPT:NEXT_STATE = "BACKUP_DBREMOVEOLD" } Else { Write-LogWarn "GBK RESTO FAILED" $SCRIPT:NEXT_STATE = "BACKUP_DBREMOVEPREFIX" } } $STATE["BACKUP_DBREMOVEOLD"] = { <# ETAT : BACKUP_DBREMOVEOLD Suppression de la base données _GINKOIA.IB Sur succès, je vais à BACKUP_STARTSERVICE Sur erreur, pas géré #> Write-LogInfo "BACKUP_DBREMOVEOLD" Remove-Item -Path $NEW_DB_NAME $SCRIPT:NEXT_STATE = "BACKUP_STARTSERVICE" } $STATE["BACKUP_DBREMOVEPREFIX"] = { <# ETAT : BACKUP_DBREMOVEOLD Suppression du préfixe de la base données _GINKOIA.IB Si LAST_STATE est BACKUP_DBCOPY: NEXT_STATE sera BACKUP_SARTSERVICE Sinon NEXT_STATE sera BACKUP_END Sur succès, je vais à BACKUP_END Sur erreur, je m'arrete la #> Write-LogInfo "BACKUP_DBREMOVEPREFIX" $END = $False $Cpt = 0 Do { try { Write-LogDebug ("Renommage du fichier {0} vers {1} {2}/5" -f $NEW_DB_NAME, $EASY_DB_PATH, $Cpt) Rename-Item -Path $NEW_DB_NAME -NewName $EASY_DB_PATH -ErrorAction Stop If ($LAST_STATE -eq "BACKUP_DBCOPY") { Write-LogDebug $LAST_STATE $SCRIPT:NEXT_STATE = "BACKUP_STARTSERVICE" } Else { Write-LogDebug $LAST_STATE $SCRIPT:NEXT_STATE = "BACKUP_END" } $END = $True } catch { Write-LogException $_ Start-Sleep -Seconds 10 $SCRIPT:NEXT_STATE = "BACKUP_ERROR" } } While ($END -eq $False -and $Cpt -lt $MAX_RETRY) } $STATE["BACKUP_ERROR"] = { # ETAT : BACKUP_ERROR Write-LogError "${SVC_NAME} Erreur pendant le processus. Controller le fichier le de LOG" Invoke-Command -ScriptBlock $STATE["BACKUP_DBREMOVEPREFIX"] Invoke-Command -ScriptBlock $STATE["BACKUP_STARTSERVICE"] $SCRIPT:NEXT_STATE = "BACKUP_END" } If ($NEXT_STATE -eq "BACKUP") { Foreach ($Service in $Services) { $SVC_NAME = $Service.Name If ( ($BACKUPS_METADATA | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name) -notcontains $SVC_NAME) { $BACKUPS_METADATA[$SVC_NAME] = [PSCustomObject]@{ "service" = "" "begin" = "" "end" = "" "gbak" = "" "lasttry" = "" "lastresult" = "" "lastsuccess" = "" "duration" = "" } } $MetaData = $BACKUPS_METADATA.$SVC_NAME Write-LogInfo "=========================== $SVC_NAME ========================" If ($Service.Status -eq "Running") { $BeginDate = Get-Date $SVC_EASY_DIR = Join-Path -Path $EASY_ROOT -ChildPath $SVC_NAME $LAST_STATE = "" $NEXT_STATE = "BACKUP_REQUIREMENT" Do { Write-LogDebug ("${SVC_NAME} LAST_STATE={0} NEXT_STATE={1}" -f $LAST_STATE,$NEXT_STATE) $LAST_STATE_TMP = $NEXT_STATE Invoke-Command -ScriptBlock $STATE["$NEXT_STATE"] $LAST_STATE = $LAST_STATE_TMP } While ($NEXT_STATE -ne "BACKUP_END") # Calcul du temps passé $EndDate = Get-Date $ElapsedTime = New-TimeSpan -Start $BeginDate -End $EndDate $MetaData.gbak = $RunGbak $MetaData.service = $SVC_NAME $MetaData.begin = $BeginDate.ToString() $MetaData.end = $EndDate.ToString() $MetaData.lasttry = $BeginDate.ToString() $MetaData.duration = $ElapsedTime.TotalSeconds If ($LAST_STATE -eq "BACKUP_ERROR") { $MetaData.lastresult = $False } Else { $MetaData.lastresult = $True $MetaData.lastsuccess = $BeginDate.ToString() } Write-LogInfo "${SVC_NAME} END OF BACKUP; It took ${ElapsedTime}" } Else { Write-LogWarn "${SVC_NAME} SERVICE IS STOPPED; NO BACKUP STARTED" } } # END Foreach ($Service in $Services) $BACKUPS_METADATA | ConvertTo-Json | Out-File -FilePath $BACKUPS_METADATA_FILE }