Archivo

Archivos de la categoria ‘Sin categoría’

Usando PowerShell para hacer un backup de sql server express

Viernes, 5 de abril de 2013

Tengo una pequeña base de datos y estoy muy atento a ella. He hecho copias de seguridad de forma más o menos manual durante demasiado tiempo, y ayer durante un muy buen webcast sobre Windows 8 de Samuel Lopez (@samuelltr) se explicaron algunas cosillas sobre PowerShell y me ha entrado el gusanillo.

¿Y si hago un pequeño script que me haga la copia de seguridad, la comprima en zip, me la mande por mail los viernes a mediodía y borre luego los archivos?

Pues manos a la obra… porque como veréis se hace en un plís. Creamos un nuevo script con PowerShell ISE (o con el notepad, que no vamos a hacer grandes maravillas) y comenzamos a teclear:

## para copias de seguridad        
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.ConnectionInfo');            
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.Management.Sdk.Sfc');            
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO');                     
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMOExtended'); 
[void][System.Reflection.Assembly]::LoadWithPartialName("WindowsBase");

##Config
$Server = "(local)";           
$Dest = "C:\Temp\";
$DBname =  "NombreBaseDatos";
$srv = New-Object Microsoft.SqlServer.Management.Smo.Server $Server;

Hasta aquí nada del otro mundo, tan solo los namespaces necesarios y unas variables de configuración. $Dest es solo una carpeta temporal.

Ahora vamos a comenzar con una función que cree la copia de seguridad en sí misma:

function CreateBackupFile($db)
{
    $timestamp = Get-Date -format yyyyMMdd-HH-mm-ss;  
    $filename = $Dest + $timestamp +  $db.Name + ".bak"         
    $backup = New-Object ("Microsoft.SqlServer.Management.Smo.Backup");            
    $backup.Action = "Database";            
    $backup.Database = $db.Name;            
    $backup.Devices.AddDevice($filename,"File");            
    $backup.BackupSetDescription = "BackUp completo de " + $db.Name + " a fecha " + $timestamp;            
    $backup.Incremental = 0;                          
    $backup.SqlBackup($srv);     
    return $filename;
}

Tampoco hay mucho que explicar de esta función. Tan solo que recibe una base de datos, crea un nombre de archivo a mi gusto  y hace una copia de seguridad de la misma usando ese nombre. En todo caso, debemos darnos cuenta de que estamos suponiendo que la base de datos que buscamos tiene un recovery model simple. De otro modo nos interesaría traernos también los archivos de log.  Comprobar si el recovery model es full es se podría hacer así

If ($db.RecoveryModel -ne 3)            
{    
    ### Lo mismo, pero paracopiar tambien los Log
    ###...
    $backup.Action = "Log";
    $backup.LogTruncation = "Truncate"; 
    ###...  
};

Ahora necesitamos una función que dado un fichero, lo meta en un zip.

function CreateZIP($fileName)
{
   $ZipPackage=[System.IO.Packaging.ZipPackage]::Open($fileName + ".zip",[System.IO.FileMode]"OpenOrCreate", [System.IO.FileAccess]"ReadWrite")
   $Relativefile = "/" + (split-path $fileName -Leaf)

   $partName=New-Object System.Uri($Relativefile, [System.UriKind]"Relative")
   $part=$ZipPackage.CreatePart($partName, "application/zip", [System.IO.Packaging.CompressionOption]"Maximum")
   $bytes=[System.IO.File]::ReadAllBytes($fileName)
   $stream=$part.GetStream()
   $stream.Write($bytes, 0, $bytes.Length)
   $stream.Close()

   $ZipPackage.Close()
   return $fileName + ".zip"
}

Como la función CreatePart como primer argumento una URI relativa, de la ruta completa del fichero hemos extraido el nombre para crear una ruta relativa usando Split-path. El fichero .Zip lo dejamos en el mismo lugar que el .Bak

Vamos a por una función que reciba la ruta de un fichero y mande el mail con ese fichero adjunto.

function SendMail($AttachedFile)
{
    $EmailFrom = "<CopiasSeguridad@MiEmpresa.es>"
    $EmailTo = "<receptor@receptor.es>"
    $EmailSubject = "Copia de seguridad Automática" 
    $emailbody = "Copia de seguridad realizada con éxito" 
    $SMTPServer = "smtp.miempresa.es"
    $SMTPAuthUsername = "Rosebud"
    $SMTPAuthPassword = "MyVoiceIsMyPassword"

    $emailattachment = $AttachedFile

    $mailmessage = New-Object system.net.mail.mailmessage
    $mailmessage.from = ($emailfrom)
    $mailmessage.To.add($emailto)
    $mailmessage.Subject = $emailsubject
    $mailmessage.Body = $emailbody

    $attachment = New-Object System.Net.Mail.Attachment($emailattachment, 'text/plain')
    $mailmessage.Attachments.Add($attachment)

    #$mailmessage.IsBodyHTML = $true
    $SMTPClient = New-Object Net.Mail.SmtpClient($SmtpServer, 25) 
    $SMTPClient.Credentials = New-Object System.Net.NetworkCredential("$SMTPAuthUsername", "$SMTPAuthPassword")
    $SMTPClient.Send($mailmessage);
    $attachment.Dispose();
    $mailmessage.Dispose();
}

Ojo a las dos últimas líneas. Si no hacemos los Disposes correspondientes, luego no podremos borrar los archivos: Nos dirá que están siendo usados por otro proceso. También hay un cmdlet que lo hace: Send-MailMessage, por si queda más claro.

Una vez que tenemos las funciones accesorias programadas, vamos a unirlo todo.

Write-Output ("Comenzando: " + (Get-Date -format yyyy-MM-dd-HH:mm:ss));                     
foreach ($db in $srv.Databases)            
{            
    If($db.Name -eq $DBname)            
    {            
        $backupfilename = CreateBackupFile($db);
        $zipfilename = CreateZIP($backupfilename);
        SendMail($zipfilename)
        If (Test-Path ($zipfilename))
        {
	       Remove-Item ($zipfilename);
           Write-Output ($zipfilename + " Borrado");
        };     
        If (Test-Path ($backupfilename))
        {
	       Remove-Item ($backupfilename);
           Write-Output ($backupfilename + " Borrado");
        };  
    }

};            
Write-Output ("Finalizado: " + (Get-Date -format  yyyy-MM-dd-HH:mm:ss));

Como veis, Localizamos la bbdd que nos interesa, creamos el archivo de copia de seguridad, el zip que lo contiene, enviamos el mail y si todo va bien borramos los archivos residuales.

Solo nos queda ir al programador de tareas y decirle cuando queremos que se ejecute el script. Le decimos que queremos que se ejecute un programa, que será powershell.exe, y en los argumentos  escribimos –File <Ruta completa del script>

Y eso es todo. Por supuesto no estaría mal usar control de errores (Try-Catch) para que nos avise por mail si falla la copia de seguridad, o si el servidor está caído. Hay mucho espacio para la mejora, pero un ejemplo es un ejemplo ;) ¡Y al menos no dice Hola Mundo!

Sin categoría