From 9bb222654f73c427c7e52ce6a77da8ca0d36beb1 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 9 Nov 2019 21:44:02 -0500 Subject: [PATCH 1/2] :sparkles: Add IDictionary handling to CSV cmdlets --- .../commands/utility/CsvCommands.cs | 63 +++++++++++++++---- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs index cbd9d4c36b4..3b39e0d2a3f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; @@ -737,14 +738,18 @@ protected override void ProcessRecord() // Write property information string properties = _helper.ConvertPropertyNamesCSV(_propertyNames); if (!properties.Equals(string.Empty)) + { WriteCsvLine(properties); + } } string csv = _helper.ConvertPSObjectToCSV(InputObject, _propertyNames); - // write to the console + // Write to the output stream if (csv != string.Empty) + { WriteCsvLine(csv); + } } #endregion Overrides @@ -908,16 +913,36 @@ internal static IList BuildPropertyNames(PSObject source, IList throw new InvalidOperationException(CsvCommandStrings.BuildPropertyNamesMethodShouldBeCalledOnlyOncePerCmdletInstance); } - // serialize only Extended and Adapted properties.. - PSMemberInfoCollection srcPropertiesToSearch = - new PSMemberInfoIntegratingCollection( + propertyNames = new Collection(); + if (source.BaseObject is IDictionary dictionary) + { + foreach (var key in dictionary.Keys) + { + propertyNames.Add(LanguagePrimitives.ConvertTo(key)); + } + + // Add additional extended members added to the dictionary object, if any + var propertiesToSearch = new PSMemberInfoIntegratingCollection( source, - PSObject.GetPropertyCollection(PSMemberViewTypes.Extended | PSMemberViewTypes.Adapted)); + PSObject.GetPropertyCollection(PSMemberViewTypes.Extended)); - propertyNames = new Collection(); - foreach (PSPropertyInfo prop in srcPropertiesToSearch) + foreach (var prop in propertiesToSearch) + { + propertyNames.Add(prop.Name); + } + } + else { - propertyNames.Add(prop.Name); + // serialize only Extended and Adapted properties.. + PSMemberInfoCollection srcPropertiesToSearch = + new PSMemberInfoIntegratingCollection( + source, + PSObject.GetPropertyCollection(PSMemberViewTypes.Extended | PSMemberViewTypes.Adapted)); + + foreach (PSPropertyInfo prop in srcPropertiesToSearch) + { + propertyNames.Add(prop.Name); + } } return propertyNames; @@ -1014,10 +1039,26 @@ internal string ConvertPSObjectToCSV(PSObject mshObject, IList propertyN _outputString.Append(_delimiter); } - // If property is not present, assume value is null and skip it. - if (mshObject.Properties[propertyName] is PSPropertyInfo property) + string value = null; + if (mshObject.BaseObject is IDictionary dictionary) + { + if (dictionary.Contains(propertyName)) + { + value = dictionary[propertyName].ToString(); + } + else if (mshObject.Properties[propertyName] is PSPropertyInfo property) + { + value = GetToStringValueForProperty(property); + } + } + else if (mshObject.Properties[propertyName] is PSPropertyInfo property) + { + value = GetToStringValueForProperty(property); + } + + // If value is null, assume property is not present and skip it. + if (value != null) { - var value = GetToStringValueForProperty(property); if (_quoteFields != null) { From 2397ca0d4de7d6998544a8e552fdd8ad69ea7787 Mon Sep 17 00:00:00 2001 From: vexx32 <32407840+vexx32@users.noreply.github.com> Date: Sat, 9 Nov 2019 22:24:37 -0500 Subject: [PATCH 2/2] :white_check_mark: Add Tests - Add tests for ConvertTo-Csv Update src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs Co-Authored-By: Ilya Update test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 --- .../commands/utility/CsvCommands.cs | 3 +- .../ConvertTo-Csv.Tests.ps1 | 42 ++++++++++++++++--- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs index 3b39e0d2a3f..96659dcf7e5 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs @@ -933,7 +933,7 @@ internal static IList BuildPropertyNames(PSObject source, IList } else { - // serialize only Extended and Adapted properties.. + // serialize only Extended and Adapted properties. PSMemberInfoCollection srcPropertiesToSearch = new PSMemberInfoIntegratingCollection( source, @@ -1059,7 +1059,6 @@ internal string ConvertPSObjectToCSV(PSObject mshObject, IList propertyN // If value is null, assume property is not present and skip it. if (value != null) { - if (_quoteFields != null) { if (_quoteFields.TryGetValue(propertyName, out _)) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 index 494051507f7..6b744b8eb88 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 @@ -49,7 +49,7 @@ Describe "ConvertTo-Csv" -Tags "CI" { It "Should output an array of objects" { $result = $testObject | ConvertTo-Csv - ,$result | Should -BeOfType System.Array + $result.GetType() | Should -Be ([object[]]) } It "Should return the type of data in the first element of the output array" { @@ -59,16 +59,16 @@ Describe "ConvertTo-Csv" -Tags "CI" { } It "Should return the column info in the second element of the output array" { - $result = $testObject | ConvertTo-Csv -IncludeTypeInformation + $result = $testObject | ConvertTo-Csv -IncludeTypeInformation $result[1] | Should -Match "`"FirstColumn`"" - $result[1] | Should -Match "`"SecondColumn`"" + $result[1] | Should -Match "`"SecondColumn`"" } It "Should return the data as a comma-separated list in the third element of the output array" { $result = $testObject | ConvertTo-Csv -IncludeTypeInformation $result[2] | Should -Match "`"Hello`"" - $result[2] | Should -Match "`"World`"" + $result[2] | Should -Match "`"World`"" } It "Includes type information when -IncludeTypeInformation is supplied" { @@ -109,7 +109,7 @@ Describe "ConvertTo-Csv" -Tags "CI" { $result[0] | Should -BeExactly "`"FirstColumn`",SecondColumn" $result[1] | Should -BeExactly "`"Hello`",World" - $result = $testObject | ConvertTo-Csv -QuoteFields FiRstCoLumn,SeCondCoLumn -Delimiter ',' + $result = $testObject | ConvertTo-Csv -QuoteFields FiRstCoLumn, SeCondCoLumn -Delimiter ',' $result[0] | Should -BeExactly "`"FirstColumn`",`"SecondColumn`"" $result[1] | Should -BeExactly "`"Hello`",`"World`"" @@ -160,4 +160,36 @@ Describe "ConvertTo-Csv" -Tags "CI" { $result[1] | Should -BeExactly "Hellor" } } + + Context 'Converting IDictionary Objects' { + BeforeAll { + $Letters = 'A', 'B', 'C', 'D', 'E', 'F' + $Items = 0..5 | ForEach-Object { + [ordered]@{ Number = $_; Letter = $Letters[$_] } + } + $CsvString = $Items | ConvertTo-Csv + } + + It 'should treat dictionary entries as properties' { + $CsvString[0] | Should -MatchExactly ($Items[0].Keys -join '","') + + for ($i = 0; $i -lt $Items.Count; $i++) { + # Index in the CSV strings will be +1 due to header line + $ValuesPattern = $Items[$i].Values -join '","' + $CsvString[$i + 1] | Should -MatchExactly $ValuesPattern + } + } + + It 'should ignore regular object properties' { + $PropertyPattern = $Items[0].PSObject.Properties.Name -join '|' + $CsvString[0] | Should -Not -Match $PropertyPattern + } + + It 'should account for extended properties added deliberately' { + $Items | Add-Member -MemberType NoteProperty -Name 'Extra' -Value 'Surprise!' + $NewCsvString = $Items | ConvertTo-Csv + $NewCsvString[0] | Should -MatchExactly 'Extra' + $NewCsvString | Select-Object -Skip 1 | Should -MatchExactly 'Surprise!' + } + } }