This repository was archived by the owner on Aug 16, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPowerShellCall.ts
More file actions
163 lines (145 loc) · 5.3 KB
/
PowerShellCall.ts
File metadata and controls
163 lines (145 loc) · 5.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import { TypeGuard } from '@shadow578/type-guardian';
import { isAny } from '@shadow578/type-guardian/lib/TypeGuards';
import { PowerShellError } from '../powershell/PowerShellError';
import { assertParametersValid, ParameterRecord } from '../powershell/shim/Parameters';
import { Shim } from '../powershell/shim/Shim';
import { getFunctionParameterNames, getRandomString } from '../util';
import { IPowerShellBinding } from './PowerShellBinding';
/**
* additional options for {@link PowerShellCall}
*/
export interface PowerShellCallOptions {
/**
* expand parameters to be available directly.
* if enabled, '$params.MyParameter' can be replaced with '$MyParameter'.
*
* default value: true
*/
expandParameters: boolean;
/**
* how deep the output object will be serialized.
* note that higher values may impact performance.
*
* default value: 2
*/
serializationDepth: number;
/**
* override for the shim used with this command.
* if not defined, the shim provided by the binding class is used.
*
* default value: undefined
*/
shim?: Shim;
}
/**
* default values for {@link PowerShellCallOptions}
*/
const DEFAULT_OPTIONS: PowerShellCallOptions = {
expandParameters: true,
serializationDepth: 2,
};
/**
* helper function to make typescript shut up about missing function implementation
*/
export function psCall<T>(): Promise<T> {
throw new Error('powershell call should have been replaced by decorator');
}
/**
* decorator for powershell binding functions
*
* @param command the powershell command to be executed by this binding function
* @param typeGuard type guard to validate the result data parsed from powershell
* @param options additional options for the binding
* @example
* class ExampleBinding extends PowerShellBinding {
* \@PowerShell('Write-Output "Hello, $name"', isString)
* greet(name: string): Promise<string> {
* // dummy implementation
* return <Promise<string>>{}
* }
* }
*/
export function PowerShellCall<T = void>(
command: string,
typeGuard: TypeGuard<T> = isAny,
options: Partial<PowerShellCallOptions> = {},
) {
// merge default values into provided options
const fullOptions: PowerShellCallOptions = {
...DEFAULT_OPTIONS,
...options,
};
// build the decorator
return (
_target: IPowerShellBinding,
_methodName: string,
descriptor: TypedPropertyDescriptor<(...params: never[]) => Promise<T>>,
) => {
// get parameter name list of original method
const originalMethod = descriptor.value;
if (originalMethod === undefined) {
throw new TypeError('method descriptor value was undefined');
}
const parameterNames = getFunctionParameterNames(originalMethod);
// re-defined the method
descriptor.value = async function (this: IPowerShellBinding, ...parameterValues: unknown[]): Promise<T> {
const executionId = getRandomString(20);
const shim = fullOptions.shim || this.shim;
// prepare parameter records
const parameters = makeParameterRecord(parameterNames, parameterValues);
// shim and execute the command
// note: powershell will not execute multi-line commands immediately, so a
// newline has to be inserted after the command
const shimmedCommand = shim.prepare(executionId, command, parameters, {
expandParameters: fullOptions.expandParameters,
resultSerializationDepth: fullOptions.serializationDepth,
});
const invocationResult = await this.shell.invoke(shimmedCommand + '\n');
// parse the result data
const result = shim.parseResult(
executionId,
shim.requiresStdout ? invocationResult.stdout?.toString() : undefined,
shim.requiresStderr ? invocationResult.stderr?.toString() : undefined,
);
// re-throw error thrown in poweshell
if (result.error !== undefined) {
throw new PowerShellError(result.error);
}
// attempt to validate the result data as-is
if (typeGuard(result.data)) {
return result.data;
}
// if the result data is a array, attempt to
// validate the elements in result data one-by-one
if (Array.isArray(result.data)) {
for (const element of result.data) {
if (typeGuard(element)) {
return element;
}
}
}
// validation failed for all possible values
throw new TypeError('validation of result data failed');
};
};
}
/**
* convert a method's raw parameter list to a record from parameter name to parameter value
*
* @param parameterNames the parameter name list. see {@link getFunctionParameterNames}
* @param parameterValues the parameter value list
* @returns a record from the method parameter name to the parameter value
*/
function makeParameterRecord(parameterNames: string[], parameterValues: unknown[]): ParameterRecord {
// ensure parameter names and parameter values have same length
if (parameterNames.length !== parameterValues.length) {
throw new RangeError('parameter values length is unequal to expected parameter names length');
}
// create a map from parameter name to parameter value
const parameters: Record<string, unknown> = {};
parameterNames.forEach((name, i) => {
parameters[name] = parameterValues[i];
});
assertParametersValid(parameters);
return parameters;
}