diff --git a/.github/workflows/analyze.yaml b/.github/workflows/analyze.yaml
index 7f16f10b6d..b5c607a05c 100644
--- a/.github/workflows/analyze.yaml
+++ b/.github/workflows/analyze.yaml
@@ -39,6 +39,8 @@ jobs:
prerelease: true
outputFormat: Sarif
outputPath: reports/ps-rule-results.sarif
+ env:
+ PSRULE_INPUT_FILEOBJECTS: true
- name: Upload results to security tab
uses: github/codeql-action/upload-sarif@v3
diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md
index 483ff39f8f..b7fe0f226e 100644
--- a/docs/CHANGELOG-v3.md
+++ b/docs/CHANGELOG-v3.md
@@ -43,6 +43,8 @@ What's changed since pre-release v3.0.0-B0198:
- Bug fixes:
- Fixed reason reported for `startsWith` by @BernieWhite.
[#1818](https://github.com/microsoft/PSRule/issues/1818)
+ - Fixes CSV output of multiple lines by @BernieWhite.
+ [#1627](https://github.com/microsoft/PSRule/issues/1627)
## v3.0.0-B0198 (pre-release)
diff --git a/pipeline.build.ps1 b/pipeline.build.ps1
index bac7c112ef..9ec61e5b85 100644
--- a/pipeline.build.ps1
+++ b/pipeline.build.ps1
@@ -322,7 +322,7 @@ task Rules {
As = 'Summary'
}
Import-Module (Join-Path -Path $PWD -ChildPath out/modules/PSRule) -Force;
- Assert-PSRule @assertParams -OutputPath reports/ps-rule-file.xml -InputPath $PWD -ErrorAction Stop;
+ Assert-PSRule @assertParams -OutputPath reports/ps-rule-file.xml -InputPath $PWD -ErrorAction Stop -Option @{ 'Input.FileObjects' = $True };
}
task Benchmark {
diff --git a/schemas/PSRule-options.schema.json b/schemas/PSRule-options.schema.json
index d7ad1d4d69..953957e58e 100644
--- a/schemas/PSRule-options.schema.json
+++ b/schemas/PSRule-options.schema.json
@@ -553,7 +553,7 @@
"type": "boolean",
"title": "File objects",
"description": "Determines if file objects are processed by rules.",
- "markdownDescription": "Determines if file objects are processed by rules. [See help](https://microsoft.github.io/PSRule/v3/concepts/PSRule/en-US/about_PSRule_Options/#fileobjects)",
+ "markdownDescription": "Determines if file objects are processed by rules. [See help](https://microsoft.github.io/PSRule/v3/concepts/PSRule/en-US/about_PSRule_Options/#inputfileobjects)",
"default": false
},
"format": {
diff --git a/src/PSRule/Pipeline/Output/CsvOutputWriter.cs b/src/PSRule/Pipeline/Output/CsvOutputWriter.cs
index d3ac2dfad5..c629dbc003 100644
--- a/src/PSRule/Pipeline/Output/CsvOutputWriter.cs
+++ b/src/PSRule/Pipeline/Output/CsvOutputWriter.cs
@@ -25,7 +25,7 @@ internal CsvOutputWriter(PipelineWriter inner, PSRuleOption option, ShouldProces
protected override string Serialize(object[] o)
{
WriteHeader();
- if (Option.Output.As == ResultFormat.Detail)
+ if (Option.Output.As.GetValueOrDefault(OutputOption.Default.As.Value) == ResultFormat.Detail)
WriteDetail(o);
else
WriteSummary(o);
@@ -127,7 +127,11 @@ private void WriteColumn(string value)
return;
_Builder.Append(QUOTE);
- _Builder.Append(value.Replace("\"", "\"\""));
+ _Builder.Append(value
+ .Replace("\"", "\"\"").Replace("\r\n", " ")
+ .Replace('\r', ' ').Replace('\n', ' ')
+ .Replace(" ", " ")
+ );
_Builder.Append(QUOTE);
}
@@ -136,8 +140,6 @@ private void WriteColumn(InfoString value)
if (!value.HasValue)
return;
- _Builder.Append(QUOTE);
- _Builder.Append(value.Text.Replace("\"", "\"\""));
- _Builder.Append(QUOTE);
+ WriteColumn(value.Text);
}
}
diff --git a/tests/PSRule.Tests/OutputWriterTests.cs b/tests/PSRule.Tests/OutputWriterTests.cs
index db1c57b0b5..8284845478 100644
--- a/tests/PSRule.Tests/OutputWriterTests.cs
+++ b/tests/PSRule.Tests/OutputWriterTests.cs
@@ -118,7 +118,10 @@ public void Yaml()
reason: []
info:
moduleName: TestModule
- recommendation: Recommendation for rule 001
+ recommendation: >-
+ Recommendation for rule 001
+
+ over two lines.
level: Error
outcome: Pass
outcomeReason: Processed
@@ -216,7 +219,7 @@ public void Json()
""displayName"": ""Rule 001"",
""moduleName"": ""TestModule"",
""name"": ""rule-001"",
- ""recommendation"": ""Recommendation for rule 001"",
+ ""recommendation"": ""Recommendation for rule 001\r\nover two lines."",
""synopsis"": ""This is rule 001.""
},
""level"": ""Error"",
@@ -340,6 +343,32 @@ public void NUnit3()
Assert.Equal("", xml);
}
+ [Fact]
+ public void Csv()
+ {
+ var option = GetOption();
+ option.Repository.Url = "https://github.com/microsoft/PSRule.UnitTest";
+ var output = new TestWriter(option);
+ var result = new InvokeResult();
+ result.Add(GetPass());
+ result.Add(GetFail());
+ result.Add(GetFail("rid-003", SeverityLevel.Warning));
+ result.Add(GetFail("rid-004", SeverityLevel.Information));
+ var writer = new CsvOutputWriter(output, option, null);
+ writer.Begin();
+ writer.WriteObject(result, false);
+ writer.End();
+
+ var actual = output.Output.OfType().FirstOrDefault();
+
+ Assert.Equal(@"RuleName,TargetName,TargetType,Outcome,Synopsis,Recommendation
+""rule-001"",""TestObject1"",""TestType"",""Pass"",""Processed"",""This is rule 001."",""Recommendation for rule 001 over two lines.""
+""rule-002"",""TestObject1"",""TestType"",""Fail"",""Processed"",""This is rule 002."",""Recommendation for rule 002""
+""rule-002"",""TestObject1"",""TestType"",""Fail"",""Processed"",""This is rule 002."",""Recommendation for rule 002""
+""rule-002"",""TestObject1"",""TestType"",""Fail"",""Processed"",""This is rule 002."",""Recommendation for rule 002""
+", actual);
+ }
+
[Fact]
public void JobSummary()
{
@@ -367,7 +396,8 @@ public void JobSummary()
private static RuleRecord GetPass()
{
- return new RuleRecord(
+ return new RuleRecord
+ (
runId: "run-001",
ruleId: ResourceId.Parse("TestModule\\rule-001"),
@ref: null,
@@ -375,12 +405,13 @@ private static RuleRecord GetPass()
targetName: "TestObject1",
targetType: "TestType",
tag: new ResourceTags(),
- info: new RuleHelpInfo(
+ info: new RuleHelpInfo
+ (
"rule-001",
"Rule 001",
"TestModule",
synopsis: new InfoString("This is rule 001."),
- recommendation: new InfoString("Recommendation for rule 001")
+ recommendation: new InfoString("Recommendation for rule 001\r\nover two lines.")
),
field: new Hashtable(),
level: SeverityLevel.Error,
diff --git a/tests/PSRule.Tests/PSRule.Common.Tests.ps1 b/tests/PSRule.Tests/PSRule.Common.Tests.ps1
index 0d41007562..25e83c50ae 100644
--- a/tests/PSRule.Tests/PSRule.Common.Tests.ps1
+++ b/tests/PSRule.Tests/PSRule.Common.Tests.ps1
@@ -642,7 +642,7 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' {
$resultCsv[1].Synopsis | Should -Be 'Test rule 3';
$resultCsv[2].RuleName | Should -Be 'WithCsv';
$resultCsv[2].Synopsis | Should -Be 'This is "a" synopsis.';
- ($resultCsv[2].Recommendation -replace "`r`n", "`n") | Should -Be "This is an extended recommendation.`n`n- That includes line breaks`n- And lists";
+ ($resultCsv[2].Recommendation -replace "`r`n", "`n") | Should -Be "This is an extended recommendation. - That includes line breaks - And lists";
# Summary
$result = $testObject | Invoke-PSRule @option -As Summary | Out-String;
@@ -659,7 +659,7 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' {
$resultCsv[1].Fail | Should -Be '1';
$resultCsv[2].RuleName | Should -Be 'WithCsv';
$resultCsv[2].Synopsis | Should -Be 'This is "a" synopsis.';
- ($resultCsv[2].Recommendation -replace "`r`n", "`n") | Should -Be "This is an extended recommendation.`n`n- That includes line breaks`n- And lists";
+ ($resultCsv[2].Recommendation -replace "`r`n", "`n") | Should -Be "This is an extended recommendation. - That includes line breaks - And lists";
}
It 'Sarif' {