Thursday, March 16, 2023

"Invalid private key file" exception while connecting SFTP server.

 I got a requirement to upload a file to SFTP. 

  • But while connecting SFTP they are using a private key file with a passphrase (password).
  • The same file I have used from the WinSCP application. It is connected and working.
  • But the same file I have triggered from C#. Then that time I got "Invalid private key file" exception error.
To resolve this we need to convert that private key from putty format to SSH Format.

Open WinSCP application. Under tools select "Run Puttygen".




Load the private key in WinSCP.
        



After loading the private key. In the conversions select the "Export OpenSSH key".





The newly generated key we can use in our code.
    var privateKey = new PrivateKeyFile(@"C:\some\path\key.pem", "passphrase");
    var client = new SftpClient("example.com", "username", new[] { privateKey });
    client.Connect();

If the private key is encrypted:
    var privateKey = new PrivateKeyFile(@"C:\some\path\key.pem", "passphrase");

Keep Daxing!!



Open table browser using x++

 Open table browser using x++.


If we want to open the table browser we need to open visual studio, right-click on the table and select the option like the below screenshot.




My functional required one form and in that he expected one field. That field lookup contains all table lists.


So I have created one form and added a field and button.



Lookup method:

    public void lookup()
    {
	Query                   query;
	QueryBuildDataSource    qbds;
	SysTableLookup          lookup;
	
	super();

	query 	= new Query();
	qbds 	= query.addDataSource(tableNum(SqlDictionary));
	lookup 	= SysTableLookup::newParameters(tableNum(SqlDictionary), this);
	
	qbds.clearRanges();
	qbds.addRange(fieldNum(SqlDictionary, fieldId)).value('0');
	
	lookup.addLookupfield(fieldNum(SqlDictionary, Name));
	lookup.addLookupfield(fieldNum(SqlDictionary, SqlName));
	lookup.parmQuery(query);
	lookup.performFormLookup();
    }


Clicked method:

    public void clicked()
    {           
	SysTableBrowser sysTableBrowser;            

	str tName;

	super();
	
	tName = TableName.valueStr();
	
	if (!tName)
	{
	    throw error(strfmt("@ApplicationFoundation:SysTableBrowser_MissingTableName"));
	}

	sysTableBrowser = new SysTableBrowser();
	sysTableBrowser.parmTableName(tName);
	sysTableBrowser.run(tablename2id(tName));
    }

Keep Daxing!!









Tuesday, March 14, 2023

Get Resource file path using x++

 Get the Resource file path using x++.


    ResourceNode resourceNode;
    FilePath filePath;
	
    resourceNode = SysResource::getResourceNode('Resource Name');
	
    if (resourceNode)
    {
	resourceNode.AOTload();
		
	filePath = SysResource::saveToTempFile(resourceNode);
    }
    Info(filePath);


Keep Daxing!!

Export XML using x++


 Export/Create XML using x++.

Output :



Here I have covered multiple scenarios.

    1. Adding a Sub tag like Name1 and Name2 under Main.

    2. Adding Header with child.

    3. Multiple child nodes are added in a header node. (PayemntList ->Payment ->Credit)

    4. Assign a value to Node.

    5. Creating Blank Tag. (SummaryChild2)


Code:

