From Svendsen Tech PowerShell Wiki





Tweet









A Simple Example Of Running A PowerShell Command With PsExec

I figure some might just be looking for a way to execute PowerShell scripts, cmdlets, commands, scriptblocks or whatever, via PsExec, so here's an example at the top of the article:

PS C:\> .\PsExec.exe \\winxpssdtemp cmd /c 'echo . | powershell.exe -command "$env:PROCESSOR_ARCHITECTURE; exit 100"' PsExec v1.98 - Execute processes remotely Copyright (C) 2001-2010 Mark Russinovich Sysinternals - www.sysinternals.com x86 cmd exited on winxpssdtemp with error code 100. PS C:\>

Getting the quoting right can be tricky. The point here is that you need to pipe something to PowerShell.exe, using cmd.exe. Also check out the example with my wrapper below. Lee Holmes has an article where he addresses some of the quoting issues.

Capturing The Error Code And Storing Output In A Variable

Here's a quick demonstration where I assign the PsExec command's output to a variable. The "rest" of the output is written to STDERR (file descriptor 2), not STDOUT (file descriptor 1). I'm too ignorant to figure out a way of avoiding a temporary file, so that's what I ended up using. I parse it with a regular expression to extract the error code.

PS C:\PS> $Output = .\PsExec.exe \\winxpssdtemp cmd /c 'echo. | powershell.exe -command "$env:PROCESSOR_ARCHITECTURE; exit 100"' 2> error.tmp PS C:\PS> type .\error.tmp PsExec.exe : At line:1 char:24 + $Output = (.\PsExec.exe <<<< \\winxpssdtemp cmd /c 'echo. | powershell.exe -command "$env:PROCESSOR_ARCHITECTURE; ex it 100"') 2> error.tmp + CategoryInfo : NotSpecified: (:String) [], RemoteException + FullyQualifiedErrorId : NativeCommandError PsExec v1.98 - Execute processes remotely Copyright (C) 2001-2010 Mark Russinovich Sysinternals - www.sysinternals.com Connecting to winxpssdtemp...Starting PsExec service on winxpssdtemp...Connecting with PsExec service on winxpssdtemp.. .Starting cmd on winxpssdtemp... cmd exited on winxpssdtemp with error code 100. PS C:\PS> ((gc .\error.tmp) -join "`n") -match 'cmd exited on \S+ with error code (\d+)\.' True PS C:\PS> $Matches[1] 100 PS C:\PS> $Output x86

General Information About The PsExec Wrapper

This script is now obsolete in favor of this new and insanely improved Invoke-PsExec script.

Presented here is a generic Sysinternals PsExec wrapper, written in PowerShell. SysInternals was purchased by Microsoft, and their web site redirects to microsoft.com. You can use this script to wrap and parse PsExec output from a command that's run against a list of computers. Rudimentary regexp parsing support and capturing against the PsExec output is available.

Note made 2015-03-01: Older and wiser, I realized a while (months, years?) after writing this article that it's way too comprehensive and complex, and requires learning a new syntax, etc., so it's not for most, but the product really is quite good functionality-wise, although horribly not-in-compliance with PowerShell standards. If I were to write it now, I'd output objects, and make lots of other changes - such as not using the ridiculous delimiter " | ". If I remember correctly, I had just seen it in some MS CSV file and it was on my mind, so that's what was used. Single character delimiter would be better, or at least not using regex meta-characters...

Note that in most cases, WMI will be what you want. See this article for an example of a WMI wrapper similar to this one, and also this article for some information about WMI timeouts. Also see "Get-Help Get-WmiObject" at your PowerShell prompt. PowerShell remoting, introduced in PowerShell v2, is also a far more sophisticated and robust option - and it has encrypted authentication, for what it's worth.

Also be aware that PsExec transmits the user credentials, including the password, in plain text. NB! Some time shortly before April 1st, 2014, PsExec was changed so that it now encrypts the credentials, so make sure you get the latest version.

Output will be in either CSV or XML format (you decide).

