{"id":10607,"date":"2015-12-17T06:00:00","date_gmt":"2015-12-17T14:00:00","guid":{"rendered":"https:\/\/www.sapien.com\/blog\/?p=10607"},"modified":"2015-12-21T16:39:33","modified_gmt":"2015-12-22T00:39:33","slug":"output-from-a-script-in-an-executable-file","status":"publish","type":"post","link":"https:\/\/dev.sapien.com\/blog\/2015\/12\/17\/output-from-a-script-in-an-executable-file\/","title":{"rendered":"Output from a Script in an Executable File"},"content":{"rendered":"<p>This is the fourth blog in a multi-part series about designing a Windows PowerShell script that will be packaged in an executable file.<\/p>\n<ul>\n<li><a href=\"https:\/\/www.sapien.com\/blog\/2015\/11\/30\/passing-parameters-to-a-script-in-an-executable-file\/\">Passing Parameters to a Script in an Executable File<\/a> explains how to use the special parsing features of PowerShell Studio and PrimalScript to make passing parameters easy for PowerShell users and authors.<\/li>\n<li><a href=\"https:\/\/wp.me\/p3tXTf-2HV\">Parsing Parameters for a Script in an Executable File<\/a> explains how to parse parameters manually for special uses.<\/li>\n<li><a href=\"https:\/\/wp.me\/p3tXTf-2Hg\">Displaying Help for a Script in an Executable File<\/a> explains how to display help for a script in an executable file.<\/li>\n<li><b>Output from a Script in an Executable File<\/b> explains how to manage string output from a script in an executable file.<\/li>\n<\/ul>\n<p>When you write scripts in Windows PowerShell, you really don&#8217;t think much about the format of the output. Your script generates objects and they appear in the console. You learn quickly that you should never use any formatting commands on your output, because the format cmdlets return format objects, instead of the original objects. Also, the user is the best judge of the format they prefer and can easily format any objects your return.<\/p>\n<p>But, all of those rules change when you package a script in an executable file. That&#8217;s because scripts in executable files always return strings &#8212; and only strings.<\/p>\n<h1>General Rule: Don&#8217;t format output objects<\/h1>\n<p>Let&#8217;s begin with the general rule. When a script generates output, don&#8217;t format it. Let the user do it. Otherwise, you return format objects that don\u2019t have the properties and methods of the original objects.<\/p>\n<p>For example, the Get-MyProcess.ps1 script returns the Process object (System.Diagnostics.Process) for the PowerShell Studio process.<\/p>\n<pre lang=\"PowerShell\">Param\r\n(\r\n    [Parameter()]\r\n    [String]\r\n    [ValidateNotNullOrEmpty]\r\n    $List\r\n)\r\n\r\nif ($p = Get-Process 'PowerShell Studio')\r\n{\r\n    if ($List) \r\n    { \r\n        $p | Format-List -Property * \r\n    }\r\n    else {$p}\r\n}<\/pre>\n<pre class=\"output\">PS C:\\&gt; .\\Get-MyProcess.ps1 | Get-Member\r\n\r\n   TypeName: System.Diagnostics.Process\r\n\r\nName                       MemberType     Definition\r\n----                       ----------     ----------\r\nHandles                    AliasProperty  Handles = Handlecount\r\nName                       AliasProperty  Name = ProcessName\r\nNPM                        AliasProperty  NPM = NonpagedSystemMemorySize64\r\nPM                         AliasProperty  PM = PagedMemorySize64\r\nSI                         AliasProperty  SI = SessionId\r\n...<\/pre>\n<p>But, if I use the <b>List<\/b> parameter, which tells the script to run the <a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=293961\" target=\"_blank\">Format-List<\/a> cmdlet, the object is a collection of Format objects, not a Process object.<\/p>\n<pre class=\"output\">PS C:\\&gt; .\\Get-MyProcess.ps1 -List True | Get-Member\r\n\r\n   TypeName: Microsoft.PowerShell.Commands.Internal.Format.FormatStartData\r\n\r\nName                                    MemberType Definition\r\n----                                    ---------- ----------\r\nEquals                                  Method     bool Equals(System.Object obj\r\nGetHashCode                             Method     int GetHashCode()\r\nGetType                                 Method     type GetType()\r\nToString                                Method     string ToString()\r\nautosizeInfo                            Property   Microsoft.PowerShell.Commands\r\nClassId2e4f51ef21dd47e99d3c952918aff9cd Property   string ClassId2e4f51ef21dd47e\r\ngroupingEntry                           Property   Microsoft.PowerShell.Commands\r\npageFooterEntry                         Property   Microsoft.PowerShell.Commands\r\npageHeaderEntry                         Property   Microsoft.PowerShell.Commands\r\nshapeInfo                               Property   Microsoft.PowerShell.Commands\r\n\r\n\r\n   TypeName: Microsoft.PowerShell.Commands.Internal.Format.GroupStartData\r\n\r\nName                                    MemberType Definition\r\n----                                    ---------- ----------\r\nEquals                                  Method     bool Equals(System.Object obj\r\nGetHashCode                             Method     int GetHashCode()\r\nGetType                                 Method     type GetType()\r\nToString                                Method     string ToString()\r\nClassId2e4f51ef21dd47e99d3c952918aff9cd Property   string ClassId2e4f51ef21dd47e\r\ngroupingEntry                           Property   Microsoft.PowerShell.Commands\r\nshapeInfo                               Property   Microsoft.PowerShell.Commands...<\/pre>\n<p>&nbsp;<\/p>\n<p>So, you never return formatted objects. Well, almost never.<\/p>\n<h1>Executable Files Return Strings &#8212; Always<\/h1>\n<p>Now, the exception to the do-not-format rule. When you package your script in an executable file, it always returns strings objects, regardless of the underlying object type, because it writes to stdout &#8212; standard output &#8212; which is always text.<\/p>\n<p>The executable runtime uses the <a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=293999\">Out-String<\/a> cmdlet, which does a pretty good job of preserving the appearance of the output, but the output loses the properties and methods of the original objects.<\/p>\n<pre class=\"output\">PS C:\\&gt; .\\Get-MyProcess.exe #Looks the same...\r\n\r\nHandles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id  SI ProcessName\r\n-------  ------    -----      ----- -----   ------     --  -- -----------\r\n   2896     178   266168     315796  1170   926.55   4428   1 PowerShell Studio\r\n\r\nPS C:\\&gt; .\\Get-MyProcess.exe | Get-Member #Just a good-looking string.\r\n\r\n   TypeName: System.String\r\n\r\nName             MemberType            Definition\r\n----             ----------            ----------\r\nClone            Method                System.Object Clone(),\r\nCompareTo        Method                int CompareTo(System.O\r\nContains         Method                bool Contains(string v\r\nCopyTo           Method                void CopyTo(int source\r\nEndsWith         Method                bool EndsWith(string v\r\nEquals           Method                bool Equals(System.Obj\r\nGetEnumerator    Method                System.CharEnumerator\r\n\r\n<\/pre>\n<p>In fact, in this case, it&#8217;s six good-looking strings, including blank lines.<\/p>\n<pre class=\"output\">PS C:\\&gt; (.\\Get-MyProcess.exe).count\r\n6\r\n\r\nPS C:\\&gt; (.\\Get-MyProcess.exe)[0]\r\n\r\nPS C:\\&gt; (.\\Get-MyProcess.exe)[1]\r\nHandles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id  SI ProcessName\r\n<\/pre>\n<p>If you run the Get-MyProcess.ps1 script, you can get the property values from the object, or call its methods, but the string does not have the Process object properties or methods.<\/p>\n<pre class=\"output\">PS C:\\&gt; (.\\Get-MyProcess.<b>ps1<\/b>).PeakWorkingSet\r\n359100416\r\n\r\nPS C:\\&gt; (.\\Get-MyProcess.<b>exe<\/b>).PeakWorkingSet\r\nPS C:\\&gt;<\/pre>\n<p>And the PowerShell options to format the properties and values into tables and lists have no apparent effect on strings.<\/p>\n<pre class=\"output\">PS C:\\&gt; .\\Get-MyProcess.exe | Format-Table\r\n\r\nHandles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id  SI ProcessName\r\n-------  ------    -----      ----- -----   ------     --  -- -----------\r\n   3298     178   266168     315796  1170   926.61   4428   1 PowerShell Studio\r\n\r\nPS C:\\&gt; .\\Get-MyProcess.exe | Format-List -Property *\r\n\r\nHandles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id  SI ProcessName\r\n-------  ------    -----      ----- -----   ------     --  -- -----------\r\n   3298     178   266168     315796  1170   926.61   4428   1 PowerShell Studio\r\n\r\nPS C:\\&gt; .\\Get-MyProcess.exe | Format-List -Property * -Force\r\nLength : 0\r\nLength : 156\r\nLength : 156\r\nLength : 156\r\nLength : 0\r\nLength : 0<\/pre>\n<h1>New Exe Rule: Add a Format Option<\/h1>\n<p>When you&#8217;re writing scripts that are designed to be packaged in executable files, you might consider adding formatting options.<\/p>\n<p>For example, the little Get-MyProcess.ps1 script (and its Get-MyProcess.exe variant) has a <strong>List<\/strong> parameter that runs the <a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=293961\" target=\"_blank\">Format-List<\/a> cmdlet and returns all properties and values of the object in a list.<\/p>\n<p>The output of the executable file is still a string &#8212; it&#8217;s always a string &#8212; but now the user can see the properties and values.<\/p>\n<pre class=\"output\">PS C:\\&gt; .\\Get-MyProcess.exe -List True\r\n\r\nName                       : PowerShell Studio\r\nId                         : 4428\r\nPriorityClass              : Normal\r\nFileVersion                : 4.2.98.0\r\nHandleCount                : 3562\r\nWorkingSet                 : 323387392\r\nPagedMemorySize            : 272556032\r\nPrivateMemorySize          : 272556032\r\nVirtualMemorySize          : 1226399744\r\nTotalProcessorTime         : 00:15:26.7187500\r\nSI                         : 1\r\nHandles                    : 3562\r\nVM                         : 1226399744\r\nWS                         : 323387392\r\nPM                         : 272556032\r\nNPM                        : 182704\r\n...<\/pre>\n<h1>Save the output as XML or JSON<\/h1>\n<p>[<i>Thanks to Joel Bennett (<a href=\"https:\/\/twitter.com\/Jaykul\" target=\"_blank\">@Jaykul<\/a>) and Rob Campbell (<a href=\"https:\/\/twitter.com\/mjolinor\" target=\"_blank\">@mjolinor)<\/a> for the idea.<\/i>]<\/p>\n<p>You can also add an option to save the output object in an XML or JSON file, instead of returning it as a string. Then, the user can convert the contents of the file to a more usable object type and include it in subsequent PowerShell commands.<\/p>\n<p>In this version of the script, I&#8217;ve added two new parameters, <strong>XMLFilePath<\/strong> and <strong>JSONFilePath<\/strong>. The parameters take the path to an XML or JSON file. I don&#8217;t validate the input, because the output cmdlets return reasonably helpful error messages.<\/p>\n<p>Because the <strong>List<\/strong>, <strong>XMLFilePath<\/strong>, and <strong>JSONFilePath<\/strong> parameters are exclusive, I put each in its own parameter set.<\/p>\n<p>To preserve the default (no parameter) option, I made the <strong>ListSet<\/strong> parameter set the default and made its <strong>List<\/strong> parameter optional. When all parameters are strings, as in an exe, you must specify a default parameter set, because the parameter binding logic in Windows PowerShell cannot use types to figure out which parameter set you want.<\/p>\n<p>(No, I did not memorize the syntax for all of this. I used the <a href=\"https:\/\/www.sapien.com\/blog\/2014\/10\/02\/adding-parameter-sets-to-a-function\/\" target=\"_blank\">Function Builder<\/a>. <a href=\"https:\/\/www.youtube.com\/watch?v=CJLfS2Djv5g\" target=\"_blank\">Prefer a video?<\/a>).<\/p>\n<pre lang=\"PowerShell\">Param\r\n(\t\r\n        [CmdletBinding(DefaultParameterSetName = 'ListSet')]\r\n        [Parameter(ParameterSetName = 'ListSet')]\r\n\t[ValidateNotNullOrEmpty()]\r\n\t[String]\r\n\t$List,\r\n\t\r\n\t[Parameter(ParameterSetName = 'XMLSet',\r\n\t\t\t   Mandatory = $true)]\r\n\t[String]\r\n\t$XMLFilePath,\r\n\t\r\n\t[Parameter(ParameterSetName = 'JSONSet',\r\n\t\t\t   Mandatory = $true)]\r\n\t[String]\r\n\t$JSONFilePath\r\n)<\/pre>\n<p>The code is pretty simple. I used an IF\/ELSEIF sequence to check the parameters and write the output as strings or save it as XML or JSON.<\/p>\n<p>Because neither <a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=293956\" target=\"_blank\">Export-Clixml<\/a> nor <a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=293996\" target=\"_blank\">Out-File<\/a> (nor <a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=293909\" target=\"_blank\">Set-Content<\/a>) return any data, I always use <a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=290495\" target=\"_blank\">Get-Item<\/a> to return the new file to the user. If they don&#8217;t need it, they can always pipe it to <a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=289602\" target=\"_blank\">Out-Null<\/a>, but I bet it saves users an extra command to check the file.<\/p>\n<pre lang=\"PowerShell\">if ($p = Get-Process 'PowerShell Studio')\r\n{\r\n\tif ($List)\r\n\t{\r\n\t\t$p | Format-List -Property *\r\n\t}\r\n\telseif ($XMLFilePath)\r\n\t{\r\n\t\t$p | Export-Clixml -Path $XMLFilePath\r\n\t\tGet-Item $XMLFilePath\r\n\t}\r\n\telseif ($JSONFilePath)\r\n\t{\r\n\t\t$p | ConvertTo-Json | Out-File $JSONFilePath\r\n\t\tGet-Item $JSONFilePath\r\n\t}\r\n\telse { $p }\r\n}<\/pre>\n<p>Now, the user can run Get-MyProcess.exe, save the output in a file, import the output as a deserialized XML object (<a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=293982\" target=\"_blank\">Import-Clixml<\/a>) or convert it to a PSCustomObject (<a href=\"http:\/\/go.microsoft.com\/fwlink\/p\/?linkid=293947\" target=\"_blank\">ConvertFrom-Json<\/a>), and use it as needed.<\/p>\n<pre class=\"output\">PS C:\\&gt;.\\Get-MyProcess.exe -XMLFilePath .\\Scripts\\SPSProcess.xml\r\n\r\n    Directory: C:\\Scripts\r\n\r\nMode                LastWriteTime         Length Name\r\n----                -------------         ------ ----\r\n-a----       12\/17\/2015   2:33 PM         634284 SPSProcess.xml\r\n\r\nPS C:\\&gt; $p = Import-Clixml -Path .\\Scripts\\SPSProcess.xml\r\nPS C:\\&gt; $p\r\n\r\nHandles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id  SI ProcessName\r\n-------  ------    -----      ----- -----   ------     --  -- -----------\r\n   1506     176   287812     283776  1212    88.84   6776   1 PowerShell Studio\r\n\r\n\r\nPS C:\\&gt; $p.Handles\r\n1506\r\n\r\nPS C:\\&gt; $p | Format-List *\r\n\r\nName                       : PowerShell Studio\r\nSI                         : 1\r\nHandles                    : 1506\r\nVM                         : 1271144448\r\nWS                         : 290586624\r\nPM                         : 294719488\r\nNPM                        : 179984\r\n...\r\n<\/pre>\n<p>You can also use string-parsing techniques and cmdlets like <a href=\"http:\/\/go.microsoft.com\/fwlink\/?LinkId=528577\" target=\"_blank\">Convert-String<\/a> and <a href=\"http:\/\/go.microsoft.com\/fwlink\/?LinkID=507579\" target=\"_blank\">Convert-FromString<\/a> to convert the string output to a custom object.<\/p>\n<p>Wrapping scripts in executable files is valuable, but remember to help the user with the string output.<\/p>\n<p><i>June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at <\/i><a href=\"mailto:juneb@sapien.com\"><em>juneb@sapien.com<\/em><\/a><em> or follow her on Twitter at <\/em><a href=\"https:\/\/twitter.com\/juneb_get_help\"><em>@juneb_get_help<\/em><\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is the fourth blog in a multi-part series about designing a Windows PowerShell script that will be packaged in an executable file. Passing Parameters to a Script in an Executable File explains how to use the special parsing features of PowerShell Studio and PrimalScript to make passing parameters easy for PowerShell users and authors. [&hellip;]<\/p>\n","protected":false},"author":31,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[1034,946,283,703,25],"tags":[135,934,28,988,1016,951],"class_list":["post-10607","post","type-post","status-publish","format-standard","hentry","category-best-practices","category-guiprogramming","category-howto","category-powershell-studio","category-windows-powershell","tag-beginner","tag-juneb","tag-powershell","tag-powershell-gui","tag-powershell-studio","tag-powershell-studio-2015"],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/10607","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/users\/31"}],"replies":[{"embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/comments?post=10607"}],"version-history":[{"count":29,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/10607\/revisions"}],"predecessor-version":[{"id":10884,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/posts\/10607\/revisions\/10884"}],"wp:attachment":[{"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/media?parent=10607"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/categories?post=10607"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev.sapien.com\/blog\/wp-json\/wp\/v2\/tags?post=10607"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}