Add Utils-project for cleaning up stale credentials

This commit is contained in:
AliveDevil
2026-02-13 18:55:05 +01:00
parent 2032327e98
commit 7c124ec65c
6 changed files with 117 additions and 1 deletions
+8 -1
View File
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18 # Visual Studio Version 18
VisualStudioVersion = 18.2.11415.280 d18.0 VisualStudioVersion = 18.2.11415.280
MinimumVisualStudioVersion = 15.0.26124.0 MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{8B08EF96-10D6-4F35-94C1-986F9F0F1506}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{8B08EF96-10D6-4F35-94C1-986F9F0F1506}"
EndProject EndProject
@@ -61,6 +61,8 @@ Project("{DAEA77DE-8320-43BA-BA7C-EF5C12478AB5}") = "Cyberduck.Cryptomator", "cr
EndProject EndProject
Project("{DAEA77DE-8320-43BA-BA7C-EF5C12478AB5}") = "Cyberduck.Cli", "cli\dll\Cyberduck.Cli.ikvmproj", "{2D33598A-21A1-4117-82DC-250F4CE8D5E5}" Project("{DAEA77DE-8320-43BA-BA7C-EF5C12478AB5}") = "Cyberduck.Cli", "cli\dll\Cyberduck.Cli.ikvmproj", "{2D33598A-21A1-4117-82DC-250F4CE8D5E5}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test-utils", "windows\src\test\utils\test-utils.csproj", "{BBCC0F0C-0135-AAAB-DB32-CC4E28BC88E0}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64 Debug|x64 = Debug|x64
@@ -141,6 +143,10 @@ Global
{2D33598A-21A1-4117-82DC-250F4CE8D5E5}.Debug|x64.Build.0 = Debug|Any CPU {2D33598A-21A1-4117-82DC-250F4CE8D5E5}.Debug|x64.Build.0 = Debug|Any CPU
{2D33598A-21A1-4117-82DC-250F4CE8D5E5}.Release|x64.ActiveCfg = Release|Any CPU {2D33598A-21A1-4117-82DC-250F4CE8D5E5}.Release|x64.ActiveCfg = Release|Any CPU
{2D33598A-21A1-4117-82DC-250F4CE8D5E5}.Release|x64.Build.0 = Release|Any CPU {2D33598A-21A1-4117-82DC-250F4CE8D5E5}.Release|x64.Build.0 = Release|Any CPU
{BBCC0F0C-0135-AAAB-DB32-CC4E28BC88E0}.Debug|x64.ActiveCfg = Debug|Any CPU
{BBCC0F0C-0135-AAAB-DB32-CC4E28BC88E0}.Debug|x64.Build.0 = Debug|Any CPU
{BBCC0F0C-0135-AAAB-DB32-CC4E28BC88E0}.Release|x64.ActiveCfg = Release|Any CPU
{BBCC0F0C-0135-AAAB-DB32-CC4E28BC88E0}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -168,6 +174,7 @@ Global
{9C7B827F-AE30-44C4-A210-E49DF883C720} = {72B4BA09-65D8-4C49-930E-B14104B2AB1B} {9C7B827F-AE30-44C4-A210-E49DF883C720} = {72B4BA09-65D8-4C49-930E-B14104B2AB1B}
{7EFC0398-8F4D-4850-BBE3-A0CC85410559} = {72B4BA09-65D8-4C49-930E-B14104B2AB1B} {7EFC0398-8F4D-4850-BBE3-A0CC85410559} = {72B4BA09-65D8-4C49-930E-B14104B2AB1B}
{2D33598A-21A1-4117-82DC-250F4CE8D5E5} = {72B4BA09-65D8-4C49-930E-B14104B2AB1B} {2D33598A-21A1-4117-82DC-250F4CE8D5E5} = {72B4BA09-65D8-4C49-930E-B14104B2AB1B}
{BBCC0F0C-0135-AAAB-DB32-CC4E28BC88E0} = {8B08EF96-10D6-4F35-94C1-986F9F0F1506}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {289E6003-15D5-4377-ADA6-2E7093785BCD} SolutionGuid = {289E6003-15D5-4377-ADA6-2E7093785BCD}
+1
View File
@@ -39,6 +39,7 @@
<PackageVersion Include="ReactiveUI" Version="17.1.50" /> <PackageVersion Include="ReactiveUI" Version="17.1.50" />
<PackageVersion Include="ReactiveUI.WPF" Version="17.1.50" /> <PackageVersion Include="ReactiveUI.WPF" Version="17.1.50" />
<PackageVersion Include="StructureMap" Version="2.6.4.1" /> <PackageVersion Include="StructureMap" Version="2.6.4.1" />
<PackageVersion Include="System.CommandLine" Version="2.0.3" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="8.0.0" /> <PackageVersion Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
<PackageVersion Include="System.Memory" Version="4.5.5" /> <PackageVersion Include="System.Memory" Version="4.5.5" />
<PackageVersion Include="System.Runtime.Caching" Version="8.0.0" /> <PackageVersion Include="System.Runtime.Caching" Version="8.0.0" />
@@ -0,0 +1,62 @@
using System;
using System.CommandLine;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Windows.Win32;
using Windows.Win32.Security.Credentials;
using static Program;
namespace test_utils;
internal static class CredDeleteCommand
{
internal static unsafe void Invoke(ParseResult result)
{
var patternValues = result.GetValue(Program.CredMatch);
Regex[] patterns = null;
if (patternValues is not null)
{
patterns = new Regex[patternValues.Length];
var patternWriter = ((Span<Regex>)patterns).GetEnumerator();
foreach (var item in patternValues)
{
patternWriter.MoveNext();
patternWriter.Current = new Regex(item, RegexOptions.Compiled);
}
}
CREDENTIALW** credentials = null;
try
{
if (!PInvoke.CredEnumerate(null, out var count, out credentials))
{
throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
}
foreach (ref readonly var credential in new ReadOnlySpan<PCREDENTIALW>(credentials, (int)count))
{
bool? matched;
if ((matched = patterns?.Any(credential.Value.TargetName.ToString().Match)) is false)
{
continue;
}
Console.WriteLine($"{credential.Value.TargetName} ({credential.Value.UserName})");
if (matched is true)
{
if (!PInvoke.CredDelete(credential.Value.TargetName, credential.Value.Type, 0))
{
Console.WriteLine($" Failure deleting: {Marshal.GetLastPInvokeErrorMessage()}");
}
}
}
}
finally
{
PInvoke.CredFree(credentials);
}
}
private static bool Match(this string text, Regex pattern) => pattern.IsMatch(text);
}
+3
View File
@@ -0,0 +1,3 @@
CredDelete
CredEnumerate
CredFree
+30
View File
@@ -0,0 +1,30 @@
using System.CommandLine;
using test_utils;
using Windows.Win32.Security.Credentials;
RootCommand command = [];
Command credDeleteCommand = new("cred-delete", "Lists Credentials, and optionally deletes them when matching.");
command.Add(credDeleteCommand);
credDeleteCommand.SetAction(CredDeleteCommand.Invoke);
credDeleteCommand.Add(CredMatch = new("--match")
{
AllowMultipleArgumentsPerToken = true,
Arity = ArgumentArity.ZeroOrMore,
Description = "When specified, Cred-Delete will delete items matching any of the Regex patterns."
});
return command.Parse(args).Invoke();
partial class Program
{
internal static Option<string[]> CredMatch;
internal readonly unsafe struct PCREDENTIALW
{
private readonly CREDENTIALW* _ptr;
public readonly ref CREDENTIALW Value => ref *_ptr;
}
}
+13
View File
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.Net.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-win</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" />
<PackageReference Include="System.CommandLine" />
</ItemGroup>
</Project>