William Asks:

Trying to fire off the ImportEDI demand process from VB.NET with:

  1. backup process checkbox checked
  2. Import folder being \\taf2k8edi1\epicor\edidata\inbound\demand.
  3. Continuous process is false.

This can be accomplished by establishing a session on the VB/C# application and creating an instance of the  ImportEDI process as shown below. After running a trace on Epicor you can see that the EDI Import Process runs 2 main methods.

	7/2/2015 09:28:33:6255008 AM

GetNewParameters: gets you an new instance of the EDI Process Import Data Set

	7/2/2015 09:28:55:7313078 AM

SubmitToAgent: schedules the process with the System Task Agent for Execution. This method requires that you fill in the appropriate values within the ImportEDIDataSet as shown on the FULL trace.

The code below is an implementation of calling these two methods in C# from an External application. You requested it in VB.NET but I don’t write VB.NET unless someone is holding a gun against my head… It makes me itchy and I break out in hives… so your homework is to convert the below code into VB.NET. You will need to reference the following DLL’s available within the Epicor Client Folder

  • Epicor.Mfg.Core.BLConnectionPool.dll
  • Epicor.Mfg.Core.Session.dll
  • Epicor.Mfg.Proc.IImportEDI.dll
  • Epicor.Mfg.Proc.ImportEDI.dll
using Epicor.Mfg.Core;
using Epicor.Mfg.Proc;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using(Session _session =  new Session("manager","manager","AppServerDC://E9P:9411",Session.LicenseType.Default))
       ImportEDI _importEDIPRoc = new ImportEDI(_session.ConnectionPool);

       ImportEDIDataSet importEDIDS = _importEDIPRoc.GetNewParameters();
       ImportEDIDataSet.ImportEDIParamRow newRow = importEDIDS.ImportEDIParam[0];

       newRow.LogFilename=@"C:\Epicor\EpicorData\ImportEDI.log"; //EDI LOG
       newRow.InboundPath=@"\\taf2k8edi1\epicor\edidata\inbound\demand"; //IMPUT PATH
       newRow.WorkstationID = Environment.MachineName + " " + Process.GetCurrentProcess().SessionId; //TIS IS IMPORTANT



Tags: , , , ,

Sometimes the libraries you need can not be compiled into the code you are writing. But this doesn’t mean that you can’t still use them, using reflection you can load any library dynamically into your code and execute any method within it.

The below example demonstrates how to dynamically load the Microsoft Interop Library for Outlook from a directory and subsequently invoke a new Email with an attachment. I’ve commented the code above each invocation with the equivalent code that would be used if this library had been loaded at compile time.

Assembly interopAssembly = Assembly.LoadFile(System.Environment.CurrentDirectory + @"\Microsoft.Office.Interop.Outlook.dll");

 //Microsoft.Office.Interop.Outlook.Application oApp = new Microsoft.Office.Interop.Outlook.Application();
 object outlookApplication = interopAssembly.CreateInstance("Microsoft.Office.Interop.Outlook.ApplicationClass");
 Type outlookApplicationType = interopAssembly.GetType("Microsoft.Office.Interop.Outlook.ApplicationClass");

 //Microsoft.Office.Interop.Outlook._MailItem oMailItem = (Microsoft.Office.Interop.Outlook._MailItem)oApp.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
 dynamic mailItem = outlookApplicationType.InvokeMember("CreateItem", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, outlookApplication, new object[] { 0 });

 mailItem.To = "test@test.com";
 mailItem.Subject = "test@test.com";
 mailItem.Body = "test@test.com";

 //oMailItem.Attachments.Add(@"C:\Logs\ContractImpl_2.txt", Microsoft.Office.Interop.Outlook.OlAttachmentType.olByValue, Type.Missing, Type.Missing);
 object attachments = mailItem.Attachments;
 Type attachmentType = attachments.GetType();
 attachmentType.InvokeMember("Add", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, attachments, new object[] { @"C:\Logs\ContractImpl_2.txt", 1, Type.Missing, Type.Missing });

 Type mailItemType = mailItem.GetType();
 mailItemType.InvokeMember("Display", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, mailItem, new object[] { true });

