From bc289afc6b9c74f4623a21c3ef62d6a21d2e676d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Dec 2025 21:23:00 +0000 Subject: [PATCH 1/3] Initial plan From f62aff64db252e3d6a63cbb8d34b992042438a5b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Dec 2025 21:31:01 +0000 Subject: [PATCH 2/3] Fix formula corruption by setting FullCalcOnLoad to false Co-authored-by: dfinke <67258+dfinke@users.noreply.github.com> --- Public/Close-ExcelPackage.ps1 | 3 + __tests__/TableFormula.tests.ps1 | 125 +++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 __tests__/TableFormula.tests.ps1 diff --git a/Public/Close-ExcelPackage.ps1 b/Public/Close-ExcelPackage.ps1 index ee64bc05..ddf91266 100644 --- a/Public/Close-ExcelPackage.ps1 +++ b/Public/Close-ExcelPackage.ps1 @@ -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 ) } diff --git a/__tests__/TableFormula.tests.ps1 b/__tests__/TableFormula.tests.ps1 new file mode 100644 index 00000000..1754080f --- /dev/null +++ b/__tests__/TableFormula.tests.ps1 @@ -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 + } + } +} From 5040fc05427c9c1070758e08dc5174873df9d319 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Dec 2025 21:33:03 +0000 Subject: [PATCH 3/3] Add FullCalcOnLoad fix to Export-Excel direct save path Co-authored-by: dfinke <67258+dfinke@users.noreply.github.com> --- Public/Export-Excel.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Public/Export-Excel.ps1 b/Public/Export-Excel.ps1 index 5c08b208..bebf5741 100644 --- a/Public/Export-Excel.ps1 +++ b/Public/Export-Excel.ps1 @@ -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)"