# Desarrollo en Medulla

# Cómo ampliar Medulla

<div id="bkmrk-first-of-all%2C-there-">En primer lugar, hay algunas cosas importantes que hay que tener en cuenta al desarrollar en torno al agente:  
  
El agente de la máquina se actualiza automáticamente cuando el código no es idéntico entre el agente y el código base del agente que se encuentra en el servidor. Para desactivar este comportamiento, es necesario añadir la siguiente configuración a agentconf.ini y reiniciar el agente:</div>```
[updateagent]
updating = 0
```

<div id="bkmrk-">  
</div><div id="bkmrk-the-plugins-are-also">Los complementos también se actualizan automáticamente si ha cambiado la versión. Si es necesario modificar un complemento, no actualice la versión hasta que se hayan realizado todas las pruebas.  
  
Hay cuatro tipos de complementos para el agente de máquina: complementos de inicio, complementos de actualización, complementos de acción y complementos programados.</div>1. Los complementos de inicio son aquellos que se ejecutan cuando se inicia el agente y se definen en start\_machine.ini;
2. Los complementos de actualización se utilizan para instalar o actualizar componentes externos utilizados por el agente;
3. Los complementos de acción son invocados por una acción recibida por el agente;
4. Los complementos programados son aquellos que se invocan a una hora o intervalo específicos.

<div id="bkmrk-each-plugin-can-have">  
Cada plugin puede tener su propio archivo de configuración con el mismo nombre que el plugin y debe añadirse al siguiente parámetro en agentconf.ini para que se cargue la configuración:</div>```
[plugin]
pluginlist = xxxxxxx, yyyyyyy
```

<div id="bkmrk-the-scheduled-plugin">  
Los complementos programados tienen su propia programación definida en el complemento, en el parámetro SCHEDULE. Sin embargo, esto se puede anular en el archivo manage_scheduler_machine.ini  
<figure class="attachment attachment--content attachment--horizontal-rule">---

</figure>  
  
Hay tres formas de ampliar Medulla:</div>1. interacción con el agente de la máquina a través de un socket TCP
2. interacción con el agente de la máquina a través de tuberías con nombre
3. complementos de acción del agente de máquina
4. complementos programados del agente de máquina

<div id="bkmrk--1"><figure class="attachment attachment--content attachment--horizontal-rule">---

</figure></div>##### Cómo interactuar con el agente a través de un socket TCP

<div id="bkmrk-a-new-action-needs-t">  
Es necesario definir una nueva acción en**server\_kiosk.py**, en la función denominada**handle\_client\_connection**, en la condición </div>```
if 'action' in result:
```

<div id="bkmrk-and-this-action-adde">y esta acción debe añadirse al mensaje JSON que se envía a través del socket TCP.   
A continuación se muestra un ejemplo del mensaje enviado:</div>```
{
    "action": "myNewAction", 
    "sessionid": "mysessionid", 
    "base64": false, 
    "data": {
        "date": "2020-06-24T15:45:02.000Z", 
        "family1": {
            "field1": "value1", 
            "field2": "value2"
        }, 
        "family2": {
            "field1": "value1", 
            "field2": "value2"
        }
    }
}
```

<div id="bkmrk-the-above-content-is">El contenido anterior debe guardarse en un archivo llamado**json\_file**para enviarlo a través de un socket TCP o añadirse a una variable llamada**json\_message**en tu código para enviarlo a través de tuberías con nombre  
  
Y su equivalente en la función**handle\_client\_connection**de la clase**manage\_kiosk\_message**.  
En la siguiente sección:</div>```
try:
    _result = json.loads(minifyjsonstringrecv(msg))
```

<div id="bkmrk-add">Añade</div>```
if _result['action'] == "myNewAction":
    substitute_recv = self.objectxmpp.sub_monitoring
    logging.getLogger().warning("send to %s to %s" % (_result,substitute_recv ))
    self.objectxmpp.send_message(mbody=json.dumps(_result),
                                 mto=substitute_recv,
                                 mtype='chat')
    return
```