Tags: , , ,

A question was asked on the mailing list regarding the ability to change the appearance (background, foreground, color) of an input control within the Epicor ERP10 configuration.

This was an unusual request and it took a few minutes to figure it out. Below is the code which when excecuted on field change will change the appearance of the given input control to have a red background. It is worth noting that because of the way that the configurator editor is written there are parts of the configurator which do not have access to the Infragistics controls / dlls. In this case you would have to load the DLL manually using reflection which I do not cover below.

However if done at the control level (OnChange, OnLeave) the Infragistic DLL seems to be available and the code below works. Please watch the companion video if you want more details.

The Input.InputName is an object called InputControlValueBound which contains a reference to a “Control” and the value of said control. Once you’ve figured this out, you can use the public property .Control to get a hold of the EpiXXX (EpiTextBox,EpiCombo,etc..) object and then set the Appearance as you normally would.

Please note that all Epicor EpiXXX controls have a default property called “UseAppStyling” if this property is set to true the control ignores any appearance changes because it assumes you want to load the default theme from the environment. This property needs to be set to false in order to allow the appearance changes to take effect.


Put this code in your OnChange , OnLeave event

Code to implement:

 Infragistics.Win.Appearance app1 = new Infragistics.Win.Appearance();
app1.BackColor =Color.Red;
((Ice.Lib.Framework.EpiTextBox)Inputs.MTPDESC.Control).UseAppStyling = false;
((Ice.Lib.Framework.EpiTextBox)Inputs.MTPDESC.Control).Appearance = app1;


Tags: , , ,

Jose C Gomez on July 1st, 2014

So it turns out that the DB for this blog had been filled up by a runaway plugin. So no one could comment or do much on here. This has been fixed… I guess I would have known this happened a long time ago if I posted more often. Oh well! Sorry guys!

Jose C Gomez on December 18th, 2013

A few years ago I wrote  an HTML Editor control that can be used with the .NET framework. Until recently it lay in obscurity being used from time to time by my and a few other people that found it online. Recently Joshua from Perficiency Development made some enhancements to it when he was trying to get it to work with Epicor. With this new enhancements and the fact that someone asked about it on the Epicor mailing list I decided to throw together this short tutorial on how to get it working in Epicor.

  1. Go download the appropriate version of the DLL from the original post
  2. Once you’ve downloaded the project, simply copy the DLL from the Release/Bin folder into the Epicor client folder where you’d like to use the control
  3. In the example code below I began by adding a new tab to the ABC Code form where I was going to place my editor
  4. Once the tab is added I drew a group box on the shape and size I want the editor to be in the new tab. This makes it much easier to position the editor without having to write much code
  5. Once this is done the code below takes care of the rest. I’ve commented the code where appropriate for clarity.

The final result of the below code can be seen below

// **************************************************
// Custom code for AbcCodeForm
// Created: 12/18/2013 12:19:03 PM
// **************************************************
using System;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Forms;
using Epicor.Mfg.BO;
using Epicor.Mfg.UI;
using Epicor.Mfg.UI.Adapters;
using Epicor.Mfg.UI.Customization;
using Epicor.Mfg.UI.ExtendedProps;
using Epicor.Mfg.UI.FormFunctions;
using Epicor.Mfg.UI.FrameWork;
using Epicor.Mfg.UI.Searches;

