Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
32b17d4
Add note, date created, and show subcommand
Trenly Apr 29, 2026
697ca1d
Add tests
Trenly Apr 29, 2026
b6502ef
Add pinning to COM APIs
Trenly Apr 29, 2026
ae483b0
Add IsPinned propery for Get-WingetPackage
Trenly Apr 29, 2026
fac4ec4
Add pinning to PowerShell Cmdlets
Trenly Apr 29, 2026
7ce3b27
Spelling
Trenly Apr 29, 2026
35322e7
Inherit from 1.0 Interface
Trenly Apr 29, 2026
236e667
Contract 29 is not released yet
Trenly Apr 29, 2026
1c89957
Adddress comments about resource strings
Trenly May 1, 2026
fa617a6
Update comment to indicate the appropriate contract version
Trenly May 1, 2026
7f33f56
Make methods public
Trenly May 1, 2026
3c7d4a6
Adjust how new schema is created
Trenly May 1, 2026
99a8760
Reduce duplicated code through inheritance
Trenly May 1, 2026
e336194
Use statement builder, savepoints, and tuples for ownership
Trenly May 1, 2026
0cb653b
Use a time point instead of strings
Trenly May 1, 2026
29d6203
Guard against non-existent source
Trenly May 1, 2026
184cc7a
Cache IsPinned
Trenly May 1, 2026
952e56e
Merge default pin add behavior into single test
Trenly May 1, 2026
e823b75
Ensure table is not created in a partial state
Trenly May 1, 2026
195846f
Add an alternate function for optional parms
Trenly May 1, 2026
0468b34
Move to PackageCatalogReference
Trenly May 1, 2026
4bfde99
Move functionality to converters
Trenly May 1, 2026
243798b
Remove version comment
Trenly May 1, 2026
4b7b8ef
Update filters so 1.1 schema is visible
Trenly May 1, 2026
e93e3d1
Create the correct interface and migrate only if newer
Trenly May 1, 2026
ae87ff1
Use correct version info
Trenly May 1, 2026
917db77
Move invariants outside try catch
Trenly May 1, 2026
34a727c
Add TryRemovePin for simplicity
Trenly May 1, 2026
01aad8e
Refactor to use -Blocking and -GatedVersion
Trenly May 1, 2026
ad9932f
Spelling
Trenly May 1, 2026
a96e43e
Add Pester Tests
Trenly May 1, 2026
fde7382
Tests should now be part of default creation test
Trenly May 1, 2026
d5f682c
Further abstract the interface
Trenly May 1, 2026
8f819c1
Update test name
Trenly May 1, 2026
91f532a
Add comment
Trenly May 1, 2026
595abca
Appease our AI overlords with an update to their tomes
Trenly May 1, 2026
7e83a83
Unify on AssignValue to avoid future confusion
Trenly May 1, 2026
615ddca
Ensure pin writes always record the last updated time
Trenly May 1, 2026
1f8932d
Use original symbol for creation
Trenly May 1, 2026
df55096
Add missing System namepace
Trenly May 1, 2026
6d085dd
Restore conditional setting of date
Trenly May 1, 2026
ea9f0d8
Ensure ShouldProcess is always respected
Trenly May 1, 2026
d207c77
Fix minor issue in copilot instructions
Trenly May 1, 2026
349a0a1
Validate provided pin types
Trenly May 1, 2026
840e5ae
Cache all pins and only fall back to get if set is empty
Trenly May 1, 2026
65f8373
Change file name to remove override
Trenly May 1, 2026
a53f6c7
Merge remote-tracking branch 'upstream/master' into PinPlus
Trenly May 29, 2026
e2315ad
Merge remote tracking branch master from microsoft/winget-cli
Trenly Jun 15, 2026
2792834
Rename back to PinTable.h
Trenly Jun 15, 2026
b350a91
Update filters for file name
Trenly Jun 15, 2026
04b5210
Move SQLite builder instructions to separate file
Trenly Jun 15, 2026
e350988
Make adds flow through 1.0 into 1.1
Trenly Jun 15, 2026
3d92b41
Use shared column names
Trenly Jun 15, 2026
37344ea
Add SQLIte Nullopt test
Trenly Jun 15, 2026
d061054
Remove protected
Trenly Jun 15, 2026
b939b8e
Lazy load all pins for catalog packages
Trenly Jun 15, 2026
ff12245
Contract version 30
Trenly Jun 15, 2026
c920cdd
Remove pin show - move into pin list with details
Trenly Jun 15, 2026
41e10af
Update release notes
Trenly Jun 15, 2026
f05bcc8
Revert instructions edit
Trenly Jun 15, 2026
fdcd860
Run renormalize
Trenly Jun 15, 2026
19406e9
Fix release notes
Trenly Jun 15, 2026
987bae2
Merge remote-tracking branch 'upstream/master' into PinPlus
Trenly Jun 23, 2026
aeb8fae
Update comments to clearly indicate relation to previous log messages
Trenly Jun 23, 2026
80cf29a
Update test to ensure empty notes column is hidden
Trenly Jun 23, 2026
fcaeace
Make public
Trenly Jun 23, 2026
a923669
Filter the list that creates row data
Trenly Jun 23, 2026
77b4b7d
Show all columns and remove --details
Trenly Jun 23, 2026
59af822
Ensure atomicity of get/remove operation in Pin Database
Trenly Jun 23, 2026
2173a38
Make migration faliures non-fatal
Trenly Jun 23, 2026
5c43088
Update table implementation
Trenly Jun 23, 2026
0c80e10
Merge remote tracking branch upstream/master
Trenly Jul 28, 2026
fa8121b
Merge branch 'master' into PinPlus
Trenly Aug 5, 2026
2a51bb3
Resolve stylecop issue and add xmldoc comment for function
Trenly Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ appdata
appinstallertest
applic
appname
appone
appshutdown
APPTERMINATION
archs
Expand Down Expand Up @@ -339,6 +340,7 @@ megamorf
microsoftentraid
microsoftentraidforazureblobstorage
midl
migratepintable
minidump
MINORVERSION
missingdependency
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/Argument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ namespace AppInstaller::CLI
return { type, "blocking"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PinType };
case Execution::Args::Type::PinInstalled:
return { type, "installed"_liv, ArgTypeCategory::None };
case Execution::Args::Type::PinNote:
return { type, "note"_liv, ArgTypeCategory::None };

