Connect to SFTP Server from D365 FO X++

 To connect to the SFTP server we required Renci.SSHnet dll should be available in our instanse. We can follow the below process to create the dll.

1. Open Visual studio 2019, then create a C# library project.

2.  Then right click on the project and click on Manage NuGet packages.


3. Now Click on the settings button appears right corner of the window. Then check for package Nuget.org. If it is not available you can create a new package with same name and add following link in the source.
 



Note: The package should be activated to select that in the next step.

4. Now select package and navigate to browse section enter Renci in search box and install below two packages.




5. Now your project will be looks like this




6. Add one class and create the below methods to establish the connection to SFTP.

using Renci.SshNet;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SFTPConnection
{
    public class sftpConnection
    {
        public SftpClient sftpClient;

        public SftpClient OpenSFTPConnection(string host, int port, string username, string password)
        {
            if (this.sftpClient == null)
            {
                this.sftpClient = new Renci.SshNet.SftpClient(host, port, username, password);
            }

            if (!this.sftpClient.IsConnected)
            {
                this.sftpClient.Connect();
            }

            return this.sftpClient;
        }

        public List<string> GetDirectories(SftpClient _SftpClient, string path)
        {
            return _SftpClient.ListDirectory(path)/*.Where(n => n.IsDirectory)*/.Select(n => n.Name).ToList();
        }

        public void MoveFile(SftpClient _SftpClient, string sourcePath, string destinationPath, bool isPosix)
        {
            _SftpClient.RenameFile(sourcePath, destinationPath, isPosix);
        }

        public Stream DownloadFile(SftpClient _SftpClient, string sourcePath)
        {
            var memoryStream = new MemoryStream();

            _SftpClient.DownloadFile(sourcePath, memoryStream);

            memoryStream.Position = 0;

            return memoryStream;
        }
    }
}
with the below code we can connect to the SFTP using SSH private key
public  SftpClient OpenSFTPConnection(string host,
                         string username,
                         string password,
                         System.IO.Stream sourceFile,
                         string destinationPath,
                         int port,
                         string fileName,
                         string privateKeyFilePath = "")
 {
     string successStr = "Fail";
     List<AuthenticationMethod> methods;

     /*It depends if the private key file is present for authentication. 
     If the SFTP is key secured then the private key file has to be passed.*/
     if (privateKeyFilePath != "")
     {
         var privateKeyFile = new PrivateKeyFile(privateKeyFilePath);// passPhrase - Password for key file
         methods = new List<AuthenticationMethod>
         {
             new PasswordAuthenticationMethod(username, password),
             new PrivateKeyAuthenticationMethod(username, privateKeyFile)
         };
     }
     else
     {
         methods = new List<AuthenticationMethod>
         {
             new PasswordAuthenticationMethod(username, password)
         };
     }

     try
     {
         var connectionInfo = new ConnectionInfo(host, port, username, methods.ToArray());

         using (SftpClient sftpclient = new SftpClient(connectionInfo))
         {
             if (this.sftpClient == null)
             {
                 this.sftpClient = sftpclient;
             }
             if (!this.sftpClient.IsConnected)
             {
                 this.sftpClient.Connect();
             }
         }

         successStr = "Pass";
     }
     catch (WebException e)
     {
         Console.WriteLine($"Error: {e.Message}");
     }

     return this.sftpClient;
 }

7. Now Build your project, the required DLL will be generated in bin folder of your project.
8. Now copy the Renci Dll and newly created SFTPConnection dll and paste that in your model bin folder.
9. Add the Dll to your FNO project reference by using add reference feature.
10. Now you can use the reference and establish connection to SFTP by using X++ code.

In additional, you can see how to connect to the FTP server in my previous blog here.

Thank you !!















Connect to the Azure blob storage to read, upload and delete files from blob using X++

 Below is the helper class to connect to the Azure blob storage to read, upload and delete the files from the blob. We required Azure connection string for that storage account and the storage container, and the proper file path should be specified in the parameters.



using Microsoft.Azure;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.File;
public class TestFileUploadToBlob
{
    #File
    #define.delimiterField(',')
    CloudStorageAccount                                 storageAccount;
    PersonnelIntegrationStorageAccountConnectionString  azureStorageKey;

    
    public void initilizedAzureConnection()
    {
        azureStorageKey     = VendParameters::find().StorageConnection;
        StorageAccount      = Microsoft.WindowsAzure.Storage.CloudStorageAccount::Parse(azureStorageKey);
    }