public void createXML()
{
	XmlDocument doc;
	XmlElement mainNode;
	XmlElement headerNode;
	XmlElement summaryNode;
	XmlElement oneChild;
	XmlElement secondChild;
	XmlElement SummaryChil1;
	XmlElement SummaryChil2;
	XmlElement paymentList;
	XmlElement payment;
	XmlElement credit;
	XmlElement dedit;
	XmlElement deditAmount;
	XmlElement creditAmount;

	doc = XmlDocument::newBlank();
	mainNode = doc.createElement('Main');
	mainNode.setAttribute('Name2','Test 123');
	mainNode.setAttribute('Name1','Test 456');
	doc.appendChild(mainNode);

	headerNode = doc.createElement('Header');
	mainNode.appendChild(headerNode);

	oneChild = doc.createElement('Child1');
	oneChild.appendChild(doc.createTextNode('child value'));
	headerNode.appendChild(oneChild);


	secondChild = doc.createElement('child2');
	secondChild.appendChild(doc.createTextNode('AccountName'));
	headerNode.appendChild(secondChild);

	summaryNode = doc.createElement('Summary');
	mainNode.appendChild(summaryNode);

	SummaryChil1 = doc.createElement('SummaryChild1');
	SummaryChil1.appendChild(doc.createTextNode('sum value'));
	summaryNode.appendChild(SummaryChil1);

	SummaryChil2 = doc.createElement('SummaryChild2');
	summaryNode.appendChild(SummaryChil2);
        
        // paymentList Node
	paymentList = doc.createElement('paymentList');
	mainNode.appendChild(paymentList);

	payment = doc.createElement('payment');
	paymentList.appendChild(payment);

	credit = doc.createElement('credit');
	payment.appendChild(credit);

	creditAmount = doc.createElement('creditAmount');
	creditAmount.appendChild(doc.createTextNode('100.00'));
	credit.appendChild(creditAmount);

	dedit = doc.createElement('dedit');
	payment.appendChild(dedit);

	deditAmount = doc.createElement('deditAmount');
	deditAmount.appendChild(doc.createTextNode('200.00'));
	dedit.appendChild(deditAmount);

      Info(doc.toString());

       // Save File to Local folder
       #define.filename(@'D:\AnyNew.xml')
     doc.save(#filename);       // Download file       // Way - 1       if (!System.String::IsNullOrEmpty(xml))       {
    System.IO.StreamWriter streamWriter;     System.IO.Stream stream;     stream = new System.IO.MemoryStream();     streamWriter = new System.IO.StreamWriter(stream);     streamWriter.write(xml);     streamWriter.Flush();     stream.Seek(0, System.IO.SeekOrigin::Begin);     File::SendFileToUser(stream, 'TestXML.xml'); }         // Way - 2         System.Byte[] reportBytes = new System.Byte[0](); System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding(); reportBytes = enc.GetBytes(doc.outerXml()); System.IO.Stream stream = new System.IO.MemoryStream(reportBytes);
File::SendFileToUser(stream, 'TestXML.xml');
}



Keep Daxing!!

Thursday, March 9, 2023

Connect SFTP server using x++


 Connect SFTP server using x++.


For this, we need to create a C# project and we need to write code in C#. After that DLL file, I need to add it as a reference for my D365 project.

We need to download Renci.ssh.Net from the NuGet package.

We can download SSH from the below link:

https://github.com/sshnet/SSH.NET/releases/tag/2020.0.1

or

https://www.dllme.com./dll/files/renci_sshnet

Please follow this blog for Renci. Link


C# Code:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Renci.SshNet;
namespace SFTPConnect
{
    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);
				
		--------------or-------------------------
		List<AuthenticationMethod> 	methods = new List<AuthenticationMethod>
		{
		    new PasswordAuthenticationMethod(username, password)
		};

		var connectionInfo = new ConnectionInfo(host, port, username, methods.ToArray());
						
		this.sftpClient = new SftpClient(connectionInfo);
            }
 
            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;
        }
	public void upLoadFile(SftpClient _SftpClient, System.IO.Stream _sourceFile, string _fileName)
	{
	    _sourceFile.Position = 0;
	    _SftpClient.BufferSize = 8 * 1024;
	    _SftpClient.UploadFile(_sourceFile, _fileName);
	}
    }
}


X++ Code:

    public void sFTPConnection()
    {	
        str                       	sftpFile;
        ClrObject                 	list = new ClrObject("System.Collections.Generic.List`1[System.String]");
        SFTPConnect.sftpConnection	sftp = new SFTPConnect.sftpConnection();

        using (var sftpConnection = sftp.OpenSFTPConnection(host, port, username, password)) // Connect
        {
            try
            {
                int totalFiles = 0;
				
                sftpConnection.ChangeDirectory('/Upload');//Import Path

                list  = (sftp.GetDirectories(sftpConnection, '/Upload/'));// Files List
ClrObject enumerator = list.getEnumerator(); while (enumerator.movenext()) { totalFiles ++; sftpFile = enumerator.get_Current(); if(sftpFile != ".." && sftpFile !=null && sftpFile != ".") {         System.IO.Stream Stream = sftp.DownloadFile(sftpConnection, '/Upload' + '/'+ sftpFile);
this.ReadCSVFile(Stream,sftpFile); sftp.MoveFile(sftpConnection, 'Import Path'+ '/'+sftpFile, 'destination path'+ '/'+ sftpFile, false); } } }     catch     { throw error(infolog.text());     } finally { if(sftpConnection.IsConnected) sftpConnection.Disconnect(); } } }          public boolean ReadCSVFile(System.IO.Stream stream, str fileName)     { AsciiStreamIo file; container record; file = AsciiStreamIo::constructForRead(stream); if (file) { if (file.status()) {              throw error("@SYS52680"); } file.inFieldDelimiter(','); file.inRecordDelimiter('\r\n'); while (!file.status()) {         record = file.read(); recordCount++; if (conLen(record) && recordCount !=1) {     conPeek(record,2)); } } } return true;
}  


