🌟 ExportAllTheThings - Export Your Custom Objects

Deleted everything.

Imported eveything.

Worked flawlessly in the new company.

Great program!!!

I love it!!!

Thank you!!!

Richard

Probably just had to add the other company to the security in the function library.

Wait until you see what’s next..

I’m late to the party here, but I just tried this out and LOVE it! Thank you @klincecum!!! :tada: This makes refreshing a Pilot db that users are still testing in so, so, much easier.

Has anyone tried exporting BOOs/BOMs? We often have people trying out new manufacturing routings in Pilot db, and when they’re happy with the new routing, they have to copy/paste them into the Live db.

@klincecum , can I export myself?

Exporting BOM data is straightforward. The table you export from is PartMtl, and the DMT upload is “Bill of Materials”. All the fields in PartMtl are in the DMT upload, AND you need both a “Plant” and “ECOGroupID” field for the DMT upload.

BOO data is a little more complicated. There are two linked tables, PartOpr and PartOpDtl (the second table holds the ResourceGroup/Resource/Capability capacity constraint). The DMT upload is “Bill of Operations”, and also requires a Plant and an ECOGroupID.

The way the DMT works for this is literally the same way you’d add (or modify) a Part Revision record through the user interface. Each line in the DMT upload essentially checks out the Part Revision to the Engineering Workbench (which is why you need the ECOGroupID field), makes the change, and then approves and checks the part revision back in.

These are NOT fast DMT uploads. Your RPM (records per minute) will almost certainly be below 100, and depending on complexity, maybe even less than 50. Do it in small chunks… error message tend to be, shall we say, not incredibly helpful.

Question: Would it be possible to use the task agent to complete this in the background so that it doesn’t freeze the front end, e.g., export to Excel?

Something else that might also be useful is the ability to export KNTCPersLayer KNTCCustLayer and possibly components (we aren’t using any).

It is possible to ‘bulk export’ from Application Studio, but it would be nice to have an ‘all-in-one’ tool.

{
  "method": "POST",
  "url": "https://host.epicorsaas.com/tenant/api/v2/odata/ACME_LAB/Ice.LIB.MetaFXSvc/GetApplications",
  "queryParams": {},
  "headers": {
    "Content-Type": "application/json",
    "Authorization": "Bearer ....",
    "callSettings": "{\"Company\":\"ACME_LAB\",\"Plant\":\"ACME_01\"}",
    "SessionInfo": "{\"sessionId\":\"45058\"}",
    "x-epi-request-etag": "true",
    "x-epi-extension-serialization": "full-metadata",
    "Accept": "application/json, text/plain, */*"
  },
  "body": {
    "request": {
      "Type": "view",
      "SubType": "",
      "SearchText": "",
      "IncludeAllLayers": true,
      "IncludePersLayers": true
    }
  }
}

