Search notes:

Powershell module: Rene.wsb

Rene.wsb is a Powershell module for Windows Sandbox

Functions

start-wsb Find a few examples for using this function here.
start-wsbWithNotepad Copies the host's notepad.exe to the sandbox's C:\Windows\System32 directory so that notepad can be run in the sandbox. See also No Notepad in Windows Sandbox.

Source code

wsb.psm1

function start-wsb {
 #
 # start-wsb creates a .wsb file in the temp directory whose
 # content is controlled by this function's parameters and
 # then launches the sandbox.
 #
   param(
       [int   ] $memoryInMB = $null,

     # Features that are enabled by default
       [switch] $disableVGpu,
       [switch] $disableNetworking,
       [switch] $disableAudioInput,
       [switch] $disableClipboardRedirection,

     # Features that are disabled by default
       [switch] $enableVideoInput,
       [switch] $enableProtectedClient,
       [switch] $enablePrinterRedirection,

       [string] $logonCommand     = $null,
       [string] $visiblePsCommand = $null,
       [string] $psScript         = $null,

       [array ] $mappedFolders    = @(),

       [switch] $keepWsb
   )

 #
 # The WindowsSandbox.exe process might be running even when
 # no sandbox is active. Therefore checking for
 # WindowsSandboxClient.exe
 #
   $runningSandbox = get-process -name WindowsSandboxClient -errorAction silentlyContinue

   if ($runningSandbox) {
      write-host 'Windows Sandbox is already running.' -foregroundColor red
      return
   }

  [xml] $xml = '<Configuration></Configuration>'

   function add-config {
       param(
           [System.Xml.XmlElement] $Parent,
           [string               ] $ElementName,
           [string               ] $Value
       )
       if ($Value) {
           $newElement = $xml.CreateElement($ElementName)
           $newElement.InnerText = $Value
           $Parent.AppendChild($newElement) | Out-Null
       }
   }

 # Use DocumentElement to avoid the string conversion issue
   $root = $xml.DocumentElement

   if ($psBoundParameters.ContainsKey('memoryInMB')) { add-config $root 'MemoryInMB' $memoryInMB }

   if ($disableVGpu                ) { add-config $root 'VGpu'                 'Disable' }
   if ($disableNetworking          ) { add-config $root 'Networking'           'Disable' }
   if ($disableAudioInput          ) { add-config $root 'AudioInput'           'Disable' }
   if ($disableClipboardRedirection) { add-config $root 'ClipboardRedirection' 'Disable' }
   if ($enableVideoInput           ) { add-config $root 'VideoInput'           'Enable'  }
   if ($enableProtectedClient      ) { add-config $root 'ProtectedClient'      'Enable'  }
   if ($enablePrinterRedirection   ) { add-config $root 'PrinterRedirection'   'Enable'  }

   $cmd  = $logonCommand

   if ($psScript) {
    #
    # Somewhat convoluted addition of the new folder because
    # mappedfolder is declared with [array] which makes it
    # fixed size
    #
       $psScript_ = resolve-path $psScript 
       $mappedFolders = @($mappedFolders) + @(
         @{ hostFolder    = split-path $psScript_
            sandboxFolder ='c:\scriptDir'
            readOnly      = $true
          }
      )

    #
    # executionPolicy is set to prevent
    #    File C:\scriptDir\...ps1 cannot be loaded because running scripts is disabled on this system.
    #    
      $cmd = "cmd /c `"start powershell.exe -noLogo -noExit -executionPolicy unrestricted -file c:/scriptDir/$(split-path $psScript_ -leaf)`""
   }
   elseif ($visiblePsCommand) {
      $cmd = $visiblePsCommand -replace '"', '\"'

    #
    # If cmd is a cmdlet (such as get-date), the -executionPolicy parameter is not needed.
    # It is needed however, if cmd is the path of a script.
    #
      $cmd = "cmd /c `"start powershell.exe -noLogo -noExit -executionPolicy unrestricted -command $cmd`""
   }

 # MappedFolders
   if ($mappedFolders.count -gt 0) {
       $mappedFoldersElem = $xml.CreateElement('MappedFolders')

       foreach ($folder in $mappedFolders) {
           $mf = $xml.CreateElement('MappedFolder')

           add-config $mf 'HostFolder' $folder.HostFolder

           if ($folder.SandboxFolder) {
               add-config $mf 'SandboxFolder' $folder.SandboxFolder
           }

           $readOnlyVal = if ($folder.ReadOnly) { 'true' } else { 'false' }
           add-config $mf 'ReadOnly' $readOnlyVal

           $mappedFoldersElem.AppendChild($mf) | Out-Null
       }
       $root.AppendChild($mappedFoldersElem) | Out-Null
   }

 # LogonCommand
   if ($cmd) {
       $logonElem = $xml.CreateElement('LogonCommand')
       $cmdElem   = $xml.CreateElement('Command')
       $cmdElem.InnerText = $cmd

       $null = $logonElem.AppendChild($cmdElem)
       $null = $root.AppendChild($logonElem)
   }

   $wsbContent = $xml.OuterXml

 # Create temporary .wsb file and launch sandbox
   $tempWsbPath = join-path $env:temp "sandbox_$(get-date -format 'yyyyMMdd_HHmmss').wsb"

   $wsbContent | out-file -filePath $tempWsbPath -encoding UTF8 -force
   write-host "Created temporary config: $tempWsbPath" -foregroundColor Green

   write-host "Starting Windows Sandbox..." -foregroundColor Cyan
   invoke-item -literalPath $tempWsbPath

   start-sleep -seconds 4

   if (! $keepWsb ) {
    # Remove wsb file
      write-host "Deleting temporary .wsb file..." -foregroundColor Yellow
      remove-item -path $tempWsbPath -force -errorAction stop
      write-host "Temporary .wsb file deleted successfully." -foregroundColor green
   }
}