Tested in Console Application:

    To test in the console application we need to add the class library project as a reference.

    We can debug using a console application.

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

namespace TestSFTPConnection
{
    class Program
    {
        static void Main(string[] args)
        {
            SFTPTest.SFTPConnect sftp = new SFTPTest.SFTPConnect();

            using (var sftpConnection = sftp.OpenSFTPConnection("Host", 22, "userName", "UserPassword"))
            {
                //try
                {
                    sftpConnection.ChangeDirectory("/uploads");// folder name

                    List<string> list = sftp.GetDirectories(sftpConnection, "/uploads/");
                    List<string>.Enumerator enumerator = list.GetEnumerator();
                    while (enumerator.MoveNext())
                    {
                        string sftpFile = enumerator.Current;

                        if (sftpFile != ".." && sftpFile != null && sftpFile != ".")
                        {
                            System.IO.Stream Stream = sftp.DownloadFile(sftpConnection, "/uploads" + '/' + sftpFile);
                        }
                    }
                }
            }
        }

    }
}


Reference Link:

https://dynamicsax4u.wordpress.com/2020/08/18/read-files-from-sftp-server-and-write-data-in-ax365-part-2/


Keep Daxing!!

ODATA Filters


ODATA Filters.


GET :

URL/data/CustomersV3?$filter=CustAccount eq '56'&dataAreaId eq 'DAT'&cross-company=true
URL/data/CustomersV3?cross-company=true


Patch or PUT:

URL/data/CustomersV3(dataAreaId='DAT',CustomerAccount='77')?Cross-company=true



Using Enums:

Microsoft.Dynamics.DataEntities.Gender'Male'

Microsoft.Dynamics.DataEntities.NoYes'Yes'

            Using in filter: 

        URL/CustomersV3?\$filter=PersonGender eq Microsoft.Dynamics.DataEntities.Gender'Male'

        URL/data/Currencies?\$filter=ReferenceCurrencyForTriangulation eq Microsoft.Dynamics.DataEntities.NoYes'No'

            Using to update:

        URL/data/CustomersV3(dataAreaId='usmf',CustomerAccount='1001', 
                                PersonGender= Microsoft.Dynamics.DataEntities.Gender'Male')?Cross-company=true


Keep Daxing!! 

No resources were found when selecting for update. In ODATA

 While updating the customer I am getting the below error :

"No resources were found when selecting for update"

So I have made the changes URL.

URL/data/CustomersV3(dataAreaId='DAT',CustomerAccount='77')?Cross-company=true

"More than one resource was found when selecting for update" for this also we can use the above URL.


Keep Daxing!! 


Create CSV file in D365FO using x++

 Create a CSV file in D365FO using x++.


	commaStreamIo       iO = commaStreamIo::constructForWrite();
	Filename            filename = "MyFile.csv";
	
	// Field List
	container header = ["Account Number",
			    "Name",
			    "Ph no"];
						
	iO.writeExp(header);
	header = conNull();

	while select myTable 
	{
	    container line =  [myTable.AccountNumber,
			        myTable.Name,
				myTable.PhNo];

	    iO.writeExp(line);
	}

	System.IO.Stream stream = iO.getStream();
	stream.Position = 0;

	System.IO.StreamReader reader = new System.IO.StreamReader(stream);

	str  csvFileContent = reader.ReadToEnd();

	File::SendStringAsFileToUser(csvFileContent,  filename);
	
	info(strFmt("CSV file %1 is created", filename));
		


Keep Daxing!!

Worker details using x++

 Worker details using x++


Worker Name, Department Name, Reports to:

HcmWorker hcmWorker;
HcmPosition hcmPosition;
HcmJob hcmJob;
HcmPositionWorkerAssignment hcmPositionWorkerAssignment;
HCmPositionHierarchy hcmPositionHierarchy;

