Securing Attachments in IBM Maximo

Need help securing attachments in IBM Maximo?

 

So you have secured your IBM Maximo installation using SSL, perhaps by following one of the secure articles in our A3J Group blog library, and are feeling pretty comfortable that your IBM Maximo users are protected. Just as you are about ready to lean back in your chair and enjoy the fruits of your labor you receive an urgent communication with a screenshot showing an IBM Maximo attachment being served up with the unsecured HTTP rather than HTTPS. Sound familiar? Thankfully you will be back to your feeling of tranquility in no time as this fix is a piece of cake.

*Note you could have been prevented the above scenario from occurring by not binding IBM Maximo to an unsecure port. This would not have allowed the attachment to work but would have prevented an unsecure connection.

Creating an Automation Script End Point in IBM Maximo

The IBM Maximo Integration Framework offers a wide variety of capabilities for publishing or consuming information to or from external systems. Some of the available methods for communication are:

  • Flat File Exchange
  • XML File Exchange with XSLT Mapping
  • Database Table Exchange (both internal and external to Maximo)
  • HTTP or SOAP Service Consumption

However, there may be times when the available end point handlers do not fit exactly what you need. What options do you have to customize the way the data is exchanged (method, format, content, etc.)?

1. Java customizations: This is old and tired. However, it is possible to write your own Router Handler class to deliver an outbound message to its destination. It is also possible to deliver the message using one of the available handlers above, and then write a custom IBM Maximo cron task to move the message to its destination. For me, that is too many moving parts.

2. Third-party solutions: Implementing a third-party middleware solution, such as Node Red or MuleSoft, can be an option for removing specific system differences when exchanging information between two systems. This is especially useful in large enterprises where many systems are exchanging data. This can allow for a single source of business logic to exchange information across a wide range of systems. For smaller integrations however, this can just add another layer of management and skills necessary to support the organization.

3. Automation Scripts: This is an easy, simple way to build your own end point handler in IBM Maximo without introducing unnecessary Java customization. Luckily, IBM provides a hook that allows us to do just that.

To create an Automation Script End Point in IBM Maximo, you will first need to register the Script Router Handler class. Please note that this class was introduced in the IBM Maximo 7.6.0.8 release. In the 7.6.1.1 release, the SCRIPT handler was added by IBM, which means that these steps can be skipped if you are on 7.6.1.1 or later. If you are running a version between 7.6.0.8 and 7.6.1.1 you can follow these steps to create the Script Router Handler in IBM Maximo:

  1. Log into IBM Maximo as an administrator
  2. Navigate to the Integration > End Points application
  3. Under the More Actions menu, choose the Add/Modify Handlers option
  4. Click the New Row button
    a. Handler: SCRIPT
    b. Consumed by: INTEGRATION
    c. Handler Class Name: com.ibm.tivoli.maximo.script.ScriptRouterHandler
  5. Click the OK button

end point-autoscript-IBM Maximo-mif-automation scripting-integration-scripting-python-code-script-Maximo-customization

Once you have the SCRIPT handler, you can use it to register a new End Point in IBM Maximo:

  1. Log into IBM Maximo as an administrator
  2. Navigate to the Integration > End Points application
  3. Click the New End Point button
    a. End Point: TEST-SCRIPT
    b. Description: TEST SCRIPT HANDLER END POINT
    c. Handler: SCRIPT
  4. After populating the Handler with SCRIPT, a SCRIPT property will appear
    a. Value: <The name of your Automation Script>, e.g. TEST-SCRIPT
  5. Click Save

end point-autoscript-IBM Maximo-mif-automation scripting-integration-scripting-python-code-script-Maximo-customization

At this point, you have an End Point that calls an Automation Script that has not yet been created. The next step is to define that Automation Script and implement your own logic:

  1. Log into IBM Maximo as an administrator
  2. Navigate to the System Configuration > Platform Configuration > Automation Scripts Application
  3. From the More Actions menu, choose the Create > Script option
  4. In the ensuing dialog, enter the basic script information:
    a. Script: TEST-SCRIPT
    b. Description: TEST AUTOMATION SCRIPT FOR END POINT
    c. Language: python
  5. Enter the source code from below and press the Create button

end point-autoscript-IBM Maximo-mif-automation scripting-integration-scripting-python-code-script-Maximo-customization

