Add profile generators for Visual Studio (#7774)

This commit adds dynamic profile generators for Visual Studio Developer
Command Prompt (VS2017+) and Visual Studio Developer PowerShell
(VS2019.2+)

Tested manually by deploying locally. My local environment has four
instances of VS installed, one VS2017 and multiple channels of VS2019.

We're wrapping the COM Visual Studio Setup Configuration API to query
for VS instances and retrieve the relevant properties.  Two different
namespaces are used so the end-user can turn off one or the other. For
instance, end user may prefer to always use Developer PowerShell. 

## Validation Steps Performed
1. Build locally using Visual Studio 2019
2. Deploy CascadiaPackage
3. Verify entries exist in profiles menu
4. Verify entries exist in settings.json
5. Open each profile
6. Validate start-in directory
7. Validate environment variables are as expected
8. Uninstall Windows Terminal - Dev package
9. Repeat.

Closes #3821
This commit is contained in:
Charles Willis 2021-09-15 23:20:06 +01:00 committed by GitHub
parent 87b695f826
commit f84da18d1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 643 additions and 10 deletions

View file

@ -66,6 +66,7 @@ iosfwd
IPackage
IPeasant
isspace
ISetup
IStorage
istream
IStringable

View file

@ -2578,6 +2578,7 @@ VREDRAW
vsc
vscprintf
VSCROLL
vsdevshell
vsinfo
vsnprintf
vso

View file

@ -21,14 +21,12 @@
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" />
<ItemDefinitionGroup>
<ClCompile>
<!-- For CLI11: It uses dynamic_cast to cast types around, which depends
on being compiled with RTTI (/GR). -->
<RuntimeTypeInfo>true</RuntimeTypeInfo>
</ClCompile>
</ItemDefinitionGroup>
<!-- ========================= XAML files ======================== -->
<ItemGroup>
<!-- HERE BE DRAGONS:
@ -238,7 +236,6 @@
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="Toast.cpp" />
</ItemGroup>
<!-- ========================= idl Files ======================== -->
<ItemGroup>
@ -379,7 +376,6 @@
</ItemDefinitionGroup>
<!-- ========================= Globals ======================== -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
@ -388,8 +384,8 @@
</PropertyGroup>
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets'))" />
</Target>
<!--
By default, the PRI file will contain resource paths beginning with the
project name. Since we enabled XBF embedding, this *also* includes App.xbf.
@ -409,6 +405,5 @@
</PackagingOutputs>
</ItemGroup>
</Target>
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
</Project>

View file

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "BaseVisualStudioGenerator.h"
#include "DefaultProfileUtils.h"
using namespace Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Settings::Model;
std::vector<Profile> BaseVisualStudioGenerator::GenerateProfiles()
{
std::vector<Profile> profiles;
// There's no point in enumerating valid Visual Studio instances more than once,
// so cache them for use by both Visual Studio profile generators.
if (!BaseVisualStudioGenerator::hasQueried)
{
instances = VsSetupConfiguration::QueryInstances();
hasQueried = true;
}
for (auto const& instance : BaseVisualStudioGenerator::instances)
{
try
{
if (!IsInstanceValid(instance))
continue;
auto DevShell{ CreateProfile(GetProfileGuidSeed(instance)) };
DevShell.Name(GetProfileName(instance));
DevShell.Commandline(GetProfileCommandLine(instance));
DevShell.StartingDirectory(instance.GetInstallationPath());
DevShell.Icon(GetProfileIconPath());
profiles.emplace_back(DevShell);
}
CATCH_LOG();
}
return profiles;
}
Profile BaseVisualStudioGenerator::CreateProfile(const std::wstring_view seed)
{
const winrt::guid profileGuid{ Microsoft::Console::Utils::CreateV5Uuid(TERMINAL_PROFILE_NAMESPACE_GUID,
gsl::as_bytes(gsl::make_span(seed))) };
auto newProfile = winrt::make_self<implementation::Profile>(profileGuid);
newProfile->Origin(OriginTag::Generated);
return *newProfile;
}

View file

@ -0,0 +1,41 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- BaseVisualStudioGenerator
Abstract:
- Base generator for Visual Studio Developer shell profiles
Author(s):
- Charles Willis - October 2020
--*/
#pragma once
#include "IDynamicProfileGenerator.h"
#include "VsSetupConfiguration.h"
namespace Microsoft::Terminal::Settings::Model
{
class BaseVisualStudioGenerator : public IDynamicProfileGenerator
{
virtual bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance instance) const = 0;
virtual std::wstring GetProfileName(const VsSetupConfiguration::VsSetupInstance instance) const = 0;
virtual std::wstring GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance instance) const = 0;
virtual std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance instance) const = 0;
virtual std::wstring GetProfileIconPath() const = 0;
// Inherited via IDynamicProfileGenerator
virtual std::wstring_view GetNamespace() override = 0;
std::vector<winrt::Microsoft::Terminal::Settings::Model::Profile> GenerateProfiles() override;
private:
inline static bool hasQueried = false;
inline static std::vector<VsSetupConfiguration::VsSetupInstance> instances;
winrt::Microsoft::Terminal::Settings::Model::Profile CreateProfile(const std::wstring_view instanceId);
};
};

