windows-development-sandbox 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (413) hide show
  1. package/config.example.json +10 -0
  2. package/dist/app/context.d.ts +8 -0
  3. package/dist/app/context.js +12 -0
  4. package/dist/app/features/defs.d.ts +322 -0
  5. package/dist/app/features/defs.js +28 -0
  6. package/dist/app/features/sandbox/index.d.ts +2 -0
  7. package/dist/app/features/sandbox/index.js +3 -0
  8. package/dist/app/features/sandbox/sandbox.def.d.ts +193 -0
  9. package/dist/app/features/sandbox/sandbox.def.js +245 -0
  10. package/dist/app/features/sandbox/sandbox.run.d.ts +72 -0
  11. package/dist/app/features/sandbox/sandbox.run.js +98 -0
  12. package/dist/app/features/sessions/index.d.ts +2 -0
  13. package/dist/app/features/sessions/index.js +3 -0
  14. package/dist/app/features/sessions/sessions.def.d.ts +141 -0
  15. package/dist/app/features/sessions/sessions.def.js +176 -0
  16. package/dist/app/features/sessions/sessions.run.d.ts +39 -0
  17. package/dist/app/features/sessions/sessions.run.js +58 -0
  18. package/dist/{cli/menu/settings/categories.d.ts → app/features/settings/categories.def.d.ts} +2 -0
  19. package/dist/{cli/menu/settings/categories.js → app/features/settings/categories.def.js} +24 -5
  20. package/dist/app/features/settings/index.d.ts +5 -0
  21. package/dist/app/features/settings/index.js +6 -0
  22. package/dist/{cli/menu/settings/config-file.d.ts → app/features/settings/settings-file.d.ts} +1 -1
  23. package/dist/{cli/menu/settings/config-file.js → app/features/settings/settings-file.js} +1 -1
  24. package/dist/app/features/settings/settings-rows.d.ts +35 -0
  25. package/dist/app/features/settings/settings-rows.js +94 -0
  26. package/dist/app/features/settings/settings.def.d.ts +157 -0
  27. package/dist/app/features/settings/settings.def.js +179 -0
  28. package/dist/app/features/settings/settings.run.d.ts +67 -0
  29. package/dist/app/features/settings/settings.run.js +84 -0
  30. package/dist/app/kernel/define.d.ts +143 -0
  31. package/dist/app/kernel/define.js +42 -0
  32. package/dist/app/kernel/errors.d.ts +34 -0
  33. package/dist/app/kernel/errors.js +45 -0
  34. package/dist/app/kernel/fields.d.ts +70 -0
  35. package/dist/app/kernel/fields.js +243 -0
  36. package/dist/app/kernel/index.d.ts +8 -0
  37. package/dist/app/kernel/index.js +10 -0
  38. package/dist/app/kernel/io.d.ts +193 -0
  39. package/dist/app/kernel/io.js +126 -0
  40. package/dist/app/kernel/rpc.d.ts +156 -0
  41. package/dist/app/kernel/rpc.js +47 -0
  42. package/dist/app/kernel/text.d.ts +6 -0
  43. package/dist/app/kernel/text.js +57 -0
  44. package/dist/app/kernel/view.d.ts +146 -0
  45. package/dist/app/kernel/view.js +28 -0
  46. package/dist/app/registry.d.ts +10 -0
  47. package/dist/app/registry.js +50 -0
  48. package/dist/cli/args.d.ts +5 -0
  49. package/dist/cli/args.js +6 -0
  50. package/dist/cli/commands/engine.d.ts +2 -0
  51. package/dist/cli/commands/engine.js +60 -0
  52. package/dist/cli/commands/index.d.ts +1 -1
  53. package/dist/cli/commands/index.js +1 -1
  54. package/dist/cli/commands/launch/launch.js +47 -7
  55. package/dist/cli/commands/launch/prepare.js +3 -3
  56. package/dist/cli/commands/launch/running.js +2 -1
  57. package/dist/cli/commands/launch/summary.d.ts +2 -0
  58. package/dist/cli/commands/maintenance/config-share.d.ts +12 -0
  59. package/dist/cli/commands/maintenance/config-share.js +80 -0
  60. package/dist/cli/commands/maintenance/config.js +8 -82
  61. package/dist/cli/commands/maintenance/logs.js +14 -0
  62. package/dist/cli/commands/registry.js +3 -7
  63. package/dist/cli/commands/repair/checks/contained.d.ts +2 -0
  64. package/dist/cli/commands/repair/checks/contained.js +139 -0
  65. package/dist/cli/commands/repair/checks/hcs.d.ts +7 -0
  66. package/dist/cli/commands/repair/checks/hcs.js +28 -0
  67. package/dist/cli/commands/repair/checks/index.js +4 -0
  68. package/dist/cli/commands/repair/checks/pc.js +32 -1
  69. package/dist/cli/commands/repair/checks/sandbox.js +16 -1
  70. package/dist/cli/commands/repair/checks/vpn.js +34 -9
  71. package/dist/cli/commands/repair/env.js +2 -1
  72. package/dist/cli/commands/sessions/index.d.ts +0 -4
  73. package/dist/cli/commands/sessions/index.js +3 -7
  74. package/dist/cli/commands/shared-use.d.ts +5 -2
  75. package/dist/cli/commands/shared-use.js +11 -8
  76. package/dist/cli/commands/storage/contained.d.ts +3 -0
  77. package/dist/cli/commands/storage/contained.js +179 -0
  78. package/dist/cli/commands/storage/offer.js +7 -1
  79. package/dist/cli/commands/storage/storage.js +10 -2
  80. package/dist/cli/commands/user-config.js +1 -1
  81. package/dist/cli/commands/vpn.js +19 -3
  82. package/dist/cli/commands/web.js +2 -0
  83. package/dist/cli/index.js +2 -1
  84. package/dist/cli/manifest.d.ts +72 -104
  85. package/dist/cli/manifest.js +64 -39
  86. package/dist/cli/menu/add-agent.d.ts +1 -1
  87. package/dist/cli/menu/add-agent.js +1 -1
  88. package/dist/cli/menu/apps.d.ts +7 -1
  89. package/dist/cli/menu/apps.js +36 -0
  90. package/dist/cli/menu/command-fields.d.ts +1 -1
  91. package/dist/cli/menu/command-form.js +2 -2
  92. package/dist/cli/menu/io-menu.d.ts +29 -0
  93. package/dist/cli/menu/io-menu.js +126 -0
  94. package/dist/cli/menu/live.d.ts +27 -0
  95. package/dist/cli/menu/live.js +69 -0
  96. package/dist/cli/menu/memory.d.ts +19 -0
  97. package/dist/cli/menu/memory.js +53 -0
  98. package/dist/cli/menu/menu.d.ts +10 -1
  99. package/dist/cli/menu/menu.js +27 -9
  100. package/dist/cli/menu/pages/agents-page.d.ts +3 -1
  101. package/dist/cli/menu/pages/agents-page.js +12 -5
  102. package/dist/cli/menu/pages/apps-page.d.ts +3 -1
  103. package/dist/cli/menu/pages/apps-page.js +19 -2
  104. package/dist/cli/menu/pages/list-page.d.ts +27 -0
  105. package/dist/cli/menu/pages/list-page.js +109 -7
  106. package/dist/cli/menu/pages/maintenance-page.d.ts +0 -5
  107. package/dist/cli/menu/pages/maintenance-page.js +17 -19
  108. package/dist/cli/menu/pages/sandbox-page.d.ts +4 -6
  109. package/dist/cli/menu/pages/sandbox-page.js +26 -118
  110. package/dist/cli/menu/pages/scopes.d.ts +1 -1
  111. package/dist/cli/menu/pages/sessions-page.d.ts +5 -11
  112. package/dist/cli/menu/pages/sessions-page.js +19 -88
  113. package/dist/cli/menu/pages/settings-page.d.ts +2 -10
  114. package/dist/cli/menu/pages/settings-page.js +64 -94
  115. package/dist/cli/menu/render-view.d.ts +28 -0
  116. package/dist/cli/menu/render-view.js +112 -0
  117. package/dist/cli/menu/settings/{path-kind.d.ts → ask-path.d.ts} +1 -7
  118. package/dist/cli/menu/settings/ask-path.js +14 -0
  119. package/dist/cli/menu/settings/collections/collection-editor.d.ts +1 -1
  120. package/dist/cli/menu/settings/collections/string-list-editor.d.ts +1 -2
  121. package/dist/cli/menu/settings/collections/string-list-editor.js +1 -1
  122. package/dist/cli/menu/settings/draft.d.ts +1 -1
  123. package/dist/cli/menu/settings/format.d.ts +1 -3
  124. package/dist/cli/menu/settings/format.js +6 -36
  125. package/dist/cli/menu/settings/index.d.ts +1 -3
  126. package/dist/cli/menu/settings/index.js +3 -3
  127. package/dist/cli/menu/settings/object-editor.d.ts +1 -1
  128. package/dist/cli/menu/settings/share.d.ts +36 -0
  129. package/dist/cli/menu/settings/share.js +131 -0
  130. package/dist/cli/menu/settings/value-editor.d.ts +5 -1
  131. package/dist/cli/menu/settings/value-editor.js +57 -54
  132. package/dist/cli/menu/several.js +1 -1
  133. package/dist/cli/menu/share-settings.d.ts +21 -0
  134. package/dist/cli/menu/share-settings.js +112 -0
  135. package/dist/cli/menu/shell.d.ts +21 -5
  136. package/dist/cli/menu/shell.js +31 -6
  137. package/dist/cli/menu/views.d.ts +7 -0
  138. package/dist/cli/menu/views.js +11 -0
  139. package/dist/cli/menu/vpn-login.js +8 -0
  140. package/dist/cli/operations/handler.d.ts +6 -0
  141. package/dist/cli/operations/handler.js +61 -0
  142. package/dist/cli/operations/index.d.ts +4 -0
  143. package/dist/cli/operations/index.js +5 -0
  144. package/dist/cli/operations/input.d.ts +6 -0
  145. package/dist/cli/operations/input.js +37 -0
  146. package/dist/cli/operations/io-cli.d.ts +7 -0
  147. package/dist/cli/operations/io-cli.js +62 -0
  148. package/dist/cli/operations/spec.d.ts +18 -0
  149. package/dist/cli/operations/spec.js +108 -0
  150. package/dist/cli/output/colors.d.ts +2 -2
  151. package/dist/cli/output/colors.js +2 -11
  152. package/dist/cli/output/index.d.ts +1 -0
  153. package/dist/cli/output/index.js +1 -0
  154. package/dist/cli/output/print.d.ts +3 -1
  155. package/dist/cli/output/print.js +7 -3
  156. package/dist/cli/output/progress.d.ts +15 -0
  157. package/dist/cli/output/progress.js +44 -0
  158. package/dist/cli/output/render-view.d.ts +4 -0
  159. package/dist/cli/output/render-view.js +63 -0
  160. package/dist/cli/services/agent-commands-service.d.ts +2 -0
  161. package/dist/cli/services/agent-commands-service.js +11 -2
  162. package/dist/cli/services/logs-service.js +4 -3
  163. package/dist/cli/tui/app.d.ts +6 -1
  164. package/dist/cli/tui/app.js +26 -4
  165. package/dist/cli/tui/list/list-screen.d.ts +14 -0
  166. package/dist/cli/tui/list/list-screen.js +64 -36
  167. package/dist/cli/tui/list/list-types.d.ts +2 -0
  168. package/dist/cli/tui/terminal.d.ts +7 -0
  169. package/dist/cli/tui/terminal.js +30 -10
  170. package/dist/core/config/defaults.js +8 -0
  171. package/dist/core/schema/config.d.ts +13 -0
  172. package/dist/core/schema/config.js +16 -1
  173. package/dist/core/schema/harness.d.ts +1 -0
  174. package/dist/core/schema/harness.js +5 -0
  175. package/dist/core/schema/sandbox.d.ts +6 -0
  176. package/dist/core/schema/sandbox.js +20 -3
  177. package/dist/core/schema/vpn.d.ts +2 -1
  178. package/dist/core/schema/vpn.js +3 -3
  179. package/dist/core/types.d.ts +23 -5
  180. package/dist/core/util/fs.d.ts +2 -0
  181. package/dist/core/util/fs.js +9 -0
  182. package/dist/core/util/index.d.ts +1 -1
  183. package/dist/core/util/index.js +1 -1
  184. package/dist/guest/agents/agent-files.d.ts +5 -0
  185. package/dist/guest/agents/agent-files.js +4 -0
  186. package/dist/guest/agents/local-state.d.ts +2 -0
  187. package/dist/guest/agents/local-state.js +21 -1
  188. package/dist/guest/boot/boot.js +2 -1
  189. package/dist/guest/daemon/daemon.js +4 -0
  190. package/dist/guest/daemon/sessions.d.ts +5 -0
  191. package/dist/guest/daemon/sessions.js +17 -3
  192. package/dist/guest/entries/daemon.js +3 -0
  193. package/dist/guest/governor/decide.d.ts +61 -0
  194. package/dist/guest/governor/decide.js +182 -0
  195. package/dist/guest/governor/governor.d.ts +42 -0
  196. package/dist/guest/governor/governor.js +214 -0
  197. package/dist/guest/governor/index.d.ts +2 -0
  198. package/dist/guest/governor/index.js +2 -0
  199. package/dist/guest/runtime/csc.d.ts +1 -0
  200. package/dist/guest/runtime/csc.js +1 -0
  201. package/dist/guest/sessions/viewer.js +3 -1
  202. package/dist/guest/toolchains/native/rust-extras.js +4 -2
  203. package/dist/guest/vpn/vpn.js +69 -21
  204. package/dist/guest/vpn/wireguard.d.ts +32 -0
  205. package/dist/guest/vpn/wireguard.js +132 -0
  206. package/dist/host/agent-commands/commands.d.ts +4 -0
  207. package/dist/host/agent-commands/commands.js +31 -7
  208. package/dist/host/audit/log.d.ts +1 -1
  209. package/dist/host/channel/attach.d.ts +3 -1
  210. package/dist/host/channel/attach.js +5 -2
  211. package/dist/host/channel/channel.d.ts +2 -0
  212. package/dist/host/channel/channel.js +4 -0
  213. package/dist/host/engines/engine.d.ts +12 -0
  214. package/dist/host/engines/engine.js +1 -0
  215. package/dist/host/engines/hcs/check.d.ts +25 -0
  216. package/dist/host/engines/hcs/check.js +95 -0
  217. package/dist/host/engines/hcs/documents.d.ts +44 -0
  218. package/dist/host/engines/hcs/documents.js +155 -0
  219. package/dist/host/engines/hcs/engine.d.ts +39 -0
  220. package/dist/host/engines/hcs/engine.js +171 -0
  221. package/dist/host/engines/hcs/helper.d.ts +43 -0
  222. package/dist/host/engines/hcs/helper.js +173 -0
  223. package/dist/host/engines/hcs/images.d.ts +37 -0
  224. package/dist/host/engines/hcs/images.js +152 -0
  225. package/dist/host/engines/index.d.ts +7 -0
  226. package/dist/host/engines/index.js +5 -0
  227. package/dist/host/engines/registry.d.ts +16 -0
  228. package/dist/host/engines/registry.js +30 -0
  229. package/dist/host/engines/types.d.ts +58 -0
  230. package/dist/host/engines/types.js +1 -0
  231. package/dist/host/engines/windows-sandbox/engine.d.ts +16 -0
  232. package/dist/host/engines/windows-sandbox/engine.js +63 -0
  233. package/dist/host/engines/windows-sandbox/wsb-text.d.ts +2 -0
  234. package/dist/host/engines/windows-sandbox/wsb-text.js +29 -0
  235. package/dist/host/launch/generate.d.ts +5 -1
  236. package/dist/host/launch/generate.js +50 -8
  237. package/dist/host/launch/index.d.ts +3 -1
  238. package/dist/host/launch/index.js +3 -1
  239. package/dist/host/launch/launch-plan.d.ts +13 -0
  240. package/dist/host/launch/launch-plan.js +26 -0
  241. package/dist/host/launch/mappings.js +2 -1
  242. package/dist/host/launch/risk-notes.d.ts +10 -0
  243. package/dist/host/launch/risk-notes.js +18 -0
  244. package/dist/host/launch/session-file.d.ts +2 -0
  245. package/dist/host/launch/session-file.js +7 -1
  246. package/dist/host/launch/wsb-file.d.ts +28 -4
  247. package/dist/host/launch/wsb-file.js +61 -42
  248. package/dist/host/life/crash-report.d.ts +27 -1
  249. package/dist/host/life/crash-report.js +173 -7
  250. package/dist/host/life/host-memory.d.ts +40 -0
  251. package/dist/host/life/host-memory.js +137 -0
  252. package/dist/host/life/index.d.ts +2 -0
  253. package/dist/host/life/index.js +2 -0
  254. package/dist/host/life/stop-events.d.ts +2 -0
  255. package/dist/host/life/stop-events.js +20 -3
  256. package/dist/host/life/stop-warning.js +1 -1
  257. package/dist/host/life/watchdog.d.ts +54 -0
  258. package/dist/host/life/watchdog.js +108 -0
  259. package/dist/host/native.d.ts +3 -2
  260. package/dist/host/native.js +22 -3
  261. package/dist/host/profile/claude-login.js +3 -2
  262. package/dist/host/sandbox/contained-deps.d.ts +2 -0
  263. package/dist/host/sandbox/contained-deps.js +17 -0
  264. package/dist/host/sandbox/index.d.ts +2 -0
  265. package/dist/host/sandbox/index.js +2 -0
  266. package/dist/host/sandbox/platform.d.ts +2 -0
  267. package/dist/host/sandbox/platform.js +7 -7
  268. package/dist/host/sandbox/tasklist.d.ts +13 -0
  269. package/dist/host/sandbox/tasklist.js +47 -0
  270. package/dist/host/secrets/launch-secrets.js +2 -1
  271. package/dist/host/sharing/current.js +2 -6
  272. package/dist/host/storage/contained/check.d.ts +9 -0
  273. package/dist/host/storage/contained/check.js +37 -0
  274. package/dist/host/storage/contained/contained-storage.d.ts +66 -0
  275. package/dist/host/storage/contained/contained-storage.js +168 -0
  276. package/dist/host/storage/contained/disk-port.d.ts +43 -0
  277. package/dist/host/storage/contained/disk-port.js +122 -0
  278. package/dist/host/storage/contained/index.d.ts +9 -0
  279. package/dist/host/storage/contained/index.js +8 -0
  280. package/dist/host/storage/contained/layout.d.ts +28 -0
  281. package/dist/host/storage/contained/layout.js +45 -0
  282. package/dist/host/storage/contained/resolve.d.ts +4 -0
  283. package/dist/host/storage/contained/resolve.js +30 -0
  284. package/dist/host/storage/contained/setting.d.ts +1 -0
  285. package/dist/host/storage/contained/setting.js +5 -0
  286. package/dist/host/storage/contained/state.d.ts +89 -0
  287. package/dist/host/storage/contained/state.js +166 -0
  288. package/dist/host/storage/contained/transfer.d.ts +24 -0
  289. package/dist/host/storage/contained/transfer.js +121 -0
  290. package/dist/host/storage/index.d.ts +2 -0
  291. package/dist/host/storage/index.js +2 -0
  292. package/dist/host/storage/providers/contained.d.ts +15 -0
  293. package/dist/host/storage/providers/contained.js +39 -0
  294. package/dist/host/storage/providers/folder.d.ts +12 -0
  295. package/dist/host/storage/providers/folder.js +20 -0
  296. package/dist/host/storage/providers/index.d.ts +4 -0
  297. package/dist/host/storage/providers/index.js +3 -0
  298. package/dist/host/storage/providers/registry.d.ts +9 -0
  299. package/dist/host/storage/providers/registry.js +14 -0
  300. package/dist/host/storage/providers/types.d.ts +33 -0
  301. package/dist/host/storage/providers/types.js +1 -0
  302. package/dist/host/tools/conpty.js +3 -2
  303. package/dist/host/tools/windows-terminal.js +8 -7
  304. package/dist/host/vpn/index.d.ts +1 -1
  305. package/dist/host/vpn/index.js +1 -1
  306. package/dist/host/vpn/prepare.d.ts +2 -0
  307. package/dist/host/vpn/prepare.js +63 -2
  308. package/dist/host/web/browser.d.ts +2 -0
  309. package/dist/host/web/browser.js +14 -1
  310. package/dist/host/web/index.d.ts +3 -2
  311. package/dist/host/web/index.js +3 -2
  312. package/dist/host/web/rpc.d.ts +24 -0
  313. package/dist/host/web/rpc.js +149 -0
  314. package/dist/host/web/runner.d.ts +2 -0
  315. package/dist/host/web/runner.js +1 -2
  316. package/dist/host/web/server.d.ts +7 -0
  317. package/dist/host/web/server.js +23 -3
  318. package/dist/runtime/agent.js +69 -8
  319. package/dist/runtime/daemon.js +1058 -624
  320. package/dist/runtime/db.js +69 -8
  321. package/dist/runtime/main.js +624 -182
  322. package/dist/runtime/netproxy.js +69 -8
  323. package/dist/runtime/view.js +87 -13
  324. package/dist/selftest/agent-paths.test.js +11 -1
  325. package/dist/selftest/app.test.d.ts +2 -0
  326. package/dist/selftest/app.test.js +324 -0
  327. package/dist/selftest/channel.test.js +13 -0
  328. package/dist/selftest/cli.test.js +2 -1
  329. package/dist/selftest/config.test.js +59 -0
  330. package/dist/selftest/contained.test.d.ts +2 -0
  331. package/dist/selftest/contained.test.js +544 -0
  332. package/dist/selftest/credentials.test.js +1 -1
  333. package/dist/selftest/engine.test.d.ts +2 -0
  334. package/dist/selftest/engine.test.js +127 -0
  335. package/dist/selftest/hcs.test.d.ts +2 -0
  336. package/dist/selftest/hcs.test.js +482 -0
  337. package/dist/selftest/heavy-load.test.d.ts +2 -0
  338. package/dist/selftest/heavy-load.test.js +329 -0
  339. package/dist/selftest/index.js +12 -0
  340. package/dist/selftest/layout.test.js +12 -0
  341. package/dist/selftest/menu-perf.test.d.ts +2 -0
  342. package/dist/selftest/menu-perf.test.js +248 -0
  343. package/dist/selftest/menu-screens.js +4 -2
  344. package/dist/selftest/menu.test.js +36 -8
  345. package/dist/selftest/support/engine-cases.d.ts +8 -0
  346. package/dist/selftest/support/engine-cases.js +65 -0
  347. package/dist/selftest/support/engine-golden.d.ts +3 -0
  348. package/dist/selftest/support/engine-golden.js +66 -0
  349. package/dist/selftest/support/fixtures.d.ts +7 -1
  350. package/dist/selftest/support/hcs-probes.d.ts +17 -0
  351. package/dist/selftest/support/hcs-probes.js +41 -0
  352. package/dist/selftest/vpn.test.js +136 -3
  353. package/dist/selftest/web.test.js +101 -3
  354. package/dist/shared/channel/run-dir.d.ts +4 -1
  355. package/dist/shared/channel/run-dir.js +7 -1
  356. package/dist/shared/channel/schema.d.ts +35 -1
  357. package/dist/shared/channel/schema.js +27 -0
  358. package/dist/shared/channel/secrets.d.ts +1 -0
  359. package/dist/shared/channel/secrets.js +6 -0
  360. package/dist/shared/memory/index.d.ts +1 -0
  361. package/dist/shared/memory/index.js +1 -0
  362. package/dist/shared/memory/policy.d.ts +33 -0
  363. package/dist/shared/memory/policy.js +55 -0
  364. package/dist/shared/pty/output-filter.d.ts +14 -1
  365. package/dist/shared/pty/output-filter.js +22 -4
  366. package/dist/shared/pty/terminal.d.ts +4 -1
  367. package/dist/shared/pty/terminal.js +4 -2
  368. package/dist/shared/system.d.ts +1 -0
  369. package/dist/shared/system.js +1 -0
  370. package/dist/shared/vpn/download.d.ts +16 -0
  371. package/dist/shared/vpn/download.js +16 -0
  372. package/dist/shared/vpn/wireguard.d.ts +51 -0
  373. package/dist/shared/vpn/wireguard.js +227 -0
  374. package/dist/sync/sync-cycle.js +5 -5
  375. package/dist/sync/watcher.d.ts +13 -0
  376. package/dist/sync/watcher.js +92 -3
  377. package/dist/web/index.html +4 -3
  378. package/dist/web/main-NRRS5O6Z.js +157 -0
  379. package/dist/web/main-T5DMCHYV.css +1 -0
  380. package/native/csharp/HcsHost/AssemblyInfo.cs +14 -0
  381. package/native/csharp/HcsHost/Guard.cs +100 -0
  382. package/native/csharp/HcsHost/Hcs.cs +256 -0
  383. package/native/csharp/HcsHost/Json.cs +78 -0
  384. package/native/csharp/HcsHost/Program.cs +185 -0
  385. package/native/csharp/HcsHost/VirtualDisk.cs +95 -0
  386. package/native/csharp/HostTools/AssemblyInfo.cs +14 -0
  387. package/native/csharp/HostTools/MemoryStatus.cs +107 -0
  388. package/native/csharp/HostTools/Program.cs +58 -17
  389. package/native/scripts/check-contained.ps1 +28 -0
  390. package/native/scripts/contained-common.ps1 +241 -0
  391. package/native/scripts/dismount-contained.ps1 +66 -0
  392. package/native/scripts/memory-status.ps1 +34 -0
  393. package/native/scripts/mount-contained.ps1 +139 -0
  394. package/native/scripts/run-elevated.ps1 +17 -4
  395. package/native/scripts/stop-events.ps1 +23 -8
  396. package/package.json +9 -2
  397. package/sandbox/csharp/MemGov.cs +398 -0
  398. package/sandbox/csharp/PtyHost.cs +111 -5
  399. package/schemas/config.schema.json +60 -6
  400. package/schemas/sandbox-config.schema.json +3 -3
  401. package/dist/cli/commands/sessions/control.d.ts +0 -4
  402. package/dist/cli/commands/sessions/control.js +0 -66
  403. package/dist/cli/commands/sessions/list.d.ts +0 -2
  404. package/dist/cli/commands/sessions/list.js +0 -48
  405. package/dist/cli/commands/sessions/status.d.ts +0 -4
  406. package/dist/cli/commands/sessions/status.js +0 -114
  407. package/dist/cli/commands/sessions/window.d.ts +0 -3
  408. package/dist/cli/commands/sessions/window.js +0 -18
  409. package/dist/cli/menu/settings/path-kind.js +0 -44
  410. package/dist/web/main-CXESL347.js +0 -45
  411. package/dist/web/main-VLR7DVQ4.css +0 -1
  412. /package/dist/{cli/menu/settings → app/kernel}/schema-node.d.ts +0 -0
  413. /package/dist/{cli/menu/settings → app/kernel}/schema-node.js +0 -0
