mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Add UseQuotes parameter (#8951)
This commit is contained in:
@@ -59,8 +59,36 @@ namespace Microsoft.PowerShell.Commands
|
||||
[Alias("NTI")]
|
||||
public SwitchParameter NoTypeInformation { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets option to use or suppress quotes in output.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[Alias("UQ")]
|
||||
public QuoteKind UseQuotes { get; set; } = QuoteKind.Always;
|
||||
|
||||
#endregion Command Line Parameters
|
||||
|
||||
/// <summary>
|
||||
/// Kind of output quoting.
|
||||
/// </summary>
|
||||
public enum QuoteKind
|
||||
{
|
||||
/// <summary>
|
||||
/// Never quote output.
|
||||
/// </summary>
|
||||
Never,
|
||||
|
||||
/// <summary>
|
||||
/// Always quote output.
|
||||
/// </summary>
|
||||
Always,
|
||||
|
||||
/// <summary>
|
||||
/// Quote output as needed (a field contains used delimiter).
|
||||
/// </summary>
|
||||
AsNeeded
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the string to a file or pipeline.
|
||||
/// </summary>
|
||||
@@ -217,7 +245,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
CreateFileStream();
|
||||
|
||||
_helper = new ExportCsvHelper(this, base.Delimiter);
|
||||
_helper = new ExportCsvHelper(base.Delimiter, base.UseQuotes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -644,7 +672,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
protected override void BeginProcessing()
|
||||
{
|
||||
base.BeginProcessing();
|
||||
_helper = new ExportCsvHelper(this, base.Delimiter);
|
||||
_helper = new ExportCsvHelper(base.Delimiter, base.UseQuotes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -806,28 +834,24 @@ namespace Microsoft.PowerShell.Commands
|
||||
#region ExportHelperConversion
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for Export-Csvlper.
|
||||
/// Helper class for Export-Csv and ConvertTo-Csv.
|
||||
/// </summary>
|
||||
internal class ExportCsvHelper : IDisposable
|
||||
{
|
||||
private PSCmdlet _cmdlet;
|
||||
|
||||
private char _delimiter;
|
||||
readonly private BaseCsvWritingCommand.QuoteKind _quoteKind;
|
||||
readonly private StringBuilder _outputString;
|
||||
|
||||
/// <summary>
|
||||
/// Create ExportCsvHelper instance.
|
||||
/// </summary>
|
||||
/// <param name="cmdlet"></param>
|
||||
/// <param name="delimiter"></param>
|
||||
/// <exception cref="ArgumentNullException">Throw if cmdlet is null.</exception>
|
||||
internal ExportCsvHelper(PSCmdlet cmdlet, char delimiter)
|
||||
/// <param name="delimiter">Delimiter char.</param>
|
||||
/// <param name="quoteKind">Kind of quoting.</param>
|
||||
internal ExportCsvHelper(char delimiter, BaseCsvWritingCommand.QuoteKind quoteKind)
|
||||
{
|
||||
if (cmdlet == null)
|
||||
{
|
||||
throw new ArgumentNullException("cmdlet");
|
||||
}
|
||||
|
||||
_cmdlet = cmdlet;
|
||||
_delimiter = delimiter;
|
||||
_quoteKind = quoteKind;
|
||||
_outputString = new StringBuilder(128);
|
||||
}
|
||||
|
||||
// Name of properties to be written in CSV format
|
||||
@@ -865,11 +889,12 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
if (propertyNames == null)
|
||||
{
|
||||
throw new ArgumentNullException("propertyNames");
|
||||
throw new ArgumentNullException(nameof(propertyNames));
|
||||
}
|
||||
|
||||
StringBuilder dest = new StringBuilder();
|
||||
_outputString.Clear();
|
||||
bool first = true;
|
||||
|
||||
foreach (string propertyName in propertyNames)
|
||||
{
|
||||
if (first)
|
||||
@@ -878,14 +903,32 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
else
|
||||
{
|
||||
// changed to delimiter
|
||||
dest.Append(_delimiter);
|
||||
_outputString.Append(_delimiter);
|
||||
}
|
||||
|
||||
EscapeAndAppendString(dest, propertyName);
|
||||
switch (_quoteKind)
|
||||
{
|
||||
case BaseCsvWritingCommand.QuoteKind.Always:
|
||||
AppendStringWithEscapeAlways(_outputString, propertyName);
|
||||
break;
|
||||
case BaseCsvWritingCommand.QuoteKind.AsNeeded:
|
||||
if (propertyName.Contains(_delimiter))
|
||||
{
|
||||
AppendStringWithEscapeAlways(_outputString, propertyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_outputString.Append(propertyName);
|
||||
}
|
||||
|
||||
break;
|
||||
case BaseCsvWritingCommand.QuoteKind.Never:
|
||||
_outputString.Append(propertyName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return dest.ToString();
|
||||
return _outputString.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -898,10 +941,10 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
if (propertyNames == null)
|
||||
{
|
||||
throw new ArgumentNullException("propertyNames");
|
||||
throw new ArgumentNullException(nameof(propertyNames));
|
||||
}
|
||||
|
||||
StringBuilder dest = new StringBuilder();
|
||||
_outputString.Clear();
|
||||
bool first = true;
|
||||
|
||||
foreach (string propertyName in propertyNames)
|
||||
@@ -912,21 +955,41 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
else
|
||||
{
|
||||
dest.Append(_delimiter);
|
||||
_outputString.Append(_delimiter);
|
||||
}
|
||||
|
||||
PSPropertyInfo property = mshObject.Properties[propertyName] as PSPropertyInfo;
|
||||
string value = null;
|
||||
// If property is not present, assume value is null
|
||||
if (property != null)
|
||||
// If property is not present, assume value is null and skip it.
|
||||
if (mshObject.Properties[propertyName] is PSPropertyInfo property)
|
||||
{
|
||||
value = GetToStringValueForProperty(property);
|
||||
}
|
||||
var value = GetToStringValueForProperty(property);
|
||||
|
||||
EscapeAndAppendString(dest, value);
|
||||
switch (_quoteKind)
|
||||
{
|
||||
case BaseCsvWritingCommand.QuoteKind.Always:
|
||||
AppendStringWithEscapeAlways(_outputString, value);
|
||||
break;
|
||||
case BaseCsvWritingCommand.QuoteKind.AsNeeded:
|
||||
if (value.Contains(_delimiter))
|
||||
{
|
||||
AppendStringWithEscapeAlways(_outputString, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_outputString.Append(value);
|
||||
}
|
||||
|
||||
break;
|
||||
case BaseCsvWritingCommand.QuoteKind.Never:
|
||||
_outputString.Append(value);
|
||||
break;
|
||||
default:
|
||||
Diagnostics.Assert(false, "BaseCsvWritingCommand.QuoteKind has new item.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dest.ToString();
|
||||
return _outputString.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -938,7 +1001,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
if (property == null)
|
||||
{
|
||||
throw new ArgumentNullException("property");
|
||||
throw new ArgumentNullException(nameof(property));
|
||||
}
|
||||
|
||||
string value = null;
|
||||
@@ -999,12 +1062,13 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// Escapes the " in string if necessary.
|
||||
/// Encloses the string in double quotes if necessary.
|
||||
/// </summary>
|
||||
internal static void EscapeAndAppendString(StringBuilder dest, string source)
|
||||
internal static void AppendStringWithEscapeAlways(StringBuilder dest, string source)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Adding Double quote to all strings
|
||||
dest.Append('"');
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
@@ -1102,12 +1166,12 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
if (cmdlet == null)
|
||||
{
|
||||
throw new ArgumentNullException("cmdlet");
|
||||
throw new ArgumentNullException(nameof(cmdlet));
|
||||
}
|
||||
|
||||
if (streamReader == null)
|
||||
{
|
||||
throw new ArgumentNullException("streamReader");
|
||||
throw new ArgumentNullException(nameof(streamReader));
|
||||
}
|
||||
|
||||
_cmdlet = cmdlet;
|
||||
|
||||
@@ -19,16 +19,15 @@ Describe "ConvertTo-Csv DRT Unit Tests" -Tags "CI" {
|
||||
}
|
||||
|
||||
It "Test convertto-csv with a useculture flag" {
|
||||
#The default value is ','
|
||||
$delimiter = [CultureInfo]::CurrentCulture.TextInfo.ListSeparator
|
||||
$returnObject = $inputObject | ConvertTo-Csv -UseCulture -IncludeTypeInformation
|
||||
$returnObject.Count | Should -Be 3
|
||||
$returnObject[0] | Should -BeExactly "#TYPE System.Management.Automation.PSCustomObject"
|
||||
$returnObject[1] | Should -BeExactly "`"First`",`"Second`""
|
||||
$returnObject[2] | Should -BeExactly "`"1`",`"2`""
|
||||
$returnObject[1] | Should -BeExactly "`"First`"$($delimiter)`"Second`""
|
||||
$returnObject[2] | Should -BeExactly "`"1`"$($delimiter)`"2`""
|
||||
}
|
||||
|
||||
It "Test convertto-csv with Delimiter" {
|
||||
#The default value is ','
|
||||
$returnObject = $inputObject | ConvertTo-Csv -Delimiter ";" -IncludeTypeInformation
|
||||
$returnObject.Count | Should -Be 3
|
||||
$returnObject[0] | Should -BeExactly "#TYPE System.Management.Automation.PSCustomObject"
|
||||
@@ -38,11 +37,13 @@ Describe "ConvertTo-Csv DRT Unit Tests" -Tags "CI" {
|
||||
}
|
||||
|
||||
Describe "ConvertTo-Csv" -Tags "CI" {
|
||||
$Name = "Hello"; $Data = "World";
|
||||
$testObject = New-Object psobject -Property @{ FirstColumn = $Name; SecondColumn = $Data }
|
||||
BeforeAll {
|
||||
$Name = "Hello"; $Data = "World";
|
||||
$testObject = [pscustomobject]@{ FirstColumn = $Name; SecondColumn = $Data }
|
||||
}
|
||||
|
||||
It "Should Be able to be called without error" {
|
||||
{ $testObject | ConvertTo-Csv } | Should -Not -Throw
|
||||
{ $testObject | ConvertTo-Csv } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It "Should output an array of objects" {
|
||||
@@ -51,22 +52,22 @@ Describe "ConvertTo-Csv" -Tags "CI" {
|
||||
}
|
||||
|
||||
It "Should return the type of data in the first element of the output array" {
|
||||
$result = $testObject | ConvertTo-Csv -IncludeTypeInformation
|
||||
$result = $testObject | ConvertTo-Csv -IncludeTypeInformation
|
||||
|
||||
$result[0] | Should -BeExactly "#TYPE System.Management.Automation.PSCustomObject"
|
||||
$result[0] | Should -BeExactly "#TYPE System.Management.Automation.PSCustomObject"
|
||||
}
|
||||
|
||||
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 "`"FirstColumn`""
|
||||
$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 = $testObject | ConvertTo-Csv -IncludeTypeInformation
|
||||
$result[2] | Should -Match "`"Hello`""
|
||||
$result[2] | Should -Match "`"World`""
|
||||
}
|
||||
|
||||
It "Includes type information when -IncludeTypeInformation is supplied" {
|
||||
@@ -76,7 +77,7 @@ Describe "ConvertTo-Csv" -Tags "CI" {
|
||||
}
|
||||
|
||||
It "Does not include type information by default" {
|
||||
$result = $testObject | ConvertTo-Csv
|
||||
$result = $testObject | ConvertTo-Csv
|
||||
|
||||
$result | Should -Not -Match ([regex]::Escape('System.Management.Automation.PSCustomObject'))
|
||||
$result | Should -Not -Match ([regex]::Escape('#TYPE'))
|
||||
@@ -90,8 +91,37 @@ Describe "ConvertTo-Csv" -Tags "CI" {
|
||||
}
|
||||
|
||||
It "Does not support -IncludeTypeInformation and -NoTypeInformation at the same time" {
|
||||
{ $testObject | ConvertTo-Csv -IncludeTypeInformation -NoTypeInformation } |
|
||||
{ $testObject | ConvertTo-Csv -IncludeTypeInformation -NoTypeInformation } |
|
||||
Should -Throw -ErrorId "CannotSpecifyIncludeTypeInformationAndNoTypeInformation,Microsoft.PowerShell.Commands.ConvertToCsvCommand"
|
||||
}
|
||||
|
||||
Context "UseQuotes parameter" {
|
||||
It "UseQuotes Always" {
|
||||
$result = $testObject | ConvertTo-Csv -UseQuotes Always -Delimiter ','
|
||||
|
||||
$result[0] | Should -BeExactly "`"FirstColumn`",`"SecondColumn`""
|
||||
$result[1] | Should -BeExactly "`"Hello`",`"World`""
|
||||
}
|
||||
|
||||
It "UseQuotes Always is default" {
|
||||
$result = $testObject | ConvertTo-Csv -UseQuotes Always -Delimiter ','
|
||||
$result2 = $testObject | ConvertTo-Csv -Delimiter ','
|
||||
|
||||
$result | Should -BeExactly $result2
|
||||
}
|
||||
|
||||
It "UseQuotes Never" {
|
||||
$result = $testObject | ConvertTo-Csv -UseQuotes Never -Delimiter ','
|
||||
|
||||
$result[0] | Should -BeExactly "FirstColumn,SecondColumn"
|
||||
$result[1] | Should -BeExactly "Hello,World"
|
||||
}
|
||||
|
||||
It "UseQuotes AsNeeded" {
|
||||
$result = $testObject | ConvertTo-Csv -UseQuotes AsNeeded -Delimiter 'r'
|
||||
|
||||
$result[0] | Should -BeExactly "`"FirstColumn`"rSecondColumn"
|
||||
$result[1] | Should -BeExactly "Hellor`"World`""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,4 +233,33 @@ Describe "Export-Csv" -Tags "CI" {
|
||||
$contents[0].Contains($delimiter) | Should -BeTrue
|
||||
$contents[1].Contains($delimiter) | Should -BeTrue
|
||||
}
|
||||
|
||||
Context "UseQuotes parameter" {
|
||||
|
||||
# A minimum of tests. The rest are in ConvertTo-Csv.Tests.ps1
|
||||
|
||||
BeforeAll {
|
||||
$Name = "Hello"; $Data = "World";
|
||||
$testOutputObject = [pscustomobject]@{ FirstColumn = $Name; SecondColumn = $Data }
|
||||
$testFile = Join-Path -Path $TestDrive -ChildPath "output.csv"
|
||||
$testFile2 = Join-Path -Path $TestDrive -ChildPath "output2.csv"
|
||||
}
|
||||
|
||||
It "UseQuotes Always" {
|
||||
$testOutputObject | Export-Csv -Path $testFile -UseQuotes Always -Delimiter ','
|
||||
$result = Get-Content -Path $testFile
|
||||
|
||||
$result[0] | Should -BeExactly "`"FirstColumn`",`"SecondColumn`""
|
||||
$result[1] | Should -BeExactly "`"Hello`",`"World`""
|
||||
}
|
||||
|
||||
It "UseQuotes Always is default" {
|
||||
$testOutputObject | Export-Csv -Path $testFile -UseQuotes Always -Delimiter ','
|
||||
$result = Get-Content -Raw -Path $testFile
|
||||
$testOutputObject | Export-Csv -Path $testFile2 -Delimiter ','
|
||||
$result2 = Get-Content -Raw -Path $testFile2
|
||||
|
||||
$result | Should -BeExactly $result2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user