diff --git a/installer/SHA256SUMS b/installer/SHA256SUMS index d185e92..84eb4d6 100644 --- a/installer/SHA256SUMS +++ b/installer/SHA256SUMS @@ -1,2 +1,2 @@ b2183b9e245ca1cf0685a9090cfc6d0bba7266c4e4c43d8c3be0f17aa00bc5be install.sh -14a7a92766e74ea7d12683ea0bb7932938daf88fc6b184a5d80315079ce01720 install.ps1 +f576d9188b0a8f0e2bc0472ece46179f48e4df029dc549a69a9059d833db3dad install.ps1 diff --git a/installer/VERSION b/installer/VERSION index b82608c..5366600 100644 --- a/installer/VERSION +++ b/installer/VERSION @@ -1 +1 @@ -v0.1.0 +v0.1.2 diff --git a/installer/install.ps1 b/installer/install.ps1 index 3014d99..f0f2c9d 100644 --- a/installer/install.ps1 +++ b/installer/install.ps1 @@ -66,6 +66,33 @@ function Invoke-WithRetry { } } +function Invoke-WithFilesystemRetry { + param( + [Parameter(Mandatory = $true)][scriptblock]$Action, + [ValidateRange(1, [int]::MaxValue)][int]$Attempts = 10, + [ValidateRange(0, [int]::MaxValue)][int]$DelayMilliseconds = 1000 + ) + + # Windows Defender's real-time scan (or Search indexing) can briefly hold + # a handle on a just-extracted or just-touched directory, and + # Directory.Move fails outright if any handle is open. Retry only the + # transient access errors that pattern produces; anything else propagates + # immediately. The defaults match MSBuild's Copy task, which retries the + # same two exception types for the same reason. + for ($attempt = 1; $attempt -le $Attempts; $attempt++) { + try { + return & $Action + } + catch [System.UnauthorizedAccessException], [System.IO.IOException] { + if ($attempt -eq $Attempts) { + Write-Warning "Filesystem operation still blocked after $Attempts attempts $DelayMilliseconds ms apart; giving up" + throw + } + Start-Sleep -Milliseconds $DelayMilliseconds + } + } +} + function Test-DownloadUriAllowed { param([Parameter(Mandatory = $true)][string]$Uri) @@ -482,17 +509,17 @@ try { # on wildcard characters like [ ] in the install root. $temporaryFinal = Join-Path $finalParent (".cloudsmith.new." + [Guid]::NewGuid().ToString("N")) $oldFinal = Join-Path $finalParent (".cloudsmith.old." + [Guid]::NewGuid().ToString("N")) - [System.IO.Directory]::Move($stagedDirectory, $temporaryFinal) + Invoke-WithFilesystemRetry { [System.IO.Directory]::Move($stagedDirectory, $temporaryFinal) } if (Test-Path -LiteralPath $binDirectory) { - [System.IO.Directory]::Move($binDirectory, $oldFinal) + Invoke-WithFilesystemRetry { [System.IO.Directory]::Move($binDirectory, $oldFinal) } } try { - [System.IO.Directory]::Move($temporaryFinal, $binDirectory) + Invoke-WithFilesystemRetry { [System.IO.Directory]::Move($temporaryFinal, $binDirectory) } } catch { if (Test-Path -LiteralPath $oldFinal) { - try { [System.IO.Directory]::Move($oldFinal, $binDirectory) } catch { Write-Warning "Failed to restore previous installation directory during rollback: $_" } + try { Invoke-WithFilesystemRetry { [System.IO.Directory]::Move($oldFinal, $binDirectory) } } catch { Write-Warning "Failed to restore previous installation directory during rollback: $_" } } throw }