// Error command
case Execution::Args::Type::ErrorInput:
Expand Down
34 changes: 34 additions & 0 deletions src/AppInstallerCLICore/Commands/PinCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace AppInstaller::CLI
std::make_unique<PinRemoveCommand>(FullName()),
std::make_unique<PinListCommand>(FullName()),
std::make_unique<PinResetCommand>(FullName()),
std::make_unique<PinShowCommand>(FullName()),
Comment thread
Trenly marked this conversation as resolved.
Outdated
});
}

Expand Down Expand Up @@ -65,6 +66,7 @@ namespace AppInstaller::CLI
Argument::ForType(Args::Type::Force),
Argument{ Args::Type::BlockingPin, Resource::String::PinAddBlockingArgumentDescription, ArgumentType::Flag },
Argument{ Args::Type::PinInstalled, Resource::String::PinInstalledArgumentDescription, ArgumentType::Flag },
Argument{ Args::Type::PinNote, Resource::String::PinNoteArgumentDescription, ArgumentType::Standard },
};
}

Expand Down Expand Up @@ -343,4 +345,36 @@ namespace AppInstaller::CLI
Workflow::ReportPins;
}
}

std::vector<Argument> PinShowCommand::GetArguments() const
{
return {
Argument::ForType(Args::Type::Query),
Argument::ForType(Args::Type::Id),
Argument::ForType(Args::Type::Name),
Argument::ForType(Args::Type::Exact),
};
}

Resource::LocString PinShowCommand::ShortDescription() const
{
return { Resource::String::PinShowCommandShortDescription };
}

Resource::LocString PinShowCommand::LongDescription() const
{
return { Resource::String::PinShowCommandLongDescription };
}

