@@ -54,7 +54,10 @@ class VisualStudioFinder {
5454 }
5555
5656 const checks = [
57- ( ) => this . findVisualStudio2017OrNewer ( ) ,
57+ ( ) => this . findVisualStudio2019OrNewerUsingSetupModule ( ) ,
58+ ( ) => this . findVisualStudio2019OrNewer ( ) ,
59+ ( ) => this . findVisualStudio2017UsingSetupModule ( ) ,
60+ ( ) => this . findVisualStudio2017 ( ) ,
5861 ( ) => this . findVisualStudio2015 ( ) ,
5962 ( ) => this . findVisualStudio2013 ( )
6063 ]
@@ -113,9 +116,84 @@ class VisualStudioFinder {
113116 throw new Error ( 'Could not find any Visual Studio installation to use' )
114117 }
115118
119+ async findVisualStudio2019OrNewerUsingSetupModule ( ) {
120+ return this . findNewVSUsingSetupModule ( [ 2019 , 2022 ] )
121+ }
122+
123+ async findVisualStudio2017UsingSetupModule ( ) {
124+ if ( this . nodeSemver . major >= 22 ) {
125+ this . addLog (
126+ 'not looking for VS2017 as it is only supported up to Node.js 21' )
127+ return null
128+ }
129+ return this . findNewVSUsingSetupModule ( [ 2017 ] )
130+ }
131+
132+ async findNewVSUsingSetupModule ( supportedYears ) {
133+ const ps = path . join ( process . env . SystemRoot , 'System32' ,
134+ 'WindowsPowerShell' , 'v1.0' , 'powershell.exe' )
135+ const vcInstallDir = this . envVcInstallDir
136+
137+ const checkModuleArgs = [
138+ '-NoProfile' ,
139+ '-Command' ,
140+ '&{@(Get-Module -ListAvailable -Name VSSetup).Version.ToString()}'
141+ ]
142+ this . log . silly ( 'Running' , ps , checkModuleArgs )
143+ const [ cErr ] = await this . execFile ( ps , checkModuleArgs )
144+ if ( cErr ) {
145+ this . addLog ( 'VSSetup module doesn\'t seem to exist. You can install it via: "Install-Module VSSetup -Scope CurrentUser"' )
146+ this . log . silly ( 'VSSetup error = %j' , cErr && ( cErr . stack || cErr ) )
147+ return null
148+ }
149+ const filterArg = vcInstallDir !== undefined ? `| where {$_.InstallationPath -eq '${ vcInstallDir } ' }` : ''
150+ const psArgs = [
151+ '-NoProfile' ,
152+ '-Command' ,
153+ `&{Get-VSSetupInstance ${ filterArg } | ConvertTo-Json -Depth 3}`
154+ ]
155+
156+ this . log . silly ( 'Running' , ps , psArgs )
157+ const [ err , stdout , stderr ] = await this . execFile ( ps , psArgs )
158+ let parsedData = this . parseData ( err , stdout , stderr )
159+ if ( parsedData === null ) {
160+ return null
161+ }
162+ this . log . silly ( 'Parsed data' , parsedData )
163+ if ( ! Array . isArray ( parsedData ) ) {
164+ // if there are only 1 result, then Powershell will output non-array
165+ parsedData = [ parsedData ]
166+ }
167+ // normalize output
168+ parsedData = parsedData . map ( ( info ) => {
169+ info . path = info . InstallationPath
170+ info . version = `${ info . InstallationVersion . Major } .${ info . InstallationVersion . Minor } .${ info . InstallationVersion . Build } .${ info . InstallationVersion . Revision } `
171+ info . packages = info . Packages . map ( ( p ) => p . Id )
172+ return info
173+ } )
174+ // pass for further processing
175+ return this . processData ( parsedData , supportedYears )
176+ }
177+
178+ // Invoke the PowerShell script to get information about Visual Studio 2019
179+ // or newer installations
180+ async findVisualStudio2019OrNewer ( ) {
181+ return this . findNewVS ( [ 2019 , 2022 ] )
182+ }
183+
184+ // Invoke the PowerShell script to get information about Visual Studio 2017
185+ async findVisualStudio2017 ( ) {
186+ if ( this . nodeSemver . major >= 22 ) {
187+ this . addLog (
188+ 'not looking for VS2017 as it is only supported up to Node.js 21' )
189+ return null
190+ }
191+ return this . findNewVS ( [ 2017 ] )
192+ }
193+
116194 // Invoke the PowerShell script to get information about Visual Studio 2017
117195 // or newer installations
118- async findVisualStudio2017OrNewer ( ) {
196+ async findNewVS ( supportedYears ) {
119197 const ps = path . join ( process . env . SystemRoot , 'System32' ,
120198 'WindowsPowerShell' , 'v1.0' , 'powershell.exe' )
121199 const csFile = path . join ( __dirname , 'Find-VisualStudio.cs' )
@@ -128,24 +206,35 @@ class VisualStudioFinder {
128206 ]
129207
130208 this . log . silly ( 'Running' , ps , psArgs )
131- const [ err , stdout , stderr ] = await execFile ( ps , psArgs , { encoding : 'utf8' } )
132- return this . parseData ( err , stdout , stderr )
209+ const [ err , stdout , stderr ] = await this . execFile ( ps , psArgs )
210+ const parsedData = this . parseData ( err , stdout , stderr , { checkIsArray : true } )
211+ if ( parsedData === null ) {
212+ return null
213+ }
214+ return this . processData ( parsedData , supportedYears )
133215 }
134216
135- // Parse the output of the PowerShell script and look for an installation
136- // of Visual Studio 2017 or newer to use
137- parseData ( err , stdout , stderr ) {
217+ // Parse the output of the PowerShell script, make sanity checks
218+ parseData ( err , stdout , stderr , sanityCheckOptions ) {
219+ const defaultOptions = {
220+ checkIsArray : false
221+ }
222+
223+ // Merging provided options with the default options
224+ const sanityOptions = { ...defaultOptions , ...sanityCheckOptions }
225+
138226 this . log . silly ( 'PS stderr = %j' , stderr )
139227
140- const failPowershell = ( ) => {
228+ const failPowershell = ( failureDetails ) => {
141229 this . addLog (
142- 'could not use PowerShell to find Visual Studio 2017 or newer, try re-running with \'--loglevel silly\' for more details' )
230+ `could not use PowerShell to find Visual Studio 2017 or newer, try re-running with '--loglevel silly' for more details. \n
231+ Failure details: ${ failureDetails } ` )
143232 return null
144233 }
145234
146235 if ( err ) {
147236 this . log . silly ( 'PS err = %j' , err && ( err . stack || err ) )
148- return failPowershell ( )
237+ return failPowershell ( ` ${ err } ` . substring ( 0 , 40 ) )
149238 }
150239
151240 let vsInfo
@@ -157,11 +246,16 @@ class VisualStudioFinder {
157246 return failPowershell ( )
158247 }
159248
160- if ( ! Array . isArray ( vsInfo ) ) {
249+ if ( sanityOptions . checkIsArray && ! Array . isArray ( vsInfo ) ) {
161250 this . log . silly ( 'PS stdout = %j' , stdout )
162- return failPowershell ( )
251+ return failPowershell ( 'Expected array as output of the PS script' )
163252 }
253+ return vsInfo
254+ }
164255
256+ // Process parsed data containing information about VS installations
257+ // Look for the required parts, extract and output them back
258+ processData ( vsInfo , supportedYears ) {
165259 vsInfo = vsInfo . map ( ( info ) => {
166260 this . log . silly ( `processing installation: "${ info . path } "` )
167261 info . path = path . resolve ( info . path )
@@ -175,11 +269,12 @@ class VisualStudioFinder {
175269 this . log . silly ( 'vsInfo:' , vsInfo )
176270
177271 // Remove future versions or errors parsing version number
272+ // Also remove any unsupported versions
178273 vsInfo = vsInfo . filter ( ( info ) => {
179- if ( info . versionYear ) {
274+ if ( info . versionYear && supportedYears . indexOf ( info . versionYear ) !== - 1 ) {
180275 return true
181276 }
182- this . addLog ( `unknown version "${ info . version } " found at "${ info . path } "` )
277+ this . addLog ( `${ info . versionYear ? 'unsupported' : ' unknown' } version "${ info . version } " found at "${ info . path } "` )
183278 return false
184279 } )
185280
@@ -438,6 +533,10 @@ class VisualStudioFinder {
438533
439534 return true
440535 }
536+
537+ async execFile ( exec , args ) {
538+ return await execFile ( exec , args , { encoding : 'utf8' } )
539+ }
441540}
442541
443542module . exports = VisualStudioFinder
0 commit comments