Skip to content

Optimize PowerShell availability check: file existence fast path with automatic recovery#2596

Draft
Copilot wants to merge 4 commits intomainfrom
copilot/optimize-powershell-validation
Draft

Optimize PowerShell availability check: file existence fast path with automatic recovery#2596
Copilot wants to merge 4 commits intomainfrom
copilot/optimize-powershell-validation

Conversation

Copy link
Contributor

Copilot AI commented Mar 3, 2026

PowerShell validation was taking ~7 seconds on some Windows systems because verifyPowershellCanRun() unconditionally spawned PowerShell processes to probe each candidate path via tryFindWorkingCommand().

Changes

  • Fast path in verifyPowershellCanRun(): Check if powershell.exe exists at the well-known absolute path using a filesystem stat call — no process is spawned. If the file is present, it is assumed to work and returned immediately:
const systemRoot = process.env.SystemRoot ?? process.env.SYSTEMROOT ?? 'C:\\Windows';
const defaultPowershellPath = `${systemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
if (await this.fileUtilities.exists(defaultPowershellPath))
{
    return defaultPowershellPath;
}
// fall through to findWorkingPowershellViaProbing()
  • findWorkingPowershellViaProbing(): The original tryFindWorkingCommand() probe logic is extracted into its own method. It is called both when the default path is absent and as the recovery path when the fast-path result is found to be broken at execution time.

  • Automatic recovery in installDotnet(): The cp.exec call is wrapped in a runInstall(cmd, psRef, isRetry) inner helper. If the first execution attempt fails with cmd.exe-level "process not found" patterns in stderr (e.g. 'powershell.exe' is not recognized as an internal or external command, The system cannot find the path specified) — indicating PowerShell itself could not be launched rather than the install script failing — the helper automatically calls findWorkingPowershellViaProbing() to discover a working alternative and retries once.

  • looksLikePowershellProcessNotFound(): New helper that distinguishes "PowerShell process itself couldn't launch" (cmd.exe-level errors) from "PowerShell ran but the install script failed" (PS-formatted errors), gating the recovery retry.

For the common case — Windows with PowerShell at its default install location — the ~7s delay is replaced by a single near-instant fs.stat call. Systems where the default path is absent or broken transparently fall back to full process-based validation and retry.

Original prompt

This section details on the original issue you should resolve

<issue_title>Powershell scan takes a long time</issue_title>
<issue_description>### Describe the bug.

Please see #2171 (comment)

when we check if powershell is available, this is taking a very long time, several seconds (7 seconds spent validating powershell with shared thread.) We should simplify that logic to check if the file exists for the powershell exectuable first and only run the more thorough validation if it fails the first time.

Please attach log file(s) here if you have any.

The log file location should be copied to your clipboard if you came from VS Code.

Reproduction Steps

.

Expected Behavior

.

Exceptions & Errors (if any)

No response

.NET Install Tool Version

.

Your operating system? Please provide more detail, such as the version and or distro above.

N/A

VS Code Version

No response</issue_description>

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

…processes

Co-authored-by: nagilson <23152278+nagilson@users.noreply.github.com>
Copilot AI changed the title [WIP] Optimize Powershell scan execution time Optimize PowerShell availability check: file existence fast path before process spawning Mar 3, 2026
{
// Fast path: check if the PowerShell executable exists at the well-known absolute path before
// spawning processes to validate, as process-based validation can take several seconds on some systems.
const systemRoot = process.env.SystemRoot ?? process.env.SYSTEMROOT ?? 'C:\\Windows';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This approach seems reasonable, but we should be able to recover from the fast mode logic if it fails and then try the full logic as implemented in the function again, and then try to run the command using the other powershell reference.

Note by fails, I don't mean if the install script itself fails, but if we instead get a specific failure like ENOENT (or the process could not be found, etc) - please test this manually to see what errors are possible (e.g. bad file there (non executable, not powershell executable, etc, or if there's a better way to test if this works)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 745b56a. The fast path now:

  1. Checks if powershell.exe exists at the well-known path
  2. If found, runs a quick single-process execution test (execute('0', [], { shell: defaultPowershellPath }))
  3. If the test succeeds (exit status '0'), returns the path immediately
  4. If the test throws any error (ENOENT, bad/non-PowerShell executable, etc.), catches it and falls through to the full tryFindWorkingCommand() validation that tries all alternative paths (powershell.exe, %SystemRoot%\...\powershell.exe, pwsh, powershell)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Hold on, the point of this PR is that we don't spawn a whole process to do this and assume that the path works by default. Instead, we should be able to recover later when we try to run the script with powershell and it doesn't exist, and can run this check and then call the existing implementation (but with the context of the path that already does not work)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in f0c2fc1. verifyPowershellCanRun() now does a file-existence-only check (no process spawning) — if the file is at the well-known path, it's returned immediately. Recovery from a broken fast-path path happens later in installDotnet(): the cp.exec call is now wrapped in a runInstall(cmd, psRef, isRetry) inner helper. When the first attempt fails with cmd.exe-level "process not found" patterns in stderr (e.g. "is not recognized as an internal or external command"), it calls findWorkingPowershellViaProbing() (the extracted slow tryFindWorkingCommand path) and retries once with the discovered alternative.

… test fails

Co-authored-by: nagilson <23152278+nagilson@users.noreply.github.com>
Copilot AI changed the title Optimize PowerShell availability check: file existence fast path before process spawning Optimize PowerShell availability check: fast path with execution test and fallback to full validation Mar 3, 2026
…lDotnet when PS process launch fails

Co-authored-by: nagilson <23152278+nagilson@users.noreply.github.com>
Copilot AI changed the title Optimize PowerShell availability check: fast path with execution test and fallback to full validation Optimize PowerShell availability check: file existence fast path with automatic recovery Mar 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Powershell scan takes a long time

2 participants