From 4c95ea55046b2087ba0ae65e7041cc2ae03620af Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Tue, 17 Mar 2026 10:56:05 -0700 Subject: [PATCH 01/26] First attempt at adding a build guard while packages are installing --- .../Cpp/Common/VSPackage.Designer.cs | 6 + dev/VSIX/Extension/Cpp/Common/VSPackage.resx | 3 + .../WindowsAppSDK.Cpp.Extension.Dev17.csproj | 1 + .../Extension/Cs/Common/VSPackage.Designer.cs | 6 + dev/VSIX/Extension/Cs/Common/VSPackage.resx | 3 + .../WindowsAppSDK.Cs.Extension.Dev17.csproj | 1 + dev/VSIX/Shared/BuildGuard.cs | 207 ++++++++++++++++++ dev/VSIX/Shared/WizardImplementation.cs | 49 +++-- 8 files changed, 259 insertions(+), 17 deletions(-) create mode 100644 dev/VSIX/Shared/BuildGuard.cs diff --git a/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs b/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs index 751ef7436c..159e20e6f5 100644 --- a/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs +++ b/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs @@ -471,5 +471,11 @@ internal static string _1054 { return ResourceManager.GetString("1054", resourceCulture); } } + + internal static string _1055 { + get { + return ResourceManager.GetString("1055", resourceCulture); + } + } } } diff --git a/dev/VSIX/Extension/Cpp/Common/VSPackage.resx b/dev/VSIX/Extension/Cpp/Common/VSPackage.resx index 826705f735..76f9fe90a3 100644 --- a/dev/VSIX/Extension/Cpp/Common/VSPackage.resx +++ b/dev/VSIX/Extension/Cpp/Common/VSPackage.resx @@ -256,4 +256,7 @@ For more details on the error, check the General tab in the Output window. No output information available. + + Build is currently disabled while NuGet packages are being installed. The build will be available once package installation is complete. + \ No newline at end of file diff --git a/dev/VSIX/Extension/Cpp/Dev17/WindowsAppSDK.Cpp.Extension.Dev17.csproj b/dev/VSIX/Extension/Cpp/Dev17/WindowsAppSDK.Cpp.Extension.Dev17.csproj index 688c0f484e..3ce3830ff6 100644 --- a/dev/VSIX/Extension/Cpp/Dev17/WindowsAppSDK.Cpp.Extension.Dev17.csproj +++ b/dev/VSIX/Extension/Cpp/Dev17/WindowsAppSDK.Cpp.Extension.Dev17.csproj @@ -85,6 +85,7 @@ + True True diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs index 1167253e0d..2bc8160db7 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs @@ -467,5 +467,11 @@ internal static string _1054 { return ResourceManager.GetString("1054", resourceCulture); } } + + internal static string _1055 { + get { + return ResourceManager.GetString("1055", resourceCulture); + } + } } } diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.resx b/dev/VSIX/Extension/Cs/Common/VSPackage.resx index 7ceb082481..752f77313e 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.resx +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.resx @@ -252,4 +252,7 @@ No output information available. + + Build is currently disabled while NuGet packages are being installed. The build will be available once package installation is complete. + \ No newline at end of file diff --git a/dev/VSIX/Extension/Cs/Dev17/WindowsAppSDK.Cs.Extension.Dev17.csproj b/dev/VSIX/Extension/Cs/Dev17/WindowsAppSDK.Cs.Extension.Dev17.csproj index 39348a4bd3..6f8e6fafbc 100644 --- a/dev/VSIX/Extension/Cs/Dev17/WindowsAppSDK.Cs.Extension.Dev17.csproj +++ b/dev/VSIX/Extension/Cs/Dev17/WindowsAppSDK.Cs.Extension.Dev17.csproj @@ -62,6 +62,7 @@ + True True diff --git a/dev/VSIX/Shared/BuildGuard.cs b/dev/VSIX/Shared/BuildGuard.cs new file mode 100644 index 0000000000..bb9002b2a1 --- /dev/null +++ b/dev/VSIX/Shared/BuildGuard.cs @@ -0,0 +1,207 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License + +using System; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Imaging; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; + +#if CSHARP_EXTENSION +using Resources = WindowsAppSDK.Cs.Extension.Dev17.VSPackage; +#elif CPP_EXTENSION +using Resources = WindowsAppSDK.Cpp.Extension.Dev17.VSPackage; +#endif + +namespace WindowsAppSDK.TemplateUtilities +{ + internal sealed class BuildGuard : IVsUpdateSolutionEvents2, IVsInfoBarUIEvents, IDisposable + { + private IVsSolutionBuildManager2 _solutionBuildManager; + private uint _adviseCookie; + private bool _isBlocking; + private bool _isAdvised; + private bool _disposed; + private bool _infoBarShown; + private IVsInfoBarUIElement _infoBarUIElement; + + public void DisableBuilds() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (_isBlocking) + { + return; + } + + _solutionBuildManager = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager2; + if (_solutionBuildManager == null) + { + System.Diagnostics.Debug.WriteLine("Warning: Could not obtain IVsSolutionBuildManager2 service."); + return; + } + + int hr = _solutionBuildManager.AdviseUpdateSolutionEvents(this, out _adviseCookie); + if (hr == VSConstants.S_OK) + { + _isAdvised = true; + _isBlocking = true; + _infoBarShown = false; + } + } + + public void EnableBuilds() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + _isBlocking = false; + + DismissInfoBar(); + + if (_isAdvised && _solutionBuildManager != null) + { + _solutionBuildManager.UnadviseUpdateSolutionEvents(_adviseCookie); + _isAdvised = false; + } + } + + public int UpdateSolution_Begin(ref int pfCancelUpdate) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (_isBlocking) + { + pfCancelUpdate = 1; + ShowInfoBar(); + } + + return VSConstants.S_OK; + } + + private void ShowInfoBar() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (_infoBarShown) + { + return; + } + + _infoBarShown = true; + + try + { + var infoBarModel = new InfoBarModel( + textSpans: new[] + { + new InfoBarTextSpan(Resources._1055) + }, + image: KnownMonikers.StatusInformation, + isCloseButtonVisible: true); + + IVsInfoBarUIFactory infoBarUIFactory = ServiceProvider.GlobalProvider.GetService(typeof(SVsInfoBarUIFactory)) as IVsInfoBarUIFactory; + if (infoBarUIFactory == null) + { + return; + } + + _infoBarUIElement = infoBarUIFactory.CreateInfoBar(infoBarModel); + if (_infoBarUIElement == null) + { + return; + } + + _infoBarUIElement.Advise(this, out _); + + IVsShell shell = ServiceProvider.GlobalProvider.GetService(typeof(SVsShell)) as IVsShell; + if (shell == null) + { + return; + } + + shell.GetProperty((int)__VSSPROPID7.VSSPROPID_MainWindowInfoBarHost, out object infoBarHostObj); + if (infoBarHostObj is IVsInfoBarHost infoBarHost) + { + infoBarHost.AddInfoBar(_infoBarUIElement); + } + } + catch (Exception) + { + // Best-effort: if we can't show the InfoBar, the build is still canceled + } + } + + private void DismissInfoBar() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (_infoBarUIElement != null) + { + _infoBarUIElement.Close(); + _infoBarUIElement = null; + } + } + + public void OnActionItemClicked(IVsInfoBarUIElement infoBarUIElement, IVsInfoBarActionItem actionItem) + { + } + + public void OnClosed(IVsInfoBarUIElement infoBarUIElement) + { + _infoBarUIElement = null; + } + + public int UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) + { + return VSConstants.S_OK; + } + + public int UpdateSolution_StartUpdate(ref int pfCancelUpdate) + { + if (_isBlocking) + { + pfCancelUpdate = 1; + } + + return VSConstants.S_OK; + } + + public int UpdateSolution_Cancel() + { + return VSConstants.S_OK; + } + + public int OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + { + return VSConstants.S_OK; + } + + public int UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, ref int pfCancel) + { + if (_isBlocking) + { + pfCancel = 1; + } + + return VSConstants.S_OK; + } + + public int UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, int fSuccess, int fCancel) + { + return VSConstants.S_OK; + } + + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + ThreadHelper.JoinableTaskFactory.Run(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + EnableBuilds(); + }); + } + } + } +} diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index 58f11c0d46..9025dc7107 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -43,6 +43,7 @@ public partial class NuGetPackageInstaller : IWizard private IVsNuGetProjectUpdateEvents _nugetProjectUpdateEvents; private IVsThreadedWaitDialog2 _waitDialog; private Dictionary _failedPackageExceptions = new Dictionary(); + private BuildGuard _buildGuard = new BuildGuard(); public void RunStarted(object automationObject, Dictionary replacementsDictionary, WizardRunKind runKind, object[] customParams) { @@ -96,30 +97,40 @@ await ThreadHelper.JoinableTaskFactory.RunAsync(async () => await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); int canceled = 0; // Initialize as not canceled - // Start the package installation task but do not await it here - var installationTask = StartInstallationAsync(); + _buildGuard.DisableBuilds(); - // Start the threaded wait dialog - if (_waitDialog != null) + try { - _waitDialog.StartWaitDialog(null, Resources._1044, null, null, Resources._1045, 0, false, true); - } + // Start the package installation task but do not await it here + var installationTask = StartInstallationAsync(); - // Now await the installation task to complete - await installationTask; + // Start the threaded wait dialog + if (_waitDialog != null) + { + _waitDialog.StartWaitDialog(null, Resources._1044, null, null, Resources._1045, 0, false, true); + } - // Once the installation is complete, end the wait dialog - if (_waitDialog != null) - { - _waitDialog.EndWaitDialog(out canceled); - } + // Now await the installation task to complete + await installationTask; - // If _waitDialog is null, canceled remains 0 (not canceled) - // Check if the process was canceled before proceeding - if (canceled == 0) // If not canceled, finalize the process + // Once the installation is complete, end the wait dialog + if (_waitDialog != null) + { + _waitDialog.EndWaitDialog(out canceled); + } + + // If _waitDialog is null, canceled remains 0 (not canceled) + // Check if the process was canceled before proceeding + if (canceled == 0) // If not canceled, finalize the process + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + SaveAllProjects(); + } + } + finally { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - SaveAllProjects(); + _buildGuard.EnableBuilds(); } }); } @@ -187,6 +198,10 @@ public void RunFinished() Guid _projectGuid = GetProjectGuid(_project); if (_projectGuid.Equals(SolutionVCProjectGuid)) { + // For C++ projects, installation is synchronous so builds are already + // re-enabled by the finally block. Dispose as a safety net. + _buildGuard.Dispose(); + if (_failedPackageExceptions.Count > 0) { var errorMessage = CreateErrorMessage(ErrorMessageFormat.MessageBox); From 32a069b2a158e7431fa05633c630d0fd96dc79ab Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Tue, 17 Mar 2026 13:47:06 -0700 Subject: [PATCH 02/26] Successful flow with Single-project, C# wapproj flow disabled all builds --- .../Standalone/source.extension.vsixmanifest | 2 +- .../Standalone/source.extension.vsixmanifest | 2 +- dev/VSIX/Shared/WizardImplementation.cs | 83 ++++++++++++++++++- 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/dev/VSIX/Extension/Cpp/Dev17/Standalone/source.extension.vsixmanifest b/dev/VSIX/Extension/Cpp/Dev17/Standalone/source.extension.vsixmanifest index a8857237a5..a35cb03096 100644 --- a/dev/VSIX/Extension/Cpp/Dev17/Standalone/source.extension.vsixmanifest +++ b/dev/VSIX/Extension/Cpp/Dev17/Standalone/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + Windows App SDK C++ VS Templates The Microsoft Windows App SDK Visual Studio extension adds C++ project and item templates to support building Windows apps and components in VS 2022-2026. https://github.com/microsoft/WindowsAppSDK/ diff --git a/dev/VSIX/Extension/Cs/Dev17/Standalone/source.extension.vsixmanifest b/dev/VSIX/Extension/Cs/Dev17/Standalone/source.extension.vsixmanifest index bcda1fcae6..8cef623698 100644 --- a/dev/VSIX/Extension/Cs/Dev17/Standalone/source.extension.vsixmanifest +++ b/dev/VSIX/Extension/Cs/Dev17/Standalone/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + Windows App SDK C# VS Templates The Microsoft Windows App SDK Visual Studio extension adds C# project and item templates to support building Windows apps and components in VS 2022-2026. https://github.com/microsoft/WindowsAppSDK/ diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index 9025dc7107..24e0667de0 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -10,6 +10,7 @@ using EnvDTE; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Imaging; +using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TemplateWizard; @@ -34,7 +35,7 @@ public enum ErrorMessageFormat InfoBar } - public partial class NuGetPackageInstaller : IWizard + public partial class NuGetPackageInstaller : IWizard, IVsSolutionEvents { internal static Guid SolutionVCProjectGuid = new Guid("8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"); private Project _project; @@ -44,6 +45,8 @@ public partial class NuGetPackageInstaller : IWizard private IVsThreadedWaitDialog2 _waitDialog; private Dictionary _failedPackageExceptions = new Dictionary(); private BuildGuard _buildGuard = new BuildGuard(); + private IVsSolution _solution; + private uint _solutionEventsCookie; public void RunStarted(object automationObject, Dictionary replacementsDictionary, WizardRunKind runKind, object[] customParams) { @@ -74,6 +77,17 @@ public void RunStarted(object automationObject, Dictionary repla { _nuGetPackages = packages.Split(';').Where(p => !string.IsNullOrEmpty(p)); } + + if (_nuGetPackages != null && _nuGetPackages.Any()) + { + _solution = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolution)) as IVsSolution; + if (_solution != null) + { + _solution.AdviseSolutionEvents(this, out _solutionEventsCookie); + } + + _buildGuard.DisableBuilds(); + } } public void ProjectFinishedGenerating(Project project) @@ -97,8 +111,6 @@ await ThreadHelper.JoinableTaskFactory.RunAsync(async () => await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); int canceled = 0; // Initialize as not canceled - _buildGuard.DisableBuilds(); - try { // Start the package installation task but do not await it here @@ -130,6 +142,7 @@ await ThreadHelper.JoinableTaskFactory.RunAsync(async () => finally { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + UnadviseSolutionEvents(); _buildGuard.EnableBuilds(); } }); @@ -200,6 +213,7 @@ public void RunFinished() { // For C++ projects, installation is synchronous so builds are already // re-enabled by the finally block. Dispose as a safety net. + UnadviseSolutionEvents(); _buildGuard.Dispose(); if (_failedPackageExceptions.Count > 0) @@ -227,6 +241,69 @@ private void ShowOutputWindow(string errorMessage) OutputWindowHelper.ShowMessageInOutputWindow(errorMessage); } + private void UnadviseSolutionEvents() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (_solution != null && _solutionEventsCookie != 0) + { + _solution.UnadviseSolutionEvents(_solutionEventsCookie); + _solutionEventsCookie = 0; + } + } + + public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) + { + ThreadHelper.ThrowIfNotOnUIThread(); + _buildGuard.DisableBuilds(); + return VSConstants.S_OK; + } + + public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) + { + return VSConstants.S_OK; + } + + public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) + { + return VSConstants.S_OK; + } + + public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) + { + return VSConstants.S_OK; + } + + public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) + { + return VSConstants.S_OK; + } + + public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) + { + return VSConstants.S_OK; + } + + public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution) + { + return VSConstants.S_OK; + } + + public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) + { + return VSConstants.S_OK; + } + + public int OnBeforeCloseSolution(object pUnkReserved) + { + return VSConstants.S_OK; + } + + public int OnAfterCloseSolution(object pUnkReserved) + { + return VSConstants.S_OK; + } + private void SaveAllProjects() { ThreadHelper.ThrowIfNotOnUIThread("SaveAllProjects must be called on the UI thread."); From 6f89e42ea56131aa426dd32221af26dee0263be9 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Tue, 17 Mar 2026 14:28:32 -0700 Subject: [PATCH 03/26] Adds a check within build button handler to release build guard, after a number of manual tests, works with single-project C# and C#/C++ wapproj (not C# unit test) --- dev/VSIX/Shared/BuildGuard.cs | 24 ++++++++++++++++++++++++ dev/VSIX/Shared/WizardImplementation.cs | 24 +++++++++++++++++++----- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/dev/VSIX/Shared/BuildGuard.cs b/dev/VSIX/Shared/BuildGuard.cs index bb9002b2a1..f224b88e78 100644 --- a/dev/VSIX/Shared/BuildGuard.cs +++ b/dev/VSIX/Shared/BuildGuard.cs @@ -24,6 +24,14 @@ internal sealed class BuildGuard : IVsUpdateSolutionEvents2, IVsInfoBarUIEvents, private bool _disposed; private bool _infoBarShown; private IVsInfoBarUIElement _infoBarUIElement; + private Func _shouldRelease; + + public bool IsBlocking => _isBlocking; + + public void SetReleaseCondition(Func condition) + { + _shouldRelease = condition; + } public void DisableBuilds() { @@ -71,6 +79,12 @@ public int UpdateSolution_Begin(ref int pfCancelUpdate) if (_isBlocking) { + if (_shouldRelease != null && _shouldRelease()) + { + EnableBuilds(); + return VSConstants.S_OK; + } + pfCancelUpdate = 1; ShowInfoBar(); } @@ -160,6 +174,11 @@ public int UpdateSolution_StartUpdate(ref int pfCancelUpdate) { if (_isBlocking) { + if (_shouldRelease != null && _shouldRelease()) + { + return VSConstants.S_OK; + } + pfCancelUpdate = 1; } @@ -180,6 +199,11 @@ public int UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCf { if (_isBlocking) { + if (_shouldRelease != null && _shouldRelease()) + { + return VSConstants.S_OK; + } + pfCancel = 1; } diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index 24e0667de0..8f3a1f1f8b 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -44,7 +44,8 @@ public partial class NuGetPackageInstaller : IWizard, IVsSolutionEvents private IVsNuGetProjectUpdateEvents _nugetProjectUpdateEvents; private IVsThreadedWaitDialog2 _waitDialog; private Dictionary _failedPackageExceptions = new Dictionary(); - private BuildGuard _buildGuard = new BuildGuard(); + private static BuildGuard s_buildGuard = new BuildGuard(); + private static volatile bool s_installationComplete; private IVsSolution _solution; private uint _solutionEventsCookie; @@ -80,13 +81,21 @@ public void RunStarted(object automationObject, Dictionary repla if (_nuGetPackages != null && _nuGetPackages.Any()) { + if (s_installationComplete) + { + s_buildGuard = new BuildGuard(); + s_installationComplete = false; + } + + s_buildGuard.SetReleaseCondition(() => s_installationComplete); + _solution = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolution)) as IVsSolution; if (_solution != null) { _solution.AdviseSolutionEvents(this, out _solutionEventsCookie); } - _buildGuard.DisableBuilds(); + s_buildGuard.DisableBuilds(); } } @@ -141,9 +150,10 @@ await ThreadHelper.JoinableTaskFactory.RunAsync(async () => } finally { + s_installationComplete = true; await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); UnadviseSolutionEvents(); - _buildGuard.EnableBuilds(); + s_buildGuard.EnableBuilds(); } }); } @@ -214,7 +224,7 @@ public void RunFinished() // For C++ projects, installation is synchronous so builds are already // re-enabled by the finally block. Dispose as a safety net. UnadviseSolutionEvents(); - _buildGuard.Dispose(); + s_buildGuard.Dispose(); if (_failedPackageExceptions.Count > 0) { @@ -255,7 +265,11 @@ private void UnadviseSolutionEvents() public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) { ThreadHelper.ThrowIfNotOnUIThread(); - _buildGuard.DisableBuilds(); + s_buildGuard.DisableBuilds(); + if (s_buildGuard.IsBlocking) + { + UnadviseSolutionEvents(); + } return VSConstants.S_OK; } From 1f9fc8d8f7b0a1aaf2eb5399b5db2b16636be0a7 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Tue, 17 Mar 2026 14:50:16 -0700 Subject: [PATCH 04/26] Add test explorer as a potential build command source for C# unit test templates --- dev/VSIX/Shared/BuildGuard.cs | 59 ++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/dev/VSIX/Shared/BuildGuard.cs b/dev/VSIX/Shared/BuildGuard.cs index f224b88e78..2fc6708def 100644 --- a/dev/VSIX/Shared/BuildGuard.cs +++ b/dev/VSIX/Shared/BuildGuard.cs @@ -15,12 +15,15 @@ namespace WindowsAppSDK.TemplateUtilities { - internal sealed class BuildGuard : IVsUpdateSolutionEvents2, IVsInfoBarUIEvents, IDisposable + internal sealed class BuildGuard : IVsUpdateSolutionEvents2, IVsUpdateSolutionEvents4, IVsInfoBarUIEvents, IDisposable { private IVsSolutionBuildManager2 _solutionBuildManager; + private IVsSolutionBuildManager5 _solutionBuildManager5; private uint _adviseCookie; + private uint _adviseCookie4; private bool _isBlocking; private bool _isAdvised; + private bool _isAdvised4; private bool _disposed; private bool _infoBarShown; private IVsInfoBarUIElement _infoBarUIElement; @@ -56,6 +59,13 @@ public void DisableBuilds() _isBlocking = true; _infoBarShown = false; } + + _solutionBuildManager5 = _solutionBuildManager as IVsSolutionBuildManager5; + if (_solutionBuildManager5 != null) + { + _solutionBuildManager5.AdviseUpdateSolutionEvents4(this, out _adviseCookie4); + _isAdvised4 = true; + } } public void EnableBuilds() @@ -71,6 +81,12 @@ public void EnableBuilds() _solutionBuildManager.UnadviseUpdateSolutionEvents(_adviseCookie); _isAdvised = false; } + + if (_isAdvised4 && _solutionBuildManager5 != null) + { + _solutionBuildManager5.UnadviseUpdateSolutionEvents4(_adviseCookie4); + _isAdvised4 = false; + } } public int UpdateSolution_Begin(ref int pfCancelUpdate) @@ -215,6 +231,47 @@ public int UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg return VSConstants.S_OK; } + public void UpdateSolution_QueryDelayFirstUpdateAction(out int pfDelay) + { + pfDelay = 0; + + if (_isBlocking) + { + if (_shouldRelease != null && _shouldRelease()) + { + ThreadHelper.ThrowIfNotOnUIThread(); + EnableBuilds(); + return; + } + + pfDelay = 1; + } + } + + public void UpdateSolution_BeginFirstUpdateAction() + { + } + + public void UpdateSolution_EndLastUpdateAction() + { + } + + public void UpdateSolution_BeginUpdateAction(uint dwAction) + { + } + + public void UpdateSolution_EndUpdateAction(uint dwAction) + { + } + + public void OnActiveProjectCfgChangeBatchBegin() + { + } + + public void OnActiveProjectCfgChangeBatchEnd() + { + } + public void Dispose() { if (!_disposed) From e0d60665f27369a286d0c7c5d0cd4525f5d06a4e Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Tue, 17 Mar 2026 16:39:53 -0700 Subject: [PATCH 05/26] Add auto-download setting check for smooth exit if disabled --- .../Cpp/Common/VSPackage.Designer.cs | 6 ++ dev/VSIX/Extension/Cpp/Common/VSPackage.resx | 3 + .../Extension/Cs/Common/VSPackage.Designer.cs | 6 ++ dev/VSIX/Extension/Cs/Common/VSPackage.resx | 3 + dev/VSIX/Shared/BuildGuard.cs | 21 +++++-- dev/VSIX/Shared/WizardImplementation.cs | 63 +++++++++++++++++++ 6 files changed, 98 insertions(+), 4 deletions(-) diff --git a/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs b/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs index 159e20e6f5..78f6ac2a85 100644 --- a/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs +++ b/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs @@ -477,5 +477,11 @@ internal static string _1055 { return ResourceManager.GetString("1055", resourceCulture); } } + + internal static string _1056 { + get { + return ResourceManager.GetString("1056", resourceCulture); + } + } } } diff --git a/dev/VSIX/Extension/Cpp/Common/VSPackage.resx b/dev/VSIX/Extension/Cpp/Common/VSPackage.resx index 76f9fe90a3..c15ea7f59f 100644 --- a/dev/VSIX/Extension/Cpp/Common/VSPackage.resx +++ b/dev/VSIX/Extension/Cpp/Common/VSPackage.resx @@ -259,4 +259,7 @@ For more details on the error, check the General tab in the Output window. Build is currently disabled while NuGet packages are being installed. The build will be available once package installation is complete. + + NuGet package restore is disabled. Enable "Allow NuGet to download missing packages" in Tools > Options > NuGet Package Manager to automatically install required packages. + \ No newline at end of file diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs index 2bc8160db7..90cad59a8d 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs @@ -473,5 +473,11 @@ internal static string _1055 { return ResourceManager.GetString("1055", resourceCulture); } } + + internal static string _1056 { + get { + return ResourceManager.GetString("1056", resourceCulture); + } + } } } diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.resx b/dev/VSIX/Extension/Cs/Common/VSPackage.resx index 752f77313e..0498ed028b 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.resx +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.resx @@ -255,4 +255,7 @@ Build is currently disabled while NuGet packages are being installed. The build will be available once package installation is complete. + + NuGet package restore is disabled. Enable "Allow NuGet to download missing packages" in Tools > Options > NuGet Package Manager to automatically install required packages. + \ No newline at end of file diff --git a/dev/VSIX/Shared/BuildGuard.cs b/dev/VSIX/Shared/BuildGuard.cs index 2fc6708def..11456e6e7f 100644 --- a/dev/VSIX/Shared/BuildGuard.cs +++ b/dev/VSIX/Shared/BuildGuard.cs @@ -101,7 +101,13 @@ public int UpdateSolution_Begin(ref int pfCancelUpdate) return VSConstants.S_OK; } - pfCancelUpdate = 1; + // When Events4 is available, delay via QueryDelayFirstUpdateAction + // instead of canceling here (canceling triggers "build errors" dialogs). + if (!_isAdvised4) + { + pfCancelUpdate = 1; + } + ShowInfoBar(); } @@ -195,7 +201,10 @@ public int UpdateSolution_StartUpdate(ref int pfCancelUpdate) return VSConstants.S_OK; } - pfCancelUpdate = 1; + if (!_isAdvised4) + { + pfCancelUpdate = 1; + } } return VSConstants.S_OK; @@ -220,7 +229,10 @@ public int UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCf return VSConstants.S_OK; } - pfCancel = 1; + if (!_isAdvised4) + { + pfCancel = 1; + } } return VSConstants.S_OK; @@ -233,13 +245,14 @@ public int UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg public void UpdateSolution_QueryDelayFirstUpdateAction(out int pfDelay) { + ThreadHelper.ThrowIfNotOnUIThread(); + pfDelay = 0; if (_isBlocking) { if (_shouldRelease != null && _shouldRelease()) { - ThreadHelper.ThrowIfNotOnUIThread(); EnableBuilds(); return; } diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index 8f3a1f1f8b..9a6381d5ad 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Resources; using System.Threading.Tasks; using System.Windows.Forms; +using System.Xml.Linq; using EnvDTE; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Imaging; @@ -48,6 +50,7 @@ public partial class NuGetPackageInstaller : IWizard, IVsSolutionEvents private static volatile bool s_installationComplete; private IVsSolution _solution; private uint _solutionEventsCookie; + private bool _packageRestoreEnabled = true; public void RunStarted(object automationObject, Dictionary replacementsDictionary, WizardRunKind runKind, object[] customParams) { @@ -81,6 +84,15 @@ public void RunStarted(object automationObject, Dictionary repla if (_nuGetPackages != null && _nuGetPackages.Any()) { + _packageRestoreEnabled = IsNuGetPackageRestoreEnabled(); + + if (!_packageRestoreEnabled) + { + LogError("NuGet package restore is disabled. Skipping automatic package installation."); + _ = DisplayInfoBarAsync(Resources._1056); + return; + } + if (s_installationComplete) { s_buildGuard = new BuildGuard(); @@ -160,6 +172,15 @@ await ThreadHelper.JoinableTaskFactory.RunAsync(async () => private async Task StartInstallationAsync() { + if (!_packageRestoreEnabled) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + LogError("NuGet package restore is disabled. Skipping package installation."); + var packageNames = string.Join(", ", _nuGetPackages); + _failedPackageExceptions[packageNames] = new InvalidOperationException("NuGet package restore is disabled."); + return; + } + if (_componentModel == null) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); @@ -251,6 +272,48 @@ private void ShowOutputWindow(string errorMessage) OutputWindowHelper.ShowMessageInOutputWindow(errorMessage); } + private static bool IsNuGetPackageRestoreEnabled() + { + try + { + string configPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "NuGet", + "NuGet.Config"); + + if (!File.Exists(configPath)) + { + return true; + } + + var doc = XDocument.Load(configPath); + var packageRestore = doc.Descendants("packageRestore").FirstOrDefault(); + if (packageRestore == null) + { + return true; + } + + var enabledElement = packageRestore.Elements("add") + .FirstOrDefault(e => string.Equals( + (string)e.Attribute("key"), "enabled", StringComparison.OrdinalIgnoreCase)); + + if (enabledElement != null) + { + string value = (string)enabledElement.Attribute("value"); + if (string.Equals(value, "False", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return true; + } + catch (Exception) + { + return true; + } + } + private void UnadviseSolutionEvents() { ThreadHelper.ThrowIfNotOnUIThread(); From f247d50c402062ba96dab2432e60ea46b1a3584c Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Tue, 17 Mar 2026 16:57:33 -0700 Subject: [PATCH 06/26] Add checks for if NuGetRestore is disabled --- dev/VSIX/Shared/WizardImplementation.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index 9a6381d5ad..dfc0c1fb21 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -213,6 +213,14 @@ private async Task StartInstallationAsync() catch (Exception ex) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + if (IsNuGetRestoreDisabledException(ex)) + { + LogError($"NuGet restore is disabled. Error: {ex.Message}"); + _ = DisplayInfoBarAsync(Resources._1056); + return; + } + LogError($"Failed to install NuGet package: {packageId}. Error: {ex.Message}"); _failedPackageExceptions[packageId] = ex; } @@ -314,6 +322,22 @@ private static bool IsNuGetPackageRestoreEnabled() } } + private static bool IsNuGetRestoreDisabledException(Exception ex) + { + for (var current = ex; current != null; current = current.InnerException) + { + if (current.Message != null && + (current.Message.IndexOf("NuGet restore is currently disabled", StringComparison.OrdinalIgnoreCase) >= 0 || + current.Message.IndexOf("package restore is disabled", StringComparison.OrdinalIgnoreCase) >= 0 || + current.Message.IndexOf("EnableNuGetPackageRestore", StringComparison.OrdinalIgnoreCase) >= 0)) + { + return true; + } + } + + return false; + } + private void UnadviseSolutionEvents() { ThreadHelper.ThrowIfNotOnUIThread(); From c596799b8fb150477435734a04096c839437ade4 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Tue, 17 Mar 2026 17:38:42 -0700 Subject: [PATCH 07/26] Add PackageReference fallback for C# projects --- dev/VSIX/Shared/WizardImplementation.cs | 62 +++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index dfc0c1fb21..53ebebc7c5 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -116,6 +116,16 @@ public void ProjectFinishedGenerating(Project project) ThreadHelper.ThrowIfNotOnUIThread(); _project = project; Guid _projectGuid = GetProjectGuid(project); + + if (!_packageRestoreEnabled && _nuGetPackages != null && _nuGetPackages.Any()) + { + if (!_projectGuid.Equals(SolutionVCProjectGuid)) + { + AddPackageReferencesToProject(); + } + return; + } + if (_projectGuid.Equals(SolutionVCProjectGuid)) { ThreadHelper.JoinableTaskFactory.Run(async () => @@ -217,6 +227,7 @@ private async Task StartInstallationAsync() if (IsNuGetRestoreDisabledException(ex)) { LogError($"NuGet restore is disabled. Error: {ex.Message}"); + AddPackageReferencesToProject(); _ = DisplayInfoBarAsync(Resources._1056); return; } @@ -280,6 +291,57 @@ private void ShowOutputWindow(string errorMessage) OutputWindowHelper.ShowMessageInOutputWindow(errorMessage); } + private void AddPackageReferencesToProject() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + try + { + if (_project == null || _nuGetPackages == null) + { + return; + } + + string projectPath = _project.FullName; + if (!File.Exists(projectPath)) + { + return; + } + + var doc = XDocument.Load(projectPath); + XNamespace ns = doc.Root.GetDefaultNamespace(); + + var itemGroup = doc.Root.Elements(ns + "ItemGroup") + .FirstOrDefault(ig => ig.Elements(ns + "PackageReference").Any()); + + if (itemGroup == null) + { + itemGroup = new XElement(ns + "ItemGroup"); + doc.Root.Add(itemGroup); + } + + foreach (var packageId in _nuGetPackages) + { + bool alreadyExists = itemGroup.Elements(ns + "PackageReference") + .Any(e => string.Equals( + (string)e.Attribute("Include"), packageId, StringComparison.OrdinalIgnoreCase)); + + if (!alreadyExists) + { + itemGroup.Add(new XElement(ns + "PackageReference", + new XAttribute("Include", packageId), + new XAttribute("Version", "*"))); + } + } + + doc.Save(projectPath); + } + catch (Exception ex) + { + LogError($"Failed to add package references to project file: {ex.Message}"); + } + } + private static bool IsNuGetPackageRestoreEnabled() { try From 65c354dc92912507f20d77d4e43923ca13389c47 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Thu, 19 Mar 2026 14:57:14 -0700 Subject: [PATCH 08/26] Attempt to add an automatic package download not enabled dialog --- dev/VSIX/Extension/Cpp/Common/VSPackage.resx | 4 +- dev/VSIX/Extension/Cs/Common/VSPackage.resx | 4 +- dev/VSIX/Shared/BuildGuard.cs | 8 +- dev/VSIX/Shared/WizardImplementation.cs | 125 +++++++++++++++---- 4 files changed, 110 insertions(+), 31 deletions(-) diff --git a/dev/VSIX/Extension/Cpp/Common/VSPackage.resx b/dev/VSIX/Extension/Cpp/Common/VSPackage.resx index c15ea7f59f..857ff5ba4d 100644 --- a/dev/VSIX/Extension/Cpp/Common/VSPackage.resx +++ b/dev/VSIX/Extension/Cpp/Common/VSPackage.resx @@ -257,9 +257,9 @@ For more details on the error, check the General tab in the Output window.No output information available. - Build is currently disabled while NuGet packages are being installed. The build will be available once package installation is complete. + [{0}] Build is currently disabled while NuGet packages are being installed: {1}. The build will be available once package installation is complete. - NuGet package restore is disabled. Enable "Allow NuGet to download missing packages" in Tools > Options > NuGet Package Manager to automatically install required packages. + [{0}] NuGet package restore is disabled. Unable to install: {1}. Enable "Allow NuGet to download missing packages" in Tools > Options > NuGet Package Manager, then restore the solution. \ No newline at end of file diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.resx b/dev/VSIX/Extension/Cs/Common/VSPackage.resx index 0498ed028b..773b9e7dfe 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.resx +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.resx @@ -253,9 +253,9 @@ No output information available. - Build is currently disabled while NuGet packages are being installed. The build will be available once package installation is complete. + [{0}] Build is currently disabled while NuGet packages are being installed: {1}. The build will be available once package installation is complete. - NuGet package restore is disabled. Enable "Allow NuGet to download missing packages" in Tools > Options > NuGet Package Manager to automatically install required packages. + [{0}] NuGet package restore is disabled. Unable to install: {1}. Enable "Allow NuGet to download missing packages" in Tools > Options > NuGet Package Manager, then restore the solution. \ No newline at end of file diff --git a/dev/VSIX/Shared/BuildGuard.cs b/dev/VSIX/Shared/BuildGuard.cs index 11456e6e7f..edad927bf4 100644 --- a/dev/VSIX/Shared/BuildGuard.cs +++ b/dev/VSIX/Shared/BuildGuard.cs @@ -28,6 +28,7 @@ internal sealed class BuildGuard : IVsUpdateSolutionEvents2, IVsUpdateSolutionEv private bool _infoBarShown; private IVsInfoBarUIElement _infoBarUIElement; private Func _shouldRelease; + private string _infoBarMessage; public bool IsBlocking => _isBlocking; @@ -36,6 +37,11 @@ public void SetReleaseCondition(Func condition) _shouldRelease = condition; } + public void SetInfoBarMessage(string message) + { + _infoBarMessage = message; + } + public void DisableBuilds() { ThreadHelper.ThrowIfNotOnUIThread(); @@ -130,7 +136,7 @@ private void ShowInfoBar() var infoBarModel = new InfoBarModel( textSpans: new[] { - new InfoBarTextSpan(Resources._1055) + new InfoBarTextSpan(_infoBarMessage ?? Resources._1055) }, image: KnownMonikers.StatusInformation, isCloseButtonVisible: true); diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index 53ebebc7c5..fd60f9514d 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -18,6 +18,8 @@ using Microsoft.VisualStudio.TemplateWizard; using Microsoft.VisualStudio.Threading; using NuGet.VisualStudio; +using Microsoft.VisualStudio.OLE.Interop; + // Although the strings are the same in the wizard for both extensions, // they are included with both their respective VSPackages. @@ -45,6 +47,9 @@ public partial class NuGetPackageInstaller : IWizard, IVsSolutionEvents private IEnumerable _nuGetPackages; private IVsNuGetProjectUpdateEvents _nugetProjectUpdateEvents; private IVsThreadedWaitDialog2 _waitDialog; + private IVsShell _shell; + private IVsInfoBarHost _infoBarHost; + private IVsInfoBarUIFactory _infoBarUIFactory; private Dictionary _failedPackageExceptions = new Dictionary(); private static BuildGuard s_buildGuard = new BuildGuard(); private static volatile bool s_installationComplete; @@ -68,6 +73,27 @@ public void RunStarted(object automationObject, Dictionary repla System.Diagnostics.Debug.WriteLine("Warning: Could not obtain IVsThreadedWaitDialog2 service."); } + _shell = ServiceProvider.GlobalProvider.GetService(typeof(SVsShell)) as IVsShell; + if (_shell == null) + { + System.Diagnostics.Debug.WriteLine("Warning: Could not obtain IVsShell service."); + } + else + { + _shell.GetProperty((int)__VSSPROPID7.VSSPROPID_MainWindowInfoBarHost, out object infoBarHostObj); + _infoBarHost = infoBarHostObj as IVsInfoBarHost; + if (_infoBarHost == null) + { + System.Diagnostics.Debug.WriteLine("Warning: Could not obtain IVsInfoBarHost from IVsShell."); + } + } + + _infoBarUIFactory = ServiceProvider.GlobalProvider.GetService(typeof(SVsInfoBarUIFactory)) as IVsInfoBarUIFactory; + if (_infoBarUIFactory == null) + { + System.Diagnostics.Debug.WriteLine("Warning: Could not obtain IVsInfoBarUIFactory service."); + } + if (_componentModel != null) { _nugetProjectUpdateEvents = _componentModel.GetService(); @@ -76,6 +102,7 @@ public void RunStarted(object automationObject, Dictionary repla _nugetProjectUpdateEvents.SolutionRestoreFinished += OnSolutionRestoreFinished; } } + // Assuming package list is passed via a custom parameter in the .vstemplate file if (replacementsDictionary.TryGetValue("$NuGetPackages$", out string packages)) { @@ -89,7 +116,6 @@ public void RunStarted(object automationObject, Dictionary repla if (!_packageRestoreEnabled) { LogError("NuGet package restore is disabled. Skipping automatic package installation."); - _ = DisplayInfoBarAsync(Resources._1056); return; } @@ -117,12 +143,25 @@ public void ProjectFinishedGenerating(Project project) _project = project; Guid _projectGuid = GetProjectGuid(project); + if (_nuGetPackages != null && _nuGetPackages.Any()) + { + var projectName = _project?.Name ?? "Unknown Project"; + var packageNames = string.Join(", ", _nuGetPackages); + s_buildGuard.SetInfoBarMessage(string.Format(Resources._1055, projectName, packageNames)); + } + if (!_packageRestoreEnabled && _nuGetPackages != null && _nuGetPackages.Any()) { + // Write C# package references to the project so that auto-download can install packages + // after project generation completes. if (!_projectGuid.Equals(SolutionVCProjectGuid)) { AddPackageReferencesToProject(); } + + var projectName = _project?.Name ?? "Unknown Project"; + var packageNames = string.Join(", ", _nuGetPackages); + _ = DisplayInfoBarAsync(string.Format(Resources._1056, projectName, packageNames)); return; } @@ -228,7 +267,9 @@ private async Task StartInstallationAsync() { LogError($"NuGet restore is disabled. Error: {ex.Message}"); AddPackageReferencesToProject(); - _ = DisplayInfoBarAsync(Resources._1056); + var projectName = _project?.Name ?? "Unknown Project"; + var packageNames = string.Join(", ", _nuGetPackages); + _ = DisplayInfoBarAsync(string.Format(Resources._1056, projectName, packageNames)); return; } @@ -239,11 +280,25 @@ private async Task StartInstallationAsync() if (_failedPackageExceptions.Count > 0) { - // Build error message in the requested format - var errorMessage = CreateErrorMessage(ErrorMessageFormat.InfoBar); await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - LogError(errorMessage); - _ = DisplayInfoBarAsync(errorMessage); + + bool isRestoreDisabled = _failedPackageExceptions.Values + .Any(e => IsNuGetRestoreDisabledException(e)); + + if (isRestoreDisabled) + { + AddPackageReferencesToProject(); + var projectName = _project?.Name ?? "Unknown Project"; + var packageNames = string.Join(", ", _nuGetPackages); + _ = DisplayInfoBarAsync(string.Format(Resources._1056, projectName, packageNames)); + } + else + { + // Build error message in the requested format + var errorMessage = CreateErrorMessage(ErrorMessageFormat.InfoBar); + LogError(errorMessage); + _ = DisplayInfoBarAsync(errorMessage); + } } } @@ -268,6 +323,18 @@ public void RunFinished() if (_failedPackageExceptions.Count > 0) { + bool isRestoreDisabled = _failedPackageExceptions.Values + .Any(ex => IsNuGetRestoreDisabledException(ex)); + + if (isRestoreDisabled) + { + var projectName = _project?.Name ?? "Unknown Project"; + var packageNames = string.Join(", ", _nuGetPackages); + _ = DisplayInfoBarAsync(string.Format(Resources._1056, projectName, packageNames)); + ShowOutputWindow(CreateDetailedErrorMessage()); + return; + } + var errorMessage = CreateErrorMessage(ErrorMessageFormat.MessageBox); LogError(errorMessage); @@ -342,6 +409,8 @@ private void AddPackageReferencesToProject() } } + // NuGet package restore is enabled by default, so only return false if we can confirm it is disabled in + // the user's config. private static bool IsNuGetPackageRestoreEnabled() { try @@ -351,11 +420,13 @@ private static bool IsNuGetPackageRestoreEnabled() "NuGet", "NuGet.Config"); + // If no NuGet.config is provided, package restore is enabled by default if (!File.Exists(configPath)) { return true; } + // If package restore is not disabled, then it is enabled by default var doc = XDocument.Load(configPath); var packageRestore = doc.Descendants("packageRestore").FirstOrDefault(); if (packageRestore == null) @@ -370,6 +441,8 @@ private static bool IsNuGetPackageRestoreEnabled() if (enabledElement != null) { string value = (string)enabledElement.Attribute("value"); + + // Only explicit disabling of package restore should return false. if (string.Equals(value, "False", StringComparison.OrdinalIgnoreCase)) { return false; @@ -618,6 +691,23 @@ private void LogError(string message) }); } + private void ShowAutomaticPackageDownloadNotEnabledErrorDialog() + { + ThreadHelper.ThrowIfNotOnUIThread(); + var errorMessage = "Automatic NuGet package download is not enabled. The required packages for this project template cannot be installed automatically.\n\n" + + "To enable automatic package download, go to Tools > Options > NuGet Package Manager and check 'Allow NuGet to download missing packages'.\n\n" + + "After enabling this option, please close and reopen the solution, then the packages will be installed automatically."; + MessageBox.Show( + errorMessage, + "Automatic Package Download Disabled", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + // Also log to activity log + LogError("Automatic NuGet package download is disabled. User prompted to enable it."); + // Show in output window + ShowOutputWindow($"Automatic NuGet package download is disabled. User prompted to enable it.\n{errorMessage}"); + } + private void ShowLocalizationErrorDialog(MissingManifestResourceException ex) { ThreadHelper.ThrowIfNotOnUIThread(); @@ -656,23 +746,7 @@ private async Task DisplayInfoBarAsync(string errorMessage) infoBarUi.Advise(new NuGetInfoBarUIEvents(detailedErrorMessage), out uint _); - IVsShell shell = ServiceProvider.GlobalProvider.GetService(typeof(SVsShell)) as IVsShell; - if (shell == null) - { - LogError("Could not obtain IVsShell service"); - return; - } - - // Get the main window's InfoBar host using VSSPROPID_MainWindowInfoBarHost - object infoBarHostObj; - int hr = shell.GetProperty((int)__VSSPROPID7.VSSPROPID_MainWindowInfoBarHost, out infoBarHostObj); - if (!(infoBarHostObj is IVsInfoBarHost infoBarHost)) - { - LogError("Could not obtain IVsInfoBarHost service"); - return; - } - - infoBarHost.AddInfoBar(infoBarUi); + _infoBarHost.AddInfoBar(infoBarUi); } private IVsInfoBarUIElement CreateInfoBarUI(IVsInfoBar infoBar) @@ -684,14 +758,13 @@ private IVsInfoBarUIElement CreateInfoBarUI(IVsInfoBar infoBar) return null; } - IVsInfoBarUIFactory infoBarUIFactory = ServiceProvider.GlobalProvider.GetService(typeof(SVsInfoBarUIFactory)) as IVsInfoBarUIFactory; - if (infoBarUIFactory == null) + if (_infoBarUIFactory == null) { LogError("Could not obtain IVsInfoBarUIFactory service"); return null; } - return infoBarUIFactory.CreateInfoBar(infoBar); + return _infoBarUIFactory.CreateInfoBar(infoBar); } private IVsInfoBar CreateNuGetInfoBar(string message) From 23b44e08ed80cb065c5fe980f8891011b86abd7b Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Thu, 19 Mar 2026 17:01:06 -0700 Subject: [PATCH 09/26] C++ template shows infobar when auto download disabled --- dev/VSIX/Shared/BuildGuard.cs | 66 ++++++++++++++++++- dev/VSIX/Shared/WizardImplementation.cs | 88 ++++++++++++++++--------- 2 files changed, 120 insertions(+), 34 deletions(-) diff --git a/dev/VSIX/Shared/BuildGuard.cs b/dev/VSIX/Shared/BuildGuard.cs index edad927bf4..38726ca8a1 100644 --- a/dev/VSIX/Shared/BuildGuard.cs +++ b/dev/VSIX/Shared/BuildGuard.cs @@ -15,7 +15,7 @@ namespace WindowsAppSDK.TemplateUtilities { - internal sealed class BuildGuard : IVsUpdateSolutionEvents2, IVsUpdateSolutionEvents4, IVsInfoBarUIEvents, IDisposable + internal sealed class BuildGuard : IVsUpdateSolutionEvents2, IVsUpdateSolutionEvents4, IVsInfoBarUIEvents, IVsShellPropertyEvents, IDisposable { private IVsSolutionBuildManager2 _solutionBuildManager; private IVsSolutionBuildManager5 _solutionBuildManager5; @@ -29,6 +29,9 @@ internal sealed class BuildGuard : IVsUpdateSolutionEvents2, IVsUpdateSolutionEv private IVsInfoBarUIElement _infoBarUIElement; private Func _shouldRelease; private string _infoBarMessage; + private IVsShell _shell; + private uint _shellPropertyCookie; + private bool _isShellPropertyAdvised; public bool IsBlocking => _isBlocking; @@ -39,7 +42,12 @@ public void SetReleaseCondition(Func condition) public void SetInfoBarMessage(string message) { + ThreadHelper.ThrowIfNotOnUIThread(); _infoBarMessage = message; + if (_isBlocking) + { + ShowInfoBarWhenShellReady(); + } } public void DisableBuilds() @@ -80,6 +88,7 @@ public void EnableBuilds() _isBlocking = false; + UnadviseShellPropertyChanges(); DismissInfoBar(); if (_isAdvised && _solutionBuildManager != null) @@ -120,6 +129,59 @@ public int UpdateSolution_Begin(ref int pfCancelUpdate) return VSConstants.S_OK; } + private void ShowInfoBarWhenShellReady() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (_shell == null) + { + _shell = ServiceProvider.GlobalProvider.GetService(typeof(SVsShell)) as IVsShell; + } + + if (_shell == null) + { + return; + } + + if (_shell.GetProperty((int)__VSSPROPID.VSSPROPID_Zombie, out object zombie) == VSConstants.S_OK + && zombie is bool isZombie && !isZombie) + { + ShowInfoBar(); + return; + } + + // Shell not yet initialized; defer until it is + if (!_isShellPropertyAdvised) + { + _shell.AdviseShellPropertyChanges(this, out _shellPropertyCookie); + _isShellPropertyAdvised = true; + } + } + + public int OnShellPropertyChange(int propid, object var) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (propid == (int)__VSSPROPID.VSSPROPID_Zombie && var is bool isZombie && !isZombie) + { + ShowInfoBar(); + UnadviseShellPropertyChanges(); + } + + return VSConstants.S_OK; + } + + private void UnadviseShellPropertyChanges() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (_isShellPropertyAdvised && _shell != null) + { + _shell.UnadviseShellPropertyChanges(_shellPropertyCookie); + _isShellPropertyAdvised = false; + } + } + private void ShowInfoBar() { ThreadHelper.ThrowIfNotOnUIThread(); @@ -155,7 +217,7 @@ private void ShowInfoBar() _infoBarUIElement.Advise(this, out _); - IVsShell shell = ServiceProvider.GlobalProvider.GetService(typeof(SVsShell)) as IVsShell; + IVsShell shell = _shell ?? ServiceProvider.GlobalProvider.GetService(typeof(SVsShell)) as IVsShell; if (shell == null) { return; diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index fd60f9514d..0585f29bcc 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -18,7 +18,6 @@ using Microsoft.VisualStudio.TemplateWizard; using Microsoft.VisualStudio.Threading; using NuGet.VisualStudio; -using Microsoft.VisualStudio.OLE.Interop; // Although the strings are the same in the wizard for both extensions, @@ -47,8 +46,6 @@ public partial class NuGetPackageInstaller : IWizard, IVsSolutionEvents private IEnumerable _nuGetPackages; private IVsNuGetProjectUpdateEvents _nugetProjectUpdateEvents; private IVsThreadedWaitDialog2 _waitDialog; - private IVsShell _shell; - private IVsInfoBarHost _infoBarHost; private IVsInfoBarUIFactory _infoBarUIFactory; private Dictionary _failedPackageExceptions = new Dictionary(); private static BuildGuard s_buildGuard = new BuildGuard(); @@ -73,21 +70,6 @@ public void RunStarted(object automationObject, Dictionary repla System.Diagnostics.Debug.WriteLine("Warning: Could not obtain IVsThreadedWaitDialog2 service."); } - _shell = ServiceProvider.GlobalProvider.GetService(typeof(SVsShell)) as IVsShell; - if (_shell == null) - { - System.Diagnostics.Debug.WriteLine("Warning: Could not obtain IVsShell service."); - } - else - { - _shell.GetProperty((int)__VSSPROPID7.VSSPROPID_MainWindowInfoBarHost, out object infoBarHostObj); - _infoBarHost = infoBarHostObj as IVsInfoBarHost; - if (_infoBarHost == null) - { - System.Diagnostics.Debug.WriteLine("Warning: Could not obtain IVsInfoBarHost from IVsShell."); - } - } - _infoBarUIFactory = ServiceProvider.GlobalProvider.GetService(typeof(SVsInfoBarUIFactory)) as IVsInfoBarUIFactory; if (_infoBarUIFactory == null) { @@ -423,6 +405,7 @@ private static bool IsNuGetPackageRestoreEnabled() // If no NuGet.config is provided, package restore is enabled by default if (!File.Exists(configPath)) { + System.Diagnostics.Debug.WriteLine($"NuGet.Config not found at: {configPath}. Assuming package restore is enabled."); return true; } @@ -431,6 +414,7 @@ private static bool IsNuGetPackageRestoreEnabled() var packageRestore = doc.Descendants("packageRestore").FirstOrDefault(); if (packageRestore == null) { + System.Diagnostics.Debug.WriteLine("No packageRestore section found in NuGet.Config. Assuming enabled."); return true; } @@ -445,14 +429,17 @@ private static bool IsNuGetPackageRestoreEnabled() // Only explicit disabling of package restore should return false. if (string.Equals(value, "False", StringComparison.OrdinalIgnoreCase)) { + System.Diagnostics.Debug.WriteLine("NuGet package restore is explicitly disabled in NuGet.Config."); return false; } } + System.Diagnostics.Debug.WriteLine("NuGet package restore is enabled (no explicit disable found)."); return true; } - catch (Exception) + catch (Exception ex) { + System.Diagnostics.Debug.WriteLine($"Error reading NuGet.Config: {ex.Message}. Assuming package restore is enabled."); return true; } } @@ -730,23 +717,60 @@ private void ShowLocalizationErrorDialog(MissingManifestResourceException ex) private async Task DisplayInfoBarAsync(string errorMessage) { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - var infoBar = CreateNuGetInfoBar(errorMessage); - var infoBarUi = CreateInfoBarUI(infoBar); + try + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var infoBar = CreateNuGetInfoBar(errorMessage); + var infoBarUi = CreateInfoBarUI(infoBar); - // Write detailed error message to output window - var detailedErrorMessage = CreateDetailedErrorMessage(); - ShowOutputWindow(detailedErrorMessage); + // Write detailed error message to output window + var detailedErrorMessage = CreateDetailedErrorMessage(); + ShowOutputWindow(detailedErrorMessage); - if (infoBarUi == null) - { - LogError("Could not create InfoBar UI element. Logged error message to output window."); - return; - } + if (infoBarUi == null) + { + ShowOutputWindow("[InfoBar] Failed: Could not create InfoBar UI element."); + return; + } - infoBarUi.Advise(new NuGetInfoBarUIEvents(detailedErrorMessage), out uint _); + infoBarUi.Advise(new NuGetInfoBarUIEvents(detailedErrorMessage), out uint _); - _infoBarHost.AddInfoBar(infoBarUi); + IVsShell shell = ServiceProvider.GlobalProvider.GetService(typeof(SVsShell)) as IVsShell; + if (shell == null) + { + ShowOutputWindow("[InfoBar] Failed: Could not obtain IVsShell service."); + return; + } + + // The main window InfoBar host may not be available immediately + // during template wizard execution while the main window initializes. + IVsInfoBarHost infoBarHost = null; + for (int retry = 0; retry < 5; retry++) + { + shell.GetProperty((int)__VSSPROPID7.VSSPROPID_MainWindowInfoBarHost, out object infoBarHostObj); + if (infoBarHostObj is IVsInfoBarHost host) + { + infoBarHost = host; + break; + } + + await Task.Delay(1000); + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + } + + if (infoBarHost != null) + { + infoBarHost.AddInfoBar(infoBarUi); + } + else + { + ShowOutputWindow("[InfoBar] Failed: Main window InfoBar host not available after retries."); + } + } + catch (Exception ex) + { + ShowOutputWindow($"[InfoBar] Exception: {ex.Message}"); + } } private IVsInfoBarUIElement CreateInfoBarUI(IVsInfoBar infoBar) From 9ea5fafbd65895cf89863a95b7fdca1788ef2759 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Thu, 19 Mar 2026 17:05:03 -0700 Subject: [PATCH 10/26] Attempt to eliminate polling --- dev/VSIX/Shared/WizardImplementation.cs | 61 +++++++++++++++---------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index 0585f29bcc..eabb5d14ff 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -735,36 +735,31 @@ private async Task DisplayInfoBarAsync(string errorMessage) infoBarUi.Advise(new NuGetInfoBarUIEvents(detailedErrorMessage), out uint _); - IVsShell shell = ServiceProvider.GlobalProvider.GetService(typeof(SVsShell)) as IVsShell; - if (shell == null) + if (TryAddInfoBarToMainWindow(infoBarUi)) { - ShowOutputWindow("[InfoBar] Failed: Could not obtain IVsShell service."); return; } - // The main window InfoBar host may not be available immediately - // during template wizard execution while the main window initializes. - IVsInfoBarHost infoBarHost = null; - for (int retry = 0; retry < 5; retry++) + // Main window InfoBar host is not yet available. This can happen + // during template wizard execution while VS transitions from the + // New Project dialog. Wait for SolutionExistsContext, which activates + // once the solution is fully loaded and the main window is ready. + var context = KnownUIContexts.SolutionExistsContext; + if (context != null) { - shell.GetProperty((int)__VSSPROPID7.VSSPROPID_MainWindowInfoBarHost, out object infoBarHostObj); - if (infoBarHostObj is IVsInfoBarHost host) + void OnContextChanged(object sender, UIContextChangedEventArgs args) { - infoBarHost = host; - break; + if (args.Activated) + { + context.UIContextChanged -= OnContextChanged; + _ = ThreadHelper.JoinableTaskFactory.RunAsync(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + TryAddInfoBarToMainWindow(infoBarUi); + }); + } } - - await Task.Delay(1000); - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - } - - if (infoBarHost != null) - { - infoBarHost.AddInfoBar(infoBarUi); - } - else - { - ShowOutputWindow("[InfoBar] Failed: Main window InfoBar host not available after retries."); + context.UIContextChanged += OnContextChanged; } } catch (Exception ex) @@ -773,6 +768,26 @@ private async Task DisplayInfoBarAsync(string errorMessage) } } + private bool TryAddInfoBarToMainWindow(IVsInfoBarUIElement infoBarUi) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + IVsShell shell = ServiceProvider.GlobalProvider.GetService(typeof(SVsShell)) as IVsShell; + if (shell == null) + { + return false; + } + + shell.GetProperty((int)__VSSPROPID7.VSSPROPID_MainWindowInfoBarHost, out object infoBarHostObj); + if (infoBarHostObj is IVsInfoBarHost infoBarHost) + { + infoBarHost.AddInfoBar(infoBarUi); + return true; + } + + return false; + } + private IVsInfoBarUIElement CreateInfoBarUI(IVsInfoBar infoBar) { ThreadHelper.ThrowIfNotOnUIThread(); From 40cd26eff6728d56fa34972ad48de05ee47f2e32 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Thu, 19 Mar 2026 17:12:40 -0700 Subject: [PATCH 11/26] Move polling into its own function --- dev/VSIX/Shared/WizardImplementation.cs | 29 +++++++------------------ 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index eabb5d14ff..0e1460692e 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -735,31 +735,18 @@ private async Task DisplayInfoBarAsync(string errorMessage) infoBarUi.Advise(new NuGetInfoBarUIEvents(detailedErrorMessage), out uint _); - if (TryAddInfoBarToMainWindow(infoBarUi)) - { - return; - } - - // Main window InfoBar host is not yet available. This can happen + // The main window InfoBar host may not be available immediately // during template wizard execution while VS transitions from the - // New Project dialog. Wait for SolutionExistsContext, which activates - // once the solution is fully loaded and the main window is ready. - var context = KnownUIContexts.SolutionExistsContext; - if (context != null) + // New Project dialog. Retry briefly while the main window initializes. + for (int retry = 0; retry < 5; retry++) { - void OnContextChanged(object sender, UIContextChangedEventArgs args) + if (TryAddInfoBarToMainWindow(infoBarUi)) { - if (args.Activated) - { - context.UIContextChanged -= OnContextChanged; - _ = ThreadHelper.JoinableTaskFactory.RunAsync(async () => - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - TryAddInfoBarToMainWindow(infoBarUi); - }); - } + return; } - context.UIContextChanged += OnContextChanged; + + await Task.Delay(1000); + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); } } catch (Exception ex) From 7a150dad0153664e78b1ad6f452a024f1eea89b0 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Thu, 19 Mar 2026 17:25:59 -0700 Subject: [PATCH 12/26] Differentiate C++ message in InfoBar --- .../Cpp/Common/VSPackage.Designer.cs | 6 ++++ dev/VSIX/Extension/Cpp/Common/VSPackage.resx | 3 ++ .../Extension/Cs/Common/VSPackage.Designer.cs | 6 ++++ dev/VSIX/Extension/Cs/Common/VSPackage.resx | 3 ++ dev/VSIX/Shared/WizardImplementation.cs | 30 +++++++++++-------- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs b/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs index 78f6ac2a85..813de362c3 100644 --- a/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs +++ b/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs @@ -483,5 +483,11 @@ internal static string _1056 { return ResourceManager.GetString("1056", resourceCulture); } } + + internal static string _1057 { + get { + return ResourceManager.GetString("1057", resourceCulture); + } + } } } diff --git a/dev/VSIX/Extension/Cpp/Common/VSPackage.resx b/dev/VSIX/Extension/Cpp/Common/VSPackage.resx index 857ff5ba4d..7b9fa3424f 100644 --- a/dev/VSIX/Extension/Cpp/Common/VSPackage.resx +++ b/dev/VSIX/Extension/Cpp/Common/VSPackage.resx @@ -262,4 +262,7 @@ For more details on the error, check the General tab in the Output window. [{0}] NuGet package restore is disabled. Unable to install: {1}. Enable "Allow NuGet to download missing packages" in Tools > Options > NuGet Package Manager, then restore the solution. + + [{0}] NuGet package restore is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually. + \ No newline at end of file diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs index 90cad59a8d..58836c6728 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs @@ -479,5 +479,11 @@ internal static string _1056 { return ResourceManager.GetString("1056", resourceCulture); } } + + internal static string _1057 { + get { + return ResourceManager.GetString("1057", resourceCulture); + } + } } } diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.resx b/dev/VSIX/Extension/Cs/Common/VSPackage.resx index 773b9e7dfe..93f5f02dc3 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.resx +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.resx @@ -258,4 +258,7 @@ [{0}] NuGet package restore is disabled. Unable to install: {1}. Enable "Allow NuGet to download missing packages" in Tools > Options > NuGet Package Manager, then restore the solution. + + [{0}] NuGet package restore is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually. + \ No newline at end of file diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index 0e1460692e..bbfd1b77a0 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -141,9 +141,7 @@ public void ProjectFinishedGenerating(Project project) AddPackageReferencesToProject(); } - var projectName = _project?.Name ?? "Unknown Project"; - var packageNames = string.Join(", ", _nuGetPackages); - _ = DisplayInfoBarAsync(string.Format(Resources._1056, projectName, packageNames)); + _ = DisplayInfoBarAsync(GetRestoreDisabledMessage()); return; } @@ -249,9 +247,7 @@ private async Task StartInstallationAsync() { LogError($"NuGet restore is disabled. Error: {ex.Message}"); AddPackageReferencesToProject(); - var projectName = _project?.Name ?? "Unknown Project"; - var packageNames = string.Join(", ", _nuGetPackages); - _ = DisplayInfoBarAsync(string.Format(Resources._1056, projectName, packageNames)); + _ = DisplayInfoBarAsync(GetRestoreDisabledMessage()); return; } @@ -270,9 +266,7 @@ private async Task StartInstallationAsync() if (isRestoreDisabled) { AddPackageReferencesToProject(); - var projectName = _project?.Name ?? "Unknown Project"; - var packageNames = string.Join(", ", _nuGetPackages); - _ = DisplayInfoBarAsync(string.Format(Resources._1056, projectName, packageNames)); + _ = DisplayInfoBarAsync(GetRestoreDisabledMessage()); } else { @@ -310,9 +304,7 @@ public void RunFinished() if (isRestoreDisabled) { - var projectName = _project?.Name ?? "Unknown Project"; - var packageNames = string.Join(", ", _nuGetPackages); - _ = DisplayInfoBarAsync(string.Format(Resources._1056, projectName, packageNames)); + _ = DisplayInfoBarAsync(GetRestoreDisabledMessage()); ShowOutputWindow(CreateDetailedErrorMessage()); return; } @@ -334,6 +326,20 @@ public void RunFinished() } } + private string GetRestoreDisabledMessage() + { + ThreadHelper.ThrowIfNotOnUIThread(); + var projectName = _project?.Name ?? "Unknown Project"; + var packageNames = string.Join(", ", _nuGetPackages); + + // C++ projects can't use PackageReference, so they need manual install + // via the NuGet Package Manager. C# projects can re-enable auto-download + // and restore the solution. + return GetProjectGuid(_project).Equals(SolutionVCProjectGuid) + ? string.Format(Resources._1057, projectName, packageNames) + : string.Format(Resources._1056, projectName, packageNames); + } + private void ShowOutputWindow(string errorMessage) { ThreadHelper.ThrowIfNotOnUIThread(); From 2598c026b20ad03c98507e26a64c185182c4af4b Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Fri, 20 Mar 2026 09:34:21 -0700 Subject: [PATCH 13/26] Update infobar messages for auto-download disabled case --- .../Extension/Cpp/Common/VSPackage.Designer.cs | 16 +++++++++++----- dev/VSIX/Extension/Cpp/Common/VSPackage.resx | 6 +++--- .../Extension/Cs/Common/VSPackage.Designer.cs | 16 +++++++++++----- dev/VSIX/Extension/Cs/Common/VSPackage.resx | 6 +++--- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs b/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs index 813de362c3..42111f2ff0 100644 --- a/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs +++ b/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs @@ -1,7 +1,4 @@ -// Copyright (c) Microsoft Corporation and Contributors. -// Licensed under the MIT License - -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -22,7 +19,7 @@ namespace WindowsAppSDK.Cpp.Extension.Dev17 { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class VSPackage { @@ -472,18 +469,27 @@ internal static string _1054 { } } + /// + /// Looks up a localized string similar to [{0}] Build is currently disabled while NuGet packages are being installed: {1}. The build will be available once package installation is complete.. + /// internal static string _1055 { get { return ResourceManager.GetString("1055", resourceCulture); } } + /// + /// Looks up a localized string similar to [{0}] NuGet package auto-download is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually.. + /// internal static string _1056 { get { return ResourceManager.GetString("1056", resourceCulture); } } + /// + /// Looks up a localized string similar to [{0}] NuGet package auto-download is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually.. + /// internal static string _1057 { get { return ResourceManager.GetString("1057", resourceCulture); diff --git a/dev/VSIX/Extension/Cpp/Common/VSPackage.resx b/dev/VSIX/Extension/Cpp/Common/VSPackage.resx index 7b9fa3424f..53334bdadc 100644 --- a/dev/VSIX/Extension/Cpp/Common/VSPackage.resx +++ b/dev/VSIX/Extension/Cpp/Common/VSPackage.resx @@ -260,9 +260,9 @@ For more details on the error, check the General tab in the Output window.[{0}] Build is currently disabled while NuGet packages are being installed: {1}. The build will be available once package installation is complete. - [{0}] NuGet package restore is disabled. Unable to install: {1}. Enable "Allow NuGet to download missing packages" in Tools > Options > NuGet Package Manager, then restore the solution. + [{0}] NuGet package auto-download is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually. - [{0}] NuGet package restore is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually. + [{0}] NuGet package auto-download is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually. - \ No newline at end of file + diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs index 58836c6728..6392d6e364 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs @@ -1,7 +1,4 @@ -// Copyright (c) Microsoft Corporation and Contributors. -// Licensed under the MIT License - -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -22,7 +19,7 @@ namespace WindowsAppSDK.Cs.Extension.Dev17 { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class VSPackage { @@ -468,18 +465,27 @@ internal static string _1054 { } } + /// + /// Looks up a localized string similar to [{0}] Build is currently disabled while NuGet packages are being installed: {1}. The build will be available once package installation is complete.. + /// internal static string _1055 { get { return ResourceManager.GetString("1055", resourceCulture); } } + /// + /// Looks up a localized string similar to [{0}] NuGet package auto-download is disabled. Unable to install: {1}. To resolve, either enable "Allow NuGet to download missing packages" in Tools > Options > NuGet Package Manager and restore the solution, or install the packages manually via the NuGet Package Manager.. + /// internal static string _1056 { get { return ResourceManager.GetString("1056", resourceCulture); } } + /// + /// Looks up a localized string similar to [{0}] NuGet package auto-download is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually.. + /// internal static string _1057 { get { return ResourceManager.GetString("1057", resourceCulture); diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.resx b/dev/VSIX/Extension/Cs/Common/VSPackage.resx index 93f5f02dc3..74f5d41fb7 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.resx +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.resx @@ -256,9 +256,9 @@ [{0}] Build is currently disabled while NuGet packages are being installed: {1}. The build will be available once package installation is complete. - [{0}] NuGet package restore is disabled. Unable to install: {1}. Enable "Allow NuGet to download missing packages" in Tools > Options > NuGet Package Manager, then restore the solution. + [{0}] NuGet package auto-download is disabled. Unable to install: {1}. To resolve, either enable "Allow NuGet to download missing packages" and "Automatically check for missing packages during build in Visual Studio" in Tools > Options > NuGet Package Manager and restore the solution, or install the packages manually via the NuGet Package Manager. - [{0}] NuGet package restore is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually. + [{0}] NuGet package auto-download is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually. - \ No newline at end of file + From 7ae5b8d4b0aebee24e1f727628640c0c75603a17 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Fri, 20 Mar 2026 09:45:49 -0700 Subject: [PATCH 14/26] Fix BuildGuard logic so infobar shows when builds are attempted --- dev/VSIX/Shared/BuildGuard.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dev/VSIX/Shared/BuildGuard.cs b/dev/VSIX/Shared/BuildGuard.cs index 38726ca8a1..c2521aaf6b 100644 --- a/dev/VSIX/Shared/BuildGuard.cs +++ b/dev/VSIX/Shared/BuildGuard.cs @@ -191,8 +191,6 @@ private void ShowInfoBar() return; } - _infoBarShown = true; - try { var infoBarModel = new InfoBarModel( @@ -227,6 +225,7 @@ private void ShowInfoBar() if (infoBarHostObj is IVsInfoBarHost infoBarHost) { infoBarHost.AddInfoBar(_infoBarUIElement); + _infoBarShown = true; } } catch (Exception) From afb0a4dc1f65d986d708b86fcbd0dfd9c2c5e7de Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Fri, 20 Mar 2026 10:00:33 -0700 Subject: [PATCH 15/26] Disable the build guard infobar when build starts --- dev/VSIX/Shared/BuildGuard.cs | 65 ++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/dev/VSIX/Shared/BuildGuard.cs b/dev/VSIX/Shared/BuildGuard.cs index c2521aaf6b..c49ed6f534 100644 --- a/dev/VSIX/Shared/BuildGuard.cs +++ b/dev/VSIX/Shared/BuildGuard.cs @@ -32,6 +32,7 @@ internal sealed class BuildGuard : IVsUpdateSolutionEvents2, IVsUpdateSolutionEv private IVsShell _shell; private uint _shellPropertyCookie; private bool _isShellPropertyAdvised; + private bool _wasJustReleased; public bool IsBlocking => _isBlocking; @@ -87,27 +88,26 @@ public void EnableBuilds() ThreadHelper.ThrowIfNotOnUIThread(); _isBlocking = false; + _wasJustReleased = true; UnadviseShellPropertyChanges(); - DismissInfoBar(); - if (_isAdvised && _solutionBuildManager != null) - { - _solutionBuildManager.UnadviseUpdateSolutionEvents(_adviseCookie); - _isAdvised = false; - } - - if (_isAdvised4 && _solutionBuildManager5 != null) - { - _solutionBuildManager5.UnadviseUpdateSolutionEvents4(_adviseCookie4); - _isAdvised4 = false; - } + // Don't unadvise from build events yet - we need to stay subscribed + // so we can dismiss the info bar when the build actually starts } public int UpdateSolution_Begin(ref int pfCancelUpdate) { ThreadHelper.ThrowIfNotOnUIThread(); + if (_wasJustReleased) + { + _wasJustReleased = false; + DismissInfoBar(); + UnadviseFromBuildEvents(); + return VSConstants.S_OK; + } + if (_isBlocking) { if (_shouldRelease != null && _shouldRelease()) @@ -245,6 +245,23 @@ private void DismissInfoBar() } } + private void UnadviseFromBuildEvents() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (_isAdvised && _solutionBuildManager != null) + { + _solutionBuildManager.UnadviseUpdateSolutionEvents(_adviseCookie); + _isAdvised = false; + } + + if (_isAdvised4 && _solutionBuildManager5 != null) + { + _solutionBuildManager5.UnadviseUpdateSolutionEvents4(_adviseCookie4); + _isAdvised4 = false; + } + } + public void OnActionItemClicked(IVsInfoBarUIElement infoBarUIElement, IVsInfoBarActionItem actionItem) { } @@ -289,6 +306,16 @@ public int OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) public int UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, ref int pfCancel) { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (_wasJustReleased) + { + _wasJustReleased = false; + DismissInfoBar(); + UnadviseFromBuildEvents(); + return VSConstants.S_OK; + } + if (_isBlocking) { if (_shouldRelease != null && _shouldRelease()) @@ -316,6 +343,14 @@ public void UpdateSolution_QueryDelayFirstUpdateAction(out int pfDelay) pfDelay = 0; + if (_wasJustReleased) + { + _wasJustReleased = false; + DismissInfoBar(); + UnadviseFromBuildEvents(); + return; + } + if (_isBlocking) { if (_shouldRelease != null && _shouldRelease()) @@ -360,7 +395,11 @@ public void Dispose() ThreadHelper.JoinableTaskFactory.Run(async () => { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - EnableBuilds(); + _isBlocking = false; + _wasJustReleased = false; + DismissInfoBar(); + UnadviseFromBuildEvents(); + UnadviseShellPropertyChanges(); }); } } From 38bdd27fdad12317e91c38b2080d00c1cbef29d3 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Fri, 20 Mar 2026 10:21:33 -0700 Subject: [PATCH 16/26] Update BuildGuard infobar message to reflect that builds are paused and start after package installation --- dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs | 2 +- dev/VSIX/Extension/Cpp/Common/VSPackage.resx | 4 ++-- dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs | 4 ++-- dev/VSIX/Extension/Cs/Common/VSPackage.resx | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs b/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs index 42111f2ff0..c70bd7d0dd 100644 --- a/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs +++ b/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs @@ -470,7 +470,7 @@ internal static string _1054 { } /// - /// Looks up a localized string similar to [{0}] Build is currently disabled while NuGet packages are being installed: {1}. The build will be available once package installation is complete.. + /// Looks up a localized string similar to [{0}] Builds are currently paused while NuGet packages are being installed: {1}. The build will begin once package installation is complete.. /// internal static string _1055 { get { diff --git a/dev/VSIX/Extension/Cpp/Common/VSPackage.resx b/dev/VSIX/Extension/Cpp/Common/VSPackage.resx index 53334bdadc..ef4d58510c 100644 --- a/dev/VSIX/Extension/Cpp/Common/VSPackage.resx +++ b/dev/VSIX/Extension/Cpp/Common/VSPackage.resx @@ -257,7 +257,7 @@ For more details on the error, check the General tab in the Output window.No output information available. - [{0}] Build is currently disabled while NuGet packages are being installed: {1}. The build will be available once package installation is complete. + [{0}] Builds are currently paused while NuGet packages are being installed: {1}. The build will begin once package installation is complete. [{0}] NuGet package auto-download is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually. @@ -265,4 +265,4 @@ For more details on the error, check the General tab in the Output window. [{0}] NuGet package auto-download is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually. - + \ No newline at end of file diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs index 6392d6e364..5ecbdd4d57 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs @@ -466,7 +466,7 @@ internal static string _1054 { } /// - /// Looks up a localized string similar to [{0}] Build is currently disabled while NuGet packages are being installed: {1}. The build will be available once package installation is complete.. + /// Looks up a localized string similar to [{0}] Builds are currently paused while NuGet packages are being installed: {1}. The build will begin once package installation is complete.. /// internal static string _1055 { get { @@ -475,7 +475,7 @@ internal static string _1055 { } /// - /// Looks up a localized string similar to [{0}] NuGet package auto-download is disabled. Unable to install: {1}. To resolve, either enable "Allow NuGet to download missing packages" in Tools > Options > NuGet Package Manager and restore the solution, or install the packages manually via the NuGet Package Manager.. + /// Looks up a localized string similar to [{0}] NuGet package auto-download is disabled. Unable to install: {1}. To resolve, either enable "Allow NuGet to download missing packages" and "Automatically check for missing packages during build in Visual Studio" in Tools > Options > NuGet Package Manager and restore the solution, or install the packages manually via the NuGet Package Manager.. /// internal static string _1056 { get { diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.resx b/dev/VSIX/Extension/Cs/Common/VSPackage.resx index 74f5d41fb7..7ca40c9c28 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.resx +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.resx @@ -253,7 +253,7 @@ No output information available. - [{0}] Build is currently disabled while NuGet packages are being installed: {1}. The build will be available once package installation is complete. + [{0}] Builds are currently paused while NuGet packages are being installed: {1}. The build will begin once package installation is complete. [{0}] NuGet package auto-download is disabled. Unable to install: {1}. To resolve, either enable "Allow NuGet to download missing packages" and "Automatically check for missing packages during build in Visual Studio" in Tools > Options > NuGet Package Manager and restore the solution, or install the packages manually via the NuGet Package Manager. @@ -261,4 +261,4 @@ [{0}] NuGet package auto-download is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually. - + \ No newline at end of file From 9b8cc4c0285c5ffdd00086c44635de32d1557c6c Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Fri, 20 Mar 2026 11:05:52 -0700 Subject: [PATCH 17/26] Add header back to Designer files --- dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs | 5 ++++- dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs b/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs index c70bd7d0dd..cc432a8ff8 100644 --- a/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs +++ b/dev/VSIX/Extension/Cpp/Common/VSPackage.Designer.cs @@ -1,4 +1,7 @@ -//------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs index 5ecbdd4d57..4262747752 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs @@ -1,4 +1,7 @@ -//------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 From 5730d5a474c2e756bdcbbe2f7c2e95911baa44a6 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Fri, 20 Mar 2026 11:06:44 -0700 Subject: [PATCH 18/26] Link designer files in .csproj for C# and C++ templates --- .../Cpp/Dev17/WindowsAppSDK.Cpp.Extension.Dev17.csproj | 2 +- .../Extension/Cs/Dev17/WindowsAppSDK.Cs.Extension.Dev17.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/VSIX/Extension/Cpp/Dev17/WindowsAppSDK.Cpp.Extension.Dev17.csproj b/dev/VSIX/Extension/Cpp/Dev17/WindowsAppSDK.Cpp.Extension.Dev17.csproj index 3ce3830ff6..66c2a5e4e9 100644 --- a/dev/VSIX/Extension/Cpp/Dev17/WindowsAppSDK.Cpp.Extension.Dev17.csproj +++ b/dev/VSIX/Extension/Cpp/Dev17/WindowsAppSDK.Cpp.Extension.Dev17.csproj @@ -86,7 +86,7 @@ - + True True VSPackage.resx diff --git a/dev/VSIX/Extension/Cs/Dev17/WindowsAppSDK.Cs.Extension.Dev17.csproj b/dev/VSIX/Extension/Cs/Dev17/WindowsAppSDK.Cs.Extension.Dev17.csproj index 6f8e6fafbc..c8c6d6e7cf 100644 --- a/dev/VSIX/Extension/Cs/Dev17/WindowsAppSDK.Cs.Extension.Dev17.csproj +++ b/dev/VSIX/Extension/Cs/Dev17/WindowsAppSDK.Cs.Extension.Dev17.csproj @@ -63,7 +63,7 @@ - + True True VSPackage.resx From 49bf795aec18a30b620b4374e784e48628ae4232 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Fri, 20 Mar 2026 16:35:29 -0700 Subject: [PATCH 19/26] Remove unused ShowAutomaticPackageDownloadNotEnabledErrorDialog() method --- dev/VSIX/Shared/WizardImplementation.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index bbfd1b77a0..16ac318dfa 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -684,23 +684,6 @@ private void LogError(string message) }); } - private void ShowAutomaticPackageDownloadNotEnabledErrorDialog() - { - ThreadHelper.ThrowIfNotOnUIThread(); - var errorMessage = "Automatic NuGet package download is not enabled. The required packages for this project template cannot be installed automatically.\n\n" + - "To enable automatic package download, go to Tools > Options > NuGet Package Manager and check 'Allow NuGet to download missing packages'.\n\n" + - "After enabling this option, please close and reopen the solution, then the packages will be installed automatically."; - MessageBox.Show( - errorMessage, - "Automatic Package Download Disabled", - MessageBoxButtons.OK, - MessageBoxIcon.Warning); - // Also log to activity log - LogError("Automatic NuGet package download is disabled. User prompted to enable it."); - // Show in output window - ShowOutputWindow($"Automatic NuGet package download is disabled. User prompted to enable it.\n{errorMessage}"); - } - private void ShowLocalizationErrorDialog(MissingManifestResourceException ex) { ThreadHelper.ThrowIfNotOnUIThread(); From 46d973359a47b39dee3c23140aade7ee9d7cc9ce Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Fri, 20 Mar 2026 17:29:19 -0700 Subject: [PATCH 20/26] Remove activity in OnAfterOpenProjects - builds are disabled in RunStarted and the Unadvise call was premature --- dev/VSIX/Shared/WizardImplementation.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index 16ac318dfa..8ddca3c26b 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -479,12 +479,6 @@ private void UnadviseSolutionEvents() public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) { - ThreadHelper.ThrowIfNotOnUIThread(); - s_buildGuard.DisableBuilds(); - if (s_buildGuard.IsBlocking) - { - UnadviseSolutionEvents(); - } return VSConstants.S_OK; } From 753775878370dfdc0d0417f5a0dd5660e71a154e Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Fri, 20 Mar 2026 18:39:15 -0700 Subject: [PATCH 21/26] Remove SolutionBuildManager2 because we're targeting newer versions of VS --- dev/VSIX/Shared/BuildGuard.cs | 56 ++++++++--------------------------- 1 file changed, 12 insertions(+), 44 deletions(-) diff --git a/dev/VSIX/Shared/BuildGuard.cs b/dev/VSIX/Shared/BuildGuard.cs index c49ed6f534..59a0a15688 100644 --- a/dev/VSIX/Shared/BuildGuard.cs +++ b/dev/VSIX/Shared/BuildGuard.cs @@ -17,12 +17,9 @@ namespace WindowsAppSDK.TemplateUtilities { internal sealed class BuildGuard : IVsUpdateSolutionEvents2, IVsUpdateSolutionEvents4, IVsInfoBarUIEvents, IVsShellPropertyEvents, IDisposable { - private IVsSolutionBuildManager2 _solutionBuildManager; private IVsSolutionBuildManager5 _solutionBuildManager5; - private uint _adviseCookie; private uint _adviseCookie4; private bool _isBlocking; - private bool _isAdvised; private bool _isAdvised4; private bool _disposed; private bool _infoBarShown; @@ -60,26 +57,16 @@ public void DisableBuilds() return; } - _solutionBuildManager = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager2; - if (_solutionBuildManager == null) - { - System.Diagnostics.Debug.WriteLine("Warning: Could not obtain IVsSolutionBuildManager2 service."); - return; - } - - int hr = _solutionBuildManager.AdviseUpdateSolutionEvents(this, out _adviseCookie); - if (hr == VSConstants.S_OK) - { - _isAdvised = true; - _isBlocking = true; - _infoBarShown = false; - } - - _solutionBuildManager5 = _solutionBuildManager as IVsSolutionBuildManager5; + _solutionBuildManager5 = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager5; if (_solutionBuildManager5 != null) { - _solutionBuildManager5.AdviseUpdateSolutionEvents4(this, out _adviseCookie4); - _isAdvised4 = true; + if (_solutionBuildManager5 != null) + { + _solutionBuildManager5.AdviseUpdateSolutionEvents4(this, out _adviseCookie4); + _isAdvised4 = true; + _isBlocking = true; + _infoBarShown = false; + } } } @@ -116,13 +103,6 @@ public int UpdateSolution_Begin(ref int pfCancelUpdate) return VSConstants.S_OK; } - // When Events4 is available, delay via QueryDelayFirstUpdateAction - // instead of canceling here (canceling triggers "build errors" dialogs). - if (!_isAdvised4) - { - pfCancelUpdate = 1; - } - ShowInfoBar(); } @@ -249,12 +229,6 @@ private void UnadviseFromBuildEvents() { ThreadHelper.ThrowIfNotOnUIThread(); - if (_isAdvised && _solutionBuildManager != null) - { - _solutionBuildManager.UnadviseUpdateSolutionEvents(_adviseCookie); - _isAdvised = false; - } - if (_isAdvised4 && _solutionBuildManager5 != null) { _solutionBuildManager5.UnadviseUpdateSolutionEvents4(_adviseCookie4); @@ -284,11 +258,6 @@ public int UpdateSolution_StartUpdate(ref int pfCancelUpdate) { return VSConstants.S_OK; } - - if (!_isAdvised4) - { - pfCancelUpdate = 1; - } } return VSConstants.S_OK; @@ -322,11 +291,6 @@ public int UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCf { return VSConstants.S_OK; } - - if (!_isAdvised4) - { - pfCancel = 1; - } } return VSConstants.S_OK; @@ -358,6 +322,10 @@ public void UpdateSolution_QueryDelayFirstUpdateAction(out int pfDelay) EnableBuilds(); return; } + else if (!_infoBarShown) + { + ShowInfoBar(); + } pfDelay = 1; } From 20a5c920569e83b1d2db0f9baad24df1c7418303 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Fri, 20 Mar 2026 19:10:57 -0700 Subject: [PATCH 22/26] Remove IVsUpdateSolutionEvents2 and its interfaces because they are no longer used --- dev/VSIX/Shared/BuildGuard.cs | 58 +---------------------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/dev/VSIX/Shared/BuildGuard.cs b/dev/VSIX/Shared/BuildGuard.cs index 59a0a15688..e743653803 100644 --- a/dev/VSIX/Shared/BuildGuard.cs +++ b/dev/VSIX/Shared/BuildGuard.cs @@ -15,7 +15,7 @@ namespace WindowsAppSDK.TemplateUtilities { - internal sealed class BuildGuard : IVsUpdateSolutionEvents2, IVsUpdateSolutionEvents4, IVsInfoBarUIEvents, IVsShellPropertyEvents, IDisposable + internal sealed class BuildGuard : IVsUpdateSolutionEvents4, IVsInfoBarUIEvents, IVsShellPropertyEvents, IDisposable { private IVsSolutionBuildManager5 _solutionBuildManager5; private uint _adviseCookie4; @@ -245,62 +245,6 @@ public void OnClosed(IVsInfoBarUIElement infoBarUIElement) _infoBarUIElement = null; } - public int UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) - { - return VSConstants.S_OK; - } - - public int UpdateSolution_StartUpdate(ref int pfCancelUpdate) - { - if (_isBlocking) - { - if (_shouldRelease != null && _shouldRelease()) - { - return VSConstants.S_OK; - } - } - - return VSConstants.S_OK; - } - - public int UpdateSolution_Cancel() - { - return VSConstants.S_OK; - } - - public int OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) - { - return VSConstants.S_OK; - } - - public int UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, ref int pfCancel) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - if (_wasJustReleased) - { - _wasJustReleased = false; - DismissInfoBar(); - UnadviseFromBuildEvents(); - return VSConstants.S_OK; - } - - if (_isBlocking) - { - if (_shouldRelease != null && _shouldRelease()) - { - return VSConstants.S_OK; - } - } - - return VSConstants.S_OK; - } - - public int UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, int fSuccess, int fCancel) - { - return VSConstants.S_OK; - } - public void UpdateSolution_QueryDelayFirstUpdateAction(out int pfDelay) { ThreadHelper.ThrowIfNotOnUIThread(); From cf428edacaeb62eb604894e378c3160ba00ee3a2 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Fri, 17 Apr 2026 12:06:09 -0700 Subject: [PATCH 23/26] Update instructions for auto-package instruction to enable both settings (resolves in one click instead of two) --- dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs | 7 ++----- dev/VSIX/Extension/Cs/Common/VSPackage.resx | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs index 4262747752..973bf46b51 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.Designer.cs @@ -1,7 +1,4 @@ -// Copyright (c) Microsoft Corporation and Contributors. -// Licensed under the MIT License. - -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -478,7 +475,7 @@ internal static string _1055 { } /// - /// Looks up a localized string similar to [{0}] NuGet package auto-download is disabled. Unable to install: {1}. To resolve, either enable "Allow NuGet to download missing packages" and "Automatically check for missing packages during build in Visual Studio" in Tools > Options > NuGet Package Manager and restore the solution, or install the packages manually via the NuGet Package Manager.. + /// Looks up a localized string similar to [{0}] NuGet package auto-download is disabled. Unable to install: {1}. To resolve, enable "Allow NuGet to download missing packages" and "Automatically check for missing packages during build in Visual Studio" in Tools > Options > NuGet Package Manager and restore the solution, or install the packages manually via the NuGet Package Manager.. /// internal static string _1056 { get { diff --git a/dev/VSIX/Extension/Cs/Common/VSPackage.resx b/dev/VSIX/Extension/Cs/Common/VSPackage.resx index 7ca40c9c28..2c03526371 100644 --- a/dev/VSIX/Extension/Cs/Common/VSPackage.resx +++ b/dev/VSIX/Extension/Cs/Common/VSPackage.resx @@ -256,9 +256,9 @@ [{0}] Builds are currently paused while NuGet packages are being installed: {1}. The build will begin once package installation is complete. - [{0}] NuGet package auto-download is disabled. Unable to install: {1}. To resolve, either enable "Allow NuGet to download missing packages" and "Automatically check for missing packages during build in Visual Studio" in Tools > Options > NuGet Package Manager and restore the solution, or install the packages manually via the NuGet Package Manager. + [{0}] NuGet package auto-download is disabled. Unable to install: {1}. To resolve, enable "Allow NuGet to download missing packages" and "Automatically check for missing packages during build in Visual Studio" in Tools > Options > NuGet Package Manager and restore the solution, or install the packages manually via the NuGet Package Manager. [{0}] NuGet package auto-download is disabled. Unable to install: {1}. Click "Manage NuGet Packages" below to install the required packages manually. - \ No newline at end of file + From 69d7cc881f082cb923efe7bd4638c487d000537d Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Fri, 17 Apr 2026 13:13:25 -0700 Subject: [PATCH 24/26] Add project null check in ProjectFinishedGenerating --- dev/VSIX/Shared/WizardImplementation.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dev/VSIX/Shared/WizardImplementation.cs b/dev/VSIX/Shared/WizardImplementation.cs index 8ddca3c26b..6b5ce34ae7 100644 --- a/dev/VSIX/Shared/WizardImplementation.cs +++ b/dev/VSIX/Shared/WizardImplementation.cs @@ -122,6 +122,11 @@ public void RunStarted(object automationObject, Dictionary repla public void ProjectFinishedGenerating(Project project) { ThreadHelper.ThrowIfNotOnUIThread(); + if (project == null) + { + return; + } + _project = project; Guid _projectGuid = GetProjectGuid(project); From 28dc0ab236abbbae956d0a3aaa6ef7d7ebbd5fd3 Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Fri, 17 Apr 2026 13:16:38 -0700 Subject: [PATCH 25/26] Reset source.extension.vsixmanifests to GetVSIXVersion --- .../Cpp/Dev17/Standalone/source.extension.vsixmanifest | 2 +- .../Extension/Cs/Dev17/Standalone/source.extension.vsixmanifest | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/VSIX/Extension/Cpp/Dev17/Standalone/source.extension.vsixmanifest b/dev/VSIX/Extension/Cpp/Dev17/Standalone/source.extension.vsixmanifest index a35cb03096..a8857237a5 100644 --- a/dev/VSIX/Extension/Cpp/Dev17/Standalone/source.extension.vsixmanifest +++ b/dev/VSIX/Extension/Cpp/Dev17/Standalone/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + Windows App SDK C++ VS Templates The Microsoft Windows App SDK Visual Studio extension adds C++ project and item templates to support building Windows apps and components in VS 2022-2026. https://github.com/microsoft/WindowsAppSDK/ diff --git a/dev/VSIX/Extension/Cs/Dev17/Standalone/source.extension.vsixmanifest b/dev/VSIX/Extension/Cs/Dev17/Standalone/source.extension.vsixmanifest index 8cef623698..bcda1fcae6 100644 --- a/dev/VSIX/Extension/Cs/Dev17/Standalone/source.extension.vsixmanifest +++ b/dev/VSIX/Extension/Cs/Dev17/Standalone/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + Windows App SDK C# VS Templates The Microsoft Windows App SDK Visual Studio extension adds C# project and item templates to support building Windows apps and components in VS 2022-2026. https://github.com/microsoft/WindowsAppSDK/ From 56da5e18e8a3417c6e28ab792e1cac0dbe32625f Mon Sep 17 00:00:00 2001 From: Lauren Ciha Date: Tue, 21 Apr 2026 11:11:30 -0700 Subject: [PATCH 26/26] Remove duplicate null check for _solutionBuildManager5 --- dev/VSIX/Shared/BuildGuard.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/dev/VSIX/Shared/BuildGuard.cs b/dev/VSIX/Shared/BuildGuard.cs index e743653803..061dac3b63 100644 --- a/dev/VSIX/Shared/BuildGuard.cs +++ b/dev/VSIX/Shared/BuildGuard.cs @@ -60,13 +60,10 @@ public void DisableBuilds() _solutionBuildManager5 = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager5; if (_solutionBuildManager5 != null) { - if (_solutionBuildManager5 != null) - { - _solutionBuildManager5.AdviseUpdateSolutionEvents4(this, out _adviseCookie4); - _isAdvised4 = true; - _isBlocking = true; - _infoBarShown = false; - } + _solutionBuildManager5.AdviseUpdateSolutionEvents4(this, out _adviseCookie4); + _isAdvised4 = true; + _isBlocking = true; + _infoBarShown = false; } }