فهرست منبع

FilteredSolutionsExtension: initial commit

Dalibor Votruba 1 سال پیش
کامیت
f2389a7de3
19فایلهای تغییر یافته به همراه1404 افزوده شده و 0 حذف شده
  1. 38 0
      .gitignore
  2. 37 0
      FilteredSolutionsExtension/FilteredSolutionsExtension.sln
  3. 163 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/FileUtils.cs
  4. 39 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/FilterSolutionsPackage.cs
  5. 59 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/FilteredSolutionGenerator.cs
  6. 96 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/FilteredSolutionsExtension.csproj
  7. 53 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/FilteredSolutionsExtensionPackage.cs
  8. 66 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectFilterDialog.xaml
  9. 183 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectFilterDialog.xaml.cs
  10. 23 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectInfo.cs
  11. 37 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectTreeItem.cs
  12. 33 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/Properties/AssemblyInfo.cs
  13. BIN
      FilteredSolutionsExtension/FilteredSolutionsExtension/Resources/FilterSolutionsCommand.png
  14. 114 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/SolutionEventsHandler.cs
  15. 179 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/SolutionOpeningInterceptor.cs
  16. 107 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/SolutionParser.cs
  17. 155 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/readme.md
  18. 22 0
      FilteredSolutionsExtension/FilteredSolutionsExtension/source.extension.vsixmanifest
  19. BIN
      bo_strong_key_pair.snk

+ 38 - 0
.gitignore

@@ -0,0 +1,38 @@
+
+#ignore thumbnails created by windows -
+Thumbs.db
+#Ignore files build by Visual Studio
+*.obj
+*.pdb
+*.user
+*.aps
+*.pch
+*.vspscc
+*_i.c
+*_p.c
+*.ncb
+*.suo
+*.tlb
+*.tlh
+*.bak
+*.cache
+*.ilk
+*.log
+*.suo
+[Bb]in
+[Dd]ebug*/
+*.lib
+*.sbr
+obj/
+[Rr]elease*/
+_ReSharper*/
+[Tt]est[Rr]esult*
+.idea/
+.vs/
+DTAR_*/
+packages/
+*.vssscc
+*.7z
+*.xsb
+*.pjb
+*.nupkg

+ 37 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension.sln

@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.36127.28 d17.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FilteredSolutionsExtension", "FilteredSolutionsExtension\FilteredSolutionsExtension.csproj", "{D5CD47F5-79CF-4E25-9075-7B44F0253A71}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|arm64 = Debug|arm64
+		Debug|x86 = Debug|x86
+		Release|Any CPU = Release|Any CPU
+		Release|arm64 = Release|arm64
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{D5CD47F5-79CF-4E25-9075-7B44F0253A71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D5CD47F5-79CF-4E25-9075-7B44F0253A71}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D5CD47F5-79CF-4E25-9075-7B44F0253A71}.Debug|arm64.ActiveCfg = Debug|arm64
+		{D5CD47F5-79CF-4E25-9075-7B44F0253A71}.Debug|arm64.Build.0 = Debug|arm64
+		{D5CD47F5-79CF-4E25-9075-7B44F0253A71}.Debug|x86.ActiveCfg = Debug|x86
+		{D5CD47F5-79CF-4E25-9075-7B44F0253A71}.Debug|x86.Build.0 = Debug|x86
+		{D5CD47F5-79CF-4E25-9075-7B44F0253A71}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D5CD47F5-79CF-4E25-9075-7B44F0253A71}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D5CD47F5-79CF-4E25-9075-7B44F0253A71}.Release|arm64.ActiveCfg = Release|arm64
+		{D5CD47F5-79CF-4E25-9075-7B44F0253A71}.Release|arm64.Build.0 = Release|arm64
+		{D5CD47F5-79CF-4E25-9075-7B44F0253A71}.Release|x86.ActiveCfg = Release|x86
+		{D5CD47F5-79CF-4E25-9075-7B44F0253A71}.Release|x86.Build.0 = Release|x86
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {63D46AD0-9415-40CD-846D-D643C9871A17}
+	EndGlobalSection
+EndGlobal

+ 163 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/FileUtils.cs