Source Code:
# ------------------
# This script will write a message to the file system 
# and FTP the file to another server for processing.
# 
# Implicit Variables:
#   INTERFACE - the name of the triggered Publish Channel
#   requestData - the message payload
# 
# Alex Walter
# alex@a3jgroup.com
# 21 JAN 2021
# ------------------
from java.io import File
from java.io import FileWriter
from org.apache.commons.io import FileUtils
from org.apache.commons.net.ftp import FTPClient
from org.apache.commons.net.ftp import FTPReply
from psdi.util.logging import MXLoggerFactory

logger = MXLoggerFactory.getLogger('maximo.script.a3jtutorial')
if logger.isDebugEnabled():
    logger.debug('Starting TEST-SCRIPT script')
    logger.debug('INTERFACE: ' + str(INTERFACE))

# If the file exists, then delete it and create it new
ftpFileName = "C:\Temp\ftpfile.xml"
ftpFile = File(ftpFileName)
if ftpFile.exists():
	ftpFile.delete()
ftpFile.createNewFile()

# Write the file to disk
fileWriter = None
try:
	fileWriter = FileWriter(ftpFile)
	fileWriter.write(requestData)
finally:
	if fileWriter:
		fileWriter.close()

# Setup FTP variables - usually good idea to create System Properties
ftpHostName = "ftp.company.com"
ftpUserName = "username"
ftpPassword = "password"

ftpClient = None
fileInput = None
try:
	# Make an FTP connection
	ftpClient = FTPClient()
	ftpClient.connect(ftpHostName)
	reply = ftpClient.getReplyCode()
	if logger.isDebugEnabled():
		logger.debug('ftp reply: ' + str(reply))
	
	if not FTPReply.isPositiveCompletion(reply):
		if logger.isDebugEnabled():
			logger.debug('not a positive ftp reply')
		ftpClient.disconnect()
	else:
		if logger.isDebugEnabled():
			logger.debug('positive ftp reply!')
		# Log into the FTP server
		if ftpClient.login(ftpUserName, ftpPassword):
			if logger.isDebugEnabled():
				logger.debug('Logged into FTP site')
			# ftpClient.setFileType(2);
			fileInput = FileUtils.openInputStream(ftpFile)
			# Put the file in the default directory
			ftpClient.storeFile(ftpFileName, fileInput)
			if logger.isDebugEnabled():
				logger.debug('File sent to Server')
		# Log out
		ftpClient.logout()
		if logger.isDebugEnabled():
			logger.debug('Logged out of FTP site')
finally:
	if ftpClient:
		ftpClient.disconnect()
	if fileInput:
		fileInput.close()

 

NOTE: If you need help installing this automation script in your IBM Maximo, or have questions with other Maximo CMMS configurations don’t hesitate to reach out! Leave a comment below, or email info@a3jgroup.com

 

Opening an Attachment on List Tab in IBM Maximo

For this scenario I wanted an option to easily open attachments without having to go to each individual record, then the attachment icon, then waiting for the dialog to open then selecting the image to open it. I wanted to save time looking at attachments for a bunch of records. (Note: This only works if there is only one attachment that you can identify to open.)

In this case we took a meter reading and have an image that goes with that meter reading in Maximo. So, there is only one image per reading.

In order to do this, you need to go to Application Designer. Find the application you want to add an image link to. Find the table you want to use and add a new tablecol to that table. Open the tablecol properties and fill out the following fields:

Type: OPENURL (Note: This was done in the xml by exporting the application and updating the field and importing the application. This value is not an option in most IBM Maximo environments in the drop-down list for the type field)

Attribute: DOCLINKS.DOCUMENT

URL Attribute: DOCLINKS.WEBURL

opening-attachements-doclink-application designer-user interface-customization-relationship-attributeopening-attachements-doclink-application designer-user interface-customization-relationship-attribute

 

For the Attribute and URL Attribute you can append as many relationships as you need. For our scenario we added a relationship to the MEASUREMENT table called DOCLINKS with a Child Table of DOCLINKS table that has the image. The whereclause for that relationship is “ownertable = ‘MEASUREMENT’ and ownerid = :MEASUREMENTID”. The table that we are adding the tablecol to is MEASUREMENT.

Once you have updated the application. Save your changes and go to the application. You will see an update to your table that looks like:

opening-attachements-doclink-application designer-user interface-customization-relationship-attribute

 

 

 

The underline and the blue text indicate that you can click that and the attachment will open.

Automatically Email Vendor on Approval of Purchase Order in IBM Maximo

