Publicación con PowerShell
Haremos uso de 4 ficheros:
- El script principal de ejecución: PS_Lab02_Publicar.ps1.
- Fichero de módulo de script con diferentes funciones que usaremos en el script principal: Funciones.psm1 (recordar que está en el post anterior).
- Un fichero de configuración donde están todos los parámetros que usaremos: Publicar.cfg.
- En el ejemplo que estamos presentando se trata de un caso con 2 nodos de consulta: QueryServers.csv.
Nuestro fichero de configuración esta vez tendrá este aspecto:
Nas = 0 ProcServer = SRVPRO IDDatabase = SSAS ProcServerDatabasePath = \\SRVPRO \Data QueryServers = SRVPROQ1;SRVPROQ2 QueryServersDatabasePaths = \\SRVPROQ1\Data;\\SRVPROQ2\Data DataFolders = D:\Program Files\Microsoft SQL Server\MSAS11.MSSQLSERVER\OLAP\Data;D:\Program Files\Microsoft SQL Server\MSAS11.MSSQLSERVER\OLAP\Data BackupFolders = D:\Program Files\Microsoft SQL Server\MSAS11.MSSQLSERVER\OLAP\Backup;D:\Program Files\Microsoft SQL Server\MSAS11.MSSQLSERVER\OLAP\Backup ProcDataFolder = D:\Program Files\Microsoft SQL Server\MSAS11.MSSQLSERVER\OLAP\Data BackupDatabasePaths = \\SRVPROQ1\Backup;\\SRVPROQ2\Backup RobocopyThreadExtensions = DBError = Error de OLE DB;valor duplicado
Y el fichero de nodos de consulta contendrá:
QueryServer,BackupFolderUNC,BackupFolder,DataFolderUNC,DataFolder SRVPROQ1,\\SRVPROQ1\Backup,D:\Program Files\Microsoft SQL Server\MSAS11.MSSQLSERVER\OLAP\Backup,\\SRVPROQ1\Data,D:\Program Files\Microsoft SQL Server\MSAS11.MSSQLSERVER\OLAP\Data SRVPROQ2,\\SRVPROQ2\Backup,D:\Program Files\Microsoft SQL Server\MSAS11.MSSQLSERVER\OLAP\Backup,\\SRVPROQ2\Data,D:\Program Files\Microsoft SQL Server\MSAS11.MSSQLSERVER\OLAP\Data
Veamos ahora paso por paso el fichero principal:
Establecemos variables de inicio (Parámetros):
$pRutaFicheroParam = "C:\Users\Administrador\Documents\PowerShell\Config\Publicar.cfg" $pFicheroModulo="C:\Users\Administrador\Documents\PowerShell\Funciones.psm1" $pFicheroQueryServers = "C:\Users\Administrador\Documents\PowerShell\Config\QueryServers.csv"
Cargamos el modulo que contiene las funciones auxiliares:
$LoadedModule = Import-Module $pFicheroModulo -PassThru -force -DisableNameChecking
Cargamos las librerias de funciones de analysis services:
ImportModulesAndAssemblies
Cargamos un array con los datos de los parámetros del fichero pRutaFicheroParam:
$ConfigParam = @{} # Tabla de hash para guardar los pares Parametro - Valor Get-Content $pRutaFicheroParam | foreach { $line = $_.split("=") $ConfigParam.($line[0].Trim()) = $line[1] } # Query servers $CsvFile= NormalizePath($pFicheroQueryServers)
Variables con las rutas de los parámetros, en esta ocasión sí las leemos de un fichero de configuración en vez de introducirlas manualmente:
$DataFolders = @() $BackupFolders = @() $SSASdatabase = Trimmed($ConfigParam.Get_Item("IDDatabase")) $auxProccessFolder = Trimmed($ConfigParam.Get_Item("ProcServerDatabasePath")) $DataFolders = SplitTrimmed($ConfigParam.Get_Item("DataFolders")) $BackupFolders = SplitTrimmed($ConfigParam.Get_Item("BackupFolders")) $DataFolderProc = Trimmed($ConfigParam.Get_Item("ProcDataFolder")) $ProcServer = (Trimmed($ConfigParam.Get_Item("ProcServer"))) $robocopyWork = { param ($ext, $ProcF, $QeryF, $LogR) #para diferenciar el "comando final" con todas las extensiones que no se han #copiado de los comandos "normales" se usa la ; if ($ext -match ";") { Robocopy $ProcF $QeryF $ext.Split(";") /Z /MT:12 /S /E /LOG+:$LogR } else { Robocopy $ProcF $QeryF $ext /Z /MT:12 /S /E /LOG+:$LogR } #lanza una excepcion para que el job se entere que ha habido un error $exitCode = $LastExitCode if ($exitCode -gt 3) { Throw $exitCode } } $RobocopyThreadExtensions= @() $RobocopyThreadExtensions += "*.*;/xf;" + ($ConfigParam.Get_Item("RobocopyThreadExtensions")) LogWriteSeparador LogWrite "INICIO Conexion con el servidor de procesado"
Se conecta con el servidor de procesado:
$svr = new-Object Microsoft.AnalysisServices.Server $Err = New-Object Microsoft.AnalysisServices.ErrorConfiguration $svr.Connect($ProcServer) Obtiene la bbdd $db = $svr.Databases.Item($SSASdatabase)
Se obtiene el número de carpetas con posibles bases de datos:
[int]$DataFolderCount=((Get-ChildItem -Path $auxProccessFolder -Directory | ? {$_.Name -ilike "*$SSASdatabase.*.db"}).count) LogWrite -text "Detectadas [$DataFolderCount] carpetas con el identificador de la base de datos ", [$SSASdatabase] -color White, yellow
Se almacenan los metadatos de las carpetas:
$ProccessFolder = (Get-ChildItem -Path $auxProccessFolder -Directory | ? {$_.Name -ilike "*$SSASdatabase.*.db"} | Sort-Object {$_.LastWriteTimeUtc} -Descending)[0].FullName $FechaLastWrite = (Get-ChildItem -Path $auxProccessFolder -Directory | ? {$_.Name -ilike "*$SSASdatabase.*.db"} | Sort-Object {$_.LastWriteTimeUtc} -Descending)[0].LastWriteTimeUtc $ProccessFolderName = (Get-ChildItem -Path $auxProccessFolder -Directory | ? {$_.Name -ilike "*$SSASdatabase.*.db"} | Sort-Object {$_.LastWriteTimeUtc} -Descending)[0].BaseName $nf = $ProccessFolderName LogWrite -text "Se ha seleccionado la carpeta '", $ProccessFolder, "' en el servidor de procesamiento ", [$ProcServer], " como última versión (fecha: $FechaLastWrite)" -Color white, yellow, white, yellow, white
Se eliminan todas las carpetas que hacen referencia a la misma bbdd OLAP, excepto la seleccionada como última versión:
$ProccessFolders = ([System.IO.Directory]::GetDirectories("$auxProccessFolder") | ? {$_ -ilike "*$SSASdatabase.*.db" -and $_ -ne $script:ProccessFolder} ) if ($DataFolderCount -gt 1) { LogWrite -text "Eliminando [$($DataFolderCount-1)] carpetas descartadas en el servidor de procesamiento ", [$ProcServer] -color white, yellow foreach($folder in $ProccessFolders) { LogWrite -text "INICIO borrado '", $folder, "'" -Color white, yellow, white remove-item $folder -Force -Recurse -ErrorAction Stop LogWrite "FIN borrado '$folder'" } }
Detach de la BBDD (Procesado), seguimos la estrategia de detach y attach:
LogWrite "INICIO detach del servidor de procesamiento [$ProcServer]" Detach-ASDatabase $svr $SSASdatabase $detachSource=$true LogWrite "FIN detach del servidor de procesamiento [$ProcServer]" LogWriteSeparador
Creamos una coleccion “QueryServerCollection” de objetos con los query servers y sus propiedades. Realiza un backup de la base de datos Analysis Services de los query servers:
$QueryServerCollection=@() $Content=Import-Csv $CsvFile $CodigoError=0 foreach($queryServer in $Content) { #Agrega las propiedades para almacenar informacion del proceso $queryserver| Add-Member NoteProperty Complete $false $queryserver| Add-Member NoteProperty DetachedDatabase $null $queryserver| Add-Member NoteProperty BackupReady false $queryserver| Add-Member NoteProperty MustAttach $false $queryserver| Add-Member NoteProperty ErrorMessage "" $queryserver| Add-Member NoteProperty LogMessages @() $currentServer= Check-ASServer( $queryServer.QueryServer) $queryserver| Add-Member NoteProperty svrq $currentServer $queryserver| Add-Member NoteProperty FailureExecutionExit "" $queryserver| Add-Member NoteProperty ExitScriptCodigo 0 $queryserver| Add-Member NoteProperty BackupFileName "" $queryserver| Add-Member NoteProperty FolderName $script:ProccessFolder $queryserver| Add-Member NoteProperty nf $script:nf $queryserver| Add-Member NoteProperty OriginalFolderName "" if($currentServer -ne $null ) { LogWrite "INICIO Backup del cubo (previo al detach) $Server" $queryServer.BackupFileName=(Backup-ASDatabase $queryServer.QueryServer $SSASdatabase $queryServer.BackupFolder) $queryServer.BackupReady=$true LogWrite "FIN Backup del cubo (previo al detach) $Server" $QueryServerCollection+=$queryserver } else { LogWrite "El servidor $($queryServer.QueryServer) no responde. No se puede continuar" } }
Recorremos el array de servidores de consulta y borramos la base de datos de destino y la copiamos desde el servidor de procesado:
foreach ($queryServer in $QueryServerCollection) { LogWriteSeparador $currentServer=$null $currentServer=$queryServer.svrq #Detach de la base de datos ######################################################################## if($currentServer -ne $null ) { if ($currentServer.Databases.FindByName($SSASdatabase)) { LogWrite -text "INICIO Detach de la bbdd en el servidor de query (", $currentServer, ")" -Color white, yellow, white Detach-ASDatabase $currentServer $SSASdatabase $queryserver.DetachedDatabase=$SSASdatabase LogWrite -text "FIN Detach de la bbdd en el servidor de query (", $currentServer, ")" -Color white, yellow, white } } # BORRADO DE CARPETAS DE BASE DE DATOS ######################################################################## # se identifican todas las carpetas con el ID de la base de datos #nos situamos en el path del script $cRutaScript = Split-Path $MyInvocation.MyCommand.Path -Parent cd $cRutaScript $Folders = ([System.IO.Directory]::GetDirectories($queryServer.DataFolderUNC) | ? {$_ -ilike "*$SSASdatabase.*.db" }) $queryServer.OriginalFolderName=(Get-ChildItem -Path $auxProccessFolder -Directory | ? {$_.Name -ilike "*$SSASdatabase.*.db"} | Sort-Object {$_.LastWriteTimeUtc} -Descending)[0].BaseName foreach($folder in $Folders) { LogWrite "INICIO borrado '$folder'" Remove-Item $folder -Force -Recurse -ErrorAction Stop LogWrite "FIN borrado '$folder'" } # COPIA DE FICHEROS A ESPACIO ALMACENAMIENTO SERVIDORES QUERY $queryserver.QueryServer ######################################################################## LogWrite "" $Destino1 = "$($queryServer.DataFolderUNC)\$($queryServer.nf)" LogWrite "INICIO copia de la carpeta del modelo multidimensional $script:ProccessFolder -> $Destino1" $LogRobocopy = "C:\Users\Administrador\Documents\PowerShell\Log\LogRobocopy.log" #obtiene las extensiones para crear "hilos" por cada una. con esto optimiza el tiempo de copia #siempre habra un hilo con todas las demas extensiones no especificadas (se hace en la declaracion del array) $jobs = @() foreach($rte in $RobocopyThreadExtensions) { $jobs += Start-Job -ScriptBlock $robocopyWork -ArgumentList ($rte, $script:ProccessFolder, $Destino1, $LogRobocopy) } #espera a que finalicen los jobs que se lanazaron en paralelo $jobs | Receive-Job -Wait #comprueba si se ha producido algun error en los procesos de copia if (@($jobs | Where-Object {$_.State -eq "Failed"}).Count -gt 0) { Throw "" } LogWrite "FIN copia de la carpeta del modelo multidimensional"
Attach de la BBDD en los servidores de consulta:
#una vez realizada la copia de los ficheros realiza el Attach en los servers de query LogWrite "INICIO attach servidores query $($queryServer.QueryServer)" $NewQueryFolder = "$($queryServer.DataFolder)\$($queryServer.nf)" Attach-ASDatabase $currentServer $NewQueryFolder "ReadOnly" LogWrite "FIN attach servidores query $($queryServer.QueryServer)" $queryserver.Complete=$true }
Attach de la BBDD en el servidor de procesado:
LogWrite "INICIO attach servidores procesado $($ProcServer)" Attach-ASDatabase $ProcServer $ProccessFolder "ReadOnly" LogWrite "FIN attach servidores procesado $($ProcServer)"
A modo de resumen de este hilo de código, veamos una diagrama de flujo de los pasos seguidos, por supuesto lo podemos complicar tanto como deseemos:
Con lo visto hasta aquí tendríamos lista la parte de publicación, continuamos en el siguiente post con la parte de automatización de la seguridad de SSAS.