@@ -0,0 +1,163 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+{
+    /// <summary>
+    /// Provides async file operations compatible with .NET 4.7.2
+    /// Implements File.ReadAllTextAsync and File.WriteAllTextAsync functionality
+    /// </summary>
+    public static class FileUtils
+    {
+        /// <summary>
+        /// Asynchronously reads all text from a file using UTF-8 encoding
+        /// </summary>
+        /// <param name="path">The file path to read from</param>
+        /// <returns>A task that represents the asynchronous read operation</returns>
+        public static Task<string> ReadAllTextAsync(string path)
+        {
+            return ReadAllTextAsync(path, Encoding.UTF8);
+        }
+
+        /// <summary>
+        /// Asynchronously reads all text from a file using the specified encoding
+        /// </summary>
+        /// <param name="path">The file path to read from</param>
+        /// <param name="encoding">The encoding to use</param>
+        /// <returns>A task that represents the asynchronous read operation</returns>
+        public static Task<string> ReadAllTextAsync(string path, Encoding encoding)
+        {
+            return ReadAllTextAsync(path, encoding, CancellationToken.None);
+        }
+
+        /// <summary>
+        /// Asynchronously reads all text from a file using the specified encoding and cancellation token
+        /// </summary>
+        /// <param name="path">The file path to read from</param>
+        /// <param name="encoding">The encoding to use</param>
+        /// <param name="cancellationToken">The cancellation token</param>
+        /// <returns>A task that represents the asynchronous read operation</returns>
+        public static async Task<string> ReadAllTextAsync(string path, Encoding encoding, CancellationToken cancellationToken)
+        {
+            if (path == null)
+                throw new ArgumentNullException(nameof(path));
+            if (encoding == null)
+                throw new ArgumentNullException(nameof(encoding));
+
+            using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true))
+            using (var streamReader = new StreamReader(fileStream, encoding))
+            {
+                return await streamReader.ReadToEndAsync().ConfigureAwait(false);
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously writes text to a file using UTF-8 encoding
+        /// </summary>
+        /// <param name="path">The file path to write to</param>
+        /// <param name="contents">The text content to write</param>
+        /// <returns>A task that represents the asynchronous write operation</returns>
+        public static Task WriteAllTextAsync(string path, string contents)
+        {
+            return WriteAllTextAsync(path, contents, Encoding.UTF8);
+        }
+
+        /// <summary>
+        /// Asynchronously writes text to a file using the specified encoding
+        /// </summary>
+        /// <param name="path">The file path to write to</param>
+        /// <param name="contents">The text content to write</param>
+        /// <param name="encoding">The encoding to use</param>
+        /// <returns>A task that represents the asynchronous write operation</returns>
+        public static Task WriteAllTextAsync(string path, string contents, Encoding encoding)
+        {
+            return WriteAllTextAsync(path, contents, encoding, CancellationToken.None);
+        }
+
+        /// <summary>
+        /// Asynchronously writes text to a file using the specified encoding and cancellation token
+        /// </summary>
+        /// <param name="path">The file path to write to</param>
+        /// <param name="contents">The text content to write</param>
+        /// <param name="encoding">The encoding to use</param>
+        /// <param name="cancellationToken">The cancellation token</param>
+        /// <returns>A task that represents the asynchronous write operation</returns>
+        public static async Task WriteAllTextAsync(string path, string contents, Encoding encoding, CancellationToken cancellationToken)
+        {
+            if (path == null)
+                throw new ArgumentNullException(nameof(path));
+            if (contents == null)
+                throw new ArgumentNullException(nameof(contents));
+            if (encoding == null)
+                throw new ArgumentNullException(nameof(encoding));
+
+            using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 4096, useAsync: true))
+            using (var streamWriter = new StreamWriter(fileStream, encoding))
+            {
+                await streamWriter.WriteAsync(contents).ConfigureAwait(false);
+                await streamWriter.FlushAsync().ConfigureAwait(false);
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously reads all bytes from a file
+        /// </summary>
+        /// <param name="path">The file path to read from</param>
+        /// <returns>A task that represents the asynchronous read operation</returns>
+        public static async Task<byte[]> ReadAllBytesAsync(string path)
+        {
+            if (path == null)
+                throw new ArgumentNullException(nameof(path));
+
+            using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true))
+            {
+                var buffer = new byte[fileStream.Length];
+                var totalBytesRead = 0;
+                var bytesRead = 0;
+
+                while (totalBytesRead < buffer.Length)
+                {
+                    bytesRead = await fileStream.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead).ConfigureAwait(false);
+                    if (bytesRead == 0)
+                        break;
+                    totalBytesRead += bytesRead;
+                }
+
+                if (totalBytesRead != buffer.Length)
+                {
+                    // File size changed during read, resize array
+                    var resizedBuffer = new byte[totalBytesRead];
+                    Array.Copy(buffer, resizedBuffer, totalBytesRead);
+                    return resizedBuffer;
+                }
+
+                return buffer;
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously writes bytes to a file
+        /// </summary>
+        /// <param name="path">The file path to write to</param>
+        /// <param name="bytes">The bytes to write</param>
+        /// <returns>A task that represents the asynchronous write operation</returns>
+        public static async Task WriteAllBytesAsync(string path, byte[] bytes)
+        {
+            if (path == null)
+                throw new ArgumentNullException(nameof(path));
+            if (bytes == null)
+                throw new ArgumentNullException(nameof(bytes));
+
+            using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 4096, useAsync: true))
+            {
+                await fileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+                await fileStream.FlushAsync().ConfigureAwait(false);
+            }
+        }
+    }
+}

+ 39 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/FilterSolutionsPackage.cs