    public void moveFileToBlob(System.IO.Stream  _memStream, Filename _filename, Str _path)
    {
        #OCCRetryCount

        CloudBlobClient     blobClient;
        CloudBlobContainer  blobContainer;
        CloudBlockBlob      blockblob;
        str                 containerName, dirPath;
        VendParameters       vendparmeters = VendParameters::find();
        containerName = vendparmeters.PRO_StorageContainer;
        dirPath = _path;
        azureStorageKey = vendparmeters.StorageConnection;
        
        if(storageAccount && containerName)
        {
            blobClient      = storageAccount.CreateCloudBlobClient();
            blobContainer   = blobClient.GetContainerReference(containerName);

            blockblob       =   blobContainer.GetBlockBlobReference(dirPath  + _filename);
            
            if (blockBlob)
            {
                if (_memStream)
                {
                    _memStream.Position = 0;
                    blockBlob.UploadFromStream(_memStream,null,null,null);
                    blockBlob.FetchAttributes(null, null, null);
                }
            
            }
            else
            {
                throw error("error uploading file");
            }
        }
    }

    public container getFileFromBlob(str _dirpath)
    {
        System.IO.MemoryStream  memoryStream;
 
        CloudBlobClient         cloudBlobClient;
        CloudBlobContainer      cloudBlobContainer;
        CloudBlobDirectory      cbDir;
        container               fileNameCon, memoryStreamCon;
        str                     containerName, dirPath;
        VendParameters vendParameters = VendParameters::find(); 
        dirPath = _dirpath;
        containerName = vendParameters.StorageContainer;
        if (StorageAccount)
        {
            cloudBlobClient     = StorageAccount.CreateCloudBlobClient();
            cloudBlobContainer  = cloudBlobClient.GetContainerReference(containerName);
        
            //file path where files are stored
            cbDir = cloudBlobContainer.GetDirectoryReference(dirPath);
            System.Collections.IEnumerable lstbolbEnumarable = cbDir.ListBlobs(false,0,null,null);
            System.Collections.IEnumerator lstbolbEnumarator = lstbolbEnumarable.GetEnumerator();
        
            //loop through all the files from azure directory path
            while(lstbolbEnumarator.MoveNext())
            {
                IListBlobItem item = lstbolbEnumarator.Current;
                if(item is CloudBlockBlob)
                {
                    CloudBlockBlob blob = item;
                    container   filecon = str2con(blob.Name, '/');
                    str         filName = conPeek(filecon, conLen(filecon));
                
                    //stored only .csf file and their file name
                    //if (strScan(filName, '.csv', 1, strLen(filName)))
                    //{
                        memoryStream = new System.IO.MemoryStream();
                        blob.DownloadToStream(memoryStream, null, null, null);
                        fileNameCon     += [filName];
                        memoryStreamCon += [memoryStream];
                   // }
                }
            }
        }
        else
        {
            throw error("error reading file from blob");
        }

        return [memoryStreamCon, fileNameCon];
    }

    public void deleteFileFromBlob(Filename _filename, str _reversefilepath)
    {
 
        CloudBlobClient         cloudBlobClient;
        CloudBlobContainer      cloudBlobContainer;
        str                     containerName, dirPath;
        CloudBlockBlob          blockblob;
        
        VendParameters vendParameters = VendParameters::find();
        dirPath = _reversefilepath;
        containerName = vendParameters.StorageContainer;
        if (StorageAccount)
        {
            cloudBlobClient     = StorageAccount.CreateCloudBlobClient();
            cloudBlobContainer  = cloudBlobClient.GetContainerReference(containerName);
            //file path where files are stored
            blockblob       =   cloudBlobContainer.GetBlockBlobReference(dirPath + _filename);
            // delete file from blob if it exist
            if (blockBlob && blockBlob.Exists(null, null))
            {
                blockBlob.DeleteIfExistsAsync();
            }
        }
        else
        {
            throw error("error deleting file from blob");
        }
    }

}


Post Vendor payment journal and Ledger journal using X++

 1. X++ code to post Pending invoices


                    ttsbegin;

                    vendInvoiceInfoTablelog.updateExpectedValuesAndMatchStatus();

                    ttscommit;

                    ttsbegin;

                    purchFormLetter =                                        PurchFormLetter_invoice::newFromSavedInvoice(vendInvoiceInfoTablelog);

                    purchFormLetter.updatePurchParmTableNum(Invoicenumber);

                    purchFormLetter.Update(vendInvoiceInfoTablelog.purchTable(), Invoicenumber, today());

                    ttscommit;

2. Post ledger vend invoice journals.

   LedgerJournalCheckPost ledgerjournalcheckpost = LedgerJournalCheckPost::newLedgerJournalTable(ledgerJournalTable,NoYes::Yes);

                    if (ledgerjournalcheckpost.validate())

                    {

                        ledgerjournalcheckpost.run();

                    }


