Skip to content

Conversation

@polatengin
Copy link
Member

@polatengin polatengin commented Oct 30, 2025

VersionChecker helper scans well-known install locations, runs bicep --version to parse versions, and reports any installations newer than the running CLI.

Fixes #5070

Description

Well-known installation locations checked:

~/.bicep/bin (default location)
~/.azure/bin (Azure CLI installation location)

Windows-specific:

  • C:\Program Files\Bicep CLI
  • C:\Program Files (x86)\Bicep CLI
  • All directories in the PATH environment variable

Linux-specific:

  • /usr/local/bin
  • /usr/bin/
  • All directories in the PATH environment variable

Example Usage

Run any bicep command. You may see this warning in the terminal;

Warning: You are running Bicep CLI version 0.38.1, but newer version(s) are installed on this system:
  - Version 0.40.23 at /usr/local/bin/bicep
  - Version 0.39.47 at ~/.azure/bin/bicep

Checklist

Microsoft Reviewers: Open in CodeFlow

…ions

VersionChecker helper scans well-known install locations, runs `bicep --version` to parse versions, and reports any installations newer than the running CLI.
@github-actions
Copy link
Contributor

github-actions bot commented Oct 30, 2025

Test this change out locally with the following install scripts (Action run 19186198907)

VSCode
  • Mac/Linux
    bash <(curl -Ls https://aka.ms/bicep/nightly-vsix.sh) --run-id 19186198907
  • Windows
    iex "& { $(irm https://aka.ms/bicep/nightly-vsix.ps1) } -RunId 19186198907"
Azure CLI
  • Mac/Linux
    bash <(curl -Ls https://aka.ms/bicep/nightly-cli.sh) --run-id 19186198907
  • Windows
    iex "& { $(irm https://aka.ms/bicep/nightly-cli.ps1) } -RunId 19186198907"

@github-actions
Copy link
Contributor

github-actions bot commented Oct 30, 2025

Dotnet Test Results

   102 files   -     42     102 suites   - 42   41m 1s ⏱️ - 35m 23s
12 554 tests +    16  12 554 ✅ +    16  0 💤 ±0  0 ❌ ±0 
28 853 runs   - 14 333  28 853 ✅  - 14 333  0 💤 ±0  0 ❌ ±0 

Results for commit f8c3d61. ± Comparison against base commit 8ea021c.

This pull request removes 1954 and adds 690 tests. Note that renamed tests count towards both.

		nestedProp1: 1
		nestedProp2: 2
		prop1: true
		prop2: false
	1
	2
	\$'")
	prop1: true
	prop2: false
…
Bicep.Cli.IntegrationTests.BuildParamsCommandTests ‑ BuildParams_ResourceInputType_ArrayOfResources_Succeeds
Bicep.Cli.IntegrationTests.BuildParamsCommandTests ‑ BuildParams_ResourceInputType_ComplexNestedObject_Succeeds
Bicep.Cli.IntegrationTests.BuildParamsCommandTests ‑ BuildParams_ResourceInputType_NestedProperty_Succeeds
Bicep.Cli.IntegrationTests.BuildParamsCommandTests ‑ BuildParams_ResourceInputType_WithValidObject_Succeeds
Bicep.Cli.UnitTests.Helpers.VersionCheckerTests ‑ CheckForNewerVersionsAsync_ShouldCheckWindowsLocations_OnWindows
Bicep.Cli.UnitTests.Helpers.VersionCheckerTests ‑ CheckForNewerVersionsAsync_ShouldHandleInvalidVersions
Bicep.Cli.UnitTests.Helpers.VersionCheckerTests ‑ CheckForNewerVersionsAsync_ShouldHandleNonExistentDirectories
Bicep.Cli.UnitTests.Helpers.VersionCheckerTests ‑ CheckForNewerVersionsAsync_ShouldNotRun_WhenShouldCheckIsFalse
Bicep.Cli.UnitTests.Helpers.VersionCheckerTests ‑ CheckForNewerVersionsAsync_ShouldNotWarn_WhenNoNewerVersionsFound
Bicep.Cli.UnitTests.Helpers.VersionCheckerTests ‑ CheckForNewerVersionsAsync_ShouldNotWarn_WhenOlderVersionFound
…

♻️ This comment has been updated with latest results.

Copy link
Member

@anthony-c-martin anthony-c-martin left a comment

Choose a reason for hiding this comment

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

This is a pretty major behavioral change, and could have serious performance implications that we need to think through, as well as product design implications. At minimum, I think we need to cover this as a topic in Bicep Discussions before continuing on with the change.

Generally, I think my feeling with doing this work is that the cons outweigh the pros. Issue #5070 was created a long time ago, and since then, I don't think it's been as much of a problem as we imagined it being.

Comment on lines 167 to 204
var startInfo = new ProcessStartInfo
{
FileName = bicepPath,
Arguments = "--version",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};

using var process = Process.Start(startInfo);
if (process == null)
{
return null;
}

process.WaitForExit(5000);

if (process.ExitCode != 0)
{
return null;
}

var output = process.StandardOutput.ReadToEnd();

// Parse version from output like "Bicep CLI version 0.30.23 (abc123)"
// Extract the version number between "version " and the next space or parenthesis
var versionMatch = System.Text.RegularExpressions.Regex.Match(
output,
@"version\s+(\d+\.\d+\.\d+)",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);

if (versionMatch.Success && Version.TryParse(versionMatch.Groups[1].Value, out var version))
{
return version;
}

return null;
Copy link
Member

Choose a reason for hiding this comment

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

This is going to add significant time to process start in the best case (becuase of .NET cold start delays), and in the worst case, you may have created an elaborate fork bomb if there are any cycles.

Copy link
Member Author

Choose a reason for hiding this comment

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

fair concerns. I've addressed your points in the updated implementation;

Zero Process Start Impact:

  • The check runs asynchronously with fire-and-forget (Task.Run without awaiting) - CLI startup continues immediately without blocking.
  • No impact on cold start time - the background task runs completely independently

No Process Spawning (No Fork Bomb Risk):

  • Uses FileVersionInfo.GetVersionInfo() instead of spawning processes
  • Directly reads the binary metadata from the file system
  • No child processes created, so no risk of fork bombs or cycles

Additional Safeguards:

  • 250ms timeout with CancellationToken - hard stop regardless of how many locations exist
  • Optimized scan - only checks specific well-known locations (no recursive directory search)
  • Deduplication - tracks checkedPaths to avoid scanning the same binary twice
  • Command filtering - only runs for "significant" commands (build, deploy, test, etc.), skips --version, --help, jsonrpc, etc.
  • Terminal detection - skips when stdout/stderr is redirected
  • Silent failure - any exceptions are caught and ignored to never disrupt normal operations

Performance characteristics:

  • User-perceived startup time: 0ms (async fire-and-forget)
  • Background scan time: ≤250ms (enforced by timeout)
  • Directories scanned: certain locations (platform-specific, non-recursive)
  • no process spawning: uses FileVersionInfo instead

The implementation prioritizes CLI responsiveness while providing helpful warnings to users who may unknowingly have multiple installations.

What you think?

…ing process

Avoid spawning a bicep process and parsing its stdout. Check file existence earlier and read FileVersionInfo.FileVersion, parsing it with Version.TryParse and returning null for missing/invalid versions.
Make VersionChecker.CheckForNewerVersions non-blocking by introducing
CheckForNewerVersionsAsync that runs the scan in a background task with a
100ms timeout and cancellation token. Add a shouldCheck parameter so
callers can skip the scan for lightweight commands or when output is
redirected. Make scanning cancellation-aware and restrict file searches
to the top directory only. Remove scanning of PATH entries from
well-known locations.

Update Program to compute whether the check should run (ShouldCheckForNewerVersions)
and invoke the new async method.
- Add VersionCheckerTests with coverage for:
  - asynchronous check behavior (shouldCheck flag)
  - no/newer/older/multiple versions detection
  - platform-specific install locations (Linux/Windows)
  - non-existent directories, invalid versions, and cancellation handling
- Add TestableVersionChecker to mock Bicep file versions and normalize paths for reliable tests
- Make FindNewerVersions and GetWellKnownInstallLocations public, and make GetBicepVersion protected virtual to allow overriding in tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Try to identify when two versions of bicep are installed on the system

4 participants