@@ -181,6 +181,8 @@ internal static class CachedReflectionInfo
181
181
182
182
internal static readonly MethodInfo FileRedirection_BindForExpression =
183
183
typeof ( FileRedirection ) . GetMethod ( nameof ( FileRedirection . BindForExpression ) , instanceFlags ) ;
184
+ internal static readonly MethodInfo FileRedirection_CallDoCompleteForExpression =
185
+ typeof ( FileRedirection ) . GetMethod ( nameof ( FileRedirection . CallDoCompleteForExpression ) , instanceFlags ) ;
184
186
internal static readonly ConstructorInfo FileRedirection_ctor =
185
187
typeof ( FileRedirection ) . GetConstructor ( instanceFlags , null , CallingConventions . Standard ,
186
188
new Type [ ] { typeof ( RedirectionStream ) , typeof ( bool ) , typeof ( string ) } , null ) ;
@@ -3186,6 +3188,7 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo
3186
3188
exprs . Add ( Expression . Call ( oldPipe , CachedReflectionInfo . Pipe_SetVariableListForTemporaryPipe , s_getCurrentPipe ) ) ;
3187
3189
}
3188
3190
3191
+ List < Expression > extraFileRedirectExprs = null ;
3189
3192
// We must generate the code for output redirection to a file before any merging redirections
3190
3193
// because merging redirections will use funcContext._outputPipe as the value to merge to, so defer merging
3191
3194
// redirections until file redirections are done.
@@ -3194,35 +3197,55 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo
3194
3197
// This will simply return a Linq.Expression representing the redirection.
3195
3198
var compiledRedirection = VisitFileRedirection ( fileRedirectionAst ) ;
3196
3199
3197
- // For non-output streams (error, warning, etc.) we must save the old stream so it can be restored.
3198
- // The savedPipe variable is used only for setting funcContext._outputPipe for redirecting Output to file.
3199
- // The savedPipes variable is used for restoring non-output streams (error, warning, etc.).
3200
- var savedPipes = NewTemp ( typeof ( Pipe [ ] ) , "savedPipes" ) ;
3201
- temps . Add ( savedPipes ) ;
3200
+ if ( extraFileRedirectExprs == null )
3201
+ {
3202
+ extraFileRedirectExprs = new List < Expression > ( commandExpr . Redirections . Count ) ;
3203
+ }
3202
3204
3205
+ // Hold the current 'FileRedirection' instance for later use
3203
3206
var redirectionExpr = NewTemp ( typeof ( FileRedirection ) , "fileRedirection" ) ;
3204
3207
temps . Add ( redirectionExpr ) ;
3205
3208
exprs . Add ( Expression . Assign ( redirectionExpr , ( Expression ) compiledRedirection ) ) ;
3206
- /*
3207
- if (fileRedirectionAst.FromStream != RedirectionStream.Output && !(redirectionExpr is ConstantExpression))
3208
- {
3209
- // We'll be reusing redirectionExpr, it's not constant, so save it in a temp.
3210
- var temp = Expression.Variable(redirectionExpr.Type);
3211
- temps.Add(temp);
3212
- exprs.Add(Expression.Assign(temp, redirectionExpr));
3213
- redirectionExpr = temp;
3214
- }
3215
- */
3209
+
3210
+ // We must save the old streams so they can be restored later.
3211
+ var savedPipes = NewTemp ( typeof ( Pipe [ ] ) , "savedPipes" ) ;
3212
+ temps . Add ( savedPipes ) ;
3216
3213
exprs . Add ( Expression . Assign (
3217
3214
savedPipes ,
3218
3215
Expression . Call ( redirectionExpr , CachedReflectionInfo . FileRedirection_BindForExpression , _functionContext ) ) ) ;
3219
- finallyExprs . Add ( Expression . Call ( redirectionExpr . Cast ( typeof ( CommandRedirection ) ) ,
3220
- CachedReflectionInfo . CommandRedirection_UnbindForExpression ,
3221
- _functionContext ,
3222
- savedPipes ) ) ;
3216
+
3217
+ // We need to call 'DoComplete' on the file redirection pipeline processor after writing the stream output to redirection pipe,
3218
+ // so that the 'EndProcessing' method of 'Out-File' would be called as expected.
3219
+ // Expressions for this purpose are kept in 'extraFileRedirectExprs' and will be used later.
3220
+ extraFileRedirectExprs . Add ( Expression . Call ( redirectionExpr , CachedReflectionInfo . FileRedirection_CallDoCompleteForExpression ) ) ;
3221
+
3222
+ // The 'UnBind' and 'Dispose' operations on 'FileRedirection' objects must be done in the reverse order of 'Bind' operations.
3223
+ // Namely, it should be done is this order:
3224
+ // try {
3225
+ // // The order is A, B
3226
+ // fileRedirectionA = new FileRedirection(..)
3227
+ // fileRedirectionA.BindForExpression()
3228
+ // fileRedirectionB = new FileRedirection(..)
3229
+ // fileRedirectionB.BindForExpression()
3230
+ // ...
3231
+ // } finally {
3232
+ // // The oder must be B, A
3233
+ // fileRedirectionB.UnBind()
3234
+ // fileRedirectionB.Dispose()
3235
+ // fileRedirectionA.UnBind()
3236
+ // fileRedirectionA.Dispose()
3237
+ // }
3238
+ //
3239
+ // Otherwise, the original pipe might not be correctly restored in the end. For example,
3240
+ // '1 *> b.txt > a.txt; 123' would result in the following error when evaluating '123':
3241
+ // "Cannot perform operation because object "PipelineProcessor" has already been disposed"
3242
+ finallyExprs . Insert ( 0 , Expression . Call ( redirectionExpr . Cast ( typeof ( CommandRedirection ) ) ,
3243
+ CachedReflectionInfo . CommandRedirection_UnbindForExpression ,
3244
+ _functionContext ,
3245
+ savedPipes ) ) ;
3223
3246
// In either case, we must dispose of the redirection or file handles won't get released.
3224
- finallyExprs . Add ( Expression . Call ( redirectionExpr ,
3225
- CachedReflectionInfo . FileRedirection_Dispose ) ) ;
3247
+ finallyExprs . Insert ( 1 , Expression . Call ( redirectionExpr ,
3248
+ CachedReflectionInfo . FileRedirection_Dispose ) ) ;
3226
3249
}
3227
3250
3228
3251
Expression result = null ;
@@ -3287,10 +3310,29 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo
3287
3310
}
3288
3311
}
3289
3312
3313
+ if ( extraFileRedirectExprs != null )
3314
+ {
3315
+ // Now that the redirection is done, we need to call 'DoComplete' on the file redirection pipeline processors.
3316
+ // Exception may be thrown when running 'DoComplete', but we don't want the exception to affect the cleanup
3317
+ // work in 'finallyExprs'. We generate the following code to make sure it does what we want.
3318
+ // try {
3319
+ // try {
3320
+ // exprs
3321
+ // } finally {
3322
+ // extraFileRedirectExprs
3323
+ // }
3324
+ // finally {
3325
+ // finallyExprs
3326
+ // }
3327
+ var wrapExpr = Expression . TryFinally ( Expression . Block ( exprs ) , Expression . Block ( extraFileRedirectExprs ) ) ;
3328
+ exprs . Clear ( ) ;
3329
+ exprs . Add ( wrapExpr ) ;
3330
+ }
3331
+
3290
3332
if ( oldPipe != null )
3291
3333
{
3292
3334
// If a temporary pipe was created at the beginning, we should restore the original pipe in the
3293
- // very end of the finally block. Otherwise, _getCurrentPipe may be messed up by the following
3335
+ // very end of the finally block. Otherwise, s_getCurrentPipe may be messed up by the following
3294
3336
// file redirection unbind operation.
3295
3337
// For example:
3296
3338
// function foo
0 commit comments