View file

@ -9,6 +9,8 @@
#include "AzureCloudShellGenerator.h"
#include "PowershellCoreProfileGenerator.h"
#include "VsDevCmdGenerator.h"
#include "VsDevShellGenerator.h"
#include "WslDistroGenerator.h"
using namespace ::Microsoft::Terminal::Settings::Model;
@ -51,6 +53,8 @@ CascadiaSettings::CascadiaSettings(const bool addDynamicProfiles) :
_profileGenerators.emplace_back(std::make_unique<PowershellCoreProfileGenerator>());
_profileGenerators.emplace_back(std::make_unique<WslDistroGenerator>());
_profileGenerators.emplace_back(std::make_unique<AzureCloudShellGenerator>());
_profileGenerators.emplace_back(std::make_unique<VsDevCmdGenerator>());
_profileGenerators.emplace_back(std::make_unique<VsDevShellGenerator>());
}
}

View file

@ -12,9 +12,9 @@
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="BaseVisualStudioGenerator.h" />
<ClInclude Include="DefaultTerminal.h">
<DependentUpon>DefaultTerminal.idl</DependentUpon>
</ClInclude>
@ -77,10 +77,14 @@
<ClInclude Include="TerminalWarnings.h">
<DependentUpon>TerminalWarnings.idl</DependentUpon>
</ClInclude>
<ClInclude Include="VsDevCmdGenerator.h" />
<ClInclude Include="VsDevShellGenerator.h" />
<ClInclude Include="VsSetupConfiguration.h" />
<ClInclude Include="WslDistroGenerator.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
<ClCompile Include="BaseVisualStudioGenerator.cpp" />
<ClCompile Include="DefaultTerminal.cpp">
<DependentUpon>DefaultTerminal.idl</DependentUpon>
</ClCompile>
@ -143,6 +147,9 @@
<ClCompile Include="EnumMappings.cpp">
<DependentUpon>EnumMappings.idl</DependentUpon>
</ClCompile>
<ClCompile Include="VsDevCmdGenerator.cpp" />
<ClCompile Include="VsDevShellGenerator.cpp" />
<ClCompile Include="VsSetupConfiguration.cpp" />
<ClCompile Include="WslDistroGenerator.cpp" />
<!-- You _NEED_ to include this file and the jsoncpp IncludePath (below) if
you want to use jsoncpp -->
@ -234,13 +241,13 @@
</ItemDefinitionGroup>
<!-- ========================= Globals ======================== -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets'))" />
</Target>
<!-- This target will take our defaults.json and stamp it into a .h file that
we can include in the code directly. This way, we don't need to worry about
@ -256,6 +263,6 @@
<Target Name="_TerminalAppGenerateUserSettingsH" Inputs="userDefaults.json" Outputs="Generated Files\userDefaults.h" BeforeTargets="BeforeClCompile">
<Exec Command="powershell.exe -noprofile ExecutionPolicy Unrestricted $(OpenConsoleDir)\tools\GenerateHeaderForJson.ps1 -JsonFile userDefaults.json -OutPath '&quot;Generated Files\userDefaults.h&quot;' -VariableName UserSettingsJson" />
</Target>
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
</Project>
<Import Project="..\..\..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets" Condition="Exists('..\..\..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets')" />
</Project>

