I was recently confronted with some seemingly simple requirements in OpenText Content Server development. In one case a process was required to search and replace all instances of a category attribute value (a common requirement when updating lookup and popup type attributes). It sounded simple with a LiveReport and WebReport, but grew in complexity once we had to consider multivalued attributes within multivalued sets (try building a generic solution if you're not convinced of its complexity). I suggested a simple one-off OScript module (based on the AttrData extensions in RHCore), but it was not an option since the target system was highly controlled and installing a module was nearly impossible.
Around the same time a user posted a question to the OpenText Knowledge Center forums asking if it were possible to schedule the generation of a System Report (opens new window). This can't be done with standard tools, and so I suggested the development of a small custom module. I received no reply, which I assume meant it was also not an option.
These types of requirements are sometimes just a few lines of code. That's the easy part, but the logistics of getting a module developed and installed can often be much more difficult or impossible.
Developers will often use tools such as LiveReports or WebReports to get around this. These tools allow complex reports to be written without having to build and install an additional module. This is great and serves a purpose, but is limiting since neither LiveReports or WebReports are scripting languages (albeit WebReports has a few "action" tags).
Writing OScript is sometimes the best or only solution to a problem, but there lacks a way to write OScript without having to build and install a module. But what if there was a way to write OScript from the web interface in the same way LiveReports allows you to write SQL? This would open many possibilities for creating small scripts and applications without having to deal with the logistics of installing a module. I pondered the idea for a few years and hesitated due to concerns with system integrity and security. However, I came to realise it wasn't a problem as long as one adopted a good programming style, had a solid foundation to build on, and trusted Content Server permissions. I prototyped the idea, built a few small applications, and was surprised by how simple and powerful it was. Why hadn't I done this before? I formalised the prototype and turned it into a subtype called ScriptNode, which is now a part of RHCore.
# What is ScriptNode?
ScriptNode is a Content Server node type that allows a privileged user to write and execute OScript directly from the web interface. ScriptNode isn't a front-end for module development; rather, it allows a user to write scripts in the browser that can be executed on demand or on a schedule. Think of it like a LiveReport, but instead of writing SQL you write OScript. ScriptNode is backed by the RHCore API, which makes it a simple and powerful solution for creating small scripts and applications without having to develop, install, or maintain a module. The only requirement is RHCore.
A ScriptNode can be added to a folder or other container through the "Add Item" menu (as long as the user has the permissions and privileges) and has a simple editor. For example, a ScriptNode to display "Hello World!" back to the user could look as follows:
The text area is for the script and is analogous to the script window in Builder. Executing the example outputs the following:
A ScriptNode script is wrapped in a temporary object at runtime, which provides a number of convenient methods via the
this context. For example,
this.echo() (or just
.echo() as in the example) is analogous to the standard
echo() function, but is used to write the output to the browser (instead of the debug window or logs). Other methods include shortcuts for fetching an RHNode, RHUser, program context, request, etc.
Input parameters are defined above the text area and is similar to how parameters are defined in a LiveReport. The "Prompt" field sets the display label and the "Field Type" defines the input widget and data type.
Running a ScriptNode with input parameters first prompts the user for the values before calling the script. Arguments can be accessed in the script by matching the function declaration to the parameters or by using the
.args() function. The
.args() function returns the arguments as a list, but also accepts an integer to return the argument at a specific index (e.g.,
.args(3) for the third argument).
Let's look at another example.
# Example: E-Mail a Group
Say you require a tool to send an e-mail to the members of an arbitrary group. This can be built with ScriptNode with a few lines of code.
The first step is to define the input fields:
- a "To" field of type "KUAFGroup" (an autosuggest field for groups) to input the recipient group;
- a "Subject" field of type "String" to input the e-mail subject; and
- a "Body" field of type "Text" to input the body of the e-mail.
The next step is to write the OScript to send the e-mail. This could be done with the
$Kernel.SMTPClient library, but for the example we'll use the
EMailer class I introduced in Part XIII: Sending E-Mail from OpenText Content Server. Putting it together looks as follows:
Running the ScriptNode prompts the user with the following form, which can be filled in and submitted to send the e-mail to the group members:
That's it! Let's look at a few more examples.
# Other Examples
I've used ScriptNode to create a number of other small tools and applications. These are conceptually similar to the previous example and include:
- reporting on the effective permissions of a user on a node;
- monitoring a file system directory for documents and adding them to Content Server when detected;
- transferring ownership of all nodes belonging to a user to another user (useful when a user account is to be deleted);
- searching and replacing category attribute values;
- generating a System Report and adding it as a document to Content Server (could also be e-mailed);
- analysing a workflow map to determine where certain actions are taking place;
- automatic sending of an e-mail to members of a testing team whenever a module under development is updated on the server;
- monitoring the
logs/directory for trace files and e-mailing them to the administrator when detected; and
- reporting on installed patches by fetching the list of files in the
patch/directory, extracting the header from each file, and outputting the results.
All of these examples are just a few lines of code and can be added to a system without having to build or install a module each time. Again, the only requirement is RHCore.
# Other Features
# Run on a Schedule
A ScriptNode can be scheduled to run once at a future date or regularly on a schedule. This is configured on the "Scheduler" tab and looks as follows:
Scheduled ScriptNodes are handled by the standard Content Server agents and run in the context of the Admin user.
# Workflow Generic Callback
ScriptNode also integrates with the Workflow Generic Callback subsystem, which means you can use ScriptNode to write Workflow Event Scripts directly from the web interface.
# WebReports and Other Node Types
A ScriptNode can also call a WebReport and vice-versa (via a drop-in sub-tag). This creates some interesting possibilities to add custom logic to a WebReport, or use a WebReport to template the output of a ScriptNode.
Of course, a ScriptNode can also call a LiveReport, a Simplate, or any other ScriptNode.
# Event Callbacks
This is still a work in progress, but I plan to allow ScriptNodes to respond to node and user events (e.g., run a ScriptNode when a document is added to a specific folder). This could be used to launch a workflow, notify a user with an e-mail, or anything else.
# What about security?
You might be thinking this is a security risk. But if you think about it, it's no different than the security risk associated with a LiveReport. The permission to create, edit, and execute a LiveReport depends on standard permissions and its Object Privilege (via the Administer Object and Usage Privileges admin settings). These must be enforced to prevent an unscrupulous user from writing and executing a LiveReport to augment their permissions or do something crazy like drop tables. The same argument can be made for ScriptNode.
ScriptNode also takes it a step further by restricting who can edit scripts. Object Privileges normally just restricts the creation of nodes, but with ScriptNode it also restricts the editing. I think this is logical due to the sensitive nature of the object type.
Finally, ScriptNodes should be developed with the same diligence used to write a module. They should be developed, tested, and vetted in a development environment before moving to production. The reason for this should be obvious: An obscure typo or bug could lead to something destructive (e.g., losing data, creating an infinite loop, etc.). Also, a development environment allows a developer to debug the ScriptNode using Builder or CSIDE.
# Wrapping Up
I'm using ScriptNode in a few projects and enjoy the ease at which I can develop a small application or one-off solution with little constraint. This has been especially useful in environments where installing a module is highly controlled and difficult.
Please leave a comment if you have any questions or thoughts of where you might find this useful. Finally, if you're interested in seeing a demo then please get in touch!
# Addendum (18 March 2015)
Some readers have commented on security concerns with ScriptNode. While I don't necessarily agree with these concerns (see the comments below), it should be noted that ScriptNode is an optional part of RHCore and is disabled by default. It must be manually enabled after RHCore is installed before it can be used.