Procesado de un tabular a través de PowerShell

Powershell es una consola de sistema más avanzado y completo que MS-DOS o CMD. En nuestro caso, a través de PowerShell, crearemos un script que nos permitirá procesar un modelo tabular (Microsoft Analysis Services Tabular Model) de forma autónoma.





Asumimos que vamos a tener el siguiente código en un fichero .ps1, es decir, crearemos un fichero de nombre ProcesaTabular.ps1 con el código que viene a continuación:




#┌────────────────────────────────────────┬────┐
#│ PARAMETROS                             │    │
#├────────────────────────────────────────┼────┤
#│ IN - Nombre del fichero de parámetros  │    │
#│ IN - Nombre del fichero de Log         │    │
#└────────────────────────────────────────┴────┘

Param
 (
 [string]$pFicheroParam,
 [string]$pFicheroLog
 )
#Variable debugging, si vale 1 da salida por pantalla del log (Verbose) y en caso de fallo no sale del debugger (PowerShell ISE)
 #$Debugging= 0   
   $Debugging = 1
  #$pFicheroParam = 'Config.Param'
        #$pFicheroLog = 'Log.out'

#┌─────────────────────────────────────────┐
#│ CONSTANTES                              │
#├─────────────────────────────────────────┤
#│ Ubicación de fichero de Log             │
#│ Ubicación de fichero de parámetros      │
#└─────────────────────────────────────────┘

$cRutaScript =  split-path $SCRIPT:MyInvocation.MyCommand.Path -parent
$cRutaFicheroLog   =  $cRutaScript + "\LOG\"
$cRutaFicheroParam =   $cRutaScript + "\CONFIG\"

$cRtdoOk = '<return xmlns="urn:schemas-microsoft-com:xml-analysis"><root xmlns="urn:schemas-microsoft-com:xml-analysis:empty"></root></return>'
$cBKdoOk = ''


########################################################################
# FUNCIONES DEL SCRIPT
########################################################################


#┌─────────────────────────────────────────┐
#│Funciones para escribir en el Log        │
#└─────────────────────────────────────────┘
    Function LogWrite
    # Ecribe en el log el string que se pasa como parámetro y el valor de DateTime
    {
     Param ([string]$logString)
     $vLog = $cRutaFicheroLog + $pFicheroLog

     $vFecha = Get-Date -format "yyyy-MM-dd HH:mm:ss"
     $logString = $vFecha + " AST-TABULAR " + $logString
   
        if ($Debugging -eq 1)
        {
            echo $logString
        }

        Add-content $vLog -value $logString
   
    }

    Function LogWriteSeparador
    # Ecribe en el log una línea de separación
    {
     $vLog = $cRutaFicheroLog + $pFicheroLog
     Add-content $vLog -value "---------------------------------------------------------------------------------------------------------------------------------"
    }

#┌────────────────────────────────────────────────────────────────────────┐
#│ Función para salir del script y devolver un código a Control-M         │
#│ o al proceso desde el que se invoca el script.                         │
#│ El valor devuelto es un parámetro que recibe la función                │
#└────────────────────────────────────────────────────────────────────────┘
    function ExitScriptCodigo
    {
        Param ($ExitCode)
   

        if ($Debugging -eq 1)
        {
            break
        }
        else
        {
            $Host.SetShouldExit($ExitCode)
            Exit
        }
    }
#┌────────────────────────────────────────────────────────────────────────┐
#│ Función que importa el script contenido en un fichero en una variable  │
#│ y sustituye las variables ProcServer y ModelID por los valores del     │
#│ archivo de configuración.                                              │
#│ Devuelve una cadena con el comando listo para ejecutar                 │
#└────────────────────────────────────────────────────────────────────────┘
    function GetCommandFromFile
    {

        Param($FileProcess)
        Try
        {

            #importa el script a ejecutar a una variable para poder cambiar las variables
            $SyncQuery = Get-Content -Path $FileProcess
            $SyncQuery = $SyncQuery.Replace("ProcServer", $ConfigParam.Get_Item("ProcServer").Trim())
            $SyncQuery = $SyncQuery.Replace("ModelID", $ConfigParam.Get_Item("ModelID").Trim())
            return [String]$SyncQuery
        }
        Catch
        {
         LogWrite "ERROR leyendo el comando del archivo XMLA.Salida del script con código 1"
         ExitScriptCodigo(1)
        }

    }

