Creating a development Windows Sandbox using PowerShell and WinGet

As I mentioned before, I like Windows Sandbox! However, since Windows 11 24H2, Windows Sandbox has been missing two things I often use: Notepad and Windows ISE. In this blog post, I will show you how to start Windows Sandbox and automatically install your preferred editors and tools using WinGet and PowerShell.

What is Windows Sandbox?

“Windows Sandbox provides a lightweight desktop environment to safely run applications in isolation. Software installed inside the Windows Sandbox environment remains “sandboxed” and runs separately from the host machine.

A sandbox is temporary. When it’s closed, all the software and files and the state are deleted. You get a brand-new instance of the sandbox every time you open the application. Note, however, that as of Windows 11, version 22H2, your data persists through a restart initiated from inside the virtualized environment—useful for installing applications that require the OS to reboot.

Software and applications installed on the host aren’t directly available in the sandbox. If you need specific applications available inside the Windows Sandbox environment, they must be explicitly installed within the environment.”

Source: https://learn.microsoft.com/en-us/windows/security/application-security/application-isolation/windows-sandbox/windows-sandbox-overview

How to install Windows Sandbox

It’s a Windows Feature that is available starting from Windows Pro. To install it, you can follow these steps:

Using the GUI

  • Go to Optional Features (You can search for that in the Windows Settings menu)
  • Click on More Windows features
  • Select Windows Sandbox and click Ok

Using PowerShell

You can add the Windows optional feature by starting an Admin PowerShell session and running:

Enable-WindowsOptionalFeature -Online -FeatureName "Containers-DisposableClientVM" -All

Windows will install the component, reboot (close/save all programs first), and restart your system. After logging in again, you will have the Windows Sandbox app in your menu:

You can start it now, and in a few seconds, you will have a clean version of Windows 10/11 (Depending on your own Windows version) in which you can do anything you want. When closed, it will all be gone and fresh when starting again.

Adding software to Windows Sandbox

Now that your Windows Sandbox is ready, you can start thinking about what you would like to be automatically installed when you start it. (Because it’s a disposable Sandbox VM, it always starts fresh, and everything is gone.) For me, having Visual Studio Code and Notepad++ are important. Let’s use that as an example; because we’re using WinGet, it’s easy to expand that.

Windows Sandbox allows you to map a local folder in the VM, which you can use for the installation script. It also allows a Logon Command during startup, which starts the installation script from the mapped folder.

How the scripts work

The scripts will create a WSB configuration file that starts Windows Sandbox. In that file, the folder containing the installation script and the Logon Command is specified, which will execute the script when Windows Sandbox’s Desktop is started. That Logon Command includes the installation of WinGet together with the package names it will install. That list is customizable, but I will install Visual Studio Code and Notepad++ in this example.

The Start-DevelopmentWindowsSandbox.ps1 script has two parameters that you can use to configure a location other than C:\WindowsSandbox and an install script other than Install_WinGet_and_Software.ps1:

  • MappedFolder: You can use -MappedFolder to specify the folder on your system that contains the installation script for WinGet and the required software. The default is C:\WindowsSandbox. The script will exit if the specified folder or the default C:\WindowsSandbox folder doesn’t exist.
  • LogonCommand: You can use -LogonCommand to specify the PowerShell script that will run when the Windows Sandbox is started and install WinGet and the software specified in that script. The default is Install_WinGet_and_Software.ps1. The script will exit if the specified file, or the default Install_WinGet_and_Software.ps1, doesn’t exist in the folder specified by the MappedFolder parameter.

The Install_WinGet_and_Software.ps1 will download the required software components for WinGet in the Windows Sandbox. Afterward, it will install the software specified in the $SoftwareToInstall variable. You can find the correct names using Winget Search NameOfSoftware, WinGet search VSCode, for example, will return Microsoft.VisualStudioCode.

Note: WinGet in Windows Sandbox can only install software from the winget source. The msstore source doesn’t work because it’s inaccessible from Windows Sandbox.

Save both scripts in C:\WindowsSandbox and start as shown in the chapter below from that folder to start Windows Sandbox for the first time. You can execute the created Windows Sandbox configuration file WindowsSandbox.wsb from that folder to start it immediately without making the configuration file again, and it contains the path that will be mounted on the Desktop folder of Windows Sandbox together with the LogonCommand to start the installation of the software. (Create a shortcut for it in your Start menu or Taskbar)

Running the scripts