View file

@ -34,6 +34,18 @@
<ClCompile Include="IconPathConverter.cpp" />
<ClCompile Include="DefaultTerminal.cpp" />
<ClCompile Include="FileUtils.cpp" />
<ClCompile Include="BaseVisualStudioGenerator.cpp">
<Filter>profileGeneration</Filter>
</ClCompile>
<ClCompile Include="VsDevCmdGenerator.cpp">
<Filter>profileGeneration</Filter>
</ClCompile>
<ClCompile Include="VsDevShellGenerator.cpp">
<Filter>profileGeneration</Filter>
</ClCompile>
<ClCompile Include="VsSetupConfiguration.cpp">
<Filter>profileGeneration</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@ -68,6 +80,18 @@
<ClInclude Include="DefaultTerminal.h" />
<ClInclude Include="FileUtils.h" />
<ClInclude Include="HashUtils.h" />
<ClInclude Include="BaseVisualStudioGenerator.h">
<Filter>profileGeneration</Filter>
</ClInclude>
<ClInclude Include="VsDevCmdGenerator.h">
<Filter>profileGeneration</Filter>
</ClInclude>
<ClInclude Include="VsDevShellGenerator.h">
<Filter>profileGeneration</Filter>
</ClInclude>
<ClInclude Include="VsSetupConfiguration.h">
<Filter>profileGeneration</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Midl Include="ActionArgs.idl" />

View file

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "VsDevCmdGenerator.h"
using namespace Microsoft::Terminal::Settings::Model;
std::wstring VsDevCmdGenerator::GetProfileName(const VsSetupConfiguration::VsSetupInstance instance) const
{
std::wstring name{ L"Developer Command Prompt for VS " };
name.append(instance.GetProfileNameSuffix());
return name;
}
std::wstring VsDevCmdGenerator::GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance instance) const
{
std::wstring commandLine{ L"cmd.exe /k \"" + instance.GetDevCmdScriptPath() + L"\"" };
return commandLine;
}

View file

@ -0,0 +1,53 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- VsDevCmdGenerator
Abstract:
- Dynamic profile generator for Visual Studio Developer Command Prompt
Author(s):
- Charles Willis - October 2020
--*/
#pragma once
#include "BaseVisualStudioGenerator.h"
namespace Microsoft::Terminal::Settings::Model
{
class VsDevCmdGenerator : public BaseVisualStudioGenerator
{
public:
VsDevCmdGenerator() = default;
~VsDevCmdGenerator() = default;
std::wstring_view GetNamespace() override
{
return std::wstring_view{ L"Windows.Terminal.VisualStudio.CommandPrompt" };
}
inline bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance instance) const override
{
// We only support version of VS from 15.0.
// Per heaths: The [ISetupConfiguration] COM server only supports Visual Studio 15.0 and newer anyway.
// Eliding the version range will improve the discovery performance by not having to parse or compare the versions.
return true;
}
inline std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance instance) const override
{
return L"VsDevCmd" + instance.GetInstanceId();
}
inline std::wstring GetProfileIconPath() const override
{
return L"ms-appx:///ProfileIcons/{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.png";
}
std::wstring GetProfileName(const VsSetupConfiguration::VsSetupInstance instance) const override;
std::wstring GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance instance) const override;
};
};

View file

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "VsDevShellGenerator.h"
#include "Setup.Configuration.h"
#include "DefaultProfileUtils.h"
#include "VsSetupConfiguration.h"
using namespace Microsoft::Terminal::Settings::Model;
std::wstring VsDevShellGenerator::GetProfileName(const VsSetupConfiguration::VsSetupInstance instance) const
{
std::wstring name{ L"Developer PowerShell for VS " };
name.append(instance.GetProfileNameSuffix());
return name;
}
std::wstring VsDevShellGenerator::GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance instance) const
{
// The triple-quotes are a PowerShell path escape sequence that can safely be stored in a JSON object.
// The "SkipAutomaticLocation" parameter will prevent "Enter-VsDevShell" from automatically setting the shell path
// so the path in the profile will be used instead.
std::wstring commandLine{ L"powershell.exe -NoExit -Command \"& {" };
commandLine.append(L"Import-Module \"\"\"" + instance.GetDevShellModulePath() + L"\"\"\";");
commandLine.append(L"Enter-VsDevShell " + instance.GetInstanceId() + L" -SkipAutomaticLocation");
commandLine.append(L"}\"");
return commandLine;
}

