Determining a VS2010 WebService project from .csproj - visual-studio-2010

I'm writing some PS to inspect .csproj files (for enforcing an SCM check-in policy)
Is the following check sufficient to determine if a .csproj is a WebService project?
# Check for WebProjectProperties (indicates project is a Web Service)
if ($xmldata.Project.ProjectExtensions.VisualStudio.FlavorProperties.WebProjectProperties -ne $null)
{
$isWebService = $true
}
The only alternative I've seen is parsing the Project.PropertyGroup.ProjectTypeGuids - but that seems overkill if this element is always set for a WS as well.

After digging a bit further, I'm (mostly) convinced. I've added this Dictionary:
# Visual Studio ProjectTypeGuid keys - store in a constant dictionary
# Courtesy of http://www.mztools.com/articles/2008/MZ2008017.aspx
New-Variable -Name ProjTypeMap -Option Constant -Value #{
"FAE04EC0-301F-11D3-BF4B-00C04F79EFBC" = "Windows (C#)";
"F184B08F-C81C-45F6-A57F-5ABD9991F28F" = "Windows (VB.NET)";
"8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942" = "Windows (Visual C++)";
"349C5851-65DF-11DA-9384-00065B846F21" = "Web Application";
"E24C65DC-7377-472B-9ABA-BC803B73C61A" = "Web Site";
"F135691A-BF7E-435D-8960-F99683D2D49C" = "Distributed System";
"3D9AD99F-2412-4246-B90B-4EAA41C64699" = "Windows Communication Foundation (WCF)";
"60DC8134-EBA5-43B8-BCC9-BB4BC16C2548" = "Windows Presentation Foundation (WPF)";
"C252FEB5-A946-4202-B1D4-9916A0590387" = "Visual Database Tools";
"A9ACE9BB-CECE-4E62-9AA4-C7E7C5BD2124" = "Database";
"4F174C21-8C12-11D0-8340-0000F80270F8" = "Database (other project types)";
"3AC096D0-A1C2-E12C-1390-A8335801FDAB" = "Test";
"20D4826A-C6FA-45DB-90F4-C717570B9F32" = "Legacy (2003) Smart Device (C#)";
"CB4CE8C6-1BDB-4DC7-A4D3-65A1999772F8" = "Legacy (2003) Smart Device (VB.NET)";
"4D628B5B-2FBC-4AA6-8C16-197242AEB884" = "Smart Device (C#)";
"68B1623D-7FB9-47D8-8664-7ECEA3297D4F" = "Smart Device (VB.NET)";
"14822709-B5A1-4724-98CA-57A101D1B079" = "Workflow Foundation 3.0 (C#)";
"D59BE175-2ED0-4C54-BE3D-CDAA9F3214C8" = "Workflow Foundation 3.0 (VB.NET)";
"32f31d43-81cc-4c15-9de6-3fc5453562b6" = "Workflow Foundation 4.0";
"06A35CCD-C46D-44D5-987B-CF40FF872267" = "Deployment Merge Module";
"3EA9E505-35AC-4774-B492-AD1749C4943A" = "Deployment Cab";
"978C614F-708E-4E1A-B201-565925725DBA" = "Deployment Setup";
"AB322303-2255-48EF-A496-5904EB18DA55" = "Deployment Smart Device Cab";
"A860303F-1F3F-4691-B57E-529FC101A107" = "Visual Studio Tools for Applications (VSTA)";
"BAA0C2D2-18E2-41B9-852F-F413020CAA33" = "Visual Studio Tools for Office (VSTO)";
"F8810EC1-6754-47FC-A15F-DFABD2E3FA90" = "SharePoint Workflow";
"6D335F3A-9D43-41b4-9D22-F6F17C4BE596" = "XNA (Windows)";
"2DF5C3F4-5A5F-47a9-8E94-23B4456F55E2" = "XNA (XBox)";
"D399B71A-8929-442a-A9AC-8BEC78BB2433" = "XNA (Zune)";
"EC05E597-79D4-47f3-ADA0-324C4F7C7484" = "SharePoint (VB.NET)";
"593B0543-81F6-4436-BA1E-4747859CAAE2" = "SharePoint (C#)";
"A1591282-1198-4647-A2B1-27E5FF5F6F3B" = "Silverlight"
}
and look-up any ProjectTypeGuids encountered. This did flush out one more WebApp than just testing on Project.ProjectExtensions.VisualStudio.FlavorProperties.WebProjectProperties, so it is more robust.