Utility::LocIndView PinShowCommand::HelpLink() const
{
return s_PinCommand_HelpLink;
}

void PinShowCommand::ExecuteInternal(Execution::Context& context) const
{
context <<
Workflow::OpenPinningIndex(/* readOnly */ true) <<
Workflow::ShowPinDetails;
}
}
15 changes: 15 additions & 0 deletions src/AppInstallerCLICore/Commands/PinCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,19 @@ namespace AppInstaller::CLI
protected:
void ExecuteInternal(Execution::Context& context) const override;
};

struct PinShowCommand final : public Command
{
PinShowCommand(std::string_view parent) : Command("show", parent) {}

std::vector<Argument> GetArguments() const override;

Resource::LocString ShortDescription() const override;
Resource::LocString LongDescription() const override;

Utility::LocIndView HelpLink() const override;

protected:
void ExecuteInternal(Execution::Context& context) const override;
};
}
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/ExecutionArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ namespace AppInstaller::CLI::Execution
GatedVersion, // Differs from Version in that this supports wildcards
BlockingPin,
PinInstalled,
PinNote, // User-provided note to attach to a pin

// Error command
ErrorInput,
Expand Down
12 changes: 12 additions & 0 deletions src/AppInstallerCLICore/Resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -530,13 +530,16 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(PinCannotOpenIndex);
WINGET_DEFINE_RESOURCE_STRINGID(PinCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinCommandShortDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinDateAdded);
WINGET_DEFINE_RESOURCE_STRINGID(PinDoesNotExist);
WINGET_DEFINE_RESOURCE_STRINGID(PinExistsOverwriting);
WINGET_DEFINE_RESOURCE_STRINGID(PinExistsUseForceArg);
WINGET_DEFINE_RESOURCE_STRINGID(PinInstalledArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinInstalledSource);
WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandShortDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinNote);
WINGET_DEFINE_RESOURCE_STRINGID(PinNoteArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinNoPinsExist);
WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription);
Expand All @@ -546,6 +549,15 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(PinResetSuccessful);
WINGET_DEFINE_RESOURCE_STRINGID(PinResettingAll);
WINGET_DEFINE_RESOURCE_STRINGID(PinResetUseForceArg);
WINGET_DEFINE_RESOURCE_STRINGID(PinShowCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinShowCommandShortDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelDateAdded);
WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelId);
WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelNote);
WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelSource);
WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelType);
WINGET_DEFINE_RESOURCE_STRINGID(PinShowLabelVersion);
WINGET_DEFINE_RESOURCE_STRINGID(PinShowNoMatchFound);
WINGET_DEFINE_RESOURCE_STRINGID(PinType);
WINGET_DEFINE_RESOURCE_STRINGID(PinVersion);
WINGET_DEFINE_RESOURCE_STRINGID(PlatformArgumentDescription);
Expand Down
115 changes: 114 additions & 1 deletion src/AppInstallerCLICore/Workflows/PinFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
#include "pch.h"
#include "Resources.h"
#include "PinFlow.h"
#include "ShowFlow.h"
#include "TableOutput.h"
#include <AppInstallerDateTime.h>
#include <winget/PinningData.h>
#include <winget/RepositorySearch.h>
#include <winget/PackageVersionSelection.h>
Expand Down Expand Up @@ -197,9 +199,21 @@ namespace AppInstaller::CLI::Workflow

if (!pinsToAddOrUpdate.empty())
{
for (const auto& pin : pinsToAddOrUpdate)
std::string dateAdded = Utility::TimePointToString(
Comment thread
Trenly marked this conversation as resolved.
Outdated
std::chrono::system_clock::now(),
Utility::TimeFacet::Year | Utility::TimeFacet::Month | Utility::TimeFacet::Day |
Utility::TimeFacet::Hour | Utility::TimeFacet::Minute | Utility::TimeFacet::Second);

std::optional<std::string> note;
if (context.Args.Contains(Execution::Args::Type::PinNote))
{
note = std::string{ context.Args.GetArg(Execution::Args::Type::PinNote) };
}

for (auto& pin : pinsToAddOrUpdate)
{
pin.SetDateAdded(dateAdded);
Comment thread
Trenly marked this conversation as resolved.
Outdated
pin.SetNote(note);
pinningData.AddOrUpdatePin(pin);
}

Expand Down Expand Up @@ -335,4 +349,103 @@ namespace AppInstaller::CLI::Workflow
context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl;
}
}

