Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle serialized CLIXML OutputFormat #106

Closed
maxlinc opened this issue Dec 15, 2014 · 4 comments · Fixed by #191
Closed

Handle serialized CLIXML OutputFormat #106

maxlinc opened this issue Dec 15, 2014 · 4 comments · Fixed by #191

Comments

@maxlinc
Copy link
Contributor

maxlinc commented Dec 15, 2014

One of the PowerShell options is:

-OutputFormat {Text | XML}
Determines how output from Windows PowerShell is formatted. Valid values are "Text" (text strings) or "XML" (serialized CLIXML format).

This gem doesn't parse the latter, so it seems like it would be good to always capture output as text. Unfortunately, this option only seems to control the stdout stream. The stderr stream is always written as CLIXML, as seen in this pry session. Changing the option from Text to XML effects stdout but not stderr:

[1] pry(#<WinRM::WinRMWebService>)> run_cmd("powershell -outputFormat Text -encodedCommand #{script}", &block)
=> {:data=>
  [{:stdout=>"Hello"},
   {:stdout=>"\n"},
   {:stderr=>"#< CLIXML\r\n"},
   {:stderr=>"<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas.microsoft.com/powershell/2004/04\"><S S=\"Error\">, world!_x000D__x000A_</S></Objs>"}],
 :exitcode=>0}
[2] pry(#<WinRM::WinRMWebService>)> run_cmd("powershell -outputFormat XML -encodedCommand #{script}", &block)
=> {:data=>
  [{:stdout=>"#< CLIXML\r\n"},
   {:stderr=>"#< CLIXML\r\n"},
   {:stdout=>"<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas.microsoft.com/powershell/2004/04\"><S S=\"Output\">Hello</S><S S=\"Output\">_x000A_</S></Objs>"},
   {:stderr=>"<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas.microsoft.com/powershell/2004/04\"><S S=\"Error\">, world!_x000D__x000A_</S></Objs>"}],
 :exitcode=>0}

Actually, even though it is always XML it does look like using Text at least limits the stderr to XML only containing serialized strings, as opposed to more complex types when using XML:

[3] pry(#<WinRM::WinRMWebService>)> run_cmd("powershell -outputFormat Text -encodedCommand #{script}", &block)
=> {:data=>
  [{:stdout=>"Hello"},
   {:stdout=>"\n"},
   {:stderr=>"#< CLIXML\r\n"},
   {:stderr=>
     "<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas.microsoft.com/powershell/2004/04\"><S S=\"Error\">      Write-Host 'Hello'_x000D__x000A_</S><S S=\"Error\">      Write-Error 'foo'_x000D__x000A_</S><S S=\"Error\">      $host.ui.WriteErrorLine(', world!')_x000D__x000A"},
   {:stderr=>
     "_</S><S S=\"Error\"> : foo_x000D__x000A_</S><S S=\"Error\">    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorExcep _x000D__x000A_</S><S S=\"Error\">   tion_x000D__x000A_</S><S S=\"Error\">    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorExceptio _x000D__x000A_</S><S S=\"Error\">   n_x000D__x000A_</S><S S=\"Error\"> _x000D__x000A_</S><S S=\"Error\">, world!_x000D__x000A_</S></Objs>"}],
 :exitcode=>0}
[4] pry(#<WinRM::WinRMWebService>)> run_cmd("powershell -outputFormat XML -encodedCommand #{script}", &block)
=> {:data=>
  [{:stdout=>"#< CLIXML\r\n"},
   {:stderr=>"#< CLIXML\r\n"},
   {:stdout=>"<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas.microsoft.com/powershell/2004/04\"><S S=\"Output\">Hello</S><S S=\"Output\">_x000A_</S></Objs>"},
   {:stderr=>
     "<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas.microsoft.com/powershell/2004/04\"><Obj S=\"Error\" RefId=\"0\"><TN RefId=\"0\"><T>System.Management.Automation.ErrorRecord</T><T>System.Object</T></TN><ToString>foo</ToString><MS><B N=\"writeErrorStream\">true</B><Obj "},
   {:stderr=>
     "N=\"Exception\" RefId=\"1\"><TN RefId=\"1\"><T>Microsoft.PowerShell.Commands.WriteErrorException</T><T>System.SystemException</T><T>System.Exception</T><T>System.Object</T></TN><ToString>Microsoft.PowerShell.Commands.WriteErrorException: foo</ToString><Props><S N=\"Message\">foo</S><Obj N=\"Data\" RefId=\"2\"><TN RefId=\"2\"><T>System.Collections.ListDictionaryInternal</T><T>System.Object</T></TN><DCT /></Obj><Nil N=\"InnerException\" /><Nil N=\"TargetSite\" /><Nil N=\"StackTrace\" /><Nil N=\"HelpLink\" /><Nil N=\"Source\" /><I32 N=\"HResult\">-2146233087</I32></Props></Obj><Nil N=\"TargetObject\" /><S N=\"FullyQualifiedErrorId\">Microsoft.PowerShell.Commands.WriteErrorException</S><Obj N=\"InvocationInfo\" RefId=\"3\"><TN RefId=\"3\"><T>System.Management.Automation.InvocationInfo</T><T>System.Object</T></TN><ToString>System.Management.Automation.InvocationInfo</ToString><Props><Obj N=\"MyCommand\" RefId=\"4\"><TN RefId=\"4\"><T>System.Management.Automation.ScriptInfo</T><T>System.Management.Automation.CommandInfo</T><T>System.Object</T></TN><ToString>      Write-Host 'Hello'_x000A_      Write-Error 'foo'_x000A_      $host.ui.WriteErrorLine(', world!')_x000A_</ToString><Props><SBK N=\"ScriptBlock\">      Write-Host 'Hello'_x000A_      Write-Error 'foo'_x000A_      $host.ui.WriteErrorLine(', world!')_x000A_</SBK><S N=\"Definition\">      Write-Host 'Hello'_x000A_      Write-Error 'foo'_x000A_      $host.ui.WriteErrorLine(', world!')_x000A_</S><Obj N=\"OutputType\" RefId=\"5\"><TN RefId=\"5\"><T>System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Management.Automation.PSTypeName, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>System.Object</T></TN><LST /></Obj><S N=\"Name\"></S><S N=\"CommandType\">Script</S><S N=\"Visibility\">Public</S><S N=\"ModuleName\"></S><Nil N=\"Module\" /><S N=\"RemotingCapability\">PowerShell</S><Obj N=\"ParameterSets\" RefId=\"6\"><TN RefId=\"6\"><T>System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Management.Automation.CommandParameterSetInfo, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>System.Object</T></TN><LST><S></S></LST></Obj></Props><MS><S N=\"Namespace\"></S><S N=\"HelpUri\"></S></MS></Obj><Obj N=\"BoundParameters\" RefId=\"7\"><TN RefId=\"7\"><T>System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]</T><T>System.Object</T></TN><DCT /></Obj><Obj N=\"UnboundArguments\" RefId=\"8\"><TN RefId=\"8\"><T>System.Collections.Generic.List`1[[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]</T><T>System.Object</T></TN><LST /></Obj><I32 N=\"ScriptLineNumber\">0</I32><I32 N=\"OffsetInLine\">0</I32><I64 N=\"HistoryId\">1</I64><S N=\"ScriptName\"></S><S N=\"Line\"></S><S N=\"PositionMessage\"></S><S N=\"PSScriptRoot\"></S><Nil N=\"PSCommandPath\" /><S N=\"InvocationName\"></S><I32 N=\"PipelineLength\">0</I32><I32 N=\"PipelinePosition\">0</I32><B N=\"ExpectingInput\">false</B><Obj N=\"CommandOrigin\" RefId=\"9\"><TN RefId=\"9\"><T>System.Management.Automation.CommandOrigin</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>Internal</ToString><I32>1</I32></Obj><Nil N=\"DisplayScriptPosition\" /></Props></Obj><I32 N=\"ErrorCategory_Category\">0</I32><S N=\"ErrorCategory_Activity\">Write-Error</S><S N=\"ErrorCategory_Reason\">WriteErrorException</S><S N=\"ErrorCategory_TargetName\"></S><S N=\"ErrorCategory_TargetType\"></S><S N=\"ErrorCategory_Message\">NotSpecified: (:) [Write-Error], WriteErrorException</S><B N=\"SerializeExtendedInfo\">false</B><Nil N=\"PSMessageDetails\" /></MS></Obj><S S=\"Error\">, world!_x000D__x000A_</S></Objs>"}],
 :exitcode=>0}
@maxlinc
Copy link
Contributor Author

maxlinc commented Dec 16, 2014

FYI - figured out at least very basic parsing, that will get all the serialized String objects from the XML:

    def stderr_text
      doc = REXML::Document.new(stderr)
      text = doc.root.get_elements('//S').map(&:text).join
      text.gsub(/_x(\h\h\h\h)_/) do
        code = Regexp.last_match[1]
        code.hex.chr
      end
    end

Dealing with the encoded strings was a bit unpleasant, but the solution above seems to work.

@sneal
Copy link
Member

sneal commented Dec 16, 2014

Wow, that's great! That will definitely make errors much easier on the eyes to parse.

@chris-rock
Copy link
Contributor

Would love to see this integrated and done in WinRM

@mwrock
Copy link
Member

mwrock commented Mar 19, 2016

Its currently in our v2 branch. Yeah it looks WAY better: https://github.com/WinRb/WinRM/blob/winrm-v2/lib/winrm/wsmv/powershell_output_decoder.rb

@sneal sneal mentioned this issue Jul 7, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants