Multi Company and Security

Can someone tell me where I can find some good information on setting up security groups and access in a multi company environment? I looked and the Epicor Multi Company user guide and it doesn’t talk about security.

Right now we set up a security group it shows the own company but can be seen in both companies. I do notice that there is an All Company check box on the back end but not maintainable. This is checked. Anyone know what would happen if I DMT to uncheck it? If there All company field there why not have it maintainable on the main screen in Security Group maintenance.

Nothing out of the box, besides duplicating Menu Items and creating alot of Security Groups prefixed with the Company XYZ_SalesAdmin ETM_SalesAdmin GLOBAL_SalesAdmin…



@Mark_Wonsil do you have a link to that post where best practice was discussed or thrown around?

1 Like

AFAIK, Security Groups (like Users) are global.

We do what @hkeric.wci mentions: we prepend the company ID for company specific groups and a global prefix for groups that apply to all companies.

However, we are getting ready to redo our security model. The fact is, Menu Security is really not security at all except for customizations. Just because you block a menu item, that doesn’t mean a user cannot access functionality in other ways (DMT, REST, etc.)

Following @timshuwy’s advice, we are going to secure programs and data first. We’ve used capacity-based groups to date (CanAddSalesOrder, CanViewSalesOrder, etc.) but then each person has many security groups. This isn’t bad in that it gives finer level of control but it would be nice to have another layer called Role where each role has multiple capabilities. That way it would be easy to duplicate capabilities with a new hire or a temp user.

Still working on a scheme for data. I want to enforce that all BAQs have a security group here shortly.

The business object is the base layer to control. This makes things like giving some people GL Entry without giving Post Journal access.

As @hkeric.wci mentioned, the SOX work would be done at the capability level and then the checking would be done against users with conflicting capabilities. I can see a table to build to describe the separation of duties and then check for conflicts on the assigned capabilities.

Big topic…

1 Like

Yep, we also have sometimes a Customization in a lower company where we would like to Run a BAQ and Grab Data from the Master Company without having to Pass Data Down… like PartRev is not Multi-Company Friendly which makes sense, but we wanted to avoid some Service Connect stuff as we want to replace it with APIs…

There are such cases too where you have Master Data and you don’t want to give Employees Access to Upper Companies…

In that case I had to instantiate a New Session as the SysAgent and Execute the BAQ as a Impersonated System Agent who has access to all Companies… Works Great by the way!

Save this Snippet! Took Days to get right! Runs and honors Session Licensing timeouts etc… Also instead of managing another Service Account, I used the “Agent User” a method discovered in the MRP Engine. Works like a charm!

// Session Impersonation Vars
Ice.Core.Session newSession;

// InitCustomCode
this.newSession = this.GetNewSessionAsSysAgent();

// DestroyCustomCode
// Begin Custom Code Disposal
Ice.Proxy.Lib.SessionModImpl sMod = GetInstanceField(typeof(Ice.Core.Session), newSession, "sessionMod") as Ice.Proxy.Lib.SessionModImpl;
this.newSession = null; // Do NOT Dispose it, it will kill Epicor

