@@ -4,34 +4,36 @@ Describe "Export-Csv" -Tags "CI" {
44 BeforeAll {
55 $testObject = @ (" test" , " object" , " array" )
66 $testCsv = Join-Path - Path $TestDrive - ChildPath " output.csv"
7+ $newLine = [environment ]::NewLine
8+ $P1 = [pscustomobject ]@ {" P1" = " first" }
9+ $P2 = [pscustomobject ]@ {" P2" = " second" }
10+ $P11 = [pscustomobject ]@ {" P1" = " eleventh" }
711 }
812
913 AfterEach {
10- Remove-Item $testCsv - Force - ErrorAction SilentlyContinue
14+ Remove-Item - Path $testCsv - Force - ErrorAction SilentlyContinue
1115 }
1216
1317 It " Should be able to be called without error" {
14- { $testObject | Export-Csv $testCsv - ErrorAction Stop } | Should -Not - Throw
18+ { $testObject | Export-Csv - Path $testCsv - ErrorAction Stop } | Should -Not - Throw
1519 }
1620
1721 It " Should throw if an output file isn't specified" {
18- { $testObject | Export-Csv - ErrorAction Stop } | ShouldBeErrorId " CannotSpecifyPathAndLiteralPath,Microsoft.PowerShell.Commands.ExportCsvCommand"
22+ { $testObject | Export-Csv - ErrorAction Stop } | Should - Throw - ErrorId " CannotSpecifyPathAndLiteralPath,Microsoft.PowerShell.Commands.ExportCsvCommand"
1923 }
2024
2125 It " Should be a string when exporting via pipe" {
22- $testObject | Export-Csv $testCsv - IncludeTypeInformation
23-
24- $piped = Get-Content $testCsv
26+ $testObject | Export-Csv - Path $testCsv - IncludeTypeInformation
27+ $results = Get-Content - Path $testCsv
2528
26- $piped [0 ] | Should - BeExactly " #TYPE System.String"
29+ $results [0 ] | Should - BeExactly " #TYPE System.String"
2730 }
2831
2932 It " Should be an object when exporting via the inputObject switch" {
3033 Export-Csv - InputObject $testObject - Path $testCsv - IncludeTypeInformation
34+ $results = Get-Content - Path $testCsv
3135
32- $switch = Get-Content $testCsv
33-
34- $switch [0 ] | Should - BeExactly " #TYPE System.Object[]"
36+ $results [0 ] | Should - BeExactly " #TYPE System.Object[]"
3537 }
3638
3739 It " Should output a csv file containing a string of all the lengths of each element when piped input is used" {
@@ -43,81 +45,186 @@ Describe "Export-Csv" -Tags "CI" {
4345 $expected = @ (" #TYPE System.String" , " `" Length`" " , $first , $second , $third )
4446
4547 for ($i = 0 ; $i -lt $expected.Count ; $i ++ ) {
46- $ (Get-Content $testCsv )[$i ] | Should - Be $expected [$i ]
48+ $ (Get-Content - Path $testCsv )[$i ] | Should - Be $expected [$i ]
4749 }
4850 }
4951
5052 It " Does not include type information by default" {
5153 $testObject | Export-Csv - Path $testCsv
54+ $results = Get-Content - Path $testCsv
5255
53- $ ( Get-Content $testCsv ) [0 ] | Should -Not -Match ([regex ]::Escape(" System.String" ))
54- $ ( Get-Content $testCsv ) [0 ] | Should -Not -Match ([regex ]::Escape(" #TYPE" ))
56+ $results [0 ] | Should -Not -Match ([regex ]::Escape(" System.String" ))
57+ $results [0 ] | Should -Not -Match ([regex ]::Escape(" #TYPE" ))
5558 }
5659
5760 It " Does not include type information with -NoTypeInformation" {
5861 $testObject | Export-Csv - Path $testCsv - NoTypeInformation
62+ $results = Get-Content - Path $testCsv
5963
60- $ ( Get-Content $testCsv ) [0 ] | Should -Not -Match ([regex ]::Escape(" System.String" ))
61- $ ( Get-Content $testCsv ) [0 ] | Should -Not -Match ([regex ]::Escape(" #TYPE" ))
64+ $results [0 ] | Should -Not -Match ([regex ]::Escape(" System.String" ))
65+ $results [0 ] | Should -Not -Match ([regex ]::Escape(" #TYPE" ))
6266 }
6367
6468 It " Includes type information when -IncludeTypeInformation is supplied" {
6569 $testObject | Export-Csv - Path $testCsv - IncludeTypeInformation
70+ $results = Get-Content - Path $testCsv
6671
67- $ ( Get-Content $testCsv ) [0 ] | Should - BeExactly " #TYPE System.String"
72+ $results [0 ] | Should - BeExactly " #TYPE System.String"
6873 }
6974
7075 It " Does not support -IncludeTypeInformation and -NoTypeInformation at the same time" {
71- { $testObject | Export-Csv - Path $testCsv - IncludeTypeInformation - NoTypeInformation } |
72- ShouldBeErrorId " CannotSpecifyIncludeTypeInformationAndNoTypeInformation,Microsoft.PowerShell.Commands.ExportCsvCommand"
76+ { $testObject | Export-Csv - Path $testCsv - IncludeTypeInformation - NoTypeInformation } | Should - Throw - ErrorId " CannotSpecifyIncludeTypeInformationAndNoTypeInformation,Microsoft.PowerShell.Commands.ExportCsvCommand"
7377 }
74- }
7578
76- Describe " Export-Csv DRT Unit Tests" - Tags " CI" {
77- BeforeAll {
78- $filePath = Join-Path $TestDrive - ChildPath " test.csv"
79- $newLine = [environment ]::NewLine
79+ It " Should support -LiteralPath parameter" {
80+ $testObject | Export-Csv - LiteralPath $testCsv
81+ $results = Import-Csv - Path $testCsv
82+
83+ $results | Should - HaveCount 3
84+ }
85+
86+ It " Should overwrite file without -NoClobber parameter" {
87+ $P1 | Export-Csv - Path $testCsv
88+ $P2 | Export-Csv - Path $testCsv
89+ $results = Import-Csv - Path $testCsv
90+
91+ $results.P2 | Should - BeExactly " second"
92+ }
93+
94+ It " Should not overwrite file with -NoClobber parameter" {
95+ $P1 | Export-Csv - Path $testCsv
96+ { $P2 | Export-Csv - Path $testCsv - NoClobber} | Should - Throw - ErrorId " NoClobber,Microsoft.PowerShell.Commands.ExportCsvCommand"
97+ $results = Import-Csv - Path $testCsv
98+
99+ $results.P1 | Should - BeExactly " first"
100+ }
101+
102+ It " Should not overwrite read-only file without -Force parameter" {
103+ $P1 | Export-Csv - Path $testCsv
104+ Set-ItemProperty - Path $testCsv - Name IsReadOnly - Value $true
105+
106+ { $P2 | Export-Csv - Path $testCsv } | Should - Throw - ErrorId " FileOpenFailure,Microsoft.PowerShell.Commands.ExportCsvCommand"
107+ $results = Import-Csv - Path $testCsv
108+
109+ $results.P1 | Should - BeExactly " first"
110+ }
111+
112+ It " Should overwrite read-only file with -Force parameter" {
113+ $P1 | Export-Csv - Path $testCsv
114+ Set-ItemProperty - Path $testCsv - Name IsReadOnly - Value $true
115+
116+ $P2 | Export-Csv - Path $testCsv - Force
117+ $results = Import-Csv - Path $testCsv
118+
119+ $results.P2 | Should - BeExactly " second"
120+ }
121+
122+ It " Should not export to file if -WhatIf parameter specified" {
123+ $P1 | Export-Csv - Path $testCsv - WhatIf
124+ $testCsv | Should -Not - Exist
125+ }
126+
127+ It " Should append to file if -Append parameter specified" {
128+ $P1 | Export-Csv - Path $testCsv
129+ $P11 | Export-Csv - Path $testCsv - Append
130+ $results = Import-Csv - Path $testCsv
131+
132+ $results [0 ].P1 | Should - BeExactly " first"
133+ $results [1 ].P1 | Should - BeExactly " eleventh"
134+ }
135+
136+ # This test is not a duplicate of the previous one, since it covers a separate branch in code.
137+ It " Should append to empty file if -Append parameter specified" {
138+ New-Item - Path $testCsv - ItemType File | Out-Null
139+
140+ $P11 | Export-Csv - Path $testCsv - Append
141+ $results = Import-Csv - Path $testCsv
142+
143+ $results [0 ].P1 | Should - BeExactly " eleventh"
144+ }
145+
146+ It " Should throw when appended property does not exist in existing .csv file" {
147+ $P1 | Export-Csv - Path $testCsv
148+ { $P2 | Export-Csv - Path $testCsv - Append - ErrorAction Stop } | Should - Throw - ErrorId " CannotAppendCsvWithMismatchedPropertyNames,Microsoft.PowerShell.Commands.ExportCsvCommand"
149+ $results = Import-Csv - Path $testCsv
150+
151+ $results [0 ].P1 | Should - BeExactly " first"
152+ }
153+
154+ It " Should append existing properties, add missing properties with empty value, and skip extra properties" {
155+ $object1 = [PSCustomObject ]@ {first = 1 ; second = 2 }
156+ $object2 = [PSCustomObject ]@ {first = 11 ; third = 13 }
157+
158+ $object1 | Export-Csv - Path $testCsv
159+ $object2 | Export-Csv - Path $testCsv - Append - Force
160+
161+ $results = Import-Csv - Path $testCsv
162+
163+ $results [0 ].first | Should - BeExactly " 1"
164+ $results [0 ].second | Should - BeExactly " 2"
165+ $results [1 ].first | Should - BeExactly " 11"
166+ $results [1 ].second | Should - BeNullOrEmpty
167+ $results [1 ].PSObject.properties.Name | Should -Not - Contain ' third'
168+ }
169+
170+ It " First line should be #TYPE if -IncludeTypeInformation used and pstypenames object property is empty" {
171+ $object = [PSCustomObject ]@ {first = 1 }
172+ $pstypenames = $object.pstypenames | ForEach-Object - Process {$_ }
173+ $pstypenames | ForEach-Object - Process {$object.pstypenames.Remove ($_ )}
174+ $object | Export-Csv - Path $testCsv - IncludeTypeInformation
175+ $content = Get-Content - Path $testCsv
176+
177+ $content [0 ] | Should - BeExactly ' #TYPE'
178+ }
179+
180+ # If type starts with CSV: Export-CSV should remove it. This would happen when you export
181+ # an imported object. Import-Csv adds CSV: prefix to the type.
182+ It " Should remove 'CSV:' from the type name" {
183+ $object = [PSCustomObject ]@ {first = 1 }
184+ $object.pstypenames.Insert (0 , " CSV:TheType" )
185+ $object | Export-Csv - Path $testCsv - IncludeTypeInformation
186+ $result = Get-Content - Path $testCsv
187+
188+ $result [0 ] | Should - BeExactly " #TYPE TheType"
189+ }
190+
191+ It " Should escape double quote with another double quote" {
192+ $object = [PSCustomObject ]@ {first = ' Double quote " in the middle.' }
193+ $object | Export-Csv - Path $testCsv
194+ $result = Get-Content - Path $testCsv
195+
196+ $result [1 ] | Should - BeExactly ' "Double quote "" in the middle."'
80197 }
81198
82199 It " Test basic function works well" {
83- $input = [pscustomobject ]@ { " P1" = " V11" ; " P2" = " V12" ; " P3" = " V13" }
84- $input | Export-Csv - Path $filePath - NoTypeInformation
85- $results = Import-Csv $filePath
200+ $in = [pscustomobject ]@ { " P1" = " V11" ; " P2" = " V12" ; " P3" = " V13" }
201+ $in | Export-Csv - Path $testCsv - NoTypeInformation
202+ $results = Import-Csv - Path $testCsv
203+
86204 $results.P1 | Should - BeExactly " V11"
87205 $results.P2 | Should - BeExactly " V12"
88206 $results.P3 | Should - BeExactly " V13"
89207 }
90208
91209 It " Test if it works with special character" {
92210 $v3 = " abc" + $newLine + " foo"
93- $input = [pscustomobject ]@ { " P1" = " " ; " P2" = " abc,foo" ; " P3" = $v3 }
94- $input | Export-Csv - Path $filePath - NoTypeInformation
95- $results = Import-Csv $filePath
211+ $in = [pscustomobject ]@ { " P1" = " " ; " P2" = " abc,foo" ; " P3" = $v3 }
212+ $in | Export-Csv - Path $testCsv - NoTypeInformation
213+ $results = Import-Csv - Path $testCsv
214+
96215 $results.P1 | Should - BeExactly " "
97216 $results.P2 | Should - BeExactly " abc,foo"
98- $results.P3 | Should - Be $v3
99- }
100-
101- It " Test force switch works well" {
102- $input = [pscustomobject ]@ { " P1" = " first" }
103- $input | Export-Csv - Path $filePath
104-
105- $input = [pscustomobject ]@ { " P2" = " second" }
106- $input | Export-Csv - Path $filePath - Force
107- $results = Import-Csv $filePath
108-
109- $results.P2 | Should - BeExactly " second"
110- $property = $results | Get-Member | Where-Object { $_.MemberType -eq " NoteProperty" } | ForEach-Object { $_.Name }
111- $property | Should -Not - Be P1
217+ $results.P3 | Should - BeExactly $v3
112218 }
113219
114220 It " Test export-csv with a useculture flag" {
115- $outputFilesDir = Join-Path $TestDrive - ChildPath " Monad"
116- $fileToGenerate = Join-Path $outputFilesDir - ChildPath " CSVTests.csv"
221+ $outputFilesDir = Join-Path - Path $TestDrive - ChildPath " Monad"
222+ $fileToGenerate = Join-Path - Path $outputFilesDir - ChildPath " CSVTests.csv"
117223 $delimiter = (Get-Culture ).TextInfo.ListSeparator
118224 New-Item - Path $outputFilesDir - ItemType Directory - Force
119- Get-Item - Path $outputFilesDir | Export-Csv - Path $fileToGenerate - UseCulture - NoTypeInformation
225+ Get-Item - Path $outputFilesDir | Export-Csv - Path $fileToGenerate - UseCulture - NoTypeInformation
120226 $contents = Get-Content - Path $fileToGenerate
227+
121228 $contents.Count | Should - Be 2
122229 $contents [0 ].Contains($delimiter ) | Should - BeTrue
123230 $contents [1 ].Contains($delimiter ) | Should - BeTrue
0 commit comments