# Where do/should Gateway Scripts live with respect to an inherited project structure?

**URL:** <https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460>\
**Category:** Ignition\
**Tags:** vision\
**Created:** [December 22, 2023, 4:39am UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460 "2023-12-22T04:39:57Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![sherman](https://avatars.discourse-cdn.com/v4/letter/s/e5b9ba/32.png) [@sherman](https://forum.inductiveautomation.com/u/sherman)\
**Post date:** [December 22, 2023, 4:39am UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/1 "2023-12-22T04:39:57Z")

</div>

I have the inheritence structure **base\_script** inherited by **base\_sfc** inherited by **project0\_base** which is then inherited by both **project0\_dev** and **project0\_staging**. It is a mess. But the reason I have done this is because I keep running into project ownership issues, while still wanting to have a project that contains all of my scripts for use downstream.

My current problem is related to Gateway Scripts, and who owns them. I need to use message handler Gateway Scripts to interact with my server filesystem, and they have to run through handlers. I have implemented a handful of functions akin to **ls** and **cat**. Anyway, I want these scripts to be owned by my **base\_script** project, as it contains all the shared scripts, but this is what happens when I try to call a message handler pointed at the Gateway Scripts:

This is when I have the project marked for inheritance and marked disabled:  
`com.inductiveautomation.ignition.client.gateway_interface.GatewayException: com.inductiveautomation.ignition.client.gateway_interface.GatewayException: Project 'base_script' is disabled.`

But if I enable the project it says this: `com.inductiveautomation.ignition.client.gateway_interface.GatewayException: com.inductiveautomation.ignition.client.gateway_interface.GatewayException: Project 'base_script' is inheritable and cannot execute message handler scripts. `

If I copy paste them into **project0\_dev**'s Gateway Scripts, they work! It is both enabled and not inheritable.

My guess is when the project is disabled, the message handlers are offline, fine. But if I activate the project, it rejects the message handlers _because it is inheritable_. But that's the whole point! I don't want to have to manually duplicate these across various active leaf projects, especially when they are system-wide scripts. So I am not sure what to do.

One thing I have considered is making a sibling project to **base\_sfc** that is **base\_gatewayscripts** because I can leave it as inheriting **base\_scripts** but I can also leave it enabled and uninheritable. It's an unappealing workaround, and I don't even know that it would work. A leaf project thats sole purpose is to run gateway filesystem requests 🤷

Any advice would be appreciated. This is one of the final steps required for my current project.

---

<div class="post-metadata">

**Author:** ![bschroeder](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bschroeder/32/13260_2.png) [@bschroeder](https://forum.inductiveautomation.com/u/bschroeder)\
**Post date:** [December 22, 2023, 11:16am UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/2 "2023-12-22T11:16:35Z")

</div>

Resources don't execute in an inherited project. So things like gateway event scripts, messages etc... only execute in a non- inherited project.

What you need to do, is write the actual logic in a library script in your base script project, and then call it with a one line as a message in your lead project where you want it to reside.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 22, 2023, 3:20pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/3 "2023-12-22T15:20:35Z")

</div>

> [@sherman](#):
>
> while still wanting to have a project that contains all of my scripts for use downstream

Put all of the logic in project library scripts, in the "universe" project at the root of your hierarchy, if you must. Then, in _ **leaf** _ projects, use one-line calls to the appropriate logic from event scripts and message handlers. (I don't agree with your desire, but this is what you must do to achieve it.)

I recommend you **not** put all your scripts (eggs) in one inheritable project (basket). I recommend a hierarchy something like this:

```auto
universe
├bg_common
│├gsp
│├background1
│├background2
│└webdev
└ui_common
 ├perspective_common
 │├ui1
 │└ui2
 └vision_common
  ├ui3
  └ui4

```

In this arrangement, `universe` and `*_common` contain only _ **well-tested** _ resources, and are only edited/modified in appropriate maintenance windows in production. (Edits to these will cause scripting restarts in the gateway in all projects under them.)

When developing something that will end up in one of these inheritable projects, use a testing leaf project below it for your development work. When well tested, send the resources to the appropriate inheritable project and delete the testing project.

Note that `perspective_common` is the appropriate place for re-usable Views, and `vision_common` in the right place for reusable windows and templates. All other inheritable projects only contain project library scripts.

{ `gsp` is the gateway scripting project. }

---

<div class="post-metadata">

**Author:** ![sherman](https://avatars.discourse-cdn.com/v4/letter/s/e5b9ba/32.png) [@sherman](https://forum.inductiveautomation.com/u/sherman)\
**Post date:** [December 22, 2023, 11:20pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/4 "2023-12-22T23:20:57Z")

</div>

> I recommend you **not** put all your scripts (eggs) in one inheritable project (basket). I recommend a hierarchy something like this:

Okay, I see this structure as an upgrade of my own.

What is really frustrating is what I _want_ is something that can allow me to invoke gateway scripts from anywhere without having to go through the rigamarole of setting up hardcoded gateway scripts that simply reflect back to project scripts but run in the gateway scope. It makes it very annoying to do filesystem IO.

Do you know of any way to do the equivalent of

```auto
def messageHandler(payload):
    try:
        return getattr(<what_goes_here?>, payload['method'])(*payload['args'], **payload['kwargs'])
    except Exception as e:
        return {"error": str(e)}

```

There's no apparent link in globals() or locals() and while the context menus pop up, I can't seem to call them programmatically.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 23, 2023, 12:52am UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/5 "2023-12-23T00:52:36Z")

</div>

> [@sherman](#):
>
> but run in the gateway scope.

Vision clients and the gateway are truly separate processes, even if running in the same computer. Some form of messaging or RPC is required. Ignition system calls that need gateway action do the RPC for you. If you have a custom requirement, you have to do the RPC yourself (`system.util.sendMessage()` or `system.util.sendRequest()`). Without RPC, all scripting is confined to the JVM in which it runs. Full stop.

---

<div class="post-metadata">

**Author:** ![sherman](https://avatars.discourse-cdn.com/v4/letter/s/e5b9ba/32.png) [@sherman](https://forum.inductiveautomation.com/u/sherman)\
**Post date:** [December 26, 2023, 11:20pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/6 "2023-12-26T23:20:05Z")

</div>

Yes, I understand. I just wish they could be handled more fluidly than the static Gateway Scripts. I tried calling a project script from within the gateway script (on a project that definitely has the script available) and it didn't really work.

I just want to be able to do something like

`result = system.util.gatewayInvocation(callback, *args, **kwargs)` that handles the dispatch of the passed callback via a `system.util.sendRequest` call

It is very annoying to have to generate a gateway script on a leaf project in order to run things in gateway scope, especially so for filesystem commands like reading or writing files.

I made a suggestion on the ideas page: [system.util.gatewayInvocation -- new method for invoking Gateway-scoped scripts from the script console / project scripts | Voters | Inductive Automation](https://inductiveautomation.canny.io/ignition-features-and-ideas/p/systemutilgatewayinvocation-new-method-for-invoking-gateway-scoped-scripts-from)

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 26, 2023, 11:35pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/7 "2023-12-26T23:35:21Z")

</div>

> [@sherman](#):
>
> to do something like

What you've described is the holy grail of malware known as an RCE, Remote Code Execution. Not gonna happen.

Ignition's setup works the way it does so that all scripting that runs in the gateway needs to pre-exist in a project. No dynamic code, short of python `eval`, which itself would have to pre-exist in a project.

On top of that, the `callback` is itself code in the local JVM, not in the gateway JVM, so how do expect it would work?

---

<div class="post-metadata">

**Author:** ![sherman](https://avatars.discourse-cdn.com/v4/letter/s/e5b9ba/32.png) [@sherman](https://forum.inductiveautomation.com/u/sherman)\
**Post date:** [December 27, 2023, 9:31pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/8 "2023-12-27T21:31:30Z")

</div>

> Ignition's setup works the way it does so that all scripting that runs in the gateway needs to pre-exist in a project. No dynamic code, short of python `eval`, which itself would have to pre-exist in a project.  
> On top of that, the `callback` is itself code in the local JVM, not in the gateway JVM, so how do expect it would work?

What if callback is a path to a script in the project library specified? It doesn't need to execute arbitrary code any more than a project script might, just invoke it without having a hardcoded in-between function. It would be conceptually the same as

```auto
# Pseudocodeish
def messageHandler(payload):
    return system.util.runScript(
        payload['projectName'], payload['callback_path'], 
        *payload['args'], **payload['kwargs'])

```

I wasn't really suggesting executing truly arbirary code, I realize thats a massive security issue (although if it's all internal to Ignition... 👀). Just a way to run establish project script code that won't actually function if within the project scope.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 27, 2023, 10:05pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/9 "2023-12-27T22:05:57Z")

</div>

> [@sherman](#):
>
> What if callback is a path to a script in the project library specified?

Have a message handler that does the following:

- Split the supplied function name on the dots.
- Look up the first part in python's normal `globals()` dictionary.
- Use repeated `getattr()` calls to drill down to the specific function
- Unpack nested positional and keyword arguments
- Call the specific function with those arguments, returning its return value to the message originator.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 27, 2023, 10:09pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/10 "2023-12-27T22:09:10Z")

</div>

> [@sherman](#):
>
> I realize thats a massive security issue (although if it's all internal to Ignition... 👀)

It **should be** all internal to Ignition, but it is theoretically possible to spoof/alter the behavior of a Vision Client. That's the whole point of things like cutting off Legacy DB permissions. (Yeah, it would have to be a determined attacker, but Ignition **is** used in infrastructure applications that would be potential targets of hostile nation-states.)

---

<div class="post-metadata">

**Author:** ![sherman](https://avatars.discourse-cdn.com/v4/letter/s/e5b9ba/32.png) [@sherman](https://forum.inductiveautomation.com/u/sherman)\
**Post date:** [December 27, 2023, 10:28pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/11 "2023-12-27T22:28:16Z")

</div>

> Have a message handler that does the following:

I have looked down this road before and I didn't manage to make it work -- I ran into an issue that perhaps you know how to solve: what do I feed to getattr()? What is the object that I can hand it that will give me access to the scripts? I just tried the following and it crashed my gateway (thats why you get a screenshot and not formatted code). The payload I handed it was `{"projectName": "myProject", "callback": "MyScripts.ls", "args": [], "kwargs": {}}`. Printing out `globals()` showed `"MyScripts"` in the output. Normally I wouldnt try to hand back the result but I am trying to crawl my way down and check out what's going on.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/4/2/423ce7f99be30adfae19b08f5d1b8871ade3827b.png)

Anyway -- if I can do all this this way (which I am not 100% sure yet that I can), it could be a builtin command!

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 27, 2023, 10:31pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/12 "2023-12-27T22:31:58Z")

</div>

You're on the right track. But the project name part is moot, as the message handler is **inside** a project, and it is **that** project's scripts that will be exposed in `globals()`. If you want to access all of your projects like this, put such a message handle in each of them. Then the caller can target any project they like.

---

<div class="post-metadata">

**Author:** ![sherman](https://avatars.discourse-cdn.com/v4/letter/s/e5b9ba/32.png) [@sherman](https://forum.inductiveautomation.com/u/sherman)\
**Post date:** [December 27, 2023, 10:42pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/13 "2023-12-27T22:42:11Z")

</div>

That probably explains why it didn't work quite right when I was bouncing between projects, until I had pinned down project inheritence a bit better.

The projectName field is half vestigal half this-is-how-my-version-of-a-builtin-would-work so that they can access other projects, although I guess I understand if thats not ok from a security perspective.

In general, I feel that if I have to copy-paste a function into a bunch of different places, it means that there is something wrong with the ability of the system to communicate internally. Hence a unified system command! With all kinds of error handling and documentation.

Did I mention I would really love for this to be a system command?

---

<div class="post-metadata">

**Author:** ![sherman](https://avatars.discourse-cdn.com/v4/letter/s/e5b9ba/32.png) [@sherman](https://forum.inductiveautomation.com/u/sherman)\
**Post date:** [December 27, 2023, 11:12pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/14 "2023-12-27T23:12:34Z")

</div>

Okay -- its kind of working actually. I can call methods that would actually not work when called from the script console:

```auto
#MyScripts in 'base_script' inherited by 'dev'
import os
def local_ls(path):
	return os.listdir(path)

def invokeGateway(callback, *args, **kwargs):
	return system.util.sendRequest("dev", "runScript", 
		{"callback": callback, "args": args, "kwargs": kwargs})

```

```auto
# script console in project base_script (should be agnostic to project?)
payload = {"callback": "MyScripts.local_ls", "args": ["/"]}
system.util.sendRequest("dev", "runScript", payload)

```

```auto
# Gateway message script 'runScript' in project "dev" 
# which is a grandchild of base_script
def handleMessage(payload):
	try:
		callback = payload['callback']
		args = payload.get('args', [])
		kwargs = payload.get('kwargs', {})
		cb_parts = callback.split('.')
		tgt = globals()
		for i in range(len(cb_parts)):
			if i == 0:
				#the handling for globals() doesn't support hasattr apparently
				tgt = tgt[cb_parts[i]] 
			elif not hasattr(tgt, cb_parts[i]):
				e = {"error": "missing {} from {}".format(cb_parts[i], str(tgt))}
				return e
			tgt = getattr(tgt, cb_parts[i])
		result = tgt(*args, **kwargs)
		return result
	except Exception as e:
		return {"error": str(e)}

```

It actually works finally!

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 27, 2023, 11:22pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/15 "2023-12-27T23:22:21Z")

</div>

> [@sherman](#):
>
> In general, I feel that if I have to copy-paste a function into a bunch of different places

That just means there should be function in your "universe" project that does this work, and your message handlers in your leaf nodes would be just one-liners. 🤷‍♂️

> [@sherman](#):
>
> Did I mention I would really love for this to be a system command?

Write a module. Modules can declare any desired RPC they like.

_Edit September 2025:_

> [@sherman](#):
>
> What if callback is a path to a script in the project library specified?

I suppose this is the right place to mention that my Integration Toolkit's `@system.util.runInGateway` decorator basically achieves this goal. You simply annotate the library function and any calls from client/designer scope will silently marshal the arguments and unmarshal the return value.

---

<div class="post-metadata">

**Author:** ![sherman](https://avatars.discourse-cdn.com/v4/letter/s/e5b9ba/32.png) [@sherman](https://forum.inductiveautomation.com/u/sherman)\
**Post date:** [December 27, 2023, 11:51pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/16 "2023-12-27T23:51:41Z")

</div>

> That just means there should be function in your "universe" project that does this work, and your message handlers in your leaf nodes would be just one-liners. 🤷‍♂️

Thats sort of how it is now, `base_script` is the equivalent of `universe` at the moment, I will refactor the projects some time in the coming weeks. Still spiritually opposed to copy-pasting boiler code regardless of the number of lines.

> Write a module. Modules can declare any desired RPC they like.

Unfortunately I do not know how to do this. I don't write in Java, so while I am sure I could do this with enough effort, it is not worth the investment for me at the moment. To even consider it I'd basically need stumble upon an "almost what I need" full tutorial from install-the-JDK all the way to publish-module-to-marketplace.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [December 27, 2023, 11:56pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/17 "2023-12-27T23:56:41Z")

</div>

> [@sherman](#):
>
> Unfortunately I do not know how to do this.

Then it is probably best to stick to methods that might be a bit "brute force", but that you understand and can maintain. When/if you get a breather, you can expand your tool set.

---

<div class="post-metadata">

**Author:** ![Matrix\_Engineering](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/matrix_engineering/32/19781_2.png) [@Matrix\_Engineering](https://forum.inductiveautomation.com/u/Matrix_Engineering)\
**Post date:** [December 27, 2023, 11:58pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/18 "2023-12-27T23:58:51Z")

</div>

> [@sherman](#):
>
> To even consider it I'd basically need stumble upon an "almost what I need" full tutorial from install-the-JDK all the way to publish-module-to-marketplace.

> **[Module Development with the Ignition SDK Course at Inductive University](https://www.inductiveuniversity.com/courses/elective-studies/module-development-with-the-ignition-sdk)**
>
> The Ignition SDK opens up Ignition to all kinds of user/organization extensions and customizations. This course will introduce you to the basic foundations of Ignition SDK module development, from ground zero upward.

Time permitting, start here, then move over to the Module sub-forum, and ask your "I'm stuck on this aspect" questions there.

---

<div class="post-metadata">

**Author:** ![sherman](https://avatars.discourse-cdn.com/v4/letter/s/e5b9ba/32.png) [@sherman](https://forum.inductiveautomation.com/u/sherman)\
**Post date:** [December 28, 2023, 12:01am UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/19 "2023-12-28T00:01:34Z")

</div>

Thank you!

---

<div class="post-metadata">

**Author:** ![YF129701](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/yf129701/32/60498_2.png) [@YF129701](https://forum.inductiveautomation.com/u/YF129701)\
**Post date:** [February 12, 2024, 8:13pm UTC](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460/20 "2024-02-12T20:13:36Z")

</div>

I have been thinking about this diagram a lot, how to improve some of the shortcomings to the large projects we have... We have projects with hundreds of views so any project design implementation choices we made early on are coming home to roost. In the diagram

```auto
universe
├bg_common
│├gsp
│├background1
│├background2
│└webdev
└ui_common
 ├perspective_common
 │├ui1
 │└ui2
 └vision_common
  ├ui3
  └ui4

```

where would you put the actual projects that put all the inherited resources together (i.e. those that don't contain reusable views, but the projects in which the reusable, global views actually get used)? I'm assuming that's what `ui1`- `ui4` are. If that's the case, it seems to me that `ui1` then wouldn't be able to use scripts in the `gsp` project because `bg_common` is a sibling to `ui_common`, not a parent. The overall idea here is still valuable and much better than what we have now - again, this is for a large project with probably close to a 1000 views and the accompanying scripts, styles and so on (by project, I almost mean product, not Ignition project - we do have 2 level inheritance set up and many different Ignition projects to split up the functionality; it just got sloppy after a few years and many developers). I will probably create a new thread to aggregate all the information I can, but wanted to get your input on this in the meantime.

[Next page](https://forum.inductiveautomation.com/t/where-do-should-gateway-scripts-live-with-respect-to-an-inherited-project-structure/83460.md?page=2)