Automate emails to vendors from approved purchase orders in Maximo CMMS

This article will walk an IBM Maximo administrator through the steps necessary to send a purchase order by email to a vendor as an attachment. It is a common requirement by customers to be able to automatically send the purchase order to vendors through email. Some of the benefits of doing this are:

  • Standardize the way that approved purchase orders are communicated to vendors. A common template can be used for the email subject and body, as well as a common workflow for when the communication is sent.
  • Reduce the time taken by procurement agents in generating reports, saving them locally, finding appropriate contact information, and typing up an email.
  • When IBM Maximo sends the email, a copy of the email that was sent is stored in the Communication Log along with the purchase order. If you ever need to know when the email was sent and to whom, the documentation of the email is in IBM Maximo attached directly to the purchase order. No more searching through email to find the communication.

To achieve this, we will deploy an automation script in IBM Maximo that will generate the report and send the email. Before we get to the script, we need to ensure that the following prerequisites are met:

  1. The email address of the vendor should be recorded in the Companies application on the Contacts tab. The script below will use the email address of the Primary Contact (set on the Addresses tab) but can be adjusted to include a larger or more targeted set of email addresses. However, they must be accessible from somewhere in the system by the script.
  2. A Communication Template should be created and activated to be used as the email subject and body of the communication. By default, I’ve named the template POTOVENDOR, but you can name it anything you want and change it in the script.
  3. A BIRT report that represents the purchase order to be sent to vendors. By default, the auto script uses the poprint.rptdesign report that comes with IBM Maximo. This can be changed in the script to a custom report but be sure to match up the report parameters if necessary.

The trigger point for the script below is a status change of the purchase order to APPR. However, this could just as easily be triggered from IBM Maximo workflow, escalations, or other means.

Here is the automation script needed to make this happen:

from com.ibm.tivoli.maximo.report.birt.queue import ReportQueueService
from com.ibm.tivoli.maximo.report.birt.runtime import ReportParameterData
from psdi.mbo import SqlFormat

# Get the current user's information
userInfo = mbo.getUserInfo()
locale = userInfo.getLocale()

# If the PO Vendor does not have a contact with email, then kindly exit
if not mbo.isNull("VENDOR.PRIMARYCONTACT.EMAIL"):
    emailTo = mbo.getString("VENDOR.PRIMARYCONTACT.EMAIL")
    # Append the current user's email address to the chain to ensure delivery
    if userInfo.getEmail() and not userInfo.getEmail() == "":
        emailTo = emailTo + ", " + userInfo.getEmail()
    
    # Create default message subject and body
    # These will be replaced by a communication template if one is found by the code below
    emailSubject = "Purchase Order"
    emailComments = "Please acknowledge receipt of this Purchase Order."
    
    # Change this value to a valid Maximo Communication Template ID
    commTemplateID = "POTOVENDOR"
    
    # Get the communication template
    commTemplateClause = SqlFormat(mbo, "templateid = :1")
    commTemplateClause.setObject(1, "COMMTEMPLATE", "TEMPLATEID", commTemplateID)
    commTemplateSet = mbo.getMboSet("$potovendor", "COMMTEMPLATE", commTemplateClause.format())
    commTemplateMbo = commTemplateSet.getMbo(0)
    if commTemplateMbo:
        # Build the email subject and body from the Communication Template
        sql = SqlFormat(mbo, commTemplateMbo.getString("SUBJECT"))
        sql.setIgnoreUnresolved(True)
        emailSubject = sql.resolveContent()
        sql = SqlFormat(mbo, commTemplateMbo.getString("MESSAGE"))
        sql.setIgnoreUnresolved(True)
        emailComments = sql.resolveContent()
    
    # Build the Report Parameters
    parameterData = ReportParameterData()
    parameterData.addParameter("appname", "PO")
    parameterData.addParameter("paramdelimiter", "")
    parameterData.addParameter("paramstring", "")
    parameterData.addParameter("where", "(po.poid = " + str(mbo.getLong("POID")) + ")")
    
    # Queue the Report to Run
    queueManager = ReportQueueService.getQueueService()
    queueManager.queueReport("poprint.rptdesign", "PO", userInfo.getUserName(), emailTo, emailSubject, emailComments, parameterData, locale.getCountry(), locale.getLanguage(), locale.getVariant(), userInfo.getTimeZone().getID(), userInfo.getLangCode(), long(0), userInfo)