#┌───────────────────────────────────────────────────────────────────┐
#│ Función que importa el script que se usa para generar un BACKUP   │
#│ de la base de datos                                               │
#└───────────────────────────────────────────────────────────────────┘
    function GetCommandFromFileBackup
    {
   
        Param($FileProcess)

        Try
        {
 
      #componemos el backup file
      $rutaBckp = $ConfigParam.Get_Item("BackupFile").Trim() + "\" + $ConfigParam.Get_Item("ModelID").Trim() + ".ABF"
            #importa el script a ejecutar a una variable para poder cambiar las variables
            $BackupQuery = Get-Content -Path $FileProcess
            $BackupQuery = $BackupQuery.Replace("ModelID", $ConfigParam.Get_Item("ModelID").Trim())
      $BackupQuery = $BackupQuery.Replace("BackupFile", $rutaBckp)
 
            return [String]$BackupQuery
        }
        Catch
        {
         LogWrite "ERROR leyendo el comando del archivo BackupFile del script con código 2"
         ExitScriptCodigo(2)
        }

    }
#┌───────────────────────────────────────────────────────────────────┐
#│ Función que importa la query que se usará para crear nuevas       │
#│ particiones y la devuelve como resultado                          │
#└───────────────────────────────────────────────────────────────────┘
    function GetPartitionQuery
    {
  Param($FileQuery)
   
       
        Try
        {
      #importa la query en una variable y la devuelve como salida
  
   
            $Fichero = ($cRutaFicheroParam + $FileQuery.Trim())
           
            $QueryParticion = Get-Content -Path $Fichero

            return [String]$QueryParticion
        }
        Catch
        {
         LogWrite "ERROR leyendo el comando del archivo QueryParticion del script con código 3"
         ExitScriptCodigo(3)
        }

    }

#┌─────────────────────────────────────────────────┐
#│ Función que crea un backup de la base de datos  │
#└─────────────────────────────────────────────────┘
    function CreateBackUp
    {
        Try
        {
         LogWrite "INICIO Backup SSAS Tabular --> $db"
         $pathBackup = $cRutaFicheroParam + $ConfigParam.Get_Item("BackupQuery").Trim()
         $BackupCommand = GetCommandFromFileBackup ($pathBackup)

         Invoke-ASCmd -Server $svr -OutVariable vSalida -Query $BackupCommand

                if ($cRtdoOk -eq $vSalida)
             {
                 LogWrite "FIN    Backup SSAS Tabular --> $db"
             }
                else
             {
           LogWrite "ERROR    backup modelo tabular ($db). Salida con código 4"
           ExitScriptCodigo(4)
          }
        }
        Catch
        {
         $ErrorMessage = $_.Exception.Message
         InterpretaError($ErrorMessage.ToString())
        }
    }


