Experiences with SAS/CONNECT and Signon Scripts

March, 2008
by Chuck Moore

About the Author
Chuck Moore

Chuck Moore began his technology career making oscilloscopes and function generators many years ago-when dinosaurs roamed about in glass houses and IBM thought personal computers weren’t of any consequence. He began programming in SAS on a daily basis in 1995 at the Chicago Board Options Exchange. He entered capacity planning on April Fools’ Day 1999 and first attended CMG in 2000. Chuck is a strong proponent of the idea that business analysis + performance analysis correlation = capacity analysis. Chuck has a Bachelor of Science in Computer Science with a Databases and Data Analysis concentration from DePaul University, Chicago.

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.

  1. The SAS signon script bypasses use of the spawner; and thus, it misses any explicit options that might be set by the spawner's command line or an explicit configuration file. So, when experimenting with this in your own implementation, it is a good idea to run "proc options;" on the remote system and review the resulting list.
  2. The command line in the script is limited to about 250+ characters. This could eliminate the ability to explicitly specify the -LOG and -PRINT options (long explicit pathnames can eat up the 250+ character limit rather quickly). On the other hand, if you use asynchronous rsubmits, it's not necessary, and actually undesirable to specify the log and list locations in the script.
  3. You should not try to use the -CONFIG option as a work around for the 250+ character command line limit. In the SAS documentation for configuration files, there is a page that has a flow chart that shows the decision tree SAS uses for finding configuration options and startup command processing. The -CONFIG option causes SAS to bypass use of the sasVx.cfg default configuration file(s), which contains a lot of necessary stuff. If you don't have access to the original, you can't make a copy. If you do have access to the original, then why bother with a signon script? SAS does provide a way to have additional configuration files in the configuration processing stream, but they require the cooperation of the SAS system administrator, and some explicit design rules to make the feature(s) useful - topics that are out of scope for this article.
  4. Generally, the script file is best referenced with a fileref from a filename statement, as opposed to an explicit filename. The fileref and its underlying file must remain intact and unchanged until after the signoff has occurred. SAS calls/uses the script for both "signon" and "signoff", even if the signoff is implicit. For asynchronous rsubmits, you cannot clear the script's fileref immediately after the endrsubmit statement, or ERRORS: will occur during signoff. Additionally, if during the course of your SAS process, you initiate multiple concurrent signons, as I do, then each signon that uses a script requires a unique fileref name - I use script&I, where &I is incremented for each new signon. If the same fileref were used for multiple concurrent signons, SAS gets confused as to where it is at in the processing of the script (there is only one internal location pointer for a single fileref).
  5. This is what I did to learn how to use a signon script for unattended batch processing on a remote SAS server.

    1. reated a "play" directory.
    2. Created a "play.sas" program.
    3. Found the sample scripts in !SASroot\connect\saslink.
    4. Copied the sample script "tcpwin.scr" to "play_tcpwin.signon.script.template" in "play"
    5. Edited the template
    6. Experimented

    !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:

    data &fileref; length script_path out_string $256; infile template _infile_=in_string; input; file &fileref filename=script_path; nothing_written = 1; *--- SAS tests = "if not zero then ... else ..."; location = index(in_string,'USERID'); if location > 0 then do; out_string = substr(in_string,1,location-1) || " '" || trim("&bbt_id") || "' " || substr(in_string,location+6); put ' ' out_string $; nothing_written = 0; end; location = index(in_string,'PASSWORD'); if location > 0 then do; out_string = substr(in_string,1,location-1) || " '" || trim("&password") || "' " || substr(in_string,location+8); put ' ' out_string $; nothing_written = 0; end; location = index(in_string,'SCRIPT_FILE'); if location > 0 then do; out_string = substr(in_string,1,location-1) || trim("&script_path") || substr(in_string,location+11); put ' ' out_string $; nothing_written = 0; end; location = index(in_string,'LOG_PATH'); if location > 0 then do; out_string = substr(in_string,1,location-1) || trim("&job") || substr(in_string,location+8); put ' ' out_string $; nothing_written = 0; end; location = index(in_string,'PRINT_PATH'); if location > 0 then do; out_string = substr(in_string,1,location-1) || "&fileref '" || trim(script_path) || "'" || substr(in_string,location+11); put ' ' out_string $; nothing_written = 0; end; if nothing_written then put ' ' in_string $; run; quit;

    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.