The WebProjectProperties section also exists for web application projects.
The distinction seems to be whether the app.config (probably web.config too?) contains (non-empty) configuration/system.serviceModel/services/service/host sections.

This list has been very helpful ! Thx a lot.
I was only missing this Guid:
"EF7E3281-CD33-11D4-8326-00C04FA0CE8D" = "BizTalk"

Related

Calling WinRT::MiracastReceiver from a Desktop Application(Win32/C++), it's non support

First, I use WinRT::MiracastReceiver(Win10 sdk) by "Windows Application Packaging Project" in Win32/C++ project.
https://learn.microsoft.com/zh-tw/windows/msix/desktop/desktop-to-uwp-packaging-dot-net.
So It already has package identity(Private Networks and Internet C/S capability).
And I check this API has DualApiPartition property, so the desktop app can call the WinRT API.
https://learn.microsoft.com/zh-tw/windows/win32/apiindex/uwp-apis-callable-from-a-classic-desktop-app
When I start a MiracastReceiver session, I get the result MiracastNotSupported?
How can I solve this?
When I put the same code in WinRT/UWP project, it's successful.
int main()
{
winrt::init_apartment();
receiver_ = MiracastReceiver();
receiver_.StatusChanged( {&OnStatusChanged} );
MiracastReceiverSettings settings = receiver_.GetDefaultSettings();
settings.FriendlyName(L"MiracastReceiver.2020.1217");
settings.AuthorizationMethod(MiracastReceiverAuthorizationMethod::None);
settings.ModelName(receiver_.GetDefaultSettings().ModelName());
settings.ModelNumber(receiver_.GetDefaultSettings().ModelNumber());
settings.RequireAuthorizationFromKnownTransmitters(false);
auto settings_sts = receiver_.DisconnectAllAndApplySettings(settings);
session_ = receiver_.CreateSession(nullptr);
session_.AllowConnectionTakeover(true);
session_.ConnectionCreated( {&OnConnectionCreated} );
session_.Disconnected( {&OnDisconnected} );
session_.MediaSourceCreated( {&OnMediaSourceCreated} );
MiracastReceiverSessionStartResult result = session_.Start();
MiracastReceiverSessionStartStatus status = result.Status();
std::wostringstream message;
message << L"ClickHandler: session_.Start, status=" << (int)status << std::endl;
OutputDebugString(message.str().c_str());
system("PAUSE");
}
status = MiracastNotSupported
env:
Visual Studio 2017 v15.9.30
Win10 SDK 10.0.19041.0
Win10 OS 2004 (19041)
I spent 8 hours on this exact problem, and eventually found out that if I compile the app as x64 instead of targeting [Any CPU] I could get it to work.

Using Powershell W/ Web Platform Installer API Only Fetches x86 Installers on 64 bit Machine

