This post is another in my series about creating apps with SAS Integration Technologies, a topic that I'm preparing for SAS Global Forum 2013.
In this article, I'll describe how to use Windows PowerShell to connect to a remote SAS Workspace, run a SAS program, and retrieve the results. This can be a useful method to implement a batch-style SAS job that you initiate from a client machine -- a common requirement in many SAS shops. The complete code example is on GitHub as SasWorkspaceExample.ps1.
Like my previous example with the SAS Metadata Server, this example begins with using SAS Object Manager. Use SAS Object Manager to create a ServerDef object that you use to connect to a SAS Workspace. This time we'll use the Workspace server port (by default, 8591) and the ClassIdentifier value for a SAS Workspace object. Remember, I used PROC IOMOPERATE to remind me which value that is, since I've never been any good at memorizing a 32-character hexadecimal value.
$objFactory = New-Object -ComObject SASObjectManager.ObjectFactoryMulti2 $objServerDef = New-Object -ComObject SASObjectManager.ServerDef $objServerDef.MachineDNSName = "server.mycompany.com" # SAS Workspace node $objServerDef.Port = 8591 # workspace server port $objServerDef.Protocol = 2 # 2 = IOM protocol # Class Identifier for SAS Workspace $objServerDef.ClassIdentifier = "440196d4-90f0-11d0-9f41-00a024bb830c"
Connecting to a SAS Workspace
With an instance of an ObjectFactory class and the ServerDef class, you can now establish a connection to the server:
# create and connect to the SAS session $objSAS = $objFactory.CreateObjectByServer( "SASApp", # server name $true, $objServerDef, # used server definition for Workspace "sasdemo", # user ID "Password1" # password )
When this statement completes successfully the $objSAS variable contains a connection to the SAS Workspace. With this connection, you can run SAS programs, read SAS data, and transfer files between the SAS Workspace and your application. (I'll cover how to transfer files in a future post.)
Running a SAS program with LanguageService
The SAS Workspace provides an API called LanguageService, which allows you to run SAS programs and retrieve the text-based results, including the SAS log and the SAS listing output. Here is a simple example using the $objSAS variable from the previous example:
$program = "ods listing; proc means data=sashelp.cars; run;" # run the program $objSAS.LanguageService.Submit($program);
These statements will run the program, but they won't retrieve your results. To see the contents of the SAS log, use the FlushLog method as in this example:
# flush the log - could redirect to external file Write-Output "LOG:" $log = "" do { $log = $objSAS.LanguageService.FlushLog(1000) Write-Output $log } while ($log.Length -gt 0)
The FlushLog method requires that you specify the number of bytes that you want to retrieve in a single call. In the above example, we used 1000. But what if your SAS log is greater than 1000 characters? That's the reason for the do..while loop. In this example, the PowerShell statements will continue to retrieve and emit the SAS log content until the FlushLog method no longer returns any content (that is, while the length of the $log variable is greater than 0).
You can retrieve the SAS listing output in a similar way by using the FlushList method:
# flush the output - could redirect to external file Write-Output "Output:" $list = "" do { $list = $objSAS.LanguageService.FlushList(1000) Write-Output $list } while ($list.Length -gt 0)
This picture shows the Windows PowerShell ISE (scripting environment) with the script output:
Closing the SAS Session
It's important to manage the lifetime of the SAS session in a responsible way. If you create a SAS Workspace connection and use it within your application, you should call the Close method on the SAS.Workspace object when your work is complete. This is especially true if your application is designed to stay running for a long time, even when the SAS session is not in use.
To close the SAS Workspace session, use the Close() method:
# end the SAS session $objSAS.Close()
23 Comments
Pingback: Using PowerShell to download a file from a SAS Workspace
Pingback: Build your own SAS client app with Microsoft .NET - The SAS Dummy
If I don't have access to use Proc iomoperate, how can I find out the class identifier of the workspace server?
Also just double checking, setting protocol = 2, tells it to use IOM Protocol?
Yes, protocol=2 is IOM. The Workspace Server class ID is always the same: 440196d4-90f0-11d0-9f41-00a024bb830c. PROC IOMOPERATE simply provides a convenient reference.
If you want to simplify the code to:
$ws = Connect-SasWorkspaceServer "iom://server.mycompany.com;Bridge" "sasdemo" "Password1" Invoke-SasCode $ws "ods listing; proc means data=sashelp.cars; run;" Read-SasLog $ws Read-SasOdsListing $ws Disconnect-SasServer $ws
check out my open source project at https://sourceforge.net/p/cmdlets4sas/
Chris, one of the comments in the code published on github is "# could be read from external file". Can you give an example of how you could run a .sas program in powershell? Thanks.
Erik,
Yes. Simply replace that line with a command to read in the program file that you want. The PowerShell Get-Content cmdlet will read an external file. For example:
If you want to pass the file name in as a command argument, you could add processing like:
Would it be possible to connect to the work space server using the metadata server profile(configuration61.xml) like EG does it, if so, how ?
Thanks!
Laurent,
EG uses this file to store information about your SAS Metadata connection. Once you connect with EG, a metadata query is issued to find the available SAS Workspace Servers.
You can do the same work via PowerShell, but you have to parse/query items yourself. You can use standard XML parsing techniques to find all of the required values for host, port, and user ID. But you'll need your own method of prompting/retrieving the password. However, this connects you to the SAS Metadata Server, and not the SAS Workspace. Then you would need to query SAS metadata for the available workspace servers.
If you have a simple single-machine server environment, perhaps the SAS Metadata Server and SAS Workspace Server are the same host machine, and only the port numbers differ. If that's true, you might be able to use the configuration file to find some values, and then hardcode the rest based on assumptions in your configuration.
You could also use EG automation to discover the profile information, as in this sample script. (See the full article here on sasCommunity.org.)
Chris how can authenticate the connection using a metadata identity (login authenticaed via ldap) instead of machine authentication?
"sasdemo", # user ID
"Password1" # password
thank you
If using integrated windows auth (kerberos), you can leave the userid/password blank, and add a value to the objServerDef:
$obServerDef.BridgeSecurityPackage = "Negotiate"
For SAS token authentication, once connected to metadata, you can obtain credentials for one-time use to connect to other resources (such as a SAS workspace). You might be interested in the Cmdlets4SAS project, which encapsulates this feature within its library.
an all-in-one script (Metadata connection and workspace execution) is easier to use. there is a way to retrieve the one-time user credentials? thank you
You'll find an example of this in the Cmdlets4SAS project that I mentioned earlier. This uses a method to get what you need for SAS token authentication.
When I run this code by submitting a SAS program file, using the Get-Content cmdlet, the log does not show me the expected sequence of source statements for each step followed by the log messages related to that step. Instead, all the source statements appear at the top of the log – with no line feeds – and then the log messages for each step appear one after another, with no source code. And the line breaks appear at random positions of the text, not at statement boundaries. I tried doing options source source2; but this doesn't affect the log content.
%let domain = HO; data ho_hos (keep=subjid studyid usubjid
1 ! hoterm hostdtc hoendtc coval EXICUYN visit) ho_icu (keep=subjid studyid usubjid h
oterm hostdtc hoendtc visit coval);
1 ! length subjid $20 studyid $11 usubjid $19 ; set raw_cdms.extrt(DROP=studyid); subjid = subject; studyid=project;
NOTE: DATA statement used (Total process time):
real time 0.01 seconds
cpu time 0.00 seconds
NOTE: DATA statement used (Total process time):
real time 0.02 seconds
cpu time 0.03 seconds
Any idea what is causing this result and why the log text isn't ordered in the normal way ?
I don't know what would cause the log content to be returned in the wrong order. You didn't change the Async property on the LanguageService by any chance, did you? Async is False by default, but if it were set to True then the Log fetching code could be processed before the program has completed running.
Hi Chris, thanks for replying. No, I didn't change Async - was not even aware of that property. Just as an experiment, I went back to the original example code, and added 2 more simple steps to the proc means: proc sort and proc print. I observe the same effect as before. Below I've pasted what is written to the console. Note the source statements for all 3 steps appear as one continuous block of text; then that is followed by the messages for each step, absent the source statements.
Is it possible that different defaults might somehow be set on the server side? I'd be really curious what result you are seeing with your log; I can't tell just from the screen shot above, as only the first line of the log is visible.
NOTE: The autoexec file, E:\SAS\Config\Lev1\SASApp\WorkspaceServer\autoexec.sas, was executed at server initial
ization.
1 options formchar='|----|+|---+=|-/\*';ods listing; proc means data=sashelp.cars mean mode min max;
run;proc sort
1 ! data=sashelp.cars out=work.cars; by origin type; run;proc print data=work.cars; where make='Volv
o'; var make model msrp
1 ! invoice; run;
NOTE: There were 428 observations read from the data set SASHELP.CARS.
NOTE: The PROCEDURE MEANS printed page 1.
NOTE: PROCEDURE MEANS used (Total process time):
real time 0.01 seconds
cpu time 0.01 seconds
NOTE: There were 428 observations read from the data set SASHELP.CARS.
NOTE: The data set WORK.CARS has 428 observations and 15 variables.
NOTE: PROCEDURE SORT used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
NOTE: There were 12 observations read from the data set WORK.CARS.
WHERE make='Volvo';
NOTE: The PROCEDURE PRINT printed page 2.
NOTE: PROCEDURE PRINT used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
Not sure what's happening here -- I'm not seeing these results. You could also try the SASHarness application (written about here) and see if you get the same thing. The logic is very similar, just using .NET instead of PowerShell.
Hi Chris,
I have install Cmdlets4Sas, but I can not import module.
Import-Module : Could not load file or assembly 'file:///C:\Program Files\WindowsPowerShell\Modules\Cmdlets4Sas\Interop.SAS.dll' or one of its dependencies. Operation is not supported. (Excep
tion from HRESULT: 0x80131515)
At line:1 char:1
+ Import-Module Cmdlets4Sas
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Import-Module], FileLoadException
+ FullyQualifiedErrorId : FormatXmlUpdateException,Microsoft.PowerShell.Commands.ImportModuleCommand
Any idea what is causing this error?
Thank you.
You'll find this file (and other required files) in the SAS Integration Technologies client folder. On my machine these are in:
C:\Program Files\SASHome\Integration Technologies
OR
C:\Program Files\SASHome\x86\Integration Technologies
depending on whether you're using 64-bit or 32-bit PowerShell. You should add this to your system search PATH environment variable.
Pingback: Build your own SAS client app with Microsoft .NET - The SAS Dummy
Pingback: Using PowerShell to download a file from a SAS Workspace
Hey Chris,
The information that you have provided above for executing a SAS program from PowerShell really helps. The same way, can we use the Get-Content cmdlet to execute the SAS EG project and read the output? Something like below -
$program = Get-Content "U:\SGF2013\PowerShell\sample.egp"
Please let me know.
Thanks!
That Get-Content won't work, as the EGP file is binary and the program is an element inside. However, you'll find some PowerShell examples here that might help -- these automate SAS Enterprise Guide and can extract content for you.