Windows Sandbox execution

September 16, 2026 ยท View on GitHub

Build on your machine, then run and automate the app in Windows Sandbox:

winapp run . --on sandbox --detach
winapp ui inspect --on sandbox -a MyApp
winapp ui invoke --on sandbox SubmitButton -a MyApp

Replace MyApp with your app name or the guest PID printed by run. --detach returns after launch so the next command can inspect the app; without it, run waits for the app to exit. The Sandbox stays running between commands and rebuilds.

Before you start

  • Use Windows 11 24H2 or newer on a supported edition, with hardware virtualization enabled.
  • Guest winapp supports x64 and Arm64. An x86 app requires guest support for running it and matching x86 dependencies; an x64 runtime does not satisfy an x86 app.
  • Keep the host session unlocked for real input and screen capture.

Enable Windows Sandbox in Turn Windows features on or off, or run this from an administrator terminal:

dism.exe /Online /Enable-Feature /FeatureName:Containers-DisposableClientVM /All /NoRestart

Save your work and restart Windows when ready. Then open Windows Sandbox from the Start menu and finish any client installation or update. winapp does not enable the feature, install the client, request elevation, or restart Windows. If prerequisites are missing, it stops with setup instructions; an observed pending Windows restart is reported separately.

A cold connection or reconnect can briefly take focus. Once connected, winapp keeps its own client window off-screen without activating it. A Sandbox window you opened yourself is left in place.

Important

Builds still run on your machine. Project evaluation, restore, and compilation are not isolated. --on sandbox does not make an untrusted project safe to build.

One Sandbox is one shared environment. Apps and workflows inside it share the user, desktop, registry, packages, runtimes, and network access. They can observe or interfere with each other. Use separate machines for mutually untrusted workflows.

Windows permits one Sandbox at a time. winapp reuses a running instance, including one you opened yourself. Preparing it adds winapp's shared bootstrap folders, guest agent, Developer Mode, and an inbound firewall rule. winapp does not stop an adopted instance or remove unrelated apps. There is no silent host fallback: a command requesting Sandbox runs there or fails.

Running and rebuilding

winapp run .\MyApp.csproj --on sandbox --detach --json
winapp run .\publish --on sandbox --detach
winapp run . --on sandbox --clean --detach

Build options such as --configuration, --arch, --framework, --property, --no-build, and --no-restore apply on the host. Registration, launch, and debugging happen in the guest; the app is not registered on your machine.

OptionEffect in Sandbox
--detachReturn after launch rather than wait for exit
--no-launchDeploy and register without launching
--cleanReinstall this deployment and clear its application data
--unregister-on-exitRemove this package registration after the app exits
--with-aliasLaunch its guest execution alias with forwarded streams
--debug-outputStream guest debug output; packaged apps only

Unpackaged apps launch their executable from the deployed folder. They have no package to register. --debug-output is rejected for unpackaged Sandbox runs.

Rerunning transfers changed files and removes files deleted from the build output. Application data is preserved unless you request --clean. An incomplete deployment does not launch; retrying rebuilds its guest copy. If build files change while winapp is preparing them, finish the build and retry.

Warm UI commands report only their result, without repeating a Sandbox preparation message. Sandbox startup and connection recovery still report progress. Use --verbose for connection timings and diagnostic details; --quiet and --json suppress progress. JSON runs include a guest process ID and target scope:

{
  "ProcessId": 4212,
  "Sandbox": true,
  "ProcessScope": "sandbox",
  "UiTargetArgs": "--on sandbox -a 4212",
  "ExecutionTarget": {
    "Kind": "sandbox",
    "Id": "default",
    "Architecture": "arm64",
    "Epoch": "..."
  }
}

These are additional fields in the run result, not a separate document. Copy the whole UiTargetArgs value when inspecting the app: winapp ui inspect --on sandbox -a 4212. Rediscover PIDs and window handles after the Sandbox is recreated; they belong to that Sandbox generation, not the host or a future guest.

Detached apps and the agent's lifetime

A detached unpackaged app ends if the guest agent stops, including during agent repair. If it disappears between commands, rerun with --detach and rediscover its UI target. Waiting for the app rather than detaching lets you observe its exit; it does not make the app survive agent loss. Packaged apps use Windows activation rather than the agent's process lifetime. Closing or restarting the Sandbox ends all apps inside it.

Shared runtimes

winapp checks the app's package dependencies, Windows App SDK requirements, and *.runtimeconfig.json before launch. It uses host caches or downloads needed payloads, then installs missing supported runtimes in the guest, not on your machine.

Package requirements include publisher, version, and architecture. Shared .NET runtime selection honors the application's configured roll-forward policy and architecture; do not assume any newer runtime in the same major version will work.

If a framework, runtime configuration, or dependency cannot be supported, the command fails explicitly before launch and identifies the requirement. Follow that error's action. Where supported by your project, publishing self-contained removes the need for the corresponding shared runtime; it does not remove unrelated package dependencies.

Automating the UI