<div id="bkmrk-and-in-the-following">Y en el siguiente bucle:</div>```
if 'action' in result:
    if result['action'] == "kioskinterface":
...
```

<div id="bkmrk-add-1">Añadir</div>```
elif result['action'] == "myNewAction":
    datasend['action'] = "myNewSubstituteAction"
    subs_recv = self.objectxmpp.sub_monitoring
    datasend['sessionid'] = getRandomName(6, "mynewsubstituteaction")
    datasend['data'] = result['data']
```

<div id="bkmrk-the-above-example-wi">  
El ejemplo anterior enviará a su vez el mensaje al JID de sub_monitoring con una nueva acción que debe llevarse a cabo: myNewSubstituteAction  
  
El puerto TCP de escucha se define en**el parámetrokiosk/am\_local\_portdel archivo agentconf.ini**. El valor predeterminado es 8765.  
  
A continuación se muestra un ejemplo de emisor TCP escrito en Python para el siguiente paso, que es el envío real de los datos al socket TCP: </div>```
#!/usr/bin/env python
# -*- coding: utf-8; -*-
#
# (c) 2023 siveo, http://www.siveo.net
#
# Este archivo forma parte de Medulla, http://www.siveo.net
#
# Pulse 2 es software libre; puede redistribuirlo y/o modificarlo
# bajo los términos de la Licencia Pública General de GNU tal y como la publica
# la Free Software Foundation; ya sea la versión 2 de la Licencia, o
# (a su elección) cualquier versión posterior.
#
# Pulse 2 se distribuye con la esperanza de que sea útil,
# pero SIN NINGUNA GARANTÍA; ni siquiera la garantía implícita de
# COMERCIABILIDAD o IDONEIDAD PARA UN FIN DETERMINADO.  Consulte la
# Licencia Pública General de GNU para obtener más detalles.
#
# Debería haber recibido una copia de la Licencia Pública General de GNU
# junto con Medulla; si no es así, escriba a la Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, EE. UU.
# archivo clientTCPcli.py

# Ejecute python clientTCPcli.py  -p ./file.json en la máquina cliente para 
# inyectar los datos

from optparse import OptionParser

import socket
import sys
import os
import select
def send_message(message, host, port, timeout_in_seconds = 5):

    # Crear un socket TCP/IP
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # Conectar el socket al puerto en el que el servidor está a la escucha


    try:

        server_address = (host, port)
        print >>sys.stderr, 'conectando a %s puerto %s' % server_address
        sock.connect(server_address)
    except socket.error , msgerror:
        print 'Error al vincular en la interfaz de comando ' + host + ' puerto ' + str(port) + ' Código de error: ' + str(msgerror[0]) + ' Mensaje: ' + msgerror[1] + '\n'
        sys.exit(str(msgerror[0]))
    try:
        # Enviar datos
        print >>sys.stderr, 'enviando "%s"' % mensaje
        sock.sendall(mensaje)

        # Buscar la respuesta
        cantidad_recibida = 0
        cantidad_esperada = len(mensaje)
        ready = select.select([sock], [], [], timeout_in_seconds)
        data = ""
        if ready[0]:
            data = sock.recv(4096)
            return 0, data
        return -1,""
    finally:
        print >>sys.stderr, 'cerrando socket'
        sock.close()


if __name__ == '__main__':
    optp = OptionParser()
    #optp.add_option("-h", "--help",action="store_true",
                        #dest="help", default=False,
                                #help="host")

    optp.add_option("-H", "--host", action="store_true",
                        dest="host", default="localhost",
                                help="host")

    optp.add_option("-P", "--port",
                        dest="port", default=8765,
                        help="port connection")

    optp.add_option("-T", "--timeout",
                        dest="timeout_in_seconds", default=2,
                            help="Tiempo de espera de recepción en segundos")

    optp.add_option("-m", "--msg",
                        dest="msg", default = "",
                            help="mensaje para enviar al servidor TCP")

    optp.add_option("-p", "--pathfile",
                        dest="pathfile", default = None,
                            help="archivo de contenido para enviar al servidor TCP")

    opts, args = optp.parse_args()
    #if opts.help:
        #print "uso del comando"
        #os._exit(0)
    message = ""
    if opts.pathfile is None:
        if opts.msg =="":
            print "opción faltante < -m | -p> "
            sys.exit(-1)
        message = opts.msg
    else:
       if os.path.exists(opts.pathfile):
            with open(opts.pathfile, 'r') as f:
                message =  f.read()

    if message !="":
        code, msg = send_message(message, opts.host, opts.port, opts.timeout_in_seconds)
    print "código de error %s, respuesta del servidor %s"%(code,msg)
```