@@ -0,0 +1,107 @@
1
+ // This PC's memory right now (HostTools memory-status): physical RAM and the commit limit (RAM plus paging files) with
2
+ // what is left of each. The commit limit is what a sandbox's memory is charged against, so it matters as much as RAM.
3
+ // C# 5.
4
+ using System;
5
+ using System.ComponentModel;
6
+ using System.Globalization;
7
+ using System.Runtime.InteropServices;
8
+ using Microsoft.Win32;
9
+
10
+ namespace AgentSandbox
11
+ {
12
+ public static class HostMemory
13
+ {
14
+ private const double BytesPerMb = 1024.0 * 1024.0;
15
+
16
+ // MEMORYSTATUSEX.
17
+ [StructLayout(LayoutKind.Sequential)]
18
+ private struct MemoryStatus
19
+ {
20
+ public uint Length;
21
+ public uint MemoryLoad;
22
+ public ulong TotalPhys;
23
+ public ulong AvailPhys;
24
+ public ulong TotalPageFile;
25
+ public ulong AvailPageFile;
26
+ public ulong TotalVirtual;
27
+ public ulong AvailVirtual;
28
+ public ulong AvailExtendedVirtual;
29
+ }
30
+
31
+ private static class NativeMethods
32
+ {
33
+ [DllImport("kernel32.dll", SetLastError = true)]
34
+ public static extern bool GlobalMemoryStatusEx(ref MemoryStatus status);
35
+ }
36
+
37
+ /// <summary>{"totalMb":..,"availableMb":..,"commitLimitMb":..,"commitAvailableMb":..}; throws Win32Exception on failure.</summary>
38
+ public static string Json()
39
+ {
40
+ MemoryStatus status = new MemoryStatus();
41
+ status.Length = (uint)Marshal.SizeOf(typeof(MemoryStatus));
42
+ if (!NativeMethods.GlobalMemoryStatusEx(ref status))
43
+ {
44
+ throw new Win32Exception(Marshal.GetLastWin32Error());
45
+ }
46
+ return "{\"totalMb\":" + Mb(status.TotalPhys) +
47
+ ",\"availableMb\":" + Mb(status.AvailPhys) +
48
+ ",\"commitLimitMb\":" + Mb(status.TotalPageFile) +
49
+ ",\"commitAvailableMb\":" + Mb(status.AvailPageFile) +
50
+ "," + PageFileJson() + "}";
51
+ }
52
+
53
+ /// <summary>
54
+ /// How this PC's paging file is set up, from the registry (read-only): "auto" (Windows manages it), "fixed"
55
+ /// (with the total of the maximum sizes) or "none". An unreadable setting is reported as "auto".
56
+ /// </summary>
57
+ private static string PageFileJson()
58
+ {
59
+ string kind = "auto";
60
+ long maxMb = 0;
61
+ try
62
+ {
63
+ using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management"))
64
+ {
65
+ string[] files = key == null ? null : key.GetValue("PagingFiles") as string[];
66
+ if (files != null)
67
+ {
68
+ kind = "none";
69
+ foreach (string entry in files)
70
+ {
71
+ string[] parts = entry.Trim().Split(' ');
72
+ if (entry.Trim().Length == 0)
73
+ {
74
+ continue;
75
+ }
76
+ long initial = 0;
77
+ long max = 0;
78
+ if (parts.Length >= 3)
79
+ {
80
+ long.TryParse(parts[parts.Length - 2], NumberStyles.Integer, CultureInfo.InvariantCulture, out initial);
81
+ long.TryParse(parts[parts.Length - 1], NumberStyles.Integer, CultureInfo.InvariantCulture, out max);
82
+ }
83
+ if (entry.StartsWith("?:") || (initial == 0 && max == 0))
84
+ {
85
+ kind = "auto";
86
+ maxMb = 0;
87
+ break;
88
+ }
89
+ kind = "fixed";
90
+ maxMb += max;
91
+ }
92
+ }
93
+ }
94
+ }
95
+ catch (Exception)
96
+ {
97
+ // Not readable: the caller treats "auto" as "no warning".
98
+ }
99
+ return "\"pageFile\":\"" + kind + "\",\"pageFileMaxMb\":" + maxMb.ToString(CultureInfo.InvariantCulture);
100
+ }
101
+
102
+ private static string Mb(ulong bytes)
103
+ {
104
+ return Math.Floor(bytes / BytesPerMb).ToString("0", CultureInfo.InvariantCulture);
105
+ }
106
+ }
107
+ }
@@ -1,6 +1,7 @@
1
1
  // HostTools.exe: what the host side asks of Windows without PowerShell. Built on first use by host/native.ts (csc).