#┌──────────────────────────────────────────────────────────────┐
#│ Funcion que procesa las tablas no particionadas del tabular  │
#└──────────────────────────────────────────────────────────────┘
    function ProcesaNoParticionadas
    {
    Try
     {
 
      $ServerName          = $ConfigParam.Get_Item("ProcServer").Trim()
      $DatabaseName        = $ConfigParam.Get_Item("ModelID").Trim()
            $ProcessType         = $ConfigParam.Get_Item("TipoProcesado").Trim()
   $CubeName            = $ConfigParam.Get_Item("CubeName").Trim()
      #[Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")
     
            [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices") >$NULL
      $server = New-Object Microsoft.AnalysisServices.Server
      $server.connect($ServerName)
      $db = $server.Databases.Item($DatabaseName)
  
   $DBTabla = @() #array
      $DBTabla = $ConfigParam.Get_Item("TablasNoParticion").ToString().Split(";").Trim()
   
 
      LogWrite "PROCESO               : INICIO PROCESADO ***********************"

      ForEach ($T in $DBTabla)
            {
                #if ($DBTabla.Name -ne $Particionada)
                #{
                    $cube = $db.Cubes.GetByName($CubeName)
     $measureGroup = $cube.MeasureGroups.GetByName($T)
     LogWrite ("PROCESO               : INICIO PROCESADO {0}" -f $measureGroup.Name)
                    $measureGroup.Process($ProcessType)
                    LogWrite ("PROCESO               : FIN PROCESADO {0}" -f $measureGroup.Name)
                #}
            }

      LogWrite "PROCESO               : FIN    PROCESADO ***********************"
  
        }
    Catch
     {
     LogWrite "ERROR 5 - Error procesando"
     LogWrite $_.Exception.Message
     ExitScriptCodigo(5)
     }
    }


function CheckListLog
{


   $ServerName           = $ConfigParam.Get_Item("ProcServer").Trim()
   $DatabaseName         = $ConfigParam.Get_Item("ModelID").Trim()

  ## Add the AMO namespace
  $loadInfo = [Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")

  $server = New-Object Microsoft.AnalysisServices.Server
  $server.connect($ServerName)
  if ($server.name -eq $null) {
   LogWrite ("Server '{0}' not found" -f $ServerName)
   break
  }

  $db = $server.Databases.Item($DatabaseName)
 
   LogWrite ( "Database: {0}; Status: {1}; Size: {2}MB" -f $db.Name, $db.State, ($db.EstimatedSize/1024/1024).ToString("#,##0") )

  
   foreach ($cube in $db.Cubes)
   {
    LogWrite ( " Modelo : {0}" -f $Cube.Name )
   
   foreach ($mg in $cube.MeasureGroups)
   {
    LogWrite ( "  Tabla : {0}; Status: {1}" -f $mg.Name.PadRight(35), $mg.State)
 
   }

   }
}
 


#┌────────────────────────────────────────────────────────────────┐
#│ Función que devuelve si el error en el procesado               │
#│ es un problema en el origen o un problema de la plataforma     │
#└────────────────────────────────────────────────────────────────┘
    function InterpretaError
    {
     param ([string]$Mens)
     Try {
 
      $DBError = @() #array con los posibles errores relacionados con BBDD
      $DBError = $ConfigParam.Get_Item("DBError").ToString().Split(";").Trim()

      $flag = 0
      foreach ($texto in $DBError) {
       If ($Mens.Contains($texto)) {
        LogWrite $texto
        $flag = 1
        break; 
       }
       else {
        $flag = 0
       }
      }
      If ($flag -eq 1) {
       LogWrite "ERROR en procesado del modelo tabular en el origen de datos. Salida del script con código 8 ($Mens)"
       write-host $Mens
       ExitScriptCodigo(8)
      }
      else {
       LogWrite "ERROR en procesado del modelo tabular. Salida del script con código 9 ($Mens)"
       ExitScriptCodigo(9)
      }
     }
     Catch {
      LogWrite "ERROR interpretando el error de procesado. Salida del script con código 10"
      ExitScriptCodigo(10)
     }

    }

########################################################################
# INICIO DEL SCRIPT
########################################################################

write-host "................en ejecución...."
# Marcar inicio de script en Fichero de Log
# Se captura posible error en parámetro de log o en escritura del mismo
########################################################################
Try
 {
 LogWriteSeparador
 LogWrite "INICIO -> Script Procesado Microsoft SSAS Tabular Model"
 LogWrite "Fichero Parámetros    :  $pFicheroParam "
 LogWrite "Fichero Log           :  $pFicheroLog "
 }
Catch
 {
 LogWrite "ERROR en parámetro de Log o en fichero de Log. Salida del script con código 11"
 ExitScriptCodigo(11)
 }

# Lectura de parámetros del fichero
# IMPORTANTE:  Se lee un par parámetro valor en una tabla hash
# En el fichero los pares parámetro valor tienen que estar separados por un =
########################################################################
$vFile =  $cRutaFicheroParam.Trim() + $pFicheroParam.Trim()

$ConfigParam = @{} # Tabla de hash para guardar los pares Parametro - Valor
Try
 {
 LogWrite "INICIO Lectura Fichero Parámetros"
 Get-Content $vFile | foreach {
  $line = $_.split("=")
  $ConfigParam.($line[0].Trim()) = $line[1].Trim()
  }
 }
Catch
 {
 LogWrite "ERROR en lectura de fichero de parámetros. Salida del script con código 12"
 ExitScriptCodigo(12)
 }

# carga en variables algunos valores de la configuración con los que se
# necesita iterar posteriormente:
# - servidores "query"
########################################################################
$QueryServers = @() # array con los servidores de query en los que desplegar el cubo
$QueryServers = $ConfigParam.Get_Item("QueryServers").ToString().Split(";").Trim()
$QueryStorage = @() # array con los directorios de almacenamiento
$QueryStorage = $ConfigParam.Get_Item("QueryStorage").ToString().Split(";").Trim()
# Importación de módulos con los cmdlets de Analysis Services      
########################################################################

Try
 {
 LogWrite "INICIO Carga Módulos Analysis Services"

 Import-Module -DisableNameChecking sqlascmdlets
 Import-Module -DisableNameChecking SQLPS
 }
Catch
 {
 LogWrite "ERROR en importación de módulos (cmdlets) Analysis Services. Salida del script con código 13"
 ExitScriptCodigo(13)
 }

# CONEXIÓN CON EL SERVIDOR DE PROCESADO
########################################################################
Try
{
    ## load the AMO and XML assemblies into the current runspace
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices") > $null
    [System.Reflection.Assembly]::LoadWithPartialName("System.Xml") > $null


 LogWrite "INICIO Conexión Servidor de Procesado"
 # se conecta con el servidor de procesado
    $svr = new-Object Microsoft.AnalysisServices.Server
    $Err = New-Object Microsoft.AnalysisServices.ErrorConfiguration
    $svr.Connect($ConfigParam.Get_Item("ProcServer").Trim())
}
Catch
{   
 LogWrite "ERROR en conexión con el servidor de procesado ($svr). Salida del script con código 14"
 ExitScriptCodigo(14)
}

###################################################################################
# INICIO DE LAS ACCIONES (PROCESADO DE DIMENSIONES, PARTICIONES, SINCRONIZADO...)      
###################################################################################
Try
{
   
    # obtiene la bbdd a procesar
 $db = $svr.Databases.Item($ConfigParam.Get_Item("ModelID").Trim())

    #Crea un backup de la BBDD
    CreateBackUp

    # procesado del cubo
 LogWrite "INICIO Procesado SSAS Tabular --> $db"
   

 $TipoProcesado       = $ConfigParam.Get_Item("TipoProcesado").Trim()

   
 


 LogWrite "INICIO Procesado $TipoProcesado SSAS Tabular --> $db"
 $db.Process($TipoProcesado)
 LogWrite "FIN    Procesado $TipoProcesado SSAS Tabular --> $db"

 CheckListLog

 LogWrite "FIN    Procesado SSAS Tabular --> $db"
 


}
Catch
{
 $ErrorMessage = $_.Exception.Message
 InterpretaError($ErrorMessage.ToString())
}


LogWrite "FIN    -> Script Procesado Microsoft SSAS Tabular Model -> Estado [OK]"
$svr.Disconnect()
break





Ahora crearemos la siguiente árbol de carpetas:

c:\Script
c:\Script\Config
c:\Script\Log


En la ruta c:\Script dejaremos el archivo ProcesaTabular.ps1, y crearemos un fichero .bat con la siguiente información:

cls
powershell -command .\
ProcesaTabular.ps1 Config.Param Log.out

Una vez guardado el .bat en la ruta c:\Script nos vamos a la carpeta c:\Script\Config y creamos los siguientes archivos:

Config.Param

El fichero estará compuesto por la siguiente información:

ProcServer     = <NOMBRE DEL SERVIDOR>
ModelID        = <Nombre de la BBDD a Procesar>
SyncQUERY      = TabularSync.xmla
RestoreQUERY   = RestoreSync.xmla
BackupQuery    = BackupSync.xmla
BackupFile     = <RUTA DE BACKUP>
DBError   = Error de OLE DB u ODBC;ORA-;SQLSTATE;[DB2/AIX64];valor duplicado
CubeName       = <Nombre del Cubo>
TipoProcesado  = ProcessFull

Simplemente hay que sustituir  los parámetros en color verde por los adecuados.


BackupSync.xmla

Con la siguiente información:

<Backup xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">
  <Object>
    <DatabaseID>ModelID</DatabaseID>
  </Object>
  <File>BackupFile</File>
  <AllowOverwrite>true</AllowOverwrite>
</Backup>


RestoreSync.xmla

Con la siguiente información:

<Restore xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">
  <File>BackupFile</File>
  <DatabaseName>ModelID</DatabaseName>
  <AllowOverwrite>true</AllowOverwrite>
</Restore>



Una vez realizado esto simplemente ejecutaríamos el .bat y el modelo se procesaría.





Comentarios

Entradas populares de este blog

Backup y Recuperación en Frío (ORACLE)

Uso de la función RANKX (DAX)