All the time I see on the list the request to export a Crystal Report from Epicor to PDF. I’ve done this many times different ways but this is my favorite, it can be done without having to bring in external libraries and it allows me to use Reflection. If you attended my talk at Epicor Insights earlier this year you heard me talk about it, utilizing the System.Reflection library we are able to call any code without the need to have access to the libraries at compile time as long as they are available in the GAC, it also allows you to call private functions and modify fields with restricted access level. Below is a brief explanation of how it works along with a sample of the code to Export an Epicor Crystal Report to PDF.

  1. The first thing is to setup a File System Watcher (FSW). What this library does is allow the raising of events upon file Creation, Deletion or Update. We will use this class to alert us when the XML (Data Source for our Report) has been generated
  2. Once the FSW has been instantiated we need to submit our report to the system agent, to do this we simply replicate what Epicor does by Tracing the “Generate” only operation of the report print.
  3. Once the file has been submitted to the system agent and we have enabled the events trigger in the FSW we simply wait for the file to be generated and our event to be raised
  4. Once the event is raised we will have the file we need (XML) to set as a data source on our report
  5. Finally we simply Open the report, set the data source (XML) and export it using one of the many export options available to Crystal.

Declare a Class Level File System Watcher

FileSystemWatcher watcher;

Within the InitilializeCustomCode function instantiate it and declare your interest in its Created / Changed events.

public void InitializeCustomCode()
	// ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Variable Initialization' lines **
	// Begin Wizard Added Variable Initialization
	// End Wizard Added Variable Initialization
	// Begin Wizard Added Custom Method Calls
	// End Wizard Added Custom Method Calls
	watcher = new FileSystemWatcher();
	watcher.Created += new FileSystemEventHandler(OnChanged);
	watcher.Changed += new FileSystemEventHandler(OnChanged);	

Within DestroyCustomCode make sure you de-register your interest in those events and dispose of the watcher

public void DestroyCustomCode()
	// ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Object Disposal' lines **
	// Begin Wizard Added Object Disposal
	// End Wizard Added Object Disposal
	// Begin Custom Code Disposal
	// End Custom Code Disposal
	watcher.Created -= new FileSystemEventHandler(OnChanged);
	watcher.Changed -= new FileSystemEventHandler(OnChanged);

In my example I will be printing a Packing Slip but this can be done with any report as long as you can trace it, simply replicate what the trace does with the appropriate DLLs. In this case I had to import the following DLLs

  • Epicor.Mfg.Core.Session.dll
  • Epicor.Mfg.Core.BLConnectionPool.dll
  • Epicor.Mfg.Rpt.PackingSlipPrint.dll
  • Epicor.Mfg.Rpt.IPackingSlipPrint.dll

Once the DLLs are imported I created a function called SubmitReport which I can call from a button click. I’ve commented the code below where needed to clarify

private void SubmitReport()
	PackingSlipPrint psp = new PackingSlipPrint(((Session)oTrans.Session).ConnectionPool);
	PackingSlipPrintDataSet psds= psp.GetNewParameters();
	Epicor.Mfg.UI.FrameWork.EpiNumericEditor num =(Epicor.Mfg.UI.FrameWork.EpiNumericEditor) csm.GetNativeControlReference("893bd788-0b39-46a1-985d-ef0bbc83e4d2");
	psds.PackingSlipParam[0].PackNumList = num.Value.ToString(); // This is the Packing Slip Number I wanted to Print, I got it out of a TextBox
	psds.PackingSlipParam[0].ReportStyleNum=1001; //I was interested in this specific report sytle you can get this from Report Style Maintenance
	psds.PackingSlipParam[0].AutoAction ="";
	psds.PackingSlipParam[0].StyleNumExt =1;
	psds.PackingSlipParam[0].WorkstationID= ((Session)oTrans.Session).WorkstationID;
	watcher.EnableRaisingEvents = false;
        // The RPT_DATA_PATH is simply a constant variable pointing to \\server\EpicorData\Reports\ from there I append the user id
        // This is telling the FSW where to watch for files being created
	watcher.Path=RPT_DATA_PATH+ ((Session)oTrans.Session).UserID; 
        // This tells the FSW that I am only interested in Packing Slip reports
	watcher.Filter="*Packing Slip Print*.xml";
        // This turns on our FSW
	watcher.EnableRaisingEvents = true;
	psp.SubmitToAgent(psds,"SystemTaskAgent",0,0, "Epicor.Mfg.UIRpt.PackingSlipPrint");

	oTrans.PushStatusText("Waiting on Report Print...",true);