function start-wsbWithNotepad {
 #
 # start-wsbWithNotepad maps the host System32 folder into the
 # sandbox and copies the host's notepad.exe to the sandbox's
 # System32 directory.
 #

   $lang = [System.Globalization.CultureInfo]::CurrentUICulture.Name
   write-host "lang = $lang"

   $mappedFolders = @(
      @{
         hostFolder    = 'C:\Windows\System32'
         sandboxFolder = 'C:\host\Windows\System32'
         readOnly      = $true
      }
   )

   $psCmd = 'copy-item C:\host\Windows\System32\notepad.exe C:\Windows\System32\notepad.exe'

   $encodedCopyScript = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($psCmd))
   $logonCommand      = "powershell.exe -noLogo -executionPolicy unrestricted -encodedCommand $encodedCopyScript"

   start-wsb -mappedFolders $mappedFolders -logonCommand $logonCommand

}
Github repository ps-module-wsb, path: /Rene.wsb.psm1

wsb.psd1

@{
   rootModule         = 'Rene.wsb.psm1'
   moduleVersion      = '2026.08.28'

   author             = 'René Nyffenegger'
   description        = 'A PowerShell module for Windows Sandbox'

   functionsToExport  = @(
     'start-wsb'
     'start-wsbWithNotepad'
   )

   guid               = '52656e65-0077-0073-0062-0123456789ab'
}
Github repository ps-module-wsb, path: /Rene.wsb.psd1

See also

The source code on github.
Because of a nameclash, the module is published under the name Rene.wsb.
René's simple PowerShell modules

Index

Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 8 attempt to write a readonly database in /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php:51 Stack trace: #0 /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php(51): PDOStatement->execute(Array) #1 /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php(66): id_of(Object(PDO), 'uri', '/notes/Windows/...') #2 /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php(30): insert_webrequest_('/notes/Windows/...', 1788365019, '216.73.217.21', 'Mozilla/5.0 App...', NULL) #3 /home/httpd/vhosts/renenyffenegger.ch/httpsdocs/notes/Windows/PowerShell/modules/personal/wsb/index(259): insert_webrequest() #4 {main} thrown in /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php on line 51