void ShowPinDetails(Execution::Context& context)
{
auto& pinningData = context.Get<Execution::Data::PinningData>();
auto allPins = pinningData.GetAllPins();

// Apply filtering based on provided arguments
bool hasId = context.Args.Contains(Execution::Args::Type::Id);
bool hasName = context.Args.Contains(Execution::Args::Type::Name);
bool hasQuery = context.Args.Contains(Execution::Args::Type::Query);
bool exactMatch = context.Args.Contains(Execution::Args::Type::Exact);

Comment thread
Trenly marked this conversation as resolved.
Outdated
std::vector<Pinning::Pin> matchingPins;
for (const auto& pin : allPins)
{
const auto& packageId = pin.GetKey().PackageId;

if (hasId)
{
std::string_view idArg = context.Args.GetArg(Execution::Args::Type::Id);
bool match = exactMatch
? Utility::CaseInsensitiveEquals(packageId, idArg)
: Utility::CaseInsensitiveContainsSubstring(packageId, idArg);
if (!match)
{
continue;
}
}
else if (hasName || hasQuery)
{
// Without an open source, we can only match against PackageId
std::string_view queryArg = hasName
? context.Args.GetArg(Execution::Args::Type::Name)
: context.Args.GetArg(Execution::Args::Type::Query);
bool match = exactMatch
? Utility::CaseInsensitiveEquals(packageId, queryArg)
: Utility::CaseInsensitiveContainsSubstring(packageId, queryArg);
if (!match)
{
continue;
}
}

matchingPins.push_back(pin);
}

if (matchingPins.empty())
{
context.Reporter.Info() << Resource::String::PinShowNoMatchFound << std::endl;
AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST);
}

auto info = context.Reporter.Info();
bool firstPin = true;
for (const auto& pin : matchingPins)
{
if (!firstPin)
{
info << std::endl;
}
firstPin = false;

const auto& pinKey = pin.GetKey();

// ID
ShowSingleLineField(info, Resource::String::PinShowLabelId, Utility::LocIndView{ pinKey.PackageId });

// Source
if (!pinKey.SourceId.empty() && !pinKey.IsForInstalled())
{
ShowSingleLineField(info, Resource::String::PinShowLabelSource, Utility::LocIndView{ pinKey.SourceId });
}

// Type
std::string pinTypeStr{ ToString(pin.GetType()) };
ShowSingleLineField(info, Resource::String::PinShowLabelType, Utility::LocIndView{ pinTypeStr });

// Version (gated version string; empty for pinning/blocking pins)
std::string gatedVersionStr = pin.GetGatedVersion().ToString();
if (!gatedVersionStr.empty())
{
ShowSingleLineField(info, Resource::String::PinShowLabelVersion, Utility::LocIndView{ gatedVersionStr });
}

// Date Added
const auto& dateAdded = pin.GetDateAdded();
if (!dateAdded.empty())
{
ShowSingleLineField(info, Resource::String::PinShowLabelDateAdded, Utility::LocIndView{ dateAdded });
}

// Note (only shown if present)
const auto& note = pin.GetNote();
if (note.has_value() && !note->empty())
{
ShowSingleLineField(info, Resource::String::PinShowLabelNote, Utility::LocIndView{ *note });
}
}
}
}
8 changes: 8 additions & 0 deletions src/AppInstallerCLICore/Workflows/PinFlow.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ namespace AppInstaller::CLI::Workflow
// Outputs: None
void ReportPins(Execution::Context& context);

// Shows details for a single matching pin (for `winget pin show`).
// Filters the pinning index by query/name/id/exact args and outputs
// detailed field-by-field info (Name, ID, Version, Source, Type, Date Added, Note).
// Required Args: None (at least one of --query/--name/--id expected)
// Inputs: PinningIndex
// Outputs: None
void ShowPinDetails(Execution::Context& context);

