From 8ab05de588f5c25dc314da06e88772109a95ff72 Mon Sep 17 00:00:00 2001 From: Hal <22532613+HalRiveria@users.noreply.github.com> Date: Wed, 28 Oct 2026 03:16:10 -0400 Subject: [PATCH 1/2] Fail clearly when loaded in Constrained Language Mode On a machine locked to Constrained Language Mode, dot-sourcing the kit failed with "Cannot convert value to type PSTypeName. Only core types are supported in this language mode", which doesn't say what to do. It now checks the language mode first and throws a message naming the mode, why the kit needs FullLanguage, and what usually sets it. The README's Limits section says so, and mentions endpoint security flagging runtime Add-Type. Co-Authored-By: Claude Opus 5.5 --- ParallelKit.Harness.ps1 | 11 +++++++++++ ParallelKit.ps1 | 8 ++++++++ README.md | 1 + 3 files changed, 20 insertions(+) diff --git a/ParallelKit.Harness.ps1 b/ParallelKit.Harness.ps1 index 39bfebe..80d42b4 100644 --- a/ParallelKit.Harness.ps1 +++ b/ParallelKit.Harness.ps1 @@ -688,6 +688,17 @@ $probeThreads = 0 if ($probe.Stdout -match 'threads=(\d+)') { $probeThreads = [int]$Matches[1] } Test-Assert ($probeThreads -ge 128) ("and raises the thread pool by itself (minimum " + $probeThreads + ")") +# Loading the kit in Constrained Language Mode fails with a message that +# says so. Needs a fresh process: the mode can't be set back. +$clmScript = Join-Path $env:TEMP ("pk-clm-" + [guid]::NewGuid().ToString("N") + ".ps1") +[System.IO.File]::WriteAllText($clmScript, @" +`$ExecutionContext.SessionState.LanguageMode = 'ConstrainedLanguage' +try { . '$kitPath'; Write-Output 'loaded' } catch { Write-Output ('error: ' + `$_) } +"@) +$clm = Invoke-PkProcessWithTimeout -FilePath (Join-Path $PSHOME "powershell.exe") -Arguments ('-NoProfile -ExecutionPolicy Bypass -File "' + $clmScript + '"') -TimeoutMs 60000 +[System.IO.File]::Delete($clmScript) +Test-Assert ($clm.Stdout -like '*ConstrainedLanguage mode, but the kit needs FullLanguage*') ("loading in Constrained Language Mode fails with a message naming the problem (" + ([string]$clm.Stdout).Trim() + ")") + # The dispatcher sizes the pool for its own concurrency. $feed = @{ Given = $false } $null = Invoke-PkProcessDispatch -MaxConcurrent 100 -GroupProvider { return $null } -JobBuilder { param($g) } -JobFinalize { param($j, $p) } diff --git a/ParallelKit.ps1 b/ParallelKit.ps1 index b4aa176..2c16ae9 100644 --- a/ParallelKit.ps1 +++ b/ParallelKit.ps1 @@ -13,6 +13,14 @@ # function called from inside a .GetNewClosure()'d block can see an empty # $script: scope, and a static has no scope to lose. +# Everything below needs Add-Type, which Constrained Language Mode blocks with +# an error that doesn't say why. Say why. +if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { + throw ("ParallelKit: this PowerShell session runs in " + $ExecutionContext.SessionState.LanguageMode + " mode, but the kit needs FullLanguage " + + "(it compiles C# with Add-Type). This is usually set by AppLocker or Windows Defender Application Control policy. " + + "Ask whoever manages the machine to allow this script, or run it where that policy doesn't apply.") +} + # ============================================================================== # Native types (guarded so the file can be dot-sourced more than once) # ============================================================================== diff --git a/README.md b/README.md index 8452e2d..0398a16 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,7 @@ Other pieces: `Request-PkAbort` triggers the same abort from code. `Test-PkAbort ## Limits - Windows PowerShell 5.1, Windows only (console and Job Object APIs). +- It needs FullLanguage mode, since it compiles a little C# with `Add-Type` when loaded. On a machine locked down to Constrained Language Mode (usually by AppLocker or Windows Defender Application Control), loading it fails with a message saying so. Some endpoint security products also flag scripts that compile code calling Windows APIs at runtime; if yours blocks the kit, that's the likely reason. - After changing `ParallelKit.ps1` itself, start a new PowerShell window. A session keeps the kit's compiled parts until it closes; the kit tells you if it finds an older copy loaded. - One run at a time per process. The abort state is shared. A nested `Invoke-PkRun` just runs inside the outer one. - A worker stuck inside a long .NET call can't be interrupted. Force-stop stops waiting for it and returns. The worker is NOT killed: once that call returns it can carry on to the end of its script. Before any step that can't be undone (deleting a source file, say), have the worker check `Test-PkCancelRequested` and skip the step if it's true. From ec0ab6b4a76d287bb4ff24b4e95f1358fc799aee Mon Sep 17 00:00:00 2001 From: Hal <22532613+HalRiveria@users.noreply.github.com> Date: Wed, 28 Oct 2026 03:36:02 -0400 Subject: [PATCH 2/2] Harness: run the CLM test with the running PowerShell, not $PSHOME\powershell.exe $PSHOME\powershell.exe doesn't exist under PowerShell 7. Uses the same $selfExe as the PowerShell 7 CI change, so the two merge cleanly. Co-Authored-By: Claude Opus 5.5 --- ParallelKit.Harness.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ParallelKit.Harness.ps1 b/ParallelKit.Harness.ps1 index 80d42b4..9bc7a65 100644 --- a/ParallelKit.Harness.ps1 +++ b/ParallelKit.Harness.ps1 @@ -43,6 +43,9 @@ function New-JobList { } $cmdExe = Join-Path $env:SystemRoot "System32\cmd.exe" +# Whichever PowerShell is running the harness (powershell.exe or pwsh.exe), +# for the tests that need a fresh process of the same version. +$selfExe = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName $pingExe = Join-Path $env:SystemRoot "System32\ping.exe" # A user function the workers share. @@ -695,7 +698,7 @@ $clmScript = Join-Path $env:TEMP ("pk-clm-" + [guid]::NewGuid().ToString("N") + `$ExecutionContext.SessionState.LanguageMode = 'ConstrainedLanguage' try { . '$kitPath'; Write-Output 'loaded' } catch { Write-Output ('error: ' + `$_) } "@) -$clm = Invoke-PkProcessWithTimeout -FilePath (Join-Path $PSHOME "powershell.exe") -Arguments ('-NoProfile -ExecutionPolicy Bypass -File "' + $clmScript + '"') -TimeoutMs 60000 +$clm = Invoke-PkProcessWithTimeout -FilePath $selfExe -Arguments ('-NoProfile -ExecutionPolicy Bypass -File "' + $clmScript + '"') -TimeoutMs 60000 [System.IO.File]::Delete($clmScript) Test-Assert ($clm.Stdout -like '*ConstrainedLanguage mode, but the kit needs FullLanguage*') ("loading in Constrained Language Mode fails with a message naming the problem (" + ([string]$clm.Stdout).Trim() + ")")