Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions app-runner/Private/DeviceProviders/DeviceProvider.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,20 @@ class DeviceProvider {
}
}

# Stops the application currently running on the device.
# Platforms that support this configure a 'stop-app' command; the rest log a warning.
[void] StopApplication() {
Write-Debug "$($this.Platform): Stopping application"

if (-not $this.Commands.ContainsKey('stop-app')) {
$this.LogNotImplemented('StopApplication')
return
}

# Not giving an argument stops whatever is in the foreground.
$this.InvokeCommand('stop-app', @(''))
}

[void] TakeScreenshot([string]$OutputPath) {
# If the output path includes a directory, split it up, otherwise use current directory
$directory = Split-Path $OutputPath -Parent
Expand Down
1 change: 1 addition & 0 deletions app-runner/Private/DeviceProviders/MockDeviceProvider.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class MockDeviceProvider : DeviceProvider {
"reset" = @("echo", "'Mock reset'")
"getstatus" = @("echo", "'Mock get status'")
"launch" = @("echo", "'Mock launch {0} {1}'")
"stop-app" = @("echo", "'Mock stop app {0}'")
"screenshot" = @("echo", "'Mock screenshot'")
# Target management commands for testing DetectAndSetDefaultTarget()
"get-default-target" = @("echo", "'Mock get-default-target'")
Expand Down
14 changes: 14 additions & 0 deletions app-runner/Private/DeviceProviders/SwitchProvider.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class SwitchProvider : DeviceProvider {
'detect-target' = @($this.TargetControlTool, 'detect-target --json', { $input | ConvertFrom-Json })
'register-target' = @($this.TargetControlTool, 'register --target "{0}"')
'launch' = @($this.ApplicationRunnerTool, '"{0}" -- {1}')
'stop-app' = @($this.TargetControlTool, 'terminate{0}')
'screenshot' = @($this.TargetControlTool, 'take-screenshot --directory "{0}" --file-name "{1}"')
'test-internet' = @($this.TargetControlTool, 'devmenu -- network confirm-internet-connection')
}
Expand Down Expand Up @@ -142,6 +143,19 @@ class SwitchProvider : DeviceProvider {
Start-Sleep -Seconds 3
}

# Override StopApplication to pass --target when a target is specified.
#
# This is needed after a crash on Switch 2 (Ounce): the crashed process stays held in
# debug mode and keeps the network use request, which stops the devkit from uploading
# the crash report. Terminating it releases the request so Nintendo forwards the report.
[void] StopApplication() {
$targetArg = if (-not [string]::IsNullOrEmpty($this.Target)) { " --target $($this.Target)" } else { '' }

Write-Debug "$($this.Platform): Terminating application$targetArg"

$this.InvokeCommand('stop-app', @($targetArg))
}

# Override TakeScreenshot to pass --target when a target is specified
[void] TakeScreenshot([string]$OutputPath) {
$directory = Split-Path $OutputPath -Parent
Expand Down
28 changes: 28 additions & 0 deletions app-runner/Public/Stop-DeviceApp.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
function Stop-DeviceApp {
<#
.SYNOPSIS
Stops the application running on the connected device.

.DESCRIPTION
Terminates the application currently running on the connected device, using the
current device session. Platforms that don't support this write a warning and return.

.EXAMPLE
Connect-Device -Platform Switch
Invoke-DeviceApp -ExecutablePath "Game.nsp" -Arguments "crash-capture"
Stop-DeviceApp

.EXAMPLE
# Terminating may fail if nothing is running - don't let that abort the caller.
try { Stop-DeviceApp } catch { Write-Warning "Failed to stop app: $_" }
#>
[CmdletBinding()]
param()

Assert-DeviceSession

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The Assert-DeviceSession health check may throw an exception on a crashed device, preventing the StopApplication() command from running and defeating the script's purpose.
Severity: HIGH

Suggested Fix

Either modify Stop-DeviceApp to skip or handle the exception from the Assert-DeviceSession check, or change Assert-DeviceSession to use a more robust health check that will not fail in post-crash scenarios.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: app-runner/Public/Stop-DeviceApp.ps1#L22

Potential issue: The `Stop-DeviceApp` script is intended to function after an
application crash. However, it calls `Assert-DeviceSession` on line 22, which performs a
health check by running a `getstatus` command. If a device becomes unresponsive to
`getstatus` after a crash—the exact scenario the script is designed for—the health check
will throw an exception. This exception prevents the script from ever reaching the
`$provider.StopApplication()` call on line 27, defeating the purpose of the new
functionality.

Did we get this right? 👍 / 👎 to inform future reviews.


Write-Debug "Stopping application on platform: $($script:CurrentSession.Platform)"

$provider = $script:CurrentSession.Provider
$provider.StopApplication()
}
1 change: 1 addition & 0 deletions app-runner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ Disconnect-Device

### App Execution
- `Invoke-DeviceApp` - Install and run application (unified command)
- `Stop-DeviceApp` - Terminate the application running on the device

### Device Lifecycle
- `Start-Device` - Power on device
Expand Down
1 change: 1 addition & 0 deletions app-runner/SentryAppRunner.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
'Restart-Device',
'Start-Device',
'Stop-Device',
'Stop-DeviceApp',
'Test-DeviceConnection',
'Test-DeviceInternetConnection'
)
Expand Down
1 change: 1 addition & 0 deletions app-runner/SentryAppRunner.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Export-ModuleMember -Function @(
# Application Management
'Install-DeviceApp',
'Invoke-DeviceApp',
'Stop-DeviceApp',

# Device Lifecycle
'Start-Device',
Expand Down
12 changes: 12 additions & 0 deletions app-runner/Tests/DeviceLifecycle.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ Context 'Stop-Device' {
}
}

Context 'Stop-DeviceApp' {
It 'Should require device session' {
Disconnect-Device
{ Stop-DeviceApp } | Should -Throw '*No active device session*'
}

It 'Should work with active session' {
Connect-Device -Platform 'Mock'
{ Stop-DeviceApp } | Should -Not -Throw
}
}

Context 'Get-DeviceStatus' {
It 'Should require device session' {
Disconnect-Device
Expand Down
1 change: 1 addition & 0 deletions app-runner/Tests/ModuleImport.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Context 'Module Loading' {
'Test-DeviceConnection',
'Test-DeviceInternetConnection',
'Invoke-DeviceApp',
'Stop-DeviceApp',

# Device Lifecycle
'Start-Device',
Expand Down
Loading