public class Script
	// ** Wizard Insert Location - Do Not Remove 'Begin/End Wizard Added Module Level Variables' Comments! **
	// Begin Wizard Added Module Level Variables **

	private EpiDataView edvAbcCode;
	// End Wizard Added Module Level Variables **

	// Add Custom Module Level Variables Here **

	//Declare a class level htmlwysiwyg
	htmlwysiwyg htmlEditor;

	public void InitializeCustomCode()
		// ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Variable Initialization' lines **
		// Begin Wizard Added Variable Initialization

		this.edvAbcCode = ((EpiDataView)(this.oTrans.EpiDataViews["AbcCode"]));
		this.edvAbcCode.EpiViewNotification += new EpiViewNotification(this.edvAbcCode_EpiViewNotification);
		// End Wizard Added Variable Initialization

		// Begin Wizard Added Custom Method Calls

		// End Wizard Added Custom Method Calls

		//In your InitializeCustomCode() function initialize the editor and add it to your groupbox
		htmlEditor = new htmlwysiwyg();
		htmlEditor.ImagePasteToPath = @"\\GOMEZFAMILY\Users\Public\Pictures\";

		//We have a need for triggering an event when validating the control to update the data view
		htmlEditor.Validating += htmlEditor_Validating;


	public void DestroyCustomCode()
		// ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Object Disposal' lines **
		// Begin Wizard Added Object Disposal

		this.edvAbcCode.EpiViewNotification -= new EpiViewNotification(this.edvAbcCode_EpiViewNotification);
		this.edvAbcCode = null;
		// End Wizard Added Object Disposal

		// Begin Custom Code Disposal

		// End Custom Code Disposal

		//Always be a good citizen and dispose of your extra objects
		htmlEditor.Validating -= htmlEditor_Validating;
		htmlEditor = null;

	private void edvAbcCode_EpiViewNotification(EpiDataView view, EpiNotifyArgs args)
		// ** Argument Properties and Uses **
		// view.dataView[args.Row]["FieldName"]
		// args.Row, args.Column, args.Sender, args.NotifyType
		// NotifyType.Initialize, NotifyType.AddRow, NotifyType.DeleteRow, NotifyType.InitLastView, NotifyType.InitAndResetTreeNodes

		// When the view initializes we must check if we have data
		// if  we do then we bind the control to the UD field where
		// we want to store the HTML. Please note that ShortChar01
		// is not a good choice since its only 50 characters long
		if ((args.NotifyType == EpiTransaction.NotifyType.Initialize))
			if(args.Row >-1)
				htmlEditor.DataBindings.Add(new Binding("HTML",view.dataView[args.Row],"ShortChar01"));
			else // If the DataView is empty then clear the control


	private void htmlEditor_Validating(object sender, CancelEventArgs args)
		// When we make a change to the control, lets make sure we get the HTML
		// and update your dataview.
		EpiDataView edvDtl = oTrans.Factory("AbcCode");
		if(edvDtl.Row >-1)
			edvDtl.dataView[edvDtl.Row]["ShortChar01"] = htmlEditor.getHTML();
			edvDtl.Notify(new EpiNotifyArgs(oTrans, edvDtl.Row, edvDtl.Column));


Tags: , , ,

Jose C Gomez on December 2nd, 2013

Update 12/18/2013:

Added a few samples of how to implement in epicor / integrate with the data views. Please not that if you are going to paste images and use it with Epicor we recommend that you download the version which puts the images on a shared network drive. Most UD fields in epicor are too small to handle the Base64 encoded image.
This sample code is provided as is and it was created using Epicor 9.05

Update 12/3/2013:

Joshua Giese from Perficiency Development made some enhancements to the editor that he wanted to share back.

  1. Added the ability to copy / paste images.
    Images are pasted and embedded using the Base 64 Data URI
    or Images are placed on a Shared Folder in a Network Drive ( 2 different versions see below)
    2013-12-03 13_42_47-6lAYWRgVoJqMXSzcdRWj2plP5W0KN.jpg (950×627)
  2. Added a custom context menu for Copy , Cut and Paste which invokes the toolbar functions
  3. Also the project has been upgraded to Visual Studio 2012