winapp ui list-windows --on sandbox
winapp ui inspect --on sandbox -a MyApp
winapp ui invoke --on sandbox SubmitButton -a MyApp
winapp ui screenshot --on sandbox -a MyApp -o .\result.png

Every ui verb accepts --on sandbox. App names, PIDs, window handles, and selectors are resolved inside the guest. Use -a/--app or -w/--window for app-targeted commands; winapp does not guess the last app launched. Omitting --on sandbox selects your host desktop instead.

Real input and recording require a connected, nonminimized Sandbox client. Read-only inspection can still work when input cannot. winapp can restore its own minimized client without activation; a minimized manually opened client must be restored by you. If input is unavailable after reconnecting, the command fails rather than claiming it delivered input. Use the reconnect command in the error and retry.

See UI automation for selectors, input methods, and assertions.

Coordinating UI workflows in the Sandbox

Use one WINAPP_UI_WORKFLOW_ID for cooperating commands, and a different value for each independent workflow. Set it on every invocation, especially when your agent starts a fresh shell for each tool call. winapp forwards a hashed, Sandbox-generation specific identity; the raw host value is not sent to the guest.

For example, record and interact in two terminals using the same value. Choose a new value for each new workflow.

Terminal 1:

$env:WINAPP_UI_WORKFLOW_ID = 'myapp-checkout-01'
winapp ui record --on sandbox -a MyApp --duration-sec 20 --frames -o .\checkout.mp4

Terminal 2, while the recording is running:

$env:WINAPP_UI_WORKFLOW_ID = 'myapp-checkout-01'
winapp ui invoke --on sandbox SubmitButton -a MyApp
$env:WINAPP_UI_WORKFLOW_ID = 'myapp-checkout-01'
winapp ui inspect --on sandbox -a MyApp

Once both the recording and actions have finished:

$env:WINAPP_UI_WORKFLOW_ID = 'myapp-checkout-01'
winapp ui yield --on sandbox

A named workflow retains its UI turn for four seconds after its last command; yield releases it immediately. Without an ID, each command releases its turn on completion. A no-ID recording therefore blocks other desktop-changing workflows for its duration. Read-only inspection does not wait. Host and guest UI turns are separate.

After a pause, inspect again and reopen any menu or dialog you need: another workflow may have used the guest desktop. Cooperative turns do not isolate apps from each other.

Screenshots and recordings

Use ui capture for an app's window, or target capture for the whole native guest desktop, including the shell and installer dialogs:

winapp ui record --on sandbox -a MyApp --duration-sec 10 --frames -o .\app.mp4
winapp target screenshot sandbox -o .\sandbox.png
winapp target record sandbox --duration-sec 20 --frames -o .\sandbox.mp4

Outputs land on the host, including when you omit -o. Screenshots default to screenshot.png; recordings use recording-<timestamp>-<guid>.mp4. For recordings, --frames also delivers the <output-name>.frames directory containing JPEGs, frames.ndjson, and manifest.json. Results report host paths. Target recordings run in the guest; their host files become available after recording finishes and delivery completes.

target screenshot waits for the guest's UI turn without activating any window. It excludes the host Sandbox window's title bar and borders. Its PNG is unscaled: with guest screen origin (0,0), image coordinates are directly usable by coordinate-input verbs such as ui drag or ui touch --at, with --on sandbox. Add the reported origin for a desktop with a negative origin. Use --json to read coordinates.sourceBounds and coordinates.contentRect; both use physical pixels and exclusive right/bottom edges.

Target recordings report the same fields in JSON and the frame manifest. MP4 and JPEG frames share the mapping, including --max-edge scaling and encoder padding. To map image pixel (x,y), first reject points outside contentRect, then compute each source coordinate as sourceStart + floor((pixel - contentStart + 0.5) * sourceSize / contentSize). Downscaling loses precision; use a native PNG when exact coordinates matter. A change to the guest desktop's bounds stops the recording with display_changed, preserving only frames from before the change and marking the frame manifest partial.

An existing MP4 or paired .frames directory is rejected by default. Use a new path, or pass --overwrite to replace them after the new take finishes. Previous frame bundles are retained as <output-name>.frames.previous-<id>, including when the replacement omits --frames. A failed capture leaves the old recording intact.

Prefer a positive --duration-sec for scripts and agents. The npm uiRecord and targetRecord helpers require durationSec; their abort signal cancels forcefully, not as a graceful stop. See ui record for supported values. Without a CLI duration, recording waits for a stop signal.

Ctrl+C after capture starts can finalize and return a recording successfully with stopReason: cancelled. Other interruptions can preserve useful video or frames. Read stopReason, partialOutput, and recoveryHint when present, and use the reported evidence paths rather than assuming a normal completion. If the whole-desktop capture becomes unavailable during a take, it stops with capture_unavailable rather than continuing to capture an unavailable desktop. It does not bring the Sandbox to the foreground to rescue a frame. Capture can fail before any usable evidence is available.