<div id="bkmrk-to-send-the-message%3A">  
Para enviar el mensaje: </div>```
python clientTCPcli.py -p <archivo_json>
```

<div id="bkmrk--2">  
<figure class="attachment attachment--content attachment--horizontal-rule">---

</figure></div>##### Cómo interactuar con el agente a través de tuberías con nombre

<div id="bkmrk-the-way-to-interact-">  
La forma de interactuar con el agente a través de tuberías con nombre es la misma que para interactuar con el agente a través de un socket TCP, excepto en lo que respecta al envío del mensaje.  
  
Para enviar el mensaje a la tubería con nombre, aquí hay un código de ejemplo escrito en Python:</div>```
import win32file

def send_message(json_message):
    fileHandle = win32file.CreateFile("\\\\.\\pipe\\interfacechang",
                                      win32file.GENERIC_READ | win32file.GENERIC_WRITE,
                                      0,
                                      None,
                                      win32file.OPEN_EXISTING,
                                      0,
                                      None)
    win32file.WriteFile(fileHandle, json_message)
    win32file.CloseHandle(fileHandle)
```

<div id="bkmrk--3">  
<figure class="attachment attachment--content attachment--horizontal-rule">---

</figure></div>##### Cómo escribir complementos de acción para el agente

<div id="bkmrk-below-is-a-template-">  
A continuación se muestra una plantilla que se puede utilizar para escribir complementos de acción:</div>```
import logging
import json

plugin = {"VERSION": "1.0", "NAME": "mynewaction",  "TYPE": "machine"}

logger = logging.getLogger()

def action( objectxmpp, action, sessionid, data, message, dataerror):
    logger.debug("###################################################")
    logger.debug("llamada %s desde %s id de sesión %s" % (plugin, message['from'], sessionid))
    logger.debug("###################################################")
    datasend = {"action" : "myNewSubstituteAction",
                "data" : data,
                "sessionid": sessionid,
                "ret": 0,
                "base64": False
                }
    objectxmpp.send_message(mto=objectxmpp.sub_monitoring,
                            mbody=json.dumps(datasend),
                            mtype='chat')
```

<div id="bkmrk-please-note-the-foll">  
Ten en cuenta lo siguiente: </div>- NAME debe coincidir con el nombre del archivo del complemento. En este caso, el archivo se llamará plugin\_mynewaction.py
- TYPE debe definirse como machine, relayserver o all, dependiendo de su destino
- La función de acción será el código que se ejecutará por defecto. El ejemplo anterior enviará a su vez el mensaje al JID sub\_monitoring con una nueva acción que se llevará a cabo: myNewSubstituteAction

<div id="bkmrk--4"><figure class="attachment attachment--content attachment--horizontal-rule">---

</figure></div>##### Cómo escribir complementos programados para el agente

