Redirect PoweShell to text file

I’ve been slow to adopt PoweShell because I have not been able to figure out how to capture what the script is doing and the results of each command. Here is an example on what I am doing with my Batch files.

This redirects the output of the batch to a txt file as it is running
Script.bat C:\ScriptResults.txt 2>&1

The redirect will give me the command that was run and in many cases the results of the command. In this case I can see each command as it processed and I can see that one file was copied. Most of the time I get successes and failures logged in the txt file.
C:\WINDOWS\ccmcache\l>msiexec.exe /i “C:\Windows\ccmcache\l\Setup.msi” /qn
C:\WINDOWS\ccmcache\l>if not exist “C:\ProgramData\Software” mkdir “C:\ProgramData\Software”
C:\WINDOWS\ccmcache\l>copy /y “C:\Windows\ccmcache\l\Config_QA.ini” “C:\ProgramData\Software\Config.ini”
1 file(s) copied.
C:\WINDOWS\ccmcache\l>exit

I cannot find a way to do something similar with PowerShell. I found ways to only give errors but in a long script I can’t always tell where the error was in the script because it doesn’t log the command that was used. Some things would be much easier with PowerShell.

Does anyone know of a way to do this is PowerShell?

 

 

Check out the Start-Transcript Cmdlet. That would probably be your best option.

Get-Help start-transcript -ShowWindow

If you’re running a script within powershell, you can redirect all streams like this (2>&1 also works):

.\test.ps1 *> test.log

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection?view=powershell-5.1

If you’re actually running powershell from the outside, all streams will go to a file:

powershell -file .\test.ps1 > test.log

I’ve tried both the Start-Transcript and variations of the redirects. Nothing that I have tried includes the actual command that is being run. It is nice having the command listed and then the results of that command. It just makes troubleshooting quicker.

Start-Transcript works just fine for me, commands and output are located in the .log file upon inspection.

Start-Transcript -Path C:\temp\test_transcript.log
Get-Process -Name explorer
Write-Output -InputObject "This should be visible in the transcript"
Stop-Transcript
notepad 'C:\temp\test_transcript.log'

results truncated
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1


Transcript started, output file is C:\temp\test_transcript.log
C:>
PS>Get-Process -Name explorer

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName


1938 101 55292 119708 82.81 3964 1 explorer

C:>
PS>Write-Output -InputObject “This should be visible in the transcript”
This should be visible in the transcript
C:>
PS>Stop-Transcript


Windows PowerShell transcript end
End time: 20181220120651


That’s odd and would be perfect but mine looks different so I must be doing something wrong. How are you running it?

Here are my results.

PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1


Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName


2748 145 141192 215432 1,754.73 24616 4 explorer
This should be visible in the transcript


Windows PowerShell transcript end
End time: 20181220134043


I am running the commands as pasted in my previous response. That is strange output on your transcript file! What version of PS are you running?

C:\> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.14393.2636
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.14393.2636
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Here is what I am running.

PS U:> $PSVersionTable

Name Value


PSVersion 5.1.16299.785
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
BuildVersion 10.0.16299.785
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1

 

Could you try this on a different machine?
I’ve never seen transcription only partially work.

I have had issues where transcription completely breaks, but that has always resulted in hard errors vs incomplete results

there are always other options using error-handling, but I’d recommend you learn the PowerShell equivalent to the older dos commands.
if you utilize those you can have proper controls and messaging around file move activities.

Ok, I didn’t understand the original question. Start-transcript works for me. What exactly are you running?

PS C:\users\admin> Start-Transcript log
Transcript started, output file is log

PS C:\users\admin> ls foo
ls : Cannot find path 'C:\users\admin\foo' because it does not exist.
At line:1 char:1
+ ls foo
+ ~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\users\admin\foo:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

PS C:\users\admin> Stop-Transcript
Transcript stopped, output file is C:\users\admin\log

PS C:\users\admin> cat log
**********************
Windows PowerShell transcript start
Start time: 20181221091851
Username: admin
RunAs User: admin
Configuration Name:
Machine: JS (Microsoft Windows NT 10.0.16299.0)
Host Application: powershell
Process ID: 9056
PSVersion: 5.1.16299.820
PSEdition: Desktop
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.16299.820
BuildVersion: 10.0.16299.820
CLRVersion: 4.0.30319.42000
WSManStackVersion: 3.0
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1
**********************
Transcript started, output file is log
PS C:\users\admin> ls foo
ls : Cannot find path 'C:\users\admin\foo' because it does not exist.
At line:1 char:1
+ ls foo
+ ~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\users\admin\foo:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
ls : Cannot find path 'C:\users\admin\foo' because it does not exist.
At line:1 char:1
+ ls foo
+ ~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\users\admin\foo:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

