Warning
At this time this project is for educational content only. Interacting directly with the underlying UDK/sandbox services may or may not be supported/recommended by Microsoft. For now, this project is for experimentation only. Proceed with caution!
I was interested in diving deep into the internals of Windows Sandbox (but not as deep as @alex-ilgayev in this article 😄) to learn how it all works. In particular, I was interested in seeing what could be done for persisting the sandbox's longer as well as how we could run multiple sandbox's side-by-side. Initially I discovered the new wsb cli tool and the GRPC server endpoint, new as of 24H2, and couldn't find much out there talking about the internals (only one test project atm). So I figured it was time to explore and write up my own investigation journey.
Overall, I see a ton of use cases. For one, windows sandbox's could be an alternate target for extensions like VS Code's Dev Containers for devs to deploy and debug their software. This would open up soo many doors for dev teams who couldn't even use containers before such as: teams who develop desktop applications, NET framework stacks, or teams who prefer not to deal with docker. I think that last one is particularly interesting since windows sandbox is installed w/o the need for a docker specific dependency (correct me if I'm wrong), albeit windows sandbox's of course are a windows only feature, so no cross-platform support (which is to be expected).
These sort of workflows would require a bit more robust control over sandbox's, such as persistence control and running multiple instances. A general management tool for exposing these experiences seemlessly would be ideal. Dev Home would have been perfect if it weren't for the fact that it is being discontinued in May 2025 (how sad).
Updates 4/14/25:
- Found minor mistake that was preventing us from interacting with our custom sandbox's created directly through UDK. Early disposal of
VMRunningReference
seems to pre-maturely shutdown VM in some capacity. After correcting that, we can now ping our custom sandbox, as well as target it with theInvoke-Command
/Enter-PSSession
PS commands. Additionally, even though we are circumventing the built-in sandbox management features and can't connect to it via thatwsb
cli (i.e.wsb connect
), turns out we can pull in theAxMSTSCLib
COM lib and reference the Remote Desktop ActiveX control within a windows forms project. This is demonstrated via this repo, albeit it relies on dynamically calling the built-in Windows Sandbox logic. I found that when creating the sandbox manually via UDK, I could manually set the pipe endpoint and user/password on the_rdpClient
object which would work to load a RDP connection.- Note: While I could create multiple custom sandbox's, the Remote Desktop ActiveX control only seemed to allow us to connect to the first one. Haven't dug in far enough to explore the extent of the issue nor a workaround.
- Note: Appears our custom windows sandbox are cleaned up after a period of time (5-10 mins?) IF we exit out of our custom program. I suspect this would be a non-issue if we stood up a service/GUI and stored those
VMRunningReference
s for the lifetime of the application.
- Ultimately this latest update confirms that we can startup several of our own custom windows sandbox's, interact with them via a remote shell, but still only connect to the first one via RDP. Since we can execute any command we want on the sandbox, we could just as easily install VNC to fill that purpose.
Updates 4/4/25:
- The below trick for running as admin/non-admin to get multiple sandbox's seems to be patched, as of
MicrosoftWindows.WindowsSandbox_0.5.0.0
(worked previously withMicrosoftWindows.WindowsSandbox_0.4.31.0
). Thewsb
CLI command seems to now behave the same regardless of which elevation the process is running under (this was an expected change). - Creating a sandbox directly using the UDK generated code (i.e. like what the test program in this repo is doing) seems to have some different behavior compared to before when I tested this. Previously I could ping the custom sandbox, however that no longer works and haven't dug into why yet. The rest of the code in the test project still functions, but still unable to interact with the VM in any of our prefered ways (via RDP or remote shell). I suspect there is some sort of process chain whitelist happening at some layer.
- For the built-in sandbox feature, found a way to interact with the VM directly w/o use of the IP or enabling PS remoting on the VM. Essentially we use the
Invoke-Command
/Enter-PSSession
with the-VMId
option. There's more to it for it work, so refer to sample script near bottom. I'm sure this was possible before, I just hadn't thought to try something like this. Mostly what I was curious was if I could use this technique to connect to my custom sandbox created when using the UDK libs directly, but unfortunately it did not work...
For my initial investigation details see my existing posts here and here.
-
Invetigate inner workings of Windows Sandbox
- RDP wrapper UI client:
WindowsSandboxRemoteSession.exe
- Backend server component:
WindowsSandboxServer.exe
- Found GRPC endpoint behind named pipe
\\.\pipe\wsandbox\{USER_SESSION_GUID}
- Can use this to retrieve existing config options, such as password, IP, and other info
- Can use existing
SandboxCommon.Grpc.GrpcClient
fromSandboxCommon.dll
to easily interact with the GRPC endpoint
- VM management uses interop calls to various
WindowsUdk.Security.Isolation.*
objects (auto-generated by CsWinRT viawindowsudk.winmd
?). This appears to point to REG key(s)HKLM\SOFTWARE\Microsoft\WindowsRuntime\ActivatableClassId\WindowsUdk.Security.Isolation.*
which point to%SystemRoot%\system32\windowsudk.shellcommon.dll
. - Creating a VM will start several processes, the server plus:
C:\Windows\System32\ManagedWindowsVM.exe
vmmemWindowsSandbox
- Found GRPC endpoint behind named pipe
- Reviewed new wsb cli tool
- Interacts with GRPC backend
- Of particular interest:
wsb list
,wsb ip --id {ID}
,wsb exec --id {ID} -c {CMD} -r {RUNAS}
wsb exec
only returns exit code, so not particular useful for output, but very powerful for configuring the sandbox w/o enabling any sort of reverse/remote shell. Escaping may be a little tricky, but PS EncodedCommand works perfecly here if need be.- Determined that we can start sandbox's and keep them alive indefinetely (until a host reboot at least). If we use
wsb start
followed bywsb connect --id {ID}
, closing the UI window will no longer destroy the sandbox. We have to initiate awsb stop --id {ID}
to destory the sandbox.
- See example PS script below for using the
SandboxCommon.Grpc.GrpcClient
,wsb
cli tool, and PS remoting together - Investigating running multiple sandbox's
- Discovered that starting an elevated and non-elevated shell will produce different
USER_SESSION_GUID
's, therefore allowing us to run two sandbox's simulatenously (major caveat*) - Additionally, we can use a tool like PsExec w/
-s
switch to run a shell asNT AUTHORITY\SYSTEM
user, which can further create a third distinct sandbox (major caveat*) - Found
AreMultipleInstancesAllowed
boolean used byWindowsSandboxServer.exe
, therefore there is built-in support for running multiple sandboxes under the current user session (w/o need for above tricks). However this bool seems to be feature locked. It will only return true if either of these conditions are met:- REG Key HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion has BuildBranch set to something that satisfies the regex ^(rs(?!.*release).+). In my case my current one is set to ge_release, so updating that value to something like rs_beta could work, although this is probably a bad idea as we don't really know what other software out there is relying on this value being set a certain way. Note that I never did actually attempt to modify my reg key to avoid any issues. I did however modify it within a sandbox instance just to make sure nothing bad would immediately happen, and while nothing actually did, I still wouldn't recommend it! Only do this if you're ok with things possibly going terribly wrong, and you can recover.
- Result of SLGetWindowsInformationDWORD for product TerminalServices-RemoteConnectionManager-WVD-Enabled does not equal zero. The underlying code issues a PInvoke to SLC.dll to get the result into a out variable. While I'm not 100% certain what this is checking, what I suspect it is looking to see if a certain terminal services feature is licensed and is enabled. On my system, that value is zero, and I couldn't find an easy way to enable or modify it. This lead me down a rabbit hole that took me to a code project blog that shows how to list out this data, but I couldn't pursue it any further because code project seems to be no more and the wayback machine doesn't seem to archive the download files (see here, plus my head was starting to hurt anyway 🤕).
- Discovered that starting an elevated and non-elevated shell will produce different
- RDP wrapper UI client:
-
Investigate interfacing with UDK directly w/o reliance on any
WindowsSandbox*.dll
orSandbox*.dll
references- See
WindowsSandboxUDKTest
project for example setup- Most code mimicks similiar configuration applied by
WindowsSandboxServer
- Most code mimicks similiar configuration applied by
- The core mechanisms are controlled by the UDK library. Windows Sandbox wraps this into the
SandboxUDK.dll
. That library appears to be auto-generated, therefore we can interface with UDK by generating our own projection using CsWinRT.- Main requirement is we must have the WINMD file that contains the
WindowsUdk.Security.Isolation.*
metadata:windowsudk.winmd
- Just so happens
windowsudk.winmd
can be found in theMicrosoftWindows.UndockedDevKit
system app directory, which can be determined withGet-AppPackage -name "MicrosoftWindows.UndockedDevKit"
(checkWindowsSandboxUDKTest
folder for10.0.26100
/24H2
version). WindowsUdk.Security.Isolation.ManagedWindowsVM
is the star of the show (seeWindowsSandboxUDKTest
for implementation details)
- Main requirement is we must have the WINMD file that contains the
- Observations:
- Since we are creating everything directly, the GRPC named pipe and
wsb
cli tool do not apply and cannot help us in any way (these areWindowsSandboxServer
specific dependencies). However, this can benefit us greatly since we can choose to run and manage multiple sandbox's simultaneously. - While executing the program does actually create a sandbox which can be pinged and SYSTEM commands executed against it, I was unable to properly configure the default user. Executing the
CreateProcess(_vm, _runningReference, cmd)
would throw system exceptions when targeting theSpecifiedUser
ofWDAGUtilityAccount
. I suspect I'm missing something very simple (for another day...) - Command for enabling remote PS does seem to execute w/o error, but invoking a remote command still fails. Contrast that to the built-in Windows Sandbox's which I can enable and invoke remote PS no problem. Again, I suspect something simple is missing (also for another day...)
- I have not specifically tried to RDP over any protocols. Using @smourier's WinformsSandbox project might help, although I believe it relies on the named pipe setup by
WindowsSandboxServer
, which doesn't exists when we run our own. So RDP TBD. - If we don't use the
ManagedWindowsVM.Terminate()
command, the sandbox will persist even if theWindowsSandboxUDKTest
program exits. However, allIsolation.ManagedWindowsVM
instances will be destroyed if either a) the host reboots, or b) theManagedWindowsVM.exe
process is killed. - We can interact with an existing sandbox (even ones created by
WindowsSandboxServer
) by using the VM ID (shown as the username of the process in task manager). This would even allow us to execute processes on the VM. So in theory if we persist the VM ID's in some sort of storage medium, we can interact with previously created sandbox's in between process restarts. One downside being that it doesn't seem possible to retrieve the originalVMOptions
(unless we store that too).
- Since we are creating everything directly, the GRPC named pipe and
- See
* See initial invesitgation links for details on caveats
Script to start a new sandbox, enable PS remoting, retrieve the auto-generated IP & password, and finally invoke a remote cmd:
# Get path from: Get-AppxPackage *WindowsSandbox*
using assembly "{path_to}\MicrosoftWindows.WindowsSandbox_0.5.0.0_x64__cw5n1h2txyewy\SandboxCommon.dll"
wsb start
[guid]$sandboxId = wsb list
$client = [SandboxCommon.Grpc.GrpcClient]::new()
$config = $client.GetRdpClientConfigAsync($sandboxId).Result
wsb exec --id $sandboxId -r SYSTEM -c 'powershell -c Enable-PSRemoting -force -SkipNetworkProfileCheck'
$sandboxIp = wsb ip --id $sandboxId
$cred = New-Object System.Management.Automation.PSCredential ("WDAGUtilityAccount", (ConvertTo-SecureString ($config.Password) -AsPlainText -Force))
Invoke-Command -ComputerName $sandboxIp -Credential $cred -ScriptBlock { Write-Host "Hello from sandbox!"; whoami; hostname; }
# OR # Enter-PSSession -ComputerName $sandboxIp -Credential $cred
wsb stop --id $sandboxId
Similiar script as above, but w/o using IP or enabling PS remoting:
# Get path from: Get-AppxPackage *WindowsSandbox*
using assembly "{path_to}\MicrosoftWindows.WindowsSandbox_0.5.0.0_x64__cw5n1h2txyewy\SandboxCommon.dll"
wsb start
[guid]$sandboxId = wsb list
$client = [SandboxCommon.Grpc.GrpcClient]::new()
$config = $client.GetRdpClientConfigAsync($sandboxId).Result
$vmId = $config.VMId
$cred = New-Object System.Management.Automation.PSCredential ("WDAGUtilityAccount", (ConvertTo-SecureString ($config.Password) -AsPlainText -Force))
# It's possible to use `Invoke-Command`/`Enter-PSSession` directly w/o enabling remote PS for a sandbox. Requires
# overriding existing `Get-VM` cmdlet w/ VM details (which is what these commands rely on). There is probably
# a better way to do this, but the internal mechanisms of `Invoke-Command` seem locked down.
function Get-VM($Id) {
# VMName doesn't seem to matter in this case
@( [PSCustomObject]@{ VMName="WinSbx"; VMId=[guid]::Parse($id); State=2; } )
# TODO: query `wsb list` and append to existing cmdlet results?
}
Invoke-Command -VMId $vmId -Credential $cred -ScriptBlock { Write-Host "Hello from sandbox!"; whoami; hostname; }
# OR # Enter-PSSession -VMId $vmId -Credential $cred
wsb stop --id $sandboxId