2
2
  // keep-awake --owner-pid N this PC stays awake until process N ends (sandbox.keepAwake)
3
3
  // stop-events --since <iso> the Windows events since then that can end a sandbox, as JSON (host/life)
4
+ // memory-status this PC's RAM and commit (what a sandbox's memory is charged against), as JSON
4
5
  // hide-window --wait S hides the sandbox window once it appears; prints the hidden handles as JSON
5
6
  // show-window --handles a,b shows those windows (else any sandbox window); prints how many
6
7
  // user-path --add|--remove D D first on, or off, your PATH; exit 3 when PATH would be too long
@@ -15,6 +16,7 @@ static class Program
15
16
  {
16
17
  private const int MaxEventsPerQuery = 100;
17
18
  private const int MaxDetail = 390;
19
+ private const int MaxResourceDetail = 1500;
18
20
 
19
21
  static int Main(string[] args)
20
22
  {
@@ -39,6 +41,17 @@ static class Program
39
41
  }
40
42
  Console.Out.Write(StopEventsJson(since));
41
43
  return 0;
44
+ case "memory-status":
45
+ try
46
+ {
47
+ Console.Out.Write(AgentSandbox.HostMemory.Json());
48
+ return 0;
49
+ }
50
+ catch (Exception e)
51
+ {
52
+ Console.Error.WriteLine(e.Message);
53
+ return 1;
54
+ }
42
55
  case "hide-window":
43
56
  int wait;
44
57
  if (!options.TryGetValue("wait", out value) || !int.TryParse(value, out wait))
@@ -84,7 +97,7 @@ static class Program
84
97
 
85
98
  static int Usage()
86
99
  {
87
- Console.Error.WriteLine("Usage: HostTools keep-awake --owner-pid N | stop-events --since <ISO time> | hide-window --wait S | show-window [--handles a,b] | user-path --add|--remove <folder>");
100
+ Console.Error.WriteLine("Usage: HostTools keep-awake --owner-pid N | stop-events --since <ISO time> | memory-status | hide-window --wait S | show-window [--handles a,b] | user-path --add|--remove <folder>");
88
101
  return 2;
89
102
  }
90
103
 
@@ -127,13 +140,20 @@ static class Program
127
140
  new[] { "System", "*[System[(EventID=1074 or EventID=6008 or EventID=41 or EventID=42 or EventID=2004) and " + time + "]]" },
128
141
  new[] { "System", "*[System[Provider[@Name='Microsoft-Windows-WER-SystemErrorReporting'] and EventID=1001 and " + time + "]]" },
129
142
  new[] { "Microsoft-Windows-Hyper-V-Worker-Admin", "*[System[(EventID=18590 or EventID=40001) and " + time + "]]" },
143
+ // Hardware errors Windows reported (WHEA: CPU, memory, bus, disk).
144
+ new[] { "System", "*[System[Provider[@Name='Microsoft-Windows-WHEA-Logger'] and (EventID=17 or EventID=18 or EventID=19 or EventID=47) and " + time + "]]" },
145
+ // Any error or critical event of the Hyper-V worker and compute services (the VM's memory, start and stop).
146
+ new[] { "Microsoft-Windows-Hyper-V-Worker-Admin", "*[System[(Level=1 or Level=2) and " + time + "]]" },
147
+ new[] { "Microsoft-Windows-Hyper-V-Compute-Admin", "*[System[(Level=1 or Level=2) and " + time + "]]" },
148
+ // A crash report of the programs that hold the VM (kept only when it names one of them, see Detail).
149
+ new[] { "Application", "*[System[(EventID=1000 or EventID=1001) and " + time + "]]" },
130
150
  };