The CSV consists of a first, double-quoted field with the computer name, then the PsExec output is joined with -MultiLineJoinString (default " | "), and added as a second field. You can specify your own join string if you want, to suit your parsing needs. You can also specify a regexp with the -ExtractionRegex parameter, where the stored CSV or XML output from PsExec will be the content of the first capture group. The default regular expression is "(.+)" (capture everything). Read more about PowerShell regular expressions here.

I decided to surround the second CSV field in double quotes since I think double quotes in the data will be more uncommon than commas, and with double quotes around the second CSV field, you get it as one field if you pass the output file to Import-Csv afterwards. I think it's what most people will want most of the time. Maybe I should have added it as an option.

See the examples below to see what the XML output looks like.

It has occurred to me that I should add an option where "cmd /c " is also included in the default command, to avoid one level of nested quoting.

Script Options

The examples below probably provide the most immediate understanding. A good tip is to specify the -ExtractionRegex parameter last, if you do specify it, since the regexp seems to have a tendency to mess up parameter name completion with tab after it's been specified. I'd think balanced single quotes around it would do, but alas.

-PsExecCommand (required): This is the actual command you would pass to psexec. The script prepends "PsExec.exe \\$Computer " and the rest is up to you to specify. See examples.

(required): This is the actual command you would pass to psexec. The script prepends "PsExec.exe \\$Computer " and the rest is up to you to specify. See examples. -ComputerList (required): Comma-separated list of computers: comp1,comp2,comp3. To specify a file containing target computer names, just use "(gc .\computerfile.txt)", including the parentheses (and without the quotes).

(required): Comma-separated list of computers: comp1,comp2,comp3. To specify a file containing target computer names, just use "(gc .\computerfile.txt)", including the parentheses (and without the quotes). -OutputFile (required): Output destination file name. NB! If you use the optional -XmlOutput parameter, you will need to specify a full path to the output file, or you will probably find it in your default profile directory.

(required): Output destination file name. If you use the optional -XmlOutput parameter, you will need to specify a path to the output file, or you will probably find it in your default profile directory. -MultiLineJoinString (optional): If you are not using the optional -XmlOutput option, output will be a CSV file with possible multi-line output joined together with this string as the second CSV data field (surrounded by double quotes). The first CSV field being the computer name. The default join string is " | " (space, pipe, space).

(optional): If you are using the optional -XmlOutput option, output will be a CSV file with possible multi-line output joined together with this string as the second CSV data field (surrounded by double quotes). The first CSV field being the computer name. The default join string is " | " (space, pipe, space). -DelimiterJoinString (optional): If you use the optional -ExtractionRegex parameter, the output will be joined together using this string if it spans multiple lines, before the regexp match is performed, so you need to "anchor", typically with "Something: (.+?)( \| |$)". See the examples for an explanation.

(optional): If you use the optional -ExtractionRegex parameter, the output will be joined together using this string if it spans multiple lines, before the regexp match is performed, so you need to "anchor", typically with "Something: (.+?)( \| |$)". See the examples for an explanation. -ExtractionRegex (optional): The default is "(.+)", which means "everything", or the first line with the "no single-line" option. A regexp to run against the lines joined together with the -DelimiterJoinString (default: " | "). If the output is only one line, the -DelimiterJoinString is not used. You need to have at least one set of capturing parentheses in the regexp, because the content of $Matches[1] is what is saved to file when you specify the -ExtractionRegex parameter. You need to anchor against " | ", typically with "Something: (.+?)(?: \| |$)". There's more information about this in the examples section. Don't forget to escape the pipe with a backslash. Or use a different -DelimiterJoinString to anchor against. Also be aware that the single-line regexp option is active by default, so the regexp meta character "." will also match newlines, so ".+" might match more than you would expect. You can disable single-line matching with the -RegexOptionNoSingleLine parameter.

(optional): The default is "(.+)", which means "everything", or the first line with the "no single-line" option. A regexp to run against the lines joined together with the -DelimiterJoinString (default: " | "). If the output is only one line, the -DelimiterJoinString is not used. -RegexOptionNoSingleLine (optional): Disables single-line regexp matching against the PsExec output.