View file

@ -0,0 +1,50 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- VsDevShellGenerator
Abstract:
- Dynamic profile generator for Visual Studio Developer PowerShell
Author(s):
- Charles Willis - October 2020
--*/
#pragma once
#include "BaseVisualStudioGenerator.h"
namespace Microsoft::Terminal::Settings::Model
{
class VsDevShellGenerator : public BaseVisualStudioGenerator
{
public:
VsDevShellGenerator() = default;
~VsDevShellGenerator() = default;
std::wstring_view GetNamespace() override
{
return std::wstring_view{ L"Windows.Terminal.VisualStudio.Powershell" };
}
inline bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance instance) const override
{
return instance.VersionInRange(L"[16.2,)");
}
inline std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance instance) const override
{
return L"VsDevShell" + instance.GetInstanceId();
}
inline std::wstring GetProfileIconPath() const override
{
return L"ms-appx:///ProfileIcons/{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.png";
}
std::wstring GetProfileName(const VsSetupConfiguration::VsSetupInstance instance) const override;
std::wstring GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance instance) const override;
};
};

View file

@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "VsSetupConfiguration.h"
using namespace Microsoft::Terminal::Settings::Model;
std::vector<VsSetupConfiguration::VsSetupInstance> VsSetupConfiguration::QueryInstances()
{
std::vector<VsSetupConfiguration::VsSetupInstance> instances;
// SetupConfiguration is only registered if Visual Studio is installed
ComPtrSetupQuery pQuery{ wil::CoCreateInstanceNoThrow<SetupConfiguration, ISetupConfiguration2>() };
if (pQuery == nullptr)
{
return instances;
}
// Enumerate all valid instances of Visual Studio
wil::com_ptr<IEnumSetupInstances> e;
THROW_IF_FAILED(pQuery->EnumInstances(&e));
std::array<ComPtrSetupInstance, 1> rgpInstance;
auto result = e->Next(1, &rgpInstance[0], NULL);
while (S_OK == result)
{
// wrap the COM pointers in a friendly interface
instances.emplace_back(VsSetupConfiguration::VsSetupInstance{ pQuery, rgpInstance[0] });
result = e->Next(1, &rgpInstance[0], NULL);
}
THROW_IF_FAILED(result);
return instances;
}
/// <summary>
/// Takes a relative path under a Visual Studio installation and returns the absolute path.
/// </summary>
std::wstring VsSetupConfiguration::ResolvePath(ComPtrSetupInstance pInst, std::wstring_view relativePath)
{
wil::unique_bstr bstrAbsolutePath;
THROW_IF_FAILED(pInst->ResolvePath(relativePath.data(), &bstrAbsolutePath));
return bstrAbsolutePath.get();
}
/// <summary>
/// Determines whether a Visual Studio installation version falls within a specified range.
/// The range is specified as a string, ex: "[15.0.0.0,)", "[15.0.0.0, 16.7.0.0)
/// </summary>
bool VsSetupConfiguration::InstallationVersionInRange(ComPtrSetupQuery pQuery, ComPtrSetupInstance pInst, std::wstring_view range)
{
auto helper = pQuery.query<ISetupHelper>();
// VS versions in a string format such as "16.3.0.0" can be easily compared
// by parsing them into 64-bit unsigned integers using the stable algorithm
// provided by ParseVersion and ParseVersionRange
unsigned long long minVersion{ 0 };
unsigned long long maxVersion{ 0 };
THROW_IF_FAILED(helper->ParseVersionRange(range.data(), &minVersion, &maxVersion));
wil::unique_bstr bstrVersion;
THROW_IF_FAILED(pInst->GetInstallationVersion(&bstrVersion));
unsigned long long ullVersion{ 0 };
THROW_IF_FAILED(helper->ParseVersion(bstrVersion.get(), &ullVersion));
return ullVersion >= minVersion && ullVersion <= maxVersion;
}
std::wstring VsSetupConfiguration::GetInstallationVersion(ComPtrSetupInstance pInst)
{
wil::unique_bstr bstrInstallationVersion;
THROW_IF_FAILED(pInst->GetInstallationVersion(&bstrInstallationVersion));
return bstrInstallationVersion.get();
}
std::wstring VsSetupConfiguration::GetInstallationPath(ComPtrSetupInstance pInst)
{
wil::unique_bstr bstrInstallationPath;
THROW_IF_FAILED(pInst->GetInstallationPath(&bstrInstallationPath));
return bstrInstallationPath.get();
}
/// <summary>
/// The instance id is unique for each Visual Studio installation on a system.
/// The instance id is generated by the Visual Studio setup engine and varies from system to system.
/// </summary>
std::wstring VsSetupConfiguration::GetInstanceId(ComPtrSetupInstance pInst)
{
wil::unique_bstr bstrInstanceId;
THROW_IF_FAILED(pInst->GetInstanceId(&bstrInstanceId));
return bstrInstanceId.get();
}
std::wstring VsSetupConfiguration::GetStringProperty(ComPtrPropertyStore pProps, std::wstring_view name)
{
if (pProps == nullptr)
{
return std::wstring{};
}
wil::unique_variant var;
if (FAILED(pProps->GetValue(name.data(), &var)))
{
return std::wstring{};
}
return var.bstrVal;
}

