11using System ;
2+ using System . Collections . Generic ;
23using System . Diagnostics ;
34using System . IO ;
5+ using System . Linq ;
46using System . Runtime . InteropServices ;
57using System . Runtime . InteropServices . ComTypes ;
68using System . Threading . Tasks ;
@@ -12,12 +14,18 @@ internal class VisualStudioIDEControl : IIDEControl
1214 private readonly RuntimeServer _runtimeServer ;
1315 private readonly IProcessLog _processLog ;
1416 private readonly IExecution _execution ;
17+ private readonly IProtobuildProvider _protobuildProvider ;
1518
16- public VisualStudioIDEControl ( RuntimeServer runtimeServer , IProcessLog processLog , IExecution execution )
19+ public VisualStudioIDEControl (
20+ RuntimeServer runtimeServer ,
21+ IProcessLog processLog ,
22+ IExecution execution ,
23+ IProtobuildProvider protobuildProvider )
1724 {
1825 _runtimeServer = runtimeServer ;
1926 _processLog = processLog ;
2027 _execution = execution ;
28+ _protobuildProvider = protobuildProvider ;
2129 }
2230
2331 public async Task LoadSolution ( string modulePath , string moduleName , string targetPlatform , string oldPlatformOnFail , bool isProtobuild )
@@ -326,9 +334,88 @@ public async Task CloseGenerateAndLoadSolution(string modulePath, string moduleN
326334 private async Task < dynamic > LaunchVisualStudio ( string modulePath , string moduleName , string targetPlatform , int ? vspid , dynamic existing )
327335 {
328336 _runtimeServer . Set ( "status" , "Starting Visual Studio..." ) ;
337+
338+ // Newer installs of Visual Studio (like 2017) don't create registry entries for MSBuild, so we have to
339+ // use a tool called vswhere in order to find MSBuild on these systems. This call will implicitly install
340+ // the vswhere package if it's not already installed.
341+ var protobuild = await _protobuildProvider . GetProtobuild ( status =>
342+ {
343+ _runtimeServer . Set ( "status" , status ) ;
344+ } ) ;
345+ List < string > installations = null ;
346+ if ( protobuild != null && File . Exists ( protobuild ) )
347+ {
348+ try
349+ {
350+ var processStartInfo = new ProcessStartInfo ( ) ;
351+ processStartInfo . FileName = protobuild ;
352+ processStartInfo . Arguments = "--execute vswhere -products * -requires Microsoft.VisualStudio.Workload.CoreEditor -property installationPath" ;
353+ processStartInfo . UseShellExecute = false ;
354+ processStartInfo . RedirectStandardOutput = true ;
355+ processStartInfo . CreateNoWindow = true ;
356+ var process = Process . Start ( processStartInfo ) ;
357+ var installationsString = process . StandardOutput . ReadToEnd ( ) ;
358+ installations = installationsString . Split ( new [ ] { Environment . NewLine } , StringSplitOptions . RemoveEmptyEntries ) . Where ( x => ! string . IsNullOrWhiteSpace ( x ) ) . ToList ( ) ;
359+ process . WaitForExit ( ) ;
360+
361+ if ( process . ExitCode != 0 )
362+ {
363+ // Try to install vswhere (it may not be installed).
364+ processStartInfo = new ProcessStartInfo ( ) ;
365+ processStartInfo . FileName = protobuild ;
366+ processStartInfo . Arguments = "--install Protobuild.vswhere" ;
367+ processStartInfo . UseShellExecute = false ;
368+ processStartInfo . RedirectStandardOutput = true ;
369+ processStartInfo . CreateNoWindow = true ;
370+ process = Process . Start ( processStartInfo ) ;
371+ process . WaitForExit ( ) ;
372+
373+ if ( process . ExitCode != 0 )
374+ {
375+ // Can't use vswhere.
376+ }
377+ else
378+ {
379+ // Install worked, try again.
380+ processStartInfo = new ProcessStartInfo ( ) ;
381+ processStartInfo . FileName = protobuild ;
382+ processStartInfo . Arguments = "--execute vswhere -products * -requires Microsoft.VisualStudio.Workload.CoreEditor -property installationPath" ;
383+ processStartInfo . UseShellExecute = false ;
384+ processStartInfo . RedirectStandardOutput = true ;
385+ processStartInfo . CreateNoWindow = true ;
386+ process = Process . Start ( processStartInfo ) ;
387+ installationsString = process . StandardOutput . ReadToEnd ( ) ;
388+ installations = installationsString . Split ( new [ ] { Environment . NewLine } , StringSplitOptions . RemoveEmptyEntries ) . Where ( x => ! string . IsNullOrWhiteSpace ( x ) ) . ToList ( ) ;
389+ process . WaitForExit ( ) ;
390+ }
391+ }
392+ }
393+ catch ( Exception ex )
394+ {
395+ // Can't find any installations.
396+ }
397+ }
329398
330- var versions = new [ ] { "14.0" , "12.0" , "11.0" , "10.0" } ;
331399 var started = false ;
400+
401+ if ( installations != null )
402+ {
403+ // Check if the IDE is present in any of those installation paths.
404+ foreach ( var basePath in installations )
405+ {
406+ var ideLocation = Path . Combine ( basePath , "Common7\\ IDE\\ devenv.exe" ) ;
407+ if ( File . Exists ( ideLocation ) )
408+ {
409+ Process . Start (
410+ ideLocation ,
411+ "\" " + Path . Combine ( modulePath , moduleName + "." + targetPlatform + ".sln" ) + "\" " ) ;
412+ started = true ;
413+ break ;
414+ }
415+ }
416+ }
417+
418+ var versions = new [ ] { "14.0" , "12.0" , "11.0" , "10.0" } ;
332419 foreach ( var version in versions )
333420 {
334421 var idePath = @"C:\Program Files (x86)\Microsoft Visual Studio " + version +
0 commit comments