// Resets all the existing pins.
// Required Args: None
// Inputs: None
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLIPackage/Package.appxmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@
</com:Class>
<com:Class Id="29B19238-81AD-4A8E-A2FC-ADF17C38CAEB" DisplayName="EditPackageCatalogOptions Server">
</com:Class>
<com:Class Id="B3A61CCB-A3D0-497D-B300-A904904EEA56" DisplayName="PinPackageOptions Server">
</com:Class>
</com:ExeServer>
</com:ComServer>
</com:Extension>
Expand Down
46 changes: 46 additions & 0 deletions src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
Original file line number Diff line number Diff line change
Expand Up @@ -1648,6 +1648,10 @@ Please specify one of them using the --source option to proceed.</value>
<data name="PinInstalledArgumentDescription" xml:space="preserve">
<value>Pin a specific installed version</value>
</data>
<data name="PinNoteArgumentDescription" xml:space="preserve">
<value>Optional note to store with the pin</value>
<comment>Description for the --note argument used with `winget pin add`</comment>
</data>
<data name="PinInstalledSource" xml:space="preserve">
<value>Installed</value>
<comment>Value used in a table to indicate that a package comes from the list of packages installed in the machine</comment>
Expand Down Expand Up @@ -1913,6 +1917,48 @@ Please specify one of them using the --source option to proceed.</value>
<value>Pinned version</value>
<comment>Table header for the version to which a package is pinned; meaning it should not update from that version.</comment>
</data>
<data name="PinDateAdded" xml:space="preserve">
<value>Date added</value>
<comment>Label shown in the pin show output for when the pin was added or last updated.</comment>
Comment thread
Trenly marked this conversation as resolved.
Outdated
</data>
<data name="PinNote" xml:space="preserve">
<value>Note</value>
<comment>Label shown in the pin show output for the user-provided note stored with the pin.</comment>
</data>
<data name="PinShowCommandShortDescription" xml:space="preserve">
<value>Show details about a pin</value>
Comment thread
Trenly marked this conversation as resolved.
Outdated
</data>
<data name="PinShowCommandLongDescription" xml:space="preserve">
<value>Show detailed information about a specific pin, including the package ID, version, type, date added, and any note stored with the pin.</value>
Comment thread
Trenly marked this conversation as resolved.
Outdated
</data>
<data name="PinShowLabelDateAdded" xml:space="preserve">
<value>Date added:</value>
Comment thread
Trenly marked this conversation as resolved.
Outdated
<comment>Label shown in the `winget pin show` output for when the pin was added or last updated.</comment>
</data>
<data name="PinShowLabelId" xml:space="preserve">
<value>Id:</value>
<comment>Label shown in the `winget pin show` output for the package identifier.</comment>
</data>
<data name="PinShowLabelNote" xml:space="preserve">
<value>Note:</value>
<comment>Label shown in the `winget pin show` output for the user-provided note stored with the pin.</comment>
</data>
<data name="PinShowLabelSource" xml:space="preserve">
<value>Source:</value>
<comment>Label shown in the `winget pin show` output for the package source.</comment>
</data>
<data name="PinShowLabelType" xml:space="preserve">
<value>Pin type:</value>
<comment>Label shown in the `winget pin show` output for the type of pin (e.g., Pinning, Gating, Blocking).</comment>
</data>
<data name="PinShowLabelVersion" xml:space="preserve">
<value>Pinned version:</value>
<comment>Label shown in the `winget pin show` output for the version string of a gating pin.</comment>
</data>
<data name="PinShowNoMatchFound" xml:space="preserve">
<value>No pin found matching the specified criteria.</value>
<comment>Shown when `winget pin show` finds no matching pin.</comment>
</data>
<data name="ConfigurationDescriptionWasTruncated" xml:space="preserve">
<value>&lt;See the log file for additional details&gt;</value>
<comment>The brackets are intended to make the value stand out from other text which it will follow. Any locale appropriate mechanism that achieves this is acceptable.</comment>
Expand Down
Loading