<div id="bkmrk-below-is-a-template--1">  
A continuación se muestra una plantilla que se puede utilizar para escribir complementos programados:</div>```
import logging
import json
import os
import ConfigParser
from pulse_xmpp_agent.lib.agentconffile import directoryconffile
from pulse_xmpp_agent.lib.utils import file_put_contents

plugin = {"VERSION": "1.0", "NAME": "scheduling_mynewscheduledaction", "TYPE": "machine", "SCHEDULED" : True}

SCHEDULE = {"schedule" : "*/15 * * * *", "nb" : -1}

logger = logging.getLogger()

def schedule_main(xmppobject):
    logger.debug("===================================================")
    logger.debug(plugin)
    logger.debug("===================================================")
    if xmppobject.num_call_scheduling_mynewscheduledaction == 0:
        __read_conf(xmppobject)
        
    if xmppobject.config.mynewscheduledaction_enable:
        data = {}
        data['family1'] = {}
        data['family1']['field1'] = "value1"
        data['family1']['field2'] = "value2"
        data['family2'] = {}
        data['family2']['field1'] = "value1"
        data['family2']['field2'] = "value2"
    
    if xmppobject.config.mynewscheduledaction_forward:
        datasend = {"action" : "myNewSubstituteAction",
                    "data" : data,
                    "sessionid": "mysessionid",
                    "base64": False
                    }
        objectxmpp.send_message(mto=objectxmpp.sub_monitoring,
                                mbody=json.dumps(datasend),
                                mtype='chat')

def __read_conf(xmppobject):
    """
        Lee la configuración del complemento
    """
    configfilename = os.path.join(directoryconffile(), "%s.ini" % plugin['NAME'])
    logger.debug("Leyendo la configuración en el archivo %s" % configfilename)
    
    #parámetros predeterminados
    xmppobject.config.mynewscheduledaction_enable = True
    xmppobject.config.mynewscheduledaction_forward = False
    
    if not os.path.isfile(configfilename):
        logger.warning("Falta el archivo de configuración %s del complemento %s" % (plugin['NAME'], configfilename))
        logger.warning("El archivo de configuración que falta se creará automáticamente.")
        file_put_contents(configfilename,
                          "[mynewscheduledaction]\n" \
                          "enable = 1\n" \
                          "forward = 0\n")
                            
    # Cargar la configuración desde el archivo
    Config = ConfigParser.ConfigParser()
    Config.read(nombre_archivo_conf)
    if os.path.exists(nombre_archivo_conf + ".local"):
        Config.read(nombre_archivo_conf + ".local")
    if Config.has_section("mynewscheduledaction"):
        if Config.has_option("mynewscheduledaction", "enable"):
            xmppobject.config.mynewscheduledaction_enable = Config.getboolean('mynewscheduledaction','enable')
        if Config.has_option("mynewscheduledaction", "forward"):
            xmppobject.config.mynewscheduledaction_forward = Config.getboolean('mynewscheduledaction','forward')
```

<div id="bkmrk-please-note-the-foll-1">Ten en cuenta lo siguiente: </div>- NAME debe coincidir con el nombre del archivo del complemento. En este caso, el archivo se llamará plugin\_mynewaction.py
- TYPE debe definirse como machine, relayserver o all, dependiendo de su destino
- La notación SCHEDULE es similar a la notación cron. El parámetro adicional nb define cuántas veces debe ejecutarse el complemento. Si es -1, se ejecutará indefinidamente
- La función schedule\_main será el código que se ejecutará por defecto. El ejemplo anterior leerá un archivo de configuración o lo creará si no existe, y enviará un mensaje al JID sub\_monitoring con una nueva acción que se debe llevar a cabo: myNewSubstituteAction

# Cómo utilizar la API XML-RPC de Medulla

