问题
I have the following code that imports two csv files and merges them together. As part of the merge I remove the two files used and export one main CSV file.
$csvfiles | ForEach-Object {
Import-Csv "$csvLocation\$_";
Remove-Item "$csvLocation\$_" -Verbose 4>&1 | tee .\log.txt -Append
} | Export-Csv -NoTypeInformation "$csvLocation\$DBName.csv" -Force
The CSV generated looks like this with the 4>&1
verbose redirection:
Notice the extra empty in between row between server1 and server2.
The CSV I'm expecting should be like this (no in between extra row):
server Database role members server1 DB1 role1 memberx server1 DB1 role2 membery server2 DB1 role1 memberx server2 DB1 role2 membery
Of course without this part
4>&1|tee .\log.txt -append
the exported CSV looks good. However, I want the verbose messages printed to the console and file (with 4>&1 | tee .\log.txt -Append
it only seems to print to file unfortunately).
VERBOSE: Performing remove...
and the only way it works is with 4>&1
.
I tried redirecting to a variable. I tried many things but to no avail.
UPDATE: In regards to the second solution HAL offered below.
$csvfiles | ForEach-Object {
Import-Csv "$csvLocation\$_" | Export-Csv -Append -NoTypeInformation "$csvLocation\$DBName.csv" -Force;
Remove-Item "$csvLocation\$_" -Verbose 4>&1 | tee .\log.txt -Append
}
the output i'd normally see on the console with just -verbose
looked like this:
VERBOSE: Performing the operation "Remove File" on target
"D:\MyFiles\Scripts\Migration\CSVFiles\Database1_RolesMembers_server1.domain.com.c
sv".
VERBOSE: Performing the operation "Remove File" on target
"D:\MyFiles\Scripts\Migration\CSVFiles\Database1_RolesMembers_server2.domain.com.c
sv".
however, HAL's code results in this verbose console output:
VERBOSE: Performing the operation "Remove File" on target
"D:\MyFiles\Scripts\Migration\CSVFiles\Datab
VERBOSE: ase1_RolesMembers_server1.domain.com.csv".
VERBOSE: Performing the operation "Remove File" on target
"D:\MyFiles\Scripts\Migration\CSVFiles\Datab
VERBOSE: ase1_RolesMembers_server2.domain.com.csv".
of course its insignificant but its worth noting the strange output style anyways
as far as the csv file generated, it STILL results in empty line, but even more:
回答1:
The issue is all about objects, streams and trying to force two different objects into the same stream. What is happening is illustrated with my SO answer here: Running Line by Line Produces Odd Result Compared to Running Lines as a Single Line with Semicolons
TLDR: Basically what is happening is you are trying to pass 2 different objects down the same pipeline (see about_Redirection), and PowerShell does a "best effort" interpretation which is a "bleahh -> blank" interpretation when it gets to Export-CSV
. It also explains why no output to the console is displayed.
The best way to interpret what is going down the pipe is to break it up and assign to variables to see what is going on:
Unrolling the loop, the First command, and type is:
PS C:\> $csv = Import-Csv a.csv
PS C:\> $csv.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Second command is:
PS C:\> $ri = Remove-Item a.csv -Verbose 4>&1 | tee .\log.txt -Append
PS C:\> $ri.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False VerboseRecord System.Management.Automation.InformationalRecord
Basically what is happening is, The first command:
Import-CSV ...
Output an Object[]
array to the Information (1) stream.
The second command:
Remove-Item -Verbose
Outputs to the Verbose (4) stream.
The third command, the redirect: 4>&1 ...
Outputs the Verbose (4) stream content to the Information (1) stream.
The fourth command,
... | tee .\log.txt -Append
Takes the Remove-Item
redirected output on the Information (1) stream, and outputs to both the Information (1) stream and the log file.
When we combine them inside the ForEach-Object loop, we are creating an array of competing objects on the Information (1) stream:
PS C:\> 1..2|foreach{$csv; $ri}
server Database role members
------ -------- ---- -------
server1 DB1 role1 memberx
server1 DB1 role2 membery
VERBOSE: Performing the operation "Remove File" on target "C:\a.csv".
server1 DB1 role1 memberx
server1 DB1 role2 membery
VERBOSE: Performing the operation "Remove File" on target "C:\a.csv".
These competing objects then get passed down the pipeline to the next Export-Csv
command:
1..2 | foreach{$csv,$ri} | Export-Csv .\out.csv -NoTypeInformation
This gets us a "Raw" CSV:
"server","Database","role","members"
"server1","DB1","role1","memberx"
"server1","DB1","role2","membery"
,,,
"server1","DB1","role1","memberx"
"server1","DB1","role2","membery"
,,,
Where we can clearly see the 4 objects were being passed down the pipeline. The Export-Csv
gets 4 objects:
Object[]
arrayVerboseRecord
object (theRemove-Item -Verbose
redirected output)- The second
Object[]
array - The second
VerboseRecord
object
This is where PowerShell will take the first Object[]
array object to build the structure of the Export-Csv
file. It will then take the VerboseRecord
object, and since it doesn't have a way to "fit" it into the CSV, it outputs an empty record. It then takes the second Object[]
, and since it "fits", will output it to the CSV. It will then take the second VerboseRecord
object, and, again, since it doesn't have a way to "fit" it into the CSV, it outputs an empty record.
Notice that all 4 objects are in the CSV, even if PowerShell didn't know what to do with them. This is why you see no output on the PowerShell console. The Information (1) stream content is fully being directed down the pipeline to be consumed by the Export-Csv
cmdlet, and not the console Host. This is why you don't "see" the -Verbose
output from the Remove-Item
command on the console, but you do see it in the file.
TLDR2: Lesson 1: Don't output multiple things to the pipeline if you have something that consumes it in the end. With pipelines, only try to work with one thing at a time.
The code can be fixed 2 ways: If you need pipelines, split the two operations into ... well... two operations to prevent corruption of the pipeline:
$csvfiles | ForEach-Object {
Import-Csv "$csvLocation\$_";
} | Export-Csv -NoTypeInformation "$csvLocation\$DBName.csv" -Force
$csvfiles | ForEach-Object {
Remove-Item "$csvLocation\$_" -Verbose 4>&1 | tee .\log.txt -Append
}
Or, IMHO, don't ForEach into a pipeline and expect everything to work, as things may have to be accumulated into memory before being passed on. Eliminate the end pipeline consumer and pull the Export-Csv
into the ForEach-Object
loop, and append your content into it.
Edit: Don't forget to start off with an empty CSV File first :-)
Remove-Item "$csvLocation\$DBName.csv" -ErrorAction SilentlyContinue
$csvfiles | ForEach-Object {
Import-Csv "$csvLocation\$_" | Export-Csv -Append -NoTypeInformation "$csvLocation\$DBName.csv" -Force;
Remove-Item "$csvLocation\$_" -Verbose 4>&1 | tee .\log.txt -Append
}
回答2:
Well, you merge verbose records into the standard stream, none of which have server, Database, role or members properties, so each verbose message will result in an empty row after the records of each csv file.
Move the merge redirector to the outer pipeline instead - at that point Export-Csv
will have written everything in the standard output stream to file, and you'll have only the verbose messages left:
$csvfiles | ForEach-Object {
Import-Csv "$csvLocation\$_"
Remove-Item "$csvLocation\$_" -Verbose
} | Export-Csv -NoTypeInformation "$csvLocation\$DBName.csv" -Force 4>&1 |tee .\log.txt -Append
来源:https://stackoverflow.com/questions/56364249/why-is-verbose-redirection-creating-a-line-between-data-in-exported-csv-file