diff --git a/app-runner/Private/DeviceLockManager.ps1 b/app-runner/Private/DeviceLockManager.ps1 index 4fd4870..cd6159b 100644 --- a/app-runner/Private/DeviceLockManager.ps1 +++ b/app-runner/Private/DeviceLockManager.ps1 @@ -108,21 +108,35 @@ function Request-DeviceAccess { # try to create the same mutex concurrently with initial ownership. $createdNew = $false $mutex = $null + $maxAttempts = 3 + $attempt = 0 - try { - # Try to open existing mutex first - $mutex = [System.Threading.Mutex]::OpenExisting($mutexName) - Write-Debug "Opened existing mutex: $mutexName" - } catch [System.Threading.WaitHandleCannotBeOpenedException] { - # Mutex doesn't exist yet, create it without requesting initial ownership - # We'll acquire it separately with WaitOne() to handle race conditions properly - Write-Debug "Mutex doesn't exist, creating: $mutexName" - $mutex = New-Object System.Threading.Mutex($false, $mutexName, [ref]$createdNew) - Write-Debug "Created new mutex: $mutexName (createdNew: $createdNew)" - } catch [System.UnauthorizedAccessException] { - throw "Access denied when accessing mutex '$mutexName'. This may require elevated privileges. Error: $($_.Exception.Message)" - } catch { - throw "Failed to open or create mutex '$mutexName': $($_.Exception.Message)" + while ($attempt -lt $maxAttempts -and -not $mutex) { + $attempt++ + + try { + # Try to open existing mutex first + $mutex = [System.Threading.Mutex]::OpenExisting($mutexName) + Write-Debug "Opened existing mutex: $mutexName (attempt $attempt)" + } catch [System.Threading.WaitHandleCannotBeOpenedException] { + # Mutex doesn't exist yet, try to create it without requesting initial ownership + # We'll acquire it separately with WaitOne() to handle race conditions properly + Write-Debug "Mutex doesn't exist, creating: $mutexName (attempt $attempt)" + $mutex = New-Object System.Threading.Mutex($false, $mutexName, [ref]$createdNew) + Write-Debug "Created new mutex: $mutexName (createdNew: $createdNew)" + } catch [System.UnauthorizedAccessException] { + # Race condition: another process created the mutex between our OpenExisting and New-Object calls + # This can happen during creation or when opening, retry in both cases + if ($attempt -lt $maxAttempts) { + Write-Debug "UnauthorizedAccessException on attempt $attempt - another process likely created mutex, retrying..." + Start-Sleep -Milliseconds 50 + continue + } else { + throw "Access denied when accessing mutex '$mutexName' after $maxAttempts attempts. This may require elevated privileges. Error: $($_.Exception.Message)" + } + } catch { + throw "Failed to open or create mutex '$mutexName': $($_.Exception.Message)" + } } # Now acquire the mutex (whether we just created it or opened existing) diff --git a/app-runner/Private/DeviceProviders/DeviceProvider.ps1 b/app-runner/Private/DeviceProviders/DeviceProvider.ps1 index da17a13..21e65cc 100644 --- a/app-runner/Private/DeviceProviders/DeviceProvider.ps1 +++ b/app-runner/Private/DeviceProviders/DeviceProvider.ps1 @@ -24,6 +24,8 @@ class DeviceProvider { [int]$MaxRetryAttempts = 2 [bool]$IsRebooting = $false # Internal flag to skip retry-on-timeout during reboot + [string]$DebugOutputForwarder = "ForEach-Object { (`$_ | Out-String).Trim() } | Where-Object { `$_.Length -gt 0 } | Tee-Object -variable capturedOutput | Foreach-Object { Write-Debug `$_ } ; `$capturedOutput" + DeviceProvider() { $this.Commands = @{} $this.Timeouts = @{} @@ -91,7 +93,7 @@ class DeviceProvider { } # Start job with the provided scriptblock and arguments - $job = Start-Job -ScriptBlock $scriptBlock -ArgumentList $platform, $action, $command + $job = Start-Job -ScriptBlock $scriptBlock -ArgumentList $platform, $action, $command, $this.DebugOutputForwarder # Wait with progress messages every 30 seconds $waitIntervalSeconds = 30 @@ -182,11 +184,11 @@ class DeviceProvider { # Build the execution scriptblock once - used for both timeout and non-timeout paths $scriptBlock = { - param($platform, $act, $cmd) + param($platform, $act, $cmd, $debugForwarder) try { $PSNativeCommandUseErrorActionPreference = $false Write-Debug "${platform}: Invoking ($act) command $cmd" - $result = Invoke-Expression "$cmd | Tee-Object -variable capturedOutput | Foreach-Object { Write-Debug `$_ } ; `$capturedOutput" + $result = Invoke-Expression "$cmd | $debugForwarder" if ($LASTEXITCODE -ne 0) { Write-Warning "Command ($act`: $cmd) failed with exit code $($LASTEXITCODE) $($result.Length -gt 0 ? 'and output:' : '')" $result | ForEach-Object { Write-Warning $_ } @@ -217,7 +219,7 @@ class DeviceProvider { } # Otherwise, execute directly without timeout - return & $scriptBlock $this.Platform $action $command + return & $scriptBlock $this.Platform $action $command $this.DebugOutputForwarder } [hashtable] CreateSessionInfo() { @@ -316,7 +318,7 @@ class DeviceProvider { $startDate = Get-Date try { $PSNativeCommandUseErrorActionPreference = $false - $result = Invoke-Expression "$command | Tee-Object -variable capturedOutput | Foreach-Object { Write-Debug `$_ } ; `$capturedOutput" + $result = Invoke-Expression "$command | $($this.DebugOutputForwarder)" $exitCode = $LASTEXITCODE } finally { $PSNativeCommandUseErrorActionPreference = $true diff --git a/sentry-api-client/Public/Find-SentryEventByTag.ps1 b/sentry-api-client/Public/Find-SentryEventByTag.ps1 index 821fffa..a2da079 100644 --- a/sentry-api-client/Public/Find-SentryEventByTag.ps1 +++ b/sentry-api-client/Public/Find-SentryEventByTag.ps1 @@ -1,5 +1,4 @@ -function Find-SentryEventByTag -{ +function Find-SentryEventByTag { <# .SYNOPSIS Finds issues and their associated events filtered by a tag. @@ -65,8 +64,7 @@ function Find-SentryEventByTag sort = $Sort } - if ($Cursor) - { + if ($Cursor) { $QueryParams.cursor = $Cursor } @@ -79,20 +77,17 @@ function Find-SentryEventByTag $Issues = if ($Response -is [Array]) { $Response } else { @($Response) } $AllEvents = @() - Write-Debug "Found $($Issues.Count) issues matching tag '$TagName`:$TagValue'. Fetching events..." + Write-Debug "Found $(@($Issues).Count) issues matching tag '$TagName`:$TagValue'. Fetching events..." - foreach ($Issue in $Issues) - { - try - { + foreach ($Issue in @($Issues)) { + try { # Use the organization issues events endpoint with tag filtering $EventsQueryParams = @{ query = $QueryString full = $true } - if ($Limit) - { + if ($Limit) { # Distribute limit across issues, minimum 1 per issue $EventsPerIssue = [Math]::Max(1, [Math]::Floor($Limit / $Issues.Count)) $EventsQueryParams.limit = $EventsPerIssue @@ -102,19 +97,15 @@ function Find-SentryEventByTag $EventsUri = Get-SentryOrganizationUrl -Resource "issues/$($Issue.id)/events/" -QueryString $EventsQueryStringParams $IssueEvents = Invoke-SentryApiRequest -Uri $EventsUri -Method 'GET' - $IssueEventsArray = if ($IssueEvents -is [Array]) { $IssueEvents } else { @($IssueEvents) } - Write-Debug "Found $($IssueEvents.Count) events for issue $($Issue.id) matching tag '$TagName`:$TagValue', fetching actual event content." + Write-Debug "Found $(@($IssueEvents).Count) events for issue $($Issue.id) matching tag '$TagName`:$TagValue', fetching actual event content." # The response from the API above differs from the Get-SentryEvent so we just grab event IDs and fetch directly. - foreach ($_event in $IssueEventsArray) - { + foreach ($_event in @($IssueEvents)) { $_event = Get-SentryEvent -EventId $_event.eventID $AllEvents += $_event } - } - catch - { + } catch { Write-Error "Failed to retrieve events for issue $($Issue.id): $_" } }