microsoft / vswhere

Locate Visual Studio 2017 and newer installations
MIT License
921 stars 97 forks source link

Add support for outputting nickname of installed instances #104

Closed abelbraaksma closed 6 years ago

abelbraaksma commented 6 years ago

Visual Studio 2017 and up allows users to select a custom nickname for side-by-side installations after the original installations.

It would be nice if this were supported and visible in the output, as this nickname is used in the start menu and when icons are created on the taskbar. As such, it works as a mnemonic for which installation maps to what nickname. Since a nickname can be used with VSIX commandline installations, this feature is invaluable if you want to use vswhere in batch-files and take proper care of side-by-side installations.

Currently, the only way to do this is by their hash value (i.e., instanceId), I think, which has no natural relation to a nickname.

In case it helps, the nickname appears to be stored in state.json in the C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances\366261d3 folder. This folder, as far as I can see, is currently not part of the result set of vswhere either.

I think this folder is fixed to be C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances\[instanceIdHere], which is simple enough. The Preview editions follow the same procedure.

image

heaths commented 6 years ago

You can get this from Get-VSSetupInstance with our PowerShell cmdlets. I'll consider adding the product but the nickname is a custom property that only some SKUs (VS, namely) use.

If you're looking to get custom display information, you might consider using the query API directly. vswhere is targeted at script-based detection of Visual Studio 2017 and related products. Do you have a use case for the nickname? The main problem is that there's nothing preventing a nested property like that from conflicting with a top-level property, and with vswhere the design means data must be flattened.

I also don't recommend you use the JSON file. It has and will change (I'm working on a change to the format right now, in fact - but the query API will always work), and may not even be JSON in the future. That's why the query API is there, and does support what you want. See https://github.com/Microsoft/vs-setup-samples for more samples beyond the PowerShell cmdlets that do expose the properties as a nested property bag.

abelbraaksma commented 6 years ago

You can get this from Get-VSSetupInstance with our PowerShell cmdlets.

Thanks, but while a small dependency on vswhere is OK, a dependency on PowerShell currently isn't (but that may change).

That's why the query API is there, and does support what you want.

Makes sense, I hadn't seen it in the API, hence I was merely pointing out that there's at least some way to get it. I agree that an undocumented and volatile format is not where you should get it, if at all possible.

Do you have a use case for the nickname?

Yes, many. Currently, for simplifying the scripts to drive the F# VSIX installments. With VSIX you can select an instanceId, but you cannot select a nickname. However, users don't know an instanceId, not even when you show it them, that's what the nickname is for: a mnemonic for the user, and that's why Microsoft introduced it.

I believe the primary goal was exactly that: if you need to install something into Visual Studio (an extension, mainly), the nickname provides a way to choose a specific instance.

Of course, if we do not want to rely on vswhere, we can build this ourselves, but the beauty of a tool like this one is that it provides a helpful means in automated build scripts.

In my particular case, if VSIXInstaller is called without an instance id, it will be installed in the first, unnamed (no nickname) instance. This may not be what the user wants, so the proper thing to do is list the instances and force the user to choose.

vswhere is targeted at script-based detection of Visual Studio 2017 and related products.

Precisely, and it fails in this respect, it doesn't detect this property and you cannot query by it, even though it is the only property users will have to unambiguously refer to a specific instance.

Your comment sounds as if you think this was a custom property (forgive me if I misinterpreted), but it isn't. It is supported by Microsoft and part of the Visual Studio 2017 Setup experience. See Bruno's post, and Microsoft's own documentation (specifically, the commandline --nickname options).

Besides, it isn't nested. There's a one-on-one relationship between an instance of a Visual Studio 2017 installation and a nickname. Though a nickname isn't required for installation (and the first installment won't have one, which can be used to determine the "main" or "prime" installation), the setup will provide a default of an increasing integer.

In short, I highly doubt there will be any naming conflict or collision when you return this property in the same way you return other properties.

heaths commented 6 years ago

As one of the people that designed and coded much of the setup engine, I'm very aware of the "nickname" property. :) I added the support to the query API so you definitely can get at it with the ISetupInstance2::GetProperties method, which returns an ISetupPropertyStore instance. This is used for property bags. ISetupInstance and derivatives also support this for all intrinsic property types (integers, strings, and booleans) by calling ISetupInstance::QueryInterface for ISetupPropertyStore. In fact, that's how about half of the properties output by vswhere are obtained. I originally just emitted specific properties but when I added the property bag support (mainly so that we didn't need to keep releasing new derivative interfaces as we added properties to track data for new features) I figured may as well QI for ISetupPropertyStore and dump any intrinsic properties I hadn't already emitted.

The property bag is indeed nested, but not all products we install support or use it. Because the property bag is nested (the properties object), anything in there would have to be flattened into the vswhere output so that scripts using it - whether batch, powershell, or whatever - still work.

I will consider some sort of prefix that won't conflict or require an expression engine. For example, I couldn't use a "." separator because if you pipe the results to JSON (e.g. in PowerShell: vswhere -format json | convertfrom-json) you wouldn't be able to reference the property:

PS> $test = '{"a": 1, "b.c": 2}' | convertfrom-json
PS> $test.a
1
PS> $test.b.c
PS>
PS> $test.'b.c' # this works in PowerShell because of how it deserializes but may not work with all JSON parsers or how scripts work with returned objects
2
PS>

Prepending properties with simple "property_" is a safe bet as I don't imagine we would ever consider doing that to top-level properties. Do you have any other suggestions?

FWIW, I coded up the productId and productPath properties as you also requested.

abelbraaksma commented 6 years ago

@heaths, aha, now I understand your concern about nested better. It doesn't preclude being unique to an instance, but it isn't at the main level either. Sorry, I'm rather new to this part of the VS2017 setup (even though I've worked with VS since before .NET came to fruition ;).

