Sunday, December 9, 2012

Dynamics AX SFTP

Dynamics AX and .NET do not natively support SFTP.  The following approach is one way around that.

We use PuTTY (psftp.exe), which is freely available.

PuTTY download page
http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html

PuTTY psftp.exe documentation
http://the.earth.li/~sgtatham/putty/0.53b/htmldoc/Chapter6.html

We call psftp.exe using command line parameters and, in more complicated cases, a script file written in psftp's scripting language.

Psftp.exe is called from a batch file (.bat) which enables us to respond to prompts that psftp generates and also provide a basic level of error checking.

I did not yet add code to detect that psftp.exe is available in the working directory or the PATH environment variable. If it is not available, the example code will appear to run and give no indication that it failed.

So, here's the code with more specific in-code notes/documentation:

//NDP 12/9/2012 - example job code to connect via SFTP (SSH File Transfer Protocol, aka Secure FTP) and transfer a file from Microsoft Dynamics AX 2012
static void uploadFileWithPSFTP(Args _args)
{
    TextIo                  textIoFile;
    Set                     permissionSet;
    FileName                filePath, filename, batchFileName, scriptFileName, errorThrownFile, errorThrownFileWithPath;
    InteropPermission       interopPerm;

    System.Diagnostics.Process              process;
    System.Diagnostics.ProcessStartInfo     processStartInfo;

    ;
    filePath = 'c:\\Export';                                        //local working folder
    fileName = 'fileToSend.txt';                                    //file existing in local working folder that is to be sent via SFTP

    errorThrownFile = 'ftpError.txt';                               //file used to detect exit codes from psftp
    errorThrownFileWithPath = filePath + '\\' + errorThrownFile;
    batchFileName = filePath + '\\sendfile.bat';                    //batch file used to call psftp.exe
    scriptFileName = filePath + '\\psftp.script';                   //script file used to instruct psftp to follow scripted steps.

    //delete file batch and script file if they exists
    interopPerm = new InteropPermission(InteropKind::ClrInterop);
    interopPerm.assert();

    if(System.IO.File::Exists(batchFileName))
    {
        System.IO.File::Delete(batchFileName);
    }
    if(System.IO.File::Exists(scriptFileName))
    {
        System.IO.File::Delete(scriptFileName);
    }

    CodeAccessPermission::revertAssert();

    //write the batch file
    permissionSet = new Set(Types::Class);
    permissionSet.add(new FileIoPermission(batchFileName, "W"));
    CodeAccessPermission::assertMultiple(permissionSet);

    textIoFile = new TextIo(batchFileName , "W", 0);

    CodeAccessPermission::revertAssert();

    //the 'echo n|' in the line below will answer 'no' to the question that psftp will ask to trust/store the certificate from the ftp server.
    textIoFile.write(strFmt('echo n|psftp ftpaddress.com -P 23 -l username -pw password -b %1',scriptFileName));

    //this creates an empty file if the exit code/errorlevel from psftp is 1 (meaning there was an error)
    textIoFile.write(strFmt('IF ERRORLEVEL 1 echo. 2>%1',errorThrownFile));

    textIoFile = null;

    //write the script file that psftp uses
    permissionSet = new Set(Types::Class);
    permissionSet.add(new FileIoPermission(scriptFileName, "W"));
    CodeAccessPermission::assertMultiple(permissionSet);

    textIoFile = new TextIo(scriptFileName , "W", 0);

    CodeAccessPermission::revertAssert();

    textIoFile.write('cd /subfolder');               //change folders on the FTP server, optional
    textIoFile.write(strFmt('put %1',fileName));     //send the file
    textIoFile.write('quit');

    textIoFile = null;

    //run the batch file
    new InteropPermission(InteropKind::ClrInterop).assert();

    process = new System.Diagnostics.Process();
    processStartInfo = new System.Diagnostics.ProcessStartInfo();
    processStartInfo.set_FileName(batchFileName);
    processStartInfo.set_WorkingDirectory(filePath);
    process.set_StartInfo(processStartInfo);

    process.Start();
    process.WaitForExit();

    //process.get_ExitCode() does not seem to be implemented in AX 2012.  Instead we're using the 'errorthrownfile' method above.

    CodeAccessPermission::revertAssert();

    interopPerm = new InteropPermission(InteropKind::ClrInterop);
    interopPerm.assert();

    //delete the files that were used to facilitate the transfer
    if(System.IO.File::Exists(batchFileName))
    {
        System.IO.File::Delete(batchFileName);
    }
    if(System.IO.File::Exists(scriptFileName))
    {
        System.IO.File::Delete(scriptFileName);
    }

    //determinte if an error was thrown by psftp.  Clean up the error indicator file.
    if(System.IO.File::Exists(errorThrownFileWithPath))
    {
        warning ("Error occured during SFTP transfer");
        System.IO.File::Delete(errorThrownFileWithPath);
    }
    else
    {
        info("SFTP transfer was successful");
    }

    //delete the file that was sent - you might not want to do this in your application
    if(System.IO.File::Exists(fileName))
    {
        System.IO.File::Delete(fileName);
    }

    CodeAccessPermission::revertAssert();
}


Have fun!

2 comments:

  1. Hi Sir,

    Please give me the steps to do It in Ax 4.0

    Regards,
    Johnkrish

    ReplyDelete