I have a C# app that contains an embedded PowerShell script. Not that this matters, it could be external--the important point is that I execute the script by invoking powershell.exe with "-Command -". That is, using the -Command
parameter followed by a hyphen, which allows you to then provide the body of the script to execute through StdIn.
I like this technique, and I've been using it for over a year as, among other reasons, the script doesn't have to be a single command, it can span many lines, declare functions etc, all the while without ever having the script written to disk in plain
text or base64-encoded.
After having installed the Anniversary Update of Windows 10 (1607 aka build 14393), this appears to be broken; the captured StdErr shows it complains about missing function bodies, while StdOut shows--and I find this part rather mind-blowing--the script
got mangled in such a way that *all* lines that contain a function declaration, and the following opening *and* closing curly braces, are gone. The rest of the script appears intact. Obviously that would cascade into all sorts of errors.
I managed to write a short(-ish) sample that demonstrates this problem. The same EXE demonstrates that it's broken on Windows 10 14393 (all 3 machines I have that run this version exhibit the same behavior), but it works perfectly fine on
Windows 10 10586 as well as Windows 7.
One thing I've discovered is that if I substitute the CR/LF characters from the script with a single space, the error goes away, but what happens then is that the StdOut stream I capture comes back empty--even on other OSes that otherwise previously
managed to handle the unmodified script just fine--rather than containing the expected output, which comes from one of the script's functions (which simply does a Write-Host).
Another thing I've discovered is that if I add "-version 2.0" as an argument when invoking powershell.exe, everything reverts back to normal behavior. While this works around the immediate problem, obviously I'd like to remain version-agnostic,
so even though I tend to write my scripts so they can run against the "lowest common denominator", I'd rather not explicitly restrict myself in this fashion, as I may eventually need to write a script that relies on some features that have only
been introduced in more recent versions. So I don't view this as a long-term or permanent solution.
If anyone with a compiler cares to try this out, here's my sample cut-down C# program. It just needs to be built as a console EXE.
Is anyone else getting the same results? Any workaround that doesn't require me to change the script itself (as I won't necessarily have any knowledge of what the scripts provided to me actually do, other than the fact that they're all expected to
end with a single Write-Host as the final output value I'm interested in)...
using System;
using System.Text;
using System.Diagnostics;
namespace PsTest
{
class Program
{
static void Main( string[] args )
{
string strMyScript = @"
function GetFunctionValue()
{
# This comment is in the GetFunctionValue function
return $true
}
function ShowValue( [string] $theValue )
{
# This comment is in the ShowValue function
Write-Host $theValue
}
# This comment is at the script level, before the function call
ShowValue( GetFunctionValue )
# This comment is at the script level, after the function call
";
//strMyScript = strMyScript.Replace( "\n", " " ).Replace( "\r", " " );
//strMyScript = strMyScript.Replace( Environment.NewLine, " " );
RunScript( strMyScript );
}
private static string RunScript( string strScriptBody )
{
var stdOutBuilder = new StringBuilder();
var stdErrBuilder = new StringBuilder();
int nExitCode = 0;
using ( var p = new Process() )
{
p.StartInfo = new ProcessStartInfo( "powershell.exe" )
{
Arguments = " -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command -",
// The following line makes the problem go away, even on Win10 14393, unlike the line above
//Arguments = " -version 2.0 -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command -",
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
Verb = "runas" // Elevate
};
p.OutputDataReceived += ( sender, e ) =>
{
if ( !String.IsNullOrEmpty( e.Data ) )
stdOutBuilder.AppendLine( e.Data );
};
p.ErrorDataReceived += ( sender, e ) =>
{
if ( !String.IsNullOrEmpty( e.Data ) )
stdErrBuilder.AppendLine( e.Data );
};
try
{
p.Start();
}
catch ( System.ComponentModel.Win32Exception x )
{
Console.WriteLine( "Process.Start() threw a Win32Exception: " + x.Message + Environment.NewLine + x.StackTrace );
return String.Empty;
}
catch ( Exception x )
{
Console.WriteLine( "Process.Start() threw an exception: " + x.Message + Environment.NewLine + x.StackTrace );
return String.Empty;
}
p.BeginOutputReadLine();
p.BeginErrorReadLine();
var sw = p.StandardInput;
sw.WriteLine( strScriptBody );
sw.Close();
p.WaitForExit();
nExitCode = p.ExitCode;
p.Close();
}
string strError = stdErrBuilder.ToString().TrimEnd();
if ( !String.IsNullOrEmpty( strError ) )
{
Console.WriteLine( $"stderr contains the following" + Environment.NewLine + ">>>>>" + Environment.NewLine + strError + Environment.NewLine + "<<<<<" );
Console.WriteLine( Environment.NewLine + Environment.NewLine + Environment.NewLine + Environment.NewLine );
}
string strOutput = stdOutBuilder.ToString().TrimEnd();
Console.WriteLine( "stdout contains the following" + Environment.NewLine + ">>>>>" + Environment.NewLine + strOutput + Environment.NewLine + "<<<<<" );
return String.Empty;
} // RunScript()
} // class
}
Here's the expected output on a working machine:
stdout contains the following>>>>>
True<<<<<
...and here's the output on 14393. Notice the very messed up script body in stdout, at the very end:
stderr contains the following>>>>>
At line:1 char:28+ function GetFunctionValue()+ ~
Missing function body in function declaration.+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordExce
ption+ FullyQualifiedErrorId : MissingFunctionBody
At line:1 char:41
+ function ShowValue( [string] $theValue )+ ~
Missing function body in function declaration.+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordExce
ption+ FullyQualifiedErrorId : MissingFunctionBody
GetFunctionValue : The term 'GetFunctionValue' is not recognized as the name of
a cmdlet, function, script file, or
operable program. Check the spelling of the name, or if a path was included, ver
ify that the path is correct and try
again.
At line:1 char:12
+ ShowValue( GetFunctionValue )+ ~~~~~~~~~~~~~~~~+ CategoryInfo : ObjectNotFound: (GetFunctionValue:String) [], Comm
andNotFoundException+ FullyQualifiedErrorId : CommandNotFoundException<<<<<
stdout contains the following
>>>>>
# This comment is in the GetFunctionValue function
return $true
# This comment is in the ShowValue function
Write-Host $theValue<<<<<