virtualshell 1.0.2__tar.gz → 1.1.0__tar.gz
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.
- {virtualshell-1.0.2 → virtualshell-1.1.0}/.gitignore +2 -2
- {virtualshell-1.0.2 → virtualshell-1.1.0}/CMakeLists.txt +3 -7
- {virtualshell-1.0.2 → virtualshell-1.1.0}/PKG-INFO +108 -172
- virtualshell-1.1.0/README.md +186 -0
- virtualshell-1.1.0/bench/bench.csv +4 -0
- virtualshell-1.1.0/bench/bench.json +435 -0
- virtualshell-1.1.0/bench/vs_bench.py +558 -0
- virtualshell-1.1.0/cpp/include/cmd_state.hpp +39 -0
- virtualshell-1.1.0/cpp/include/config.hpp +25 -0
- virtualshell-1.1.0/cpp/include/dev_debug.hpp +228 -0
- virtualshell-1.1.0/cpp/include/execution_result.hpp +38 -0
- virtualshell-1.1.0/cpp/include/helpers.hpp +65 -0
- virtualshell-1.1.0/cpp/include/io_pump.hpp +101 -0
- virtualshell-1.1.0/cpp/include/powershell_process.hpp +174 -0
- virtualshell-1.1.0/cpp/include/process.hpp +32 -0
- virtualshell-1.1.0/cpp/include/py_bridge.hpp +327 -0
- virtualshell-1.1.0/cpp/include/timeout_watcher.hpp +112 -0
- {virtualshell-1.0.2 → virtualshell-1.1.0}/cpp/include/virtual_shell.hpp +130 -266
- virtualshell-1.1.0/cpp/src/binder.cpp +253 -0
- virtualshell-1.1.0/cpp/src/io_pump.cpp +233 -0
- virtualshell-1.1.0/cpp/src/powershell_process.cpp +645 -0
- virtualshell-1.1.0/cpp/src/virtual_shell.cpp +1339 -0
- {virtualshell-1.0.2 → virtualshell-1.1.0}/pyproject.toml +1 -1
- {virtualshell-1.0.2 → virtualshell-1.1.0}/src/virtualshell/__init__.py +4 -4
- virtualshell-1.1.0/src/virtualshell/_version.py +1 -0
- virtualshell-1.1.0/src/virtualshell/get-session.ps1 +153 -0
- virtualshell-1.1.0/src/virtualshell/save-session.ps1 +107 -0
- virtualshell-1.1.0/src/virtualshell/shell.py +522 -0
- virtualshell-1.1.0/wiki/Getting started/Getting started.md +27 -0
- virtualshell-1.1.0/wiki/Getting started/Installation.md +11 -0
- virtualshell-1.1.0/wiki/Getting started/Quickstart.md +30 -0
- virtualshell-1.1.0/wiki/Help/FAQ.md +11 -0
- virtualshell-1.1.0/wiki/Help/Troubleshooting.md +20 -0
- virtualshell-1.1.0/wiki/Home.md +10 -0
- virtualshell-1.1.0/wiki/Project/Benchmarks.md +65 -0
- virtualshell-1.1.0/wiki/Project/Changelog.md +19 -0
- virtualshell-1.1.0/wiki/Project/Design & Architecture.md +12 -0
- virtualshell-1.1.0/wiki/Usage/API Overview.md +23 -0
- virtualshell-1.1.0/wiki/Usage/Asynchronous Execution.md +25 -0
- virtualshell-1.1.0/wiki/Usage/Configuration.md +14 -0
- virtualshell-1.1.0/wiki/Usage/Error Handling.md +23 -0
- virtualshell-1.1.0/wiki/Usage/Performance Tips.md +7 -0
- virtualshell-1.1.0/wiki/Usage/Running Scripts.md +26 -0
- virtualshell-1.1.0/wiki/Usage/Security Notes.md +5 -0
- virtualshell-1.1.0/wiki/Usage/Synchronous Execution.md +27 -0
- virtualshell-1.0.2/README.md +0 -250
- virtualshell-1.0.2/cpp/src/binder.cpp +0 -529
- virtualshell-1.0.2/cpp/src/virtual_shell.cpp +0 -1806
- virtualshell-1.0.2/demo.py +0 -328
- virtualshell-1.0.2/src/virtualshell/_version.py +0 -1
- virtualshell-1.0.2/src/virtualshell/shell.py +0 -515
- {virtualshell-1.0.2 → virtualshell-1.1.0}/.github/workflows/workflow.yml +0 -0
- {virtualshell-1.0.2 → virtualshell-1.1.0}/LICENSE +0 -0
- {virtualshell-1.0.2 → virtualshell-1.1.0}/src/virtualshell/errors.py +0 -0
|
@@ -21,14 +21,10 @@ set(CMAKE_CXX_STANDARD 17)
|
|
|
21
21
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
22
22
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|
26
|
-
message(FATAL_ERROR "MinGW is not supported on Windows for this project. Use MSVC.")
|
|
27
|
-
endif()
|
|
28
|
-
|
|
29
|
-
|
|
30
24
|
set(SRC
|
|
31
25
|
cpp/src/binder.cpp
|
|
26
|
+
cpp/src/io_pump.cpp
|
|
27
|
+
cpp/src/powershell_process.cpp
|
|
32
28
|
cpp/src/virtual_shell.cpp
|
|
33
29
|
)
|
|
34
30
|
|
|
@@ -39,7 +35,7 @@ else()
|
|
|
39
35
|
set(_VS_OUTDIR "${SKBUILD_PLATLIB_DIR}/virtualshell")
|
|
40
36
|
endif()
|
|
41
37
|
|
|
42
|
-
pybind11_add_module(_core MODULE ${SRC})
|
|
38
|
+
pybind11_add_module(_core MODULE ${SRC} cpp/include/py_bridge.hpp)
|
|
43
39
|
target_include_directories(_core PRIVATE cpp/include)
|
|
44
40
|
|
|
45
41
|
if(UNIX)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: virtualshell
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: High-performance PowerShell bridge (C++ pybind11 backend)
|
|
5
5
|
Keywords: powershell,automation,shell,cpp,pybind11
|
|
6
6
|
Author: Kim-Andre Myrvold
|
|
@@ -219,251 +219,187 @@ Description-Content-Type: text/markdown
|
|
|
219
219
|
|
|
220
220
|
# virtualshell
|
|
221
221
|
|
|
222
|
-
High-performance Python
|
|
223
|
-
|
|
222
|
+
High-performance PowerShell automation for Python. `virtualshell` keeps a single PowerShell host warm and exposes it through a thin Python wrapper backed by a C++ engine. The result: millisecond-scale latency, async execution, and session persistence without juggling subprocesses.
|
|
223
|
+
|
|
224
|
+
> Full documentation now lives in the [project wiki](https://github.com/Chamoswor/virtualshell/wiki). This README gives you the essentials and quick links.
|
|
224
225
|
|
|
225
226
|
---
|
|
226
227
|
|
|
227
|
-
##
|
|
228
|
+
## Why virtualshell?
|
|
229
|
+
|
|
230
|
+
- **Persistent session** – reuse modules, `$env:*`, and functions between calls.
|
|
231
|
+
- **Low latency** – avoid the 200+ ms penalty of `subprocess.run("pwsh")`; most commands settle in ~2-4 ms.
|
|
232
|
+
- **Async + batching** – schedule commands concurrently or in batches with strong timeout control.
|
|
233
|
+
- **Structured results** – every invocation returns stdout/stderr, exit code, success flag, and timing.
|
|
234
|
+
- **Predictable failures** – typed Python exceptions for “pwsh missing”, timeouts, and execution errors.
|
|
228
235
|
|
|
229
|
-
|
|
230
|
-
- **Sync & async** execution (Futures + optional callbacks)
|
|
231
|
-
- **Script execution** (positional / named args, optional dot-sourcing)
|
|
232
|
-
- **Batch** with per-command timeout & early-stop
|
|
233
|
-
- **Clear failures** (typed exceptions), **context manager** lifecycle
|
|
236
|
+
Typical users embed PowerShell inside Python orchestration, long-running agents, or test suites that need reliability and speed.
|
|
234
237
|
|
|
235
238
|
---
|
|
236
239
|
|
|
237
|
-
##
|
|
240
|
+
## Installation
|
|
238
241
|
|
|
239
242
|
```bash
|
|
240
243
|
pip install virtualshell
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
## Platform & Python Support
|
|
244
|
-
|
|
245
|
-
Prebuilt wheels are provided via PyPI for common platforms and Python versions.
|
|
246
|
-
This means you can usually `pip install virtualshell` without needing a compiler.
|
|
247
|
-
|
|
248
|
-
**Supported Python versions:**
|
|
249
|
-
- 3.8, 3.9, 3.10, 3.11, 3.12, 3.13
|
|
250
|
-
|
|
251
|
-
**Supported platforms:**
|
|
252
|
-
- **Windows** (x86_64, MSVC build)
|
|
253
|
-
- **Linux** (x86_64, aarch64, manylinux2014/2.28)
|
|
254
|
-
- **macOS** (universal2: x86_64 + arm64)
|
|
255
|
-
|
|
256
|
-
If your platform is not listed above, pip will fall back to building from source.
|
|
257
|
-
See [Building from source](#building-from-source-advanced) for details.
|
|
258
|
-
|
|
244
|
+
```
|
|
259
245
|
|
|
260
|
-
|
|
246
|
+
Pre-built wheels are published for Windows, Linux (x86_64/aarch64), and macOS universal2. PowerShell (`pwsh` or `powershell.exe`) must be discoverable on `PATH` unless you pass an explicit path.
|
|
261
247
|
|
|
262
248
|
---
|
|
263
249
|
|
|
264
250
|
## Quick start
|
|
265
251
|
|
|
266
252
|
```python
|
|
267
|
-
import
|
|
268
|
-
|
|
269
|
-
# Create a shell with a 5s default timeout
|
|
270
|
-
sh = virtualshell.Shell(timeout_seconds=5).start()
|
|
271
|
-
|
|
272
|
-
# 1) One-liners (sync)
|
|
273
|
-
res = sh.run("Write-Output 'hello'")
|
|
274
|
-
print(res.out.strip()) # -> hello
|
|
275
|
-
|
|
276
|
-
# 2) Async single command
|
|
277
|
-
fut = sh.run_async("Write-Output 'async!'")
|
|
278
|
-
print(fut.result().out.strip())
|
|
279
|
-
|
|
280
|
-
# 3) Scripts with positional args
|
|
281
|
-
r = sh.run_script(r"C:\temp\demo.ps1", args=["alpha", "42"])
|
|
282
|
-
print(r.out)
|
|
283
|
-
|
|
284
|
-
# 4) Scripts with named args
|
|
285
|
-
r = sh.run_script_kv(r"C:\temp\demo.ps1", named_args={"Name":"Alice","Count":"3"})
|
|
286
|
-
print(r.out)
|
|
253
|
+
from virtualshell import Shell
|
|
287
254
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
print(
|
|
255
|
+
with Shell(timeout_seconds=5) as sh:
|
|
256
|
+
result = sh.run("Write-Output 'Hello from pwsh'")
|
|
257
|
+
print(result.out.strip())
|
|
291
258
|
|
|
292
|
-
sh.
|
|
259
|
+
sh.run("function Inc { $global:i++; $global:i }")
|
|
260
|
+
print(sh.run("Inc").out.strip()) # 1
|
|
261
|
+
print(sh.run("Inc").out.strip()) # 2
|
|
293
262
|
```
|
|
294
263
|
|
|
295
|
-
|
|
264
|
+
### Async execution
|
|
296
265
|
|
|
297
266
|
```python
|
|
298
267
|
from virtualshell import Shell
|
|
299
|
-
|
|
300
|
-
sh.run("function Inc { $global:i++; $global:i }")
|
|
301
|
-
nums = [sh.run("Inc").out.strip() for _ in range(5)]
|
|
302
|
-
print(nums) # ['1','2','3','4','5']
|
|
303
|
-
```
|
|
268
|
+
import asyncio
|
|
304
269
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
270
|
+
async def main():
|
|
271
|
+
shell = Shell().start()
|
|
272
|
+
fut = shell.run_async("Get-Date")
|
|
273
|
+
res = await asyncio.wrap_future(fut)
|
|
274
|
+
print(res.out.strip())
|
|
275
|
+
shell.stop()
|
|
308
276
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
from virtualshell import ExecutionResult # dataclass view
|
|
277
|
+
asyncio.run(main())
|
|
278
|
+
```
|
|
312
279
|
|
|
313
|
-
|
|
314
|
-
powershell_path=None, # optional explicit path
|
|
315
|
-
working_directory=None, # resolved to absolute path
|
|
316
|
-
timeout_seconds=5.0, # default per-command timeout
|
|
317
|
-
environment={"FOO": "BAR"}, # extra child env vars
|
|
318
|
-
initial_commands=["$ErrorActionPreference='Stop'"], # post-start setup
|
|
319
|
-
).start()
|
|
280
|
+
### Scripts and arguments
|
|
320
281
|
|
|
321
|
-
|
|
322
|
-
|
|
282
|
+
```python
|
|
283
|
+
from pathlib import Path
|
|
284
|
+
from virtualshell import Shell
|
|
323
285
|
|
|
324
|
-
|
|
325
|
-
res = sh.run_script(r"/path/to/job.ps1", args=["--fast","1"])
|
|
326
|
-
res = sh.run_script_kv(r"/path/to/job.ps1", named_args={"Mode":"Fast","Count":"1"})
|
|
327
|
-
res = sh.run_script(r"/path/init.ps1", dot_source=True)
|
|
286
|
+
shell = Shell().start()
|
|
328
287
|
|
|
329
|
-
#
|
|
330
|
-
|
|
331
|
-
f2 = sh.run_async_batch(["$PSVersionTable", "Get-Random"])
|
|
288
|
+
# Positional arguments
|
|
289
|
+
shell.script(Path("./scripts/test.ps1"), args=["alpha", "42"])
|
|
332
290
|
|
|
333
|
-
#
|
|
334
|
-
|
|
291
|
+
# Named arguments (hashtable splatting)
|
|
292
|
+
shell.script(
|
|
293
|
+
Path("./scripts/test.ps1"),
|
|
294
|
+
args={"Name": "Alice", "Count": "3"},
|
|
295
|
+
)
|
|
335
296
|
|
|
336
|
-
|
|
297
|
+
shell.stop()
|
|
337
298
|
```
|
|
338
299
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
By default you get a Python dataclass:
|
|
300
|
+
Every API surface (sync/async/script) accepts `timeout` overrides and optional error raising via `raise_on_error` or callbacks.
|
|
342
301
|
|
|
343
|
-
|
|
344
|
-
@dataclass(frozen=True)
|
|
345
|
-
class ExecutionResult:
|
|
346
|
-
out: str
|
|
347
|
-
err: str
|
|
348
|
-
exit_code: int
|
|
349
|
-
success: bool
|
|
350
|
-
execution_time: float
|
|
351
|
-
```
|
|
302
|
+
---
|
|
352
303
|
|
|
353
|
-
|
|
304
|
+
## Core API overview
|
|
354
305
|
|
|
355
|
-
|
|
306
|
+
| Method | Purpose |
|
|
307
|
+
| --- | --- |
|
|
308
|
+
| `Shell.run(cmd, *, timeout=None, raise_on_error=False)` | Execute a single command synchronously. |
|
|
309
|
+
| `Shell.run_async(cmd, *, callback=None, timeout=None)` | Schedule a command; returns a `concurrent.futures.Future`. |
|
|
310
|
+
| `Shell.script(path, args=None, *, timeout=None, dot_source=False, raise_on_error=False)` | Execute `.ps1` files with positional or named arguments. |
|
|
311
|
+
| `Shell.script_async(...)` | Async counterpart of `script`. |
|
|
312
|
+
| `Shell.save_session()` | Persist the current session to an XML snapshot. |
|
|
313
|
+
| `Shell.pwsh(text)` | Safely echo a literal PowerShell string (auto quoting). |
|
|
356
314
|
|
|
357
|
-
|
|
358
|
-
* On timeout: `success=False`, `exit_code=-1`, `err` contains `"timeout"`.
|
|
359
|
-
* Async futures resolve with the timeout result; late output is dropped in C++.
|
|
315
|
+
More helpers live in the wiki, including session restore, batching, and diagnostic tips.
|
|
360
316
|
|
|
361
317
|
---
|
|
362
318
|
|
|
363
|
-
##
|
|
364
|
-
|
|
365
|
-
* **Thin wrapper:** heavy I/O in C++; Python does orchestration only.
|
|
366
|
-
* **No surprises:** stable API, documented side-effects.
|
|
367
|
-
* **Clear failure modes:** `raise_on_error` and typed exceptions.
|
|
368
|
-
* **Thread-friendly:** async returns Futures/callbacks; no Python GIL-level locking.
|
|
369
|
-
* **Boundary hygiene:** explicit path/arg conversions, minimal marshalling.
|
|
370
|
-
|
|
371
|
-
### Security
|
|
319
|
+
## Configuration
|
|
372
320
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
* Avoid logging secrets; env injection happens via `Shell(..., environment=...)`.
|
|
376
|
-
|
|
377
|
-
### Performance
|
|
321
|
+
```python
|
|
322
|
+
from virtualshell import Shell
|
|
378
323
|
|
|
379
|
-
|
|
380
|
-
|
|
324
|
+
shell = Shell(
|
|
325
|
+
powershell_path=r"C:\Program Files\PowerShell\7\pwsh.exe",
|
|
326
|
+
working_directory=r"C:\automation",
|
|
327
|
+
environment={"MY_FLAG": "1"},
|
|
328
|
+
initial_commands=[
|
|
329
|
+
"$ErrorActionPreference = 'Stop'",
|
|
330
|
+
"$ProgressPreference = 'SilentlyContinue'",
|
|
331
|
+
],
|
|
332
|
+
timeout_seconds=10,
|
|
333
|
+
auto_restart_on_timeout=True,
|
|
334
|
+
)
|
|
381
335
|
|
|
382
|
-
|
|
336
|
+
shell.start()
|
|
337
|
+
```
|
|
383
338
|
|
|
384
|
-
|
|
385
|
-
* `with Shell(...)` guarantees stop-on-exit, even on exceptions.
|
|
339
|
+
Configuration is applied before the process starts. You can inspect or replace it later with `shell._core.get_config()` or rebuild the shell.
|
|
386
340
|
|
|
387
341
|
---
|
|
388
342
|
|
|
389
|
-
##
|
|
343
|
+
## Error handling
|
|
390
344
|
|
|
391
345
|
```python
|
|
392
|
-
from virtualshell
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
ExecutionError,
|
|
397
|
-
)
|
|
346
|
+
from virtualshell import Shell
|
|
347
|
+
from virtualshell.errors import ExecutionError, ExecutionTimeoutError
|
|
348
|
+
|
|
349
|
+
shell = Shell().start()
|
|
398
350
|
|
|
399
351
|
try:
|
|
400
|
-
|
|
352
|
+
shell.run("throw 'boom'", raise_on_error=True)
|
|
401
353
|
except ExecutionTimeoutError:
|
|
402
|
-
|
|
403
|
-
except ExecutionError as
|
|
404
|
-
print("PowerShell
|
|
354
|
+
print("Timed out")
|
|
355
|
+
except ExecutionError as exc:
|
|
356
|
+
print("PowerShell failure:", exc)
|
|
357
|
+
finally:
|
|
358
|
+
shell.stop()
|
|
405
359
|
```
|
|
406
360
|
|
|
407
|
-
|
|
408
|
-
* Otherwise, APIs return `ExecutionResult(success=False)`.
|
|
361
|
+
Refer to `virtualshell.errors` for all exception types.
|
|
409
362
|
|
|
410
363
|
---
|
|
411
364
|
|
|
412
|
-
##
|
|
413
|
-
|
|
414
|
-
If PowerShell isn’t on `PATH`, pass `powershell_path`:
|
|
365
|
+
## Performance
|
|
415
366
|
|
|
416
|
-
|
|
417
|
-
Shell(powershell_path=r"C:\Program Files\PowerShell\7\pwsh.exe")
|
|
418
|
-
```
|
|
367
|
+
Up-to-date benchmark artefacts (`bench.json`, `bench.csv`) and analysis live in [docs/wiki/Project/Benchmarks.md](docs/wiki/Project/Benchmarks.md). Headline numbers from the latest run (Windows 11, Python 3.13):
|
|
419
368
|
|
|
420
|
-
|
|
369
|
+
- Sequential commands: ~3.5 ms average
|
|
370
|
+
- Batch commands: ~3.2 ms per command
|
|
371
|
+
- Async latency: ~1.9–2.4 ms with 50 outstanding tasks
|
|
372
|
+
- Session save: ~0.30 s median
|
|
421
373
|
|
|
422
|
-
|
|
423
|
-
Shell(initial_commands=[
|
|
424
|
-
"$OutputEncoding = [Console]::OutputEncoding = [Text.UTF8Encoding]::new()",
|
|
425
|
-
"$ErrorActionPreference = 'Stop'"
|
|
426
|
-
])
|
|
427
|
-
```
|
|
374
|
+
See the wiki for charts and methodology.
|
|
428
375
|
|
|
429
376
|
---
|
|
430
377
|
|
|
431
|
-
|
|
432
|
-
Source builds require a C++ toolchain and CMake.
|
|
378
|
+
## Building from source
|
|
433
379
|
|
|
434
|
-
|
|
380
|
+
Dependencies:
|
|
435
381
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
python -m pip install dist/virtualshell-*.whl
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
Editable install:
|
|
382
|
+
- Python 3.8+
|
|
383
|
+
- CMake 3.20+
|
|
384
|
+
- A C++17 compiler (MSVC, Clang, or GCC)
|
|
385
|
+
- `scikit-build-core`, `pybind11`
|
|
444
386
|
|
|
445
387
|
```bash
|
|
446
|
-
python -m pip install -
|
|
388
|
+
python -m pip install -U build
|
|
389
|
+
python -m build
|
|
390
|
+
python -m pip install dist/virtualshell-*.whl
|
|
447
391
|
```
|
|
448
392
|
|
|
449
|
-
* Linux wheels target **manylinux_2_28** (x86_64/aarch64).
|
|
450
|
-
* macOS builds target **universal2** (x86_64 + arm64).
|
|
451
|
-
|
|
452
393
|
---
|
|
453
394
|
|
|
454
|
-
##
|
|
455
|
-
|
|
456
|
-
* ✅ Windows x64 wheels (3.8–3.13)
|
|
457
|
-
* ✅ Linux x64/aarch64 wheels (manylinux_2_28)
|
|
458
|
-
* ✅ macOS x86_64/arm64 wheels
|
|
459
|
-
* ⏳ Streaming APIs and richer progress events
|
|
460
|
-
|
|
461
|
-
---
|
|
395
|
+
## Learn more
|
|
462
396
|
|
|
463
|
-
|
|
397
|
+
- [Usage guides](docs/wiki/Usage)
|
|
398
|
+
- [Performance tips](docs/wiki/Usage/Performance%20Tips.md)
|
|
399
|
+
- [Benchmarks](docs/wiki/Project/Benchmarks.md)
|
|
464
400
|
|
|
465
|
-
|
|
401
|
+
Bug reports and feature requests are welcome via issues or discussions.
|
|
466
402
|
|
|
467
403
|
---
|
|
468
404
|
|
|
469
|
-
|
|
405
|
+
Licensed under the Apache 2.0 license. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# virtualshell
|
|
2
|
+
|
|
3
|
+
High-performance PowerShell automation for Python. `virtualshell` keeps a single PowerShell host warm and exposes it through a thin Python wrapper backed by a C++ engine. The result: millisecond-scale latency, async execution, and session persistence without juggling subprocesses.
|
|
4
|
+
|
|
5
|
+
> Full documentation now lives in the [project wiki](https://github.com/Chamoswor/virtualshell/wiki). This README gives you the essentials and quick links.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Why virtualshell?
|
|
10
|
+
|
|
11
|
+
- **Persistent session** – reuse modules, `$env:*`, and functions between calls.
|
|
12
|
+
- **Low latency** – avoid the 200+ ms penalty of `subprocess.run("pwsh")`; most commands settle in ~2-4 ms.
|
|
13
|
+
- **Async + batching** – schedule commands concurrently or in batches with strong timeout control.
|
|
14
|
+
- **Structured results** – every invocation returns stdout/stderr, exit code, success flag, and timing.
|
|
15
|
+
- **Predictable failures** – typed Python exceptions for “pwsh missing”, timeouts, and execution errors.
|
|
16
|
+
|
|
17
|
+
Typical users embed PowerShell inside Python orchestration, long-running agents, or test suites that need reliability and speed.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install virtualshell
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Pre-built wheels are published for Windows, Linux (x86_64/aarch64), and macOS universal2. PowerShell (`pwsh` or `powershell.exe`) must be discoverable on `PATH` unless you pass an explicit path.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from virtualshell import Shell
|
|
35
|
+
|
|
36
|
+
with Shell(timeout_seconds=5) as sh:
|
|
37
|
+
result = sh.run("Write-Output 'Hello from pwsh'")
|
|
38
|
+
print(result.out.strip())
|
|
39
|
+
|
|
40
|
+
sh.run("function Inc { $global:i++; $global:i }")
|
|
41
|
+
print(sh.run("Inc").out.strip()) # 1
|
|
42
|
+
print(sh.run("Inc").out.strip()) # 2
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Async execution
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from virtualshell import Shell
|
|
49
|
+
import asyncio
|
|
50
|
+
|
|
51
|
+
async def main():
|
|
52
|
+
shell = Shell().start()
|
|
53
|
+
fut = shell.run_async("Get-Date")
|
|
54
|
+
res = await asyncio.wrap_future(fut)
|
|
55
|
+
print(res.out.strip())
|
|
56
|
+
shell.stop()
|
|
57
|
+
|
|
58
|
+
asyncio.run(main())
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Scripts and arguments
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from pathlib import Path
|
|
65
|
+
from virtualshell import Shell
|
|
66
|
+
|
|
67
|
+
shell = Shell().start()
|
|
68
|
+
|
|
69
|
+
# Positional arguments
|
|
70
|
+
shell.script(Path("./scripts/test.ps1"), args=["alpha", "42"])
|
|
71
|
+
|
|
72
|
+
# Named arguments (hashtable splatting)
|
|
73
|
+
shell.script(
|
|
74
|
+
Path("./scripts/test.ps1"),
|
|
75
|
+
args={"Name": "Alice", "Count": "3"},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
shell.stop()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Every API surface (sync/async/script) accepts `timeout` overrides and optional error raising via `raise_on_error` or callbacks.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Core API overview
|
|
86
|
+
|
|
87
|
+
| Method | Purpose |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| `Shell.run(cmd, *, timeout=None, raise_on_error=False)` | Execute a single command synchronously. |
|
|
90
|
+
| `Shell.run_async(cmd, *, callback=None, timeout=None)` | Schedule a command; returns a `concurrent.futures.Future`. |
|
|
91
|
+
| `Shell.script(path, args=None, *, timeout=None, dot_source=False, raise_on_error=False)` | Execute `.ps1` files with positional or named arguments. |
|
|
92
|
+
| `Shell.script_async(...)` | Async counterpart of `script`. |
|
|
93
|
+
| `Shell.save_session()` | Persist the current session to an XML snapshot. |
|
|
94
|
+
| `Shell.pwsh(text)` | Safely echo a literal PowerShell string (auto quoting). |
|
|
95
|
+
|
|
96
|
+
More helpers live in the wiki, including session restore, batching, and diagnostic tips.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from virtualshell import Shell
|
|
104
|
+
|
|
105
|
+
shell = Shell(
|
|
106
|
+
powershell_path=r"C:\Program Files\PowerShell\7\pwsh.exe",
|
|
107
|
+
working_directory=r"C:\automation",
|
|
108
|
+
environment={"MY_FLAG": "1"},
|
|
109
|
+
initial_commands=[
|
|
110
|
+
"$ErrorActionPreference = 'Stop'",
|
|
111
|
+
"$ProgressPreference = 'SilentlyContinue'",
|
|
112
|
+
],
|
|
113
|
+
timeout_seconds=10,
|
|
114
|
+
auto_restart_on_timeout=True,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
shell.start()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Configuration is applied before the process starts. You can inspect or replace it later with `shell._core.get_config()` or rebuild the shell.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Error handling
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from virtualshell import Shell
|
|
128
|
+
from virtualshell.errors import ExecutionError, ExecutionTimeoutError
|
|
129
|
+
|
|
130
|
+
shell = Shell().start()
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
shell.run("throw 'boom'", raise_on_error=True)
|
|
134
|
+
except ExecutionTimeoutError:
|
|
135
|
+
print("Timed out")
|
|
136
|
+
except ExecutionError as exc:
|
|
137
|
+
print("PowerShell failure:", exc)
|
|
138
|
+
finally:
|
|
139
|
+
shell.stop()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Refer to `virtualshell.errors` for all exception types.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Performance
|
|
147
|
+
|
|
148
|
+
Up-to-date benchmark artefacts (`bench.json`, `bench.csv`) and analysis live in [docs/wiki/Project/Benchmarks.md](docs/wiki/Project/Benchmarks.md). Headline numbers from the latest run (Windows 11, Python 3.13):
|
|
149
|
+
|
|
150
|
+
- Sequential commands: ~3.5 ms average
|
|
151
|
+
- Batch commands: ~3.2 ms per command
|
|
152
|
+
- Async latency: ~1.9–2.4 ms with 50 outstanding tasks
|
|
153
|
+
- Session save: ~0.30 s median
|
|
154
|
+
|
|
155
|
+
See the wiki for charts and methodology.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Building from source
|
|
160
|
+
|
|
161
|
+
Dependencies:
|
|
162
|
+
|
|
163
|
+
- Python 3.8+
|
|
164
|
+
- CMake 3.20+
|
|
165
|
+
- A C++17 compiler (MSVC, Clang, or GCC)
|
|
166
|
+
- `scikit-build-core`, `pybind11`
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
python -m pip install -U build
|
|
170
|
+
python -m build
|
|
171
|
+
python -m pip install dist/virtualshell-*.whl
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Learn more
|
|
177
|
+
|
|
178
|
+
- [Usage guides](docs/wiki/Usage)
|
|
179
|
+
- [Performance tips](docs/wiki/Usage/Performance%20Tips.md)
|
|
180
|
+
- [Benchmarks](docs/wiki/Project/Benchmarks.md)
|
|
181
|
+
|
|
182
|
+
Bug reports and feature requests are welcome via issues or discussions.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
Licensed under the Apache 2.0 license. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
size,single_mean_ms,single_thr,batch_per_cmd_mean_ms,batch_thr,async_lat_mean_ms,async_thr,batch_eff,async_eff
|
|
2
|
+
50,4.041,247.301,3.387,295.240,2.359,293.607,1.193,1.186
|
|
3
|
+
100,3.754,266.165,3.685,271.400,2.247,304.264,1.019,1.142
|
|
4
|
+
200,3.476,287.452,3.203,312.230,1.938,348.655,1.085,1.212
|