(optional): Disables single-line regexp matching against the PsExec output. -RegexOptionCaseSensitive (optional): Enables case sensitivity for the regexp.

(optional): Enables case sensitivity for the regexp. -Clobber (optional): Overwrite output file if it exists without asking. Otherwise you will be prompted to overwrite (default: yes, so you can just press enter).

(optional): Overwrite output file if it exists without asking. Otherwise you will be prompted to overwrite (default: yes, so you can just press enter). -XmlOutput (optional): Rather than CSV output, you will get XML output. See examples.

Examples And Screenshots

Some examples and screenshots.

First Simple Example

Let's start with something simple, using only the required parameters. Have PsExec simply "cmd /c echo %PROCESSOR_ARCHITECTURE%" and use the default CSV format, against two computers specified on the command line.

PS C:\prog\Powershell> .\PsExec-Wrapper.ps1 -PsExecCommand 'cmd /c echo %PROCESSOR_ARCHITECTURE%' -ComputerList winxpesxi,win7esxi -OutputFile arch.csv -Clobber Script start time: 11/13/2011 08:16:06 Found PsExec.exe in current working directory. Using: .\PsExec.exe Processing winxpesxi... Regex matched. Captured: x86 Processing win7esxi... Regex matched. Captured: AMD64 Done! Script start time: 11/13/2011 08:16:06 Script end time: 11/13/2011 08:16:28 Output file: arch.csv # Type the file to look at it: PS C:\prog\Powershell> type .\arch.csv "ComputerName","Output" "winxpesxi","x86" "win7esxi","AMD64" # Try Import-Csv on the file: PS C:\prog\Powershell> Import-Csv .\arch.csv ComputerName Output ------------ ------ winxpesxi x86 win7esxi AMD64

It looks right.

You'll notice the PowerShell console window title gets "stuck" with the last PsExec stuff. If you want to set it back to the default, you can run this command:

(Get-Host).UI.RawUI.WindowTitle = 'Windows PowerShell'

Or this:

$Host.UI.RawUI.WindowTitle = 'Windows PowerShell'





Second Example With Regexp Extraction

Now let's look at something a little more complicated where we deal with multi-line output from a command. For some reason, I want the serial number on the system drives of a range of computers. It's quick and dirty, and I'll just parse "dir %SYSTEMDRIVE%\", looking for "Volume Serial Number is <CAPTURE_REST_OF_LINE>". A "line" in the output from the PsExec command is user-defined with the -DelimiterJoinString parameter, which by default is " | ". I will use a regular expression to extract what I want.

The command will be:

.\PsExec-Wrapper.ps1 -PsExecCommand "cmd /c dir %SYSTEMDRIVE%\" -ComputerList (gc computers.txt) -OutputFile c:\prog\powershell\disk-serial.csv -clobber -extractionregex 'volume serial number is (.+?)(?: \| |$)'

The trickiest part here might be understanding the -DelimiterJoinString part, which joins all the lines together, separated by " | " (by default), so often you will be "anchoring" on " | " to indicate the end of a line, and using the non-greedy ".+?" or ".*?" regular expressions before it to capture. Also see example 5 for more information.

The regular expression in the command is "volume serial number is (.+?)(?: \| |$)". It will be case-insensitive by default. There you anchor on "volume serial number is", followed by a space, then you non-greedily capture everything with "(.+?)", until you hit the first instance of " | ", or the end of the text.

