Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ShellExecute.cs
1using System;
2using System.Diagnostics;
3using System.Text;
4using System.Threading;
5using System.Threading.Tasks;
6using Waher.Events;
11
13{
18 {
30 : base(new ScriptNode[] { FileName, Arguments, WorkFolder },
32 {
33 }
34
46 ScriptNode TimeoutMs, int Start, int Length, Expression Expression)
47 : base(new ScriptNode[] { FileName, Arguments, WorkFolder, TimeoutMs },
49 {
50 }
51
63 ScriptNode TimeoutMs, ScriptNode LogStandardOutput, int Start, int Length, Expression Expression)
64 : base(new ScriptNode[] { FileName, Arguments, WorkFolder, TimeoutMs, LogStandardOutput },
66 {
67 }
68
82 ScriptNode TimeoutMs, ScriptNode LogStandardOutput, ScriptNode KillOnTimeout, int Start, int Length, Expression Expression)
83 : base(new ScriptNode[] { FileName, Arguments, WorkFolder, TimeoutMs, LogStandardOutput, KillOnTimeout },
85 {
86 }
87
91 public override string FunctionName => nameof(ShellExecute);
92
96 public override string[] DefaultArgumentNames => new string[]
97 {
98 "FileName",
99 "Arguments",
100 "WorkFolder",
101 "TimeoutMs",
102 "LogStandardOutput",
103 "KillOnTimeout"
104 };
105
110 public override bool IsAsynchronous => true;
111
119 {
120 return this.EvaluateAsync(Arguments, Variables).Result;
121 }
122
129 public override async Task<IElement> EvaluateAsync(IElement[] Arguments, Variables Variables)
130 {
131 if (!(Arguments[0].AssociatedObjectValue is string FileName) ||
132 !(Arguments[1].AssociatedObjectValue is string Arg) ||
133 !(Arguments[2].AssociatedObjectValue is string WorkFolder))
134 {
135 throw new ScriptRuntimeException("Expected string arguments.", this);
136 }
137
138 int TimeoutMs;
139 bool LogStandardOut = false;
140 bool KillOnTimeout = true;
141
142 if (Arguments.Length > 3)
143 {
144 TimeoutMs = (int)Expression.ToDouble(Arguments[3].AssociatedObjectValue);
145 if (TimeoutMs < 0)
146 throw new ScriptRuntimeException("Timeout must be non-negative.", this);
147 }
148 else
149 TimeoutMs = 1000 * 60 * 5;
150
151 if (Arguments.Length > 4)
152 {
153 if (Arguments[4].AssociatedObjectValue is bool PLogStandardOut)
154 LogStandardOut = PLogStandardOut;
155 else
156 throw new ScriptRuntimeException("LogStandardOut out must be a Boolean value.", this);
157 }
158
159 if (Arguments.Length > 5)
160 {
161 if (Arguments[5].AssociatedObjectValue is bool PKillOnTimout)
162 KillOnTimeout = PKillOnTimout;
163 else
164 throw new ScriptRuntimeException("PKillOnTimout must be a Boolean value.", this);
165 }
166
167 ProcessStartInfo ProcessInformation = new ProcessStartInfo()
168 {
169 FileName = FileName,
170 Arguments = Arg,
171 UseShellExecute = false,
172 RedirectStandardError = true,
173 RedirectStandardOutput = true,
174 WorkingDirectory = WorkFolder,
175 CreateNoWindow = true,
176 WindowStyle = ProcessWindowStyle.Hidden,
177 ErrorDialog = false
178 };
179
180 Process P = new Process();
181 TaskCompletionSource<IElement> ResultSource = new TaskCompletionSource<IElement>();
182
183 if (LogStandardOut)
184 {
185 P.Exited += (Sender, e) =>
186 {
187 ResultSource.TrySetResult(new BooleanValue(P.ExitCode == 0));
188 };
189 }
190 else
191 {
192 P.Exited += async (Sender, e) =>
193 {
194 try
195 {
196 if (P.ExitCode != 0)
197 {
198 string ErrorText = await P.StandardError.ReadToEndAsync();
199 ResultSource.TrySetException(new ScriptRuntimeException(ErrorText, this));
200 }
201 else
202 {
203 string s = await P.StandardOutput.ReadToEndAsync();
204 ResultSource.TrySetResult(new StringValue(s));
205 }
206 }
207 catch (Exception ex)
208 {
209 ResultSource.TrySetException(ex);
210 }
211 };
212 }
213
214 if (TimeoutMs > 0)
215 _ = Task.Delay(TimeoutMs).ContinueWith(Prev => ResultSource.TrySetException(new TimeoutException("Process did not exit within the provided time.")));
216
217 using (CancellationTokenRegistration Registration = Variables.CancellationToken.Register(() => ResultSource.TrySetException(new OperationCanceledException("Evaluation cancelled."))))
218 {
219 P.StartInfo = ProcessInformation;
220 P.EnableRaisingEvents = true;
221 P.Start();
222
223 if (LogStandardOut)
224 {
225 BufferedLogger OutputLogger = new BufferedLogger(Message => Log.Informational(Message));
226 BufferedLogger ErrorLogger = new BufferedLogger(Message => Log.Error(Message));
227
228 P.ErrorDataReceived += (Sender, e) => ErrorLogger.Push(e.Data);
229 P.OutputDataReceived += (Sender, e) => OutputLogger.Push(e.Data);
230
231 P.BeginOutputReadLine();
232 P.BeginErrorReadLine();
233 }
234
235 try
236 {
237 return await ResultSource.Task;
238 }
239 finally
240 {
241 try
242 {
243 bool Kill = false;
244
245 if (ResultSource.Task.Exception.InnerException is OperationCanceledException)
246 Kill = true;
247
248 if (ResultSource.Task.Exception.InnerException is TimeoutException && KillOnTimeout)
249 Kill = true;
250
251 if (P.HasExited)
252 Kill = false;
253
254 if (Kill)
255 P.Kill();
256 }
257 catch (Exception e)
258 {
259 Log.Exception(e);
260 }
261 }
262 }
263 }
264
265 private class BufferedLogger
266 {
267 private readonly object @lock;
268 private readonly StringBuilder buffer;
269 private readonly Action<string> logger;
270 private CancellationTokenSource cts;
271
272 public BufferedLogger(Action<string> Logger)
273 {
274 this.@lock = new object();
275 this.buffer = new StringBuilder();
276 this.cts = new CancellationTokenSource();
277 this.logger = Logger;
278 }
279
280 public void Push(string Text)
281 {
282 lock (this.@lock)
283 {
284 this.buffer.AppendLine(Text);
285
286 CancellationTokenSource Prev = this.cts;
287 this.cts = new CancellationTokenSource();
288
289 Prev.Cancel();
290 Prev.Dispose();
291
292 _ = this.FlushDelayed(this.cts.Token);
293 }
294 }
295
296 private async Task FlushDelayed(CancellationToken Token)
297 {
298 try
299 {
300 await Task.Delay(500, Token);
301 }
302 catch (TaskCanceledException)
303 {
304 return;
305 }
306
307 this.Flush();
308 }
309
310 void Flush()
311 {
312 string Message;
313
314 lock (this.@lock)
315 {
316 Message = this.buffer.ToString();
317 if (string.IsNullOrEmpty(Message.Trim()))
318 return;
319
320 this.buffer.Clear();
321 }
322
323 this.logger(Message);
324 }
325 }
326 }
327}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
static void Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
Definition: Log.cs:692
static void Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
Definition: Log.cs:344
Class managing a script expression.
Definition: Expression.cs:41
static double ToDouble(object Object)
Converts an object to a double value.
Definition: Expression.cs:5110
Base class for multivariate funcions.
static readonly ArgumentType[] argumentTypes5Scalar
Five scalar parameters.
ScriptNode[] Arguments
Function arguments.
static readonly ArgumentType[] argumentTypes3Scalar
Three scalar parameters.
static readonly ArgumentType[] argumentTypes6Scalar
Six scalar parameters.
static readonly ArgumentType[] argumentTypes4Scalar
Four scalar parameters.
Base class for all nodes in a parsed script tree.
Definition: ScriptNode.cs:69
int Length
Length of expression covered by node.
Definition: ScriptNode.cs:101
Expression Expression
Expression of which the node is a part.
Definition: ScriptNode.cs:177
int Start
Start position in script expression.
Definition: ScriptNode.cs:92
Boolean-valued number.
Definition: BooleanValue.cs:12
ShellExecute(FileName,Arguments,WorkFolder[,TimeoutMs[,LogStandardOutput[,KillOnTimeout]]])
Definition: ShellExecute.cs:18
ShellExecute(ScriptNode FileName, ScriptNode Arguments, ScriptNode WorkFolder, ScriptNode TimeoutMs, ScriptNode LogStandardOutput, int Start, int Length, Expression Expression)
Definition: ShellExecute.cs:62
ShellExecute(ScriptNode FileName, ScriptNode Arguments, ScriptNode WorkFolder, int Start, int Length, Expression Expression)
ShellExecute(FileName,Arguments,WorkFolder)
Definition: ShellExecute.cs:28
override IElement Evaluate(IElement[] Arguments, Variables Variables)
Evaluates the function.
ShellExecute(ScriptNode FileName, ScriptNode Arguments, ScriptNode WorkFolder, ScriptNode TimeoutMs, int Start, int Length, Expression Expression)
ShellExecute(FileName,Arguments,WorkFolder,TimeoutMs)
Definition: ShellExecute.cs:45
ShellExecute(ScriptNode FileName, ScriptNode Arguments, ScriptNode WorkFolder, ScriptNode TimeoutMs, ScriptNode LogStandardOutput, ScriptNode KillOnTimeout, int Start, int Length, Expression Expression)
ShellExecute(FileName,Arguments,WorkFolder,TimeoutMs,LogStandardOutput,KillOnTimeout)
Definition: ShellExecute.cs:81
override bool IsAsynchronous
If the node (or its decendants) include asynchronous evaluation. Asynchronous nodes should be evaluat...
override string FunctionName
Name of the function
Definition: ShellExecute.cs:91
override string[] DefaultArgumentNames
Default Argument names
Definition: ShellExecute.cs:96
override async Task< IElement > EvaluateAsync(IElement[] Arguments, Variables Variables)
Evaluates the function.
Collection of variables.
Definition: Variables.cs:25
CancellationToken CancellationToken
Cancellation token, that can be used to monitor for script abortion.
Definition: Variables.cs:444
Basic interface for all types of elements.
Definition: IElement.cs:21
Definition: ImplTypes.g.cs:58