@@ -181,6 +181,8 @@ internal static class CachedReflectionInfo
181181
182182 internal static readonly MethodInfo FileRedirection_BindForExpression =
183183 typeof ( FileRedirection ) . GetMethod ( nameof ( FileRedirection . BindForExpression ) , instanceFlags ) ;
184+ internal static readonly MethodInfo FileRedirection_CallDoCompleteForExpression =
185+ typeof ( FileRedirection ) . GetMethod ( nameof ( FileRedirection . CallDoCompleteForExpression ) , instanceFlags ) ;
184186 internal static readonly ConstructorInfo FileRedirection_ctor =
185187 typeof ( FileRedirection ) . GetConstructor ( instanceFlags , null , CallingConventions . Standard ,
186188 new Type [ ] { typeof ( RedirectionStream ) , typeof ( bool ) , typeof ( string ) } , null ) ;
@@ -3186,6 +3188,7 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo
31863188 exprs . Add ( Expression . Call ( oldPipe , CachedReflectionInfo . Pipe_SetVariableListForTemporaryPipe , s_getCurrentPipe ) ) ;
31873189 }
31883190
3191+ List < Expression > extraFileRedirectExprs = null ;
31893192 // We must generate the code for output redirection to a file before any merging redirections
31903193 // because merging redirections will use funcContext._outputPipe as the value to merge to, so defer merging
31913194 // redirections until file redirections are done.
@@ -3194,35 +3197,55 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo
31943197 // This will simply return a Linq.Expression representing the redirection.
31953198 var compiledRedirection = VisitFileRedirection ( fileRedirectionAst ) ;
31963199
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+ }
32023204
3205+ // Hold the current 'FileRedirection' instance for later use
32033206 var redirectionExpr = NewTemp ( typeof ( FileRedirection ) , "fileRedirection" ) ;
32043207 temps . Add ( redirectionExpr ) ;
32053208 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 ) ;
32163213 exprs . Add ( Expression . Assign (
32173214 savedPipes ,
32183215 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 ) ) ;
32233246 // 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 ) ) ;
32263249 }
32273250
32283251 Expression result = null ;
@@ -3287,10 +3310,29 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo
32873310 }
32883311 }
32893312
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+
32903332 if ( oldPipe != null )
32913333 {
32923334 // 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
32943336 // file redirection unbind operation.
32953337 // For example:
32963338 // function foo
0 commit comments