@@ -0,0 +1,39 @@
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Shell.Interop;
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Task = System.Threading.Tasks.Task;
+
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+{
+
+    [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
+    [Guid(FilterSolutionsPackage.PackageGuidString)]
+    [ProvideMenuResource("Menus.ctmenu", 1)]
+    [ProvideAutoLoad(UIContextGuids80.NoSolution, PackageAutoLoadFlags.BackgroundLoad)]
+    [ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)]
+    public sealed class FilterSolutionsPackage : AsyncPackage
+    {
+        public const string PackageGuidString = "12345678-1234-1234-1234-123456789abc";
+        private SolutionOpeningInterceptor _solutionInterceptor;
+
+        protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
+        {
+            await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
+
+            _solutionInterceptor = new SolutionOpeningInterceptor(this);
+            await _solutionInterceptor.InitializeAsync();
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                _solutionInterceptor?.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+    }
+
+}

+ 59 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/FilteredSolutionGenerator.cs

@@ -0,0 +1,59 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+{
+    public class FilteredSolutionGenerator
+    {
+        private static readonly Regex ProjectRegex = new Regex(
+            @"Project\(""{([^}]+)}""\)\s*=\s*""([^""]+)""\s*,\s*""([^""]+)""\s*,\s*""{([^}]+)}""",
+            RegexOptions.Compiled);
+
+        public async Task GenerateFilteredSolutionAsync(string originalSolutionPath, string targetSolutionPath, List<ProjectInfo> selectedProjects)
+        {
+            var originalContent = await FileUtils.ReadAllTextAsync(originalSolutionPath);
+            var lines = originalContent.Split('\n');
+            var filteredLines = new List<string>();
+            var selectedGuids = new HashSet<string>(selectedProjects.Select(p => p.ProjectGuid));
+
+            bool inProjectSection = false;
+            bool currentProjectIncluded = false;
+
+            foreach (var line in lines)
+            {
+                var trimmedLine = line.Trim();
+
+                if (trimmedLine.StartsWith("Project("))
+                {
+                    inProjectSection = true;
+                    var match = ProjectRegex.Match(line);
+                    if (match.Success)
+                    {
+                        var projectGuid = match.Groups[4].Value;
+                        currentProjectIncluded = selectedGuids.Contains(projectGuid);
+                    }
+                }
+
+                if (inProjectSection && currentProjectIncluded)
+                {
+                    filteredLines.Add(line);
+                }
+                else if (!inProjectSection)
+                {
+                    filteredLines.Add(line);
+                }
+
+                if (trimmedLine.StartsWith("EndProject"))
+                {
+                    inProjectSection = false;
+                    currentProjectIncluded = false;
+                }
+            }
+
+            await FileUtils.WriteAllTextAsync(targetSolutionPath, string.Join("\n", filteredLines));
+        }
+    }
+}

+ 96 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/FilteredSolutionsExtension.csproj

@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <MinimumVisualStudioVersion>17.0</MinimumVisualStudioVersion>
+    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+  </PropertyGroup>
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectTypeGuids>{82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <ProjectGuid>{D5CD47F5-79CF-4E25-9075-7B44F0253A71}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension</RootNamespace>
+    <AssemblyName>FilteredSolutionsExtension</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <GeneratePkgDefFile>true</GeneratePkgDefFile>
+    <UseCodebase>true</UseCodebase>
+    <IncludeAssemblyInVSIXContainer>true</IncludeAssemblyInVSIXContainer>
+    <IncludeDebugSymbolsInVSIXContainer>false</IncludeDebugSymbolsInVSIXContainer>
+    <IncludeDebugSymbolsInLocalVSIXDeployment>false</IncludeDebugSymbolsInLocalVSIXDeployment>
+    <CopyBuildOutputToOutputDirectory>true</CopyBuildOutputToOutputDirectory>
+    <CopyOutputSymbolsToOutputDirectory>true</CopyOutputSymbolsToOutputDirectory>
+    <StartAction>Program</StartAction>
+    <StartProgram Condition="'$(DevEnvDir)' != ''">$(DevEnvDir)devenv.exe</StartProgram>
+    <StartArguments>/rootsuffix Exp</StartArguments>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="FileUtils.cs" />
+    <Compile Include="FilteredSolutionGenerator.cs" />
+    <Compile Include="FilterSolutionsPackage.cs" />
+    <Compile Include="ProjectFilterDialog.xaml.cs">
+      <DependentUpon>ProjectFilterDialog.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="ProjectInfo.cs" />
+    <Compile Include="ProjectTreeItem.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="FilteredSolutionsExtensionPackage.cs" />
+    <Compile Include="SolutionEventsHandler.cs" />
+    <Compile Include="SolutionOpeningInterceptor.cs" />
+    <Compile Include="SolutionParser.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="readme.md" />
+    <None Include="source.extension.vsixmanifest">
+      <SubType>Designer</SubType>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+    <Reference Include="System" />
+    <Reference Include="System.Xaml" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.0.32112.339" ExcludeAssets="runtime" NoWarn="NU1604" />
+    <PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.14.2094" NoWarn="NU1604" />
+  </ItemGroup>
+  <ItemGroup>
+    <Page Include="ProjectFilterDialog.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="Resources\FilterSolutionsCommand.png" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

+ 53 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/FilteredSolutionsExtensionPackage.cs

@@ -0,0 +1,53 @@
+using Microsoft.VisualStudio.Shell;
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Task = System.Threading.Tasks.Task;
+
+namespace FilteredSolutionsExtension
+{
+    /// <summary>
+    /// This is the class that implements the package exposed by this assembly.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// The minimum requirement for a class to be considered a valid package for Visual Studio
+    /// is to implement the IVsPackage interface and register itself with the shell.
+    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
+    /// to do it: it derives from the Package class that provides the implementation of the
+    /// IVsPackage interface and uses the registration attributes defined in the framework to
+    /// register itself and its components with the shell. These attributes tell the pkgdef creation
+    /// utility what data to put into .pkgdef file.
+    /// </para>
+    /// <para>
+    /// To get loaded into VS, the package must be referred by &lt;Asset Type="Microsoft.VisualStudio.VsPackage" ...&gt; in .vsixmanifest file.
+    /// </para>
+    /// </remarks>
+    [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
+    [Guid(FilteredSolutionsExtensionPackage.PackageGuidString)]
+    public sealed class FilteredSolutionsExtensionPackage : AsyncPackage
+    {
+        /// <summary>
+        /// FilteredSolutionsExtensionPackage GUID string.
+        /// </summary>
+        public const string PackageGuidString = "3fb488a4-d627-4ddd-a424-03f493cd0915";
+
+        #region Package Members
+
+        /// <summary>
+        /// Initialization of the package; this method is called right after the package is sited, so this is the place
+        /// where you can put all the initialization code that rely on services provided by VisualStudio.
+        /// </summary>
+        /// <param name="cancellationToken">A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down.</param>
+        /// <param name="progress">A provider for progress updates.</param>
+        /// <returns>A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method.</returns>
+        protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
+        {
+            // When initialized asynchronously, the current thread may be a background thread at this point.
+            // Do any initialization that requires the UI thread after switching to the UI thread.
+            await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
+        }
+
+        #endregion
+    }
+}

+ 66 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectFilterDialog.xaml

@@ -0,0 +1,66 @@
+<!-- ProjectFilterDialog.xaml -->
+<Window x:Class="Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+.ProjectFilterDialog"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        Title="Select Projects for Solution" Height="600" Width="800" 
+        WindowStartupLocation="CenterScreen" ResizeMode="CanResize">
+    <Grid Margin="10">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+        
+        <!-- Filter Section -->
+        <GroupBox Grid.Row="0" Header="Filter" Padding="10" Margin="0,0,0,10">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="Auto"/>
+                </Grid.ColumnDefinitions>
+                
+                <Label Grid.Column="0" Content="Filter pattern:" VerticalAlignment="Center"/>
+                <TextBox Grid.Column="1" x:Name="FilterTextBox" Margin="5,0" 
+                         VerticalAlignment="Center" Height="25"/>
+                <Button Grid.Column="2" x:Name="FilterButton" Content="Filter" 
+                        Width="75" Click="FilterButton_Click" Margin="5,0,0,0"/>
+            </Grid>
+        </GroupBox>
+        
+        <!-- Button Section -->
+        <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,10">
+            <Button x:Name="SelectAllButton" Content="Select All" Width="80" Height="30" 
+                    Click="SelectAllButton_Click" Margin="0,0,5,0"/>
+            <Button x:Name="ClearAllButton" Content="Clear All" Width="80" Height="30" 
+                    Click="ClearAllButton_Click"/>
+        </StackPanel>
+        
+        <!-- Project Tree -->
+        <Border Grid.Row="2" BorderBrush="Gray" BorderThickness="1" Margin="0,0,0,10">
+            <TreeView x:Name="ProjectTreeView" ScrollViewer.HorizontalScrollBarVisibility="Auto">
+                <TreeView.ItemTemplate>
+                    <DataTemplate>
+                        <StackPanel Orientation="Horizontal">
+                            <CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" 
+                                      Checked="ProjectTreeItem_Checked"
+                                      Unchecked="ProjectTreeItem_Unchecked"
+                                      Margin="0,0,5,0"/>
+                            <TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center"/>
+                        </StackPanel>
+                    </DataTemplate>
+                </TreeView.ItemTemplate>
+            </TreeView>
+        </Border>
+        
+        <!-- Action Buttons -->
+        <StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
+            <Button x:Name="OkButton" Content="OK" Width="75" Height="30" 
+                    Click="OkButton_Click" IsDefault="True" Margin="0,0,5,0"/>
+            <Button x:Name="CancelButton" Content="Cancel" Width="75" Height="30" 
+                    Click="CancelButton_Click" IsCancel="True"/>
+        </StackPanel>
+    </Grid>
+</Window>

+ 183 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectFilterDialog.xaml.cs

@@ -0,0 +1,183 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.ComponentModel;
+using System.Collections.ObjectModel;
+
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+{
+    public partial class ProjectFilterDialog : Window
+    {
+        private readonly SolutionInfo _solutionInfo;
+        private readonly ObservableCollection<ProjectTreeItem> _projectItems;
+        private readonly ObservableCollection<ProjectTreeItem> _filteredItems;
+        
+        public List<ProjectInfo> SelectedProjects { get; private set; } = new List<ProjectInfo>();
+
+        public ProjectFilterDialog(SolutionInfo solutionInfo)
+        {
+            InitializeComponent();
+            _solutionInfo = solutionInfo;
+            _projectItems = new ObservableCollection<ProjectTreeItem>(CreateProjectTreeItems());
+            _filteredItems = new ObservableCollection<ProjectTreeItem>(_projectItems);
+            
+            ProjectTreeView.ItemsSource = _filteredItems;
+            Title = $"Select Projects - {System.IO.Path.GetFileNameWithoutExtension(_solutionInfo.SolutionPath)}";
+            
+            // Subscribe to property changes for dependency handling
+            foreach (var item in _projectItems)
+            {
+                item.PropertyChanged += ProjectItem_PropertyChanged;
+            }
+        }
+
+        private void ProjectItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
+        {
+            if (e.PropertyName == nameof(ProjectTreeItem.IsChecked))
+            {
+                var item = sender as ProjectTreeItem;
+                if (item != null)
+                {
+                    if (item.IsChecked)
+                    {
+                        CheckDependencies(item.ProjectInfo);
+                    }
+                    else
+                    {
+                        UncheckDependents(item.ProjectInfo);
+                    }
+                }
+            }
+        }
+
+        private List<ProjectTreeItem> CreateProjectTreeItems()
+        {
+            return _solutionInfo.Projects.Select(p => new ProjectTreeItem(p)).ToList();
+        }
+
+        private void SelectAllButton_Click(object sender, RoutedEventArgs e)
+        {
+            foreach (var item in _projectItems)
+            {
+                item.IsChecked = true;
+            }
+        }
+
+        private void ClearAllButton_Click(object sender, RoutedEventArgs e)
+        {
+            foreach (var item in _projectItems)
+            {
+                item.IsChecked = false;
+            }
+        }
+
+        private void FilterButton_Click(object sender, RoutedEventArgs e)
+        {
+            var pattern = FilterTextBox.Text.Trim();
+            _filteredItems.Clear();
+            
+            if (string.IsNullOrEmpty(pattern))
+            {
+                foreach (var item in _projectItems)
+                {
+                    _filteredItems.Add(item);
+                }
+            }
+            else
+            {
+                var matchingItems = _projectItems.Where(item => 
+                    MatchesPattern(item.ProjectInfo.DisplayName, pattern));
+                
+                foreach (var item in matchingItems)
+                {
+                    _filteredItems.Add(item);
+                }
+            }
+        }
+
+        private bool MatchesPattern(string text, string pattern)
+        {
+            if (string.IsNullOrEmpty(pattern)) return true;
+            
+            // Simple wildcard matching
+            pattern = pattern.Replace("*", ".*").Replace("?", ".");
+            return System.Text.RegularExpressions.Regex.IsMatch(text, pattern, 
+                System.Text.RegularExpressions.RegexOptions.IgnoreCase);
+        }
+
+        private void OkButton_Click(object sender, RoutedEventArgs e)
+        {
+            var checkedItems = _projectItems.Where(item => item.IsChecked).ToList();
+            SelectedProjects = checkedItems.Select(item => item.ProjectInfo).ToList();
+            
+            // Ensure all dependencies are included (should already be checked due to auto-checking)
+            var finalProjects = new HashSet<ProjectInfo>(SelectedProjects);
+            foreach (var project in SelectedProjects.ToList())
+            {
+                AddDependencies(project, finalProjects);
+            }
+            
+            SelectedProjects = finalProjects.ToList();
+            DialogResult = true;
+            Close();
+        }
+
+        private void AddDependencies(ProjectInfo project, HashSet<ProjectInfo> finalProjects)
+        {
+            foreach (var depGuid in project.Dependencies)
+            {
+                var depProject = _solutionInfo.Projects.FirstOrDefault(p => 
+                    p.ProjectGuid.Equals(depGuid, StringComparison.OrdinalIgnoreCase));
+                
+                if (depProject != null && !finalProjects.Contains(depProject))
+                {
+                    finalProjects.Add(depProject);
+                    AddDependencies(depProject, finalProjects);
+                }
+            }
+        }
+
+        private void CancelButton_Click(object sender, RoutedEventArgs e)
+        {
+            DialogResult = false;
+            Close();
+        }
+
+        private void CheckDependencies(ProjectInfo project)
+        {
+            foreach (var depGuid in project.Dependencies)
+            {
+                var depItem = _projectItems.FirstOrDefault(item => 
+                    item.ProjectInfo.ProjectGuid.Equals(depGuid, StringComparison.OrdinalIgnoreCase));
+                
+                if (depItem != null && !depItem.IsChecked)
+                {
+                    depItem.PropertyChanged -= ProjectItem_PropertyChanged; // Temporarily unsubscribe
+                    depItem.IsChecked = true;
+                    depItem.PropertyChanged += ProjectItem_PropertyChanged; // Resubscribe
+                    CheckDependencies(depItem.ProjectInfo);
+                }
+            }
+        }
+
+        private void UncheckDependents(ProjectInfo project)
+        {
+            foreach (var item in _projectItems)
+            {
+                if (item.ProjectInfo.Dependencies.Contains(project.ProjectGuid) && item.IsChecked)
+                {
+                    item.PropertyChanged -= ProjectItem_PropertyChanged; // Temporarily unsubscribe
+                    item.IsChecked = false;
+                    item.PropertyChanged += ProjectItem_PropertyChanged; // Resubscribe
+                    UncheckDependents(item.ProjectInfo);
+                }
+            }
+        }
+
+        // Remove the old event handlers as we're using PropertyChanged now
+        private void ProjectTreeItem_Checked(object sender, RoutedEventArgs e) { }
+        private void ProjectTreeItem_Unchecked(object sender, RoutedEventArgs e) { }
+    }
+}

+ 23 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectInfo.cs

@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+{
+    public class ProjectInfo
+    {
+        public string Name { get; set; }
+        public string Path { get; set; }
+        public string ProjectGuid { get; set; }
+        public string TypeGuid { get; set; }
+        public List<string> Dependencies { get; set; } = new List<string>();
+        public bool IsSelected { get; set; } = true;
+
+        public string DisplayName => System.IO.Path.GetFileNameWithoutExtension(Name);
+    }
+
+    public class SolutionInfo
+    {
+        public string SolutionPath { get; set; }
+        public List<ProjectInfo> Projects { get; set; } = new List<ProjectInfo>();
+        public Dictionary<string, List<string>> ProjectDependencies { get; set; } = new Dictionary<string, List<string>>();
+    }
+}

+ 37 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectTreeItem.cs

@@ -0,0 +1,37 @@
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+{
+
+    public class ProjectTreeItem : System.ComponentModel.INotifyPropertyChanged
+    {
+        private bool _isChecked = true;
+
+        public ProjectInfo ProjectInfo { get; }
+
+        public bool IsChecked
+        {
+            get => _isChecked;
+            set
+            {
+                if (_isChecked != value)
+                {
+                    _isChecked = value;
+                    OnPropertyChanged();
+                }
+            }
+        }
+
+        public string DisplayName => ProjectInfo.DisplayName;
+
+        public ProjectTreeItem(ProjectInfo projectInfo)
+        {
+            ProjectInfo = projectInfo;
+        }
+
+        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
+
+        protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
+        {
+            PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
+        }
+    }
+}

+ 33 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/Properties/AssemblyInfo.cs

@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("FilteredSolutionsExtension")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("FilteredSolutionsExtension")]
+[assembly: AssemblyCopyright("")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

BIN
FilteredSolutionsExtension/FilteredSolutionsExtension/Resources/FilterSolutionsCommand.png


+ 114 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/SolutionEventsHandler.cs

@@ -0,0 +1,114 @@
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Shell.Interop;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+{
+    public class SolutionEventsHandler : IVsSolutionEvents, IDisposable
+    {
+        private readonly AsyncPackage _package;
+        private IVsSolution _solution;
+        private uint _solutionEventsCookie;
+
+        public SolutionEventsHandler(AsyncPackage package)
+        {
+            _package = package;
+        }
+
+        public async Task InitializeAsync()
+        {
+            await _package.JoinableTaskFactory.SwitchToMainThreadAsync();
+
+            _solution = await _package.GetServiceAsync(typeof(SVsSolution)) as IVsSolution;
+            if (_solution != null)
+            {
+                _solution.AdviseSolutionEvents(this, out _solutionEventsCookie);
+            }
+        }
+
+        public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
+        {
+            // Solution already opened, we need to intercept before this
+            return VSConstants.S_OK;
+        }
+
+        public int OnBeforeOpenSolution(string pszSolutionFilename)
+        {
+            ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
+            {
+                await HandleSolutionOpeningAsync(pszSolutionFilename);
+            });
+
+            return VSConstants.S_OK;
+        }
+
+        private async Task HandleSolutionOpeningAsync(string solutionPath)
+        {
+            try
+            {
+                var solutionInfo = SolutionParser.ParseSolution(solutionPath);
+                var dialog = new ProjectFilterDialog(solutionInfo);
+
+                if (dialog.ShowDialog() == true && dialog.SelectedProjects.Count > 0)
+                {
+                    var tempSolutionPath = await CreateFilteredSolutionAsync(solutionPath, dialog.SelectedProjects);
+                    if (!string.IsNullOrEmpty(tempSolutionPath))
+                    {
+                        // Close current solution and open filtered one
+                        await _package.JoinableTaskFactory.SwitchToMainThreadAsync();
+                        var dte = await _package.GetServiceAsync(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
+                        dte?.Solution.Open(tempSolutionPath);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                // Log error
+                System.Diagnostics.Debug.WriteLine($"Error filtering solution: {ex.Message}");
+            }
+        }
+
+        private async Task<string> CreateFilteredSolutionAsync(string originalSolutionPath, System.Collections.Generic.List<ProjectInfo> selectedProjects)
+        {
+            try
+            {
+                var tempDir = Path.Combine(Path.GetTempPath(), "VSProjectFilter", Guid.NewGuid().ToString());
+                Directory.CreateDirectory(tempDir);
+
+                var tempSolutionPath = Path.Combine(tempDir, Path.GetFileName(originalSolutionPath));
+
+                var solutionGenerator = new FilteredSolutionGenerator();
+                await solutionGenerator.GenerateFilteredSolutionAsync(originalSolutionPath, tempSolutionPath, selectedProjects);
+
+                return tempSolutionPath;
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
+        public void Dispose()
+        {
+            ThreadHelper.ThrowIfNotOnUIThread();
+            if (_solution != null && _solutionEventsCookie != 0)
+            {
+                _solution.UnadviseSolutionEvents(_solutionEventsCookie);
+            }
+        }
+
+        // Other IVsSolutionEvents methods (return S_OK for unused events)
+        public int OnAfterCloseSolution(object pUnkReserved) => VSConstants.S_OK;
+        public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) => VSConstants.S_OK;
+        public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) => VSConstants.S_OK;
+        public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) => VSConstants.S_OK;
+        public int OnBeforeCloseSolution(object pUnkReserved) => VSConstants.S_OK;
+        public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) => VSConstants.S_OK;
+        public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) => VSConstants.S_OK;
+        public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) => VSConstants.S_OK;
+        public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) => VSConstants.S_OK;
+    }
+}

