Sunday, February 21, 2010

Exec Processes while Controlling stdin and stdout

A few months ago I read a question on the ITToolbox PeopleTools-I forum asking if it was possible to read the output of a spawned process. Unfortunately this is not possible with the delivered PeopleCode Exec function. I got to thinking about this though, and I wondered if it was possible to accomplish this by using Java's Runtime object from PeopleCode. The answer is yes. To test this, create a batch file in your c:\temp directory named sayHello.bat and then add the following batch file commands to this file:

@echo off
echo Hello %1
echo How are you?

Next, open App Designer and create a new App Engine program. Add a new PeopleCode action to this program and insert the following PeopleCode:

Local JavaObject &runtime = GetJavaClass("java.lang.Runtime").getRuntime();
Local JavaObject &process = &runtime.exec("c:\temp\sayHello.bat """ | %OperatorId | """");

Local JavaObject &inputStreamReader = CreateJavaObject("java.io.InputStreamReader", &process.getInputStream());
Local JavaObject &bufferedReader = CreateJavaObject("java.io.BufferedReader", &inputStreamReader);
Local any &inputLine;

While True
&inputLine = &bufferedReader.readLine();
If (&inputLine <> Null) Then
MessageBox(0, "", 0, 0, &inputLine);
Else
Break;
End-If;
End-While;

Open the new App Engine's properties and disable restart. From the App Designer menu bar, choose Edit | Run Program. When the Run Request dialog appears, select the Output Log to File checkbox and then activate the Run button. When the App Engine finishes, open the log file (usually c:\temp\NAME_OF_AE.log) and review its contents. Here is the contents of my log file. Notice that I was logged in as user PS and named my App Engine JJM_JAVAEXEC. I expect your results to differ slightly based on your tools version, operator ID, and program name.

PeopleTools 8.49 - Application Engine
Copyright (c) 1988-2010 PeopleSoft, Inc.
All Rights Reserved


Hello "PS" (0,0)
Message Set Number: 0
Message Number: 0
Message Reason: Hello "PS" (0,0) (0,0)

How are you? (0,0)
Message Set Number: 0
Message Number: 0
Message Reason: How are you? (0,0) (0,0)
Application Engine program JJM_JAVAEXEC ended normally

The following example is similar to the previous, but writes to stdin as well as reading from stdout. To run this example, you need a copy of grep. I use the version that comes with UnxUtils (Note: You do not need grep or UnxUtils to write to stdin. Grep is a common command line tool, so I used it here for example purposes only). The following code writes two lines to the grep program's standard input (stdin): EMPLID and OPRID. It then asks grep to find the line containing OPRID. The PeopleCode then reads the grep program's output and writes it to the App Engine log.

Local JavaObject &runtime = GetJavaClass("java.lang.Runtime").getRuntime();
Local JavaObject &process = &runtime.exec("grep ""OPRID""");

Local JavaObject &inputStreamReader = CreateJavaObject("java.io.InputStreamReader", &process.getInputStream());
Local JavaObject &bufferedReader = CreateJavaObject("java.io.BufferedReader", &inputStreamReader);
Local JavaObject &outputStreamWriter = CreateJavaObject("java.io.OutputStreamWriter", &process.getOutputStream());
Local JavaObject &outputBuffer = CreateJavaObject("java.io.BufferedWriter", &outputStreamWriter);
Local any &inputLine;

&outputBuffer.write("EMPLID: " | %EmployeeId);
&outputBuffer.newLine();
&outputBuffer.write("OPRID: " | %OperatorId);
&outputBuffer.close();

Repeat
&inputLine = &bufferedReader.readLine();
If (All(&inputLine)) Then
MessageBox(0, "", 0, 0, &inputLine);
End-If;
Until None(&inputLine);

If you want to run this example and have a copy of grep in your %PATH% environment variable, then add a new step and PeopleCode action to your App Engine, deactivate the previously created step, and then run the program. Your output should look something like:

PeopleTools 8.49 - Application Engine
Copyright (c) 1988-2010 PeopleSoft, Inc.
All Rights Reserved


OPRID: PS (0,0)
Message Set Number: 0
Message Number: 0
Message Reason: OPRID: PS (0,0) (0,0)
Application Engine program JJM_JAVAEXEC ended normally

25 comments:

Chili Joe said...

Excellent! This may come in handy.

Alex Shilman said...

Hi Jim, Im trying to use your code to PGP encrypt two values for SSO.
Do I need to create a file, or the output buffer should handle that?
Thanks in advance

Jim Marion said...

@Alex, I think you should be able to use the output buffer. An alternative would be to use a Java PGP encryption library.

Alex Shilman said...

