Programmatically retrieve Visual Studio install directory - visual-studio

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

Related

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
}
}

Is it possible programmatically add folders to the Windows 10 Quick Access panel in the explorer window?

Apparently Microsoft has (sort of) replaced the "Favorites" Windows explorer item with the Quick Access item. But I haven't been able to find a way to programmatically add folders to it (neither on Google not MSDN). Is there no way to do this yet?
There is a simple way to do it in powershell (at least) :
$o = new-object -com shell.application
$o.Namespace('c:\My Folder').Self.InvokeVerb("pintohome")
Hope it helps.
Yohan Ney's answer for pinning an item is correct. To unpin an item you can do this:
$QuickAccess = New-Object -ComObject shell.application 
($QuickAccess.Namespace("shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}").Items() | where {$_.Path -eq "C:\Temp"}).InvokeVerb("unpinfromhome")
Here's a script I wrote to make pin/unpin a little easier:
https://gallery.technet.microsoft.com/Set-QuickAccess-117e9a89
Maybe it will help someone until MS releases an API.
I ran procmon and it seems that these registry keys are involved
Pin to Quick access:
HKEY_CLASSES_ROOT\Folder\shell\pintohome
When unpin:
HKEY_CLASSES_ROOT\PinnedFrequentPlace\shell\unpinfromhome\command
Also this resource is used when pinning: (EDIT1: can't find it any longer..)
AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations\{SOME_SORT_OF_GUID}.automaticDestinations-ms
You can try opening it with 7-zip, there are several files in there which fit the destination
EDIT2: I found running this in the 'Run' opens up Quick access:
shell:::{679F85CB-0220-4080-B29B-5540CC05AAB6}
I got an answer here:
Windows 10 - Programmatically use Quick Access
Apparently, it's not possible yet, but a proposition for such an API has been made.
I like Johan's answer but I added a little bit to make not remove some of the items that were already in there. I had a ton pinned in there by accident I must have selected pin folder or something to quick access.
$QuickAccess = New-Object -ComObject shell.application
$okItems = #("Desktop","Downloads","Documents","Pictures","iCloud Photos","iCloud Drive","PhpstormProjects","Wallpapers 5","Videos", "Schedules for testing")
($QuickAccess.Namespace("shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}").Items() | where {$_.name -notin $okItems}).InvokeVerb("unpinfromhome");
Building on what others have said... This allows you to remove all pinned folders (not just all/recent folders/items):
$o = new-object -com shell.application
$($o.Namespace("shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}").Items() | where { $_.IsFolder -eq "True" -and ($($_.Verbs() | Where-Object {$_.Name -in "Unpin from Quick access"}) -ne $null)}).InvokeVerb("unpinfromhome")
I needed this so I could backup / restore my list of Quick Access links quickly. So I put this at the top of my script (to remove all pinned items, then the rest of the script re-adds them. This ensures the order is correct.
And yes, I'm sure there's a better syntax for the above code.
EDIT: After further investigation, I have realized Quick Access contains two "sections". One is Pinned Items, and the other is Frequent Folders. For some reason, Music and Videos come by default on the second section (at least in 1909), unlike the rest (Desktop/Downloads/Documents/Pictures). So the verb to invoke changes from unpinfromhome to removefromhome (defined in HKEY_CLASSES_ROOT\FrequentPlace, CLSID: {b918dbc4-162c-43e5-85bf-19059a776e9e}). In PowerShell:
$Unpin = #("$env:USERPROFILE\Videos","$env:USERPROFILE\Music")
$qa = New-Object -ComObject shell.application
$ob = $qa.Namespace('shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}').Items() | ? {$_.Path -in $Unpin}
$ob.InvokeVerb('removefromhome')
In Windows 1909, you can't unpin the Music or Videos links from Quick Access with the proposed PowerShell solution. It seems they're special because they don't include the "pin" icon, unlike the rest.
The solution is to pin and unpin them. I don't know much about the Windows API or PowerShell so there may be a less convoluted way.
$Unpin = #("$env:USERPROFILE\Videos","$env:USERPROFILE\Music")
$qa = New-Object -ComObject shell.application
ForEach ($dir in $Unpin) { $qa.Namespace($dir).Self.InvokeVerb('pintohome') }
$ob = $qa.Namespace('shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}').Items() | ? {$_.Path -in $Unpin}
$ob.InvokeVerb('unpinfromhome')
Another way is renaming f01b4d95cf55d32a.automaticDestinations-ms, then logging off/rebooting so that it's recreated. But I don't know if it has side effects. Batch script:
:: f01b4d95cf55d32a => Frequent Folders
:: 5f7b5f1e01b83767 => Recent Files
rename "%APPDATA%\Microsoft\Windows\Recent\AutomaticDestinations\f01b4d95cf55d32a.automaticDestinations-ms" f01b4d95cf55d32a.automaticDestinations-ms.bak
void PinToHome(const std::wstring& folder)
{
ShellExecute(0, L"pintohome", folder.c_str(), L"", L"", SW_HIDE);
}
that was the easy part, still unable to do an unpinfromhome
I was able to get this to work in C# using shell32 based on the information in this post and some info on shell32 from this post https://stackoverflow.com/a/19035049
You need to add a reference to "Microsoft Shell Controls and Automation".
This will add a link
Type shellAppType = Type.GetTypeFromProgID("Shell.Application");
Object shell = Activator.CreateInstance(shellAppType);
Shell32.Folder2 f = (Shell32.Folder2)shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, new object[] { "C:\\temp" });
f.Self.InvokeVerb("pintohome");
This will remove a link by name
Type shellAppType = Type.GetTypeFromProgID("Shell.Application");
Object shell = Activator.CreateInstance(shellAppType);
Shell32.Folder2 f2 = (Shell32.Folder2)shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, new object[] { "shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}" });
Console.WriteLine("item count: " + f2.Items().Count);
foreach (FolderItem fi in f2.Items())
{
Console.WriteLine(fi.Name);
if (fi.Name == "temp")
{
((FolderItem)fi).InvokeVerb("unpinfromhome");
}
}
For those that work with .NET Core:
Sadly, you cannot include a reference to "Microsoft Shell Controls and Automation" in the build-process.
But you can instead use dynamic, and omit the reference:
public static void PinToQuickAccess(string folder)
{
// You need to include "Microsoft Shell Controls and Automation" reference
// Cannot include reference in .NET Core
System.Type shellAppType = System.Type.GetTypeFromProgID("Shell.Application");
object shell = System.Activator.CreateInstance(shellAppType);
// Shell32.Folder2 f = (Shell32.Folder2)shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, new object[] { folder });
dynamic f = shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, new object[] { folder });
f.Self.InvokeVerb("pintohome");
}
And to unpin:
public static void UnpinFromQuickAccess(string folder)
{
// You need to include "Microsoft Shell Controls and Automation" reference
System.Type shellAppType = System.Type.GetTypeFromProgID("Shell.Application");
object shell = System.Activator.CreateInstance(shellAppType);
// Shell32.Folder2 f2 = (Shell32.Folder2)shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, new object[] { "shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}" });
dynamic f2 = shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, new object[] { "shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}" });
foreach (dynamic fi in f2.Items())
{
if (string.Equals(fi.Path, folder))
{
fi.InvokeVerb("unpinfromhome");
}
}
}

