March, 2008
by Chuck Moore
SAS/CONNECT provides a means to submit SAS code to a remote SAS server. This is handy because it allows offloading processing to bigger faster boxes, or to instances where large datasets reside, minimizing data transfers. In my case, we have PC SAS on our local workstations, SAS on a mainframe, and SAS on some hefty Windows servers. My group has a number of regularly scheduled - production like - SAS jobs that run in batch over night. Even though we have Control-M available to use for scheduling, the bureaucracy around it would currently impede our SAS programming/processing needs. So, we rely on a single SAS program that is scheduled through Windows Scheduled Tasks to start at 00:01 and act as our own scheduler of the processes. This scheduling program uses SAS/CONNECT and asynchronous rsubmits to run some of the SAS programs on a remote server.
Generally, SAS/CONNECT uses SAS's Integration Technologies to make the remote server connections. This involves the use of a spawner process that contains the actual "sas ..." command line that actually starts SAS in batch mode. Since we do not have the permissions to make a remote desktop connection, and I am not the SAS administrator for the remote server, I do not have access to the spawner's "sas ..." command line, nor to the configuration file(s).
There are some SAS system options that can only be set via either the command line (SAS invocation) or via a configuration file that SAS will find in a hierarchical search sequence, or explicitly through the command line. These options include ASYNCHIO, BATCH, ECHOAUTO and LOGPARM which are handy to my group's batch processing.
Since the remote SAS server is an enterprise shared resource, and supports multiple SAS applications, not just simple batch SAS, it is in my group's better interest, at this time, to simply set these options explicitly for our batch jobs. But, how can we do this? The answer was found in the use of SAS signon scripts - "signon &session_id script = ..."
Unfortunately, SAS's documentation for writing a signon script is rather weak - uncharacteristic of SAS documentation. Fortunately, there is a set of sample scripts to work from. Unfortunately, the samples and documentation is geared towards an interactive session, not unattended batch processing. Fortunately, scripting is simple to understand and easily edited. Most fortunate of all is that using a script gives the user direct access to the "sas ..." command line used on the remote server.
Since the signon script contains the "sas ..." command line that is sent to the remote server, we can set command line options by editing the "sas ..." command in the script file. But, this is not without its own set of Murphyisms - things that can go wrong.
This is what I did to learn how to use a signon script for unattended batch processing on a remote SAS server.
!SASroot is a SAS specific internal configuration variable used in its configuration files. On my workstation it represents "C:\Program Files\SAS\SAS 9.1". The sample signon scripts are then below this, in "...\connect\saslink". There may/should be a "...\connect\sample" directory. This directory contains sample SAS code - "*.sas" files. The script samples are in "...\connect\saslink" - "*.scr" files.
When I experiment with things, I tend to use a test program I arbitrarily call "play" or "[something]_play". That's because I like to play around with things when learning their use. This play program began simply with:
%let rhost = ... ; Signon rhost ... ; Rsubmit; %sysget(COMPUTERNAME); Endrsubmit;
From there it evolved into reading the script template, editing the script template, writing out the dynamically generated script, and then actually using the script to do the "signon rhost script= ... ; rsubmit; [test code] endrsubmit;" stuff.
Our goal is to derive a useful signon script for use in a "signon rhost script=... ... ;" statement from a base template file. The sample script is very complete, so I would highly recommend starting from it, not from scratch, and in minimizing the alterations.
At the top of the script are the lines:
/* trace on; */ /* echo on; */
Which, when uncommented, are useful for learning and script debugging.
To connect to a box, to run SAS, it is necessary to login to the box. Using a script, and with the appropriate system option set ( COMAMID=tcp ) SAS performs a tcp/ip login, requiring a username and a password. As a default, the script is originally written using "input" script directives to interactively obtain the "UserID" and "password". These, then, are "type"d out to the remote system. This is undesirable for unattended batch processing. The actual original sample script code is
/* --------------- TCP SIGNON ------------------------------------*/ waitfor 'Username:' , 'Hello>' : ready , 120 seconds : noprompt ; input 'Userid?'; type LF; waitfor 'Password:' , 120 seconds: nolog; input nodisplay 'Password?'; type LF; waitfor 'Hello>' , 'access denied' : nouser , 120 seconds : timeout ;
The transformed template script code to make it work for unattended batch is as follows
/* --------------- TCP SIGNON ------------------------------------*/ waitfor 'Username:' , 'Hello>' : ready , 120 seconds : noprompt ; type USERID LF; waitfor 'Password:' , 120 seconds: nolog; type PASSWORD LF; waitfor 'Hello>' , 'access denied' : nouser , 120 seconds : timeout ;
The "input" directives were removed, and "USERID" and "PASSWORD" were added to the outputting "type" lines. These all cap words act as keyword place holders in the template, which will allow a SAS program to replace them with actual values. Since the resulting script will have a specific user's name and password hard coded into the resulting script file, it should be obvious why we should work from a template.
Now, there should be some concern! We are putting a person's private password into a stored text file, exposing it for someone else to possibly see, if they go looking for it. This is a significant security risk. BUT! There are some remedies (improvements) for this. Since the script file is read by SAS, and SAS understands its own encoding scheme(s), the stored password can be a SAS encoded text string - e.g. "{SAS001}garbl3dy_g00k!" - (read SAS documentation for a discussion about the difference between encoded and encrypted). An additional security/safety precaution would be to use the TEMP device type in the FILENAME statement for the script file:
Filename scriptfr temp; Signon script=scriptfr ... ; ... Endrsubmit; ... Filename scriptfr clear;
In this way, the script file is in use by the controlling SAS process until it is cleared, or the SAS session terminates, shortening its life, somewhat limiting access ("... in use by another process ...") and somewhat obscuring its location. SAS creates the file within the current WORK library (directory) for the SAS session, giving it a humanly meaningless name. If the user written SAS code does not cause the name of the file to be meaningfully revealed - say, in the log - then it is more difficult to discover. When SAS clears the fileref, the file goes away; when SAS terminates, WORK goes away, minimizing any artifacts left behind (Windows internals can still contain something somewhere, and probably does). An added benefit to using the TEMP device type is that it allows better support for multiple concurrent signons by differing users, without programmatically having to explicitly code filename variations. It helps to fight against the script fileref Murphyisms previously noted.
Now we are ready for the meat of the issue, the "sas ..." command line.
The old command line:
type 'sas -device grlink -nosyntaxcheck' LF;
The new command line:
type 'sas -device grlink -asynchio -echoauto -batch -logparm "open=replace write=immediate rollover=session" ' LF;
All of that is on one line. However this article is presented, it will probably appear to be wrapped on some word boundary. Again, the new command line is all on one line in the template and script file. There are script rules for spanning a command/statement/directive across multiple lines, explained in the SAS documentation. But, don't do it unless you really absolutely need to.
The sample script contains a number of "log NOTE:" messages. I replaced these with "log SCRIPT NOTE:" so that I could differentiate between regular SAS "NOTE:" messages and script notes. I did the same for "log ERROR:" , changing them to "log SCRIPT ERROR:". I also changed the first log message to allow reporting the actual script used:
log "NOTE: Script file 'tcpwin.scr' entered.";
became
log "SCRIPT NOTE: Entered script file SCRIPT_FILE";
My SAS program replaces "SCRIPT_FILE" with the script's fileref and actual filename. This breaks some of the security advantages, so I may remove it later; but, it is currently a helpful debugging feature.
My final change to the sample was to add a revision history section to the header comments - part of good change management practice.
That's it. That's all the changes I made to the sample script, to make it a workable template for unattended batch processing.
I created a macro that I call %CreateBatchSignonScript to produce the script. The heart of the macro follows:
The use of the script is also contained within macro code:
%let fileref = scrpt&I ; filename &fileref temp; %CreateBatchSignonScript( &fileref ); signon &server_id script=&fileref ;
Questions? Comments? Post them in the SAS forum "SAS Macro Facility, Data Step and SAS Language Elements". I have "watch" activated for the SAS forums.