Skip to content

Fix lib/ folder DLL resolution for InvokeButton/LinkButton and merge per-user extension paths in all-users installs - #3511

Draft
jmcouffin with Copilot wants to merge 4 commits into
developfrom
copilot/pyrevit-clc-search-paths-bugfix
Draft

Fix lib/ folder DLL resolution for InvokeButton/LinkButton and merge per-user extension paths in all-users installs#3511
jmcouffin with Copilot wants to merge 4 commits into
developfrom
copilot/pyrevit-clc-search-paths-bugfix

Conversation

Copilot AI commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Two regressions in the v6 new loader affecting extensions that use custom DLLs and all-users deployments.

Issue #5lib/ folder no longer searched for extension DLLs

InvokeButton and LinkButton assembly resolution only searched bin/ subdirectories. Extensions that followed the pre-v6 convention of placing DLLs under lib/ silently failed to load.

  • CommandTypeGenerator.ResolveInvokeAssemblyPath(): adds CollectLibraryPaths() alongside the existing CollectBinaryPaths() call
  • LinkButtonBuilder.ResolveAssemblyPath(): same change for LinkButton target assembly resolution
// Before — only bin/ was searched
searchPaths.AddRange(extension.CollectBinaryPaths(component));

// After — lib/ is also searched for backward compatibility
searchPaths.AddRange(extension.CollectBinaryPaths(component));
foreach (var libPath in extension.CollectLibraryPaths(component))
    if (!searchPaths.Contains(libPath))
        searchPaths.Add(libPath);

Issue #2 — Per-user extension search paths ignored in all-users installs

When pyRevit is installed machine-wide, ExtensionParser.GetExtensionRoots() read userextensions exclusively from the ProgramData config. Paths written to the per-user AppData config (by the Settings UI or a non-admin CLI invocation) were never discovered by the C# loader.

  • ExtensionParser.GetExtensionRoots(): when IsAllUsersInstall() is true and a separate per-user config exists at %AppData%\pyRevit, its userextensions entries are merged into the roots list using a HashSet<string> for O(1) case-insensitive deduplication.

@devloai

devloai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Unable to trigger custom agent "Code Reviewer". You have run out of credits 😔
Please upgrade your plan or buy additional credits from the subscription page.

Copilot AI changed the title [WIP] Fix CLI-based search paths and DLLs issue in pyRevit 6.4.0.26100 Fix lib/ folder DLL resolution for InvokeButton/LinkButton and merge per-user extension paths in all-users installs Jul 24, 2026
Copilot AI requested a review from jmcouffin July 24, 2026 08:22
@jmcouffin
jmcouffin requested a review from romangolev July 24, 2026 08:27
@romangolev

Copy link
Copy Markdown
Member

@jmcouffin I'm getting back from vacation tomorrow and will take a look on that and others PRs this weekend

@romangolev romangolev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two configuration-scope issues need attention before this fix fully addresses all-users installations.

// For all-users installs, the active config is in ProgramData. Individual users may
// also have their own extension paths stored in their per-user config (AppData). Merge
// both lists so that paths added via the Settings UI or CLI at user scope are honoured.
if (PyRevitInstallScope.IsAllUsersInstall())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P1] Respect the read-only machine-config policy before merging user paths. GetActiveConfig() deliberately treats a ProgramData config with the ReadOnly attribute as an admin lock, but this condition checks only the installation scope. It therefore still loads extensions from a user-writable AppData config, bypassing that policy. Please skip this merge when the active config is read-only.


var activeConfigPath = GetConfig().ConfigPath ?? string.Empty;
if (File.Exists(perUserConfigPath) &&
!string.Equals(perUserConfigPath, activeConfigPath, StringComparison.OrdinalIgnoreCase))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P2] Merge machine paths when the active config has fallen back to the per-user config. In a normal non-elevated all-users session, GetActiveConfig() uses AppData whenever ProgramData is not writable. perUserConfigPath then equals activeConfigPath, so this block is skipped and machine-scope userextensions added after the initial seed are never discovered—the admin-scope case from #3312 remains. Consider merging both configs based on ActiveConfigInfo while still honoring the read-only lock.

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.

[Bug]: pyRevit_6.4.0.26100 Bugs [CLI-based Search Paths, DLLs lib vs bin folder]

3 participants