MSDyn365FO. How-to send PDF document to a printer from X++

SendPDFToPrinter.png

In AX 2012 it could be done with 2 lines of X++ code:

#WinAPI
WinApi::shellExecute(fileName,'', fileFolder, #ShellExePrint);

Now it’s not that easy. Files are in Azure BLOB storage instead of a folder, printers are in a local network that is not accessible from the cloud and WinApi is deprecated. To print standard reports Document Routing Agent should be installed. We need it to send PDF directly from the system as well.

Let’s say we want to print PDF file saved in document attachments (DocuRef). First, we need to check if a printer selected by a user is active and get printer details:

SrsReportPrinterContract activePrinterContract = SrsReportRunUtil::getActivePrinter(printerName);
if (!activePrinterContract.parmPrinterName())
{
    return;
}

printerName value should come from somewhere, for example a field on a form. In this case we can use SrsReportRunUtil::lookupPrinters() method to add a lookup with all available printers:

public static void lookupPrinters(FormStringControl _ctrl)
{
    SrsReportRunUtil::lookupPrinters(_ctrl);
}

Then we need to create print destination settings:

SRSPrintDestinationSettings srsPrintDestinationSettings = new SRSPrintDestinationSettings();
srsPrintDestinationSettings.printMediumType(SRSPrintMediumType::Printer);
srsPrintDestinationSettings.fileFormat(SRSReportFileFormat::PDF);
srsPrintDestinationSettings.printerName(activePrinterContract.parmPrinterName());
srsPrintDestinationSettings.printerWhere(activePrinterContract.parmPrinterPath());
srsPrintDestinationSettings.numberOfCopies(1);
srsPrintDestinationSettings.collate(false);
srsPrintDestinationSettings.printOnBothSides(SRSReportDuplexPrintingSetting::None);
srsPrintDestinationSettings.printAllPages(true);
srsPrintDestinationSettings.fromPage(0);
srsPrintDestinationSettings.toPage(0);

srsPrintDestinationSettings.printerWhere() is important bit here. This parameter accepts path to a printer. It’s possible to install multiple DRA on different servers and path for DRA installed on a print server could be different to path for DRA installed on any other server, so watch for this.

To send document we need to read file into a memory stream:

container fileCon = DocumentManagement::getAttachmentAsContainer(_docuRef);
var stream = Binary::constructFromContainer(fileCon).getMemoryStream();

And create new DocumentContract:

DocumentContract documentContract = DocumentContractFactory::Instance.Create(DocumentContractType::Pdf);

documentContract.Name = _docuRef.Name;
documentContract.Contents =  stream.ToArray();
documentContract.TargetType = TargetType::Printer;
documentContract.Settings = srsPrintDestinationSettings.printerPageSettings();
documentContract.ActivityID = newGuid();

If you send multiple documents ActivityID should be initialized for each document. Don’t forget to add a reference to Microsoft.Dynamics.AX.Framework.DocumentContract:

using Microsoft.Dynamics.AX.Framework.DocumentContract;

And finally send the contract to DRA:

SrsReportRunPrinter::sendDocumentContractToDocumentRouter(documentContract);

Whole method:

public static void sendToPrinter(DocuRef _docuRef, str _printerName)
{
    SrsReportPrinterContract activePrinterContract = SrsReportRunUtil::getActivePrinter(_printerName);
    if (!activePrinterContract.parmPrinterName())
    {
        return;
    }

    SRSPrintDestinationSettings srsPrintDestinationSettings = new SRSPrintDestinationSettings();
    srsPrintDestinationSettings.printMediumType(SRSPrintMediumType::Printer);
    srsPrintDestinationSettings.fileFormat(SRSReportFileFormat::PDF);
    srsPrintDestinationSettings.printerName(activePrinterContract.parmPrinterName());
    srsPrintDestinationSettings.printerWhere(activePrinterContract.parmPrinterPath());
    srsPrintDestinationSettings.numberOfCopies(1);
    srsPrintDestinationSettings.collate(false);
    srsPrintDestinationSettings.printOnBothSides(SRSReportDuplexPrintingSetting::None);
    srsPrintDestinationSettings.printAllPages(true);
    srsPrintDestinationSettings.fromPage(0);
    srsPrintDestinationSettings.toPage(0);

    container fileCon = DocumentManagement::getAttachmentAsContainer(_docuRef);
    if (fileCon)
    {
        var stream = Binary::constructFromContainer(fileCon).getMemoryStream();
        if (stream)
        {
            DocumentContract documentContract = DocumentContractFactory::Instance.Create(DocumentContractType::Pdf);
            documentContract.Name = _docuRef.Name;
            documentContract.Contents =  stream.ToArray();
            documentContract.TargetType = TargetType::Printer;
            documentContract.Settings = srsPrintDestinationSettings.printerPageSettings();
            documentContract.ActivityID = newGuid();
SrsReportRunPrinter::sendDocumentContractToDocumentRouter(documentContract);
        }
    }
}

If you want to check documents printed or see if there are any in a queue you can go to Common -> Inquiries -> Document routing status

DRA status

18 thoughts on “MSDyn365FO. How-to send PDF document to a printer from X++

  1. SARATHKUMAR S February 7, 2019 / 12:43 pm

    I have used your code to print one .pdf file from the douref record. I can see the status succeeded in document routing status form but It doesn’t sent to printer

    • Ievgen Miroshnikov February 7, 2019 / 6:22 pm

      Go to document routing status form and check that job IDs are different and printer name is correct. If you’ve sent record via print management and it works than use it to compare your one to see what is different.

      • SARATHKUMAR S February 8, 2019 / 10:17 am

        Job IDs are different and printer name is correct and used the same printer name for both. I ran your code using Job but same result. Can you please help me?

      • SARATHKUMAR S February 8, 2019 / 12:33 pm

        public static void main(Args _args)
        {
        InventSite site = InventSite::find(‘A’);
        DocuRef _docuRef;
        System.Byte[] reportBytes;
        System.IO.MemoryStream stream;

        _docuRef = DocuRef::findTableIdRecId(curExt(), tableNum(InventSite), site.RecId);
        SrsReportPrinterContract activePrinterContract = SrsReportRunUtil::getActivePrinter(‘Lexmark MX310 Series Class Driver’);

        SRSPrintDestinationSettings srsPrintDestinationSettings = new SRSPrintDestinationSettings();
        srsPrintDestinationSettings.printMediumType(SRSPrintMediumType::Printer);
        srsPrintDestinationSettings.fileFormat(SRSReportFileFormat::PDF);
        srsPrintDestinationSettings.printerName(activePrinterContract.parmPrinterName());
        srsPrintDestinationSettings.printerWhere(activePrinterContract.parmPrinterPath());
        srsPrintDestinationSettings.numberOfCopies(1);
        srsPrintDestinationSettings.collate(false);
        srsPrintDestinationSettings.printOnBothSides(SRSReportDuplexPrintingSetting::None);
        srsPrintDestinationSettings.printAllPages(true);
        srsPrintDestinationSettings.fromPage(0);
        srsPrintDestinationSettings.toPage(0);

        container fileCon = DocumentManagement::getAttachmentAsContainer(_docuRef);

        if (fileCon)
        {
        stream = Binary::constructFromContainer(fileCon).getMemoryStream();

        reportBytes = stream.ToArray();

        if (stream)
        {
        DocumentContract documentContract = DocumentContractFactory::Instance.Create(DocumentContractType::Pdf);
        documentContract.Name = _docuRef.Name;
        documentContract.Contents = reportBytes;
        documentContract.TargetType = TargetType::Printer;
        documentContract.Settings = srsPrintDestinationSettings.printerPageSettings();
        documentContract.ActivityID = newGuid();
        // file::SendFileToUser(stream, _docuRef.Name +’.pdf’);
        SrsReportRunPrinter::sendDocumentContractToDocumentRouter(documentContract);
        }
        }
        }

        It is my code

  2. SARATHKUMAR S February 7, 2019 / 1:30 pm

    Can you please help me on this?… I have printed the standard report using print management and that woked fine

    • Ievgen Miroshnikov February 8, 2019 / 6:53 pm

      Code looks perfectly fine for me, when you do file::SendFileToUser(stream, _docuRef.Name +’.pdf’); does it give you valid PDF ?

    • Ievgen Miroshnikov February 13, 2019 / 6:27 pm

      Do you have this question on community forum also? Server with DRA should have Adobe Acrobat Reader installed. Check if it is there.

  3. SARATHKUMAR S February 11, 2019 / 10:26 am

    Normally the standard report with print mangement destination as ‘printer’ calls SrsReportRunPrinter/toDocumentRouter…
    But my logic works according to SrsReportRunPrinter/toDocumentRouterAsPdf method… Still I couldn’t able send my .pdf file printer. Can you suggest any possibilities?

  4. Alberto February 13, 2019 / 9:09 am

    Could I use this but to print a pdf from a local path (ex: C:\Temp\File.PDF), without having to use “docuRef” in an onpremise version?

    • Ievgen Miroshnikov February 13, 2019 / 9:17 am

      There is no “local path” in the cloud, you have it in dev VM, but how are you going to add it to prod server?

      • Alberto February 13, 2019 / 9:58 am

        In my case I do not have a cloud, it’s all Dynamics 365 on premise (VM, UAT and Production). I understand that I can access any shared path.

    • Ievgen Miroshnikov February 13, 2019 / 6:26 pm

      All you need is to read a file into a byte array, so probably yes, you can.

      • Alberto February 15, 2019 / 10:44 am

        Yes, I could do it, I send code on runnableClass:

        using Microsoft.Dynamics.AX.Framework.DocumentContract;
        using System.IO;

        class SendFileLocalToPrinter
        {
        public static void main(Args _args)
        {
        str PrinterName = “Generic 36C-9SeriesPCL Planta 15″;
        Filename FileName = @”C:\Temp\Invoice\001-000374.pdf”;

        SrsReportPrinterContract activePrinterContract = SrsReportRunUtil::getActivePrinter(printerName);
        if (!activePrinterContract.parmPrinterName())
        {
        return;
        }

        SRSPrintDestinationSettings srsPrintDestinationSettings = new SRSPrintDestinationSettings();
        srsPrintDestinationSettings.printMediumType(SRSPrintMediumType::Printer);
        srsPrintDestinationSettings.fileFormat(SRSReportFileFormat::PDF);
        srsPrintDestinationSettings.printerName(activePrinterContract.parmPrinterName());
        srsPrintDestinationSettings.printerWhere(activePrinterContract.parmPrinterPath());
        srsPrintDestinationSettings.numberOfCopies(1);
        srsPrintDestinationSettings.collate(false);
        srsPrintDestinationSettings.printOnBothSides(SRSReportDuplexPrintingSetting::None);
        srsPrintDestinationSettings.printAllPages(true);
        srsPrintDestinationSettings.fromPage(0);
        srsPrintDestinationSettings.toPage(0);

        using (MemoryStream ms = new MemoryStream())
        using (FileStream file = new FileStream(Filename, FileMode::Open, FileAccess::Read))
        {
        file.CopyTo(ms);

        DocumentContract documentContract = DocumentContractFactory::Instance.Create(DocumentContractType::Pdf);
        documentContract.Name = “Test”;
        documentContract.Contents = ms.ToArray();
        documentContract.TargetType = TargetType::Printer;
        documentContract.Settings = srsPrintDestinationSettings.printerPageSettings();
        documentContract.ActivityID = newGuid();
        SrsReportRunPrinter::sendDocumentContractToDocumentRouter(documentContract);

        }
        }

        }

        Thanks!

  5. Paul Noakes October 18, 2019 / 3:14 pm

    Great article but I am having the same issue – sending a valid PDF to DocumentRouter (running as a service) generates a file in the “Select the target folder for PDF files” folder, but doesn’t actually print it! It prints if I run the DRA on the desktop, but then it also pops up Adobe which is irritating! Perhaps my printerPageSettings are making this happen?

    • Ievgen Miroshnikov October 18, 2019 / 7:51 pm

      Is this specific to documents you send through code ? DO you have any issues with standard report printing ?

      • Paul Noakes October 21, 2019 / 12:12 pm

        Hi! I think the printing issue may have been related to my VM.

        Documents I send through code with sendDocumentContractToDocumentRouter (PU29) seem to instruct the DRA to also spool the pdf to the “pdf folder” (specified when running the process as a service.) This doesn’t seem to happen when you render/print the report directly. I guess there is a slight difference in the way the SrsReportRunPrinter class instructs the DRA… just wondered whether you had noticed this as well? Worst case, we will have to schedule a batch job to purge the folder every now and then….

    • Paul Noakes October 21, 2019 / 8:31 pm

      I can answer my own question! Printing PDFs isn’t supported when the DRA is running as a service (take a look at Microsoft.Dynamics.AX.Framework.DocumentRouting.Runtime.PdfHelper)

Leave a Reply to Alberto Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s