Download it here HTMLWYSIWYG (Base 64 Encoded Image)

Download it here HTMLWYSIWYG_sharedFolder (Shared folder Paste Image)

I’ve been busy at work lately one of the projects assigned to me is to create an application to redact and edit emails that will be sent to customers. This provided a great opportunity for me to get familiar with creating a custom control in C#. I needed a way to create rich content and although C# has the rich text control it provides everything in RTF which is difficult to work with and hard to embed. So I got to work on my very own WYSIWYG control that produces HTML. So I am making it open source and available to anyone have fun and enjoy if you make any cool modifications to it let me know. I would love to include spell check ability but I don’t have much time to work on it right now.

Download Here HTMLWYSIWYG (Original version)

Sample Uses

 private void button1_Click_1(object sender, EventArgs e)
 //Gets the HTML Code generated by the control
 Console.WriteLine( htmlwysiwyg1.getHTML())
 //Getts the PLain Tex code generated by the control.


 private void frm_main_Load(object sender, EventArgs e)
 //Sets the control to allow edits
 //Loads the HTML into the control
 //Loads aditional fonts into the control
 htmlwysiwyg1.ImagePasteToPath =@"\\YOUR_UNC_PATH"; //Only available in the Paste to Network Version

Tags: , , , , ,

Jose C Gomez on October 8th, 2013

I decided to start sharing some of the recipes that I make every day, some of these I’ve adapter from others and some are recipes I just threw together, please let me know your thoughts and if you like or dislike any of the dishes.

Napa cabbage, sausage and white bean soup
Serves 6
A hearty, warm and inviting soup that can be paired with rice to make a complete meal.
Write a review
Prep Time
30 min
Cook Time
45 min
Total Time
1 hr 15 min
Prep Time
30 min
Cook Time
45 min
Total Time
1 hr 15 min
  1. 1 Napa cabbage (chopped into strips)
  2. 4 carrots (chopped)
  3. 2 celery stalks (chopped)
  4. 4 small red potatoes (chopped)
  5. 1 lbs mild sausage
  6. 1/2 lbs pork stew meat
  7. 2 cans white bean (drained)
  8. 2 tbsp tomato sauce
  9. 1 tbsp olive oil
  10. 3 cloves of garlic
  11. 2 boxes of chicken or vegetable broth (low sodium)
  1. Drizzle a nice size pot with olive oil and brown sausage on medium high heat until cooked through.
  2. Remove the sausage from the pot and set aside, add in the pork stew meat
  3. Season well with salt and pepper and cook until it has browned on all sides
  4. Remove the pork from the pot and set aside
  5. Add the Napa cabbage chopped into strips and cook until wilted and slightly brown.
  6. Remove the Napa cabbage, add a little bit more of oil and stir in the carrots, onions and celery
  7. Season generously with salt and pepper and cook until onions are translucent and carrots are glistening
  8. Add in the garlic and tomato sauce and cook for another minute stirring to make sure the garlic doesn't burn
  9. Add in the potatoes and stir to combine
  10. Add in the two boxes of broth, bring to a boil then reduce the heat to simmer
  11. Add in the sausage, and the pork and simmer until carrots and potatoes are nice and soft ( 15-20 minutes)
  12. Add in the cabbage and the rinsed white beans and simmer for another 10-15 minutes until the meat and all the veggies are fully cooked
  13. Adjust salt and paper to taste
  14. Serve over rice for a complete meal (veggies, protein and carbs)
  1. If using sausage links simply chop them up before cooking them
  1. Note that you do not need to cook the pork all the way through when browning at first, we simply want to give it color at this point. It will finish cooking when the soup is simmering
  1. When doing the initial cooking / browning you may need to add a bit more oil here and there to keep things from sticking.
  1. We add the garlic last because garlic can burn easily
josecgomez.com http://www.josecgomez.com/wordpress/

Tags: ,

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.

Tags: , , , ,