select firstonly hcmWorker where hcmWorker.personnelNumber == ''; 
hcmWorker.name())); // worker Name
info(strfmt('Department Name :%1',hcmWorker.primaryDepartmentName(); // department name

select * from hcmPositionWorkerAssignment where hcmPositionWorkerAssignment.Worker == hcmWorker.RecId;
info(strfmt('Start date :%1', DateTimeUtil::date(hcmPositionWorkerAssignment.ValidFrom))); // Start date

select * from hcmPosition where hcmPosition.RecId == hcmPositionWorkerAssignment.Position;
info(strfmt('Position name :%1', hcmPositionDetail::find(hcmPosition.RecId).Description)); // Position Name

hcmJob = hcmJob::find(hcmPositionDetail::find(hcmPosition.RecId).Job);
info(strfmt('Job Level :%1', hcmJob.JobId)); // Job level

select * from hcmPositionHierarchy where hcmPositionHierarchy.Position == hcmPosition.RecId;

hcmPosition = HcmPosition::find(hcmPositionHierarchy.ParentPosition);

select * from hcmPositionWorkerAssignment where hcmPositionWorkerAssignment.Position == hcmPosition.RecId;

info(strfmt('Reports To :%1',HcmWorker::find(hcmPositionWorkerAssignment.Worker).name()));// Reports to

First name, Last name, Birth date:

	HcmWorker 		hcmWorker;
	DirPerson               dirPerson;
	DirPersonName           dirPersonName;
	HcmEmployment           hcmemployment;
	CompanyInfo             companyInfo;
	HcmPersonPrivateDetails hcmPersonPrivateDetails;
	
	select hcmemployment order by hcmemployment.legalEntity
		join hcmworker where hcmworker.RecId == hcmemployment.Worker
		join companyInfo
			where companyInfo.RecId == hcmemployment.legalEntity
			   && companyInfo.DataArea == '';
	
	// FirstName, LastName
	select firstonly FirstName, LastName from dirPersonName where dirPersonName.person == hcmworker.person;
	
	//BirthDate
	select firstonly BirthDate from hcmPersonPrivateDetails where hcmPersonPrivateDetails.person == hcmworker.person;
	
	//DimensionName
	DefaultDimensionView    defaultDimensionView;

	select firstonly DisplayValue from defaultDimensionView
	where defaultDimensionView.Name == 'dimensionName'
		&& defaultDimensionView.DefaultDimension == hcmemployment.defaultDimension;

	return defaultDimensionView.DisplayValue;



Keep Daxing!!




get current user worker in X++

 get current user worker in X++.


curUserId()

HcmWorkerLookup::currentWorker();
HcmWorker::userId2Worker(curUserId());
HcmWorker::worker2Name(HcmWorker::userId2Worker(curUserId()));


Keep Daxing!!


Monday, March 6, 2023

How to add a DLL file as a reference to the D365 project.

 How to add a DLL file as a reference to the D365 project.


1. Open visual studio and create a project using the class library.

 2. Give the project name and click next.

3. Just I am returning the message. You can add your code to your class based on your requirement.


4. After ReBuild the solution DLL file will be created in the below folder.

5. That DLL file we need to copy to our bin folder.

    AosService\PackagesLocalDirectory\MyModel\bin.


6. Create a project for d365FO. Right-click on references and add your DLL file.



6. I have created a job and called the DLL file.





To check in the DLL please follow the below process.

1. Open the source controller and navigate to the metadata node. Right-click on your model, and choose the '+ Add items to folder' option.



2. Choose the 'bin' folder and click 'Next.'

 


3. In the 'Excluded items,' select your DLL. Then, click 'Finish.'

4. This will add your DLL to the bin folder. During check-in, ensure to include it from the undo pending changes.




Keep daxing!!


CONNECT TO AN EXTERNAL SQL DATABASE USING X++

 CONNECT TO AN EXTERNAL SQL DATABASE USING X++.


using System.Data.SqlClient;
class sQLConnect
{
    public static void main(Args _args)
    {
        System.Exception            ex;

	//Fetching
        SqlConnection conn = new SqlConnection("Server='IP adress'; Database='Database name'; 
                            User Id='Id'; Password='Password'; Min Pool Size = 5; Max Pool Size=100;");
      
        str myField= 'Test123';
        
        str sqlL = strFmt("Select field1,format(DATEANDTIME,'dd/MM/yyyy HH:mm:ss') as DATEANDTIME,
                    field2  FROM MyTble WHERE MyTble.field1 = '" + myField + "' and MyTble.isTrue = 1");
      
        SqlCommand      command = new SqlCommand(sqlL, conn);

        System.Data.SqlClient.SqlDataReader reader;
        System.Data.SqlClient.SqlParameterCollection parameterCollection;
        new InteropPermission(InteropKind::ClrInterop ).assert();

        try
        {
            conn.Open();
            try
            {
                reader = command.ExecuteReader();
                while (reader.Read())
                {
                    Info(reader.get_Item('field1'));
                    Info(reader.get_Item('DATEANDTIME'));
                    Info(reader.get_Item('field2'));
                }
                reader.Dispose();
            }
            catch
            {
                reader.Dispose();
            }
        }
        catch
        {
            ex = CLRInterop::getLastException().GetBaseException();
            error(ex.get_Message());
        }

	--------------------------------------------------------------------------------------
		
        System.Exception            ex;

        //Inserting
	SqlConnection conn = new SqlConnection("Server='IP adress'; Database='Database name'; 
                        User Id='Id'; Password='Password'; Min Pool Size = 5; Max Pool Size=100;");
        try
        {
           
            str insert = "INSERT INTO [MyTble](field1,DATEANDTIME,field2) VALUES";

	    insert = insert + "('"+field1+"','"+DATEANDTIME+"','"+field2+"')";

            SqlCommand insCmd = new SqlCommand(insert, conn);

            conn.Open();
			
	    // for update
            //update="UPDATE MyTble SET field2 = '" + newvalue + "' WHERE field1 = '" + field1 + "'";
            //updateCmd.CommandText=update;
            insCmd.ExecuteNonQuery();
        }
        catch
        {
            ex = CLRInterop::getLastException().GetBaseException();
            error(ex.get_Message());
        }
    }

}


Ref : Link

Keep Daxing!!

Thursday, March 2, 2023

Send a file to SFTP Server using x++

Send a file to SFTP Server using x++

For this, we need to create a DLL file that we will call in our x++.

Please follow the below link for the DLL file creation and add that file as a reference for the x++ project.

How to add a DLL file as a reference to the D365 project

In the C# class, we need to use ssh Extention and for that, we need to follow the below process.

  • Right-click on the C# solution and select the manage NuGet package.

        

  •         Right-click on the setting button and add the below URL

                    https://api.nuget.org/v3/index.json 

        



  • Search Renci in browse and install the below extensions.
        

 X++ code:

public void sendFileToSFTP(System.IO.Stream 	sourceStream)
{
    try
    {
        str FileName = 'TestFile.csv';
         // 'Destination file path' ->  /MainFolder/SubFolder
str success = ExportSFTP.SFTPConnect::uploadSFTPFile('Host URL','User Name', 'Pasword', sourceStream, 'Destination File Path','PortNum', FileName); if(success == 'pass') {     Info(strFmt('%1 File successfuly sent to SFTP', sftpFileName)); } else {     Error('Error in sending file to SFTP due to incorrect Host/Port/User Credential'); }     }     catch     { Info(infolog.text());     } }



C# Code:
using System;
using System.IO;
using System.Net;
using System.Collections.Generic;
using Renci.SshNet;
using System.Text;

namespace ExportSFTP
{
    public class SFTPConnect
    {
	public static string uploadSFTPFile(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);// 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)) {     sftpclient.Connect();     sftpclient.ChangeDirectory(destinationPath.Trim());     sourceFile.Position = 0;     sftpclient.BufferSize = 8 * 1024;     sftpclient.UploadFile(sourceFile, fileName); } successStr = 'Pass';     }     catch (WebException e)     { successStr = 'Fail';     }             return successStr; }     } }

Reference Link:     

https://axmriganka.wordpress.com/2020/04/07/sending-csv-text-files-to-sftp-using-x-c-code-in-dynamics-365-finance-operations-ax/


We can download SSH from the below link:

https://github.com/sshnet/SSH.NET/releases/tag/2020.0.1

or

https://www.dllme.com./dll/files/renci_sshnet


Keep Daxing!!


CrossCompany and changeCompany using x++.

 CrossCompany and changeCompany using x++.


 CrossCompany:

    Required company filter:

    MyTable 	myTable; 
    container 	conCompanies = ['cmp1', 'cmp2', 'cmp3'];

    while select crossCompany : conCompanies * from myTable
    {
	// code
    }

    All company filters:

    MyTable 	myTable; 

    while select crossCompany * from myTable
    {
        changeCompany(myTable.dataAreaId)
     {
            // code             myTable.update() or myTable.delete() or myTable.insert();         }     }

  

  Applying On Dynamic queries:

    Query 			query = new Query();
    QueryRun 			queryRun;
    QueryBuildDataSource 	qBDS;

    qBDS = query.addDataSource(TableNum(CustTable));
	
    // Way-1 (we can add required company)
    query.allowCrossCompany(true);
    query.addCompanyRange('cmp1');
    query.addCompanyRange('cmp2');

    // Way-2 (It will loop all companies)
    queryRun = new QueryRun(query);
    queryRun.allowCrossCompany(true);


Change Company:

    MyTable 	myTable; 
    changeCompany('cmp1') { while select myTable { // code } }


Ref : Link


Keep Daxing!!