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.