131
151
  List<string> items = new List<string>();
132
152
  foreach (string[] query in queries)
133
153
  {
134
154
  try
135
155
  {
136
- ReadEvents(query[0], query[1], items);
156
+ ReadEvents(query[0], query[1], items, query[0] == "Application");
137
157
  }
138
158
  catch (EventLogException)
139
159
  {
@@ -148,7 +168,7 @@ static class Program
148
168
  }
149
169
 
150
170
  /// <summary>The newest events first, at most 100 per query, as JSON objects { at, id, provider, process }.</summary>
151
- static void ReadEvents(string log, string xpath, List<string> items)
171
+ static void ReadEvents(string log, string xpath, List<string> items, bool onlyVmPrograms)
152
172
  {
153
173
  EventLogQuery query = new EventLogQuery(log, PathType.LogName, xpath);
154
174
  query.ReverseDirection = true;
@@ -163,37 +183,58 @@ static class Program
163
183
  return;
164
184
  }
165
185
  DateTime at = record.TimeCreated.HasValue ? record.TimeCreated.Value.ToUniversalTime() : DateTime.UtcNow;
186
+ string detail = Detail(record);
187
+ if (onlyVmPrograms && !MentionsVmProgram(detail))
188
+ {
189
+ continue;
190
+ }
166
191
  items.Add(
167
192
  "{\"at\":" + Json(at.ToString("o", CultureInfo.InvariantCulture)) +
168
193
  ",\"id\":" + record.Id.ToString(CultureInfo.InvariantCulture) +
169
194
  ",\"provider\":" + Json(record.ProviderName ?? "") +
170
- ",\"process\":" + Json(Detail(record)) + "}");
195
+ ",\"process\":" + Json(detail) + "}");
171
196
  }