In the example below, I use the Start-DevelopmentWindowsSandbox.ps1 script (I reused parts of the script from my previous blog post about starting Windows Sandbox with parameters, which can be found here) to start Windows Sandbox and install Notepad++ and Visual Studio Code.

.\Start-DevelopmentWindowsSandbox.ps1
Saved configuration in C:\WindowsSandbox\WindowsSandbox.wsb and Starting Windows Sandbox...
Done!

After starting the script, Windows Sandbox will start, and on the Windows Desktop, you will see the folder name of the MappedFolder. After a few seconds, the LogonCommand will begin running in the background. You will not see the progress because it’s running as a System; I solved that by using Start-Transcript, which outputs its progress log to the desktop in an Installing.txt file 🙂

When the installation is done, which could take a while, depending on the amount of software specified in the $SoftwareToInstall variable and the speed of installing WinGet, the Installing.txt will be renamed to Done.txt.

And the software is installed 🙂

The contents of the Done.txt Transaction log will look like this:

Wrapping up

In the chapters above, I showed you how to use the scripts to start a fresh Windows Sandbox with your software automatically installed. You could also use a Hyper-V VM on your laptop to revert to a snapshot after testing things on that VM. I find using Windows Sandbox easier and cleaner, and the scripts are portable to another computer without copying and importing a large VM. And it saves disk space on your device, too 🙂

And if you’re using Recast in your environment, check out the adjusted script mentioned here: https://www.recastsoftware.com/resources/windows-sandbox-powershell-and-application-workspace-for-application-testing/ (Also available in my GitHub as AW_Sandbox.ps1)

The scripts

Below are the scripts I created. Save them to C:\WindowsSandbox (Or another folder if desired. Specify that folder using the -MappedFolder parameter or edit the script to change the default value)

Start-DevelopmentWindowsSandbox.ps1

param(
    [parameter(Mandatory = $false)][string]$MappedFolder = 'C:\WindowsSandbox',
    [parameter(Mandatory = $false)][string]$LogonCommand = 'Install_WinGet_and_Software.ps1'
)
#Check if Windows Sandbox is already running. Exit if yes
if (Get-Process -Name 'WindowsSandbox' -ErrorAction SilentlyContinue) {
    Write-Warning ("Windows Sandbox is already running, exiting...")
    return
}
#Validate if $mappedfolder exists
if ($MappedFolder) {
    if (Test-Path $MappedFolder -ErrorAction SilentlyContinue) {
        Write-Host ("Specified {0} path exists, continuing..." -f $MappedFolder) -ForegroundColor Green
    }
    else {
        Write-Warning ("Specified {0} path doesn't exist, exiting..." -f $MappedFolder)
        return
    }
}
#Create .wsb config file, overwrite  existing file if present and check if specified logoncommand exist
$wsblocation = "$($MappedFolder)\WindowsSandbox.wsb"
if (-not (Test-Path "$($MappedFolder)\$($LogonCommand)")) {
    Write-Warning ("Specified LogonCommand {0} doesn't exist in {1}, exiting..." -f $MappedFolder, $LogonCommand)
    return
}
Tee-Object -FilePath $wsblocation -Append:$false
$wsb = @()
$wsb += "<Configuration>"
$wsb += "<MappedFolders>"
$wsb += "<MappedFolder>"
$wsb += "<HostFolder>$($MappedFolder)</HostFolder>"
$wsb += "<ReadOnly>true</ReadOnly>"
$wsb += "</MappedFolder>"
$wsb += "</MappedFolders>"
$LogonCommandFull = 'Powershell.exe -ExecutionPolicy bypass -File C:\users\wdagutilityaccount\desktop\' + $(Get-childitem -Path $($wsblocation) -Directory).Directory.Name + '\' + $logoncommand
$wsb += "<LogonCommand>"
$wsb += "<Command>$($LogonCommandFull)</Command>"
$wsb += "</LogonCommand>"
$wsb += "</Configuration>"
    
#Create sandbox .wsb file in $mappedfolder and start Windows Sandbox using it
$wsb | Out-File $wsblocation -Force:$true
Write-Host ("Saved configuration in {0} and Starting Windows Sandbox..." -f $wsblocation) -ForegroundColor Green
Invoke-Item $wsblocation
Write-Host ("Done!") -ForegroundColor Green

Install_WinGet_and_Software.ps1

