Skip to content

Commit c8f363e

Browse files
authored
Merge pull request #18 from nfedyk/feature/updates-tests
Extended testing and sanitization
2 parents 5c9ca73 + d529dac commit c8f363e

9 files changed

+663
-20
lines changed

Dockerfile

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
FROM mcr.microsoft.com/powershell:7.4-mariner-2.0-arm64
2+
3+
ENV APP_ROOT_DIR="/app"
4+
5+
RUN pwsh -Command Set-PSRepository -Name PSGallery -InstallationPolicy Trusted && \
6+
pwsh -Command Install-Module -Name ExchangeOnlineManagement -Scope AllUsers -RequiredVersion 3.5.0 && \
7+
pwsh -Command Set-PSRepository -Name PSGallery -InstallationPolicy Untrusted
8+
9+
RUN yum install -y nodejs npm
10+
11+
# Set the working directory in the container
12+
WORKDIR /app
13+
14+
# Copy package.json and package-lock.json
15+
COPY package*.json ./
16+
17+
# Install dependencies
18+
RUN npm install
19+
20+
# Copy the rest of the application code
21+
COPY . .
22+
23+
# Command to run tests
24+
CMD ["npm", "run", "test-docker"]

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Node.js module that provides a registry and gateway for execution of pre-defined
77
* [Overview](#overview)
88
* [Concepts](#concepts)
99
* [Usage](#usage)
10+
* [Testing](#testing)
1011
* [History](#history)
1112
* [Related tools](#related)
1213

@@ -54,9 +55,20 @@ Three sets of init commands are availiable as of version `1.1.0`:
5455

5556
7) There is also a unit-test (```test\all.js```) for the command registry in ```o365Utils.js``` which gives an example of usage for all thre possible Exchange connect variations.
5657

58+
### <a id="testing"></a>Testing
59+
Project test can be executed by running `npm test` command on Windows machine. Connection to Exchange Online is required for the tests to pass.
60+
61+
There is also option to run Docker based tests. You need to configure `environment` variables in `docker-compose.yml` file in order to define connection parameters. To run tests in Docker container, execute `docker-compose run test` command once the configuration is done.
62+
63+
Exchange online tests will be skipped if the connection is not available.
64+
65+
5766
### <a id="history"></a>History
5867

5968
```
69+
v1.1.4 - 2024-11-22
70+
- Extended testing and fixed escaping reserved variables and special characters in commands
71+
6072
v1.1.3 - 2024-11-14
6173
- Added support for [multivalued parameters](https://learn.microsoft.com/en-us/exchange/modifying-multivalued-properties-exchange-2013-help) in commands
6274

docker-compose.yml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: '3'
2+
services:
3+
test:
4+
build: .
5+
volumes:
6+
- .:/app
7+
- /app/node_modules
8+
environment:
9+
- APPLICATION_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
10+
- TENANT=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
11+
- CERTIFICATE_PASSWORD=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
12+
- CERTIFICATE=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
13+
- O365_TENANT_DOMAIN_NAME=sample.com

o365Utils.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,13 @@ var o365CommandRegistry = {
602602
'return': {
603603
type: 'none'
604604
}
605-
}
605+
},
606+
getStatus: {
607+
command: 'Get-ConnectionInformation | ConvertTo-Json',
608+
return: {
609+
type: 'json'
610+
}
611+
},
606612
};
607613

608614
module.exports.o365CommandRegistry = o365CommandRegistry;

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
{
22
"name": "powershell-command-executor",
3-
"version": "1.1.3",
3+
"version": "1.1.4",
44
"description": "Provides a registry and gateway for execution powershell commands through long-lived established remote PSSessions via a stateful-process-command-proxy pool of powershell processes",
55
"main": "psCommandService.js",
66
"directories": {
77
"test": "test"
88
},
99
"scripts": {
10-
"test": "mocha test/all.js"
10+
"test": "mocha test/all.js",
11+
"test-docker": "mocha test/unit.js"
1112
},
1213
"keywords": [
1314
"command",

psCommandService.js

+18-15
Original file line numberDiff line numberDiff line change
@@ -366,13 +366,15 @@ PSCommandService.prototype._sanitize = function (toSanitize, isQuoted) {
366366
.replace(/\\n/g, "\\$&") // kill string based newline attempts
367367
.replace(/[`#]/g, "`$&"); // escape stuff that could screw up variables
368368

369+
const sanitizeRegex = /[;\$\|\(\)\{\}\[\]\\]/g;
370+
const multiValuedRegex = /@\{([^}]*)\}/g;
371+
369372
if (isQuoted) { // if quoted, escape all quotes
370373
toSanitize = toSanitize.replace(/'/g, "'$&");
371-
} else if (
372-
!reservedVariableNames.includes(toSanitize) && // skip if this is reserved variable name
373-
multiValuedRegex.test(toSanitize) // process is this is multi-valued parameter
374-
) {
374+
} else if (multiValuedRegex.test(toSanitize)) {
375+
// process is this is multi-valued parameter
375376
const extractParams = (str, key) => {
377+
// values must be wrapped in double quotes, so we can split them by comma
376378
const match = str.match(new RegExp(`${key}="([^;]+)(?:";|"})`, "i"));
377379
return match
378380
? match[1]
@@ -385,18 +387,19 @@ PSCommandService.prototype._sanitize = function (toSanitize, isQuoted) {
385387

386388
const addItemsSanitized = extractParams(toSanitize, "Add");
387389
const removeItemsSanitized = extractParams(toSanitize, "Remove");
388-
389-
let result = "@{";
390-
if (addItemsSanitized.length > 0) {
391-
result += `Add="${addItemsSanitized.join('","')}"`;
392-
}
393-
if (removeItemsSanitized.length > 0) {
394-
if (addItemsSanitized.length > 0) result += "; ";
395-
result += `Remove="${removeItemsSanitized.join('","')}"`;
390+
if (addItemsSanitized.length > 0 || removeItemsSanitized.length > 0) {
391+
let result = "@{";
392+
if (addItemsSanitized.length > 0) {
393+
result += `Add="${addItemsSanitized.join('","')}"`;
394+
}
395+
if (removeItemsSanitized.length > 0) {
396+
if (addItemsSanitized.length > 0) result += "; ";
397+
result += `Remove="${removeItemsSanitized.join('","')}"`;
398+
}
399+
result += "}";
400+
toSanitize = result;
396401
}
397-
result += "}";
398-
toSanitize = result;
399-
} else {
402+
} else if (!reservedVariableNames.includes(toSanitize)) { // skip if this is reserved variable name
400403
toSanitize = toSanitize.replace(sanitizeRegex, "`$&");
401404
}
402405

test/all.js

+1
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ describe('test PSCommandService w/ o365CommandRegistry', function () {
463463
TENANT_ID,
464464
10000, 30000, 60000), o365Utils.getO365PSDestroyCommands());
465465
});
466+
// The CertificateThumbprint parameter is supported only in Microsoft Windows.
466467
it('Should test all group and mail contact commands then cleanup with Certificate Thumb Print based auth', function (done) {
467468
this.timeout(120000);
468469
testRun(done, o365Utils.getO365PSInitCommands(

0 commit comments

Comments
 (0)