From b02c35739cb48f750e705803f646ed1e6441c939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20CORTIER?= Date: Sat, 24 Oct 2026 04:41:25 +0900 Subject: [PATCH 1/2] chore(package): regenerate installer strings in LF checkouts TextTransform.exe fails with CS1519 when the template has LF line endings, which is what a checkout with core.autocrlf=input or false produces, since Strings.g.tt has no eol attribute. T4 then replaces Strings.g.cs with `ErrorGeneratingOutput`, so the failed run also corrupts the working tree. Write the temporary template with CRLF endings whatever the checkout uses, and restore the generated files from their original bytes when TextTransform fails, so a failed run exits non-zero and changes nothing. Regenerating also restores the trailing tabs the template emits on five Agent Tunnel constants of the Gateway Strings.g.cs, which had been added without them. Co-Authored-By: Claude Opus 5.5 --- package/Update-InstallerLocalization.ps1 | 11 ++++++++++- package/WindowsManaged/Resources/Strings.g.cs | 10 +++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/package/Update-InstallerLocalization.ps1 b/package/Update-InstallerLocalization.ps1 index 6fb7b694c8..8b7e1a0015 100644 --- a/package/Update-InstallerLocalization.ps1 +++ b/package/Update-InstallerLocalization.ps1 @@ -52,12 +52,17 @@ foreach ($project in $projects) { # Use a temporary copy of the template (it must live next to the original: outputs are written beside the template). $temporaryTemplate = Join-Path $resources "_Strings.$([guid]::NewGuid().ToString('N')).tt" $text = [IO.File]::ReadAllText($template).Replace('$(SolutionDir)Resources\Newtonsoft.Json.dll', $newtonsoft) + # TextTransform.exe fails to compile a template with LF line endings (CS1519), so use CRLF whatever the checkout has. + $text = $text -replace '\r?\n', "`r`n" [IO.File]::WriteAllText($temporaryTemplate, $text) # T4 drops the byte order mark that some existing files carry; keep each file as it was. + # On failure T4 still overwrites its output with `ErrorGeneratingOutput`, so keep the original bytes to restore them. + $original = @{} $hadBom = @{} Get-ChildItem $resources -Include '*.wxl', 'Strings.g.cs' -File -Recurse | ForEach-Object { $bytes = [IO.File]::ReadAllBytes($_.FullName) + $original[$_.FullName] = $bytes $hadBom[$_.FullName] = $bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF } @@ -65,7 +70,11 @@ foreach ($project in $projects) { & $TextTransformPath $temporaryTemplate -out (Join-Path $resources 'Strings.g.cs') if ($LASTEXITCODE -ne 0) { - throw "TextTransform failed for $project" + foreach ($path in $original.Keys) { + [IO.File]::WriteAllBytes($path, $original[$path]) + } + + throw "TextTransform failed for $project; generated files were left unchanged" } } finally { diff --git a/package/WindowsManaged/Resources/Strings.g.cs b/package/WindowsManaged/Resources/Strings.g.cs index 9321599c80..2e2811b3fc 100644 --- a/package/WindowsManaged/Resources/Strings.g.cs +++ b/package/WindowsManaged/Resources/Strings.g.cs @@ -195,7 +195,7 @@ public static string I18n(this MsiRuntime runtime, string res) /// /// Agent Tunnel /// - public const string Group_AgentTunnel = "Group_AgentTunnel"; + public const string Group_AgentTunnel = "Group_AgentTunnel"; /// /// External Access /// @@ -419,11 +419,11 @@ public static string I18n(this MsiRuntime runtime, string res) /// /// Enabled /// - public const string Property_EnableAgentTunnel = "Property_EnableAgentTunnel"; + public const string Property_EnableAgentTunnel = "Property_EnableAgentTunnel"; /// /// UDP Port /// - public const string Property_AgentTunnelPort = "Property_AgentTunnelPort"; + public const string Property_AgentTunnelPort = "Property_AgentTunnelPort"; /// /// Directory /// @@ -587,11 +587,11 @@ public static string I18n(this MsiRuntime runtime, string res) /// /// Enable Agent Tunnel /// - public const string EnableAgentTunnel = "EnableAgentTunnel"; + public const string EnableAgentTunnel = "EnableAgentTunnel"; /// /// UDP port /// - public const string AgentTunnelUdpPort = "AgentTunnelUdpPort"; + public const string AgentTunnelUdpPort = "AgentTunnelUdpPort"; /// /// Enable the Gateway web interface /// From b8c2746efd250d779bc725998f51ec7464b2f07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20CORTIER?= Date: Sun, 25 Oct 2026 11:30:38 +0900 Subject: [PATCH 2/2] chore(package): roll back every selected project on failure Snapshot the generated files of all selected projects before running T4, and on failure remove outputs the run created before restoring the rest, so a failure in a later project does not keep the earlier project's changes or a new ErrorGeneratingOutput file. Co-Authored-By: Claude Opus 5.5 --- package/Update-InstallerLocalization.ps1 | 84 ++++++++++++++---------- 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/package/Update-InstallerLocalization.ps1 b/package/Update-InstallerLocalization.ps1 index 8b7e1a0015..9c372c26ce 100644 --- a/package/Update-InstallerLocalization.ps1 +++ b/package/Update-InstallerLocalization.ps1 @@ -43,49 +43,63 @@ $projects = switch ($Product) { default { @('AgentWindowsManaged', 'WindowsManaged') } } -foreach ($project in $projects) { - $resources = Join-Path $PSScriptRoot "$project\Resources" - $template = Join-Path $resources 'Strings.g.tt' - $newtonsoft = Join-Path $resources 'Newtonsoft.Json.dll' - - # TextTransform.exe does not expand the `$(SolutionDir)` macro that Visual Studio resolves. - # Use a temporary copy of the template (it must live next to the original: outputs are written beside the template). - $temporaryTemplate = Join-Path $resources "_Strings.$([guid]::NewGuid().ToString('N')).tt" - $text = [IO.File]::ReadAllText($template).Replace('$(SolutionDir)Resources\Newtonsoft.Json.dll', $newtonsoft) - # TextTransform.exe fails to compile a template with LF line endings (CS1519), so use CRLF whatever the checkout has. - $text = $text -replace '\r?\n', "`r`n" - [IO.File]::WriteAllText($temporaryTemplate, $text) - - # T4 drops the byte order mark that some existing files carry; keep each file as it was. - # On failure T4 still overwrites its output with `ErrorGeneratingOutput`, so keep the original bytes to restore them. - $original = @{} - $hadBom = @{} - Get-ChildItem $resources -Include '*.wxl', 'Strings.g.cs' -File -Recurse | ForEach-Object { - $bytes = [IO.File]::ReadAllBytes($_.FullName) - $original[$_.FullName] = $bytes - $hadBom[$_.FullName] = $bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF +function Get-GeneratedFiles { + $projects | ForEach-Object { + Get-ChildItem (Join-Path $PSScriptRoot "$_\Resources") -Include '*.wxl', 'Strings.g.cs' -File -Recurse } +} - try { - & $TextTransformPath $temporaryTemplate -out (Join-Path $resources 'Strings.g.cs') +# T4 drops the byte order mark that some existing files carry; keep each file as it was. +# On failure T4 still overwrites its output with `ErrorGeneratingOutput`, so keep the original bytes of every selected project to restore them. +$original = @{} +$hadBom = @{} +Get-GeneratedFiles | ForEach-Object { + $bytes = [IO.File]::ReadAllBytes($_.FullName) + $original[$_.FullName] = $bytes + $hadBom[$_.FullName] = $bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF +} - if ($LASTEXITCODE -ne 0) { - foreach ($path in $original.Keys) { - [IO.File]::WriteAllBytes($path, $original[$path]) +try { + foreach ($project in $projects) { + $resources = Join-Path $PSScriptRoot "$project\Resources" + $template = Join-Path $resources 'Strings.g.tt' + $newtonsoft = Join-Path $resources 'Newtonsoft.Json.dll' + + # TextTransform.exe does not expand the `$(SolutionDir)` macro that Visual Studio resolves. + # Use a temporary copy of the template (it must live next to the original: outputs are written beside the template). + $temporaryTemplate = Join-Path $resources "_Strings.$([guid]::NewGuid().ToString('N')).tt" + $text = [IO.File]::ReadAllText($template).Replace('$(SolutionDir)Resources\Newtonsoft.Json.dll', $newtonsoft) + # TextTransform.exe fails to compile a template with LF line endings (CS1519), so use CRLF whatever the checkout has. + $text = $text -replace '\r?\n', "`r`n" + [IO.File]::WriteAllText($temporaryTemplate, $text) + + try { + & $TextTransformPath $temporaryTemplate -out (Join-Path $resources 'Strings.g.cs') + + if ($LASTEXITCODE -ne 0) { + throw "TextTransform failed for $project" } + } + finally { + Remove-Item $temporaryTemplate -ErrorAction SilentlyContinue + } - throw "TextTransform failed for $project; generated files were left unchanged" + # The repository stores these files with LF line endings, while T4 emits mixed endings. + Get-ChildItem $resources -Include '*.wxl', 'Strings.g.cs' -File -Recurse | ForEach-Object { + $content = [IO.File]::ReadAllText($_.FullName).Replace("`r`n", "`n") + [IO.File]::WriteAllText($_.FullName, $content, (New-Object Text.UTF8Encoding($hadBom[$_.FullName] -eq $true))) } + + Write-Output "Regenerated localization files for $project" } - finally { - Remove-Item $temporaryTemplate -ErrorAction SilentlyContinue - } +} +catch { + # Leave every selected project as it was: drop outputs this run created, then restore the rest. + Get-GeneratedFiles | Where-Object { -not $original.ContainsKey($_.FullName) } | Remove-Item - # The repository stores these files with LF line endings, while T4 emits mixed endings. - Get-ChildItem $resources -Include '*.wxl', 'Strings.g.cs' -File -Recurse | ForEach-Object { - $content = [IO.File]::ReadAllText($_.FullName).Replace("`r`n", "`n") - [IO.File]::WriteAllText($_.FullName, $content, (New-Object Text.UTF8Encoding($hadBom[$_.FullName] -eq $true))) + foreach ($path in $original.Keys) { + [IO.File]::WriteAllBytes($path, $original[$path]) } - Write-Output "Regenerated localization files for $project" + throw "$_; generated files were left unchanged" }