diff --git a/readme.md b/readme.md
index 2e05e12..8a55656 100644
--- a/readme.md
+++ b/readme.md
@@ -107,6 +107,7 @@ Usage: dnx vs -- install [options]
| `sku` | Edition, one of `e\|ent\|enterprise`, `p\|pro\|professional`, `c\|com\|community`, `b\|build\|buildtools` or `t\|test\|testagent` |
| `filter` | Expression to filter VS instances. E.g. `x => x.InstanceId = '123'` |
| `nick\|nickname` | Optional nickname to use |
+| `v\|version` | Install specific (semantic) version, such as 18.7 or 18.7.3 |
| `add` | A workload ID |
@@ -129,6 +130,12 @@ Examples:
# Install VS community with the .NET Core, ASP.NET and Azure workloads,
# shows installation progress and waits for it to finish before returning
> dnx vs -- install +core +web +azure
+
+# Install VS 18 Enterprise
+> dnx vs -- install -v:18 -sku:ent
+
+# Install the latest VS 17 (2022) Community
+> dnx vs -- install --version 17
```
@@ -279,7 +286,20 @@ Usage: dnx vs -- update [options]
| `filter` | Expression to filter VS instances. E.g. `x => x.InstanceId = '123'` |
| `first` | Update first matching instance. |
| `all` | Update all instances. |
+| `v\|version` | Update specific (semantic) version, such as 18.7 or 18.7.3 |
+
+
+Examples:
+
+```
+# Update the installed VS 18.7 instance
+> dnx vs -- update -v:18.7
+
+# Update all matching VS 17 instances
+> dnx vs -- update --version 17 --all
+```
+
## where
diff --git a/src/VisualStudio.Tests/Commands/VersionOptionTests.cs b/src/VisualStudio.Tests/Commands/VersionOptionTests.cs
new file mode 100644
index 0000000..98a6b47
--- /dev/null
+++ b/src/VisualStudio.Tests/Commands/VersionOptionTests.cs
@@ -0,0 +1,58 @@
+using System.CommandLine;
+using System.Linq;
+using Xunit;
+
+namespace Devlooped.Tests
+{
+ public class VersionOptionTests
+ {
+ [Theory]
+ [InlineData(Commands.Run)]
+ [InlineData(Commands.Install)]
+ [InlineData(Commands.Update)]
+ public void when_command_is_defined_then_it_accepts_version_option(string commandName)
+ {
+ var root = new VsRootCommand();
+ var command = root.Subcommands.Single(c => c.Name == commandName);
+
+ Assert.Contains(command.Options, o => o.Name == "--version" && o.Aliases.Contains("-v"));
+ }
+
+ [Theory]
+ [InlineData(Commands.Install, "--version", "18.7")]
+ [InlineData(Commands.Install, "-v", "18.7.3")]
+ [InlineData(Commands.Update, "--version", "18.7")]
+ [InlineData(Commands.Update, "-v", "17.14")]
+ [InlineData(Commands.Run, "--version", "18.7")]
+ [InlineData(Commands.Run, "-v", "18.7.3")]
+ public void when_parsing_version_then_option_is_bound_and_not_unmatched(
+ string commandName, string option, string value)
+ {
+ var root = new VsRootCommand();
+ var parse = root.Parse(new[] { commandName, option, value });
+
+ Assert.Empty(parse.Errors);
+ Assert.DoesNotContain(option, parse.UnmatchedTokens);
+ Assert.DoesNotContain(value, parse.UnmatchedTokens);
+
+ var version = parse.CommandResult.Command.Options.OfType