Skip to content

(#1109) Fix issue on upgrading prerelease packages#1118

Open
gep13 wants to merge 1 commit intochocolatey:developfrom
gep13:issue-1109
Open

(#1109) Fix issue on upgrading prerelease packages#1118
gep13 wants to merge 1 commit intochocolatey:developfrom
gep13:issue-1109

Conversation

@gep13
Copy link
Copy Markdown
Member

@gep13 gep13 commented Mar 19, 2026

Description Of Changes

This is a fairly significant overhaul of how the Chocolatey GUI internal
messaging is done, in order to work around an issue that was idenitifed
with the process of upgrading packages, when there is a prerelease
version of the package available from a remote source. There were a
combination of issues are play here, and I think it is safe to say that
this never worked. What follows is a summary of the changes that had
to be made in order to get things to work, along with some other minor
tweaks that had to be made in order to get things to work cleanly. The
root of the problem was essentially that the RemoteSourceViews and some
of the methods that are executed in the ChocolateyService, simply didn't
know anything about prerelease packages. As a result, the ability to
use the Upgrade Context menu action was never presented, and the
Outdated/Installed status for a package was never updated correctly.

  • In order to keep information about the remote package that was located
    for a RemoteSource, we need to keep information about it around, so
    that this information can be used at later times. To that end, the
    ChocolateySource and RemoteVersion properties were added to the
    IPackageViewModel interface.
  • On the IChocolateyService, two new methods were added that can take
    into account what package version is being acted on, and also which
    RemoteSource made the request. This latter part is important when it
    comes to updating information about packages. In addition, the
    packageName argument was removed from the GetOutdatedPackages method,
    as this is no longer required. The old methods were marked as
    Obsolete, so that they aren't used again by mistake.
  • On the IChocoalteyGuiCacheService, a new method was added for purging
    the outdated packages. This needed to be more granular, and only
    delete the file(s) associated with a particular RemoteSource.
  • The new PurgeOutdatedPackages takes into account the fact that a
    persisted file is now kept around on a per RemoteSource basis. This
    was required due to the fact that a package could be outdated from
    more than one RemoteSource, with different package versions. As such,
    we need to be able to either delete all files when purging, or only
    specific ones.
  • New Resource Strings were added to cover some new areas of
    functionality, and a single string was updated to make it clearer
    about what version is being shown.
  • Several new discrete message classes were added, and ultimately
    replaced the single PackageChangedMessage. This was the biggest part
    of the refactoring here, since it proved to be too complicated trying
    to update things based on a single message, with different types. By
    having a message for each change that can happen to a packge, it
    became much easier to update the necessary portions of the UI when
    things happened.
  • The RemoteSourceView was updated to include the information about
    whether a package was outdated or not. This had always been shown when
    using the Grid View layout, but it was never included in the list view
    for the remote source. On top of this, the binding for the list of
    Packages was switched to use an ICollectionView, similar to what is
    done on the LocalSourceView, there was no good reason for having a
    different approach here.
  • On the PackageView, an additional TextBlock was added to show the
    Available Version, in addition to essentially the installed version of
    a package, when the package is marked as IsOutdated.
  • The RemoteSourceViewModel was updated to record the Source that is
    used when querying for packages, as well as the RemoteVersion that was
    located. This information is very important when processing the update
    messages that are being sent out. It was also found that the
    information about whether a locally installed package was IsPinned or
    not wasn't being added to the PackageViewModel, so this was corrected
    as part of the overall work here.
  • In the LocalSrouceViewModel, the handle method for the
    PackageChangedMessage was completely removed, and replaced with
    smaller message handlers, to only deal with the install and uninstall
    methods, which are responsible for adding/removing a package from the
    overall package list. Important change here is to mark the
    ChocolateySource as null for this ViewModel, so it is clear that these
    are packages on the local computer.
  • Several changes in the PackageViewModel... It was found that some
    methods, Uninstall and Reinstall were available when a package was
    pinned, which isn't allowed. The Can methods were updated to include
    information about the package IsPinned property. All the
    _eventAggregator.PublishOnUIThreadAsync methods were updated to use
    the smaller messages, rather than the larger PackageChangedMessage
    message, which has beenn deleted. New handle methods were added for
    each of the new small messages, to correctly update the package
    inforamtion when an action was taken. Care was taken to only do this
    when it is known to be required, for example, uninstalling when on the
    LocalSource is not required. The biggest check here is about only
    marking a package as outdated when it is known to be being shown on
    the RemoteSource associated with where the outdated information is
    coming from.
  • In the ChocolateyService, a race condition was discovered, where the
    ChocolateyConfiguration was being overwritten before the intended
    execution could be completed. To guard against this,
    Lock.WriteLockAsync was added before each invocation. This approach
    was already being used in some areas, but not all of them, so the
    decision was taken to added it to all methods. When storing the
    information about what packages are outdated, a file is now created
    per RemoteSource, and also whether it includes prerelease packages or
    not. The actual call for outdated packages was updated to use the same
    approach that is used in Chocolatey Agent, rather than calling
    UpgradeDryRun. These are essentially the same thing, but it was
    felt that it is better to keep things consistent across products. The
    UpdatePackage method was updated to include the version number, which
    now allows the installation of prerelease package versions.

Motivation and Context

A bug was raised showing how it wasn't possible to upgrade to a prerelease package.

Testing

  1. Check out this PR branch
  2. Open the solution in Visual Studio as an administrator
  3. Ensure you have the following sources configured, CCR, ops-choco-packages, choco-internal-testing
  4. Enable the feature to show the aggregated source
  5. Ensure that you don't have the chocolatey-au package already installed
  6. Switch to the chocolatey source, and search for the chocolatey-au package (best done using the Match Word Exactly checkbox)
  7. Switch to the ops-choco-packages source, and search for the chocolatey-au package, including the Include Prerelease checkbox
  8. Uncheck the Include Prerelease checkbox
  9. Switch to the choco-internal-testing source, and search for the chocolatey-au package, including the Include Prerelease checkbox
  10. Uncheck the Include Prerelease checkbox
  11. Switch to the All Sources source, and search for the chocolatey-au package (best done using the Match Word Exactly checkbox), including the Include Prerelease checkbox
  12. Uncheck the Include Prerelease checkbox
  13. Check the C:\Users\<user>\AppData\Local\Chocolatey GUI folder, you should see xml files for all the sources, including -re.xml. You won't see outdatedPackages-pre.xml since you can't search for prerelease on the This PC source
  14. Click through all the Sources, clicking the Include Prerelease checkbox each time, and you should see the following for the chocolatey-au package:
  • All Sources - 1.0.1 and 1.1.0-alphapullrequest0088 when Include Prerelease checked
  • chocolatey - 1.0.0 in both cases
  • ops-choco-packages - 1.0.0 and 1.1.0-alphapullrequest0088 when Include Prerelease checked
  • choco-internal-testing - 1.0.1 and 1.1.0-alphapullrequest0048 when Include Prerelease checked
  1. Ensure to leave the Include prerelease checkbox checked as you leave each source
  2. Click on the chocolatey source and install the chocolatey-au package
  3. Click through all the sources and verify that the chocolatey-au package is marked as installed, and also as a tile in the This PC source. In addition, it should be marked as:
  • This PC - 1.0.1 and 1.0.0 - Out of Date
  • All Sources - 1.1.0-alphapullrequest0088 and 1.0.0 - Out of Date
  • chocolatey - 1.0.0 - Installed
  • ops-choco-packages - 1.1.0-alphapullrequest0088 and 1.0.0 - Out of Date
  • choco-internal-testing - 1.1.0-alphapullrequest0048 and 1.0.0 - Out of Date
  1. Switch to the choco-internal-testing source, and choose to upgrade the package
  2. Click through all the sources and verify that the chocolatey-au package is marked as:
  • This PC - 1.1.0-alphapullrequest0048 - Installed
  • All Sources - 1.1.0-alphapullrequest0088 and 1.1.0-alphapullrequest0048 - Out of Date
  • chocolatey - 1.1.0-alphapullrequest0048 - Installed
  • ops-choco-packages - 1.1.0-alphapullrequest0088 and 1.1.0-alphapullrequest0048 - Out of Date
  • choco-internal-testing - 1.1.0-alphapullrequest0048 - Installed
  1. On the This PC source, right click on the chocolatey-au package and choose to pin the package
  2. Loop through all the sources and make source that each one is marked as Pinned
  3. Check the context menu and ensure that you can't uninstall or re-install the package, since it is pinned
  4. On any source, right click on the chocolatey-au package and choose to unpin the package
  5. Loop through all the sources and make source that each one is not marked as Pinned

That is it for testing, but feel free to play around with some additional tests as you see fit.

Operating Systems Testing

Dev VM 4

Change Types Made

  • Bug fix (non-breaking change).
  • Feature / Enhancement (non-breaking change).
  • Breaking change (fix or feature that could cause existing functionality to change).
  • Documentation changes.
  • PowerShell code changes.

Change Checklist

  • Requires a change to the documentation.
  • Documentation has been updated.
  • Tests to cover my changes, have been added.
  • All new and existing tests passed?
  • PowerShell code changes: PowerShell v3 compatibility checked?
  • All items are complete on the Definition of Done.

Related Issue

Fixes #1109

Copy link
Copy Markdown
Member

@AdmiringWorm AdmiringWorm left a comment

Choose a reason for hiding this comment

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

Not had a chance to take this for a spin yet, but figured I'd leave a few comments first.

private string _localAppDataPath = string.Empty;
private const string ErrorRegex = "^\\s*(ERROR|FATAL|WARN)";

private static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
Copy link
Copy Markdown
Member

@AdmiringWorm AdmiringWorm Mar 19, 2026

Choose a reason for hiding this comment

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

Weren't it decided to not use this Lock, and instead use the async lock instead?

I also couldn't see this lock being used anywhere in the file.

config.RegularOutput = false;
config.QuietOutput = true;
config.Prerelease = false;
var configuration = choco.GetConfiguration();
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.

Doesn't this have the same problem as other places (like list/search), since it isn't wrapped inside of an async lock, and since the Configuration object is shared, wouldn't it change the configuration object in other locations?

Comment on lines +495 to +496
_chocolateyGuiCacheService.PurgeOutdatedPackages(source: null, includePrerelease: false);
await _eventAggregator.PublishOnUIThreadAsync(new PackageInstalledMessage(Id, Version, ChocolateySource));
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.

Not saying to change anything right now, but we may consider an optimization in the future to verify that the cache file actually includes the package id that we reinstalled/installed/upgraded, instead of blindly removing it.

Comment on lines +808 to 826
public void Handle(PackagePinnedMessage message)
{
if (!string.Equals(message.Id, Id, StringComparison.OrdinalIgnoreCase))
{
return;
}

IsPinned = true;
}

public void Handle(PackageUnpinnedMessage message)
{
if (!string.Equals(message.Id, Id, StringComparison.OrdinalIgnoreCase))
{
return;
}

LatestVersion = message.Version;
IsOutdated = true;
IsPinned = false;
}
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.

Instead of having two seperate handlers on this, it would maybe be a bit better to just have a single message, which have a property that say explicitly whether it should be pinned or not?

@gep13 gep13 force-pushed the issue-1109 branch 2 times, most recently from 7677a94 to 7be1b96 Compare March 24, 2026 16:24
@gep13 gep13 requested a review from a team as a code owner March 24, 2026 16:24
@gep13 gep13 requested a review from pauby March 24, 2026 16:24
@gep13 gep13 changed the base branch from develop to hotfix/3.0.1 March 24, 2026 16:24
@gep13 gep13 requested review from st3phhays and removed request for pauby March 24, 2026 16:47
@gep13
Copy link
Copy Markdown
Member Author

gep13 commented Mar 24, 2026

@st3phhays I have some things to respond to @AdmiringWorm about on this one, but can I get your eyes on this from a Code Owners perspective? Thanks!

st3phhays
st3phhays previously approved these changes Mar 24, 2026
@gep13 gep13 force-pushed the issue-1109 branch 2 times, most recently from 4b1c9d3 to 5c25793 Compare March 25, 2026 06:40
This is a fairly significant overhaul of how the Chocolatey GUI internal
messaging is done, in order to work around an issue that was idenitifed
with the process of upgrading packages, when there is a prerelease
version of the package available from a remote source.  There were a
combination of issues are play here, and I think it is safe to say that
this _never_ worked.  What follows is a summary of the changes that had
to be made in order to get things to work, along with some other minor
tweaks that had to be made in order to get things to work cleanly. The
root of the problem was essentially that the RemoteSourceViews and some
of the methods that are executed in the ChocolateyService, simply didn't
know anything about prerelease packages.  As a result, the ability to
use the Upgrade Context menu action was never presented, and the
Outdated/Installed status for a package was never updated correctly.

- In order to keep information about the remote package that was located
  for a RemoteSource, we need to keep information about it around, so
  that this information can be used at later times.  To that end, the
  ChocolateySource and RemoteVersion properties were added to the
  IPackageViewModel interface.
- On the IChocolateyService, two new methods were added that can take
  into account what package version is being acted on, and also which
  RemoteSource made the request.  This latter part is important when it
  comes to updating information about packages.  In addition, the
  packageName argument was removed from the GetOutdatedPackages method,
  as this is no longer required. The old methods were marked as
  Obsolete, so that they aren't used again by mistake.
- On the IChocoalteyGuiCacheService, a new method was added for purging
  the outdated packages.  This needed to be more granular, and only
  delete the file(s) associated with a particular RemoteSource.
- The new PurgeOutdatedPackages takes into account the fact that a
  persisted file is now kept around on a per RemoteSource basis.  This
  was required due to the fact that a package could be outdated from
  more than one RemoteSource, with different package versions.  As such,
  we need to be able to either delete _all_ files when purging, or only
  specific ones.
- New Resource Strings were added to cover some new areas of
  functionality, and a single string was updated to make it clearer
  about what version is being shown.
- Several new discrete message classes were added, and ultimately
  replaced the single PackageChangedMessage.  This was the biggest part
  of the refactoring here, since it proved to be too complicated trying
  to update things based on a single message, with different types.  By
  having a message for each change that can happen to a packge, it
  became much easier to update the necessary portions of the UI when
  things happened.
- The RemoteSourceView was updated to include the information about
  whether a package was outdated or not. This had always been shown when
  using the Grid View layout, but it was never included in the list view
  for the remote source. On top of this, the binding for the list of
  Packages was switched to use an ICollectionView, similar to what is
  done on the LocalSourceView, there was no good reason for having a
  different approach here.
- On the PackageView, an additional TextBlock was added to show the
  Available Version, in addition to essentially the installed version of
  a package, when the package is marked as IsOutdated.
- The RemoteSourceViewModel was updated to record the Source that is
  used when querying for packages, as well as the RemoteVersion that was
  located. This information is very important when processing the update
  messages that are being sent out. It was also found that the
  information about whether a locally installed package was IsPinned or
  not wasn't being added to the PackageViewModel, so this was corrected
  as part of the overall work here.
- In the LocalSrouceViewModel, the handle method for the
  PackageChangedMessage was completely removed, and replaced with
  smaller message handlers, to only deal with the install and uninstall
  methods, which are responsible for adding/removing a package from the
  overall package list.  Important change here is to mark the
  ChocolateySource as null for this ViewModel, so it is clear that these
  are packages on the local computer.
- Several changes in the PackageViewModel...  It was found that some
  methods, Uninstall and Reinstall were available when a package was
  pinned, which isn't allowed.  The Can methods were updated to include
  information about the package IsPinned property. All the
  _eventAggregator.PublishOnUIThreadAsync methods were updated to use
  the smaller messages, rather than the larger PackageChangedMessage
  message, which has beenn deleted. New handle methods were added for
  each of the new small messages, to correctly update the package
  inforamtion when an action was taken. Care was taken to only do this
  when it is known to be required, for example, uninstalling when on the
  LocalSource is not required.  The biggest check here is about only
  marking a package as outdated when it is known to be being shown on
  the RemoteSource associated with where the outdated information is
  coming from.
- In the ChocolateyService, a race condition was discovered, where the
  ChocolateyConfiguration was being overwritten before the intended
  execution could be completed. To guard against this,
  Lock.WriteLockAsync was added before each invocation. This approach
  was already being used in some areas, but not all of them, so the
  decision was taken to added it to all methods.  When storing the
  information about what packages are outdated, a file is now created
  per RemoteSource, and also whether it includes prerelease packages or
  not. The actual call for outdated packages was updated to use the same
  approach that is used in Chocolatey Agent, rather than calling
  UpgradeDryRun.  These are _essentially_ the same thing, but it was
  felt that it is better to keep things consistent across products.  The
  UpdatePackage method was updated to include the version number, which
  now allows the installation of prerelease package versions.
@gep13 gep13 changed the base branch from hotfix/3.0.1 to develop March 25, 2026 06:50
@gep13 gep13 dismissed st3phhays’s stale review March 25, 2026 06:50

The base branch was changed.

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.

Unable to upgrade beta packages through Chocolatey GUI

3 participants