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:

Automatizar la gestión de SSAS con PowerShell pt. 2

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.

0 Shares:
Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

You May Also Like
Leer más

Autenticación Integrada en Azure Database con SSIS

En muchos escenarios se nos presenta la necesidad de usar autenticación integrada para acceder a los orígenes de datos necesarios para alimentar nuestro sistema analítico. Con el uso cada vez más extendido de Azure, como al menos parte de nuestra infraestructura, algunos de estos orígenes van a estar alojados en bases de datos en Azure. En este caso vamos hablar de un error real que hemos tenido en la configuración y uso de la autenticación integrada contra bases de datos Azure con SSIS.
Leer más

Power BI Dataflows: Machine Learning en dos clicks!

En esta entrada continuaremos con la saga "en dos clicks", en la entrada anterior explicamos como hacer análisis de sentimiento en dos clicks con Power BI dataflows y ahora es el turno de mostrar cómo crear modelos de machine learning de forma automática utilizando la nueva funcionalidad abierta a través de los Power BI Dataflows.