View file

@ -0,0 +1,240 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- VsSetupConfiguration
Abstract:
- Encapsulates the Visual Studio Setup Configuration COM APIs
Author(s):
- Charles Willis - October 2020
--*/
#pragma once
#include "Setup.Configuration.h"
namespace Microsoft::Terminal::Settings::Model
{
/// <summary>
/// See https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.setup.configuration?view=visualstudiosdk-2019
/// </summary>
class VsSetupConfiguration
{
private:
typedef wil::com_ptr<ISetupConfiguration2> ComPtrSetupQuery;
typedef wil::com_ptr<ISetupHelper> ComPtrSetupHelper;
typedef wil::com_ptr<ISetupInstance> ComPtrSetupInstance;
typedef wil::com_ptr<ISetupInstance2> ComPtrSetupInstance2;
typedef wil::com_ptr<ISetupPropertyStore> ComPtrPropertyStore;
typedef wil::com_ptr<ISetupPackageReference> ComPtrPackageReference;
typedef wil::com_ptr<ISetupInstanceCatalog> ComPtrInstanceCatalog;
typedef ComPtrPropertyStore ComPtrCustomPropertyStore;
typedef ComPtrPropertyStore ComPtrCatalogPropertyStore;
public:
struct VsSetupInstance
{
inline std::wstring ResolvePath(std::wstring_view relativePath) const
{
return VsSetupConfiguration::ResolvePath(inst, relativePath);
}
inline std::wstring GetDevShellModulePath() const
{
// The path of Microsoft.VisualStudio.DevShell.dll changed in 16.3
if (VersionInRange(L"[16.3,"))
{
return ResolvePath(L"Common7\\Tools\\Microsoft.VisualStudio.DevShell.dll");
}
return ResolvePath(L"Common7\\Tools\\vsdevshell\\Microsoft.VisualStudio.DevShell.dll");
}
inline std::wstring GetDevCmdScriptPath() const
{
return ResolvePath(L"Common7\\Tools\\VsDevCmd.bat");
}
inline bool VersionInRange(std::wstring_view range) const
{
return VsSetupConfiguration::InstallationVersionInRange(query, inst, range);
}
inline std::wstring GetVersion() const
{
return VsSetupConfiguration::GetInstallationVersion(inst);
}
inline std::wstring GetInstallationPath() const
{
return VsSetupConfiguration::GetInstallationPath(inst);
}
inline std::wstring GetInstanceId() const
{
return VsSetupConfiguration::GetInstanceId(inst);
}
inline ComPtrPropertyStore GetInstancePropertyStore() const
{
ComPtrPropertyStore properties;
inst.query_to<ISetupPropertyStore>(&properties);
return properties;
}
inline ComPtrCustomPropertyStore GetCustomPropertyStore() const
{
ComPtrSetupInstance2 instance2;
inst.query_to<ISetupInstance2>(&instance2);
ComPtrCustomPropertyStore properties;
if (FAILED(instance2->GetProperties(&properties)))
{
return nullptr;
}
return properties;
}
inline ComPtrCatalogPropertyStore GetCatalogPropertyStore() const
{
ComPtrInstanceCatalog instanceCatalog;
inst.query_to<ISetupInstanceCatalog>(&instanceCatalog);
ComPtrCatalogPropertyStore properties;
if (FAILED(instanceCatalog->GetCatalogInfo(&properties)))
{
return nullptr;
}
return properties;
}
inline std::wstring GetProfileNameSuffix() const
{
return profileNameSuffix;
}
private:
friend class VsSetupConfiguration;
VsSetupInstance(const ComPtrSetupQuery pQuery, const ComPtrSetupInstance pInstance) :
query(pQuery),
helper(pQuery.query<ISetupHelper>()),
inst(pInstance),
profileNameSuffix(BuildProfileNameSuffix())
{
}
const ComPtrSetupQuery query;
const ComPtrSetupHelper helper;
const ComPtrSetupInstance inst;
std::wstring profileNameSuffix;
inline std::wstring BuildProfileNameSuffix() const
{
ComPtrCatalogPropertyStore catalogProperties = GetCatalogPropertyStore();
if (catalogProperties != nullptr)
{
std::wstring suffix;
std::wstring productLine{ GetProductLineVersion(catalogProperties) };
suffix.append(productLine);
ComPtrCustomPropertyStore customProperties = GetCustomPropertyStore();
if (customProperties != nullptr)
{
std::wstring nickname{ GetNickname(customProperties) };
if (!nickname.empty())
{
suffix.append(L" (" + nickname + L")");
}
else
{
ComPtrPropertyStore instanceProperties = GetInstancePropertyStore();
suffix.append(GetChannelNameSuffixTag(instanceProperties));
}
}
else
{
ComPtrPropertyStore instanceProperties = GetInstancePropertyStore();
suffix.append(GetChannelNameSuffixTag(instanceProperties));
}
return suffix;
}
return GetVersion();
}
inline std::wstring GetChannelNameSuffixTag(ComPtrPropertyStore instanceProperties) const
{
std::wstring tag;
std::wstring channelName{ GetChannelName(instanceProperties) };
if (channelName.empty())
{
return channelName;
}
if (channelName != L"Release")
{
tag.append(L" [" + channelName + L"]");
}
return tag;
}
inline std::wstring GetChannelId(ComPtrPropertyStore instanceProperties) const
{
return VsSetupConfiguration::GetStringProperty(instanceProperties, L"channelId");
}
inline std::wstring GetChannelName(ComPtrPropertyStore instanceProperties) const
{
std::wstring channelId{ GetChannelId(instanceProperties) };
if (channelId.empty())
{
return channelId;
}
std::wstring channelName;
// channelId is in the format <ProductName>.<MajorVersion>.<ChannelName>
size_t pos = channelId.rfind(L".");
if (pos != std::wstring::npos)
{
channelName.append(channelId.substr(pos + 1));
}
return channelName;
}
inline std::wstring GetNickname(ComPtrCustomPropertyStore customProperties) const
{
return VsSetupConfiguration::GetStringProperty(customProperties, L"nickname");
}
inline std::wstring GetProductLineVersion(ComPtrCatalogPropertyStore customProperties) const
{
return VsSetupConfiguration::GetStringProperty(customProperties, L"productLineVersion");
}
};
static std::vector<VsSetupConfiguration::VsSetupInstance> QueryInstances();
private:
VsSetupConfiguration() = default;
~VsSetupConfiguration() = default;
static bool InstallationVersionInRange(ComPtrSetupQuery pQuery, ComPtrSetupInstance pInst, std::wstring_view range);
static std::wstring ResolvePath(ComPtrSetupInstance pInst, std::wstring_view relativePath);
static std::wstring GetInstallationVersion(ComPtrSetupInstance pInst);
static std::wstring GetInstallationPath(ComPtrSetupInstance pInst);
static std::wstring GetInstanceId(ComPtrSetupInstance pInst);
static std::wstring GetStringProperty(ComPtrPropertyStore pProps, std::wstring_view name);
};
};

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.VisualStudio.Setup.Configuration.Native" version="2.3.2262" targetFramework="native" developmentDependency="true" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.210825.3" targetFramework="native" />
</packages>