diff --git a/ParallelKit.Harness.ps1 b/ParallelKit.Harness.ps1 index 39bfebe..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. @@ -688,6 +691,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 $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() + ")") + # 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.