Once the report is Submitted we just wait for the FSW to trigger the event and then execute the code to produce the report on the OnChange event of the FSW

private void OnChanged(object source, FileSystemEventArgs e)
	watcher.EnableRaisingEvents=false; //I've gotten my file so I am turning off my FSW
	oTrans.PushStatusText("Got File, Processing please wait...",true);
	Thread.Sleep(5000); //I am waiting a few seconds for the report to finish (this may not be necessary if you have a small report)
	String fileName = e.FullPath; //I am getting the full path of the XML file
	String epxortName = Path.GetTempFileName().Replace(".tmp",".pdf"); //I am generating a temporary file (PDF) for me to export to
	printRpt(e.FullPath, epxortName); //I am calling my export function

My export function is 100% driven by the reflection library and its fairly tough to understand if you are not familiar with it, I’ve done my best in the comments to explain it but its not easy so follow along if you can.

private void printRpt(String data, String Tempfile)
                // Here I am dynamically loading the needed assemblies from the assembly cache (Note that this uses version 12 Crystal 2008 version 13 is 2010
                Assembly assembly      = Assembly.Load("CrystalDecisions.CrystalReports.Engine, Version=12.0.2000.0, Culture=neutral, PublicKeyToken=692fbea5521e1304");
                Assembly crystalShared = Assembly.Load("CrystalDecisions.Shared, Version=12.0.2000.0, Culture=neutral, PublicKeyToken=692fbea5521e1304");
                    Make Report Object
                    ReportDocument rdoc = new ReportDocument();
                object doc          = assembly.CreateInstance("CrystalDecisions.CrystalReports.Engine.ReportDocument");
                Type crystalDocType = assembly.GetType("CrystalDecisions.CrystalReports.Engine.ReportDocument");
                    Load Report RPT
                    rdoc.Load(report path);
                                            BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public,
                                            null, doc, new object[] { CRYSTAL_REPORT });
                                            //CRYSTAL_REPORT is a constant that contains the full UNC path to the RPT File I am working with
                //Load report from XML
                DataSet ds = new DataSet();
                   Set The Report Data Source
                                            BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public,
                                            null, doc, new object[] { ds });
                    Instanciate a File Destination Options 
                    DiskFileDestinationOptions d = new DiskFileDestinationOptions();
                object dfd = crystalShared.CreateInstance("CrystalDecisions.Shared.DiskFileDestinationOptions");
                    //Set the Path of the Temporary File which will be generated
                    d.DiskFileName = TempFile
                Type dfdType              = crystalShared.GetType("CrystalDecisions.Shared.DiskFileDestinationOptions");
                PropertyInfo diskFileName = dfdType.GetProperty("DiskFileName");
                diskFileName.SetValue(dfd, Tempfile, null);
                    //Get a hold of the RPT Export options
                    ExportOptions eo = rdoc.ExportOptions;
                object exportOptionsObj = crystalShared.CreateInstance("CrystalDecisions.Shared.ExportOptions");
                    Set the Destination to the Destination Options Above
                    eo.ExportDestinationOptions = d;
                Type exportOptionsType          = crystalShared.GetType("CrystalDecisions.Shared.ExportOptions");
                PropertyInfo destinationOptions = exportOptionsType.GetProperty("DestinationOptions");
                destinationOptions.SetValue(exportOptionsObj, dfd, null);
                    //Set the Format as PDF
                    eo.ExportFormatType = ExportFormatType.PortableDocFormat;
                PropertyInfo exportFormatType = exportOptionsType.GetProperty("ExportFormatType");
                exportFormatType.SetValue(exportOptionsObj, 5, null);
                // x is the enum value of the PDF option the rest of the values can be seen below
                    //Set the export type as Export to File
                    eo.ExportDestinationType = ExportDestinationType.DiskFile;
                PropertyInfo exportDesType = exportOptionsType.GetProperty("ExportDestinationType");
                exportDesType.SetValue(exportOptionsObj, 1, null);
                // 1 is the enum for Export to File
                    //Call Export
                                            BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public,
                                            null, doc, new object[] { exportOptionsObj });
	catch(Exception ex)