+ 179 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/SolutionOpeningInterceptor.cs

@@ -0,0 +1,179 @@
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Shell.Interop;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using EnvDTE;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+{
+public class SolutionOpeningInterceptor : IVsRunningDocTableEvents, IDisposable
+{
+    private readonly AsyncPackage _package;
+    private IVsRunningDocumentTable _runningDocumentTable;
+    private uint _rdtCookie;
+    private readonly Dictionary<string, bool> _processedSolutions = new Dictionary<string, bool>();
+
+    public SolutionOpeningInterceptor(AsyncPackage package)
+    {
+        _package = package;
+    }
+
+    public async Task InitializeAsync()
+    {
+        await _package.JoinableTaskFactory.SwitchToMainThreadAsync();
+        
+        _runningDocumentTable = await _package.GetServiceAsync(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
+        if (_runningDocumentTable != null)
+        {
+            _runningDocumentTable.AdviseRunningDocTableEvents(this, out _rdtCookie);
+        }
+
+        // Also monitor DTE events for solution opening
+        var dte = await _package.GetServiceAsync(typeof(DTE)) as DTE;
+        if (dte?.Events?.SolutionEvents != null)
+        {
+            dte.Events.SolutionEvents.Opened += SolutionEvents_Opened;
+            dte.Events.SolutionEvents.BeforeClosing += SolutionEvents_BeforeClosing;
+        }
+    }
+
+    private void SolutionEvents_BeforeClosing()
+    {
+        ThreadHelper.ThrowIfNotOnUIThread();
+        // Clear processed solutions when current solution is closing
+        _processedSolutions.Clear();
+    }
+
+    private void SolutionEvents_Opened()
+    {
+        ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
+        {
+            await HandleSolutionOpenedAsync();
+        });
+    }
+
+    private async Task HandleSolutionOpenedAsync()
+    {
+        try
+        {
+            await _package.JoinableTaskFactory.SwitchToMainThreadAsync();
+            
+            var dte = await _package.GetServiceAsync(typeof(DTE)) as DTE;
+            if (dte?.Solution?.FullName != null)
+            {
+                var solutionPath = dte.Solution.FullName;
+                
+                // Check if we already processed this solution in this session
+                if (_processedSolutions.ContainsKey(solutionPath))
+                    return;
+
+                _processedSolutions[solutionPath] = true;
+
+                // Only show dialog if solution has multiple projects
+                if (dte.Solution.Projects.Count > 1)
+                {
+                    await ShowProjectFilterDialogAsync(solutionPath, dte);
+                }
+            }
+        }
+        catch (Exception ex)
+        {
+            System.Diagnostics.Debug.WriteLine($"Error in HandleSolutionOpenedAsync: {ex.Message}");
+        }
+    }
+
+    private async Task ShowProjectFilterDialogAsync(string solutionPath, DTE dte)
+    {
+        try
+        {
+            var solutionInfo = SolutionParser.ParseSolution(solutionPath);
+            
+            // Show dialog on UI thread
+            await _package.JoinableTaskFactory.SwitchToMainThreadAsync();
+            
+            var dialog = new ProjectFilterDialog(solutionInfo);
+            var result = dialog.ShowDialog();
+            
+            if (result == true && dialog.SelectedProjects.Count > 0 && 
+                dialog.SelectedProjects.Count < solutionInfo.Projects.Count)
+            {
+                await CreateAndOpenFilteredSolutionAsync(solutionPath, dialog.SelectedProjects, dte);
+            }
+        }
+        catch (Exception ex)
+        {
+            System.Diagnostics.Debug.WriteLine($"Error showing project filter dialog: {ex.Message}");
+        }
+    }
+
+    private async Task CreateAndOpenFilteredSolutionAsync(string originalSolutionPath, List<ProjectInfo> selectedProjects, DTE dte)
+    {
+        try
+        {
+            var tempDir = Path.Combine(Path.GetTempPath(), "VSProjectFilter", Guid.NewGuid().ToString());
+            Directory.CreateDirectory(tempDir);
+            
+            var tempSolutionPath = Path.Combine(tempDir, Path.GetFileName(originalSolutionPath));
+            
+            var solutionGenerator = new FilteredSolutionGenerator();
+            await solutionGenerator.GenerateFilteredSolutionAsync(originalSolutionPath, tempSolutionPath, selectedProjects);
+            
+            // Close current solution and open filtered one
+            await _package.JoinableTaskFactory.SwitchToMainThreadAsync();
+            
+            // Mark this solution as processed to avoid showing dialog again
+            _processedSolutions[tempSolutionPath] = true;
+            
+            dte.Solution.Close(false);
+            dte.Solution.Open(tempSolutionPath);
+        }
+        catch (Exception ex)
+        {
+            System.Diagnostics.Debug.WriteLine($"Error creating filtered solution: {ex.Message}");
+        }
+    }
+
+    public void Dispose()
+    {
+        ThreadHelper.ThrowIfNotOnUIThread();
+        if (_runningDocumentTable != null && _rdtCookie != 0)
+        {
+            _runningDocumentTable.UnadviseRunningDocTableEvents(_rdtCookie);
+        }
+    }
+
+    // IVsRunningDocTableEvents implementation
+    public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
+    {
+        return Microsoft.VisualStudio.VSConstants.S_OK;
+    }
+
+    public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
+    {
+        return Microsoft.VisualStudio.VSConstants.S_OK;
+    }
+
+    public int OnAfterSave(uint docCookie)
+    {
+        return Microsoft.VisualStudio.VSConstants.S_OK;
+    }
+
+    public int OnAfterAttributeChange(uint docCookie, uint grfAttribs)
+    {
+        return Microsoft.VisualStudio.VSConstants.S_OK;
+    }
+
+    public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
+    {
+        return Microsoft.VisualStudio.VSConstants.S_OK;
+    }
+
+    public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame)
+    {
+        return Microsoft.VisualStudio.VSConstants.S_OK;
+    }
+}
+}