Hey Jim, this command line doesn't seem to like PGP encryption. Your help will be greatly appreciated.
I just need to encrypt 2 values: lastname and emplid.
Thanks.

Jim Marion said...

@Alex, I assume you tried a simple command and made sure that worked for you (no pgp or gpg). Here is the command I used to encrypt a string:

echo the_user_id | gpg --symmetric --passphrase-fd 8 | base64

I didn't try it in App Engine online (Linux), but as you can see from the command, everything is reading from stdin and writing to stdout. You can tell this by my use of pipes.

Alex Shilman said...

Jim, yes I've tried it on command line in Unix. And it works. But when I do the same command in getRuntime().exec() it just outputs everything after echo as a string.
FYI Im doing this as a function. Where it accepts an input param, and should output encrypted value.
Any thoughts? Appreciate your help on this.

Jim Marion said...

@Alex, that makes sense. Let me test something. I'll get back to you.

Jim Marion said...

@Alex, What I pasted, and what you tested was a series of commands, not a single command. The ability to use pipes is built into the shell, not exec. The Runtime.exec method interprets it as a single command. You have a couple of options. To get the username/password into gpg, write to the process.getOutputStreamWriter as shown in the second example. The next hurdle is to base64 encode the gpg binary output. There are options for gpg that produce base64, such as --armor. A simple alternative is to create a batch file/shell script that combines both commands using the pipe (|). If you can't or don't want to use a shell script, another alternative is to create a shell and exec commands within that shell. See this blog post and this forum post for details on creating a shell using System.Runtime.

Alex Shilman said...

Hey Jim, forgot to let you know that it works. I basically created a shell script that does the encryption,and I just pass it the value to encrypt.
Thanks.

Jim Marion said...

@Alex, thanks for the update.

Amol Kotkar said...

Hi Jim,
I have to run a bat file using exec function in App Engine PeopleCode.

The bat file has a code of opening the App Designer, comparing the project between source and target environments and export the compare report to a location given in it.

I am able to run the bat file using App Designer 2 tier mode, but When I run it through PIA, it does not do anything.

Below is the PeopleCode in the AE:-

&XmlFile = "C:\windows\system32\cmd.exe /c E:\users\AKR6\Compare\TEST1\Batch\migrate.bat " | DSX_XML_GEN_AET.PROJECTNAME.Value | " " | DSX_XML_GEN_AET.OPRID.Value | " " | DSX_XML_GEN_AET.PASSWORD.Value | " " | DSX_XML_GEN_AET.SOURCE_DESCR.Value | " " | DSX_XML_GEN_AET.SOURCE_OBJECT;

&execute = Exec(&XmlFile, %Exec_Synchronous + %FilePath_Absolute);

Could you please help in this?

Jim Marion said...

@Amol, is your app server on Windows and does everything live in the same paths for your app server? When the app server service is running, does it know about the mapped drive? Does the app server service have a named user with drive mappings?

Aswath said...

Hi Jim,

Your posts are excellent, i want to ask your help to send Push notifications to mobile from peoplesoft.

we have a mobile app (android and ios) written in native languages, now i have to send push notifications to mobile server from peoplesoft.
They expect me to send a push notification to GCM(google cloud server). i am struggling to do it.

what i tried :
i tried to execute a Java program from peoplecode which(java) will send message to GCM like below, but since App server cant send message to internet this fails


&jobject = CreateJavaObject("GCMServer");
rem &jobject.sendMessage("AIzaSyAO2TA3unQAwfjQoLBlILlokTEf1YU6B64", "message content here", "AAAAogECAwQAAQAAAAACvAAAAAAAAAAsAARTaGRyAgBObwgAOAAuADEAMBTOa8S7KrWvaHYvN+m8klkNRMlfNwAAAGIABVNkYXRhVnicHYlBDkBAEARrlzj6CbEbsa4IbiJZd0/wQY/TYyap7lQDj/NFiUPnX2PNwcnMxEVHqFgl9lous8ndLOSeaCMDjTKKgVE026qnn9aT3mzS/gF0GQuA");

&jobject.sendMessage("AIzaSyAO2TA3unQAwfjQoLBlILlokTEf1YU6B64", "This is message for mob", "cpW8gpq6EN8:APA91bEnVcYg-Qpgk86QXJqh7PHcHDoxOg8Jjyi_EYm23zJ6nS5KXjc4-s4W25kx6ii2Pl4qAeAPyzVqGcDOzgHZ6-IxdZLAkgqjo4cdTi3VbquZAeSj7f_Qs99E0bx3ttugCVeVs_dC");
&jobject = Null;


Note : the restriction here is they have a 3 tier architecture and only the webserver is exposed to internet.

