none
Invoking PowerShell.exe with "-Command -" (reading input from StdIn) broken in Windows 10 14393? RRS feed

  • Question

  • 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
    <<<<<


    • Edited by _dandy_ Thursday, August 18, 2016 6:00 PM
    Thursday, August 18, 2016 5:48 PM

Answers

  • You have to read the rest of the walk through.  Your script must generate objects to the pipeline.

    You can actually test this code in PowerShell:

    <# test.ps1 Co tains one line
       Get-ChildItem
    #>
    $ps = [powershell]::Create()
    $p=$ps.AddScript('d:\scripts\test.ps1')
    $ps.Invoke()

    Check $p for errors after the invoke.

    Look up all classes and read how they work. If you want to code in C# this is absolutely necessary.

    Here is the command example:

    $ps = [powershell]::Create()
    $ps.AddCommand('Get-Process”'
    $ps.AddParameter('Name', 'PowerShell')
    $ps.Invoke();

    One of the best uses for PowerShell is learning Net classes since we can browse the objects and classes:

    [powershell]|gm

    For detail on net classes just reference the MSDN library.

    start 'http://msdn.microsoft.com/system.management.automation.powershell'

    You can add the full type name of any class and get the docs from a prompt.


    \_(ツ)_/


    • Edited by jrvModerator Friday, August 19, 2016 4:20 PM
    • Proposed as answer by Hello_2018Moderator Monday, August 29, 2016 8:04 AM
    • Marked as answer by _dandy_ Monday, August 29, 2016 2:09 PM
    Friday, August 19, 2016 4:20 PM
    Moderator

All replies

  • Place the - in single quotes.  '-'


    \_(ツ)_/

    Thursday, August 18, 2016 6:28 PM
    Moderator
  • Interesting idea.  But if I do that, the whole script is ignored, and all I get back is, literally, the hyphen character.  That is, the EXE then returns:

    stdout contains the following
    >>>>>
    -
    <<<<<

    Do you have any insight on this idea of surrounding the hyphen with quotes?

    Or perhaps I've completely misunderstood you.  I changed my powershell.exe params to:

    Arguments = " -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command '-'",

    Is this what you meant?



    • Edited by _dandy_ Thursday, August 18, 2016 6:43 PM
    Thursday, August 18, 2016 6:42 PM
  • Windows 10 does not seem to allow the dash anymore even at a prompt.

    \_(ツ)_/

    Thursday, August 18, 2016 6:48 PM
    Moderator
  • The help that comes up by entering "powershell.exe /?" makes no claim that it's been taken out.

    Do you have an alternate method of using "-Command -" that can demonstrate this without getting an EXE involved in the way I do with my sample?

    Thursday, August 18, 2016 6:54 PM
  • I just thought I would add that I can confirm _dandy_'s finding. His program fails on Anniversary Update, but works on other O/S's.

    But I have no idea why. It looks legal to me.

    Mike

    Thursday, August 18, 2016 7:06 PM
  • A lot of things were broken by the anniversary update.  I have been sorting it out for almost two weeks now.


    \_(ツ)_/

    Thursday, August 18, 2016 7:08 PM
    Moderator
  • A lot of things in PowerShell? I haven't noticed that (apart from _dandy_s dandy [sorry, pun]). Overall I have been pretty happy with the Anniversary Update.

    Mike

    Thursday, August 18, 2016 7:17 PM
  • No.  Other things.  I have had to reinstall a number of programs and the lock screen was completely hung.  I still have an issue of recovery from sleep and many HP programs and drivers won't run.  The HP WiDi driver just up and aborted with a cryptic message about the OS not being capable of supporting some object and  that it would be disabled.

    PowerShell has been just fine until this issue.


    \_(ツ)_/

    Thursday, August 18, 2016 7:23 PM
    Moderator
  • Found out why StdOut comes back blank when I substitute CR/LF with spaces, and the answer in hindsight is pretty obvious. Since I have comments ("#") in the code, the first such character encountered effectively comments out the rest of the script once everything is collapsed on to a single line.

    I suppose I *could* further massage the script that RunScript() is being provided--iterate over each line, ignore anything past the first "#" character encountered, if any exists, and reconstruct the script on the fly without the comments, but I suspect this will lead me down a path that would be best avoided...for instance, I could get a script that's using the "#" character as part of a display string - simple string substitution in this case will break things...and then there's the back-tick (`) line-continuation character that would need to be handled. In the end, I'd need an all-out PowerShell parser. To which I say: Nope.

    Plus manipulating the script body is going to invalidate any signed script.  Bottom line - I have to leave the script alone, and provide it untouched to powershell.exe.

    "The usual other options" aren't very appealing to me, as the scripts I'm running may contain protected IP, so files written to disk ("-File") can be intercepted, and base64 strings ("-Base4EncodedCommand") are trivially decoded - not to mention that the full parameters to these options typically end up being sent to the event log under Applications and Services Logs\Windows PowerShell so there's a full trace of them.  This is why I rather insist on using "-Command -" -- I haven't seen any method that can intercept the script when it's sent through StdIn.

    Thursday, August 18, 2016 8:24 PM
  • This is all why we use the SDK and the PowerShell "host' class to run scripts from C# and where we want to finely control the execution and outcome.


    \_(ツ)_/

    Thursday, August 18, 2016 8:27 PM
    Moderator
  • Can't say I had considered that.  I'm definitely going to look into it.  However, this doesn't negate the fact that, as far as an end user would see it, the behavior has been changed and it's now broken.  Unfortunately I can't find much from Microsoft on the subject of updates to PowerShell in Windows 10.
    Thursday, August 18, 2016 8:40 PM
  • Seems to me like there's a lot of overhead to getting .NET's PowerShell classes involved, and would require a significant rewrite of a lot of my code.  Before committing to going down this road, I'd rather try to exhaust all other possible avenues.
    Friday, August 19, 2016 2:38 PM
  • Seems to me like there's a lot of overhead to getting .NET's PowerShell classes involved, and would require a significant rewrite of a lot of my code.  Before committing to going down this road, I'd rather try to exhaust all other possible avenues.

    It takes about five lines to launch a script.

    https://msdn.microsoft.com/en-us/library/dn569260(v=vs.85).aspx

    #simple command
    PowerShell ps = PowerShell.Create();
    ps.AddCommand(“Get-Process”);
    ps.Invoke();
    
    # run a script:
    PowerShell ps = PowerShell.Create();
    ps.AddScript(“D:\PSScripts\MyScript.ps1”).Invoke();
    
    Simple as that.


    \_(ツ)_/

    Friday, August 19, 2016 2:54 PM
    Moderator
  • That certainly looks more promising - thanks.

    2 questions:

    a) The documentation doesn't make it clear whether AddScript() only allows script filenames, to be passed in, or it has to smarts to know how to make the distinction between a filename vs a multi-line script.

    b) Invoke() returns a collection of PSObject, but whether I provided the full script body to AddScript() or a filename containing the same script, I'm not getting any results back (the collection returned by Invoke() contains 0 items). How can I get the value(s) returned by the script? I'm still using the script from my sample at the very start of this thread. Ideally I'd want access to both StdOut and StdErr.

    [Edit]

    Of course I'm not going to ask you to do my homework for me.  It does look like you can pass in a script body, *or* a filename.  As for the output, I initially got some errors in ps.Streams.Error about Write-Host not being available (thus explaining why nothing got added to the list returned by Invoke()).  Once I replaced Write-Host with "echo" in my ShowValue() function, the PSObject collection returned by Invoke() got filled, and I do have the correct value.

    All that, with no error on Windows 10 14393.  So this may be my permanent fix...

    What I'm still not clear on is what version of .NET I can use this with.  Ideally, I should be able to run this on machines that have nothing but .NET 2.  So, the investigation continues...meanwhile, a big thanks for the pointers!

    • Edited by _dandy_ Friday, August 19, 2016 4:06 PM
    Friday, August 19, 2016 3:34 PM
  • You have to read the rest of the walk through.  Your script must generate objects to the pipeline.

    You can actually test this code in PowerShell:

    <# test.ps1 Co tains one line
       Get-ChildItem
    #>
    $ps = [powershell]::Create()
    $p=$ps.AddScript('d:\scripts\test.ps1')
    $ps.Invoke()

    Check $p for errors after the invoke.

    Look up all classes and read how they work. If you want to code in C# this is absolutely necessary.

    Here is the command example:

    $ps = [powershell]::Create()
    $ps.AddCommand('Get-Process”'
    $ps.AddParameter('Name', 'PowerShell')
    $ps.Invoke();

    One of the best uses for PowerShell is learning Net classes since we can browse the objects and classes:

    [powershell]|gm

    For detail on net classes just reference the MSDN library.

    start 'http://msdn.microsoft.com/system.management.automation.powershell'

    You can add the full type name of any class and get the docs from a prompt.


    \_(ツ)_/


    • Edited by jrvModerator Friday, August 19, 2016 4:20 PM
    • Proposed as answer by Hello_2018Moderator Monday, August 29, 2016 8:04 AM
    • Marked as answer by _dandy_ Monday, August 29, 2016 2:09 PM
    Friday, August 19, 2016 4:20 PM
    Moderator
  • Just got emailed the "proposed as answer" notification.  While I agree this is a suitable workaround for me in this case, I'd still like to bring this to Microsoft's attention through User Voice or whatever other feedback mechanisms exist, as this is functionality that is now demonstrably broken, and I saw no announcement that any of this was going to be deprecated.

    To Andy (who marked this as the answer)--or anyone else, really:  What's the best course of action here to pursue this?

    Monday, August 29, 2016 1:16 PM
  • You can file a bug report here:

    https://windowsserver.uservoice.com/forums/301869-powershell

    As an aside - Andy didn't mark an answer here, he just proposed one. You can mark jrv's post as an answer if you so choose. If you don't want it to be marked as the answer, you can unpropose it.


    Monday, August 29, 2016 1:55 PM
    Moderator
  • Thanks - I had come across that page, but wasn't entirely sure whether this was specifically for PowerShell running on Server, as the page title might imply.  I'll follow up there.
    Monday, August 29, 2016 2:09 PM
  • Something similar has been noted as an issue on the PowerShell GitHub repository - https://github.com/PowerShell/PowerShell/issues/2071

    Rob



    Tuesday, August 30, 2016 5:13 PM
  • Something similar has been noted as an issue on the PowerShell GitHub repository - https://github.com/PowerShell/PowerShell/issues/2071

    Rob

    Thanks, useful to know.

    Tuesday, August 30, 2016 5:17 PM
    Moderator
  • The PR that fixes the issue has been merged at https://github.com/PowerShell/PowerShell/pull/2090. Please vote up https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/15854689-powershell-exe-command-is-broken-on-windows-10 and suggest that this fix get backported to address the PowerShell 5.1 Anniversary edition.

    Rob


    Wednesday, August 31, 2016 9:21 PM
  • Has anyone verified the change on Github fixes this specific issue detailed above?
    Monday, September 12, 2016 11:30 PM