PS C:\users\admin> Stop-Transcript
**********************
Windows PowerShell transcript end
End time: 20181221091900
**********************

I did change slightly Logan’s code below, instead of calling a full path for the log file, this will build the test_transcript.log file in whatever path your powershell session is open in
open a fresh PowerShell window.
paste the following commands in

[pre]
Start-Transcript test_transcript.log
Get-Process -Name explorer
Write-Output -InputObject “This should be visible in the transcript”
Stop-Transcript
notepad.exe test_transcript.log
[/pre]

Look at the contents of the file that is opened up, you should see results that match relatively closely with what Logan provided.

in your pasted example “Start-Transcript log” you don’t actually give a really valid filename…

I have tried this on several Windows 10 computers and always get the same results.

I just tried it from a command prompt “C:\Temp>powershell .\TranscriptTest.ps1” to see if that would change the results but it didn’t. I have been running it from PowerShell ISE but either way it is the same.

It creates the file in Logan’s code probably because C:\Temp exists on my computers. I used your code and here is the result.

Machine: TestComputer (Microsoft Windows NT 10.0.16299.0)
Host Application: C:\WINDOWS\system32\WindowsPowerShell\v1.0\PowerShell_ISE.exe
Process ID: 13608
PSVersion: 5.1.16299.785
PSEdition: Desktop
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.16299.785
BuildVersion: 10.0.16299.785
CLRVersion: 4.0.30319.42000
WSManStackVersion: 3.0
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1


Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName


2991 159 166516 184808 2,118.03 24616 4 explorer
This should be visible in the transcript


Windows PowerShell transcript end
End time: 20181221084402


is that the content of the text file that is opened in notepad?
if so, i’d see if you can try to run this on another machine.

It looks like start-transcript has to be run interactively, not within a script, to get the results you want.

Looks like you are correct. Start-Transcript doesn’t work in non-interactive. Well I guess I’m back to square one.

I have hacked together a way to achieve these results, but its not pretty and its requires additional coding. I used get-help to learn more about Powershell Redirection

Get-Help about_redirection -ShowWindow

I found this little gem here:

WINDOWS POWERSHELL REDIRECTION OPERATORS
The redirection operators enable you to send particular types of output
to files and to the success output stream.

  The Windows PowerShell redirection operators use the following characters 
  to represent each output type:
    *   All output
    1   Success output
    2   Errors
    3   Warning messages
    4   Verbose output
    5   Debug messages
    6   Informational messages 

          NOTE: The All (*), Warning (3), Verbose (4) and Debug (5) redirection operators were introduced
                        in Windows PowerShell 3.0; the Information (6) redirection operator was introduced
                        in Windows PowerShell 5.0. They do not work in earlier versions of Windows PowerShell.

  The Windows PowerShell redirection operators are as follows.
  Operator  Description                Example  
  --------  ----------------------     ------------------------------
  >         Sends output to the        Get-Process > Process.txt
            specified file.

  >>        Appends the output to      dir *.ps1 >> Scripts.txt
            the contents of the  
            specified file.

  2>        Sends errors to the        Get-Process none 2> Errors.txt
            specified file.

  2>>       Appends errors to          Get-Process none 2>> Save-Errors.txt
            the contents of the 
            specified file.

  2>&1      Sends errors (2) and       Get-Process none, Powershell 2>&1
            success output (1) 
            to the success 
            output stream.

  3>        Sends warnings to the      Write-Warning "Test!" 3> Warnings.txt
            specified file.

  3>>       Appends warnings to        Write-Warning "Test!" 3>> Save-Warnings.txt
            the contents of the 
            specified file.

  3>&1      Sends warnings (3) and     function Test-Warning 
            success output (1)         {  Get-Process PowerShell; 
            to the success                Write-Warning "Test!" }
            output stream.             Test-Warning 3>&1

  4>        Sends verbose output to    Import-Module * -Verbose 4> Verbose.txt
            the specified file.

  4>>       Appends verbose output     Import-Module * -Verbose 4>> Save-Verbose.txt
            to the contents of the 
            specified file.

  4>&1      Sends verbose output (4)   Import-Module * -Verbose 4>&1
            and success output (1)    
            to the success output
            stream.              

  5>        Sends debug messages to    Write-Debug "Starting" 5> Debug.txt
            the specified file.

  5>>       Appends debug messages     Write-Debug "Saving" 5>> Save-Debug.txt
            to the contents of the 
            specified file.

  5>&1      Sends debug messages (5)   function Test-Debug 
            and success output (1)     { Get-Process PowerShell 
            to the success output        Write-Debug "PS" }
            stream.                    Test-Debug 5>&1

  6>        Sends informational        Write-Information "Here they are" 6> Info.txt
            messages to a specified
            file 

  6>>       Appends informational      Write-Information "Nothing found" 6>> Info.txt
            messages to the contents
            of a specified file.

  6>&1      Sends informational        function Test-Info
            messages and success       { Get-Process P*
            output to the success        Write-Information "Here you go" }
            output stream.             Test-Info 6>&1

  *>        Sends all output types     function Test-Output
            to the specified file.     { Get-Process PowerShell, none  
                                         Write-Warning "Test!"
  *>>       Appends all output types     Write-Verbose "Test Verbose"
            to the contents of the       Write-Debug "Test Debug" } 
            specified file.            
                                       Test-Output *> Test-Output.txt
  *>&1      Sends all output types     Test-Output *>> Test-Output.txt
            (*) to the success output  Test-Output *>&1      
            stream.     