<section class="push_double--top" id="bkmrk-this-will-guide-the-"><div class="formatted_content formatted_content--large"><div>Esto guiará al usuario en el uso de la API XML-RPC de Medulla: [Medulla's XML-RPC API.php](https://docs.medulla-tech.io/books/development-on-medulla/page/medullas-xml-rpc-apiphp "Medulla's XML-RPC API.php")  
  
</div></div>##### 1: Configuración

<div class="formatted_content formatted_content--large"><div>Compruebe lo siguiente:</div>- Certificado SSL: Obtenga el certificado del servidor de Medulla:[http://&lt;medulla\_server&gt;/downloads/medulla-ca-chain.cert.pem](/downloads/medulla-ca-chain.cert.pem)
- Instala el certificado en el servidor que va a consultar la API 
    - Para sistemas basados en Debian, copie el archivo a*/usr/local/share/ca-certificates*y cambie su extensión a crt; a continuación, importe el certificado ejecutando el comando*update-ca-certificates*
    - Para sistemas basados en RedHat, copie el archivo a*/etc/pki/ca-trust/source/anchors*y cambie su extensión a crt; a continuación, importe el certificado ejecutando el comando*update-ca-trust extract*
- Si es necesario, añada la IP y el nombre de host del servidor XML-RPC a /etc/hosts

<div>  
</div></div>##### 2: Uso de la API

<div class="formatted_content formatted_content--large"><div>Primero autentique al usuario y obtenga la cookie de sesión:</div></div>```
/**
 * Ejecuta una solicitud XML-RPC.
 *
 * @param string $method El nombre del método XML-RPC que se va a llamar.
 * @param array $params Los parámetros que se van a pasar al método.
 * @param bool $includeCookie Indica si la cookie de sesión debe incluirse en la solicitud.
 * @return array Un array que contiene los encabezados HTTP y el cuerpo de la respuesta.
 * @throws <em>Exception</em> Si se produce un error al conectarse o al enviar la solicitud.
 */
function executeRequest($method, $params, $includeCookie = false) {
    $agentInfo = $_SESSION["XMLRPC_agent"];
    $requestXml = xmlrpc_encode_request($method, $params, ['output_type' => 'php', 'verbosity' => 'pretty', 'encoding' => 'UTF-8']);

    // Definimos los encabezados HTTP
    $url = "/";
    $httpQuery  = "POST " . $url . " HTTP/1.0\r\n";
    $httpQuery .= "User-Agent: MMC web interface\r\n";
    $httpQuery .= "Host: " . $agentInfo["host"] . ":" . $agentInfo["port"] . "\r\n";
    $httpQuery .= "Content-Type: text/xml\r\n";
    $httpQuery .= "Content-Length: " . strlen($requestXml) . "\r\n";
    // Se añade la cookie si es necesario
    if ($includeCookie) {
        $httpQuery .= "Cookie: " . $_SESSION['cookie'] . "\r\n";
    }
    $httpQuery .= "Authorization: Basic " . base64_encode($agentInfo["login"] . ":" . $agentInfo["password"]) . "\r\n\r\n";
    $httpQuery .= $requestXml;

    // Configurar el contexto SSL
    // 'allow_self_signed' se establece en false para aceptar solo certificados firmados por una autoridad de certificación reconocida
    // 'verify_peer' se establece en true para verificar el certificado SSL del servidor
    $context = stream_context_create();
    $proto = $agentInfo["scheme"] == "https" ? "ssl://" : "";
    if ($proto) {
        stream_context_set_option($context, "ssl", "allow_self_signed", false);
        stream_context_set_option($context, "ssl", "verify_peer", true);
    }

    // Abrimos la conexión con el servidor
    $socket = stream_socket_client($proto . $agentInfo["host"] . ":" . $agentInfo["port"], $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
    if (!$socket) {
        throw new <em>Exception</em>("No se puede conectar al servidor XML-RPC: $errstr ($errno)");
    }

    // Se añade un tiempo de espera de 60 segundos para la lectura
    stream_set_timeout($socket, 60);

    if (!fwrite($socket, $httpQuery)) {
        throw new <em>Exception</em>("No se pueden enviar datos al servidor XML-RPC");
    }

    $responseXml = '';
    while (!feof($socket)) {
        $ret = fgets($socket, 128);
        $responseXml .= $ret;
    }
    fclose($socket);

    // Separa los encabezados HTTP del cuerpo de la respuesta
    list($headers, $body) = explode("\r\n\r\n", $responseXml, 2);
    return [$headers, $body];
}

/**
 * Autentifica al usuario y recupera la cookie de sesión.
 * @param string $method El nombre del método XML-RPC que se va a llamar.
 * @param array $params Los parámetros que se van a pasar al método.
 * @return string La cookie de sesión.
 * @throws <em>Exception</em> Si se produce un error durante la autenticación.
 */
function authenticateAndGetCookie($method, $params) {
    list($headers, $body) = executeRequest($method, $params);

    // Análisis de los encabezados HTTP para extraer la cookie de sesión
    $headers_array = array();
    $header_lines = explode("\r\n", $headers);
    foreach ($header_lines as $header) {
        $parts = explode(': ', $header, 2);
        if (count($parts) == 2) {
            $headers_array[$parts[0]] = $parts[1];
        }
    }

    // Uso de los encabezados analizados
    if (isset($headers_array['Set-Cookie'])) {
        $cookie = $headers_array['Set-Cookie'];
        $_SESSION['cookie'] = $cookie;
    } else {
        throw new <em>Exception</em>('Autenticación fallida, no se ha recibido ninguna cookie');
    }

    return $cookie;
}
```

<div class="formatted_content formatted_content--large"><div>  
</div></div>##### 3: Ejemplos de solicitudes

<div class="formatted_content formatted_content--large">- Obtener la lista de todos los paquetes

</div>```
/**
 * Envía una solicitud XML-RPC y devuelve la respuesta solo si se ha autenticado mediante cookie.
 * @param string $method El nombre del método XML-RPC que se va a llamar.
 * @param array $params Los parámetros que se van a pasar al método.
 * @return array La respuesta XML-RPC.
 * @throws <em>Exception</em> Si se produce un error al enviar la solicitud.
 */
function sendXmlRpcRequest($method, $params) {
    list($headers, $body) = executeRequest($method, $params, true);
    $responseXml = substr($body, strpos($body, '<?xml'));
    $response = xmlrpc_decode($responseXml, 'UTF-8');
    if (is_array($response) && xmlrpc_is_fault($response)) {
        throw new <em>Exception</em>("Error XML-RPC: {$response['faultString']} ({$response['faultCode']})");
    }
    return $response;
}

    $method = "pkgs.get_all_packages";
    $params = [
        'root', // inicio de sesión
        false,  // sharing_activated
        0,      // inicio
        10,      // fin
        [
            'filter' => 'hostname', // nombre de ejemplo del paquete
        ] // ctx
    ];

    try {
        $responseXml = sendXmlRpcRequest($method, $params);

    } catch (<em>Exception</em> $e) {
        echo "Error: " . $e->getMessage();
    }
```

<div class="formatted_content formatted_content--large"><div>*Consejos: Para obtener el UUID del paquete: $respuestaXml\['datas'\]\['uuid'\]\[$key\]*   
</div>- Obtener la lista de todas las máquinas

</div>```
$method = "xmppmaster.get_machines_list";
$params = [
     0, // inicio
     20, // fin
     [
        'filter' => '',
        'field' => 'allchamp',
        'computerpresence' => 'presence', // Definir si se quieren las máquinas presentes
        // 'computerpresence' => 'no_presence', // Definir si se quieren las máquinas no presentes
        'location' => "UUID0", // glpi_id - entidad
    ] // ctx
];

try {
    $responseXml = sendXmlRpcRequest($method, $params);
} catch (<em>Exception</em> $e) {
    echo "Error: " . $e->getMessage();
}
```

<div class="formatted_content formatted_content--large"><div>  
</div>- Obtener los detalles de una máquina

</div>```
$method = "glpi.getLastMachineInventoryPart";
$params = [
     $uuid,
     'Summary',
      0, // minbound
      0, // maxbound
      "", // filter
      [
           "hide_win_updates" => false,
           "history_delta" => false
       ], // opciones
];

try {
    $responseXml = sendXmlRpcRequest($method, $params);
} catch (<em>Exception</em> $e) {
    echo "Error: " . $e->getMessage();
}
```

<div class="formatted_content formatted_content--large"><div>*Consejos: Para obtener el UUID de la máquina: $reponseXml\['data'\]\['uuid\_inventorymachine'\]\[$key\]. Esto también se corresponde con el ID de máquina de GLPI precedido por el UUID*   
</div>- Implementar un paquete específico en una máquina de destino

</div>```
$method = "msc.add_command_api";
$pid = "96982fce-hostname_vq918j7wtnwzm610by"; // pid - id_del_paquete
$target = "UUID1"; // target - uuid_de_la_máquina

$params = [
    $pid,  // pid - id_del_paquete
    $target,  // target - uuid_de_la_máquina
    array(  // parámetros
    "name" => "devdemo-win-1",
    "hostname" => "devdemo-win-1",
    "uuid" => $target,
    "gid" => NULL,
    "from" => "base|computers|msctabs|tablogs",
    "pid" => $pid,
    // "title" => "TÍTULO DE LA IMPLEMENTACIÓN",
    "create_directory" => "on",
    "start_script" => "on",
    "clean_on_success" => "on",
    "do_reboot" => "",
    "do_wol" => "",
    "do_inventory" => "on",
    "next_connection_delay" => "60",
    "max_connection_attempt" => "3",
    "maxbw" => "0",
    "deployment_intervals" => "",
    "tab" => "tablaunch",
    "issue_halt_to" => array(),
    ),
    "push",  // modo
    NULL,  // gid
    array(),  // proxy 
    0  // cmd_type
];

    try {
        $responseXml = sendXmlRpcRequest($method, $params);

        $commandId = $responseXml; // Recuperar el ID del comando

        $method2 = "xmppmaster.addlogincommand";
        $params2 = [
            'root', // inicio de sesión
            $commandId, // ID del comando
            '', // ID del grupo
            '', // n.º de máquinas en el grupo
            '', // número de máquinas para ejecución
            '', // fecha y hora de ejecución
            '', // paquete de parámetros
            0, // reinicio requerido
            0, // apagado requerido
            0, // ancho de banda
            0, // sincronización
            [] // parámetros
        ];

        try {
            $responseXml2 = sendXmlRpcRequest($method2, $params2);

        } catch (<em>Exception</em> $e) {
            echo "Error: " . $e->getMessage();
        }

    } catch (<em>Exception</em> $e) {
        echo "Error: " . $e->getMessage();
}
```

<div class="formatted_content formatted_content--large"><div>  
</div>- Obtener registros de implementación (Audit) de sessionname

</div>```
$method = "xmppmaster.getlinelogssession";
$params = [
    "command63bb5ee8fc834eae89" // nombre de sesión
];

try {
    $responseXml = sendXmlRpcRequest($method, $params);
} catch (<em>Exception</em> $e) {
    echo "Error: " . $e->getMessage();
}
```

<div class="formatted_content formatted_content--large"><div>*Consejos: Para obtener el nombre de sesión de la implementación: $reponseXml\['tabdeploy'\]*['sessionid'][$key]  
  
</div>- Obtener todos los registros de implementación por usuario y/o destino

</div>```
$method = "xmppmaster.get_deploy_by_user_with_interval";
$params = [
    "root",       // usuario de inicio de sesión
    "",           // estado de implementación
    86400,        // intervalo de búsqueda
    0,            // inicio de paginación
    "20",         // fin de paginación
    "spo-win-1",  // filtro - nombre_de_host_de_la_máquina
    "command"     // tipo_de_implementación
];

try {
    $responseXml = sendXmlRpcRequest($method, $params);
} catch (<em>Exception</em> $e) {
    echo "Error: " . $e->getMessage();
}
```

</section><div class="push--top u-hide-on-template" id="bkmrk-new-boost"><div class="boosts boosts--small metadata push--bottom boosts--empty"><div class="boost-form__link-container">[<span class="a-for-screen-reader">Nuevo impulso</span>](https://3.basecamp.com/4759565/buckets/18027007/boosts/new?boost%5Bboostable_gid%5D=Z2lkOi8vYmMzL1JlY29yZGluZy84NDYzMTQ5NjAx)</div></div></div>