Skip to content

Commit a3d2d71

Browse files
authored
Merge pull request #26 from agileobjects/InstallerImprovements
Installer improvements
2 parents 53e070f + ec7b163 commit a3d2d71

File tree

8 files changed

+369
-288
lines changed

8 files changed

+369
-288
lines changed

ReadableExpressions.Visualizers.Installer.Custom/ReadableExpressions.Visualizers.Installer.Custom.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<ItemGroup>
3535
<Reference Include="System" />
3636
<Reference Include="System.Core" />
37+
<Reference Include="System.Windows.Forms" />
3738
<Reference Include="System.Xml.Linq" />
3839
<Reference Include="Microsoft.CSharp" />
3940
<Reference Include="System.Xml" />

ReadableExpressions.Visualizers.Installer.Custom/RegistryData.cs

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
namespace AgileObjects.ReadableExpressions.Visualizers.Installer.Custom
22
{
33
using System;
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.IO;
47
using System.Linq;
58
using Microsoft.Win32;
69

710
internal class RegistryData : IDisposable
811
{
9-
public RegistryData()
12+
private readonly FileVersionInfo _thisAssemblyVersion;
13+
14+
public RegistryData(FileVersionInfo thisAssemblyVersion)
1015
{
16+
_thisAssemblyVersion = thisAssemblyVersion;
1117
const string REGISTRY_KEY = @"SOFTWARE\Microsoft";
1218

1319
MsMachineKey = Registry.LocalMachine.OpenSubKey(REGISTRY_KEY);
1420

1521
if (MsMachineKey == null)
1622
{
23+
ErrorMessage = $"Unable to open the '{REGISTRY_KEY}' registry key";
1724
NoVisualStudio = true;
1825
return;
1926
}
@@ -25,6 +32,7 @@ public RegistryData()
2532

2633
if (vsKeyNames.Length == 0)
2734
{
35+
ErrorMessage = $@"Unable to find any '{REGISTRY_KEY}\VisualStudio' registry keys from which to determine your Visual Studio install paths";
2836
NoVisualStudio = true;
2937
return;
3038
}
@@ -35,6 +43,8 @@ public RegistryData()
3543
VsPost2015Data = vsKeyNames
3644
.Where(kn => kn.StartsWith("VisualStudio_"))
3745
.Select(kn => new VsPost2017Data(MsMachineKey.OpenSubKey(kn)))
46+
.GroupBy(d => d.InstallPath)
47+
.Select(grp => grp.First())
3848
.ToArray();
3949
}
4050

@@ -48,6 +58,92 @@ public RegistryData()
4858

4959
public bool NoVisualStudio { get; }
5060

61+
public string ErrorMessage { get; private set; }
62+
63+
public IEnumerable<Visualizer> GetInstallableVisualizersFor(Visualizer visualizer)
64+
{
65+
if (!TryPopulateInstallPaths(visualizer, out var targetVisualizers))
66+
{
67+
yield break;
68+
}
69+
70+
foreach (var targetVisualizer in targetVisualizers)
71+
{
72+
using (targetVisualizer)
73+
{
74+
var vsInstallPath = targetVisualizer.VsInstallDirectory;
75+
var indexOfIde = vsInstallPath.IndexOf("IDE", StringComparison.OrdinalIgnoreCase);
76+
var pathToCommon7 = vsInstallPath.Substring(0, indexOfIde);
77+
var pathToVisualizers = Path.Combine(pathToCommon7, "Packages", "Debugger", "Visualizers");
78+
var pathToExtensions = GetPathToExtensions(vsInstallPath);
79+
80+
targetVisualizer.SetInstallPath(pathToVisualizers);
81+
targetVisualizer.SetVsixManifestPath(pathToExtensions);
82+
targetVisualizer.PopulateVsSetupData();
83+
84+
yield return targetVisualizer;
85+
}
86+
}
87+
}
88+
89+
private bool TryPopulateInstallPaths(
90+
Visualizer visualizer,
91+
out ICollection<Visualizer> targetVisualizers)
92+
{
93+
targetVisualizers = new List<Visualizer>();
94+
95+
PopulatePre2017InstallPath(visualizer, targetVisualizers);
96+
PopulatePost2015InstallPaths(visualizer, targetVisualizers);
97+
98+
return targetVisualizers.Count > 0;
99+
}
100+
101+
private void PopulatePre2017InstallPath(
102+
Visualizer visualizer,
103+
ICollection<Visualizer> targetVisualizers)
104+
{
105+
var pre2017Key = VsPre2017KeyNames
106+
.FirstOrDefault(name => name == visualizer.VsFullVersionNumber);
107+
108+
if (pre2017Key == null)
109+
{
110+
return;
111+
}
112+
113+
var vsSubKey = VsPre2017MachineKey.OpenSubKey(pre2017Key);
114+
var installPath = vsSubKey?.GetValue("InstallDir") as string;
115+
116+
if (string.IsNullOrWhiteSpace(installPath) || !Directory.Exists(installPath))
117+
{
118+
return;
119+
}
120+
121+
targetVisualizers.Add(visualizer.With(vsSubKey, installPath));
122+
}
123+
124+
private void PopulatePost2015InstallPaths(
125+
Visualizer visualizer,
126+
ICollection<Visualizer> targetVisualizers)
127+
{
128+
var relevantDataItems = VsPost2015Data
129+
.Where(d => d.IsValid && (d.VsFullVersionNumber == visualizer.VsFullVersionNumber));
130+
131+
foreach (var dataItem in relevantDataItems)
132+
{
133+
targetVisualizers.Add(visualizer.With(dataItem.RegistryKey, dataItem.InstallPath));
134+
}
135+
}
136+
137+
private string GetPathToExtensions(string vsInstallPath)
138+
{
139+
return Path.Combine(
140+
vsInstallPath,
141+
"Extensions",
142+
_thisAssemblyVersion.CompanyName,
143+
_thisAssemblyVersion.ProductName,
144+
_thisAssemblyVersion.FileVersion);
145+
}
146+
51147
public void Dispose()
52148
{
53149
MsMachineKey?.Dispose();
Lines changed: 190 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,216 @@
11
namespace AgileObjects.ReadableExpressions.Visualizers.Installer.Custom
22
{
33
using System;
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Reflection;
8+
using System.Text;
9+
using System.Text.RegularExpressions;
410
using Microsoft.Win32;
511

612
internal class Visualizer : IDisposable
713
{
8-
public int VsVersionNumber { get; set; }
14+
private static readonly Assembly _thisAssembly = typeof(Visualizer).Assembly;
15+
16+
private readonly Action<string> _logger;
17+
private readonly Version _version;
18+
private readonly string _vsixManifest;
19+
private RegistryKey _registryKey;
20+
private string _vsExePath;
21+
private string _vsSetupArgument;
22+
private string _installPath;
23+
private string _vsixManifestPath;
24+
25+
public Visualizer(
26+
Action<string> logger,
27+
FileVersionInfo version,
28+
string vsixManifest,
29+
string resourceName)
30+
: this(logger, new Version(version.FileVersion), vsixManifest, resourceName, GetVsVersionNumber(resourceName))
31+
{
32+
}
33+
34+
#region Setup
35+
36+
private static readonly Regex _versionNumberMatcher =
37+
new Regex(@"Vs(?<VersionNumber>[\d]+)\.dll$", RegexOptions.IgnoreCase);
38+
39+
private static int GetVsVersionNumber(string resourceName)
40+
{
41+
var matchValue = _versionNumberMatcher
42+
.Match(resourceName)
43+
.Groups["VersionNumber"]
44+
.Value;
45+
46+
return int.Parse(matchValue);
47+
}
48+
49+
#endregion
50+
51+
private Visualizer(
52+
Action<string> logger,
53+
Version version,
54+
string vsixManifest,
55+
string resourceName,
56+
int vsVersionNumber)
57+
{
58+
_logger = logger;
59+
_version = version;
60+
_vsixManifest = vsixManifest;
61+
ResourceName = resourceName;
62+
VsVersionNumber = vsVersionNumber;
63+
}
64+
65+
public int VsVersionNumber { get; }
966

1067
public string VsFullVersionNumber => VsVersionNumber + ".0";
1168

12-
public string ResourceName { get; set; }
69+
public string ResourceName { get; }
70+
71+
public string GetResourceFileName()
72+
{
73+
var resourceAssemblyNameLength = (typeof(Visualizer).Namespace?.Length + 1).GetValueOrDefault();
74+
var resourceFileName = ResourceName.Substring(resourceAssemblyNameLength);
75+
76+
return resourceFileName;
77+
}
78+
79+
public string VsInstallDirectory { get; private set; }
80+
81+
public void SetInstallPath(string pathToVisualizers)
82+
=> _installPath = Path.Combine(pathToVisualizers, GetResourceFileName());
83+
84+
public void SetVsixManifestPath(string pathToExtensions)
85+
=> _vsixManifestPath = Path.Combine(pathToExtensions, "extension.vsixmanifest");
86+
87+
public void Install()
88+
{
89+
// ReSharper disable once AssignNullToNotNullAttribute
90+
if (!Directory.Exists(Path.GetDirectoryName(_installPath)))
91+
{
92+
Log("Skipping as directory does not exist: " + _installPath);
93+
return;
94+
}
95+
96+
using (var resourceStream = _thisAssembly.GetManifestResourceStream(ResourceName))
97+
using (var visualizerFileStream = File.OpenWrite(_installPath))
98+
{
99+
Log("Writing visualizer to " + _installPath);
100+
// ReSharper disable once PossibleNullReferenceException
101+
resourceStream.CopyTo(visualizerFileStream);
102+
}
103+
104+
var manifestDirectory = Path.GetDirectoryName(_vsixManifestPath);
105+
106+
Log("Writing manifest to " + _vsixManifestPath);
107+
// ReSharper disable once AssignNullToNotNullAttribute
108+
Directory.CreateDirectory(manifestDirectory);
109+
File.WriteAllText(_vsixManifestPath, _vsixManifest, Encoding.ASCII);
110+
111+
ResetVsExtensions();
112+
}
113+
114+
public DirectoryInfo GetVsixManifestDirectory()
115+
{
116+
// ReSharper disable once AssignNullToNotNullAttribute
117+
return new DirectoryInfo(Path.GetDirectoryName(_vsixManifestPath));
118+
}
119+
120+
public void Uninstall()
121+
{
122+
DeletePreviousManifests();
123+
124+
// ReSharper disable PossibleNullReferenceException
125+
if (File.Exists(_vsixManifestPath))
126+
{
127+
var vsixManifestDirectory = GetVsixManifestDirectory();
128+
var productDirectory = vsixManifestDirectory.Parent;
129+
var companyDirectory = productDirectory.Parent;
130+
131+
Log("Deleting previous manifest directory " + vsixManifestDirectory.FullName);
132+
vsixManifestDirectory.Delete(recursive: true);
133+
134+
if (!productDirectory.GetDirectories().Any())
135+
{
136+
Log("Deleting previous manifest product directory " + productDirectory.FullName);
137+
productDirectory.Delete(recursive: true);
138+
139+
if (!companyDirectory.GetDirectories().Any())
140+
{
141+
Log("Deleting previous manifest company directory " + companyDirectory.FullName);
142+
companyDirectory.Delete(recursive: true);
143+
}
144+
}
145+
146+
ResetVsExtensions();
147+
}
148+
// ReSharper restore PossibleNullReferenceException
149+
150+
if (File.Exists(_installPath))
151+
{
152+
File.Delete(_installPath);
153+
}
154+
}
13155

14-
public RegistryKey RegistryKey { get; set; }
156+
private void DeletePreviousManifests()
157+
{
158+
var productDirectory = GetVsixManifestDirectory().Parent;
15159

16-
public string VsInstallDirectory { get; set; }
160+
if ((productDirectory == null) || !productDirectory.Exists)
161+
{
162+
return;
163+
}
17164

18-
public string InstallPath { get; set; }
165+
// ReSharper disable once PossibleNullReferenceException
166+
foreach (var versionDirectory in productDirectory.GetDirectories("*"))
167+
{
168+
if (Version.TryParse(versionDirectory.Name, out var directoryVersion))
169+
{
170+
if (directoryVersion != _version)
171+
{
172+
Log("Deleting previous manifest version directory " + versionDirectory.FullName);
173+
versionDirectory.Delete(recursive: true);
174+
}
175+
}
176+
}
177+
}
19178

20-
public string VsixManifestPath { get; set; }
179+
private void ResetVsExtensions()
180+
{
181+
if (_vsExePath == null)
182+
{
183+
return;
184+
}
21185

22-
public string VsExePath { get; set; }
186+
Log("Updating VS extension records using " + _vsExePath);
187+
using (Process.Start(_vsExePath, _vsSetupArgument)) { }
188+
}
23189

24-
public string VsSetupArgument { get; set; }
190+
public void PopulateVsSetupData()
191+
{
192+
var pathToDevEnv = Path.Combine(VsInstallDirectory, "devenv.exe");
193+
194+
if (!File.Exists(pathToDevEnv))
195+
{
196+
return;
197+
}
198+
199+
_vsExePath = pathToDevEnv;
200+
_vsSetupArgument = _registryKey?.GetValue("SetupCommandLine") as string ?? "/setup";
201+
}
25202

26203
public Visualizer With(RegistryKey registryKey, string vsInstallPath)
27204
{
28-
return new Visualizer
205+
return new Visualizer(_logger, _version, _vsixManifest, ResourceName, VsVersionNumber)
29206
{
30-
VsVersionNumber = VsVersionNumber,
31-
ResourceName = ResourceName,
32-
RegistryKey = registryKey,
207+
_registryKey = registryKey,
33208
VsInstallDirectory = vsInstallPath
34209
};
35210
}
36211

37-
public void Dispose()
38-
{
39-
RegistryKey?.Dispose();
40-
}
212+
private void Log(string message) => _logger.Invoke(message);
213+
214+
public void Dispose() => _registryKey?.Dispose();
41215
}
42216
}

0 commit comments

Comments
 (0)