"(?: \| |$)" might look cryptic, but the "?:" construct means it's a non-capturing group, which doesn't matter here, since only the first capture is used, but it's considered best practices by most - although it's slightly more unreadable to the untrained eye. You could have used "( \| |$)" instead and it would not have mattered in this case. The first pipe, escaped with a backslash ("\"), means a literal pipe, here surrounded by spaces, then there's a non-escaped pipe ("|"), which means OR in regular expressions.

So it means either " | " or "$", and the regexp meta-character "$" means the end of the text (or line with the multi-line option, which you can add with an inline mode modifier). If you know there's a line after what you're grabbing, you can just use " \| " instead (such as in this example case). I'm describing what's most likely to work flexibly in diverse scenarios.

PS C:\prog\Powershell> .\PsExec-Wrapper.ps1 -PsExecCommand "cmd /c dir %SYSTEMDRIVE%\" -ComputerList (gc computers.txt) -OutputFile c:\prog\powershell\disk-serial.csv -clobber -extractionregex 'volume serial number is (.+?)(?: \| |$)' Script start time: 11/13/2011 08:31:19 Found PsExec.exe in current working directory. Using: .\PsExec.exe Processing winxpesxi... Regex matched. Captured: D8B2-1634 Processing win7esxi... Regex matched. Captured: 847E-4BE4 Processing notexisting... Regex did not match and no output. Processing 2008r2esxi... Regex matched. Captured: 88C4-00FE Done! Script start time: 11/13/2011 08:31:19 Script end time: 11/13/2011 08:32:04 Output file: c:\prog\powershell\disk-serial.csv # Type the file to inspect PS C:\prog\Powershell> type .\disk-serial.csv "ComputerName","Output" "winxpesxi","D8B2-1634" "win7esxi","847E-4BE4" "notexisting","ERROR: No output" "2008r2esxi","88C4-00FE" # Check it out with Import-Csv PS C:\prog\Powershell> Import-Csv .\disk-serial.csv ComputerName Output ------------ ------ winxpesxi D8B2-1634 win7esxi 847E-4BE4 notexisting ERROR: No output 2008r2esxi 88C4-00FE

If we use a regexp that doesn't match, we will get the full output separated by -MultiLineJoinString, which by default is " | " (space, pipe, space). If the output is single-line, the join can be disregarded. Test on a few computers before running this against the masses, so you can weed out errors. Here I use "serial number WAS" rather than "serial number is" in the regexp, otherwise it is identical to the previous example. This is to demonstrate what kind of output you get if there isn't a regexp match and multiple-line output.

PS C:\prog\Powershell> .\psexec-Wrapper.ps1 -PsExecCommand "cmd /c dir %SYSTEMDRIVE%\" -ComputerList (gc computers.txt) -OutputFile c:\prog\powershell\disk-serial.csv -clobber -ExtractionRegex 'volume serial number WAS (.+?)(?: \| |$)' Script start time: 11/13/2011 08:33:18 Found PsExec.exe in current working directory. Using: .\PsExec.exe Processing winxpesxi... Regex did not match. Using all output in lines joined with ' | '. Processing win7esxi... Regex did not match. Using all output in lines joined with ' | '. Processing notexisting... Regex did not match and no output. Processing 2008r2esxi... Regex did not match. Using all output in lines joined with ' | '. Done! Script start time: 11/13/2011 08:33:18 Script end time: 11/13/2011 08:34:01 Output file: c:\prog\powershell\disk-serial.csv # If you look at it with notepad or Import-Csv, you will see that the entire output # from "cmd /c dir %SYSTEMDRIVE%\" is there, joined together with " | ": PS C:\prog\Powershell> Import-Csv .\disk-serial.csv ComputerName Output ------------ ------ winxpesxi Volume in drive C has no label. | Volume Serial Number... win7esxi Volume in drive C has no label. | Volume Serial Number... notexisting ERROR: No output 2008r2esxi Volume in drive C has no label. | Volume Serial Number...

Third Example With XML Output

Now, we'll capture either the boot time or up time from the output from the application systeminfo. Again, there are better ways to do this, it's just an example (I'm not the one looking for a PsExec wrapper, I just wrote one :-). I have written an article about how to find when remote computers were last booted up, and put it here; this uses WMI.

I've added the parameter "-XmlOutput". NB! It's important to remember that you need to specify a FULL path to the output file when you use the -XmlOutput parameter! Otherwise, it will probably end up in your default profile directory if you just use ".\output.xml".

PS C:\prog\Powershell> .\PsExec-Wrapper.ps1 -PsExecCommand "cmd /c systeminfo" -ComputerList (gc computers.txt) -OutputFile c:\prog\powershell\uptime.xml -Clobber -ExtractionRegex '((?:boot|up) time:\s+.+?)(?: \| |$)' -XmlOutput Script start time: 11/13/2011 09:25:28 Found PsExec.exe in current working directory. Using: .\PsExec.exe Processing winxpesxi... Regex matched. Captured: Up Time: 13 Days, 10 Hours, 10 Minutes, 24 Seconds Processing win7esxi... Regex matched. Captured: Boot Time: 09.11.2011, 03:30:01 Processing notexisting... Regex did not match and no output. Processing 2008r2esxi... Regex matched. Captured: Boot Time: 09.11.2011, 03:44:59 Done! Script start time: 11/13/2011 09:25:28 Script end time: 11/13/2011 09:26:21 Output file: c:\prog\powershell\uptime.xml PS C:\prog\Powershell> .\uptime.xml PS C:\prog\Powershell>

And this is what the resulting XML looks like in Internet Explorer:

Fourth Example Where We Run a PowerShell Command Using PsExec

While we're rolling around in the filth...: Let's look at how we can execute PowerShell commands with the PsExec-Wrapper.ps1 script. Getting the quoting right can be a little tricky, and also, you need to know about a little trick: The trick is piping something to PowerShell, such as in: "cmd /c echo . | powershell ...". You can find more about that elsewhere on the web.

We're currently in quote hell; I really miss Perl's q() and qq() operators. Check this out, and notice how I enclose the entire -PsExecCommand parameter in single quotes, and then use a doubled-up single quote inside to represent a literal single quote inside the string, along with double quotes for PowerShell's -Command parameter:

PS C:\prog\Powershell> .\PsExec-Wrapper.ps1 -PsExecCommand 'cmd /c ''echo . | PowerShell -Command "$env:windir"''' -ComputerList (gc .\computers.txt) -OutputFile test.csv -Clobber Script start time: 11/13/2011 10:22:38 Found PsExec.exe in current working directory. Using: .\PsExec.exe Processing winxpesxi... Regex matched. Captured: C:\WINDOWS Processing win7esxi... Regex matched. Captured: C:\Windows Processing notexisting... Regex did not match and no output. Processing 2008r2esxi... Regex matched. Captured: C:\Windows Done! Script start time: 11/13/2011 10:22:38 Script end time: 11/13/2011 10:23:24 Output file: test.csv # Look at the text file: PS C:\prog\Powershell> type .\test.csv "ComputerName","Output" "winxpesxi","C:\WINDOWS" "win7esxi","C:\Windows" "notexisting","ERROR: No output" "2008r2esxi","C:\Windows" # Try Import-Csv on it: PS C:\prog\Powershell> Import-Csv .\test.csv ComputerName Output ------------ ------ winxpesxi C:\WINDOWS win7esxi C:\Windows notexisting ERROR: No output 2008r2esxi C:\Windows

Of course, you can also output stuff to XML, like the memory size in GB, with this command:

PS C:\prog\Powershell> .\PsExec-Wrapper.ps1 -PsExecCommand 'cmd /c ''echo . | powershell -command "[Math]::Round((((gwmi Win32_ComputerSystem).TotalPhysicalMemory)/1GB), 2)"''' -ComputerList (gc .\computers.txt) -OutputFile C:\prog\Powershell\memory.xml -XmlOutput -Clobber

I really wanted to use "(((gwmi Win32_ComputerSystem).TotalPhysicalMemory)/1GB).ToString('N')", which I normally use for formatting numbers nicely and in a way that respects system locale settings, but I simply couldn't figure out an easy way of getting quotes to be correctly parsed in that mess. Nothing seemed to work. You might find the magic incantation required. Instead, I found the [Math]::Round($Value, $Precision) function, which I wrapped it in. Post-data-collection processing might be required, or easier.

The command above gives XML output like this:

Fifth Example Demonstrating "Advanced" Options

For a more "normal" regexp experience, you might expect "$" to match before every newline in the output from PsExec, rather than having to use " \| " or "(?: \| |$)" as mentioned in previous examples. Also, having "." match newlines might not be what you want. I had single-line CSV output in mind when I created this script. I might have made poor decisions for some use cases.

There are two, or possibly three, things you need to do to have "normal", multi-line behavior:

Add " (?m) " at the start of your regexp.

" at the start of your regexp. Use the -DelimiterJoinString "`n" parameter

parameter Use the -RegexOptionNoSingleLine parameter

The last parameter mentioned above would not be needed in the example below, if you made ".+" non-greedy with ".+?", since "$" takes precedence with the multi-line flag, (?m). See the PowerShell regexp article for more information about regular expressions in general. If you do add the parameter -RegexOptionNoSingleLine, you can, on the other hand, leave out the question mark that makes ".+" non-greedy. There are several ways to do things; understanding, and experimenting, helps.

Here's one way to do it:

PS C:\prog\Powershell> .\PsExec-Wrapper.ps1 -PsExecCommand 'cmd /c systeminfo' -ComputerList (gc .\computers.txt) -OutputFile C:\prog\Powershell\uptime.xml -Clobber -XmlOutput -DelimiterJoinString "`n" -RegexOptionNoSingleLine -ExtractionRegex '(?m)((?:boot|up) time:\s+.+)$' Script start time: 11/19/2011 09:25:28 Found PsExec.exe in current working directory. Using: .\PsExec.exe Processing winxpesxi... Regex matched. Captured: Up Time: 1 Days, 7 Hours, 21 Minutes, 34 Seconds Processing win7esxi... Regex matched. Captured: Boot Time: 09.11.2011, 03:30:01 Processing notexisting... Regex did not match and no output. Processing 2008r2esxi... Regex matched. Captured: Boot Time: 09.11.2011, 03:44:59 Done! Script start time: 11/19/2011 09:25:28 Script end time: 11/19/2011 09:26:21 Output file: C:\prog\Powershell\uptime.xml

I'm tossing in a quick example of how to peek at the XML with PowerShell.

PS C:\prog\Powershell> $Xml = [xml] (gc .\uptime.xml) PS C:\prog\Powershell> $Xml.computers.computer name output ---- ------ winxpesxi Up Time: 1 Days, 7 Hours, 21 Minutes, 34 Seconds win7esxi Boot Time: 09.11.2011, 03:30:01 notexisting ERROR: No output 2008r2esxi Boot Time: 09.11.2011, 03:44:59 PS C:\prog\Powershell> type .\uptime.xml | select-string 'output' <output>Up Time: 1 Days, 7 Hours, 21 Minutes, 34 Seconds</output> <output>Boot Time: 09.11.2011, 03:30:01</output> <output>ERROR: No output</output> <output>Boot Time: 09.11.2011, 03:44:59</output> PS C:\prog\Powershell> $Xml.computers.computer | %{ $_.output } Up Time: 1 Days, 7 Hours, 21 Minutes, 34 Seconds Boot Time: 09.11.2011, 03:30:01 ERROR: No output Boot Time: 09.11.2011, 03:44:59 PS C:\prog\Powershell> $Xml.computers.computer | %{ $_.output -replace '\s+', ' ' } Up Time: 1 Days, 7 Hours, 21 Minutes, 34 Seconds Boot Time: 09.11.2011, 03:30:01 ERROR: No output Boot Time: 09.11.2011, 03:44:59

The Integrated Help Text

Tip: What you'll usually want to list is the parameter list with brief descriptions, or to check out this page again. To do this, you can type "Get-Help .\PsExec-Wrapper.ps1 -Detailed":

NAME C:\PowerShell\PsExec-Wrapper.ps1 SYNOPSIS Svendsen Tech's generic PowerShell PsExec wrapper. Also look into PowerShell remoting which came with PowerShell v2, and consider WMI, which are better solutions in most cases. Author: Joakim Svendsen SYNTAX C:\PowerShell\PsExec-Wrapper.ps1 [-PsExecCommand] <String> [-ComputerList] <String[]> [-OutputFi le] <String> [[-MultiLineJoinString] <String>] [[-DelimiterJoinString] <String>] [[-ExtractionRegex] <Regex>] [-Reg exOptionNoSingleLine] [-RegexOptionCaseSensitive] [-Clobber] [-XmlOutput] [<CommonParameters>] DESCRIPTION See the online documentation for comprehensive documentation and examples at: http://www.powershelladmin.com/wiki/Powershell_psexec_wrapper PARAMETERS -PsExecCommand <String> The command to pass to PsExec after "PsExec.exe \\<computer> ". -ComputerList <String[]> List of computers to process. Use a file with "(gc computerfile.txt)". -OutputFile <String> Output file name. You will be asked to overwrite unless -Clobber is specified. -MultiLineJoinString <String> The string to join multi-line output with in the second CSV field or XML field, if necessary. -DelimiterJoinString <String> The string that separates lines in the PsExec output. You can specify a newline with "`n". Also see -RegexoptionNoSingleLine -ExtractionRegex <Regex> The first capture group, indicated by parentheses, in the specified regexp, will be extracted instead of the entire output. If there is no match, it will fall back to -MultiLineJoinString and store the entire output. By default, it's "(.+)", which means "one or more of any character". -RegexOptionNoSingleLine [<SwitchParameter>] Makes the regexp meta-character "." NOT match newlines. -RegexOptionCaseSensitive [<SwitchParameter>] Makes the regexp case sensitive. -Clobber [<SwitchParameter>] Overwrite output file if it exists without prompting. -XmlOutput [<SwitchParameter>] Output to XML instead of CSV. Remember to use a full path!

Download and Source Code

But you really want this Invoke-PsExec script instead!

Source code:

<# .SYNOPSIS Svendsen Tech's generic PowerShell PsExec wrapper. Also look into PowerShell remoting which came with PowerShell v2, and consider WMI, which are better solutions in most cases. Author: Joakim Svendsen .DESCRIPTION See the online documentation for comprehensive documentation and examples at: http://www.powershelladmin.com/wiki/Powershell_psexec_wrapper .PARAMETER PsExecCommand The command to pass to PsExec after "PsExec.exe \\<computer> ". .PARAMETER ComputerList List of computers to process. Use a file with "(gc computerfile.txt)". .PARAMETER OutputFile Output file name. You will be asked to overwrite unless -Clobber is specified. .PARAMETER MultiLineJoinString The string to join multi-line output with in the second CSV field or XML field, if necessary. .PARAMETER DelimiterJoinString The string that separates lines in the PsExec output. You can specify a newline with "`n". Also see -RegexoptionNoSingleLine .PARAMETER ExtractionRegex The first capture group, indicated by parentheses, in the specified regexp, will be extracted instead of the entire output. If there is no match, it will fall back to -MultiLineJoinString and store the entire output. By default, it's "(.+)", which means "one or more of any character". .PARAMETER RegexOptionNoSingleLine Makes the regexp meta-character "." NOT match newlines. .PARAMETER RegexOptionCaseSensitive Makes the regexp case sensitive. .PARAMETER Clobber Overwrite output file if it exists without prompting. .PARAMETER XmlOutput Output to XML instead of CSV. Remember to use a full path! #> param( [Parameter(Mandatory=$true)][string] $PsExecCommand, [Parameter(Mandatory=$true)][string[]] $ComputerList , [Parameter(Mandatory=$true)][string] $OutputFile, [string] $MultiLineJoinString = ' | ', [string] $DelimiterJoinString = ' | ', [regex] $ExtractionRegex = '(.+)' , [switch] $RegexOptionNoSingleLine, [switch] $RegexOptionCaseSensitive, [switch] $Clobber, [switch] $XmlOutput ) Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' # Store script start time. Will be displayed at the end along with the end time. $StartTime = Get-Date "Script start time: $StartTime" # Prompt to overwrite unless -Clobber is specified. if ( -not $Clobber -and (Test-Path $OutputFile) ) { $Answer = Read-Host 'Output file already exists and -Clobber not specified. Overwrite? (y/n) [yes]' if ($Answer -imatch 'n') { Write-Output 'Aborted.'; exit; } } # Remove it if it exists, and just suppress errors if it doesn't. Remove-Item $OutputFile -ErrorAction SilentlyContinue # Some minor sanity checking if the user supplied an extraction regex other than '(.+)'. Make sure they capture something. if ( $ExtractionRegex.ToString() -ne '(.+)' -and ($ExtractionRegex.ToString() -inotmatch '\(' -or $ExtractionRegex.ToString() -inotmatch '\)') ) { Write-Output "The supplied regex '$($ExtractionRegex.ToString())' is missing either a '(' or a ')'.`nYou must capture something.`nSee Get-Help $($MyInvocation.MyCommand.Name).`nWill now exit with exit code 1" exit 1 } # 45 lines to check if we have psexec.exe in current working dir or path... Should write a generic function for this. $PsExecFile = 'PsExec.exe' if ( Test-Path $PsExecFile ) { $PsExecFullPath = '.\' + $PsExecFile Write-Output "Found $PsexecFile in current working directory. Using: $PsExecFullPath" } elseif ($PsExecFileObject = Get-Command $PsExecFile) { if ($PsExecFileObject -is [System.Array]) { $PsexecFullPath = $PsExecFileObject[0].Definition Write-Output "Found multiple $PsExecFile instances in path. Using this path: $PsexecFullPath" } elseif ($PsExecFileObject -is [System.Management.Automation.ApplicationInfo]) { $PsExecFullPath = $PsExecFileObject.Definition Write-Output "Found one instance of $PsExecFile in path. Using this path: $PsexecFullPath" } else { Write-Output "Unknown object type returned from 'Get-Command $PsExecFile'.`nWill now exit with status code 3" exit 3 } } else { @" You need to download PsExec from www.sysinternals.com (redirects to microsoft.com) in order to use this PsExec wrapper script. It needs to be in the current working directory, or in a directory in the current PATH environment variable. Will now exit with status code 2. "@ exit 2 } # Output array $OutputArray = @() # Temporary file name $TempFileName = '.\Svendsen-Tech-PsExec-wrapper.tmp' # Add the option that makes "." match newlines unless -RegexOptionNoSingleLine is passed if (-not $RegexOptionNoSingleLine) { $ExtractionRegex = [regex] ('(?s)' + $ExtractionRegex.ToString()) } # Make the regex case-insensitive by default, unless -RegexOptionCaseSensitive is passed if (-not $RegexOptionCaseSensitive) { $ExtractionRegex = [regex] ('(?i)' + $ExtractionRegex.ToString()) } foreach ($Computer in $ComputerList) { Write-Host -NoNewline "Processing ${Computer}... " # It returns some odd error, but it's redirected if you set $ErrorActionPreference to "Continue"... $ErrorActionPreference = 'Continue' $Output = Invoke-Expression "$PsExecFullPath \\$Computer $PsExecCommand 2> $TempFileName" $ErrorActionPreference = 'Stop' if (($Output -join $DelimiterJoinString) -imatch $ExtractionRegex) { Write-Host "Regex matched. Captured: $($Matches[1])" $ExtractedOutput = $Matches[1] } else { if ($Output) { $ExtractedOutput = $Output Write-Host "Regex did not match. Using all output in $(if ($XmlOutput) { 'XML field' } else { "lines joined with '$($MultiLineJoinString)'" })." } else { $ExtractedOutput = 'ERROR: No output' Write-Host "Regex did not match and no output." } } $OutputArray += ,$ExtractedOutput } if (Test-Path $TempFileName) { Remove-Item $TempFileName -ErrorAction 'Continue' } if ($XmlOutput) { $XmlString = '<computers>' foreach ( $i in 0..($ComputerList.Length - 1) ) { $XmlString += '<computer><name>' + $ComputerList[$i] + '</name><output>' + $OutputArray[$i] + '</output></computer>' } $XmlString += '</computers>' $Xml = [xml] $XmlString $Xml.Save($OutputFile) } else { # Create CSV headers manually '"ComputerName","Output"' | Out-File $OutputFile foreach ( $i in 0..($ComputerList.Length - 1) ) { # Create CSV manually '"' + $ComputerList[$i] + '","' + ($OutputArray[$i] -join $MultiLineJoinString) + '"' | Out-File -Append $OutputFile } } @" Done! Script start time: $StartTime Script end time: $(Get-Date) Output file: $OutputFile "@