Functions/GenXdev.FileSystem/ReadJsonWithRetry.ps1
|
############################################################################### <# .SYNOPSIS Reads JSON file with retry logic and automatic lock cleanup. .DESCRIPTION Attempts to read a JSON file with retry logic to handle concurrent access. Implements automatic cleanup of stale lock files. Returns empty hashtable if file doesn't exist. .LICENSE Copyright (C) 2026 René Vaessen / GenXdev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. .PARAMETER FilePath The path to the JSON file to read. .PARAMETER AsHashtable Return the parsed JSON as a hashtable instead of PSCustomObject. Defaults to true. .PARAMETER MaxRetries Maximum number of retry attempts. Defaults to 10. .PARAMETER RetryDelayMs Delay in milliseconds between retries. Defaults to 200. #> function ReadJsonWithRetry { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$FilePath, [Parameter(Mandatory = $false)] [int]$MaxRetries = 10, [Parameter(Mandatory = $false)] [int]$RetryDelayMs = 200, [Parameter(Mandatory = $false)] [switch]$AsHashtable ) # return empty hashtable if file doesn't exist if (-not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $FilePath)) { if ($AsHashtable) { return @{} } else { return } } # construct lock file path $lockFile = "${FilePath}.lock" # attempt to read with retries for ($attempt = 0; $attempt -lt $MaxRetries; $attempt++) { try { # clean up stale lock files older than 30 seconds if (Microsoft.PowerShell.Management\Test-Path -LiteralPath $lockFile) { $lockInfo = [System.IO.FileInfo]::new($lockFile) $ageSeconds = ([DateTime]::Now - $lockInfo.LastWriteTime).TotalSeconds if ($ageSeconds -gt 30) { Microsoft.PowerShell.Utility\Write-Verbose ` "Removing stale lock file: $lockFile (age: ${ageSeconds}s)" Microsoft.PowerShell.Management\Remove-Item ` -LiteralPath $lockFile ` -Force ` -ErrorAction SilentlyContinue } } # read and parse json file $content = Microsoft.PowerShell.Management\Get-Content ` -LiteralPath $FilePath ` -Raw ` -ErrorAction Stop if ([string]::IsNullOrWhiteSpace($content)) { if ($AsHashtable) { return @{} } else { return } } if ($AsHashtable) { $data = $content | Microsoft.PowerShell.Utility\ConvertFrom-Json ` -AsHashtable ` -ErrorAction Stop } else { $data = $content | Microsoft.PowerShell.Utility\ConvertFrom-Json ` -ErrorAction Stop } return $data } catch { # log retry attempt Microsoft.PowerShell.Utility\Write-Verbose ` "Read attempt $($attempt + 1) failed: $($_.Exception.Message)" # wait before retry unless this is the last attempt if ($attempt -lt ($MaxRetries - 1)) { Microsoft.PowerShell.Utility\Start-Sleep ` -Milliseconds $RetryDelayMs } else { # final attempt failed, throw error throw "Failed to read JSON file after ${MaxRetries} attempts: $_" } } } } |