{"id":666,"date":"2025-03-16T17:56:06","date_gmt":"2025-03-16T17:56:06","guid":{"rendered":"https:\/\/datacrazyworld.com\/?p=666"},"modified":"2025-04-24T08:17:55","modified_gmt":"2025-04-24T08:17:55","slug":"automatiza-backups-de-sql-server-con-powershell","status":"publish","type":"post","link":"https:\/\/datacrazyworld.com\/index.php\/2025\/03\/16\/automatiza-backups-de-sql-server-con-powershell\/","title":{"rendered":"Automatiza backups de SQL Server con PowerShell"},"content":{"rendered":"\n<p>Hace unas semanas sali\u00f3 en el canal de Telegram <a href=\"https:\/\/linktr.ee\/Sqlserverespanol\" title=\"\">SQL Server Espa\u00f1ol<\/a> el tema de los restores autom\u00e1ticos, y yo coment\u00e9 que ten\u00eda por ah\u00ed unos scripts para automatizarlos. El tema es bastante extenso y puede tener su complicaci\u00f3n, adem\u00e1s de que no estar\u00eda completo el RESTORE sin el de BACKUP, por lo que he pensado preparar varios art\u00edculos sobre esto. Empecemos por lo primero que necesitamos, que es generar el Backup. \u00a1All\u00e1 vamos!<\/p>\n\n\n\n<p>Lo primero que quiero indicar es que para este tipo de automatizaciones yo elijo Power Shell. Puede haber mil formas de hacerlo e igual alguna es mejor que lo que yo planteo, pero como siempre digo, es la forma que yo encontr\u00e9 de solucionar mi problema.<\/p>\n\n\n\n<p>Tambi\u00e9n quisiera aclarar que este script parte de uno que encontr\u00e9 por internet hace muchos a\u00f1os y que fui adaptando. Me encantar\u00eda poneros el origen de \u00e9ste, pero es que actualmente lo desconozco. As\u00ed que quiero agradecer a esa persona desconocida para mi que puso su primer granito para que este script naciera.<\/p>\n\n\n\n<p>El script que voy a explicar a continuaci\u00f3n lanza una serie de hilos que se encargan de generar el backup de las bbdds indicadas, empezando por las m\u00e1s grandes, y luego mover ese archivo .bak a una carpeta.<\/p>\n\n\n\n<p>Elementos por configurar:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>IP del servidor de SQL server: <strong>$SQLServer<\/strong><\/li>\n\n\n\n<li>Nombre de la instancia de SQL server: <strong>$SQLServerName<\/strong><\/li>\n\n\n\n<li>Usuario para lanzar los comandos de este script (debe tener los permisos suficientes): <strong>$dbuser<\/strong><\/li>\n\n\n\n<li>Password del usuario indicado anteriormente: <strong>$dbpass<\/strong><\/li>\n\n\n\n<li>Bases de datos que quieras excluir de este script: por ejemplo, tempdb: <strong>$objetcExclude<\/strong><\/li>\n\n\n\n<li>Url del fichero del log de lo que hace y no hace el script: <strong>$logfile<\/strong><\/li>\n\n\n\n<li>Listado de tareas\/pasos que quieres que se hagan con cada una de las bases de datos que se vean incluidas en la ejecuci\u00f3n del script: <strong>$Mastertasks<\/strong><\/li>\n\n\n\n<li>N\u00ba m\u00e1ximo de hilos que quieres que se lancen a la vez: <strong>$maxThreads<\/strong><\/li>\n\n\n\n<li>N\u00ba de intentos m\u00e1ximos que se har\u00e1 de cada tarea en caso de error: <strong>$NumIntentosMax<\/strong><\/li>\n\n\n\n<li>El criterio que haremos de ordenaci\u00f3n de las bbdds: <strong>$objectSortExpression1<\/strong><\/li>\n\n\n\n<li>Cada cuantos milisegundos comprobaremos si la tarea ha terminado: <strong>$SleepTimer<\/strong><\/li>\n\n\n\n<li>La cantidad m\u00e1xima de milisegundos que vamos a dejar en una tarea. Pasado este tiempo mataremos la tarea. Si no queremos limitarla, indicaremos 0: <strong>$querytimeout<\/strong><\/li>\n\n\n\n<li>N\u00ba de segundos de m\u00e1xima duraci\u00f3n de este script al completo. Pasado el cu\u00e1l, mataremos todas las tareas abiertas por \u00e9l. <strong>$MaxResultTime<\/strong><\/li>\n<\/ul>\n\n\n\n<p>Adem\u00e1s, es importante indicar que este script tiene que ejecutarse bajo una configuraci\u00f3n de entorno de 64 bits.<\/p>\n\n\n\n<p>Hasta aqu\u00ed tengo lo siguiente:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>###############################################################################################\n# Datos a rellenar\n# $SQLServer --&gt; Ip del servidor\n# $SQLServerName --&gt; Nombre del servidor\n# $objectExclude --&gt; BDs que queramos excluir\n# $Logfile --&gt; Url del fichero de log\n# $Mastertasks --&gt; Dentro poner las urls en la Task2 de d\u00f3nde hacer el backup y en la Task4 de d\u00f3nde copiar a donde copiarlo.\n###############################################################################################\n&#91;Environment]::Is64BitProcess\n$SQLServer = \"0.0.0.0\"\n$SQLServerName = \"NombreInstancia\"\n$db3 = \"master\" # No lo he indicado porque no hay que configurarla, pero aqu\u00ed hay que indicar el nombre de la bbdd master\n$dbuser = \"NombreUsuario\"\n$dbpass = \"PassUsuario\"\n$maxThreads = 4\n$querytimeout = 0 #0 significa que no hay l\u00edmite. Si necesitas se puede cambiar.\n$objectExclude = 'tempdb' -split \", \" #bases de datos que no queremos hacer el backup\n$objectNameField1 = \"DATABASE_NAME\" ##esto es un valor que obtenemos del $qcd\n$objectNameField2 = \"DATABASE_SIZE\" ## esto es un valor que obtenemos del $qcd\n$objectSortExpression1 = @{Expression={$_&#91;1]}; Descending=$true} # En el campo DATABASE_SIZE devuelto por sp_databases, para ordenar por tama\u00f1o\n$SleepTimer = 1000 #despu\u00e9s de X millisegundos, comprueba si los jobs ha terminado. \n$MaxResultTime = 14400 # despu\u00e9s X segundos, se matan todos los jobs. 7200 son dos horas.\n$timestamp = $(get-date -f yyyyMMddhhmmss) #para coger la marca de tiempo que usar\u00e9 luego en el $Logfile\n$Logfile = \"C:\\DirectorioBackupLogFiles\\\"+$SQLServerName+\"_\"+$timestamp+\".log\" #Url del Directorio donde dejar los logs\n$NumIntentosMax = 6 #N\u00ba intentos\n<\/code><\/pre>\n\n\n\n<p>Ahora voy a definir el comando sql que va a devolver las bases de datos y su tama\u00f1o. En este caso, como yo trabajaba con una instancia en Always On, me aseguro de que no estoy cogiendo BBDDs de lectura. Tambi\u00e9n me quedo s\u00f3lo con las que est\u00e9n ONLINE y de las que tengo permiso. De todo ello se encarga el comando indicado en <strong>$qscd <\/strong>que tiene que devolver dos campos: DATABASE_NAME y DATABASE_SIZE indicados anteriormente en las variables <strong>$objectNameField1<\/strong> y <strong>$objectNameField2<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$qcd = \"SELECT\n\t        b.DATABASE_NAME\n\t        ,b.DATABASE_SIZE\n        FROM\n\t        (\n\t\t        SELECT name as DATABASE_NAME FROM sys.databases where is_read_only = 0\n\t\t        EXCEPT\n\t\t        SELECT database_name FROM sys.availability_databases_cluster\n\t\t        UNION\n\t\t        select\n\t\t\t\t        d.database_name\n\t\t\t        from sys.dm_hadr_availability_replica_states ars\n\t\t\t        inner join sys.availability_groups ag\n\t\t\t        on ars.group_id = ag.group_id\n\t\t\t        inner join sys.availability_databases_cluster d\n\t\t\t\t        on ars.group_id = d.group_id\n\t\t        where ars.is_local = 1\n\t\t\t\t        and ars.role_desc='PRIMARY'\n\t        )a\n\t        inner join\n\t        (\n\t\t        select\n\t\t\t        DATABASE_NAME   = db_name(s_mf.database_id),\n\t\t\t        DATABASE_SIZE   = sum(convert(bigint,s_mf.size))\n\t\t        from\n\t\t\t        sys.master_files s_mf\n\t\t        where\n\t\t\t        s_mf.state = 0 and -- ONLINE\n\t\t\t        has_dbaccess(db_name(s_mf.database_id)) = 1 -- Only look at databases to which we have access\n\t\t        group by s_mf.database_id\n\t        )b\n\t        on a.DATABASE_NAME = b.DATABASE_NAME\"\n<\/code><\/pre>\n\n\n\n<p>Lo siguiente que necesito hacer es crear la funci\u00f3n que se va a encargar de escribir el Log.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Write-Host \"Paso 0:Crear LogWrite\"\nFunction LogWrite\n{\n   Param (&#91;string]$logstring)\n\n   $timestamp = Get-Date -Format o | ForEach-Object { $_ -replace \":\", \".\" }\n\n   $LogEntry = $timestamp +\": \"+$logstring\n\n   Add-content $Logfile -value $LogEntry\n}\nIf (&#91;Environment]::Is64BitProcess) {LogWrite \"Version64bits:true\"}else{LogWrite \"Version64bits:false\"}\n<\/code><\/pre>\n\n\n\n<p>A continuaci\u00f3n voy a recoger el listado de bases de datos (usando la sentencia indicada en <strong>$qcd<\/strong> ), excluyendo las que indicamos en <strong>$objectExclude<\/strong> y ordenarlas como se indicaba en <strong>$objectSortExpression1<\/strong>.<\/p>\n\n\n\n<p>Con todo ello creamos un hashtable vac\u00edo que convertimos en un diccionario ordenado, para incluir luego una a una las bases de datos, con las caracter\u00edsticas indicadas en los campos <strong>$objectNameField1<\/strong> y <strong>$objectNameField2<\/strong>, y que se vayan ordenando.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Write-Host \"Paso 1: Obtener la lista de BBDD y ordenar\"\nLogWrite \" Paso 1: Obtener la lista de BBDD y ordenar \"\n\n# Obtener la lista de BBDD y ordenar\n$objects = @((Invoke-Sqlcmd -ServerInstance $SQLServer -Database $db3 -Query $qcd -querytimeout $queryTimeout -Username $dbuser -Password $dbpass) | where {$objectExclude -notcontains $_.$objectNameField1} | sort $objectSortExpression1)\n\n#Empezamos a tratar los objetos\n$databases = &#91;ordered]@{}\n$databasesTasks =&#91;ordered]@{}#name,Tasks\nfor ($i=0; $i -lt $objects.length; $i++) {\n\t$object = $objects&#91;$i].$objectNameField1\n    $size = $objects&#91;$i].$objectNameField2\n    \n    $databases.Add($object,$size)   \n}\n<\/code><\/pre>\n\n\n\n<p>Bien, ahora es el momento de definir cada tarea que quiero que hagan cada uno de las BBDDs. Aqu\u00ed defino el PATR\u00d3N y luego este se aplicar\u00e1 en cada una de ellas. En mi caso voy a definir lo siguiente:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Indica en el Log que va a empezar con la BBDD XXX<\/li>\n\n\n\n<li>Lanza el comando de Backup. Aqu\u00ed tendr\u00e1s que indicar la url donde quieres dejar el .bak<\/li>\n\n\n\n<li>Indica en el Log que ha terminado de hacer el backup de la BBDD XXX<\/li>\n\n\n\n<li>Copia el archivo .bak a una carpeta que tendr\u00e1s que indicar<\/li>\n<\/ol>\n\n\n\n<p>En mi caso hago este paso 4 porque quiero que el backup acabe en una carpeta compartida con otro servidor donde voy a hacer el restore, pero no es necesario. Lo he querido dejar, para mostrar que se pueden encadenar m\u00e1s pasos y no s\u00f3lo el backup.<\/p>\n\n\n\n<p>Por \u00faltimo, indicar\u00e9 que tiene que empezar por la primera tarea, la 1.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$Mastertasks = @(# taskOrder, taskName, scriptToRun) args0=databaseName args1=$SQLServer args2=$db3 args3=$dbuser args4=$dbpass args5=$querytimeout args6= $SQLServerName. Only one command.\n\t,@(1, 'Log-message1', 'Write-Host \"$(Get-Date -Format o | ForEach-Object { $_ -replace \":\", \".\" }) : Starts backup with $args0\"')\n\t,@(2, 'backup', 'Invoke-Sqlcmd -ServerInstance $args1 -Database $args2 -Username $args3 -Password ''$args4'' -Query \"BACKUP DATABASE &#91;$args0] TO DISK=N''B:\\NombreCarpetaBackups\\$args6_$args0.bak'' WITH INIT, CHECKSUM\" -querytimeout $args5') <strong>##Configurar URL<\/strong>\n    ,@(3, 'Log-message2', 'Write-Host \"$(Get-Date -Format o | ForEach-Object { $_ -replace \":\", \".\" }) : Ends backup with $args0\"')\n    ,@(4, 'Move-files','Copy-Item -Path \"B:\\NombreCarpetaBackups\\$args6_$args0.bak\" -Destination \"\\\\11.11.11.11\\carpetas_compartida\\$args6_$args0.bak\"')\n) <strong>##Configurar URL y carpeta compartida<\/strong>\n\n$startAtTask = 1 #i.e. Si la red no estaba disponible, es posible que necesites volver a ejecutar comenzando desde esa tarea.\n<\/code><\/pre>\n\n\n\n<p>Como he dicho esto es el PATR\u00d3N. Ahora hay que aplicarlo a cada una de las BBDDs de mi lista.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#Por cada BBDD crearemos una lista de Tasks individual, partiendo de la $MasterTasks\n$MaxTasks = 0\nforeach ($h in $databases.Keys) {  \n    $tasks = @()  # taskOrder, taskName, scriptToRun) args0=databaseName args1=$SQLServer args2=$db3 args3=$dbuser args4=$dbpass args5=$querytimeout args6= $cmd. Only one command.\n    $task0 =  \"\" | select taskOrder, taskName,  scriptToRun\n    $task0.taskOrder = $Mastertasks&#91;0]&#91;0];\n    $task0.taskName = $Mastertasks&#91;0]&#91;1]\n    $task0.scriptToRun = $Mastertasks&#91;0]&#91;2].Replace('$args0',$h)\n    $tasks += $task0\n         \n    $task1 =  \"\" | select taskOrder, taskName,  scriptToRun\n    $task1.taskOrder = $Mastertasks&#91;1]&#91;0];\n    $task1.taskName = $Mastertasks&#91;1]&#91;1]\n    $task1.scriptToRun = $Mastertasks&#91;1]&#91;2].Replace('$args0',$h).Replace('$args1',$SQLServer).Replace('$args2',$db3).Replace('$args3',$dbuser).Replace('$args4',$dbpass).Replace('$args5',$querytimeout).Replace('$args6',$SQLServerName)\n    $tasks += $task1\n\n    $task2 =  \"\" | select taskOrder, taskName,  scriptToRun\n    $task2.taskOrder = $Mastertasks&#91;2]&#91;0];\n    $task2.taskName = $Mastertasks&#91;2]&#91;1]\n    $task2.scriptToRun = $Mastertasks&#91;2]&#91;2].Replace('$args0',$h)\n    $tasks += $task2\n\n    $task3 =  \"\" | select taskOrder, taskName,  scriptToRun\n    $task3.taskOrder = $Mastertasks&#91;3]&#91;0];\n    $task3.taskName = $Mastertasks&#91;3]&#91;1]\n    $task3.scriptToRun = $Mastertasks&#91;3]&#91;2].Replace('$args0',$h).Replace('$args6',$SQLServerName)\n    $tasks += $task3\n        \n    $databasesTasks.Add($h,($tasks | sort taskOrder))\n    if ($MaxTasks -lt $tasks.Count) { $MaxTasks = $tasks.Count}\n}\n<\/code><\/pre>\n\n\n\n<p>Ahora configurar\u00e9 el entorno, para indicar cosas como el n\u00ba de tareas m\u00e1xima, el n\u00ba de hilos,\u2026 Esto es necesario porque lo voy a lanzar en paralelo usando threads y necesito configurarlos.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Write-Host \"Paso 2: Configuraci\u00f3n del entorno\"\nLogWrite \" Paso 2: Configuraci\u00f3n del entorno \"\n\n#environment setup\n$RunspacePools = @()\n$Jobs = @()\n$ISS = &#91;system.management.automation.runspaces.initialsessionstate]::CreateDefault()\n$taskInfo_db = @{} #key=databasename; values = $taskInfo\n$maxTaskOrder = -1\n$objectInfo_db = @{} #key=databasename; values = $objectInfo\n$objectInfoArr = @(0) * $databasesTasks.count\n$output = \"\"\n$errors = \"\"\n$errorCount_db = @{} #key=databasename; values = $errorCount\n#Hacemos un RunSpacePool por cada tarea \nfor ($i=0; $i -lt $MaxTasks; $i++) {\n    $RunspacePools += &#91;runspacefactory]::CreateRunspacePool(1, $maxThreads, $ISS, $Host)\n    $RunspacePools&#91;$i].Open()\n}\nforeach ($h in $databasesTasks.Keys) { \n    $taskInfo = @{} # key=taskOrder; value=nextTaskOrder\n    $objectInfo = @{} # key=taskId; values=each database\n    $errorCount = @{}\n    $tasks = $databasesTasks&#91;$h]\n    for ($i=0; $i -lt $tasks.length; $i++) {\n\t    if ($taskInfo.Count -eq 0 -Or -Not $taskInfo.ContainsKey($tasks&#91;$i].taskOrder)) { #taskOrder\n\t\t    $taskInfo.Add($tasks&#91;$i].taskOrder, -1)\n\t\t    if ($tasks&#91;$i].taskOrder -gt $maxTaskOrder) { $maxTaskOrder = $tasks&#91;$i].taskOrder }\n\t    }\n\t    $objectInfo.Add($i, $objectInfoArr.clone())\n\t    $tasks&#91;$i].scriptToRun = '$ErrorActionPreference = \"Stop\"; try { $output = ' + $tasks&#91;$i].scriptToRun + ' 2&gt;&amp;1 } catch { $err = $_.Exception; $errors = $err.Message; while($err.InnerException) { $err = $err.InnerException; $errors += \"|\" + $err.Message } } $LastExitCode; $errors | where { $_ } | Out-String; $output | where { $_ } | Out-String'\n        $errorCount.Add($i, 0)\n    }\n    foreach ($key in @($taskInfo.Keys)) { $taskInfo&#91;$key] = ($taskInfo.Keys | where {$_ -gt $key} | sort | select -First 1) }\n    $taskInfo_db.Add($h,$taskInfo)\n    $objectInfo_db.Add($h,$objectInfo)\n    $errorCount_db.Add($h,$errorCount)\n<\/code><\/pre>\n\n\n\n<p>Ahora crear\u00e9 la funci\u00f3n que va a crear cada Thread y voy a decirle qu\u00e9 tiene que hacer, que viene a ser lanzar en la bbdd que se le indica, las tareas indicadas, con n\u00ba m\u00e1ximo de intentos.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Write-Host \" Paso 3: funci\u00f3n para crear el hilo y comenzar el procesamiento. \"\nLogWrite \" Paso 3: funci\u00f3n para crear el hilo y comenzar el procesamiento. \"\n\n#: funci\u00f3n para crear el hilo y comenzar el procesamiento\nfunction CreateThread() {\n\tparam (&#91;string]$databaseName, &#91;int]$objectIndex, &#91;int]$taskIndex, &#91;int]$taskOrder, &#91;ref]$Jobs), &#91;int]$NumIntentosMax       \n    \n    $PowershellThread = &#91;powershell]::Create().AddScript(\n        $databasesTasks&#91;$databaseName]&#91;$taskIndex].scriptToRun\n        ) \t\n\t$PowershellThread.RunspacePool = $RunspacePools&#91;$taskIndex]\n\t$Handle = $PowershellThread.BeginInvoke()\n    LogWrite \" IniciarInvocaci\u00f3n BBDD: $databaseName. TaskIndex: $taskIndex. TaskOrder: $taskOrder\"\n\t$Job = \"\" | select Handle, Thread, DatabaseName, ObjectIndex, TaskIndex, TaskOrder, NumIntentosMax\n\t$Job.Handle = $Handle; \n    $Job.Thread = $PowershellThread;\n\t$Job.DatabaseName = $databaseName; \n    $Job.ObjectIndex = $objectIndex; \n    $Job.TaskIndex = $taskIndex; \n    $Job.TaskOrder = $taskOrder;\n\t$Job.NumIntentosMax =$NumIntentosMax\n\t$Jobs.value += $Job;\n}\n$ResultTimer = Get-Date\n<\/code><\/pre>\n\n\n\n<p>Configurado ya el entorno y creada la funci\u00f3n para crear los threads, s\u00f3lo nos queda empezar a lanzarlo todo.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#Paso 4: comenzar a procesar la primera tarea para cada base de datos.\n#Recorro las BBDDs y llamo al CreateThread\n$i=0\nforeach ($h in $databasesTasks.Keys) {    \n    $tasks = $databasesTasks&#91;$h]\n    $tasks | where {$_.TaskOrder -eq $startAtTask} | foreach { CreateThread $h $i (&#91;array]::IndexOf($tasks, $_)) $_.TaskOrder (&#91;ref]$Jobs) $NumIntentosMax  }\n    $i++\n}\nwhile (@($Jobs | where {$_.Handle -ne $Null}).count -gt 0) {\n\t#Actualizar trabajos completados y liberarlos.\n\tforeach ($Job in @($Jobs | where {$_.Handle -ne $Null -and $_.Handle.IsCompleted -eq $True})) {\n\t\t#actualiza estadp\n\t\t$objectInfo_db&#91;$Job.DatabaseName]&#91;$Job.TaskIndex]&#91;$Job.ObjectIndex] = 1\n\t\t#Cojo resultados\n        $databaseName = $Job.DatabaseName\n        $taskOrder = $Job.TaskOrder\n        $taskIndex = $Job.TaskIndex\n\t\t$results = $Job.Thread.EndInvoke($Job.Handle)\n        LogWrite \"EndInvoke DB: $databaseName; TaskIndex: $taskIndex; TaskOrder: $taskOrder; Results: $results\"\n\t\tif (($results&#91;0] -and $results&#91;0] -ne 0) -or $results&#91;1] -ne \"\") \n\t\t{  #Si hay error.\n\t\t\tIf ($errorCount_db&#91;$Job.DatabaseName]&#91;$Job.TaskIndex] -lt $Job.NumIntentosMax)\n\t\t\t{\n\t\t\t\t#Creo otro Thread\n\t\t\t\tLogWrite \"Retry DB: $databaseName; TaskIndex: $taskIndex; TaskOrder: $taskOrder;\"\n\t\t\t\tCreateThread $Job.DatabaseName $Job.ObjectIndex $Job.TaskIndex $Job.TaskOrder (&#91;ref]$Jobs) $Job.NumIntentosMax\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t$errors += $results&#91;1] + \"`r`n\"\n\t\t\t\t$output += $results&#91;2] + \"`r`n\"\n\t\t\t\t$objectInfoArr&#91;$Job.ObjectIndex] = 1; \n\t\t\t}\n\t\t\t$errorCount_db&#91;$Job.DatabaseName]&#91;$Job.TaskIndex] += 1; \n\t\t}\n\t\tif ($errorCount_db&#91;$Job.DatabaseName]&#91;$Job.TaskIndex] -eq 0 -or $errorCount_db&#91;$Job.DatabaseName]&#91;$Job.TaskIndex] -gt 3)\n\t\t{\n\t\t\t#lanzo la siguiente tarea\n\t\t\tif ($Job.TaskOrder -lt $maxTaskOrder -and #Hay tareas pendientes\n\t\t\t\t(@($Jobs | where {$_.TaskOrder -eq $Job.TaskOrder -and #mismo taskOrder\n\t\t\t\t\t\t\t\t\t $_.DatabaseName -eq $Job.DatabaseName -and #mismo database\n\t\t\t\t\t\t\t\t\t $_.Handle.IsCompleted -eq $False}).count -eq 0) -and #no hay thread activo\n\t\t\t\t$objectInfoArr&#91;$Job.ObjectIndex] -eq 0) { #No hay errores hasta ahora.\n\t\t\t\t$tasks | where {$_.TaskOrder -eq $taskInfo&#91;$Job.TaskOrder]} | foreach { CreateThread $Job.DatabaseName $Job.ObjectIndex (&#91;array]::IndexOf($tasks, $_)) $_.TaskOrder (&#91;ref]$Jobs) $Job.NumIntentosMax}\n\t\t\t}\n\t\t}\n\t\t#end thread\n\t\t$Job.Thread.Dispose()\n\t\t$Job.Thread = $Null\n\t\t$Job.Handle = $Null\t\t\n\t}\n\t$currentTime = Get-Date\n\tif (($currentTime - $ResultTimer).totalseconds -gt $MaxResultTime) {\n\t\tWrite-Error \"Parece que el script secundario est\u00e1 congelado, intenta aumentar MaxResultTime.\"\n        LogWrite \"\u00a1\u00a1ERROR!!! Parece que el script secundario est\u00e1 congelado, intenta aumentar MaxResultTime.\"\n\t\tbreak\n\t}\n\t#Espera\n\tStart-Sleep -Milliseconds $SleepTimer\n}\n<\/code><\/pre>\n\n\n\n<p>Por \u00faltimo, queda asegurarse que se cierran todos los threads<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Write-Host \"Paso 5: liberar los grupos de hilos.\"\nLogWrite \"Paso 5: liberar los grupos de hilos.\"\n#liberamos los grupos de hilos\nforeach ($r in $RunspacePools.Keys) {   \n\t$RunspacePools&#91;$r].Close() | Out-Null\n\t$RunspacePools&#91;$r].Dispose() | Out-Null\n}\nif (($errors -replace \"`r`n\", \"\") -ne \"\") { \n\tLogWrite \"ERROR!!! $errors\"\n\tthrow $errors + \" \" + $output \n} else { \n\t$output \n}\n<\/code><\/pre>\n\n\n\n<p>Bueno, es un script complejo que realmente sirve para automatizar el lanzamiento de comandos en una serie de hilos en paralelo, y que yo he utilizado para generar mis backups de manera autom\u00e1tica, iniciando siempre por los m\u00e1s grandes.<\/p>\n\n\n\n<p>Una vez junt\u00e9is todos los trozos y gener\u00e9is vuestro archivo .ps1, basta con que us\u00e9is el programador de tareas de Windows para tenerlo automatizado. Si no quer\u00e9is este \u00faltimo paso, siempre pod\u00e9is ejecutarlo vosotros lanz\u00e1ndolo manualmente.<\/p>\n\n\n\n<p>\u00a1Espero que os sirva de ayuda! \ud83d\ude09<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Se puede automatizar la generaci\u00f3n de Backups y de Restores del SQL Server con PowerShell. Aqu\u00ed te explico paso a paso un script que genera el backup de N bases de datos en paralelo.<\/p>\n","protected":false},"author":2,"featured_media":614,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[11,8],"tags":[14,21],"class_list":["post-666","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-powershell","category-sqlserver","tag-powershell","tag-sqlserver"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/datacrazyworld.com\/index.php\/wp-json\/wp\/v2\/posts\/666","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/datacrazyworld.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/datacrazyworld.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/datacrazyworld.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/datacrazyworld.com\/index.php\/wp-json\/wp\/v2\/comments?post=666"}],"version-history":[{"count":4,"href":"https:\/\/datacrazyworld.com\/index.php\/wp-json\/wp\/v2\/posts\/666\/revisions"}],"predecessor-version":[{"id":672,"href":"https:\/\/datacrazyworld.com\/index.php\/wp-json\/wp\/v2\/posts\/666\/revisions\/672"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/datacrazyworld.com\/index.php\/wp-json\/wp\/v2\/media\/614"}],"wp:attachment":[{"href":"https:\/\/datacrazyworld.com\/index.php\/wp-json\/wp\/v2\/media?parent=666"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/datacrazyworld.com\/index.php\/wp-json\/wp\/v2\/categories?post=666"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/datacrazyworld.com\/index.php\/wp-json\/wp\/v2\/tags?post=666"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}