allison
allison

Reputation: 35

Powershell / cmd - Redirecting embedded script's output streams to file

I have a situation in which a cmd script must launch a powershell script (install.ps1), elevating to admin if the cmd is not already. The line that launches the powershell looks like this:

powershell -WindowStyle Hidden "Start-Process powershell \"-NoP -Exec Bypass -File `\"%~dp0install.ps1`\" %args%\" -Verb runAs -Wait"

Or this also works:

powershell -WindowStyle Hidden "Start-Process powershell \"-NoP -Exec Bypass invoke-command { %~dp0install.ps1 %args% } \" -Verb runAs -Wait"

I would like to redirect the output from the install.ps1 script to a file for logging purposes, but having trouble doing this. Something like the following will generate the log.txt file, but output will still be shown in the console and the resulting log.txt file will be empty:

powershell -WindowStyle Hidden "Start-Process powershell \"-NoP -Exec Bypass invoke-command { %~dp0install.ps1 %args% } \" *> log.txt -Verb runAs -Wait"

Moving the *> log.txt portion to inside the Start-Process block (just after the invoke-command block), which I thought would be the key, seems to not even run the script at all (or it's flashing an error in the console too quick to see because it closes immediately).

Is it possible to achieve this logging behavior when the data I want is buried in a couple layers of powershell, executed by a cmd file?

We've technically gotten this to work by creating a powershell wrapper script that is called/elevated by the cmd, then within the wrapper calling the install.ps1 script and assigning logging in that call. Unfortunately the extra script layer causes a bunch of other tricky / more critical problems regarding getting arguments passed at the command line all the way through to the actual install script correctly, so we're really trying to avoid that route.

EDIT

Thanks to @mklement0 for the pointer that the redirect needed to be escaped, which was my problem. Follow-up question - The following command works great to log to file, but is there any way to get this same behavior using -File rather than -Command when invoking the PS script ("-Command %~dp0pg.ps1")?

powershell -Command "Start-Process -WindowStyle Hidden -Verb RunAs -Wait powershell \"-NoProfile -ExecutionPolicy Bypass -Command %~dp0pg.ps1 *^> %CD%\log.txt\""

Upvotes: 1

Views: 636

Answers (1)

mklement0
mklement0

Reputation: 437111

Moving the *>log.txt redirection into the Invoke-Command block works in principle, but your problem is that in Windows PowerShell (as opposed to PowerShell Core) a process invoked with elevation (as admin), via -Verb RunAs, defaults to C:\Windows\System32 as the working directory, not the caller's working dir.

Aside from the fact that you probably didn't mean to create a log file in C:\Windows\System32, the command will fail, because writing to that location requires the caller to already be elevated.

The simplest solution is to make *> redirect to a file specified with a full path instead:

powershell -Command "Start-Process -WindowStyle Hidden -Verb RunAs -Wait powershell \"-NoProfile -ExecutionPolicy Bypass -Command %~dp0pg.ps1 *^> %CD%\log.txt\""

Note:

  • There is no need for Invoke-Command - just invoke the *.ps1 file directly; however, I've added -Command to make it more obvious that the remainder of the command line is to be interpreted as PowerShell code (not a script-file path with arguments only).

  • Because > is a cmd.exe metacharacter, it must be escaped as ^> in order to be passed through to PowerShell - perhaps surprisingly, cmd.exe considers the > to be unquoted, because it doesn't recognize the \" sequences as embedded double quotes - only PowerShell does.

  • As in your original command, the assumption is that neither %~dp0 - the batch file's folder dir. path - nor %CD% - the caller's working dir. path - contain spaces or other special chars. that would need additional quoting / escaping.

Upvotes: 3

Related Questions