+ 107 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/SolutionParser.cs

@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.RegularExpressions;
+
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+{
+    public static class SolutionParser
+    {
+        private static readonly Regex ProjectRegex = new Regex(
+            @"Project\(""{([^}]+)}""\)\s*=\s*""([^""]+)""\s*,\s*""([^""]+)""\s*,\s*""{([^}]+)}""",
+            RegexOptions.Compiled);
+
+        private static readonly Regex ProjectDependencyRegex = new Regex(
+            @"{([^}]+)}\s*=\s*{([^}]+)}",
+            RegexOptions.Compiled);
+
+        public static SolutionInfo ParseSolution(string solutionPath)
+        {
+            var solutionInfo = new SolutionInfo { SolutionPath = solutionPath };
+            var content = File.ReadAllText(solutionPath);
+
+            // Parse projects
+            var projectMatches = ProjectRegex.Matches(content);
+            foreach (Match match in projectMatches)
+            {
+                var typeGuid = match.Groups[1].Value;
+                var name = match.Groups[2].Value;
+                var path = match.Groups[3].Value;
+                var projectGuid = match.Groups[4].Value;
+
+                // Skip solution folders
+                if (typeGuid.Equals("{2150E333-8FDC-42A3-9474-1A3956D46DE8}", StringComparison.OrdinalIgnoreCase))
+                    continue;
+
+                var fullPath = Path.IsPathRooted(path) ? path : Path.Combine(Path.GetDirectoryName(solutionPath), path);
+
+                solutionInfo.Projects.Add(new ProjectInfo
+                {
+                    Name = name,
+                    Path = fullPath,
+                    ProjectGuid = projectGuid,
+                    TypeGuid = typeGuid
+                });
+            }
+
+            // Parse project dependencies
+            ParseProjectDependencies(content, solutionInfo);
+
+            return solutionInfo;
+        }
+
+        private static void ParseProjectDependencies(string content, SolutionInfo solutionInfo)
+        {
+            var lines = content.Split('\n');
+            string currentProjectGuid = null;
+
+            for (int i = 0; i < lines.Length; i++)
+            {
+                var line = lines[i].Trim();
+
+                if (line.StartsWith("ProjectSection(ProjectDependencies)"))
+                {
+                    // Find the project GUID for this section
+                    for (int j = i - 1; j >= 0; j--)
+                    {
+                        var projectMatch = ProjectRegex.Match(lines[j]);
+                        if (projectMatch.Success)
+                        {
+                            currentProjectGuid = projectMatch.Groups[4].Value;
+                            break;
+                        }
+                    }
+                    continue;
+                }
+
+                if (line == "EndProjectSection" || line.StartsWith("EndProject"))
+                {
+                    currentProjectGuid = null;
+                    continue;
+                }
+
+                if (currentProjectGuid != null)
+                {
+                    var depMatch = ProjectDependencyRegex.Match(line);
+                    if (depMatch.Success)
+                    {
+                        var dependentGuid = depMatch.Groups[2].Value;
+
+                        if (!solutionInfo.ProjectDependencies.ContainsKey(currentProjectGuid))
+                        {
+                            solutionInfo.ProjectDependencies[currentProjectGuid] = new List<string>();
+                        }
+                        solutionInfo.ProjectDependencies[currentProjectGuid].Add(dependentGuid);
+
+                        // Also add to project info
+                        var project = solutionInfo.Projects.Find(p => p.ProjectGuid.Equals(currentProjectGuid, StringComparison.OrdinalIgnoreCase));
+                        if (project != null)
+                        {
+                            project.Dependencies.Add(dependentGuid);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 155 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/readme.md

@@ -0,0 +1,155 @@
+# Visual Studio 2022 Filter Solutions Extension
+
+## Project Structure
+```
+FilteredSolutionsExtension/
+├── FilteredSolutionsExtension.csproj
+├── extension.vsixmanifest
+├── FilterSolutionsPackage.vsct
+├── FilterSolutionsPackage.cs
+├── SolutionOpeningInterceptor.cs
+├── ProjectInfo.cs
+├── SolutionParser.cs
+├── FilteredSolutionGenerator.cs
+├── ProjectFilterDialog.xaml
+├── ProjectFilterDialog.xaml.cs
+├── ProjectTreeItem.cs
+└── Properties/
+    └── AssemblyInfo.cs
+```
+
+## Additional Files Needed
+
+### Properties/AssemblyInfo.cs
+```csharp
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("FilteredSolutionsExtension")]
+[assembly: AssemblyDescription("Visual Studio extension to filter projects when opening solutions")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("YourCompany")]
+[assembly: AssemblyProduct("Filter Solutions Extension")]
+[assembly: AssemblyCopyright("Copyright © 2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: ComVisible(false)]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+```
+
+### Resources/FilterSolutionsCommand.png
+Create a 16x16 pixel icon for the command button (simple filter icon).
+
+## Building the Extension
+
+1. **Prerequisites:**
+   - Visual Studio 2022 with Visual Studio extension development workload
+   - .NET 9.0 SDK
+
+2. **Create the Project:**
+   ```bash
+   # Create new VSIX project
+   dotnet new vsix -n FilteredSolutionsExtension
+   cd FilteredSolutionsExtension
+   ```
+
+3. **Replace/Add Files:**
+   - Replace the generated files with the code provided above
+   - Ensure all files are in the correct namespace: `FilteredSolutionsExtension`
+
+4. **Build:**
+   ```bash
+   dotnet build --configuration Release
+   ```
+
+## Key Features Implementation
+
+### 1. Solution Opening Interception
+- Uses `SolutionOpeningInterceptor` to monitor DTE solution events
+- Intercepts after solution is opened but before projects are fully loaded
+- Prevents duplicate dialogs with solution tracking
+
+### 2. Project Tree with Dependencies
+- Displays all projects in a tree view with checkboxes
+- Automatically handles project dependencies:
+  - Checking a project auto-checks its dependencies
+  - Unchecking a project auto-unchecks dependent projects
+
+### 3. Filtering Capabilities
+- Text-based pattern filtering with wildcard support (* and ?)
+- "Select All" and "Clear All" functionality
+- Real-time filtering of project list
+
+### 4. Solution Generation
+- Creates filtered `.sln` file in temp directory
+- Preserves original solution file
+- Only includes selected projects and their dependencies
+- Maintains proper solution file format
+
+## Usage Workflow
+
+1. **Open Solution:** When opening a solution with multiple projects
+2. **Dialog Appears:** Project filter dialog shows automatically
+3. **Select Projects:** Use checkboxes, filter pattern, or bulk selection
+4. **Dependencies:** Dependencies are automatically selected/deselected
+5. **Generate:** Click "OK" to create filtered solution
+6. **Open Filtered:** Original solution closes, filtered solution opens
+
+## Advanced Configuration
+
+### Customizing Auto-Load Behavior
+In `FilterSolutionsPackage.cs`, modify the `ProvideAutoLoad` attributes:
+```csharp
+[ProvideAutoLoad(UIContextGuids80.NoSolution, PackageAutoLoadFlags.BackgroundLoad)]
+[ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)]
+```
+
+### Adding Command Menu
+The extension includes a `.vsct` file that adds a "Filter Solutions..." command to the File menu for manual triggering.
+
+### Debugging
+1. Set the startup project to the VSIX project
+2. Press F5 to launch experimental instance of Visual Studio
+3. Open a solution to test the extension
+
+## Installation
+
+1. **Build the VSIX:**
+   ```bash
+   dotnet build --configuration Release
+   ```
+
+2. **Install Extension:**
+   - Double-click the generated `.vsix` file in `bin/Release/`
+   - Or use Visual Studio Extensions Manager
+
+3. **Verify Installation:**
+   - Check Extensions > Manage Extensions
+   - Look for "Filter Solutions Extension"
+
+## Troubleshooting
+
+### Common Issues:
+1. **Dialog doesn't appear:** Check that solution has multiple projects
+2. **Dependencies not working:** Verify project references in original solution
+3. **Filtered solution won't open:** Check temp directory permissions
+4. **Extension won't load:** Verify Visual Studio 2022 compatibility
+
+### Debug Output:
+The extension writes debug information to Visual Studio Output window. Check for error messages in the debug output.
+
+## Limitations
+
+1. Only works with standard MSBuild-based projects
+2. Solution folders are filtered out during parsing
+3. Temporary solutions are created in system temp directory
+4. Complex project configurations may need manual adjustment
+
+## Future Enhancements
+
+- Remember user preferences for project selection
+- Support for solution folders
+- Integration with Visual Studio's solution explorer
+- Batch processing of multiple solutions
+- Export/import of filter configurations

+ 22 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/source.extension.vsixmanifest

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
+    <Metadata>
+        <Identity Id="FilteredSolutionsExtension.e12e79ba-6c63-4737-868d-4e888accaed1" Version="1.0.0" Language="en-US" Publisher="Dalibor Votruba" />
+        <DisplayName>FilteredSolutionsExtension</DisplayName>
+        <Description xml:space="preserve">Visual Studio extension to filter projects when opening solutions.</Description>
+    </Metadata>
+    <Installation>
+        <InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[17.0, 18.0)">
+            <ProductArchitecture>amd64</ProductArchitecture>
+        </InstallationTarget>
+    </Installation>
+    <Dependencies>
+        <Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" />
+    </Dependencies>
+    <Prerequisites>
+        <Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[17.0,18.0)" DisplayName="Visual Studio core editor" />
+    </Prerequisites>
+    <Assets>
+        <Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" />
+    </Assets>
+</PackageManifest>

BIN
bo_strong_key_pair.snk