Invoke source control compare operation in vs2012 extension

I am writing a vs2012 extension that will talk to TFS 2010 (though I would prefer if it could also work with tfs2012).
I need to invoke a compare operations on a file from the extension.
I want to use the default compare tool that is configured in visual studio at the moment of the innovation (because the user can configure a different compare tool).
I have the location of the file and I want to be able to invoke the following:
open the default compare.
open a compare with latest version
open a compare with workspace version
Use IVsDifferenceService to invoke Visual Studio diff tool from your VSPackage:
private void Compare(string leftFile, string rightFile)
{
var diffService = (IVsDifferenceService)GetService(typeof(SVsDifferenceService));
if (diffService != null)
{
ErrorHandler.ThrowOnFailure(
diffService.OpenComparisonWindow(leftFile, rightFile).Show()
);
}
}
To test it you need to set the workspace and download the file you want to compare:
// TODO: add some error handling
var tpc = new TfsTeamProjectCollection(new Uri("http://tfs.company.com:8080/tfs"));
var vcs = tpc.GetService<VersionControlServer>();
var workspace = vcs.GetWorkspace(Environment.MachineName, vcs.AuthorizedUser);
string localItem = #"C:\workspace\project\somefile.cs";
var folder = workspace.GetWorkingFolderForLocalItem(localItem);
var item = vcs.GetItem(folder.ServerItem, VersionSpec.Latest);
var latestItem = string.Format("{0}~{1}", localItem, item.ChangesetId);
item.DownloadFile(latestItem);
Compare(localItem, latestItem);
References:
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;

How to get expanded path from EnvDTE / VCProjectEngine