#Install WinGet, used https://learn.microsoft.com/en-us/windows/package-manager/winget/#install-winget-on-windows-sandbox
Start-Transcript C:\users\wdagutilityaccount\desktop\Installing.txt
$progressPreference = 'silentlyContinue'
Write-Information "Downloading WinGet and its dependencies..."
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force:$true -Verbose
Install-Module Microsoft.WinGet.Client -Force:$true -Confirm:$false -Verbose
Import-Module Microsoft.WinGet.Client -Verbose
Repair-WinGetPackageManager -Force:$true -Latest -Verbose

#Install software
$SoftwareToInstall = "Notepad++.Notepad++", "Microsoft.VisualStudioCode"
foreach ($Software in $SoftwareToInstall) {
    WinGet.exe install $software --silent --force --accept-source-agreements --disable-interactivity --source winget
}
Stop-Transcript
Rename-Item -Path C:\users\wdagutilityaccount\desktop\Installing.txt -NewName C:\users\wdagutilityaccount\desktop\Done.txt

Download the script(s) from GitHub here.

20 thoughts on “Creating a development Windows Sandbox using PowerShell and WinGet

  1. You mentioned Windows 11/24H2’s Sandbox is lacking Notepad and ISE, so I guess this article doesn’t apply to us Win10/22H2 users – is that correct?

    1. Lacking those two gave me the idea to create this solution. You can still use it and install other software in Windows Sandbox. But there’s no need if you run older versions of Windows if you just need a simple editor as notepad and ISE. It depends on your needs 😊

  2. Thank you, I think more developers must make use of windows sandbox. As a reference for other readers: in my version of the ps1 installer code, I changed the temp folder with a static folder to not redownload each time and then commented the download out totally after 1 run.
    Thank you again

    1. Yes, they should and nice! That’s better😅But I do think that downloading the installer every time makes sure that you have the latest version, that might be a problem if big changes occur in WinGet

  3. Hey Harm

    This have stopped working with Sandbox on 24H2 – was working fine until a week ago.
    Looks like some dependancy to VCLibs version?

    Logs here:

    Windows PowerShell transcript start
    Start time: 20241106120635
    Username: F4EE291F-8852-4\WDAGUtilityAccount
    RunAs User: F4EE291F-8852-4\WDAGUtilityAccount
    Configuration Name:
    Machine: F4EE291F-8852-4 (Microsoft Windows NT 10.0.26100.0)
    Host Application: powershell.exe -executionpolicy bypass -file C:\Users\pbje\OneDrive – COMM2IG_SandBox\setup.ps1
    Process ID: 6744
    PSVersion: 5.1.26100.1591
    PSEdition: Desktop
    PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.26100.1591
    BuildVersion: 10.0.26100.1591
    CLRVersion: 4.0.30319.42000
    WSManStackVersion: 3.0
    PSRemotingProtocolVersion: 2.3
    SerializationVersion: 1.1.0.1

    Transcript started, output file is C:\users\wdagutilityaccount\desktop\Installing.txt
    INFO: Downloading WinGet and its dependencies…
    Add-AppxPackage : Deployment failed with HRESULT: 0x80073CF3, Package failed updates, dependency or conflict validation.

    Windows cannot install package Microsoft.DesktopAppInstaller_1.24.25180.0_x64__8wekyb3d8bbwe because this package
    depends on a framework that could not be found. Provide the framework “Microsoft.VCLibs.140.00.UWPDesktop” published by
    “CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US”, with neutral or x64 processor
    architecture and minimum version 14.0.33728.0, along with this package to install.

    NOTE: For additional information, look for [ActivityId] 0c4a3897-2c2f-0011-7f53-4a0c2f2cdb01 in the Event Log or use
    the command line Get-AppPackageLog -ActivityID 0c4a3897-2c2f-0011-7f53-4a0c2f2cdb01

    At C:\Users\xxx\OneDrive_SandBox\setup.ps1:32 char:1
    + Add-AppxPackage $env:temp\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : WriteError: (C:\Users\WDAGUt…bbwe.msixbundle:String) [Add-AppxPackage], IOException
    + FullyQualifiedErrorId : DeploymentError,Microsoft.Windows.Appx.PackageManager.Commands.AddAppxPackageCommand
    Add-AppxPackage : Deployment failed with HRESULT: 0x80073CF3, Package failed
    updates, dependency or conflict validation.

    Windows cannot install package
    Microsoft.DesktopAppInstaller_1.24.25180.0_x64__8wekyb3d8bbwe because this
    package depends on a framework that could not be found. Provide the framework
    “Microsoft.VCLibs.140.00.UWPDesktop” published by “CN=Microsoft Corporation,
    O=Microsoft Corporation, L=Redmond, S=Washington, C=US”, with neutral or x64
    processor architecture and minimum version 14.0.33728.0, along with this
    package to install.

    NOTE: For additional information, look for [ActivityId]
    0c4a3897-2c2f-0011-7f53-4a0c2f2cdb01 in the Event Log or use the command line
    Get-AppPackageLog -ActivityID 0c4a3897-2c2f-0011-7f53-4a0c2f2cdb01

    At C:\Users\xxx\OneDrive_SandBox\setup.ps1:32 char:1
    + Add-AppxPackage $env:temp\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : WriteError: (C:\Users\WDAGUt…bbwe.msixbundle:S
    tring) [Add-AppxPackage], IOException
    + FullyQualifiedErrorId : DeploymentError,Microsoft.Windows.Appx.PackageMa
    nager.Commands.AddAppxPackageCommand

      1. Updated the blog post and GitHub with the new installation script, WinGet now gets installed using the Microsoft.WinGet.Client module and the Repair-WinGetPackageManager cmdlet in that. That will download and install all the dependencies, there have been some changes in publishing the VCLib dependencies making it difficult to install it like I could previously.

  4. Thanks for your work. I just want to add my experiences:

    I tried the script and resulting wsb file several times today.
    With mixed results. It seems to work about 33 % of the time.
    The other times I need to re-run the install script after the Sandbox spawned or it just doesn’t work at all.

    So this feature seems pretty handy. And pretty unstable 🙁

      1. Yes, I use Windows 11 24H2

        But I bet Sandbox will work flawlessly on the next release …

      2. It did it a lot better with 23H2, for example. I don’t have internet after starting it, have to change DNS servers to get it to work, for example. But could you tell me more about the things you encounter? Re-Run install script inside the Windows Sandbox, you mean?

      3. Sorry, somehow the reply button for your last comment is not visible.

        Most of the time, the script hangs trying to repair the winget installation. Nothing changes after that.

        When I try to run the Install_WinGet_and_Software.ps1 script in the sandbox it works like half of the time and I get winget working and software installed.

        Without intervention I mostly get stuck at this place and nothing happens after that (log excerpt):

        VERBOSE: Running winget.exe with –version
        VERBOSE: ‘winget.exe’ Win32Exception Das System kann die angegebene Datei nicht finden
        PS>winget
        The term ‘winget’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
        VERBOSE: Executing Appx cmdlet Get-AppxPackage -Name Microsoft.DesktopAppInstaller
        VERBOSE: Integrity category type: AppInstallerNotInstalled
        VERBOSE: Downloading https://github.com/microsoft/winget-cli/releases/download/v1.10.340/DesktopAppInstaller_Dependencies.json
        VERBOSE: Size 222 bytes
        VERBOSE: Executing Appx cmdlet Get-AppxPackage -Name Microsoft.VCLibs.140.00.UWPDesktop
        VERBOSE: Executing Appx cmdlet Get-AppxPackage -Name Microsoft.UI.Xaml.2.8
        VERBOSE: Downloading https://github.com/microsoft/winget-cli/releases/download/v1.10.340/DesktopAppInstaller_Dependencies.zip
        VERBOSE: Size 49831448 bytes
        VERBOSE: Executing Appx cmdlet Add-AppxPackage -Path C:\Users\WDAGUtilityAccount\AppData\Local\Temp\5kdotgw5.0as\x64\Microsoft.VCLibs.140.00.UWPDesktop_14.0.33728.0_x64.appx -ErrorAction Stop
        VERBOSE: Executing Appx cmdlet Add-AppxPackage -Path C:\Users\WDAGUtilityAccount\AppData\Local\Temp\5kdotgw5.0as\x64\Microsoft.UI.Xaml.2.8_8.2501.31001.0_x64.appx -ErrorAction Stop
        VERBOSE: Executing Appx cmdlet Add-AppxPackage -Path https://github.com/microsoft/winget-cli/releases/download/v1.10.340/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle -ErrorAction Stop -StubPackageOption UsePreference -ForceTargetApplicationShutdown

      4. Ok, so it’s not starting the Windows Sandbox VM but more the getting WinGet installed using the repair option after installing the Microsoft.WinGet.Client Powershell module… I will try to do some more testing for that today

  5. I updated the script from “Repair-WinGetPackageManager -Force:$true -Verbose” to “Repair-WinGetPackageManager -Force:$true -Latest -Verbose” (Added -Latest). It fixed it for me, I ran the script three times without and installation failed. With the parameter, it didn’t and hope it also works for you. Updated GitHub and blog post to reflect that change.

Leave a Reply to StephanCancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.