{
  "headers": {
    "access-control-allow-credentials": "true",
    "access-control-allow-origin": "https://host.epicorsaas.com",
    "access-control-expose-headers": "ContextHeader",
    "cache-control": "no-cache",
    "callinfo": "{\"CorrelationId\":\"bc9f\"}",
    "connection": "keep-alive",
    "content-length": "461910",
    "content-type": "application/json; charset=utf-8",
    "date": "Tue, 20 Jan 2026 16:34:36 GMT",
    "telemetry": "{\"TelemetryKey\":\"b6f48\",\"Telemetry\":true}",
    "vary": "Origin"
  },
  "body": {
    "returnObj": [
      {
        "Id": "Erp.UI.ProjectEntry",
        "Type": "view",
        "SubType": "Apps",
        "LastUpdated": "2026-01-20T00:00:00",
        "IsLayerDisabled": false,
        "SystemFlag": true,
        "HasDraftContent": false,
        "CreatedBy": "",
        "CanAccessBase": false,
        "SecurityCode": "",
        "LastUpdatedBy": ""
      },
      {
        "Id": "Erp.UI.POEntry",
        "Type": "view",
        "SubType": "Apps",
        "LastUpdated": "2026-01-19T00:00:00",
        "IsLayerDisabled": false,
        "SystemFlag": true,
        "HasDraftContent": false,
        "Layers": [
          {
            "Id": "Erp.UI.POEntry",
            "SubType": "Apps",
            "LastUpdated": "2024-08-24T00:00:00",
            "IsPublished": true,
            "IsSilentExport": false,
            "TypeCode": "KNTCCustLayer",
            "Company": "ACME_LAB",
            "LayerName": "ACME_Science",
            "DeviceType": "Desktop",
            "CGCCode": "",
            "SystemFlag": false,
            "HasDraftContent": false,
            "LastUpdatedBy": "EpicAdmin"
          }
      },
      {
        "Id": "Erp.UI.PartEntry",
        "Type": "view",
        "SubType": "Apps",
        "LastUpdated": "2026-01-20T00:00:00",
        "IsLayerDisabled": false,
        "SystemFlag": true,
        "HasDraftContent": false,
        "Layers": [
          {
            "Id": "Erp.UI.PartEntry",
            "SubType": "Apps",
            "LastUpdated": "2025-12-17T00:00:00",
            "IsPublished": true,
            "IsSilentExport": false,
            "TypeCode": "KNTCPersLayer",
            "Company": "ACME_LAB",
            "LayerName": "userID_1^ACME_Science",
            "DeviceType": "Desktop",
            "CGCCode": "",
            "SystemFlag": false,
            "HasDraftContent": false,
            "LastUpdatedBy": "userID_1"
          }.
          {
            "Id": "Erp.UI.PartEntry",
            "SubType": "Apps",
            "LastUpdated": "2025-04-09T00:00:00",
            "IsPublished": true,
            "IsSilentExport": false,
            "TypeCode": "KNTCPersLayer",
            "Company": "ACME_LAB",
            "LayerName": "userID_2^ACME_Science",
            "DeviceType": "Desktop",
            "CGCCode": "",
            "SystemFlag": false,
            "HasDraftContent": false,
            "LastUpdatedBy": "userID_2"
          }
      }
    ]
  },
  "status": 200,
  "statusText": "OK"
}

{
  "method": "POST",
  "url": "https://host.epicorsaas.com/SaaS681/api/v2/odata/ACME_LAB/Ice.LIB.MetaFXSvc/ExportLayers",
  "queryParams": {},
  "headers": {
    "Content-Type": "application/json",
    "Authorization": "Bearer .....",
    "callSettings": "{\"Company\":\"ACME_LAB\",\"Plant\":\"ACME_01\"}",
    "SessionInfo": "{\"sessionId\":\"45058b\"}",
    "x-epi-request-etag": "true",
    "x-epi-extension-serialization": "full-metadata",
    "Accept": "application/json, text/plain, */*"
  },
  "body": {
    "apps": [
      {
        "Id": "Erp.UI.POEntry",
        "SubType": "Apps",
        "LastUpdated": "2024-08-24T06:00:00.000Z",
        "IsPublished": true,
        "IsSilentExport": false,
        "TypeCode": "KNTCCustLayer",
        "Company": "ACME_LAB",
        "LayerName": "ACME_Science",
        "DeviceType": "Desktop",
        "CGCCode": "",
        "SystemFlag": false,
        "HasDraftContent": false,
        "LastUpdatedBy": "EpicAdmin",
        "Type": "view",
        "initialPosition": 1815,
        "UniqueId": "Erp.UI.POEntryACME_ScienceKNTCCustLayerDesktop",
        "Status": 0,
        "IsLayerDisabled": false
      },
      {
        "Id": "Erp.UI.PartEntry",
        "SubType": "Apps",
        "LastUpdated": "2025-12-17T07:00:00.000Z",
        "IsPublished": true,
        "IsSilentExport": false,
        "TypeCode": "KNTCPersLayer",
        "Company": "ACME_LAB",
        "LayerName": "UserID_1^ACME_Science",
        "DeviceType": "Desktop",
        "CGCCode": "",
        "SystemFlag": false,
        "HasDraftContent": false,
        "LastUpdatedBy": "cbanwo",
        "Type": "view",
        "initialPosition": 1906,
        "UniqueId": "Erp.UI.PartEntryUserID_1^ACME_ScienceKNTCPersLayerDesktop",
        "Status": 0,
        "IsLayerDisabled": false
      },
      {
        "Id": "Erp.UI.PartEntry",
        "SubType": "Apps",
        "LastUpdated": "2025-04-09T06:00:00.000Z",
        "IsPublished": true,
        "IsSilentExport": false,
        "TypeCode": "KNTCPersLayer",
        "Company": "ACME_LAB",
        "LayerName": "UserID_2^ACME_Science",
        "DeviceType": "Desktop",
        "CGCCode": "",
        "SystemFlag": false,
        "HasDraftContent": false,
        "LastUpdatedBy": "choneyman",
        "Type": "view",
        "initialPosition": 1907,
        "UniqueId": "Erp.UI.PartEntryUserID_2^ACME_ScienceKNTCPersLayerDesktop",
        "Status": 0,
        "IsLayerDisabled": false
      }
    ]
  }
}

{
  "headers": {
    "access-control-allow-credentials": "true",
    "access-control-allow-origin": "https://host.epicorsaas.com",
    "access-control-expose-headers": "ContextHeader",
    "cache-control": "no-cache",
    "callinfo": "{\"CorrelationId\":\"f03c827d-7e89-44d1-8397-544199ae8c71\"}",
    "connection": "keep-alive",
    "content-length": "22744",
    "content-type": "application/json; charset=utf-8",
    "date": "Tue, 20 Jan 2026 16:55:11 GMT",
    "telemetry": "{\"TelemetryKey\":\"b6f48e22-5af5-4dd9-a40c-769900bae46c\",\"Telemetry\":true}",
    "vary": "Origin"
  },
  "body": {
    "returnObj": "<BLOB>"
  },
  "status": 200,
  "statusText": "OK"
}

I thought I would turn my hand at this; but I’m running into an error:

{
  "method": "POST",
  "url": "https://host.epicorsaas.com/instance/api/v2/odata/CompanyID/Ice.Lib.FileTransferSvc/DownloadFileForCompany",
  "queryParams": {},
  "headers": {
    "Content-Type": "application/json; charset=utf-8",
    "Authorization": "Bearer ey....ek",
    "SessionInfo": "{\"sessionId\":\"79cf60ab-081c-44c9-8a97-0a1c93280251\"}",
    "callSettings": "{\"Company\":\"CompanyID\",\"Plant\":\"Plant_ID\"}",
    "x-epi-request-etag": "true",
    "x-epi-extension-serialization": "full-metadata",
    "Accept": "application/json, text/plain, */*"
  },
  "body": {
    "folder": 5,
    "serverPath": "EATT://ExportAllKineticPersonalizationLayers",
    "companyID": "MS67964"
  }
}
{
  "headers": {
    "access-control-allow-credentials": "true",
    "access-control-allow-origin": "https://host.epicorsaas.com",
    "access-control-expose-headers": "ContextHeader",
    "cache-control": "no-cache,no-store",
    "callinfo": "{\"CorrelationId\":\"1ad3cf82-5dfa-4596-bfc4-6e12091a2360\"}",
    "connection": "keep-alive",
    "content-type": "application/json",
    "date": "Thu, 22 Jan 2026 15:09:25 GMT",
    "expires": "-1",
    "pragma": "no-cache",
    "server": "nginx",
    "telemetry": "{\"TelemetryKey\":\"\",\"Telemetry\":true}",
    "transfer-encoding": "chunked",
    "vary": "Origin"
  },
  "body": {
    "HttpStatus": 400,
    "ReasonPhrase": "REST API Exception",
    "ErrorMessage": "Ice.BLException: Failure running plugin with path: \"EATT://ExportAllKineticPersonalizationLayers\"\r\n   at Epicor.Customization.Bpm.BO.DownloadFileForCompanyPreProcessingDirective_InterceptFileDownloadForCompanyRequest_61344827A11840BB948C74C379F718B0.A001_CustomCodeAction()",
    "ErrorType": "Ice.BLException",
    "ErrorDetails": [
      {
        "Message": "Ice.BLException: Failure running plugin with path: \"EATT://ExportAllKineticPersonalizationLayers\"\r\n   at Epicor.Customization.Bpm.BO.DownloadFileForCompanyPreProcessingDirective_InterceptFileDownloadForCompanyRequest_61344827A11840BB948C74C379F718B0.A001_CustomCodeAction()",
        "Type": "Error",
        "Program": "Ice.Lib.FileTransfer.DownloadFileForCompany.dll",
        "Method": "A001_CustomCodeAction"
      }
    ],
    "CorrelationId": "1ad3cf82-5dfa-4596-bfc4-6e12091a2360"
  },
  "status": 400,
  "statusText": "Bad Request"
}
/*
* ==========================================================================================
* AUTHOR:    Kevin Barrow
* COPYRIGHT: Kevin Barrow 2026
* LICENSE:   MIT
* ==========================================================================================
* Library:     ExportAllTheThings
* Function:    ExportAllKineticPersonalizationLayers
* Description: This plugin downloads all Kinetic Personalization Layers.
* ==========================================================================================
* 
* INPUTS: NONE
*
* OUTPUTS:
*   BOOL:   Success        -> Function Success / Failure
*   STRING: ListErrorsJson -> Json Serialized List<Exception>
*   STRING: ZipBase64      -> Base64 Encoded Byte Array
*
* CHANGELOG:
* 01/21/2026 | kbarrow   | Kevin Barrow   | Initial Implementation via Core_ExportKineticMetaFX
*
* ==========================================================================================
*/
  //Helper Functions Section----------------------------------------------------------------------------------------------------------------------------------------->
  Func<Exception, string, string> AddExceptionToList = (exception, exceptionListJson) =>
  {
      List<Exception> exceptionList = new List<Exception>(){exception};
      if(!String.IsNullOrEmpty(exceptionListJson)) { try { exceptionList.AddRange( JsonConvert.DeserializeObject<List<Exception>>(exceptionListJson) ); } catch {} }
      return JsonConvert.SerializeObject(exceptionList);
  };
  //<-----------------------------------------------------------------------------------------------------------------------------------------Helper Functions Section
 
 
  try
  {
  //****
  
     CallService<Ice.Contracts.MetaFXSvcContract>(metaFX =>
     {
        // Configuration for Personalization Layers
        var config = new 
        {
            ExportBaseApps = false,
            ExportLayers = true,
            SystemFlag = false,
            LayerTypeCodes = new [] { "KNTCPersLayer" },
            IncludePersLayers = true
        };
        
        string configJson = JsonConvert.SerializeObject(config);
        
        // Call Centralized Core Function
        string resultJson = ThisLib.Core_ExportKineticMetaFX(configJson);
        
        dynamic result = JsonConvert.DeserializeObject(resultJson);
        Success = result.Success;
        ListErrorJson = result.ListErrorJson;
        ZipBase64 = result.ZipBase64;
     }); 
     
  //****   
  }
  catch (Exception ex)
  {
      Success = false;
      ListErrorJson = AddExceptionToList(ex, ListErrorJson);
  }
  finally
  {
      //Maybe later?
  }
/*
* ==========================================================================================
* AUTHOR:    Kevin Barrow
* COPYRIGHT: Kevin Barrow 2026
* LICENSE:   MIT
* ==========================================================================================
* Library:     ExportAllTheThings
* Function:    Core_ExportKineticMetaFX
* Description: Centralized logic for exporting Kinetic MetaFX artifacts.
* ==========================================================================================
* 
* INPUTS:
*   STRING: ConfigJson -> JSON configuration object defining what to export.
*           {
*             "ExportBaseApps": bool,
*             "ExportLayers": bool,
*             "SystemFlag": bool? (null=all, true=system, false=custom),
*             "LayerTypeCodes": [string],
*             "CreatedBy": string,
*             "SearchText": string,
*             "IncludePersLayers": bool
*           }
*
* OUTPUTS:
*   STRING: ResultJson -> JSON object { Success, ListErrorJson, ZipBase64 }
*
* CHANGELOG:
* 01/21/2026 | kbarrow   | Kevin Barrow   | Initial Implementation
* 01/22/2026 | kbarrow   | Kevin Barrow   | Added SearchText and IncludePersLayers support
*
* ==========================================================================================
*/

  Func<Exception, string, string> AddExceptionToList = (exception, exceptionListJson) =>
  {
      List<Exception> exceptionList = new List<Exception>(){exception};
      if(!String.IsNullOrEmpty(exceptionListJson)) { try { exceptionList.AddRange( JsonConvert.DeserializeObject<List<Exception>>(exceptionListJson) ); } catch {} }
      return JsonConvert.SerializeObject(exceptionList);
  };

  string zipBase64 = "";
  string listErrorJson = "";
  bool success = false;

  try
  {
      dynamic config = JsonConvert.DeserializeObject(ConfigJson);
      bool exportBaseApps = config.ExportBaseApps ?? false;
      bool exportLayers = config.ExportLayers ?? false;
      bool? systemFlag = config.SystemFlag; 
      JArray layerTypeCodesJ = config.LayerTypeCodes;
      List<string> layerTypeCodes = layerTypeCodesJ?.ToObject<List<string>>();
      string createdBy = config.CreatedBy;
      string searchText = config.SearchText;
      bool includePersLayers = config.IncludePersLayers ?? false;

      // Auto-enable personalization fetch if we are asking for personalization layers
      if (layerTypeCodes != null && layerTypeCodes.Contains("KNTCPersLayer"))
      {
          includePersLayers = true;
      }

      CallService<Ice.Contracts.MetaFXSvcContract>(metaFX =>
      {
          var request = new Epicor.MetaFX.Core.Models.Applications.ApplicationRequest()
          {
              Type = "view",
              SubType = "",
              SearchText = searchText ?? "",
              IncludeAllLayers = true,
              IncludePersLayers = includePersLayers
          };
          var applications = metaFX.GetApplications(request);
          Dictionary<string, string> mainZipFileDictionary = new Dictionary<string, string>();

          foreach(var app in applications)
          {
              // Base Apps Logic
              if (exportBaseApps)
              {
                  bool match = true;
                  if (systemFlag.HasValue) match = (app.SystemFlag == systemFlag.Value);
                  if (match)
                  {
                      try
                      {
                          var list = new List<Epicor.MetaFX.Core.Models.Layers.EpMetaFxLayerForApplication> { 
                              new Epicor.MetaFX.Core.Models.Layers.EpMetaFxLayerForApplication() { Id = app.Id } 
                          };
                          byte[] bytes = metaFX.ExportLayers(list);
                          mainZipFileDictionary.Add($"{app.Id}.zip", Convert.ToBase64String(bytes));
                      }
                      catch (Exception ex)
                      {
                          ex.Data.Add("AppID", app.Id);
                          listErrorJson = AddExceptionToList(ex, listErrorJson);
                      }
                  }
              }
              
              // Layers Logic
              if (exportLayers && app.Layers != null)
              {
                  foreach(var layer in app.Layers)
                  {
                      bool match = true;
                      // For layers, SystemFlag usually denotes if the layer itself is system-owned
                      if (systemFlag.HasValue) match = (layer.SystemFlag == systemFlag.Value);
                      
                      if (match && layerTypeCodes != null && layerTypeCodes.Count > 0) 
                          match = layerTypeCodes.Contains(layer.TypeCode);
                      
                      if (match && !string.IsNullOrEmpty(createdBy)) 
                          match = (layer.LastUpdatedBy == createdBy || layer.CreatedBy == createdBy);
                      
                      if (match)
                      {
                          try
                          {
                              var list = new List<Epicor.MetaFX.Core.Models.Layers.EpMetaFxLayerForApplication> { layer };
                              byte[] bytes = metaFX.ExportLayers(list);
                              string name = $"{app.Id}_{layer.LayerName}_{layer.TypeCode}.zip";
                              name = name.Replace(" ", "_").Replace("^", "_");
                              mainZipFileDictionary.Add(name, Convert.ToBase64String(bytes));
                          }
                          catch (Exception ex)
                          {
                              ex.Data.Add("Layer", layer.LayerName);
                              listErrorJson = AddExceptionToList(ex, listErrorJson);
                          }
                      }
                  }
              }
          }
          
          if (mainZipFileDictionary.Count > 0)
          {
              string dictJson = JsonConvert.SerializeObject(mainZipFileDictionary);
              zipBase64 = ThisLib.ZipFiles(dictJson);
          }
      });
      success = true;
  }
  catch (Exception ex)
  {
      success = false;
      listErrorJson = AddExceptionToList(ex, listErrorJson);
  }

  ResultJson = JsonConvert.SerializeObject(new { Success = success, ListErrorJson = listErrorJson, ZipBase64 = zipBase64 });

@spaceage did you ever figure out the sytax for getting custom layers for both system apps and non-system? I tried the above and got all system apps but not custom layer .jsonc

I never did figure it out, but I didn’t put a ton of time into it either.

If you take a look at following it may give you the insight into what needs done, I tried turning my hand at it, but I haven’t had a lot of time to delve into it.

Essentially though ‘Layers’ are a sub-component of a “Base Layer”

My code was executing but wasn’t returning anything, it may be the push you need to get everything working.

@klincecum So I’m actually getting a similar error with my attempt at a custom function as I get when I try to run your code for system layers.

I wonder if that means my code will work for someone with the Epicor SDK?
…

That one is broken for me too. I haven’t had time to revisit.

WOOT!!! WOOT!!!
@klincecum @jbooker @spaceage

/*
* ==========================================================================================
* AUTHOR:    Kevin Barrow
* COPYRIGHT: Kevin Barrow 2026
* LICENSE:   MIT
* ==========================================================================================
* Library:     ExportAllTheThings
* Function:    ExportAllKineticPersonalizationLayers
* Description: This plugin downloads all Kinetic Personalization Layers.
* ==========================================================================================
* 
* INPUTS: NONE
*
* OUTPUTS:
*   BOOL:   Success        -> Function Success / Failure
*   STRING: ListErrorsJson -> Json Serialized List<Exception>
*   STRING: ZipBase64      -> Base64 Encoded Byte Array
*
* CHANGELOG:
* 01/21/2026 | kbarrow | Initial Implementation via Core_ExportKineticMetaFX
* 01/28/2026 | kbarrow | BPM-safe normalize + single-pass + no member declarations
* ==========================================================================================
*/

// --- Helpers as delegates (BPM-safe: no method declarations) ---
Func<object, string> ToBase64 = exportResult =>
{
    if (exportResult == null) return null;

    var bytes = exportResult as byte[];
    if (bytes != null && bytes.Length > 0)
        return Convert.ToBase64String(bytes);

    var s = exportResult as string;
    if (!string.IsNullOrWhiteSpace(s))
    {
        // If it looks like a file path, try to read bytes
        if (s.IndexOfAny(new[] {'\\','/'}) >= 0)
        {
            try
            {
                var fileBytes = System.IO.File.ReadAllBytes(s);
                return Convert.ToBase64String(fileBytes);
            }
            catch
            {
                // If file read fails, fall through and assume it's already base64
            }
        }
        return s;
    }
    return null;
};

Func<Exception, string, string> AddExceptionToList = (exception, exceptionListJson) =>
{
    List<Exception> exceptionList = new List<Exception>() { exception };
    if (!String.IsNullOrEmpty(exceptionListJson))
    {
        try
        {
            var prior = JsonConvert.DeserializeObject<List<Exception>>(exceptionListJson);
            if (prior != null) exceptionList.AddRange(prior);
        }
        catch
        {
            // ignore JSON issues
        }
    }
    return JsonConvert.SerializeObject(exceptionList);
};

// --- Outputs ---
string zipBase64 = "";
string listErrorJson = "";
bool success = false;

try
{
    // --- Input config (FIRST) ---
    dynamic config = JsonConvert.DeserializeObject(ConfigJson);
    bool exportBaseApps = config.ExportBaseApps ?? false;
    bool exportLayers   = config.ExportLayers ?? false;
    bool? systemFlag    = config.SystemFlag;
    JArray layerTypeCodesJ = config.LayerTypeCodes;
    List<string> layerTypeCodes = layerTypeCodesJ?.ToObject<List<string>>();
    string createdBy    = config.CreatedBy;
    string searchText   = config.SearchText;
    bool includePersLayers = config.IncludePersLayers ?? false;

    // Ensure personalization layers included if requested explicitly
    if (layerTypeCodes != null && layerTypeCodes.Contains("KNTCPersLayer"))
        includePersLayers = true;

    // If nothing to export, finish early
    if (!exportBaseApps && !exportLayers)
    {
        success = true;
        ResultJson = JsonConvert.SerializeObject(new { Success = success, ListErrorJson = listErrorJson, ZipBase64 = zipBase64 });
        return;
    }

    // --- Main service call (SINGLE PASS) ---
    CallService<Ice.Contracts.MetaFXSvcContract>(metaFX =>
    {
        var request = new Epicor.MetaFX.Core.Models.Applications.ApplicationRequest()
        {
            Type = "view",
            SubType = "",
            SearchText = searchText ?? "",
            IncludeAllLayers = true,
            IncludePersLayers = includePersLayers
        };

        var applications = metaFX.GetApplications(request);
        Dictionary<string, string> mainZipFileDictionary = new Dictionary<string, string>();

        foreach (dynamic app in applications)
        {
            // --- BASE APPS ---
            if (exportBaseApps)
            {
                bool match = true;
                if (systemFlag.HasValue) match = (app.SystemFlag == systemFlag.Value);
                if (match)
                {
                    try
                    {
                        var baseAppExport = new Epicor.MetaFX.Core.Models.Layers.EpMetaFxLayerForApplication
                        {
                            Id = app.Id.ToString(),
                            Company = Session.CompanyID,
                            DeviceType = "Desktop"
                        };

                        var list = new List<Epicor.MetaFX.Core.Models.Layers.EpMetaFxLayerForApplication> { baseAppExport };

                        var exportObj = metaFX.ExportLayers(list);
                        string base64 = ToBase64(exportObj);
                        if (!string.IsNullOrEmpty(base64))
                        {
                            string fileName = $"{app.Id.ToString()}.zip";
                            if (!mainZipFileDictionary.ContainsKey(fileName))
                                mainZipFileDictionary.Add(fileName, base64);
                        }
                    }
                    catch (Exception ex)
                    {
                        listErrorJson = AddExceptionToList(ex, listErrorJson);
                    }
                }
            }

            // --- LAYERS ---
            if (exportLayers && app.Layers != null)
            {
                foreach (dynamic layer in app.Layers)
                {
                    bool match = true;
                    if (systemFlag.HasValue) match = (layer.SystemFlag == systemFlag.Value);
                    if (match && layerTypeCodes != null && layerTypeCodes.Count > 0)
                        match = layerTypeCodes.Contains((string)layer.TypeCode);
                    if (match && !string.IsNullOrEmpty(createdBy))
                        match = (layer.LastUpdatedBy == createdBy || layer.CreatedBy == createdBy);

                    if (!match) continue;

                    try
                    {
                        var exportItem = new Epicor.MetaFX.Core.Models.Layers.EpMetaFxLayerForApplication
                        {
                            Id = layer.Id.ToString(),
                            LayerName = layer.LayerName.ToString(),
                            TypeCode = layer.TypeCode.ToString(),
                            Company = (layer.Company != null) ? layer.Company.ToString() : Session.CompanyID,
                            DeviceType = (layer.DeviceType != null) ? layer.DeviceType.ToString() : "Desktop",
                            IsPublished = true
                        };

                        var list = new List<Epicor.MetaFX.Core.Models.Layers.EpMetaFxLayerForApplication> { exportItem };

                        var exportObj2 = metaFX.ExportLayers(list);
                        string base64Data = ToBase64(exportObj2);

                        if (!string.IsNullOrEmpty(base64Data))
                        {
                            var safeLayerName = System.Text.RegularExpressions.Regex.Replace(
                                layer.LayerName.ToString(), @"[^a-zA-Z0-9_.\- ]", "_");
                            string fileName = $"{app.Id.ToString()}_{safeLayerName}.zip";
                            if (!mainZipFileDictionary.ContainsKey(fileName))
                                mainZipFileDictionary.Add(fileName, base64Data);
                        }
                    }
                    catch (Exception ex)
                    {
                        listErrorJson = AddExceptionToList(ex, listErrorJson);
                    }
                }
            }
        }

        if (mainZipFileDictionary.Count > 0)
        {
            string dictJson = JsonConvert.SerializeObject(mainZipFileDictionary);
            zipBase64 = ThisLib.ZipFiles(dictJson);
        }
    });

    success = true;
}
catch (Exception ex)
{
    success = false;
    listErrorJson = AddExceptionToList(ex, listErrorJson);
}

ResultJson = JsonConvert.SerializeObject(new { Success = success, ListErrorJson = listErrorJson, ZipBase64 = zipBase64 });

ExportAllTheThings.efxj (85.8 KB)


:hammer_and_wrench: Reply Overhauled

This response has been overhauled to incorporate expanded details that reflect the enhancements.

@klincecum I’ve forked your repo and submitted a pull request with some significant enhancements.


For those looking for the most current version while the PR is pending, you can find the updated file and source below.

:down_arrow: Download / Source


:rocket: Enhancements & Fixes

This version acts as a drop-in replacement for the original, adding comprehensive support for Kinetic layers and cross-company stability:

  • Kinetic System Layers: Support for Base Applications. (Enhanced)
  • Kinetic Customization Layers: Support for modified Base Layers at the menu level. (Added)
  • Kinetic Custom Layers: Support for user-created layers (e.g., Kinetic Dashboards). (Enhanced)
  • Kinetic Personalization: Support for user-level modifications. (Added)
  • Code Consolidation: Centralized logic for exporting all four layer types.
  • Multi-Company Fixes: Resolved issues with layer resolution/exportation when working in multi-company environments.

Kevin, when you have an opportunity to test/review, it would be great to get these updates merged and pinned to the top!

Best regards,

Kevin