I am trying to write a tool to create a zip file containing all PDBs files from one Visual Studio 2010 solution.
I can get every PDB filepath in the solution with the following code. However, the property value contains Visual Studio macro like $(TargetDir), $(TargetName) and so on.
Is there a function in the EnvDTE API to expand those macros to their current values ?
On the other hand, any other methods that would achieve my initial goal are also welcome !
Thanks
System.Type t = System.Type.GetTypeFromProgID("VisualStudio.DTE.10.0");
object obj = Activator.CreateInstance(t, true);
DTE dte = (DTE)obj;
Solution sln = dte.Solution;
sln.Open(args[0]);
while (sln.IsOpen == false)
{
System.Threading.Thread.Sleep(100);
}
sln.SolutionBuild.SolutionConfigurations.Item("Release").Activate();
foreach (EnvDTE.Project project in sln.Projects)
{
Console.WriteLine("Inspecting project {0}", project.Name);
VCProject vcproj = (VCProject)project.Object;
if (vcproj == null) // this is not a visual c++ project
continue;
IVCCollection cfgs = vcproj.Configurations;
VCConfiguration cfg = cfgs.Item(1);
VCLinkerTool tool = cfg.Tools("VCLinkerTool");
if (tool == null) // this is not a DLL/EXE project
continue;
Console.WriteLine("Program database = " + tool.ProgramDatabaseFile);
}
I haven't tried this with VS2010, but in VS2008 you can call VCConfiguration.Evaluate to do this. In your example, it would be something like this:
string evaluatedPdbPath = cfg.Evaluate(tool.ProgramDatabaseFile);

Save all files in Visual Studio project as UTF-8

