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

24 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.

      • Alberto December 10, 2019 / 11:38 am

        On Premise enviroment, Do you know where Adobe Acrobat Reader should be installed? I try to install on AOS server and it doesn’t run.

  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?

    • Alberto December 10, 2019 / 11:07 am

      On Premise enviroment, Do you know where Adobe Acrobat Reader should be installed? I try to install on AOS server and it doesn’t run.

  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)

      • Pankaj Shantaram Kadlag December 4, 2020 / 4:01 pm

        Hi Paul,

        I am facing exactely same Issue,

        I am trying to send file in metafile format as I want to run DRA as a service, but when I select DRA as a service and sends metafile i am getting error,
        Can you please help me with what exactely you did to run as a metafile ?

        Pankaj

      • Minh Hao November 17, 2021 / 8:31 pm

        Hi Paul, I think I run in the same issue and don’t know how to fix it. File that is sent to local printer is fine but not for network printer. It just sits on the DRA and not actually print. Did you actually have it working ? I know it’s an old post but really appreciate if you share your experience?

  6. guido pilenga November 28, 2019 / 10:26 pm

    Could you pls share the code to send a report to a printer instead of a docuref?

    • Ievgen Miroshnikov November 28, 2019 / 10:53 pm

      Just set print destination to a printer, there are multiple blogs with examples how to do that.

Leave a reply to Ievgen Miroshnikov Cancel reply