Chiamiamo da C# un programma IBM-i nativo tramite WinSCP/SSH

Chiamare un programma nativo IBM-i (o AS400) da Windows fuori dall’emulatore e con soluzioni automatizzate è sempre stato un incubo: Molti nel tempo hanno sviluppato soluzioni basate sulla DLL di Client Access cwbx.dll, che purtroppo ad oggi è considerata deprecata. Molti hanno preferito usare OleDB con moltissimi limiti (ad esempio non è possibile passare parametri). Altri temerari hanno intrapreso invece lo sviluppo di WS appositi o l’uso di JT400 (ed a loro va tutto il mio rispetto!).

C’è un altro metodo, molto semplice e performante per far iteragire Windows ed il nostro monolite preferito: WinSCP ed SSH!

Perchè chiamare un programma IBM al di fuori del terminale?

In sistemi informativi complessi spesso è necessario far interfacciare componenti Legacy con applicazioni windows.
Orchestrare una serie di programmi/iterazioni complesse, schedulare applicazioni in catene e far comunicare applicazioni differenti è una delle sfide giornaliere che devono affrontare i i system integrator e gli architect solutions.

Prerequisiti

Sul sistema target deve essere presente SSH (il componente è 5733-SC1 Licensed Program Product) configurato e funzionante. Una credenziale d’accesso ed un collegamento aperto sulla porta.

WinSCP e .NET

Molti di voi conoscono WinSCP come un software molto utile per fare file-transfer come alternativa di Filezilla ma in pochi sanno che nella cartella del programma c’è una magnifica DLL, winscpnet.dll che può essere referenziata in un progetto .NET.
Di setuito, un elenco di esempi presi dal sito ufficiale:
Documentazione .net: https://winscp.net/eng/docs/library

Esempio di chiamata in C#

Di seguito il POC da cui sono partito. Io per semplificarmi le cose ho messo parte delle configurazioni in un config file:

using System;
using WinSCP;
using System.Configuration;    
using System.Xml;
using System.Xml.Serialization;
class Programma

{
    public static int Main()
    {
        string server = ConfigurationManager.AppSettings["server"];
        string SshHostKeyFingerprint = ConfigurationManager.AppSettings["SshHostKeyFingerprint"];
        string UserName = ConfigurationManager.AppSettings["UserName"];
        string password = ConfigurationManager.AppSettings["password"];
        string SshPrivateKey = ConfigurationManager.AppSettings["SshPrivateKey"];
        string SshPrivateKeyPassphrase = ConfigurationManager.AppSettings["SshPrivateKeyPassphrase"];
        string LogPath = ConfigurationManager.AppSettings["LogPath"];

        try
        {
            // Setup session options
            SessionOptions sessionOptions = new SessionOptions
            {
                Protocol = Protocol.Sftp,
                HostName = server,
                UserName = UserName,
                //Password = password,
                SshPrivateKeyPath = SshPrivateKey,
                PrivateKeyPassphrase = SshPrivateKeyPassphrase,
                SshHostKeyFingerprint = SshHostKeyFingerprint
            };

            using (Session session = new Session())
            {
                // Connect
                session.SessionLogPath = LogPath;

                session.Open(sessionOptions);

                string CommandAS400 = string.Format(@"system ""CALL PGM(LIBRERIA/PROGRAMMA) PARM('PARAMETRO')""");
                string output;
                output = session.ExecuteCommand(CommandAS400).Output.ToString();
            }

            return 0;
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: {0}", e);
            return 1;
        }
    }
}

Analizziamo il programma nelle componenti su cui prestare attenzione:
La sezione di SessionOptions raccoglie tutte le informazioni di connessione:

Protocol: Protocol.Sftp Questo è un valore fisso
HostName: Nome del server o il suo IP
UserName: Login AS400
Password: Password AS400
SshHostKeyFingerprint: Chiave pubblica del server

In alternativa alla password è possibile usare una chiave privata SSH:

SshPrivateKeyPath: Posizione del file PPK contenente la chiave privata
PrivateKeyPassphrase: Eventuale password della chiave privata

il blocco successivo contiene la chiamata:

il comando va costruito così:

system ""CALL PGM(LIBRERIA/PGM) PARM('PARM')""

La chiamata sfrutta il comando QSH System che permette di chiamare da console POSIX i comandi e programmi di sistema della CLI nativa del sistema operativo.
Invece tramite questo statement:

output = session.ExecuteCommand(CommandAS400).Output.ToString();

Permette di intercettare l’output del programma.
– Se il chiamato è un CLP, può essere usato il comando SNDPGMMSG  MSG(&MSG1)
– Se il chiamato è uno script SH, viene wrappato lo STDout

Usando questo statement poi

session.SessionLogPath = LogPath;

è possibile loggare in maniera molto dettagliata il log di connessione, utile per fare eventuale troubleshooting.

Conclusione

La soluzione si presta per molti scenari, personalmente la uso per chiamare programmi da SSIS o per gestire schedulazioni centralizzate ed orchestrate.

Chiamare da un CL IBMi uno script SH e ricevere un output da esso

CLP (Control Language Program) è un linguaggio di scripting eccezionalmente potente che permette di automatizzare operazioni su IBM-i (conosciuto anche come AS400).

Seppur potente, non copre proprio tutto tutto, e spesso va affiancato ad altri strumenti quali script SH *nix-like, che permette a chi viene da sistemi Linux o Unix di poter creare cose molto interessanti.

Qui di seguito un esempio di programma che intercetta l’output di uno script SH che può essere utilizzato come variabile all’interno di un programma CLP:

PREMESSA: sulla macchina deve essere presente 5770-SS1 – Portable Application Solutions Environment.

Per intercettare il STDOUT va creata una variabile di sistema tramite il comando:

ADDENVVAR ENVVAR(QIBM_QSH_CMD_OUTPUT)  VALUE(STDOUT) LEVEL(*JOB)

Esempio di programma:

PGM                                               
DCLF       FILE(QTEMP/O1)                         
DLTF       FILE(QTEMP/O1)                         
MONMSG     MSGID(CPF2105)                         
CRTPF      FILE(QTEMP/O1) RCDLEN(1000)            
OVRDBF     FILE(STDOUT) TOFILE(QTEMP/O1)          
STRQSH     CMD('SH path dello script')
DLTOVR     FILE(*ALL)                             
RCVF                                              
MONMSG     MSGID(CPF0864)                         
SNDPGMMSG  MSG(&O1)  
ENDPGM                                            

Questo esempio:
– scrive sul file QTEMP/O1 tramite OVRDBF l’output dello script SH
– tramite RCVF del file QTEMP/O1 può essere letto il campo appena scritto come variabile &O1

Letteratura a supporto

https://www.ibm.com/docs/en/i/7.2?topic=reference-calling-qshell-commands-from-i-command-line

https://www.ibm.com/docs/en/i/7.3?topic=i-installing-pase