172
197
  }
173
198
  }
174
199
  }
175
200
 
176
- /// <summary>What an event says beyond its id: the program that asked for a restart (1074), else its message start.</summary>
201
+ /// <summary>
202
+ /// What an event says beyond its id: the program that asked for a restart (1074), else its message start (the
203
+ /// resource-exhaustion event 2004 lists the biggest memory users, so it gets more room). Not for the plain power events.
204
+ /// </summary>
177
205
  static string Detail(EventRecord record)
178
206
  {
179
207
  if (record.Id == 1074)
180
208
  {
181
209
  return record.Properties.Count > 0 ? Convert.ToString(record.Properties[0].Value, CultureInfo.InvariantCulture) ?? "" : "";
182
210
  }
183
- if (record.Id == 18590 || record.Id == 40001 || record.Id == 1001)
211
+ if (record.Id == 41)
184
212
  {
185
- string message;
186
- try
187
- {
188
- message = record.FormatDescription() ?? "";
189
- }
190
- catch (EventLogException)
191
- {
192
- message = "";
193
- }
194
- return message.Substring(0, Math.Min(MaxDetail, message.Length));
213
+ // Kernel-Power 41 carries the stop code of a blue screen (0 when there was none: a freeze or power loss).
214
+ return record.Properties.Count > 0 ? "BugcheckCode " + Convert.ToString(record.Properties[0].Value, CultureInfo.InvariantCulture) : "";
195
215
  }
196
- return "";
216
+ if (record.Id == 42 || record.Id == 6008)
217
+ {
218
+ return "";
219
+ }
220
+ string message;
221
+ try
222
+ {
223
+ message = record.FormatDescription() ?? "";
224
+ }
225
+ catch (EventLogException)
226
+ {
227
+ message = "";
228
+ }
229
+ int max = record.Id == 2004 ? MaxResourceDetail : MaxDetail;
230
+ return message.Substring(0, Math.Min(max, message.Length));
231
+ }
232
+
233
+ /// <summary>Whether an application crash report names a program that runs the sandbox's VM.</summary>
234
+ static bool MentionsVmProgram(string detail)
235
+ {
236
+ string lower = detail.ToLowerInvariant();
237
+ return lower.Contains("vmwp.exe") || lower.Contains("vmmem") || lower.Contains("vmcompute") || lower.Contains("windowssandbox");
197
238
  }
