Compare commits

..

3 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
5040fc0542 Add FullCalcOnLoad fix to Export-Excel direct save path
Co-authored-by: dfinke <67258+dfinke@users.noreply.github.com>
2025-12-26 21:33:03 +00:00
copilot-swe-agent[bot]
f62aff64db Fix formula corruption by setting FullCalcOnLoad to false
Co-authored-by: dfinke <67258+dfinke@users.noreply.github.com>
2025-12-26 21:31:01 +00:00
copilot-swe-agent[bot]
bc289afc6b Initial plan 2025-12-26 21:23:00 +00:00
5 changed files with 131 additions and 186 deletions

View File

@@ -113,57 +113,6 @@ function Add-ExcelChart {
if ($null -ne $XMinValue) { $chart.XAxis.MinValue = $XMinValue }
if ($null -ne $XMaxValue) { $chart.XAxis.MaxValue = $XMaxValue }
if ($XAxisNumberformat) { $chart.XAxis.Format = (Expand-NumberFormat $XAxisNumberformat) }
# Fix for category axis (used in line charts, etc.): EPPlus doesn't serialize MajorUnit/MinorUnit to XML for category axes
# We need to manually add these elements to the XML if they were specified
if ($XMajorUnit -or $XMinorUnit) {
$chartXml = $chart.ChartXml
$nsManager = New-Object System.Xml.XmlNamespaceManager($chartXml.NameTable)
$null = $nsManager.AddNamespace("c", "http://schemas.openxmlformats.org/drawingml/2006/chart")
$catAx = $chartXml.SelectSingleNode("//c:catAx", $nsManager)
if ($catAx) {
# Category axis exists - need to add majorUnit/minorUnit elements
if ($XMajorUnit) {
$existingMajorUnit = $catAx.SelectSingleNode("c:majorUnit", $nsManager)
if (-not $existingMajorUnit) {
$majorUnitElement = $chartXml.CreateElement("c", "majorUnit", "http://schemas.openxmlformats.org/drawingml/2006/chart")
$null = $majorUnitElement.SetAttribute("val", $XMajorUnit)
# Insert after scaling element or at the beginning
$scalingNode = $catAx.SelectSingleNode("c:scaling", $nsManager)
if ($scalingNode) {
$null = $catAx.InsertAfter($majorUnitElement, $scalingNode)
} else {
$null = $catAx.PrependChild($majorUnitElement)
}
} else {
$null = $existingMajorUnit.SetAttribute("val", $XMajorUnit)
}
}
if ($XMinorUnit) {
$existingMinorUnit = $catAx.SelectSingleNode("c:minorUnit", $nsManager)
if (-not $existingMinorUnit) {
$minorUnitElement = $chartXml.CreateElement("c", "minorUnit", "http://schemas.openxmlformats.org/drawingml/2006/chart")
$null = $minorUnitElement.SetAttribute("val", $XMinorUnit)
# Insert after majorUnit if it exists, otherwise after scaling
$majorUnitNode = $catAx.SelectSingleNode("c:majorUnit", $nsManager)
if ($majorUnitNode) {
$null = $catAx.InsertAfter($minorUnitElement, $majorUnitNode)
} else {
$scalingNode = $catAx.SelectSingleNode("c:scaling", $nsManager)
if ($scalingNode) {
$null = $catAx.InsertAfter($minorUnitElement, $scalingNode)
} else {
$null = $catAx.PrependChild($minorUnitElement)
}
}
} else {
$null = $existingMinorUnit.SetAttribute("val", $XMinorUnit)
}
}
}
}
if ($YAxisTitleText) {
$chart.YAxis.Title.Text = $YAxisTitleText

View File

@@ -19,6 +19,9 @@ function Close-ExcelPackage {
try { [OfficeOpenXml.CalculationExtension]::Calculate($ExcelPackage.Workbook) }
catch { Write-Warning "One or more errors occured while calculating, save will continue, but there may be errors in the workbook." }
}
# Set FullCalcOnLoad to false to prevent Excel from corrupting formulas during recalculation
# This fixes issues with table-structured references like [[#This Row],[ColumnName]]
$ExcelPackage.Workbook.FullCalcOnLoad = $false
if ($SaveAs) {
$SaveAs = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($SaveAs)
if ($Password) { $ExcelPackage.SaveAs( $SaveAs, $Password ) }

View File

@@ -682,6 +682,9 @@
else {
if ($ReturnRange) { $dataRange }
# Set FullCalcOnLoad to false to prevent Excel from corrupting formulas during recalculation
# This fixes issues with table-structured references like [[#This Row],[ColumnName]]
$pkg.Workbook.FullCalcOnLoad = $false
if ($Password) { $pkg.Save($Password) }
else { $pkg.Save() }
Write-Verbose -Message "Saved workbook $($pkg.File)"

View File

@@ -1,135 +0,0 @@
# Test for X-Axis configuration on line charts
# This test verifies that XMajorUnit and XMinorUnit parameters work correctly for line charts
Describe "X-Axis Configuration for Line Charts" {
BeforeAll {
$path = "TestDrive:\xaxis_test.xlsx"
}
Context "X-Axis MajorUnit and MinorUnit on Line Charts" {
BeforeAll {
# Create test data
$excel = 1..7 | ForEach-Object {
[pscustomobject][ordered]@{
XValue = @(5, 10, 15, 20, 25, 30, 35)[$_ - 1]
YValue = @(18, 11, 23, 17, 12, 32, 12)[$_ - 1]
}
} | Export-Excel -Path $path -WorksheetName "Test" -PassThru -AutoNameRange
# Add chart with X-axis configuration
Add-ExcelChart -Worksheet $excel.Workbook.Worksheets["Test"] `
-ChartType Line `
-XRange "XValue" `
-YRange "YValue" `
-Title "Test Chart" `
-XMinValue 0 `
-XMaxValue 40 `
-XMajorUnit 10 `
-XMinorUnit 5 `
-YMinValue 0 `
-YMaxValue 40 `
-YMajorUnit 10 `
-YMinorUnit 5
Close-ExcelPackage $excel
# Reopen to verify
$excel = Open-ExcelPackage -Path $path
$ws = $excel.Workbook.Worksheets["Test"]
$chart = $ws.Drawings[0]
}
It "Set XAxis.MinValue correctly" {
$chart.XAxis.MinValue | Should -Be 0
}
It "Set XAxis.MaxValue correctly" {
$chart.XAxis.MaxValue | Should -Be 40
}
It "Set XAxis.MajorUnit correctly" {
$chart.XAxis.MajorUnit | Should -Be 10
}
It "Set XAxis.MinorUnit correctly" {
$chart.XAxis.MinorUnit | Should -Be 5
}
It "Added MajorUnit to category axis XML" {
$xml = $chart.ChartXml
$nsmgr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$nsmgr.AddNamespace("c", "http://schemas.openxmlformats.org/drawingml/2006/chart")
$catAx = $xml.SelectSingleNode("//c:catAx", $nsmgr)
$majorUnit = $catAx.SelectSingleNode("c:majorUnit", $nsmgr)
$majorUnit | Should -Not -BeNullOrEmpty
$majorUnit.val | Should -Be "10"
}
It "Added MinorUnit to category axis XML" {
$xml = $chart.ChartXml
$nsmgr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$nsmgr.AddNamespace("c", "http://schemas.openxmlformats.org/drawingml/2006/chart")
$catAx = $xml.SelectSingleNode("//c:catAx", $nsmgr)
$minorUnit = $catAx.SelectSingleNode("c:minorUnit", $nsmgr)
$minorUnit | Should -Not -BeNullOrEmpty
$minorUnit.val | Should -Be "5"
}
AfterAll {
Close-ExcelPackage $excel -NoSave
}
}
Context "X-Axis without MajorUnit and MinorUnit" {
BeforeAll {
$path2 = "TestDrive:\xaxis_test2.xlsx"
# Create test data without X-axis units
$excel2 = 1..7 | ForEach-Object {
[pscustomobject][ordered]@{
XValue = @(5, 10, 15, 20, 25, 30, 35)[$_ - 1]
YValue = @(18, 11, 23, 17, 12, 32, 12)[$_ - 1]
}
} | Export-Excel -Path $path2 -WorksheetName "Test" -PassThru -AutoNameRange
# Add chart WITHOUT X-axis MajorUnit/MinorUnit
Add-ExcelChart -Worksheet $excel2.Workbook.Worksheets["Test"] `
-ChartType Line `
-XRange "XValue" `
-YRange "YValue" `
-Title "Test Chart"
Close-ExcelPackage $excel2
# Reopen to verify
$excel2 = Open-ExcelPackage -Path $path2
$ws2 = $excel2.Workbook.Worksheets["Test"]
$chart2 = $ws2.Drawings[0]
}
It "Should not add MajorUnit to category axis XML when not specified" {
$xml = $chart2.ChartXml
$nsmgr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$nsmgr.AddNamespace("c", "http://schemas.openxmlformats.org/drawingml/2006/chart")
$catAx = $xml.SelectSingleNode("//c:catAx", $nsmgr)
$majorUnit = $catAx.SelectSingleNode("c:majorUnit", $nsmgr)
$majorUnit | Should -BeNullOrEmpty
}
It "Should not add MinorUnit to category axis XML when not specified" {
$xml = $chart2.ChartXml
$nsmgr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$nsmgr.AddNamespace("c", "http://schemas.openxmlformats.org/drawingml/2006/chart")
$catAx = $xml.SelectSingleNode("//c:catAx", $nsmgr)
$minorUnit = $catAx.SelectSingleNode("c:minorUnit", $nsmgr)
$minorUnit | Should -BeNullOrEmpty
}
AfterAll {
Close-ExcelPackage $excel2 -NoSave
}
}
}

View File

@@ -0,0 +1,125 @@
#Requires -Modules @{ ModuleName="Pester"; ModuleVersion="4.0.0" }
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'False Positives')]
param()
Describe "Table Formula Bug Fix" -Tag "TableFormula" {
BeforeAll {
$WarningAction = "SilentlyContinue"
}
Context "FullCalcOnLoad is set to false to prevent formula corruption" {
BeforeAll {
$path = "TestDrive:\table_formula.xlsx"
Remove-Item -Path $path -ErrorAction SilentlyContinue
# Create a table with a blank record
$BlankRecordForFile = [PsCustomObject]@{
'Action Add' = ''
UserName = ''
Address = ''
Name = ''
NewName = ''
}
# Export as a table
$ExcelFile = $BlankRecordForFile | Export-Excel -Path $path -WorksheetName 'Data' `
-TableName 'DataTable' -TableStyle 'Light1' -AutoSize:$false -AutoFilter `
-BoldTopRow -FreezeTopRow -StartRow 1 -PassThru
$Worksheet = $ExcelFile.Workbook.Worksheets['Data']
# Insert a row and add a complex formula with table-structured references
$Worksheet.InsertRow(2, 1)
# This formula uses old-style table references [[#This Row],[ColumnName]]
# which Excel converts to [@ColumnName] when opening
$Formula = '=IFS( [[#This Row],[UserName]]="","", [[#This Row],[Action Add]]=TRUE, CONCAT([[#This Row],[Address]],"-",[[#This Row],[UserName]]), CONCAT([[#This Row],[Address]],"-",[[#This Row],[UserName]]) <> [[#This Row],[Name]], CONCAT([[#This Row],[Address]],"-",[[#This Row],[UserName]]), TRUE, "")'
$Cell = $Worksheet.Cells['e2']
$Cell.Formula = $Formula
Close-ExcelPackage $ExcelFile
# Reopen to verify
$ExcelFile2 = Open-ExcelPackage -Path $path
$Worksheet2 = $ExcelFile2.Workbook.Worksheets['Data']
}
It "Sets fullCalcOnLoad to false in the workbook XML" {
# Extract and check the XML directly
$TempExtractPath = Join-Path -Path $TestDrive -ChildPath "extracted_$(Get-Random)"
Expand-Archive -Path $path -DestinationPath $TempExtractPath -Force
$WorkbookXml = Get-Content (Join-Path -Path $TempExtractPath -ChildPath "xl/workbook.xml") -Raw
$WorkbookXml | Should -Match 'fullCalcOnLoad="0"'
Remove-Item -Path $TempExtractPath -Recurse -Force
}
It "Preserves the formula correctly after save and reopen" {
$Cell2 = $Worksheet2.Cells['e2']
$Cell2.Formula | Should -Not -BeNullOrEmpty
$Cell2.Formula | Should -Match 'IFS\('
$Cell2.Formula | Should -Match 'CONCAT\('
}
It "Does not corrupt the formula with extra @ symbols" {
$Cell2 = $Worksheet2.Cells['e2']
# The formula should not have extra @ symbols added by Excel during recalculation
# The specific bug was an @ being inserted before CONCAT in the middle of the formula
# We can't test this directly without opening in Excel, but we can verify the formula is unchanged
$Cell2.Formula.Length | Should -BeGreaterThan 100
}
AfterAll {
if ($ExcelFile2) {
Close-ExcelPackage -ExcelPackage $ExcelFile2 -NoSave
}
}
}
Context "FullCalcOnLoad setting works with different save methods" {
It "Sets fullCalcOnLoad to false when using SaveAs" {
$path = "TestDrive:\saveas_test.xlsx"
$path2 = "TestDrive:\saveas_test2.xlsx"
Remove-Item -Path $path, $path2 -ErrorAction SilentlyContinue
$data = [PSCustomObject]@{ Name = 'Test' }
$excel = $data | Export-Excel -Path $path -PassThru
Close-ExcelPackage $excel -SaveAs $path2
# Extract and check the XML
$TempExtractPath = Join-Path -Path $TestDrive -ChildPath "extracted_saveas_$(Get-Random)"
Expand-Archive -Path $path2 -DestinationPath $TempExtractPath -Force
$WorkbookXml = Get-Content (Join-Path -Path $TempExtractPath -ChildPath "xl/workbook.xml") -Raw
$WorkbookXml | Should -Match 'fullCalcOnLoad="0"'
Remove-Item -Path $TempExtractPath -Recurse -Force
}
It "Sets fullCalcOnLoad to false when using Calculate flag" {
$path = "TestDrive:\calculate_test.xlsx"
Remove-Item -Path $path -ErrorAction SilentlyContinue
$data = [PSCustomObject]@{ Name = 'Test'; Value = 100 }
$excel = $data | Export-Excel -Path $path -PassThru
# Set a formula
$ws = $excel.Workbook.Worksheets[1]
$ws.Cells['C2'].Formula = 'B2*2'
Close-ExcelPackage $excel -Calculate
# Extract and check the XML
$TempExtractPath = Join-Path -Path $TestDrive -ChildPath "extracted_calc_$(Get-Random)"
Expand-Archive -Path $path -DestinationPath $TempExtractPath -Force
$WorkbookXml = Get-Content (Join-Path -Path $TempExtractPath -ChildPath "xl/workbook.xml") -Raw
$WorkbookXml | Should -Match 'fullCalcOnLoad="0"'
Remove-Item -Path $TempExtractPath -Recurse -Force
}
}
}