Jim Marion said...

@Aswath, just because a server isn't exposed to the internet, doesn't mean it can't contact the internet. It just means other people can't contact it through the internet. It is quite common to have an app server sitting behind a firewall and requiring proxy server settings to connect to the internet. Beside the Integration Broker proxy server settings, make sure you have your app server proxy configuration settings (specifically the Java related ones) set in you app server configuration.

Unknown said...

i created an AE with only one step with peoplecode action.

the code is as below:


&my_command = "E:\PT853\PS_APP_HOME\FSCM92\bat\AERUN.bat";


&command_ftp = Exec(&my_command, %Exec_Synchronous + %FilePath_Absolute);

If &command_ftp = 0 Then
/* Continue on with program */
MessageBox(0, "", 30000, 0, "Execution of script SUCCESSFUL");
Else
MessageBox(0, "", 30000, 0, "Execution of script Has Failed");
Exit (1);
End-If;

Now, when i run this in 2 tier that is through App D this works as expected.
The same AE when i run through PIA system process requests, it does not work.

The command in the .bat is to invoke PTF. The command is as follows.

cmd.exe /c %PSAE%\PSAE.exe -CT ORACLE -CD FIN92 -CO VP1 -CP VP1 -R 1 -AI PTF_AE_CI

Can you please tell me what should i do to get it work in 3 tier?
Thank you!!

Regards,
Aniket Sen

Jim Marion said...

@Aniket, I assume you are running on a Windows server AND that you copied the batch file to that location on the server AND that the server knows the "E:\" path? The reason I ask about the path is because Windows servers often run with a different registry hive and don't always know about mapped drives. Sometimes you have to use the "UNC" path.

Unknown said...

Hi Jim,

Thank you for your prompt response. To answer your questions, yes, I am running on Windows server and I have placed the .bat file in PS_Home where all the other .bat files are kept.
None the less, even if instead of pointing to the .bat file, I directly write the command as a parameter to the Exec function, even then it works when ran in 2 tier, directly through the App D.

My question is that what do I have to do differently for it to work the same way in 3 tier when scheduled through system process requests.

What baffles me is that I am looking in as the same super user both in app d and on PIA, also, the fact that when ran through PIA in the .stdout file, the Exec code returns an exit code as 0 and the AE goes to success, yet I do not see the cmd.exe getting invoked and the PTF client getting invoked, as is the case when ran directly through App D.

How do I make it work from PIA.IS this even possible with 3 tier?

Thanks & Regards,
Aniket Sen

Jim Marion said...

@Aniket, I also mentioned path mappings and I didn't see that in your response. I suggest using the \\servername\sharename\path style instead of the mapped drive letter.

Unknown said...

@Jim,I did use the UNC path style. However, there is no change in the behavior.

Thanks & Regards,
Aniket Sen

Jim Marion said...

@Aniket, those are the only things I can think of. It might be helpful to use the stdin/stdout redirection described in this blog post to exec your process. You can then use MessageBox to print stdout to the AE log file.

Unknown said...

Hi Jim, i am trying to read the output of below unix command but not able to do so with the example you gave. Am i missing something....

Command
pr xyz.pdf | grep 'Count'

Jim Marion said...

@Rakesh, any error messages or anything? Are you reading stdout and stderr?

Unknown said...

I have a Java program which has the main method to accept a parameter from the command line, and I need to execute this executable through PeopleCode in the field change event, I tried to execute the program through exec function but the exec command is not returning any results after the execution.

Exec("java -cp /apps/psofthr/pt8.54/sdevhr/appserv/classes/itextpdf-5.5.8.jar edu.pdf.PDFFlatten /apps/psofthr/test.pdf /apps/psofthr/test1.pdf", %Exec_Synchronous + %FilePath_Absolute)

Any suggestion??

Regards,
Nirmal

Unknown said...

I have a Java program which has the main method to accept a parameter from the command line, and I need to execute this executable through PeopleCode in the field change event, I tried to execute the program through exec function but the exec command is not returning any results after the execution.
Exec("java -cp /apps/psofthr/pt8.54/sdevhr/appserv/classes/itextpdf-5.5.8.jar edu.pdf.PDFFlatten /apps/psofthr/test.pdf /apps/psofthr/test1.pdf", %Exec_Synchronous + %FilePath_Absolute)
Any suggestion?

REgards,
Nirmal

Jim Marion said...

@Nirmal, if I remember correctly, #Exec_Synchronous will cause PeopleCode to pause and wait for execution to complete. When finished, Exec should return the shell scripts exit code. 0 is the standard exit code for success. Using the approach outlined in this blog post gives you significantly more control over the process.