Skip to content

Commit

Permalink
feat: improve visual studio detection (#2957)
Browse files Browse the repository at this point in the history
Detect visual studio installation using the VSSetup module and
Get-VSSetupInstance method. It works even in systems with the
Constrained language mode of PowerShell.
  • Loading branch information
jarig authored Feb 9, 2024
1 parent 3298731 commit 109e3d4
Show file tree
Hide file tree
Showing 8 changed files with 1,517 additions and 51 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ Install tools and configuration manually:

To use the native ARM64 C++ compiler on Windows on ARM, ensure that you have Visual Studio 2022 [17.4 or later](https://devblogs.microsoft.com/visualstudio/arm64-visual-studio-is-officially-here/) installed.

It's advised to install following Powershell module: [VSSetup](https://github.com/microsoft/vssetup.powershell) using `Install-Module VSSetup -Scope CurrentUser`.
This will make Visual Studio detection logic to use more flexible and accessible method, avoiding Powershell's `ConstrainedLanguage` mode.

### Configuring Python Dependency

`node-gyp` requires that you have installed a [supported version of Python](https://devguide.python.org/versions/).
Expand Down
87 changes: 77 additions & 10 deletions lib/find-visualstudio.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class VisualStudioFinder {
}

const checks = [
() => this.findVisualStudio2017OrNewerUsingSetupModule(),
() => this.findVisualStudio2017OrNewer(),
() => this.findVisualStudio2015(),
() => this.findVisualStudio2013()
Expand Down Expand Up @@ -113,6 +114,52 @@ class VisualStudioFinder {
throw new Error('Could not find any Visual Studio installation to use')
}

async findVisualStudio2017OrNewerUsingSetupModule () {
const ps = path.join(process.env.SystemRoot, 'System32',
'WindowsPowerShell', 'v1.0', 'powershell.exe')
const vcInstallDir = this.envVcInstallDir

const checkModuleArgs = [
'-NoProfile',
'-Command',
'&{@(Get-Module -ListAvailable -Name VSSetup).Version.ToString()}'
]
this.log.silly('Running', ps, checkModuleArgs)
const [cErr] = await this.execFile(ps, checkModuleArgs)
if (cErr) {
this.addLog('VSSetup module doesn\'t seem to exist. You can install it via: "Install-Module VSSetup -Scope CurrentUser"')
this.log.silly('VSSetup error = %j', cErr && (cErr.stack || cErr))
return null
}
const filterArg = vcInstallDir !== undefined ? `| where {$_.InstallationPath -eq '${vcInstallDir}' }` : ''
const psArgs = [
'-NoProfile',
'-Command',
`&{Get-VSSetupInstance ${filterArg} | ConvertTo-Json -Depth 3}`
]

this.log.silly('Running', ps, psArgs)
const [err, stdout, stderr] = await this.execFile(ps, psArgs)
let parsedData = this.parseData(err, stdout, stderr)
if (parsedData === null) {
return null
}
this.log.silly('Parsed data', parsedData)
if (!Array.isArray(parsedData)) {
// if there are only 1 result, then Powershell will output non-array
parsedData = [parsedData]
}
// normalize output
parsedData = parsedData.map((info) => {
info.path = info.InstallationPath
info.version = `${info.InstallationVersion.Major}.${info.InstallationVersion.Minor}.${info.InstallationVersion.Build}.${info.InstallationVersion.Revision}`
info.packages = info.Packages.map((p) => p.Id)
return info
})
// pass for further processing
return this.processData(parsedData)
}

// Invoke the PowerShell script to get information about Visual Studio 2017
// or newer installations
async findVisualStudio2017OrNewer () {
Expand All @@ -128,24 +175,35 @@ class VisualStudioFinder {
]

this.log.silly('Running', ps, psArgs)
const [err, stdout, stderr] = await execFile(ps, psArgs, { encoding: 'utf8' })
return this.parseData(err, stdout, stderr)
const [err, stdout, stderr] = await this.execFile(ps, psArgs)
const parsedData = this.parseData(err, stdout, stderr, { checkIsArray: true })
if (parsedData === null) {
return null
}
return this.processData(parsedData)
}

// Parse the output of the PowerShell script and look for an installation
// of Visual Studio 2017 or newer to use
parseData (err, stdout, stderr) {
// Parse the output of the PowerShell script, make sanity checks
parseData (err, stdout, stderr, sanityCheckOptions) {
const defaultOptions = {
checkIsArray: false
}

// Merging provided options with the default options
const sanityOptions = { ...defaultOptions, ...sanityCheckOptions }

this.log.silly('PS stderr = %j', stderr)

const failPowershell = () => {
const failPowershell = (failureDetails) => {
this.addLog(
'could not use PowerShell to find Visual Studio 2017 or newer, try re-running with \'--loglevel silly\' for more details')
`could not use PowerShell to find Visual Studio 2017 or newer, try re-running with '--loglevel silly' for more details. \n
Failure details: ${failureDetails}`)
return null
}

if (err) {
this.log.silly('PS err = %j', err && (err.stack || err))
return failPowershell()
return failPowershell(`${err}`.substring(0, 40))
}

let vsInfo
Expand All @@ -157,11 +215,16 @@ class VisualStudioFinder {
return failPowershell()
}

if (!Array.isArray(vsInfo)) {
if (sanityOptions.checkIsArray && !Array.isArray(vsInfo)) {
this.log.silly('PS stdout = %j', stdout)
return failPowershell()
return failPowershell('Expected array as output of the PS script')
}
return vsInfo
}

// Process parsed data containing information about VS installations
// Look for the required parts, extract and output them back
processData (vsInfo) {
vsInfo = vsInfo.map((info) => {
this.log.silly(`processing installation: "${info.path}"`)
info.path = path.resolve(info.path)
Expand Down Expand Up @@ -438,6 +501,10 @@ class VisualStudioFinder {

return true
}

async execFile (exec, args) {
return await execFile(exec, args, { encoding: 'utf8' })
}
}

module.exports = VisualStudioFinder
235 changes: 235 additions & 0 deletions test/fixtures/VSSetup_VS_2019_Professional_workload.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
[
{
"InstanceId": "2619cf21",
"InstallationName": "VisualStudio/16.11.33+34407.143",
"InstallationPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Professional",
"InstallationVersion": {
"Major": 16,
"Minor": 11,
"Build": 34407,
"Revision": 143,
"MajorRevision": 0,
"MinorRevision": 143
},
"InstallDate": "\/Date(1706804943503)\/",
"State": 4294967295,
"DisplayName": "Visual Studio Professional 2019",
"Description": "Professional IDE best suited to small teams",
"ProductPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Professional\\Common7\\IDE\\devenv.exe",
"Product": {
"Id": "Microsoft.VisualStudio.Product.Professional",
"Version": {
"Major": 16,
"Minor": 11,
"Build": 34407,
"Revision": 143,
"MajorRevision": 0,
"MinorRevision": 143
},
"Chip": null,
"Branch": null,
"Type": "Product",
"IsExtension": false,
"UniqueId": "Microsoft.VisualStudio.Product.Professional,version=16.11.34407.143"
},
"Packages": [
{
"Id": "Microsoft.VisualStudio.Product.Professional",
"Version": "16.11.34407.143",
"Chip": null,
"Branch": null,
"Type": "Product",
"IsExtension": false,
"UniqueId": "Microsoft.VisualStudio.Product.Professional,version=16.11.34407.143"
},
{
"Id": "Microsoft.VisualStudio.Component.VC.14.29.16.10.ATL",
"Version": "16.11.31314.313",
"Chip": null,
"Branch": null,
"Type": "Component",
"IsExtension": false,
"UniqueId": "Microsoft.VisualStudio.Component.VC.14.29.16.10.ATL,version=16.11.31314.313"
},
{
"Id": "Microsoft.VisualStudio.VC.MSBuild.X64.v142",
"Version": "16.11.31503.54",
"Chip": null,
"Branch": null,
"Type": "Vsix",
"IsExtension": false,
"UniqueId": "Microsoft.VisualStudio.VC.MSBuild.X64.v142,version=16.11.31503.54"
},
{
"Id": "Microsoft.VisualStudio.VC.MSBuild.X64",
"Version": "16.11.31503.54",
"Chip": null,
"Branch": null,
"Type": "Vsix",
"IsExtension": false,
"UniqueId": "Microsoft.VisualStudio.VC.MSBuild.X64,version=16.11.31503.54"
},
{
"Id": "Microsoft.VisualStudio.VC.MSBuild.x86.v142",
"Version": "16.11.31503.54",
"Chip": null,
"Branch": null,
"Type": "Vsix",
"IsExtension": false,
"UniqueId": "Microsoft.VisualStudio.VC.MSBuild.x86.v142,version=16.11.31503.54"
},
{
"Id": "Microsoft.VisualStudio.VC.MSBuild.X86",
"Version": "16.11.31503.54",
"Chip": null,
"Branch": null,
"Type": "Vsix",
"IsExtension": false,
"UniqueId": "Microsoft.VisualStudio.VC.MSBuild.X86,version=16.11.31503.54"
},
{
"Id": "Microsoft.VisualStudio.VC.MSBuild.Base",
"Version": "16.11.31829.152",
"Chip": null,
"Branch": null,
"Type": "Vsix",
"IsExtension": false,
"UniqueId": "Microsoft.VisualStudio.VC.MSBuild.Base,version=16.11.31829.152"
},
{
"Id": "Microsoft.VisualStudio.VC.MSBuild.Base.Resources",
"Version": "16.11.31829.152",
"Chip": null,
"Branch": null,
"Type": "Vsix",
"IsExtension": false,
"UniqueId": "Microsoft.VisualStudio.VC.MSBuild.Base.Resources,version=16.11.31829.152,language=en-US"
},
{
"Id": "Microsoft.VisualStudio.Branding.Professional",
"Version": "16.11.31605.320",
"Chip": null,
"Branch": null,
"Type": "Vsix",
"IsExtension": false,
"UniqueId": "Microsoft.VisualStudio.Branding.Professional,version=16.11.31605.320,language=en-US"
},
{
"Id": "Microsoft.VisualStudio.Component.Windows10SDK.19041",
"Version": "16.10.31205.252",
"Chip": null,
"Branch": null,
"Type": "Component",
"IsExtension": false,
"UniqueId": "Microsoft.VisualStudio.Component.Windows10SDK.19041,version=16.10.31205.252"
},
{
"Id": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"Version": "16.11.32406.258",
"Chip": null,
"Branch": null,
"Type": "Component",
"IsExtension": false,
"UniqueId": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64,version=16.11.32406.258"
}
],
"Properties": [
{
"Key": "CampaignId",
"Value": "09"
},
{
"Key": "SetupEngineFilePath",
"Value": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\setup.exe"
},
{
"Key": "Nickname",
"Value": ""
},
{
"Key": "ChannelManifestId",
"Value": "VisualStudio.16.Release/16.11.33+34407.143"
}
],
"Errors": null,
"EnginePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\resources\\app\\ServiceHub\\Services\\Microsoft.VisualStudio.Setup.Service",
"IsComplete": true,
"IsLaunchable": true,
"CatalogInfo": [
{
"Key": "Id",
"Value": "VisualStudio/16.11.33+34407.143"
},
{
"Key": "BuildBranch",
"Value": "d16.11"
},
{
"Key": "BuildVersion",
"Value": "16.11.34407.143"
},
{
"Key": "LocalBuild",
"Value": "build-lab"
},
{
"Key": "ManifestName",
"Value": "VisualStudio"
},
{
"Key": "ManifestType",
"Value": "installer"
},
{
"Key": "ProductDisplayVersion",
"Value": "16.11.33"
},
{
"Key": "ProductLine",
"Value": "Dev16"
},
{
"Key": "ProductLineVersion",
"Value": "2019"
},
{
"Key": "ProductMilestone",
"Value": "RTW"
},
{
"Key": "ProductMilestoneIsPreRelease",
"Value": "False"
},
{
"Key": "ProductName",
"Value": "Visual Studio"
},
{
"Key": "ProductPatchVersion",
"Value": "33"
},
{
"Key": "ProductPreReleaseMilestoneSuffix",
"Value": "1.0"
},
{
"Key": "ProductSemanticVersion",
"Value": "16.11.33+34407.143"
},
{
"Key": "RequiredEngineVersion",
"Value": "2.11.72.18200"
}
],
"IsPrerelease": false,
"PSPath": "Microsoft.PowerShell.Core\\FileSystem::C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
"UpdateDate": "2024-01-09T19:19:11.0115234Z",
"ResolvedInstallationPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise",
"ChannelId": "VisualStudio.17.Release",
"InstalledChannelId": "VisualStudio.17.Release",
"ChannelUri": "https://aka.ms/vs/17/release/channel",
"InstalledChannelUri": "https://aka.ms/vs/17/release/channel",
"ReleaseNotes": "https://docs.microsoft.com/en-us/visualstudio/releases/2022/release-notes-v17.8#17.8.4",
"ThirdPartyNotices": "https://go.microsoft.com/fwlink/?LinkId=661288"
}
]
Loading

0 comments on commit 109e3d4

Please sign in to comment.