Prepending properties with simple "property_" is a safe bet as I don't imagine we would ever consider doing that to top-level properties. Do you have any other suggestions?

That is actually an excellent and simple idea. And indeed, it should be processable by either commandline FOR-style processing or JSON/XML etc. I can't think of a better suggestion ;).

FWIW, I coded up the productId and productPath properties as you also requested.

Perfect! I look forward to the PR.

As one of the people that designed and coded much of the setup engine,

I wasn't aware of that, but considering you're doing the vswhere, it makes sense ;). We (as in, the F# community) are currently working on improving setup experience of its tools and SDK bits, hence came this request around to begin with.

heaths commented 6 years ago

The attached PR will now output properties in a format-compatible way, i.e. for JSON and XML "properties" will be a nested object while for the text formatter each property will be prefixed with "properties_". The value formatter will continue to just output the values with no names, and is really only intended for a single value anyway since I never made any guarantees about property order.

Additionally, you can optionally prefix properties to output with "properties" followed by delimiters including ".", "/", or "_" (delimiters used natively by their respective formats but all will work regardless of which format you specify). It's only necessary for disambiguation in case that ever were a problem (currently isn't, but who knows about the future). You can also just specify "properties" to get all custom properties dumped since "properties" is itself a property. I don't imagine that's useful, but given how I designed the formatters in the first place it would actually be more work to not provide said capability and - perhaps - could be useful to someone in some scenario.

E.g.

[
  {
    "instanceId": "d9a2ceee",
    "installDate": "2017-08-15T21:41:27Z",
    "installationName": "VisualStudioPreview/15.5.0-pre.1.0+26927.1.d15rel",
    "installationPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Preview\\Enterprise",
    "installationVersion": "15.5.26927.1",
    "productId": "Microsoft.VisualStudio.Product.Enterprise",
    "productPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Preview\\Enterprise\\Common7\\IDE\\devenv.exe",
    "isPrerelease": true,
    "displayName": "Visual Studio Enterprise 2017",
    "description": "Microsoft DevOps solution for productivity and coordination across teams of any size",
    "enginePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\resources\\app\\ServiceHub\\Services\\Microsoft.VisualStudio.Setup.Service",
    "channelId": "VisualStudio.15.IntPreview",
    "channelPath": "C:\\Users\\heaths\\AppData\\Local\\Microsoft\\VisualStudio\\Packages\\_Channels\\BD9CC1FC\\catalog.json",
    "channelUri": "https://aka.ms/vs/15/intpreview/channel",
    "installChannelUri": "https://vsdrop.corp.microsoft.com/file/v1/Products/DevDiv/VS/rel/d15rel/26814.01;VisualStudio.15.IntPreview.chman",
    "releaseNotes": "https://go.microsoft.com/fwlink/?LinkId=660693#15.5.0-pre.1.0",
    "thirdPartyNotices": "https://go.microsoft.com/fwlink/?LinkId=660708",
    "properties": {
      "campaignId": "",
      "setupEngineFilePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vs_installershell.exe",
      "nickname": "",
      "channelManifestId": "VisualStudio.15.IntPreview/15.5.0-pre.1.0+26927.1.d15rel"
    }
  }
]
heaths commented 6 years ago

I won't be able to release a signed version for a while, but in the meantime you can use https://ci.appveyor.com/api/buildjobs/sl60nm0p5f7e36nd/artifacts/bin%2FRelease.zip.

abelbraaksma commented 6 years ago

Great! I'll be testing it shortly. Your explanation seems very clear.

abelbraaksma commented 6 years ago

I ran it for another test and it works perfectly! Thanks again for the quick action. Any idea when this will become part of an RTM or Preview release? (which is important for running this in a batch program, to know whether a given version of vswhere can be used to query this information, or should fallback to instanceId discrimination).

heaths commented 6 years ago

Rather than take a dependency on a Visual Studio update (this should come out with 15.6), why not use the nuget package vswhere? For example, in https://github.com/Microsoft/vswhere/blob/develop/.nuget/packages.config I reference it, so running nuget restore anywhere in my solution directory will always pull it down. Then in https://github.com/Microsoft/vswhere/blob/develop/tools/test.ps1 I can easily use it (the same is possible in batch as well - I use batch internally for the setup engine build for legacy reasons).

abelbraaksma commented 6 years ago

I didn't realize the most recent version was available this way, but yes, it makes sense, that should certainly be better.