Done. Not in the library yet, but here ya go…
ExportAllTheThings.ExportAllKineticCustomLayers
/*
* ==========================================================================================
* AUTHOR: Kevin Lincecum
* COPYRIGHT: Kevin Lincecum 2024
* LICENSE: MIT
* ==========================================================================================
* Library: ExportAllTheThings
* Function: ExportAllKineticCustomLayers
* Description: This plugin downloads all custom Kinetic layers.
* ==========================================================================================
*
* INPUTS: NONE
*
* OUTPUTS:
* BOOL: Success -> Function Success / Failure
* STRING: ListErrorsJson -> Json Serialized List<Exception>
* STRING: ZipBase64 -> Base64 Encoded Byte Array
*
* CHANGELOG:
* 09/04/2024 | klincecum | Kevin Lincecum | Initial Implementation
*
* ==========================================================================================
*/
//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 =>
{
//Create a request to list the apps
var request = new Epicor.MetaFX.Core.Models.Applications.ApplicationRequest()
{
Type = "view",
SubType = "",
SearchText = "",
IncludeAllLayers = true
};
//Get a list of apps
List<Epicor.MetaFX.Core.Models.Applications.Application> applications = metaFX.GetApplications(request);
//Create an export request list
List<Epicor.MetaFX.Core.Models.Layers.EpMetaFxLayerForApplication> applicationList = new List<Epicor.MetaFX.Core.Models.Layers.EpMetaFxLayerForApplication>();
//Loop through the list and add custom apps to the export list
foreach(var item in applications.Where(x => x.SystemFlag == false))
{
applicationList.Add(new Epicor.MetaFX.Core.Models.Layers.EpMetaFxLayerForApplication() { Id = item.Id });
}
//Export the apps and return the zip file data as a Base64 encoded string.
ZipBase64 = metaFX.ExportLayers(applicationList);
});
Success = true;
//****
}
catch (Exception ex)
{
Success = false;
ListErrorJson = AddExceptionToList(ex, ListErrorJson);
}
finally
{
//Maybe later?
}
ExportAllTheThings.ExportAllKineticSystemLayers
/*
* ==========================================================================================
* AUTHOR: Kevin Lincecum
* COPYRIGHT: Kevin Lincecum 2024
* LICENSE: MIT
* ==========================================================================================
* Library: ExportAllTheThings
* Function: ExportAllKineticSystemLayers
* Description: This plugin downloads all system Kinetic layers.
* ==========================================================================================
*
* INPUTS: NONE
*
* OUTPUTS:
* BOOL: Success -> Function Success / Failure
* STRING: ListErrorsJson -> Json Serialized List<Exception>
* STRING: ZipBase64 -> Base64 Encoded Byte Array
*
* CHANGELOG:
* 09/04/2024 | klincecum | Kevin Lincecum | Initial Implementation
*
* ==========================================================================================
*/
//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 =>
{
//Create a request to list the apps
var request = new Epicor.MetaFX.Core.Models.Applications.ApplicationRequest()
{
Type = "view",
SubType = "",
SearchText = "",
IncludeAllLayers = true
};
//Get a list of apps
List<Epicor.MetaFX.Core.Models.Applications.Application> applications = metaFX.GetApplications(request);
//Create an export request list
List<Epicor.MetaFX.Core.Models.Layers.EpMetaFxLayerForApplication> applicationList = new List<Epicor.MetaFX.Core.Models.Layers.EpMetaFxLayerForApplication>();
//Loop through the list and add custom apps to the export list
foreach(var item in applications.Where(x => x.SystemFlag == true))
{
applicationList.Add(new Epicor.MetaFX.Core.Models.Layers.EpMetaFxLayerForApplication() { Id = item.Id });
}
//Export the apps and return the zip file data as a Base64 encoded string.
ZipBase64 = metaFX.ExportLayers(applicationList);
});
Success = true;
//****
}
catch (Exception ex)
{
Success = false;
ListErrorJson = AddExceptionToList(ex, ListErrorJson);
}
finally
{
//Maybe later?
}
In the library at the top, and on github.
I have been a big advocate of just splitting custom stuff from the std db. Just having a setting in either your database settings or server settings that allows you to define what custom dB everthing is pointing to is a sensible approach. I have seen this approach used successfully with other software
I hope Epicor continue down the path of the common dB.
Data related to ud tables may be of concern, not really sure how to explain what I mean there, but if we are only talking about just the definition of the fields should be OK…
Having everything custom in a separate dB then allows you to compare customs pretty easily Compare and Synchronize the Data of Two Databases - SQL Server Data Tools (SSDT) | Microsoft Learn
Which makes the upgrade process a lot easier not to mention development against the demo dB @Mark_Wonsil ![]()
Awesome work @klincecum
UD fields → Done, not posted yet
Classic Form Customizations → Done, not posted yet
Form customizations are currently exported as rows tablesets, because putting it the xml export format is hard. Will revisit. (maybe?
)
It’s a real nice templating system that lets him do this so quickly.
I just found this. I think its gonna really help us thanks. We go live end of October, we have a basedata environment which is supposed to be our starting point for cutovcer, as well as various test and dev environments which a number of remote consultants have been working on customizations in. just tracking down what’s custom, and what’s missing in each envirnoment is a chore; hopefully this helps!
Has anybody actually tried it?
@klincecum
Perhaps I’m missing something but functionally, how is this different than the Solution Workbench?
That’s been my go to for Dev to Prod deployments.
No shade, just trying to understand.
No problem Rick.
1 → This is a demo of a concept for doing a dynamic app in Kinetic.
2 → Depends on your use cases.
I didn’t build this into a fully featured product, the plugins are all or nothing, but I use it for backups for development.
Solution workbench leaves a lot to be desired for that.
I also plan to reuse the functions as part of some devops practices.
Example → I’ve been working on a bunch of function libraries. The CFO calls and says he needs new data in pilot to test some stuff. Previously, I had to go figure out what I was working on and export all that stuff out manually.
Now I open the app, click the export libraries button, and get my zip. Then I push his data.
I can import what I want back in later at my discretion.
Plan to use the backend of this (or something based on the same concepts) for versioning and storage at some point.
Added those today. Sorry, had them done on the 9th ![]()
Been busy.
Or someone else goes ahead and restores without mentioning it, wiping out a week of work ![]()

Well as for the song choice, @Mark_Wonsil always told me to go for the brass ring..
@Banderson can check his sound, and @hmwillett can die laughing.
For the rest of you, I’m sorry.
I tried.
Any suggestions as to why I’m not seeing the app in Menu Maintenance App Search?
I don’t know, I’ve never imported one myself. I can try to import it into my live later to see how it goes.
Ok, what I had to do was open the base in App Studio, and publish it. Then it showed up in menu maintenance.
Ah. It showed Published=true so didn’t try that. Worked! Thanks!
FYI - Export ALL Custom Report Data gives Invalid Company error.