For a failed guest recording, recovered evidence is placed in a unique <output>.partial-<id> directory on the host. If delivery fails, received files remain under the reported recovery path, such as <output>.recovery-<id>, and guest originals are retained. Keep the Sandbox running and follow the error's recovery action before retrying or closing it. A preserved partial file is not necessarily a playable video.

Screenshots and video may contain sensitive information. Handle the frame directory with the same care as the MP4. See ui record for recording options and result fields.

Inspecting the Sandbox

winapp target snapshot sandbox
winapp target snapshot sandbox --json

This reports readiness, current deployments, and guest windows without creating a VM, reconnecting the client, or repairing the agent. With no Sandbox running, it reports that fact and exits successfully. To start one, use winapp run . --on sandbox --detach.

The report distinguishes what the guest supports from what the current client can do; a minimized client can prevent input or capture even when the guest supports both. Use the guest window list for UI PIDs, not a deployment's tracked launcher process. If several client windows prevent an unambiguous capture, the error lists candidates; decide which to close before retrying.

Running commands and copying files

winapp target exec sandbox -- dotnet --info
winapp target push sandbox .\setup.ps1 Setup\setup.ps1
winapp target exec sandbox --cwd C:\WinApp\work\Setup -- powershell -ExecutionPolicy Bypass -File .\setup.ps1
winapp target pull sandbox Results .\results

Use target exec for setup and diagnostics. It runs as the guest user, forwards standard streams, and returns the command's exit code. It is not a full interactive terminal; console applications see redirected pipes. --json formats winapp errors, not the child command's stdout.

For push and pull, target paths are relative to C:\WinApp\work. Absolute, rooted, and UNC target paths are refused. A single file lands at exactly the destination you name; a directory preserves its structure beneath that destination. Use the resolved guest path printed after a push as the next command's --cwd.

Only run setup scripts you trust. The example uses process-scoped -ExecutionPolicy Bypass because a fresh Sandbox normally refuses scripts under its Restricted policy.

Transfers skip unchanged files and verify replacements before publishing them. Symbolic links and junctions are not followed: deployment rejects them, while directory copies skip linked entries. A directly named linked source or a destination path through a link is refused. Copy the real files or directories instead.

Removing an app and ending the Sandbox

winapp unregister --on sandbox --manifest .\Package.appxmanifest

With a manifest in the current directory, you can omit --manifest. This removes only the matching development package registered by winapp in the current Sandbox. An externally installed package is left alone, even if its identity matches. --force is not supported with --on; it cannot bypass ownership checks. This is manifest-based package cleanup, not an unregister command for unpackaged apps or a .cs input.

The Sandbox remains running. Manage its lifetime with Windows Sandbox's own CLI:

wsb list
wsb connect --id <id>
wsb stop --id <id>

Stopping discards the guest and its work. Save needed evidence first, and obtain the user's consent before stopping an instance they may be using. Later winapp commands can create a fresh Sandbox; rediscover all app targets afterwards.

Troubleshooting

Follow the error's userAction; an advisory nextCommand is a suggestion, not permission to run it automatically. In automation, inspect the structured error.code. Infrastructure failures may exit 70, but an arbitrary application can also return 70; the numeric exit status alone does not distinguish them.

Recovery commands suggested by routed UI operations retain --on <target>, so copying a suggestion keeps it on the same execution target.

Error or symptomWhat to do
sandbox_unsupportedCheck Windows edition/version and firmware virtualization
sandbox_setup_requiredEnable Windows Sandbox using the instructions above, then restart when ready
sandbox_setup_requires_restartWindows reports a pending restart; save work and restart when ready, then retry
sandbox_setup_incompleteOpen Windows Sandbox from Start and finish client setup/update, then retry
sandbox_unmanaged_instance, sandbox_target_ambiguousInspect the reported instances/windows; do not stop unrelated work to resolve ambiguity
sandbox_input_not_ready, sandbox_no_interactive_sessionRestore the existing client or reconnect as directed, then retry
sandbox_agent_incompatibleFollow the version error; upgrade the installed CLI using its installation method if requested, then close/retry only with consent
sandbox_agent_busyWait for another command to finish, then retry
sandbox_terminated, sandbox_target_stale, sandbox_stale_handleRerun the app and rediscover guest PIDs/windows
sandbox_deployment_dirty, sandbox_transfer_interruptedRetry the deployment or transfer
sandbox_runtime_provision_failedResolve the named dependency or unsupported runtime configuration; see Shared runtimes
sandbox_package_conflict, sandbox_provisioned_package_conflictFollow the package-specific action; do not remove unrelated or inbox packages
sandbox_artifact_failedCheck the reported output and client readiness; preserve any partial evidence
target_invalid, target_invalid_argumentsCorrect the target or options shown in the error

winapp update updates project SDK dependencies, not the installed CLI. It is not a fix for host/guest CLI incompatibility.

Share targets in the build 28000 Sandbox

The tested build 28000 Sandbox cannot enumerate Share targets. Test other app features in Sandbox, but validate Share source-to-target flows outside it.

See also