// We need a Session Link to 003 which Local Users
// may not have access to - lets Auth as Agent
// could be refactored a bit
public Ice.Core.Session GetNewSessionAsSysAgent()
	string username = string.Empty;
	string password = string.Empty;

	object searchReslts = ProcessCaller.PerformSearch(oTrans.EpiBaseForm, "SysAgentAdapter", "Rows", string.Empty);

	if (searchReslts is DataSet) {
		DataSet searchDS = (DataSet) searchReslts;

		if (searchDS.Tables[0].Rows.Count > 0) {
			username = searchDS.Tables["SysAgent"].Rows[0]["SysUserID"].ToString();
			password = searchDS.Tables["SysAgent"].Rows[0]["SysPassWord"].ToString();

	if (username != string.Empty && password != string.Empty)
		try {
			return new Ice.Core.Session(username, Epicor.Security.Cryptography.Encryptor.DecryptToString(password));
		catch (Exception ex)
			EpiMessageBox.Show("Unable to Authenticate with Company 00300000.\n" + ex.Message, "SMI Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

	return null;

internal static object GetInstanceField(Type type, object instance, string fieldName)
	BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
		| BindingFlags.Static;
	FieldInfo field = type.GetField(fieldName, bindFlags);
	return field.GetValue(instance);

public System.Data.DataSet GetBAQDataSetDataTagFromMainCompany(string partNum, string partRev)
	if (newSession == null)
		MessageBox.Show("Unable to Authenticate with Company 003, Please See System Administrator");
		return null;

	using (this.oTrans.PushDisposableStatusText("Initializing Global Query...", true))
		ILauncher iLnch = new ILauncher( newSession );
		//removeEventhandlers(newSession);  // Done Globally

		using (var newTransaction = new EpiTransaction(iLnch))
		using (var adapterDynamicQuery = new DynamicQueryAdapter(newTransaction))

			// Declare and Initialize Variables
			string BAQName = "SMI-zGetDataTagByPartRev";
			Ice.BO.QueryExecutionDataSet ds = new Ice.BO.QueryExecutionDataSet();

			// Add Parameter Rows
			ds.ExecutionParameter.AddExecutionParameterRow("PartNum", partNum, "nvarchar", false, Guid.Empty, "A");
			ds.ExecutionParameter.AddExecutionParameterRow("RevisionNum", partRev, "nvarchar", false, Guid.Empty, "A");
			// Call Adapter method
			adapterDynamicQuery.ExecuteByID(BAQName, ds);

			System.Data.DataSet results = adapterDynamicQuery.QueryResults;
			return results;


	return null;

Def would be nice if On-Prem folks could do this with some ease :slight_smile: tags @Rich

1 Like

Interesting discussion.

So you are wanting a way to use a BAQ to get data from another company without explicitly allowing the user getting the data to have access to the company where the data exists? Kind of an interesting variation on the Cross-Company BAQs we have today. I will think on this…

1 Like

Yes, but under controlled circumstances such as via “Code”. Since most MC Environments house their “Master” Data in a upper company, makes it easy to pull details down when needed in a Customization. The other option obviously would be to push the data down on Update via Service Connect or LINQ Queries. Another option is External BAQs… But this just makes it easier to just use the tools out of the box that we are already using every day, all day.

For those tables or data that does not transfer down out of the box. (MC Processes)

In a BPM you do it by using LINQ since its not isolated to the company ( User Session )

var row = (from x in Db.PartRev where x.Company == "MAIN" select x).FirstOrDefault();

// This is Company CHILD
ttUD05Row.SpecialField_c = row.MyField_c;

I’ve done it with the snippet above! It works. By instantiating another Ice.Session and running the BAQ as a special Service Account or System Agent User “Print” who can run the BAQ against any company.

Almost like a “Run As…” functionality. Because when you have a Cross-Company BAQ its only Cross-Company again if the User has access to those Companies, no way to force it explicitly. Atleast in the Execution Settings or anywhere.

Unless there is an easy way to add new Tables / Columns to the MC Process that are not listed out of the box in Epicor… Like UD100 or UDCodes or PartRev.

the discussion took a turn…from original question.

I would honestly try this in a test environment and see what happens when you update that checkbox via DMT. I know a lot of companies create sec. groups and add a prefix or suffix of the company identifier to the sec. group name to keep track of which company the sec group applies to.

1 Like

Hi Kimberley,
I’ve never found a good guide that covers Epicor’s security system (sec groups, sec IDs, Menu, field and Process security resources. I have built a few different security models over the years of differing user management and privilege delegation. In the end, I now build to the leaf instead of any branches. Each application gets security group(s). Fields and processes get security group(s) if needed.

If you are multi-company and/or multi-site (Plant in the old parlance), you can also create security groups that are organization specific and assign those to users as well. The organization specific groups can then be used with the “Deny” functionality to extend some privileges in one org and not in others - this takes a lot of planning and imagining to get right. Same goes for the field, table (thru BMPs) and process security… same technique but now additional layering.

For some tools, i.e. Quantity Adjustment, GL Control Code, part class, etc I create a “one trick” sec group (QuantAdj) so that these tools can be granted to individuals without granting them to specific IDs directly. Hope that gives you more ideas, at least.

@Rich - I’d like you to consider a different angle to this. I posed this question at the E10help group session last year and @timshuwy (I think) confirmed that it would not work as is. The question being that the Enterprise configurator has no facility for the ‘selling’ company to obtain the ‘cost’ from the manufacturing company, so there is no way for a Cost+ model to be used at the ‘selling’ company. We’ve had to do a ton of code and lookup tables replicated from the MFG company in order for the configurator to calculate costs so the COST+ model would be possible. If a BAQ/Lookup from within the configurator would allow cross company (without the security group/company access) then we could eliminate a ton of code. And I’m surprised the the system supports the multi-company sales model with configurator access, but negates the Cost+ model - or at least I’ve not found another way.