Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ public abstract class BaseCsvWritingCommand : PSCmdlet
/// <summary>
/// Property that sets delimiter.
/// </summary>
[Parameter(Position = 1, ParameterSetName = "Delimiter")]
[Parameter(Position = 1)]
[ValidateNotNull]
public char Delimiter { get; set; }
Comment on lines +31 to 33

/// <summary>
/// Culture switch for csv conversion
/// </summary>
[Parameter(ParameterSetName = "UseCulture")]
[Parameter]
public SwitchParameter UseCulture { get; set; }

/// <summary>
Expand Down Expand Up @@ -122,6 +122,13 @@ protected override void BeginProcessing()
this.ThrowTerminatingError(errorRecord);
}

if (this.MyInvocation.BoundParameters.ContainsKey(nameof(Delimiter)) && this.MyInvocation.BoundParameters.ContainsKey(nameof(UseCulture)))
{
InvalidOperationException exception = new(CsvCommandStrings.CannotSpecifyDelimiterAndUseCulture);
ErrorRecord errorRecord = new(exception, "CannotSpecifyDelimiterAndUseCulture", ErrorCategory.InvalidData, null);
this.ThrowTerminatingError(errorRecord);
}
Comment on lines +125 to +130

Delimiter = ImportExportCSVHelper.SetDelimiter(this, ParameterSetName, Delimiter, UseCulture);
}
}
Expand All @@ -132,7 +139,7 @@ protected override void BeginProcessing()
/// <summary>
/// Implementation for the Export-Csv command.
/// </summary>
[Cmdlet(VerbsData.Export, "Csv", SupportsShouldProcess = true, DefaultParameterSetName = "Delimiter", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096608")]
[Cmdlet(VerbsData.Export, "Csv", SupportsShouldProcess = true, DefaultParameterSetName = "Path", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096608")]
public sealed class ExportCsvCommand : BaseCsvWritingCommand, IDisposable
{
#region Command Line Parameters
Expand All @@ -149,7 +156,7 @@ public sealed class ExportCsvCommand : BaseCsvWritingCommand, IDisposable
/// <summary>
/// Mandatory file name to write to.
/// </summary>
[Parameter(Position = 0)]
[Parameter(Position = 0, Mandatory = true, ParameterSetName = "Path")]
[ValidateNotNullOrEmpty]
public string Path
{
Expand All @@ -161,17 +168,15 @@ public string Path
set
{
_path = value;
_specifiedPath = true;
}
}

private string _path;
private bool _specifiedPath = false;

/// <summary>
/// The literal path of the mandatory file name to write to.
/// </summary>
[Parameter]
[Parameter(Mandatory = true, ParameterSetName = "LiteralPath")]
[ValidateNotNullOrEmpty]
[Alias("PSPath", "LP")]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
Expand Down Expand Up @@ -251,15 +256,6 @@ public Encoding Encoding
protected override void BeginProcessing()
{
base.BeginProcessing();

// Validate that they don't provide both Path and LiteralPath, but have provided at least one.
if (!(_specifiedPath ^ _isLiteralPath))
{
InvalidOperationException exception = new(CsvCommandStrings.CannotSpecifyPathAndLiteralPath);
ErrorRecord errorRecord = new(exception, "CannotSpecifyPathAndLiteralPath", ErrorCategory.InvalidData, null);
this.ThrowTerminatingError(errorRecord);
}

// Validate that Append and NoHeader are not specified together.
if (Append && NoHeader)
{
Expand Down Expand Up @@ -349,11 +345,6 @@ protected override void EndProcessing()

private void CreateFileStream()
{
if (_path == null)
{
throw new InvalidOperationException(CsvCommandStrings.FileNameIsAMandatoryParameter);
}

string resolvedFilePath = PathUtils.ResolveFilePath(this.Path, this, _isLiteralPath);

bool isCsvFileEmpty = true;
Expand Down Expand Up @@ -1770,40 +1761,17 @@ internal static class ImportExportCSVHelper

internal static char SetDelimiter(PSCmdlet cmdlet, string parameterSetName, char explicitDelimiter, bool useCulture)
{
char delimiter = explicitDelimiter;
switch (parameterSetName)
// UseCulture takes priority; its mutual exclusion with -Delimiter is
// enforced in BaseCsvWritingCommand.BeginProcessing() before this call.
if (useCulture)
{
case "Delimiter":
case "DelimiterPath":
case "DelimiterLiteralPath":

// if delimiter is not given, it should take , as value
if (explicitDelimiter == '\0')
{
delimiter = ImportExportCSVHelper.CSVDelimiter;
}

break;
case "UseCulture":
case "CulturePath":
case "CultureLiteralPath":
if (useCulture)
{
// ListSeparator is apparently always a character even though the property returns a string, checked via:
// [CultureInfo]::GetCultures("AllCultures") | % { ([CultureInfo]($_.Name)).TextInfo.ListSeparator } | ? Length -ne 1
delimiter = CultureInfo.CurrentCulture.TextInfo.ListSeparator[0];
}

break;
default:
{
delimiter = ImportExportCSVHelper.CSVDelimiter;
}

break;
// ListSeparator is apparently always a character even though the property returns a string, checked via:
// [CultureInfo]::GetCultures("AllCultures") | % { ([CultureInfo]($_.Name)).TextInfo.ListSeparator } | ? Length -ne 1
return CultureInfo.CurrentCulture.TextInfo.ListSeparator[0];
}

return delimiter;
// If no explicit delimiter was supplied, default to comma.
return explicitDelimiter == '\0' ? CSVDelimiter : explicitDelimiter;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,7 @@
<data name="CannotSpecifyAppendAndNoHeader" xml:space="preserve">
<value>You must specify either the -Append or -NoHeader parameters, but not both.</value>
</data>
<data name="CannotSpecifyDelimiterAndUseCulture" xml:space="preserve">
<value>You must specify either the -Delimiter or -UseCulture parameters, but not both.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,62 @@ Describe "Export-Csv" -Tags "CI" {
}

It "Should throw if an output file isn't specified" {
{ $testObject | Export-Csv -ErrorAction Stop } | Should -Throw -ErrorId "CannotSpecifyPathAndLiteralPath,Microsoft.PowerShell.Commands.ExportCsvCommand"
{ $testObject | Export-Csv -ErrorAction Stop } | Should -Throw
}
Comment on lines 25 to +27

It "Should throw if both Path and LiteralPath are specified" {
{ $testObject | Export-Csv -Path $testCsv -LiteralPath $testCsv -ErrorAction Stop } | Should -Throw -ErrorId "AmbiguousParameterSet,Microsoft.PowerShell.Commands.ExportCsvCommand"
}

It "Should support -LiteralPath parameter with -Delimiter" {
$testObject | Export-Csv -LiteralPath $testCsv -Delimiter ';'
$results = Import-Csv -Path $testCsv -Delimiter ';'

$results | Should -HaveCount 3
}

It "Should support -LiteralPath parameter with -UseCulture" {
$testObject | Export-Csv -LiteralPath $testCsv -UseCulture
$results = Import-Csv -Path $testCsv -UseCulture

$results | Should -HaveCount 3
}

It "Should support -LiteralPath parameter with literal brackets" {
$bracketCsv = Join-Path -Path $TestDrive -ChildPath "[output].csv"
try {
$testObject | Export-Csv -LiteralPath $bracketCsv
Test-Path -LiteralPath $bracketCsv | Should -BeTrue
$results = Import-Csv -LiteralPath $bracketCsv
$results | Should -HaveCount 3
} finally {
Remove-Item -LiteralPath $bracketCsv -Force -ErrorAction SilentlyContinue
}
}

It "Should have correct parameter set metadata" {
$cmd = Get-Command Export-Csv

$pathParam = $cmd.Parameters['Path']
$pathSets = $pathParam.Attributes.Where({ $_.TypeId.Name -eq 'ParameterAttribute' -and $_.Mandatory }).ParameterSetName
$pathSets | Should -Contain 'Path'
$pathSets.Count | Should -Be 1

$literalPathParam = $cmd.Parameters['LiteralPath']
$literalPathSets = $literalPathParam.Attributes.Where({ $_.TypeId.Name -eq 'ParameterAttribute' -and $_.Mandatory }).ParameterSetName
$literalPathSets | Should -Contain 'LiteralPath'
$literalPathSets.Count | Should -Be 1
}

It "Should support -LiteralPath parameter alone" {
$testObject | Export-Csv -LiteralPath $testCsv
$results = Import-Csv -Path $testCsv
$results | Should -HaveCount 3
}

It "Should throw if -Delimiter and -UseCulture are both specified" {
{ $testObject | Export-Csv -Path $testCsv -Delimiter ';' -UseCulture -ErrorAction Stop } |
Should -Throw -ErrorId "CannotSpecifyDelimiterAndUseCulture,Microsoft.PowerShell.Commands.ExportCsvCommand"
}

It "Should be a string when exporting via pipe" {
Expand Down