The syntax of the redirection operators is as follows:
<input> <operator> [<path>\]<file>

That gave me a couple of ideas. One, write transcript manipulation into your scripts and two, make use of the “Write-Information” Cmdlet. See below for an example.

# This produces output that the OP requested
$hereString = @'
Start-Transcript C:\Temp\Test_transcript.txt
Write-Information -MessageData "Write-Output 'This is a test. Will this command be redirected, or only the output?'"
Write-Output "This is a test. Will this command be redirected, or only the output?"
Stop-Transcript
'@

$hereString | Out-File C:\TEMP\test_redirect.ps1 -Force

Start-Process cmd -ArgumentList "/C powershell.exe -file C:\temp\test_redirect.ps1" -Wait
notepad C:\Temp\Test_transcript.txt

truncated
WSManStackVersion: 3.0
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1


Transcript started, output file is C:\Temp\Test_transcript.txt
INFO: Write-Output ‘This is a test. Will this command be redirected, or only the output?’
This is a test. Will this command be redirected, or only the output?


Windows PowerShell transcript end
End time: 20181221160639


Again, not pretty but technically achieves your desired outcome.

Thanks Logan but I couldn’t get the Write-Information to work.

Does anyone know if there would be issues using Set-Debug? On more advanced scripts it gives a lot of info but that might be good for troubleshooting.

I took Logan’s code and replaced the Transcript cmdlet’s with the PSDebug cmdlet’s
Set-PSDebug -Trace 2
Get-Process -Name explorer
Write-Output -InputObject “This should be visible in the transcript”
Set-PSDebug -Off

From a command prompt I run.
powershell -file .\TranscriptTest.ps1 *> c:\temp\Debug.txt

Here are the results of the Debug.txt
DEBUG: 3+ >>>> Get-Process -Name explorer
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName


2368 95 96828 89692 48.64 4296 1 explorer
DEBUG: 4+ >>>> Write-Output -InputObject “This should be visible in the transcript”
This should be visible in the transcript

It appears to give me what I want which is the command and then the results of that command but hopefully this won’t cause any other issues.

I think you would be much happier if you instituted some try/catch blocks around each of your cmdlets, you could then manually create text strings to have output to a log file in a location of your choosing.
a really fast hatchet-job to get something that should work, something like the following…

[pre]
$logfile = c:\temp\log.txt

try
{
$msiargumentlist = “/i C:\Windows\ccmcache\l\Setup.msi /qn”
$return = Start-Process msiexec -ArgumentList $msiArgumentList -Wait -passthru
If (@(0,3010) -contains $return.exitcode)
{
“Setup.msi executed successfully”| out-file $logfile -Append
}
else
{
“Setup.msi did not succeed” | out-file $logfile -Append
}
}
catch
{
“Eror starting msiexec” | out-file $logfile -Append
}
if (test-path “C:\ProgramData\Software”)
{
“C:\ProgramData\Software existed no action taken”|out-file $logfile -Append
}
else
{
try
{
new-item -ItemType directory “C:\ProgramData\Software”
“Built folder location C:\ProgramData\Software” | out-file $logfile -Append
}
catch
{
“Error building folder location C:\ProgramData\Software” | out-file $logfile -Append
}
}
try
{
copy-item -Path “C:\Windows\ccmcache\l\Config_QA.ini” -Destination “C:\ProgramData\Software\Config.ini”
“Copied Config_QA.ini to new location” | out-file $logfile -Append
}
catch
{
“Error Copying Config_QA.ini to new location” | out-file $logfile -Append
}
[/pre]

(note the error handling around msiexec I just did a fast google search for and found the following (no guarantee that bit works)
https://www.reddit.com/r/PowerShell/comments/30t5xh/how_to_catch_error_when_msi_install_fails/

Wow that’s a long thread … sorry … tl;dr:wink:

If you want to have a reasonable logging for Powershell you will have to create something by yourself or you can use one of the available modules you can find in the PowershellGallery. There are a few you can choose from.

Another great article about logging you can find here: Greater Visibility Through PowerShell Logging.

If you want to know what exactly your MSI installation does you can use the integrated logging of the MSI-Service.