I'm trying to write a script to automate the installation of the Application Request Routing package on a x64 Windows Server 2012 R2 with IIS 8 and Web Platform Installer 5. I've reproduced the code I'm using below:
Try {
[reflection.assembly]::LoadWithPartialName("Microsoft.Web.PlatformInstaller") | Out-Null
$ProductManager = New-Object Microsoft.Web.PlatformInstaller.ProductManager
$ProductManager.Load()
$product = $ProductManager.Products | Where { $_.ProductId -eq "ARRv3_0" }
#Get an instance of InstallManager class to perform package install
$InstallManager = New-Object Microsoft.Web.PlatformInstaller.InstallManager
$installer = New-Object 'System.Collections.Generic.List[Microsoft.Web.PlatformInstaller.Installer]'
$Language = $ProductManager.GetLanguage("en")
#Get dependencies
$deplist = New-Object 'System.Collections.Generic.List[Microsoft.Web.PlatformInstaller.Product]'
$deplist.add($product)
$deps = $product.getMissingDependencies($deplist)
foreach ($dep in $deps) {
Write-Host "$($dep.GetInstaller($Language))"
$Installer.Add($dep.GetInstaller($Language))
Write-Host "Dependency $($dep.Title) not found..."
}
$installer.Add($product.Installers[1])
$InstallManager.Load($installer)
#Download the installer package
$failureReason=$null
foreach ($installerContext in $InstallManager.InstallerContexts) {
$InstallManager.DownloadInstallerFile($installerContext, [ref]$failureReason)
Write-Host $($installerContext)
}
$InstallManager.StartSynchronousInstallation()
notepad $product.Installers[1].LogFiles
Write-Host "Opening logs at $($product.Installers[1].LogFiles)"
Write-Host "Installation finished"
}
Catch {
Write-Error "FATAL ERROR! $($_)"
}
Finally {
Write-Host "Press any key to continue ..."
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
The ARRv3_0 has two dependencies, ExternalCache and UrlRewrite2.
However, when I try to pull the installers using:
$Language = $ProductManager.GetLanguage("en")
$installer.Add($dep.GetInstaller($Language))
(where $dep is the reference to the product) it only fetches the x86 version, which will not install on a 64 bit machine. I've looked through the ProductList Xml that contains a listing of the Web Platform Packages here, and I've copied and pasted below the occurrence of an x64 variant of the UrlRewrite2 package, which exists.
<installer>
<id>20</id>
<languageId>en</languageId>
<architectures>
<x64/>
</architectures>
<eulaURL>
......
</installer>
Interestingly enough, there's an architecture parameter, but looking at the Microsoft.Web.PlatformInstaller API there doesn't seem to be a way to set/access it. Other than hardcoding, is there any possible way to tell the API to fetch the 64 bit versions instead?
I'm definitely running this in a 64 bit powershell on a 64 bit machine, but it seems incredibly counter intuitive that the api would fetch x86 installers. Is there some incredibly obvious (and poorly documented) setting that I'm missing?
The Product class as the Installers property. Instead of getting the default installer, I get a specific installer (64bit) and add it to a generic list of installers that is passed as argument to the InstallManager. Here is the snippet.
$installers = New-Object 'System.Collections.Generic.List[Microsoft.Web.PlatformInstaller.Installer]'
foreach($i in $product.Installers)
{
if($i.InstallerFile.InstallerUrl.ToString().ToLower().EndsWith("_amd64.msi"))
{
$i.InstallerFile
$installers.Add($i)
break
}
}

Windows Update Agent API - Searching for Updates

I wrote a Powershell script that uses the Windows Update Agent API (IUpdateSearcher, IUpdateDownloader, IUpdateInstaller etc.). Everything works fine, the script finds avaiable updates, downloads and installs them.
However, there is a problem when searching for consecutive updates. For example, there is an update for the .Net Framework 4.5.2. The update is installed by script and the PC is rebooted afterwards. Now there should be an update for the .Net Framework 4.5.2 Language Pack avaiable.
But it is not. At least not via the API. A manual search with the GUI (Windows Update) works.
After the manual search, the API finds the update a well!
What am I missing in my script? I could not find anything in Microsofts documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/aa386868(v=vs.85).aspx
$updateSession = New-Object -ComObject 'Microsoft.Update.Session'
$UpdateSession.WebProxy.AutoDetect = $false
$updateSearcher = $updateSession.CreateUpdateSearcher()
$searchResult = $updateSearcher.Search('IsInstalled=0 and IsHidden=0')
$objCollectionDownload = New-Object -ComObject 'Microsoft.Update.UpdateColl'
foreach ($update in $searchResult.Updates)
{
$objCollectionTmp = New-Object -ComObject 'Microsoft.Update.UpdateColl'
$objCollectionTmp.Add($update) | Out-Null
$downloader = $updateSession.CreateUpdateDownloader()
$downloader.Updates = $objCollectionTmp
try
{
$downloadResult = $downloader.Download()
}
catch
{
//exception Handling
}
$objCollectionDownload.Add($update) | Out-Null
}
$updatesToInstall = New-Object -ComObject 'Microsoft.Update.UpdateColl'
$updateInstaller = $updateSession.CreateUpdateInstaller()
foreach ($update in $objCollectionDownload)
{
//accept Eula etc...
$updatesToInstall.Add($update) | Out-Null
}
$updateInstaller.Updates = $updatesToInstall
$installationRestult = $updateInstaller.Install()
//check installation result
Oddly enough I had the same issue just now, Windows GUI showed a particular update, Our GUI using the API wouldn't show this particular update... I had IsInstalled = 0 and IsHidden = 0.... I looked in the WIndows Update log and found the criteria that the WIndows GUI was using.
IsInstalled=0 and DeploymentAction='Installation' or IsPresent=1 and DeploymentAction='Uninstallation' or IsInstalled=1 and DeploymentAction='Installation' and RebootRequired=1 or IsInstalled=0 and DeploymentAction='Uninstallation' and RebootRequired=1
Added this to my application in place of IsInstalled = 0 and IsHidden = 0 and the update showed straight up :-/ don't really understand why but I am not complaining.

Installing file system minifilter from INF launched from debug

I am working on my first file system mini-filter. I am using the SwapBuffers sample project in the WDK. I have successfully compiled and deployed this project to a VM from a physical laptop. But the installation from the INF file is failing. I looked in the log file in C:\DriverTest\Logs on the target machine and found these lines in the log file.
UserText="WDTF_TARGETS: Query("HardwareIDs=&apos;WDTF\NOEXIST&apos;")"
UserText="WDTF_DRIVER_SETUP_SYSTEM: CreateRootEnumeratedDevicesFromPackage()"
UserText="WDTF_DRIVER_SETUP_SYSTEM:
UserText="WDTF_TEST: System has no device the driver package can be installed onto."
Here is the INF file. It is unmodified from the original sample.
;;; SwapBuffers
;;; Copyright (c) 2001, Microsoft Corporation
[Version]
signature = "$Windows NT$"
Class = "Encryption" ;This is determined by the work this filter driver does
ClassGuid = {a0a701c0-a511-42ff-aa6c-06dc0395576f} ;This value is determined by the Class
Provider = %Msft%
DriverVer = 06/16/2007,1.0.0.3
CatalogFile = swapbuffers.cat
[DestinationDirs]
DefaultDestDir = 12
MiniFilter.DriverFiles = 12 ;%windir%\system32\drivers
;; Default install sections
[DefaultInstall]
OptionDesc = %ServiceDescription%
CopyFiles = MiniFilter.DriverFiles
[DefaultInstall.Services]
AddService = %ServiceName%,,MiniFilter.Service
;; Default uninstall sections
[DefaultUninstall]
DelFiles = MiniFilter.DriverFiles
[DefaultUninstall.Services]
DelService = SwapBuffers,0x200 ;Ensure service is stopped before deleting
; Services Section
[MiniFilter.Service]
DisplayName = %ServiceName%
Description = %ServiceDescription%
ServiceBinary = %12%\%DriverName%.sys ;%windir%\system32\drivers\
Dependencies = "FltMgr"
ServiceType = 2 ;SERVICE_FILE_SYSTEM_DRIVER
;StartType = 0 ;SERVICE_BOOT_START
StartType = 3 ;SERVICE_DEMAND_START
ErrorControl = 1 ;SERVICE_ERROR_NORMAL
LoadOrderGroup = "FSFilter Encryption"
AddReg = MiniFilter.AddRegistry
; Registry Modifications
[MiniFilter.AddRegistry]
HKR,,"SupportedFeatures",0x00010001,0x3
HKR,"Instances","DefaultInstance",0x00000000,%Instance1.Name%
HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude%
HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags%
; Copy Files
[MiniFilter.DriverFiles]
%DriverName%.sys
[SourceDisksFiles]
swapbuffers.sys = 1,,
[SourceDisksNames]
1 = %DiskId1%,,,
;; String Section
[Strings]
Msft = "Microsoft Corporation"
ServiceDescription = "Swap Buffers Sample Mini-Filter Driver"
ServiceName = "SwapBuffers"
DriverName = "SwapBuffers"
DiskId1 = "SwapBuffers Device Installation Disk"
;Instances specific information.
Instance1.Name = "SwapBuffers Instance"
Instance1.Altitude = "141000"
Instance1.Flags = 0x0 ; allow automatic attachments
The VMware session has a single hard drive that shows up as a SCSI drive looking at DEVCON.
Can anyone tell me what I'm missing here?
If your driver is not for a piece of hardware, go to
select the "driver package"
open "configuration"
go to "driver install" -> "deployment"
select "Do Not Install"
If needed use a custom command e.g. to run "svcctrl.exe" with the desired parameters.
inf install can fail on these common cases:
The driver is not signed.
The driver is a debug driver signed with the test certificate but the certificate wasn't installed on the target machine. The test certificate generated under your output dir (cer file). Copy it to the target machine and double click it.
The target machine needs to have test signing enabled:
Open admin console
bcdedit -set TESTSIGNING ON
reboot
I find it easier to install from a batch file (don't omit the "./"):
RUNDLL32.EXE SETUPAPI.DLL,InstallHinfSection DefaultInstall 132 ./MY_DRIVER_NAME.inf

Programmatically retrieve Visual Studio install directory

I know there is a registry key indicating the install directory, but I don't remember what it is off-hand.
I am currently interested in Visual Studio 2008 install directory, though it wouldn't hurt to list others for future reference.
I use this method to find the installation path of Visual Studio 2010:
private string GetVisualStudioInstallationPath()
{
string installationPath = null;
if (Environment.Is64BitOperatingSystem)
{
installationPath = (string)Registry.GetValue(
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\VisualStudio\\10.0\\",
"InstallDir",
null);
}
else
{
installationPath = (string)Registry.GetValue(
"HKEY_LOCAL_MACHINE\\SOFTWARE \\Microsoft\\VisualStudio\\10.0\\",
"InstallDir",
null);
}
return installationPath;
}
I'm sure there's a registry entry as well but I couldn't easily locate it. There is the VS90COMNTOOLS environment variable that you could use as well.
Registry Method
I recommend querying the registry for this information. This gives the actual installation directory without the need for combining paths, and it works for express editions as well. This could be an important distinction depending on what you need to do (e.g. templates get installed to different directories depending on the edition of Visual Studio). The registry locations are as follows (note that Visual Studio is a 32-bit program and will be installed to the 32-bit section of the registry on x64 machines):
Visual Studio: HKLM\SOFTWARE\Microsoft\Visual Studio\Major.Minor:InstallDir
Visual C# Express: HKLM\SOFTWARE\Microsoft\VCSExpress\Major.Minor:InstallDir
Visual Basic Express: HKLM\SOFTWARE\Microsoft\VBExpress\Major.Minor:InstallDir
Visual C++ Express: HKLM\SOFTWARE\Microsoft\VCExpress\Major.Minor:InstallDir
where Major is the major version number, Minor is the minor version number, and the text after the colon is the name of the registry value. For example, the installation directory of Visual Studio 2008 Professional would be located at the HKLM\SOFTWARE\Microsoft\Visual Studio\9.0 key, in the InstallDir value.
Here's a code example that prints the installation directory of several versions of Visual Studio and Visual C# Express:
string visualStudioRegistryKeyPath = #"SOFTWARE\Microsoft\VisualStudio";
string visualCSharpExpressRegistryKeyPath = #"SOFTWARE\Microsoft\VCSExpress";
List<Version> vsVersions = new List<Version>() { new Version("10.0"), new Version("9.0"), new Version("8.0") };
foreach (var version in vsVersions)
{
foreach (var isExpress in new bool[] { false, true })
{
RegistryKey registryBase32 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
RegistryKey vsVersionRegistryKey = registryBase32.OpenSubKey(
string.Format(#"{0}\{1}.{2}", (isExpress) ? visualCSharpExpressRegistryKeyPath : visualStudioRegistryKeyPath, version.Major, version.Minor));
if (vsVersionRegistryKey == null) { continue; }
Console.WriteLine(vsVersionRegistryKey.GetValue("InstallDir", string.Empty).ToString());
}
Environment Variable Method
The non-express editions of Visual Studio also write an environment variable that you could check, but it gives the location of the common tools directory, not the installation directory, so you'll have to do some path combining. The format of the environment variable is VS*COMNTOOLS where * is the major and minor version number. For example, the environment variable for Visual Studio 2010 is VS100COMNTOOLS and contains a value like C:\Program Files\Microsoft Visual Studio 10.0\Common7\Tools.
Here's some example code to print the environment variable for several versions of Visual Studio:
List<Version> vsVersions = new List<Version>() { new Version("10.0"), new Version("9.0"), new Version("8.0") };
foreach (var version in vsVersions)
{
Console.WriteLine(Path.Combine(Environment.GetEnvironmentVariable(string.Format("VS{0}{1}COMNTOOLS", version.Major, version.Minor)), #"..\IDE"));
}
Environment: Thanks to Zeb and Sam for the VS*COMNTOOLS environment variable suggestion. To get to the IDE in PowerShell:
$vs = Join-Path $env:VS90COMNTOOLS '..\IDE\devenv.exe'
Registry: Looks like the registry location is HKLM\Software\Microsoft\VisualStudio, with version-specific subkeys for each install. In PowerShell:
$vsRegPath = 'HKLM:\Software\Microsoft\VisualStudio\9.0'
$vs = (Get-ItemProperty $vsRegPath).InstallDir + 'devenv.exe'
[Adapted from here]
For Visual Studio 2017 and Visual Studio 2019 there is the Setup API from Microsoft.
In C#, just add the NuGet package "Microsoft.VisualStudio.Setup.Configuration.Interop", and use it in this way:
try {
var query = new SetupConfiguration();
var query2 = (ISetupConfiguration2)query;
var e = query2.EnumAllInstances();
var helper = (ISetupHelper)query;
int fetched;
var instances = new ISetupInstance[1];
do {
e.Next(1, instances, out fetched);
if (fetched > 0)
Console.WriteLine(instances[0].GetInstallationPath());
}
while (fetched > 0);
return 0;
}
catch (COMException ex) when (ex.HResult == REGDB_E_CLASSNOTREG) {
Console.WriteLine("The query API is not registered. Assuming no instances are installed.");
return 0;
}
You can find more samples for VC, C#, and VB here.
It is a real problem that all Visual Studio versions have their own location. So the solutions here proposed are not generic. However, Microsoft has made a utility available for free (including the source code) that solved this problem (i.e. annoyance). It is called vswhere.exe and you can download it from here. I am very happy with it, and hopefully it will also do for future releases. It makes the whole discussion on this page redundant.
#Dim-Ka has a great answer. If you were curious how you'd implement this in a batch script, this is how.
#echo off
:: BATCH doesn't have logical or, otherwise I'd use it
SET platform=
IF /I [%PROCESSOR_ARCHITECTURE%]==[amd64] set platform=true
IF /I [%PROCESSOR_ARCHITEW6432%]==[amd64] set platform=true
:: default to VS2012 = 11.0
:: the Environment variable VisualStudioVersion is set by devenv.exe
:: if this batch is a child of devenv.exe external tools, we know which version to look at
if not defined VisualStudioVersion SET VisualStudioVersion=11.0
if defined platform (
set VSREGKEY=HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\%VisualStudioVersion%
) ELSE (
set VSREGKEY=HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\%VisualStudioVersion%
)
for /f "skip=2 tokens=2,*" %%A in ('reg query "%VSREGKEY%" /v InstallDir') do SET VSINSTALLDIR=%%B
echo %VSINSTALLDIR%
Ah, the 64-bit machine part was the issue. It turns out I need to make sure I'm running the PowerShell.exe under the syswow64 directory in order to get the x86 registry keys.
Now that wasn't very fun.
Use Environment.GetEnvironmentVariable("VS90COMNTOOLS");.
Also in a 64-bit environment, it works for me.
Here's a solution to always get the path for the latest version:
$vsEnvVars = (dir Env:).Name -match "VS[0-9]{1,3}COMNTOOLS"
$latestVs = $vsEnvVars | Sort-Object | Select -Last 1
$vsPath = Get-Content Env:\$latestVs
You can read the VSINSTALLDIR environment variable.
Here is something I have been updating over the years... (for CudaPAD)
Usage examples:
var vsPath = VS_Tools.GetVSPath(avoidPrereleases:true, requiredWorkload:"NativeDesktop");
var vsPath = VS_Tools.GetVSPath();
var vsPath = VS_Tools.GetVSPath(specificVersion:"15");
The drop-in function:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.Setup.Configuration;
using System.IO;
using Microsoft.Win32;
static class VS_Tools
{
public static string GetVSPath(string specificVersion = "", bool avoidPrereleases = true, string requiredWorkload = "")
{
string vsPath = "";
// Method 1 - use "Microsoft.VisualStudio.Setup.Configuration.SetupConfiguration" method.
// Note: This code has is a heavily modified version of Heath Stewart's code.
// original source: (Heath Stewart, May 2016) https://github.com/microsoft/vs-setup-samples/blob/80426ad4ba10b7901c69ac0fc914317eb65deabf/Setup.Configuration.CS/Program.cs
try
{
var e = new SetupConfiguration().EnumAllInstances();
int fetched;
var instances = new ISetupInstance[1];
do
{
e.Next(1, instances, out fetched);
if (fetched > 0)
{
var instance2 = (ISetupInstance2)instances[0];
var state = instance2.GetState();
// Let's make sure this install is complete.
if (state != InstanceState.Complete)
continue;
// If we have a version to match lets make sure to match it.
if (!string.IsNullOrWhiteSpace(specificVersion))
if (!instances[0].GetInstallationVersion().StartsWith(specificVersion))
continue;
// If instances[0] is null then skip
var catalog = instances[0] as ISetupInstanceCatalog;
if (catalog == null)
continue;
// If there is not installation path lets skip
if ((state & InstanceState.Local) != InstanceState.Local)
continue;
// Let's make sure it has the required workload - if one was given.
if (!string.IsNullOrWhiteSpace(requiredWorkload))
{
if ((state & InstanceState.Registered) == InstanceState.Registered)
{
if (!(from package in instance2.GetPackages()
where string.Equals(package.GetType(), "Workload", StringComparison.OrdinalIgnoreCase)
where package.GetId().Contains(requiredWorkload)
orderby package.GetId()
select package).Any())
{
continue;
}
}
else
{
continue;
}
}
// Let's save the installation path and make sure it has a value.
vsPath = instance2.GetInstallationPath();
if (string.IsNullOrWhiteSpace(vsPath))
continue;
// If specified, avoid Pre-release if possible
if (avoidPrereleases && catalog.IsPrerelease())
continue;
// We found the one we need - lets get out of here
return vsPath;
}
}
while (fetched > 0);
}
catch (Exception){ }
if (string.IsNullOrWhiteSpace(vsPath))
return vsPath;
// Fall-back Method: Find the location of visual studio (%VS90COMNTOOLS%\..\..\vc\vcvarsall.bat)
// Note: This code has is a heavily modified version of Kevin Kibler's code.
// source: (Kevin Kibler, 2014) http://stackoverflow.com/questions/30504/programmatically-retrieve-visual-studio-install-directory
List<Version> vsVersions = new List<Version>() { new Version("15.0"), new Version("14.0"),
new Version("13.0"), new Version("12.0"), new Version("11.0") };
foreach (var version in vsVersions)
{
foreach (var isExpress in new bool[] { false, true })
{
RegistryKey registryBase32 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
RegistryKey vsVersionRegistryKey = registryBase32.OpenSubKey(
string.Format(#"{0}\{1}.{2}",
(isExpress) ? #"SOFTWARE\Microsoft\VCSExpress" : #"SOFTWARE\Microsoft\VisualStudio",
version.Major, version.Minor));
if (vsVersionRegistryKey == null) { continue; }
string path = vsVersionRegistryKey.GetValue("InstallDir", string.Empty).ToString();
if (!string.IsNullOrEmpty(path))
{
path = Directory.GetParent(path).Parent.Parent.FullName;
if (File.Exists(path + #"\VC\bin\cl.exe") && File.Exists(path + #"\VC\vcvarsall.bat"))
{
vsPath = path;
break;
}
}
}
if (!string.IsNullOrWhiteSpace(vsPath))
break;
}
return vsPath;
}
}
Nowadays, I use the following PowerShell command to get the Visual Studio 2017/2019 path (here with the Common7\IDE suffix, so it mimics the DevEnvDir property):
Get-ChildItem HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall | foreach { Get-ItemProperty $_.PsPath } | where { $_.DisplayName -like '*Visual Studio*' -and $_.InstallLocation.Length -gt 0 } | sort InstallDate -Descending | foreach { (Join-Path $_.InstallLocation 'Common7\IDE') } | where { Test-Path $_ } | select -First 1
If you want to execute it from cmd.exe, the command would look like this:
powershell.exe -ExecutionPolicy Bypass -Command "Get-ChildItem HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall | foreach { Get-ItemProperty $_.PsPath } | where { $_.DisplayName -like '*Visual Studio*' -and $_.InstallLocation.Length -gt 0 } | sort InstallDate -Descending | foreach { (Join-Path $_.InstallLocation 'Common7\IDE') } | where { Test-Path $_ } | select -First 1"
I am using it in a C# project, where I use Rider instead of Visual Studio as my IDE (of course I could have also just manually setup the DevEnvDir property in Rider's settings):
<Target Name="MyTarget" BeforeTargets="Build">
<Exec Condition="'$(DevEnvDir)' == '' Or '$(DevEnvDir)' == '*Undefined*' Or !Exists('$(DevEnvDir)')"
Command="powershell.exe -ExecutionPolicy Bypass -Command "Get-ChildItem HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall | foreach { Get-ItemProperty $_.PsPath } | where { $_.DisplayName -like '*Visual Studio*' -and $_.InstallLocation.Length -gt 0 } | sort InstallDate -Descending | foreach { (Join-Path $_.InstallLocation 'Common7\IDE') } | where { Test-Path $_ } | select -First 1""
ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="DevEnvDir" />
</Exec>
</Target>
I use it to get the path to the VS command prompt batch files (like vcvars64.bat or vcvarsall.bat), so I can invoke them before calling MIDL.exe to generate a type library for my IDL file, so my .NET 5 COM classes can register a type library for themselves when the comhost.dll is being registered via regsvr32.exe.
Note that if you're using Visual Studio Express or Visual C++ Express the keynames contain WDExpress or VCExpress, respectively, instead of VisualStudio.
Aren't there environment settings?
I have VCToolkitInstallDir and VS71COMNTOOLS although I'm using Visual Studio 2003, I don't know if that changed for later versions. Type "set V" at the command line and see if you have them.
This is the easiest solution I came with. It works for x86 and x64, regardless of VS version:
Use
Environment.GetEnvironmentVariable("VSAPPIDDIR")
To get the IDE folder, such as:
"C:\Program Files\Microsoft Visual Studio\2019\Community\Common7\IDE\" On x86 machine.
You can use that to go to any other directory you want, such as:
Dim x = Environment.GetEnvironmentVariable("VSAPPIDDIR").Trim("\"c, "/"c)
x = System.IO.Path.GetDirectoryName(x)
Dim XsdFile = IO.Path.Combine(x, "Packages\Schemas\html\html_5.xsd")
In x64 machine XsdFile will refer to:
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Packages\Schemas\html\html_5.xsd"
Caution: This sems to work with Community edition only!
For newer versions of VS it is better to use from Microsoft provided APIs, because install information is no longer maintained in registry correctly.
install Nuget package Microsoft.VisualStudio.Setup.Configuration.Native
do the trick (returned is tuple with version and path of all VS instances):
private const int REGDB_E_CLASSNOTREG = unchecked((int)0x80040154);
public static IEnumerable<(string, string)> GetVisualStudioInstallPaths()
{
var result = new List<(string, string)>();
try
{
var query = new SetupConfiguration() as ISetupConfiguration2;
var e = query.EnumAllInstances();
int fetched;
var instances = new ISetupInstance[1];
do
{
e.Next(1, instances, out fetched);
if (fetched > 0)
{
var instance2 = (ISetupInstance2)instances[0];
result.Add((instance2.GetInstallationVersion(), instance2.GetInstallationPath()));
}
}
while (fetched > 0);
}
catch (COMException ex) when (ex.HResult == REGDB_E_CLASSNOTREG)
{
}
catch (Exception)
{
}
return result;
}
Regards

Resources