I wonder if it's possible to save all files in a Visual Studio 2008 project into a specific character encoding. I got a solution with mixed encodings and I want to make them all the same (UTF-8 with signature).
I know how to save single files, but how about all files in a project?
Since you're already in Visual Studio, why not just simply write the code?
foreach (var f in new DirectoryInfo(#"...").GetFiles("*.cs", SearchOption.AllDirectories)) {
string s = File.ReadAllText(f.FullName);
File.WriteAllText (f.FullName, s, Encoding.UTF8);
}
Only three lines of code! I'm sure you can write this in less than a minute :-)
This may be of some help.
link removed due to original reference being defaced by spam site.
Short version: edit one file, select File -> Advanced Save Options. Instead of changing UTF-8 to Ascii, change it to UTF-8. Edit: Make sure you select the option that says no byte-order-marker (BOM)
Set code page & hit ok. It seems to persist just past the current file.
In case you need to do this in PowerShell, here is my little move:
Function Write-Utf8([string] $path, [string] $filter='*.*')
{
[IO.SearchOption] $option = [IO.SearchOption]::AllDirectories;
[String[]] $files = [IO.Directory]::GetFiles((Get-Item $path).FullName, $filter, $option);
foreach($file in $files)
{
"Writing $file...";
[String]$s = [IO.File]::ReadAllText($file);
[IO.File]::WriteAllText($file, $s, [Text.Encoding]::UTF8);
}
}
I would convert the files programmatically (outside VS), e.g. using a Python script:
import glob, codecs
for f in glob.glob("*.py"):
data = open("f", "rb").read()
if data.startswith(codecs.BOM_UTF8):
# Already UTF-8
continue
# else assume ANSI code page
data = data.decode("mbcs")
data = codecs.BOM_UTF8 + data.encode("utf-8")
open("f", "wb").write(data)
This assumes all files not in "UTF-8 with signature" are in the ANSI code page - this is the same what VS 2008 apparently also assumes. If you know that some files have yet different encodings, you would have to specify what these encodings are.
Using C#:
1) Create a new ConsoleApplication, then install Mozilla Universal Charset Detector
2) Run code:
static void Main(string[] args)
{
const string targetEncoding = "utf-8";
foreach (var f in new DirectoryInfo(#"<your project's path>").GetFiles("*.cs", SearchOption.AllDirectories))
{
var fileEnc = GetEncoding(f.FullName);
if (fileEnc != null && !string.Equals(fileEnc, targetEncoding, StringComparison.OrdinalIgnoreCase))
{
var str = File.ReadAllText(f.FullName, Encoding.GetEncoding(fileEnc));
File.WriteAllText(f.FullName, str, Encoding.GetEncoding(targetEncoding));
}
}
Console.WriteLine("Done.");
Console.ReadKey();
}
private static string GetEncoding(string filename)
{
using (var fs = File.OpenRead(filename))
{
var cdet = new Ude.CharsetDetector();
cdet.Feed(fs);
cdet.DataEnd();
if (cdet.Charset != null)
Console.WriteLine("Charset: {0}, confidence: {1} : " + filename, cdet.Charset, cdet.Confidence);
else
Console.WriteLine("Detection failed: " + filename);
return cdet.Charset;
}
}
I have created a function to change encoding files written in asp.net.
I searched a lot. And I also used some ideas and codes from this page. Thank you.
And here is the function.
Function ChangeFileEncoding(pPathFolder As String, pExtension As String, pDirOption As IO.SearchOption) As Integer
Dim Counter As Integer
Dim s As String
Dim reader As IO.StreamReader
Dim gEnc As Text.Encoding
Dim direc As IO.DirectoryInfo = New IO.DirectoryInfo(pPathFolder)
For Each fi As IO.FileInfo In direc.GetFiles(pExtension, pDirOption)
s = ""
reader = New IO.StreamReader(fi.FullName, Text.Encoding.Default, True)
s = reader.ReadToEnd
gEnc = reader.CurrentEncoding
reader.Close()
If (gEnc.EncodingName <> Text.Encoding.UTF8.EncodingName) Then
s = IO.File.ReadAllText(fi.FullName, gEnc)
IO.File.WriteAllText(fi.FullName, s, System.Text.Encoding.UTF8)
Counter += 1
Response.Write("<br>Saved #" & Counter & ": " & fi.FullName & " - <i>Encoding was: " & gEnc.EncodingName & "</i>")
End If
Next
Return Counter
End Function
It can placed in .aspx file and then called like:
ChangeFileEncoding("C:\temp\test", "*.ascx", IO.SearchOption.TopDirectoryOnly)
if you are using TFS with VS :
http://msdn.microsoft.com/en-us/library/1yft8zkw(v=vs.100).aspx
Example :
tf checkout -r -type:utf-8 src/*.aspx
Thanks for your solutions, this code has worked for me :
Dim s As String = ""
Dim direc As DirectoryInfo = New DirectoryInfo("Your Directory path")
For Each fi As FileInfo In direc.GetFiles("*.vb", SearchOption.AllDirectories)
s = File.ReadAllText(fi.FullName, System.Text.Encoding.Default)
File.WriteAllText(fi.FullName, s, System.Text.Encoding.Unicode)
Next
If you want to avoid this type of error :
Use this following code :
foreach (var f in new DirectoryInfo(#"....").GetFiles("*.cs", SearchOption.AllDirectories))
{
string s = File.ReadAllText(f.FullName, Encoding.GetEncoding(1252));
File.WriteAllText(f.FullName, s, Encoding.UTF8);
}
Encoding number 1252 is the default Windows encoding used by Visual Studio to save your files.
Convert from UTF-8-BOM to UTF-8
Building on rasx's answer, here is a PowerShell function that assumes your current files are already encoded in UTF-8 (but maybe with BOM) and converts them to UTF-8 without BOM, therefore preserving existing Unicode characters.
Function Write-Utf8([string] $path, [string] $filter='*')
{
[IO.SearchOption] $option = [IO.SearchOption]::AllDirectories;
[String[]] $files = [IO.Directory]::GetFiles((Get-Item $path).FullName, $filter, $option);
foreach($file in $files)
{
"Writing $file...";
[String]$s = [IO.File]::ReadAllText($file, [Text.Encoding]::UTF8);
[Text.Encoding]$e = New-Object -TypeName Text.UTF8Encoding -ArgumentList ($false);
[IO.File]::WriteAllText($file, $s, $e);
}
}
Experienced encoding problems after converting solution from VS2008 to VS2015. After conversion all project files was encoded in ANSI, but they contained UTF8 content and was recongnized as ANSI files in VS2015. Tried many conversion tactics, but worked only this solution.
Encoding encoding = Encoding.Default;
String original = String.Empty;
foreach (var f in new DirectoryInfo(path).GetFiles("*.cs", SearchOption.AllDirectories))
{
using (StreamReader sr = new StreamReader(f.FullName, Encoding.Default))
{
original = sr.ReadToEnd();
encoding = sr.CurrentEncoding;
sr.Close();
}
if (encoding == Encoding.UTF8)
continue;
byte[] encBytes = encoding.GetBytes(original);
byte[] utf8Bytes = Encoding.Convert(encoding, Encoding.UTF8, encBytes);
var utf8Text = Encoding.UTF8.GetString(utf8Bytes);
File.WriteAllText(f.FullName, utf8Text, Encoding.UTF8);
}
the item is removed from the menu in Visual Studio 2017
You can still access the functionality through File-> Save As -> then clicking the down arrow on the Save button and clicking "Save With Encoding...".
You can also add it back to the File menu through Tools->Customize->Commands if you want to.
I'm only offering this suggestion in case there's no way to automatically do this in Visual Studio (I'm not even sure this would work):
Create a class in your project named 足の不自由なハッキング (or some other unicode text that will force Visual Studio to encode as UTF-8).
Add "using MyProject.足の不自由なハッキング;" to the top of each file. You should be able to do it on everything by doing a global replace of "using System.Text;" with "using System.Text;using MyProject.足の不自由なハッキング;".
Save everything. You may get a long string of "Do you want to save X.cs using UTF-8?" messages or something.

Resources