The following is just a list of Enums available within the EportFormatType (these can vary based on the version of crystal)

public enum ExportFormatType
        NoFormat = 0,
        CrystalReport = 1,
        RichText = 2,
        WordForWindows = 3,
        Excel = 4,
        PortableDocFormat = 5,
        HTML32 = 6,
        HTML40 = 7,
        ExcelRecord = 8,
        Text = 9,
        CharacterSeparatedValues = 10,
        TabSeperatedText = 11,
        EditableRTF = 12,
        Xml = 13,
        RPTR = 14,
        ExcelWorkbook = 15,

After this point the report has been submitted, and exported and the “PDF” file is available in the temp location you created earlier.

Be Sociable, Share!

Tags: , , , ,

15 Comments on How to Export a Crystal Report through code by using Reflection in Epicor

  1. Chris says:

    Is there anyway to have Intellisense when creating C# customizations in Epicor? Also do you know where I can find documentation for simple operations such as reading a new Purchase Order from the database and that kind of thing?

    • Jose C Gomez says:

      No intelisense, and there is not such a thing as a simple operation such as reading a new purchase order from the database. Any ERP is a very complex environment and there are 10 thousand ways to do something, it all depends on what you are trying to do.

  2. Jeff Waldron says:

    I have a crystal report I just developed for Epicor that I need to export to plain text. I don’t want any formatting carried over or anything like that. Would I be able to modify this code to do that using the same functionality?

  3. Chris says:

    Great guide Jose, thanks for the information. I have one problem with this code though, I’m doing it for Purchase Orders and everything works up until the export method where it gives me this error: “Exception has been thrown by the target of an invocation.Logon failed. Error in File POForm.rpt: Unable to cnonect: incorrect logon parameters.

    Here is my code:

    Any chance you could help out?

  4. Chris says:

    Nevermind I figured it out. DIdn’t have the default POForm rpt setup anymore.

  5. Jeff Waldron says:

    Thanks for the reply! I’m going to start testing this today. I have one other request that I get all the time that probably can be built off of this. Is there a way, by extending this code, to have the crystal report export to a PDF and send it via an email message? What I’m envisioning is an “email button” on the report dialog that will basically create the PDF and attach it to a new message window, so the user can edit the message text and send it. Perhaps even populate the recipient email address with a CRM contact (for our example we want to send PDF quotes from Quote Entry). Is that taking it too far or making it too complicated?


  6. Alex says:

    Been working with this example for a day or so. Getting same message as above. “Exception has been thrown by the target of an invocation.Logon filed. Error in File [mycrystalform.rpt]: Unable to connect: incorrect log on parameters.” Earlier with other problems I did get it to work through this once, now can’t figure out how to make it work. Any ideas what Chris did to get it to work on 10/30/2013? He said: Didn’t have the default POForm rpt setup anymore.

  7. Jose C Gomez says:

    Hi Alex,
    This issue usually has to do with the report data set being out of date. Try opening the report in Crystal Developer and refresh the dataset.

  8. Nick Salters says:


    I am trying to do the same thing mentioned above by Jeff Waldron. I Understand the code you have to create the PDF, but I am not quite able to get Outlook to open a new message with the appropriate file and information attached. Any help or guidance would be greatly appreciated.

  9. Nick Salters says:

    I got it to work with email and all for my quote. Now I’m stuck on the PO one like Chris above. I have tried opening the report and refreshing it, but still getting the Logon failure.

  10. Aaron mathis says:

    Does this work with Crystal Reports 2011 SP4 Version:

  11. Aaron mathis says:

    I am wanting to do this in E10 with AR Invoice Form. I noticed you wrote this for what looks like 9. Is it still compatible with 10? How did you figure out the Assembly stuff? like the PublicKeyToken and what not for Crystal. Thank you!

  12. Aaron mathis says:

    Also in E10 you can no longer do this:
    PackingSlipPrint psp = new PackingSlipPrint(((Session)oTrans.Session).ConnectionPool);

    What did you replace that with in E10?