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.