Thank you !!

X++ code to create default dimension in D365 fo

X++ code to create default dimension .  

In parameters pass the attribute names in conAttribute container and attribute values in attributeValue container in Sequentially.

public static DimensionDefault createDefaultDimension(container conAttribute,container attributeValue)

    {

        DimensionAttributeValueSetStorage   valueSetStorage                     = new DimensionAttributeValueSetStorage();

        DimensionDefault        result;

        DimensionAttribute      dimensionAttribute;

        DimensionAttributeValue dimensionAttributeValue;

        int                     i;

        container               conAttr = conAttribute;

        container               conValue = attributeValue;

        str                     dimValue;

          for (i = 1; i <= conLen(conAttr); i++)

        {

            dimensionAttribute = dimensionAttribute::findByName(conPeek(conAttr,i));

                  if (dimensionAttribute.RecId == 0)

            {

                continue;

            }

                  dimValue = conPeek(conValue,i);

                  if (dimValue != "")

            {

                // The last parameter is "true". A dimensionAttributeValue record will be created if not found.

                dimensionAttributeValue =

                   dimensionAttributeValue::findByDimensionAttributeAndValue(dimensionAttribute,dimValue,false,true);

                          // Add the dimensionAttibuteValue to the default dimension

                valueSetStorage.addItem(dimensionAttributeValue);

            }

        }

          result = valueSetStorage.save();

        return result;

    }

Thank you !!

Create ledger or offset ledger dimension in D365 fo X++

X++ code to create ledger/offset ledger dimension in D365 fo.

1. Pass the attribute values sequentially in a container as below. you can find these sequence from ledger setup in general ledger module.

          //  container offsetConLoc  =   [mainaccount,_businessunit,site, region, department,customersVendorNbr, project, employe, fixedasset];

            DimensionDynamicAccount    OffsetLedgerDimension  = this.generateLedgerDimension(offsetConLoc, _mainaccount);

2. create a new method to generate the recid of dimension values.

 public DimensionDynamicAccount   generateLedgerDimension(container    conData,   MainAccountNum  mainAccountNum)

    {

        int                                  hierarchyCount;

        int                                  hierarchyIdx;

        RecId                                dimAttId_MainAccount;

        LedgerRecId                          ledgerRecId;

        MainAccount                          mainAccount;

        RefRecId                             recordvalue;

        DimensionAttribute                   dimensionAttribute;

        DimensionAttributeValue              dimensionAttributeValue;

        DimensionSetSegmentName              DimensionSet;

        DimensionStorage                     dimStorage;

        LedgerAccountContract                LedgerAccountContract = new                                                                                                                                                          LedgerAccountContract();

        DimensionAttributeValueContract      ValueContract;

        List                                 valueContracts = new List(Types::Class);

        dimensionAttributeValueCombination   dimensionAttributeValueCombination;

 


        mainAccount     =  MainAccount::findByMainAccountId(_mainAccountNum);

        recordvalue     =  DimensionHierarchy::getAccountStructure(mainAccount.RecId,Ledger::current());

        hierarchyCount  =  DimensionHierarchy::getLevelCount(recordvalue);

        DimensionSet    =  DimensionHierarchyLevel::getDimensionHierarchyLevelNames(recordvalue);


        for(hierarchyIdx = 1;hierarchyIdx<=hierarchyCount;hierarchyIdx++)

        {

            if(hierarchyIdx == 1)

            {

                continue;

            }

            dimensionAttribute = DimensionAttribute::findByLocalizedName(DimensionSet[hierarchyIdx],false, #enus);


            if(dimensionAttribute)

            {

                dimensionAttributeValue =DimensionAttributeValue::findByDimensionAttributeAndValue(dimensionAttribute,conPeek(_conData,hierarchyIdx));


                if(dimensionAttributeValue)

                {

                    ValueContract = new DimensionAttributeValueContract();

                    ValueContract.parmName(dimensionAttribute.Name) ;

                    ValueContract.parmValue(dimensionAttributeValue.CachedDisplayValue);

                    valueContracts.addEnd(ValueContract);

                }

            }

        }

        LedgerAccountContract.parmMainAccount(_mainAccountNum);

        LedgerAccountContract.parmValues(valueContracts);

        dimStorage                          =    DimensionServiceProvider::buildDimensionStorageForLedgerAccount(LedgerAccountContract);

        dimensionAttributeValueCombination  =    DimensionAttributeValueCombination::find(dimStorage.save());

        ledgerRecId                         =    dimensionAttributeValueCombination.RecId;


        return ledgerRecId;

    }


Thank you !!