{"id":5531,"date":"2008-09-03T23:24:00","date_gmt":"2008-09-03T23:24:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/powershell\/2008\/09\/03\/text-output-is-not-a-contract\/"},"modified":"2019-02-18T13:13:02","modified_gmt":"2019-02-18T20:13:02","slug":"text-output-is-not-a-contract","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/powershell\/text-output-is-not-a-contract\/","title":{"rendered":"Text Output Is Not a Contract"},"content":{"rendered":"<p>In PS V1, this is what you get for GET-COMMAND -SYNTAX<\/p>\n<p>PS&gt; Get-Command Get-Process -Syntax<br \/>Get-Process [[-Name] &lt;String[]&gt;] [-ComputerName &lt;String[]&gt;] [-Module] [-FileVersionInfo] [-Verbose] [-Debug] [-ErrorAction &lt;ActionPreference&gt;] [-WarningAction &lt;ActionPreference&gt;] [-ErrorVariable &lt;String&gt;] [-WarningVariable &lt;String&gt;] [-OutVariable &lt;String&gt;] [-OutBuffer &lt;Int32&gt;]<br \/>Get-Process -Id &lt;Int32[]&gt; [-ComputerName &lt;String[]&gt;] [-Module] [-FileVersionInfo] [-Verbose] [-Debug] [-ErrorAction &lt;ActionPreference&gt;] [-WarningAction &lt;ActionPreference&gt;] [-ErrorVariable &lt;String&gt;] [-WarningVariable &lt;String&gt;]&nbsp;[-OutVariable &lt;String&gt;] [-OutBuffer &lt;Int32&gt;]<br \/>Get-Process [-ComputerName &lt;String[]&gt;] [-Module] [-FileVersionInfo] -InputObject &lt;Process[]&gt; [-Verbose] [-Debug] [-ErrorAction &lt;ActionPreference&gt;] [-WarningAction &lt;ActionPreference&gt;] [-ErrorVariable &lt;String&gt;] [-WarningVariable &lt;String&gt;] [-OutVariable &lt;String&gt;] [-OutBuffer &lt;Int32&gt;]<\/p>\n<p>I look at that and a little bit of vomit comes up into my mouth.&nbsp; I&#8217;ve been&nbsp;experimenting with my own radically different version but that probably won&#8217;t ship in V2 and we&#8217;d like V2 to be better.&nbsp; I mentioned that if we just put a newline between the parametersets and got rid of the ubiquitous parameters &#8211; we would be TONS ahead.&nbsp; This is what it would look like:<\/p>\n<p>PS&gt; Get-Command Get-Process -Syntax<br \/>Get-Process [[-Name] &lt;String[]&gt;] [-ComputerName &lt;String[]&gt;] [-Module] [-FileVersionInfo] <\/p>\n<p>Get-Process -Id &lt;Int32[]&gt; [-ComputerName &lt;String[]&gt;] [-Module] [-FileVersionInfo] <\/p>\n<p>Get-Process [-ComputerName &lt;String[]&gt;] [-Module] [-FileVersionInfo] -InputObject &lt;Process[]&gt; <\/p>\n<p>MUCH better right?&nbsp; As we discussed this, someone pointed out that this change would break any scripts that parsed the text output for the V1 version.&nbsp;&nbsp;At first I looked at the person as though they had a rat&#8217;s tail hanging out of their mouth (thinking &#8211; Get-Command gives you a structured object &#8211; why would you render&nbsp;it to text and then parse it?!!!).&nbsp; Then I realized that we hadn&#8217;t actually stated what I think is obvious so now I&#8217;ll state it:<\/p>\n<p><strong>In PowerShell &#8211; TEXT OUTPUT IS NOT A CONTRACT.<\/strong><\/p>\n<p>In UNIX, text output is effectively a programming contract because that is what the whole system is built upon.&nbsp; One command outputs text and other programs know what to expect so they parse the text to get the appropriate data elements so that they can code against it.&nbsp; In this model, if you change the text output of a command &#8211; you run the risk of breaking a bunch of scripts.&nbsp; In reality, this happens.&nbsp; It doesn&#8217;t happen often because the commands don&#8217;t change that much.&nbsp; The other reality is that admins cope.&nbsp; That&#8217;s what admins do &#8211; they deal with the world as it is not as it should be.&nbsp; &#8220;Unix commands shouldn&#8217;t change their&nbsp;outout because they break my scripts, they do, my scripts are broken, grrrrrrr &#8211; cope.&#8221;&nbsp; It&#8217;s a dirty business but someone has to do it.&nbsp; \ud83d\ude42<\/p>\n<p>That is the world of TEXT parsing.&nbsp; In PowerShell &#8211; we provide an object-based world where you don&#8217;t have to parse the text to get the properties that you want &#8211; you just ask for property and you get it &#8211; no fuss, no muss.&nbsp; Given that &#8211; in Powershell TEXT OUTPUT IS NOT A CONTRACT.&nbsp; Said aother way &#8211; <br \/><strong>We reserve the right to radically change our text rendering to improve our customer experience.<\/strong>&nbsp; <br \/>(God knows we have some improvements to make. \ud83d\ude42 )<\/p>\n<p>Clever people will point out that we can change our text output and not break scripts as long as they are PowerShell scripts and not say a PERL script that invokes PowerShell and parses its output.&nbsp; That is exactly correct and exactly why I&#8217;m blogging this now so that everyone is put on notice and can code accordingly.&nbsp; <\/p>\n<p>I imagine that some people will howl about us doing our Perl buddies wrong but&nbsp;the reality is that this is not an issue as long as we are clear about the rules and people can adjust.&nbsp; In reality, we&nbsp;probably don&#8217;t even need to do that.&nbsp;&nbsp;<\/p>\n<p>1) If you go the National Bureau of Standards in Denver and open up&nbsp;the closet marked &#8220;COPER&#8221;, you&#8217;ll find a System&nbsp;Admin clutching a PERL manual.&nbsp; (I&#8217;m serious, these guys do an awesome job given the world we&#8217;ve created for them.)<br \/>2) These guys are going to invoke PowerShell in a way that it outputs text that is easy to parse.&nbsp; They are going to render it to CSVs or XML or some other useful format which is SUPER easy to do in PowerShell and avoids a bunch of horrible parsing.<\/p>\n<p>As I say, it is pretty obvious when you think about it for a couple minutes but I felt it was important for us to be explicit about it so there is no confusion downstream (well &#8230; less confusion).<\/p>\n<p>Cheers.<\/p>\n<p>Jeffrey Snover [MSFT]<br \/>Windows Management Partner Architect<br \/>Visit the Windows PowerShell Team blog at:&nbsp;&nbsp;&nbsp; <a href=\"http:\/\/blogs.msdn.com\/PowerShell\">http:\/\/blogs.msdn.com\/PowerShell<\/a><br \/>Visit the Windows PowerShell ScriptCenter at:&nbsp; <a href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/hubs\/msh.mspx\">http:\/\/www.microsoft.com\/technet\/scriptcenter\/hubs\/msh.mspx<\/a><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In PS V1, this is what you get for GET-COMMAND -SYNTAX PS&gt; Get-Command Get-Process -SyntaxGet-Process [[-Name] &lt;String[]&gt;] [-ComputerName &lt;String[]&gt;] [-Module] [-FileVersionInfo] [-Verbose] [-Debug] [-ErrorAction &lt;ActionPreference&gt;] [-WarningAction &lt;ActionPreference&gt;] [-ErrorVariable &lt;String&gt;] [-WarningVariable &lt;String&gt;] [-OutVariable &lt;String&gt;] [-OutBuffer &lt;Int32&gt;]Get-Process -Id &lt;Int32[]&gt; [-ComputerName &lt;String[]&gt;] [-Module] [-FileVersionInfo] [-Verbose] [-Debug] [-ErrorAction &lt;ActionPreference&gt;] [-WarningAction &lt;ActionPreference&gt;] [-ErrorVariable &lt;String&gt;] [-WarningVariable &lt;String&gt;]&nbsp;[-OutVariable &lt;String&gt;] [-OutBuffer &lt;Int32&gt;]Get-Process [-ComputerName [&hellip;]<\/p>\n","protected":false},"author":600,"featured_media":13641,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-5531","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-powershell"],"acf":[],"blog_post_summary":"<p>In PS V1, this is what you get for GET-COMMAND -SYNTAX PS&gt; Get-Command Get-Process -SyntaxGet-Process [[-Name] &lt;String[]&gt;] [-ComputerName &lt;String[]&gt;] [-Module] [-FileVersionInfo] [-Verbose] [-Debug] [-ErrorAction &lt;ActionPreference&gt;] [-WarningAction &lt;ActionPreference&gt;] [-ErrorVariable &lt;String&gt;] [-WarningVariable &lt;String&gt;] [-OutVariable &lt;String&gt;] [-OutBuffer &lt;Int32&gt;]Get-Process -Id &lt;Int32[]&gt; [-ComputerName &lt;String[]&gt;] [-Module] [-FileVersionInfo] [-Verbose] [-Debug] [-ErrorAction &lt;ActionPreference&gt;] [-WarningAction &lt;ActionPreference&gt;] [-ErrorVariable &lt;String&gt;] [-WarningVariable &lt;String&gt;]&nbsp;[-OutVariable &lt;String&gt;] [-OutBuffer &lt;Int32&gt;]Get-Process [-ComputerName [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts\/5531","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/users\/600"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/comments?post=5531"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts\/5531\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/media\/13641"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/media?parent=5531"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/categories?post=5531"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/tags?post=5531"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}