198
239
 
199
240
  /// <summary>A JSON string literal.</summary>
@@ -0,0 +1,28 @@
1
+ #Requires -Version 5.1
2
+ # Looks at the contained-storage image of one data set without changing anything and prints one line of JSON:
3
+ # { imageExists, imageBytes, mountExists, mountIsMountPoint, attached }. `attached` is null when Windows will not say
4
+ # without administrator rights. Used by doctor, repair and the launch (host/storage/contained) to compare what the
5
+ # state file says with what is really mounted. Paths are never followed through links: a link gives mountIsMountPoint.
6
+ param(
7
+ [Parameter(Mandatory = $true)][string]$Image,
8
+ [Parameter(Mandatory = $true)][string]$Mount
9
+ )
10
+ $ErrorActionPreference = 'Stop'
11
+
12
+ $answer = [ordered]@{ imageExists = $false; imageBytes = 0; mountExists = $false; mountIsMountPoint = $false; attached = $null }
13
+ try {
14
+ if (Test-Path -LiteralPath $Image -PathType Leaf) {
15
+ $answer.imageExists = $true
16
+ $answer.imageBytes = (Get-Item -LiteralPath $Image -Force).Length
17
+ try { $answer.attached = [bool](Get-DiskImage -ImagePath $Image -ErrorAction Stop).Attached } catch { $answer.attached = $null }
18
+ }
19
+ if (Test-Path -LiteralPath $Mount -PathType Container) {
20
+ $answer.mountExists = $true
21
+ $answer.mountIsMountPoint = [bool]([IO.File]::GetAttributes($Mount) -band [IO.FileAttributes]::ReparsePoint)
22
+ }
23
+ Write-Output ($answer | ConvertTo-Json -Compress)
24
+ exit 0
25
+ } catch {
26
+ [Console]::Error.WriteLine($_.Exception.Message)
27
+ exit 1
28
+ }
@@ -0,0 +1,241 @@
1
+ #Requires -Version 5.1
2
+ # Shared by mount-contained.ps1, dismount-contained.ps1 and check-contained.ps1 (dot-sourced; defines functions only).
3
+ # Everything a request says is checked again here, because mounting a disk image needs administrator rights and the
4
+ # request file was written by a program that is not elevated: the image and the mount folder must be exactly
5
+ # <storage>\images\<name>.vhdx and <storage>\mounts\<name>, none of the folders on the way may be a link, and what is
6
+ # there must be yours. Nothing in this file changes anything.
7
+ Set-StrictMode -Version 2.0
8
+
9
+ $script:NameRule = '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'
10
+ $script:PathRule = '^[^"<>|*?\x00-\x1f]+$'
11
+ $script:AdministratorsSid = 'S-1-5-32-544'
12
+ $script:Lines = New-Object System.Collections.Generic.List[string]
13
+
14
+ function Add-Note([string]$Text) {
15
+ $script:Lines.Add($Text)
16
+ }
17
+
18
+ function Test-PlainAbsolute([string]$Path) {
19
+ if ([string]::IsNullOrWhiteSpace($Path)) { return $false }
20
+ if ($Path -notmatch $script:PathRule) { return $false }
21
+ # A drive letter and plain folder names: no UNC or \\?\ form, no . or .. part, no stream (a colon), no slash.
22
+ # (GetFullPath is not used: it turns a short 8.3 name such as WDAGUT~1 into a long one, and those are fine.)
23
+ if ($Path -notmatch '^[A-Za-z]:\\') { return $false }
24
+ if ($Path.IndexOf(':', 2) -ge 0 -or $Path.Contains('/')) { return $false }
25
+ foreach ($part in $Path.Substring(3).TrimEnd('\').Split('\')) {
26
+ if ($part -eq '' -or $part -eq '.' -or $part -eq '..' -or $part.EndsWith(' ') -or $part.EndsWith('.')) { return $false }
27
+ }
28
+ return $true
29
+ }
30
+
31
+ function Test-ReparsePoint([string]$Path) {
32
+ return [bool]([IO.File]::GetAttributes($Path) -band [IO.FileAttributes]::ReparsePoint)
33
+ }
34
+
35
+ function Test-IsMe([string]$Sid) {
36
+ $me = [Security.Principal.WindowsIdentity]::GetCurrent()
37
+ return ($me.User.Value -ieq $Sid)
38
+ }
39
+
40
+ function Test-IsAdministrator {
41
+ $me = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
42
+ return $me.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
43
+ }
44
+
45
+ function Read-ContainedRequest([string]$JsonPath, [string]$Sha256, [bool]$Dry) {
46
+ if (-not (Test-Path -LiteralPath $JsonPath -PathType Leaf)) { throw "The request file is missing: $JsonPath" }
47
+ $bytes = [IO.File]::ReadAllBytes($JsonPath)
48
+ if ($bytes.Length -gt 65536) { throw 'The request file is too large.' }
49
+ if ($Sha256) {
50
+ $hash = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash($bytes)).Replace('-', '')
51
+ if ($hash -ine $Sha256) { throw 'The request file changed after it was written; nothing was done.' }
52
+ } elseif (-not $Dry) {
53
+ throw 'No checksum was given for the request file.'
54
+ }
55
+ $text = [Text.Encoding]::UTF8.GetString($bytes).TrimStart([char]0xFEFF)
56
+ return ($text | ConvertFrom-Json)
57
+ }
58
+
59
+ function Assert-ContainedRequest($Req) {
60
+ foreach ($name in @('storageRoot', 'image', 'mount', 'userSid', 'set')) {
61
+ if (-not ($Req.PSObject.Properties.Name -contains $name) -or -not $Req.$name) { throw "The request has no $name." }
62
+ }
63
+ if ($Req.userSid -notmatch '^S-1-[0-9-]{3,120}$') { throw 'The request has no valid user.' }
64
+ if ($Req.set -notmatch $script:NameRule) { throw "The data set name is not allowed: $($Req.set)" }
65
+ foreach ($p in @($Req.storageRoot, $Req.image, $Req.mount)) {
66
+ if (-not (Test-PlainAbsolute $p)) { throw "Not a plain full path (no .., no network path): $p" }
67
+ }
68
+ $root = $Req.storageRoot.TrimEnd('\')
69
+ $wantImage = Join-Path (Join-Path $root 'images') ($Req.set + '.vhdx')
70
+ $wantMount = Join-Path (Join-Path $root 'mounts') $Req.set
71
+ if ($Req.image.TrimEnd('\') -ine $wantImage) { throw "The image must be $wantImage, not $($Req.image)." }
72
+ if ($Req.mount.TrimEnd('\') -ine $wantMount) { throw "The mount folder must be $wantMount, not $($Req.mount)." }
73
+ if ($root.Length -le 3) { throw 'The storage folder cannot be a drive root.' }
74
+ }
75
+
76
+ function Assert-NoLinksOnTheWay($Req, [bool]$MountMayBeLink) {
77
+ $root = $Req.storageRoot.TrimEnd('\')
78
+ foreach ($p in @($root, (Join-Path $root 'images'), (Join-Path $root 'mounts'))) {
79
+ if (-not (Test-Path -LiteralPath $p -PathType Container)) { throw "A folder is missing: $p" }
80
+ if (Test-ReparsePoint $p) { throw "Refusing a link: $p" }
81
+ }
82
+ # Everything above the storage folder must not be a link either (a link there would move the whole tree).
83
+ $parent = Split-Path -Parent $root
84
+ while ($parent) {
85
+ if ((Test-Path -LiteralPath $parent) -and (Test-ReparsePoint $parent)) { throw "Refusing a link above the storage folder: $parent" }
86
+ $next = Split-Path -Parent $parent
87
+ if ($next -eq $parent) { break }
88
+ $parent = $next
89
+ }
90
+ if (Test-Path -LiteralPath $Req.image) {
91
+ if (Test-ReparsePoint $Req.image) { throw "Refusing a link: $($Req.image)" }
92
+ if (-not (Test-Path -LiteralPath $Req.image -PathType Leaf)) { throw "The image is not a file: $($Req.image)" }
93
+ }
94
+ if (-not (Test-Path -LiteralPath $Req.mount -PathType Container)) { throw "The mount folder is missing: $($Req.mount)" }
95
+ if ((-not $MountMayBeLink) -and (Test-ReparsePoint $Req.mount)) { throw "Refusing a link: $($Req.mount)" }
96
+ }
97
+
98
+ function Get-OwnerSid([string]$Path) {
99
+ return (Get-Acl -LiteralPath $Path).GetOwner([Security.Principal.SecurityIdentifier]).Value
100
+ }
101
+
102
+ function Assert-OwnedByUser($Req, [string[]]$Paths) {
103
+ # An object made by an elevated process of your own account is owned by Administrators, not by you.
104
+ $allowed = @($Req.userSid)
105
+ if ((Test-IsMe $Req.userSid) -and (Test-IsAdministrator)) { $allowed += $script:AdministratorsSid }
106
+ foreach ($p in $Paths) {
107
+ $owner = Get-OwnerSid $p
108
+ if ($allowed -inotcontains $owner) { throw "Not yours (owner $owner): $p" }
109
+ }
110
+ }
111
+
112
+ function Read-ExactBytes([IO.FileStream]$Stream, [long]$Offset, [int]$Count) {
113
+ if ($Offset -lt 0 -or ($Offset + $Count) -gt $Stream.Length) { throw 'The image is not a valid VHDX file (a part is missing).' }
114
+ [void]$Stream.Seek($Offset, [IO.SeekOrigin]::Begin)
115
+ $buffer = New-Object byte[] $Count
116
+ $done = 0
117
+ while ($done -lt $Count) {
118
+ $n = $Stream.Read($buffer, $done, $Count - $done)
119
+ if ($n -le 0) { throw 'The image could not be read.' }
120
+ $done += $n
121
+ }
122
+ return , $buffer
123
+ }
124
+
125
+ function New-GuidFrom([byte[]]$Bytes, [int]$Offset) {
126
+ $part = New-Object byte[] 16
127
+ [Array]::Copy($Bytes, $Offset, $part, 0, 16)
128
+ return (New-Object System.Guid -ArgumentList (, $part))
129
+ }
130
+
131
+ # Reads the VHDX header of a file that is not attached (the layout is in the VHDX specification): is it a VHDX, how
132
+ # big may it grow, does it have a parent (a differencing disk would reach into another file)?
133
+ function Read-VhdxInfo([string]$Path) {
134
+ $metadataRegion = New-Object System.Guid '8b7ca206-4790-4b9a-b8fe-575f050f886e'
135
+ $virtualSizeItem = New-Object System.Guid '2fa54224-cd1b-4876-b211-5dbed83bf4b8'
136
+ $parentItem = New-Object System.Guid 'a8d35f2d-b30b-454d-abf7-d3d84834ab0c'
137
+ $stream = [IO.File]::Open($Path, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite)
138
+ try {
139
+ $id = [Text.Encoding]::ASCII.GetString((Read-ExactBytes $stream 0 8))
140
+ if ($id -ne 'vhdxfile') { throw 'The file is not a VHDX image.' }
141
+ $table = Read-ExactBytes $stream 196608 65536
142
+ if ([Text.Encoding]::ASCII.GetString($table, 0, 4) -ne 'regi') { throw 'The image has no region table.' }
143
+ $count = [BitConverter]::ToUInt32($table, 8)
144
+ if ($count -gt 64) { throw 'The image has too many regions.' }
145
+ $metadataOffset = -1L
146
+ for ($i = 0; $i -lt $count; $i++) {
147
+ $at = 16 + 32 * $i
148
+ if ((New-GuidFrom $table $at) -eq $metadataRegion) { $metadataOffset = [long][BitConverter]::ToUInt64($table, $at + 16) }
149
+ }
150
+ if ($metadataOffset -lt 0) { throw 'The image has no metadata region.' }
151
+ $meta = Read-ExactBytes $stream $metadataOffset 65536
152
+ if ([Text.Encoding]::ASCII.GetString($meta, 0, 8) -ne 'metadata') { throw 'The image has no metadata table.' }
153
+ $items = [BitConverter]::ToUInt16($meta, 10)
154
+ if ($items -gt 2047) { throw 'The image has too many metadata items.' }
155
+ $size = [uint64]0
156
+ $hasParent = $false
157
+ for ($i = 0; $i -lt $items; $i++) {
158
+ $at = 32 + 32 * $i
159
+ $guid = New-GuidFrom $meta $at
160
+ if ($guid -eq $parentItem) { $hasParent = $true }
161
+ if ($guid -eq $virtualSizeItem) {
162
+ $itemOffset = [long][BitConverter]::ToUInt32($meta, $at + 16)
163
+ $size = [BitConverter]::ToUInt64((Read-ExactBytes $stream ($metadataOffset + $itemOffset) 8), 0)
164
+ }
165
+ }
166
+ return @{ VirtualSize = $size; HasParent = $hasParent }
167
+ } finally {
168
+ $stream.Dispose()
169
+ }
170
+ }
171
+
172
+ function Assert-PlainImage($Req, [bool]$Dry) {
173
+ $info = Read-VhdxInfo $Req.image
174
+ if ($info.HasParent) { throw 'The image is a differencing disk (it has a parent file); refusing it.' }
175
+ $cap = [uint64]$Req.maxGb * 1GB
176
+ if ($info.VirtualSize -eq 0) { throw 'The image size could not be read.' }
177
+ if ($info.VirtualSize -gt $cap) { throw "The image is bigger than the limit of $($Req.maxGb) GB." }
178
+ return $info
179
+ }
180
+
181
+ # Is this very image attached, and where? Null as the second answer when Windows will not say.
182
+ function Get-ImageState([string]$Image, [bool]$Dry) {
183
+ try {
184
+ $di = Get-DiskImage -ImagePath $Image -ErrorAction Stop
185
+ if (-not $di.Attached) { return @{ Attached = $false; Paths = @() } }
186
+ $disk = $di | Get-Disk -ErrorAction Stop
187
+ $paths = @()
188
+ foreach ($part in @(Get-Partition -DiskNumber $disk.Number -ErrorAction SilentlyContinue)) {
189
+ foreach ($a in @($part.AccessPaths)) { if ($a) { $paths += $a } }
190
+ }
191
+ return @{ Attached = $true; Paths = $paths }
192
+ } catch {
193
+ if ($Dry) { return @{ Attached = $false; Paths = @() } }
194
+ throw "Windows could not tell whether the image is attached: $($_.Exception.Message)"
195
+ }
196
+ }
197
+
198
+ function Test-MountedAt($State, [string]$Mount) {
199
+ $want = $Mount.TrimEnd('\') + '\'
200
+ foreach ($p in $State.Paths) { if ($p -ieq $want) { return $true } }
201
+ return $false
202
+ }
203
+
204
+ function Invoke-DiskPart([string[]]$DiskPartLines, [string]$TempDir, [bool]$Dry) {
205
+ foreach ($line in $DiskPartLines) {
206
+ if ($line -match '[\r\n]') { throw 'A diskpart line has a line break.' }
207
+ }
208
+ if ($Dry) {
209
+ foreach ($line in $DiskPartLines) { Add-Note ('DISKPART: ' + $line) }
210
+ return ''
211
+ }
212
+ $diskpart = Join-Path $env:SystemRoot 'System32\diskpart.exe'
213
+ if (-not (Test-Path -LiteralPath $diskpart)) { throw 'diskpart.exe was not found.' }
214
+ $file = Join-Path $TempDir ('diskpart-' + [guid]::NewGuid().ToString('N') + '.txt')
215
+ [IO.File]::WriteAllLines($file, $DiskPartLines, [Text.Encoding]::ASCII)
216
+ try {
217
+ # /s stops at the first command that fails.
218
+ $output = & $diskpart /s $file 2>&1 | Out-String
219
+ if ($LASTEXITCODE -ne 0) { throw "diskpart failed (exit $LASTEXITCODE): $output" }
220
+ return $output
221
+ } finally {
222
+ Remove-Item -LiteralPath $file -Force -ErrorAction SilentlyContinue
223
+ }
224
+ }
225
+
226
+ function Write-ContainedResult([hashtable]$Result, [string]$JsonPath, [bool]$Dry) {
227
+ if ($Dry) {
228
+ foreach ($line in $script:Lines) { Write-Output $line }
229
+ if ($Result.ok) { Write-Output 'RESULT: ok' } else { Write-Output ('RESULT: refused: ' + $Result.message) }
230
+ return
231
+ }
232
+ $Result.notes = @($script:Lines)
233
+ $file = Join-Path (Split-Path -Parent $JsonPath) 'result.json'
234
+ [IO.File]::WriteAllText($file, ($Result | ConvertTo-Json -Compress), (New-Object Text.UTF8Encoding($false)))
235
+ }
236
+
237
+ function Enter-ContainedLock {
238
+ $mutex = New-Object System.Threading.Mutex($false, 'Global\AgentSandboxContainedStorage')
239
+ if (-not $mutex.WaitOne(30000)) { throw 'Another mount or dismount is running. Try again in a minute.' }
240
+ return $mutex
241
+ }
@@ -0,0 +1,66 @@
1
+ #Requires -Version 5.1
2
+ # Detaches the contained-storage disk image of one data set (the image file stays, with everything in it). Run elevated
3
+ # (one Windows permission prompt). The request is a JSON file: { set, storageRoot, image, mount, userSid }. The same
4
+ # checks as mount-contained.ps1 apply (exact paths, no links, yours). It refuses while Windows Sandbox is running (the
5
+ # sandbox may be using the folders in the image). Only `detach vdisk` for this one image is run; nothing is deleted.
6
+ # -DryRun prints the diskpart lines and does nothing else; the result goes to result.json next to the request file.
7
+ param(
8
+ [Parameter(Mandatory = $true)][string]$Json,
9
+ [string]$Sha256 = '',
10
+ [switch]$DryRun
11
+ )
12
+ $ErrorActionPreference = 'Stop'
13
+ . (Join-Path $PSScriptRoot 'contained-common.ps1')
14
+
15
+ $result = @{ ok = $false; message = ''; action = 'dismount'; created = $false; alreadyMounted = $false }
16
+
17
+ function Invoke-Dismount($Req, [bool]$Dry, [string]$TempDir) {
18
+ Assert-NoLinksOnTheWay $Req $true
19
+ $root = $Req.storageRoot.TrimEnd('\')
20
+ Assert-OwnedByUser $Req @($root, (Join-Path $root 'images'), (Join-Path $root 'mounts'))
21
+ if (-not (Test-Path -LiteralPath $Req.image -PathType Leaf)) { throw "The image is missing: $($Req.image)" }
22
+ Assert-OwnedByUser $Req @($Req.image)
23
+ $image = $Req.image
24
+ $mount = $Req.mount.TrimEnd('\')
25
+ if (-not $Dry) {
26
+ if (Get-Process -Name 'WindowsSandbox*' -ErrorAction SilentlyContinue) {
27
+ throw 'A Windows Sandbox is running and may be using the image. Close it first.'
28
+ }
29
+ }
30
+ $state = Get-ImageState $image $Dry
31
+ if ((-not $Dry) -and (-not $state.Attached)) {
32
+ if (Test-ReparsePoint $mount) { throw "The mount folder is a mount point of something else; not touching it: $mount" }
33
+ $script:result.ok = $true
34
+ $script:result.message = 'The image is not mounted.'
35
+ Add-Note 'not attached: nothing to do'
36
+ return
37
+ }
38
+ [void](Invoke-DiskPart @("select vdisk file=""$image""", 'detach vdisk') $TempDir $Dry)
39
+ if (-not $Dry) {
40
+ $after = Get-ImageState $image $false
41
+ if ($after.Attached) { throw 'The image is still attached (something is using it). Close programs that use files in it and try again.' }
42
+ if (Test-ReparsePoint $mount) { throw "The mount folder is still a mount point: $mount" }
43
+ }
44
+ $script:result.ok = $true
45
+ $script:result.message = 'The image was detached.'
46
+ }
47
+
48
+ $mutex = $null
49
+ try {
50
+ $request = Read-ContainedRequest -JsonPath $Json -Sha256 $Sha256 -Dry $DryRun.IsPresent
51
+ Assert-ContainedRequest $request
52
+ if (-not $DryRun) {
53
+ if (-not (Test-IsAdministrator)) { throw 'Detaching a disk image needs administrator rights.' }
54
+ $mutex = Enter-ContainedLock
55
+ }
56
+ Invoke-Dismount $request $DryRun.IsPresent (Split-Path -Parent $Json)
57
+ } catch {
58
+ $result.ok = $false
59
+ $result.message = $_.Exception.Message
60
+ } finally {
61
+ if ($mutex) { $mutex.ReleaseMutex(); $mutex.Dispose() }
62
+ }
63
+ Write-ContainedResult $result $Json $DryRun.IsPresent
64
+ if ($result.ok) { exit 0 }
65
+ if ($DryRun) { [Console]::Error.WriteLine($result.message) }
66
+ exit 1
@@ -0,0 +1,34 @@
1
+ #Requires -Version 5.1
2
+ # Prints this PC's memory as JSON: { totalMb, availableMb, commitLimitMb, commitAvailableMb }. Read-only. The fallback
3
+ # for HostTools.exe memory-status (host/life/host-memory.ts).
4
+ param()
5
+ $ErrorActionPreference = 'Stop'
6
+
7
+ $os = Get-CimInstance -ClassName Win32_OperatingSystem
8
+ # The paging file setup from the registry: auto (Windows manages it), fixed (with its maximum sizes) or none.
9
+ $pageFile = 'auto'
10
+ $pageFileMax = 0
11
+ try {
12
+ $entries = @((Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management' -ErrorAction Stop).PagingFiles | Where-Object { $_ -and $_.Trim() })
13
+ $pageFile = 'none'
14
+ foreach ($entry in $entries) {
15
+ $parts = $entry.Trim() -split ' '
16
+ $initial = 0; $max = 0
17
+ if ($parts.Count -ge 3) { $initial = [int64]$parts[-2]; $max = [int64]$parts[-1] }
18
+ if ($entry.StartsWith('?:') -or ($initial -eq 0 -and $max -eq 0)) { $pageFile = 'auto'; $pageFileMax = 0; break }
19
+ $pageFile = 'fixed'
20
+ $pageFileMax += $max
21
+ }
22
+ } catch {
23
+ # Not readable: treated as automatic (no warning).
24
+ }
25
+
26
+ # Win32_OperatingSystem counts in KB.
27
+ ConvertTo-Json -Compress -InputObject ([ordered]@{
28
+ totalMb = [int64][math]::Floor($os.TotalVisibleMemorySize / 1024)
29
+ availableMb = [int64][math]::Floor($os.FreePhysicalMemory / 1024)
30
+ commitLimitMb = [int64][math]::Floor($os.TotalVirtualMemorySize / 1024)
31
+ commitAvailableMb = [int64][math]::Floor($os.FreeVirtualMemory / 1024)
32
+ pageFile = $pageFile
33
+ pageFileMaxMb = [int64]$pageFileMax
34
+ })