Skip to content

Commit 3e50d4f

Browse files
authored
feat: load interop dlls for iom/com, supply appname (#1409)
**Summary** This change updates both IOM & COM connections to load SAS interop dlls so that we can specify an application name when starting a connection. We attempt to load the DLLs in the following order... - A user specified location (specified in a new profile option, `interopLibraryFolderPath`). If this location can't be found, we proceed checking other locations - If no `interopLibraryFolderPath` is specified, we attempt to lookup the path in the registry - Then, in the Integration Technologies folder If all of these fail, we provide the user with an error message. **Testing** - [x] Tested each different way of loading interop dlls, and made sure connections worked as expected
1 parent 156fa88 commit 3e50d4f

File tree

10 files changed

+122
-14
lines changed

10 files changed

+122
-14
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). If you introduce breaking changes, please group them together in the "Changed" section using the **BREAKING:** prefix.
66

7+
## [Unreleased]
8+
9+
### Added
10+
11+
- Add application name to ITC-based (IOM/COM) connections ([#762](https://github.com/sassoftware/vscode-sas-extension/issues/762))
12+
713
## [v1.13.1] - 2025-03-04
814

915
### Fixed

client/src/connection/itc/index.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ export class ITCSession extends Session {
8989
* @returns void promise.
9090
*/
9191
protected establishConnection = async (): Promise<void> => {
92+
const { host, port, protocol, username, interopLibraryFolderPath } =
93+
this._config;
9294
const setupPromise = new Promise<void>((resolve, reject) => {
9395
this._runResolve = resolve;
9496
this._runReject = reject;
@@ -110,7 +112,7 @@ export class ITCSession extends Session {
110112
this._shellProcess.stderr.on("data", this.onShellStdErr);
111113
this._shellProcess.stdin.write(scriptContent + "\n", this.onWriteComplete);
112114
this._shellProcess.stdin.write(
113-
"$runner = New-Object -TypeName SASRunner\n",
115+
`$runner = New-Object -TypeName SASRunner -ArgumentList "${escapePowershellString(interopLibraryFolderPath || "")}"\n`,
114116
this.onWriteComplete,
115117
);
116118

@@ -120,7 +122,6 @@ export class ITCSession extends Session {
120122
* will not exist. The work dir should only be deleted when close is invoked.
121123
*/
122124
if (!this._workDirectory) {
123-
const { host, port, protocol, username } = this._config;
124125
this._shellProcess.stdin.write(`$profileHost = "${host}"\n`);
125126
this._shellProcess.stdin.write(`$port = ${port}\n`);
126127
this._shellProcess.stdin.write(`$protocol = ${protocol}\n`);
@@ -333,7 +334,11 @@ export class ITCSession extends Session {
333334
);
334335

335336
// If we encountered an error in setup, we need to go through everything again
336-
const fatalErrors = [/Setup error/, /powershell\.exe/];
337+
const fatalErrors = [
338+
/Setup error/,
339+
/powershell\.exe/,
340+
/LoadingInterop error/,
341+
];
337342
if (fatalErrors.find((regex) => regex.test(errorMessage))) {
338343
// If we can't even run the shell script (i.e. powershell.exe not found),
339344
// we'll also need to dismiss the password prompt
@@ -368,6 +373,10 @@ export class ITCSession extends Session {
368373
return l10n.t("This platform does not support this connection type.");
369374
}
370375

376+
if (/LoadingInterop error/.test(msg)) {
377+
return l10n.t("Unable to load required libraries.");
378+
}
379+
371380
// Do we have SAS messages?
372381
const sasMessages = errorMessage
373382
.replace(/\n|\t/gm, "")

client/src/connection/itc/script.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,42 @@ export const scriptContent = `
1212
class SASRunner{
1313
[System.__ComObject] $objSAS
1414
15+
SASRunner([string]$interopPath) {
16+
try {
17+
$interopDir = $this.GetInteropDirectory($interopPath)
18+
Add-Type -Path "$interopDir\\SASInterop.dll"
19+
Add-Type -Path "$interopDir\\SASOManInterop.dll"
20+
} catch {
21+
Write-Error "${ERROR_START_TAG}LoadingInterop error: $_${ERROR_END_TAG}"
22+
}
23+
}
24+
25+
[string] GetInteropDirectory([string]$defaultInteropPath) {
26+
# try to load from user specified path first
27+
if ($defaultInteropPath) {
28+
if (Test-Path -Path "$defaultInteropPath\\SASInterop.dll") {
29+
return $defaultInteropPath
30+
}
31+
}
32+
33+
# try to load path from registry
34+
try {
35+
$pathFromRegistry = (Get-ItemProperty -ErrorAction Stop -Path "HKLM:\\SOFTWARE\\WOW6432Node\\SAS Institute Inc.\\Common Data\\Shared Files\\Integration Technologies").Path
36+
if (Test-Path -Path "$pathFromRegistry\\SASInterop.dll") {
37+
return $pathFromRegistry
38+
}
39+
} catch {
40+
}
41+
42+
# try to load path from integration technologies
43+
$itcPath = "C:\\Program Files\\SASHome\\x86\\Integration Technologies"
44+
if (Test-Path -Path "$itcPath\\SASInterop.dll") {
45+
return $itcPath
46+
}
47+
48+
return ""
49+
}
50+
1551
[void]ResolveSystemVars(){
1652
try {
1753
Write-Host "${WORK_DIR_START_TAG}"
@@ -21,14 +57,16 @@ class SASRunner{
2157
Write-Error "${ERROR_START_TAG}Setup error: $_${ERROR_END_TAG}"
2258
}
2359
}
60+
2461
[void]Setup([string]$profileHost, [string]$username, [string]$password, [int]$port, [int]$protocol, [string]$serverName, [string]$displayLang) {
2562
try {
2663
# Set Encoding for input and output
2764
$OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding
2865
2966
# create the Integration Technologies objects
30-
$objFactory = New-Object -ComObject SASObjectManager.ObjectFactoryMulti2
31-
$objServerDef = New-Object -ComObject SASObjectManager.ServerDef
67+
$objFactory = New-Object -TypeName SASObjectManager.ObjectFactoryMulti2Class
68+
$objFactory.ApplicationName = "SAS Extension for Visual Studio Code"
69+
$objServerDef = New-Object -TypeName SASObjectManager.ServerDefClass
3270
$objServerDef.MachineDNSName = $profileHost # SAS Workspace node
3371
$objServerDef.Port = $port # workspace server port
3472
$objServerDef.Protocol = $protocol # 0 = COM protocol

client/src/connection/itc/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ export interface Config extends BaseConfig {
2323
port: number;
2424
username: string;
2525
protocol: ITCProtocol;
26+
interopLibraryFolderPath?: string;
2627
}

client/test/connection/itc/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ describe("ITC connection", () => {
103103
//using args here allows use of deep equal, that generates a concise diff in the test output on failures
104104
expect(stdinStub.args[0][0]).to.deep.equal(scriptContent + "\n");
105105
expect(stdinStub.args[1][0]).to.deep.equal(
106-
"$runner = New-Object -TypeName SASRunner\n",
106+
'$runner = New-Object -TypeName SASRunner -ArgumentList ""\n',
107107
);
108108

109109
expect(stdinStub.args[2][0]).to.deep.equal(

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,11 @@
367367
"description": "%configuration.SAS.connectionProfiles.profiles.iom.port%",
368368
"exclusiveMinimum": 1,
369369
"exclusiveMaximum": 65535
370+
},
371+
"interopLibraryFolderPath": {
372+
"type": "string",
373+
"default": "",
374+
"description": "%configuration.SAS.connectionProfiles.profiles.iom.interopLibraryFolderPath%"
370375
}
371376
}
372377
}
@@ -388,6 +393,11 @@
388393
"type": "string",
389394
"default": "",
390395
"description": "%configuration.SAS.connectionProfiles.profiles.com.host%"
396+
},
397+
"interopLibraryFolderPath": {
398+
"type": "string",
399+
"default": "",
400+
"description": "%configuration.SAS.connectionProfiles.profiles.com.interopLibraryFolderPath%"
391401
}
392402
}
393403
}

package.nls.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@
3939
"configuration.SAS.connectionProfiles.profiles.clientId": "SAS Viya Client ID",
4040
"configuration.SAS.connectionProfiles.profiles.clientSecret": "SAS Viya Client Secret",
4141
"configuration.SAS.connectionProfiles.profiles.com.host": "SAS COM Connection Host",
42+
"configuration.SAS.connectionProfiles.profiles.com.interopLibraryFolderPath": "SAS COM interop library path",
4243
"configuration.SAS.connectionProfiles.profiles.connectionType": "SAS Profile Connection Type",
4344
"configuration.SAS.connectionProfiles.profiles.context": "SAS Viya Context",
4445
"configuration.SAS.connectionProfiles.profiles.endpoint": "SAS Viya Connection Profile Endpoint",
4546
"configuration.SAS.connectionProfiles.profiles.iom.host": "SAS IOM Connection Host",
4647
"configuration.SAS.connectionProfiles.profiles.iom.port": "SAS IOM Connection port",
4748
"configuration.SAS.connectionProfiles.profiles.iom.username": "SAS IOM Connection username",
49+
"configuration.SAS.connectionProfiles.profiles.iom.interopLibraryFolderPath": "SAS IOM interop library path",
4850
"configuration.SAS.connectionProfiles.profiles.name": "SAS Connection Profile Name",
4951
"configuration.SAS.connectionProfiles.profiles.sasOptions": "SAS Connection SAS options",
5052
"configuration.SAS.connectionProfiles.profiles.ssh.host": "SAS SSH Connection SSH Host",

website/docs/Configurations/Profiles/additional.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,43 @@ For SAS Viya connection profiles, you can set up autoexec code that executes eac
132132
}
133133
}
134134
```
135+
136+
## SAS Interop Library Settings
137+
138+
SAS Interop library settings can be configured for ITC-based connections ("remote - IOM" and "local" connection types). ITC-based connections have a dependency on `SASInterop.dll` and `SASOManInterop.dll`. The extension tries to load these libraries from the following locations (ordered by priority):
139+
140+
1. The path specified in `interopLibraryFolderPath`. This should be an absolute path to the folder containing the files listed above.
141+
2. If `interopLibraryFolderPath` is empty or invalid, the extension will attempt to resolve the path from Windows registry
142+
3. If steps 1 and 2 fail, the extension will attempt to load libraries from the default Integration Technologies Client folder (`C:\Program Files\SASHome\x86\Integration Technologies`)
143+
144+
The following demonstrates how to setup `interopLibraryFolderPath` in user settings:
145+
146+
- SAS 9.4 (remote - IOM):
147+
148+
```json
149+
{
150+
"profiles": {
151+
"sas9IOM": {
152+
"host": "host",
153+
"username": "username",
154+
"port": 8591,
155+
"ConnectionType": "iom",
156+
"interopLibraryFolderPath": "C:\\Program Files\\SASHome\\x86\\Integration Technologies"
157+
}
158+
}
159+
}
160+
```
161+
162+
- SAS 9.4 (local):
163+
164+
```json
165+
{
166+
"profiles": {
167+
"sas9COM": {
168+
"host": "localhost",
169+
"ConnectionType": "com",
170+
"interopLibraryFolderPath": "C:\\Program Files\\SASHome\\x86\\Integration Technologies"
171+
}
172+
}
173+
}
174+
```

website/docs/Configurations/Profiles/sas9iom.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ A SAS 9.4 (remote – IOM) connection profile includes the following parameters:
2222

2323
`"connectionType": "iom"`
2424

25-
| Name | Description | Additional Notes |
26-
| ---------- | ------------------- | ----------------------------------------------------------- |
27-
| `host` | IOM Server Host | Appears when hovering over the status bar. |
28-
| `username` | IOM Server Username | The username to establish the IOM connection to the server. |
29-
| `port` | IOM Server Port | The port of the IOM server. Default value is 8591. |
25+
| Name | Description | Additional Notes |
26+
| -------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------ |
27+
| `host` | IOM Server Host | Appears when hovering over the status bar. |
28+
| `username` | IOM Server Username | The username to establish the IOM connection to the server. |
29+
| `port` | IOM Server Port | The port of the IOM server. Default value is 8591. |
30+
| `interopLibraryFolderPath` | IOM interop library path | A custom path to a folder containing SAS interop libraries (`SASInterop.dll` and `SASOManInterop.dll`) |
3031

3132
## Steps to install ITCLIENT
3233

website/docs/Configurations/Profiles/sas9local.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ A local SAS 9.4 connection profile includes the following parameters:
1616

1717
`"connectionType": "com"`
1818

19-
| Name | Description | Additional Notes |
20-
| ------ | -------------------------------- | ------------------------------- |
21-
| `host` | Indicates SAS 9.4 local instance | Defaults to "localhost" for com |
19+
| Name | Description | Additional Notes |
20+
| -------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------ |
21+
| `host` | Indicates SAS 9.4 local instance | Defaults to "localhost" for com |
22+
| `interopLibraryFolderPath` | COM interop library path | A custom path to a folder containing SAS interop libraries (`SASInterop.dll` and `SASOManInterop.dll`) |

0 commit comments

Comments
 (0)