-
Notifications
You must be signed in to change notification settings - Fork 76
Description
Problem
pak::pkg_install() downloads packages successfully but fails during extraction on corporate Windows machines with AppLocker or Software Restriction Policies:
pak::pkg_install("dplyr")
#> Downloaded 27 packages (53.27 MB) in 40.9s
#> Error:
#> ! error in pak subprocess
#> Caused by error in `process_initialize(...)`:
#> ! Native call to `processx_exec` failed
#> Caused by error:
#> ! create process 'C:/Users/.../pak/library/zip/bin/x64/cmdunzip.exe'
#> (system error 1260, This program is blocked by group policy.)The root cause is that pkgdepends:::make_install_process() calls make_unzip_process() → zip::unzip_process(), which spawns cmdunzip.exe as an external process. Corporate group policy blocks this .exe.
Standard R configuration (R_UNZIPCMD, options(unzip=), TAR) has no effect (I had TAR available on my corporate PC) because pak's internal code path ignores all of these.
Impact
This completely blocks pak on corporate Windows. The alternative is install.packages(), which loses pak's parallel downloads and dependency resolution and takes 6-10x longer (over an hour vs ~10 minutes in my case).
Proposed solution
Short term: Add a pak configuration option (e.g., PKG_ZIP_METHOD = "internal") that tells pkgdepends to use zip::unzip() (in-process C code, no external .exe) instead of zip::unzip_process() for .zip extraction.
Long term: The upstream fix belongs in the zip package — see r-lib/zip#135. If zip::unzip_process() could fall back to zip::unzip() when process creation fails, pak would inherit the fix automatically.
Working workaround
We developed a patch that replaces make_install_process in pak's worker subprocess. It extracts .zip files synchronously using zip::unzip(), then returns a real processx::process object (a finished cmd /c exit 0) so pak's async polling loop continues normally. Non-zip archives use the original code path.
library(pak)
pak:::remote(function() {
ns <- asNamespace("pkgdepends")
original <- get("make_install_process", envir = ns)
replacement <- function(filename, lib = .libPaths()[[1L]], metadata = NULL) {
type <- ns$detect_package_archive_type(filename)
if (type != "zip") return(original(filename, lib = lib, metadata = metadata))
now <- Sys.time()
lib_cache <- ns$library_cache(lib)
ns$mkdirp(pkg_cache <- tempfile(tmpdir = lib_cache))
zip::unzip(filename, exdir = pkg_cache, overwrite = TRUE)
ns$install_extracted_binary(filename, lib_cache, pkg_cache, lib, metadata, now)
p <- processx::process$new("cmd.exe", c("/c", "exit", "0"),
stdout = "|", stderr = "2>&1")
p$wait()
reg.finalizer(p, function(...) unlink(pkg_cache, recursive = TRUE), onexit = TRUE)
p
}
unlockBinding("make_install_process", ns)
assign("make_install_process", replacement, envir = ns)
lockBinding("make_install_process", ns)
invisible(TRUE)
})
pak::pkg_install("dplyr") # worksTested successfully with 100+ packages on Windows 11 Enterprise with AppLocker.
Only issue with this is I have to source("patch_pak.R") after each library(pak) load or jam it into my .Rprofile, which would be fine if it was just me, but there's a few hundred R users where I am.
Environment
- Windows 11 Enterprise with AppLocker/SRP enabled
- R 4.4.x
- pak 0.8.0+