@@ -54,7 +54,10 @@ class VisualStudioFinder {
54
54
}
55
55
56
56
const checks = [
57
- ( ) => this . findVisualStudio2017OrNewer ( ) ,
57
+ ( ) => this . findVisualStudio2019OrNewerUsingSetupModule ( ) ,
58
+ ( ) => this . findVisualStudio2019OrNewer ( ) ,
59
+ ( ) => this . findVisualStudio2017UsingSetupModule ( ) ,
60
+ ( ) => this . findVisualStudio2017 ( ) ,
58
61
( ) => this . findVisualStudio2015 ( ) ,
59
62
( ) => this . findVisualStudio2013 ( )
60
63
]
@@ -113,9 +116,84 @@ class VisualStudioFinder {
113
116
throw new Error ( 'Could not find any Visual Studio installation to use' )
114
117
}
115
118
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
+
116
194
// Invoke the PowerShell script to get information about Visual Studio 2017
117
195
// or newer installations
118
- async findVisualStudio2017OrNewer ( ) {
196
+ async findNewVS ( supportedYears ) {
119
197
const ps = path . join ( process . env . SystemRoot , 'System32' ,
120
198
'WindowsPowerShell' , 'v1.0' , 'powershell.exe' )
121
199
const csFile = path . join ( __dirname , 'Find-VisualStudio.cs' )
@@ -128,24 +206,35 @@ class VisualStudioFinder {
128
206
]
129
207
130
208
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 )
133
215
}
134
216
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
+
138
226
this . log . silly ( 'PS stderr = %j' , stderr )
139
227
140
- const failPowershell = ( ) => {
228
+ const failPowershell = ( failureDetails ) => {
141
229
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 } ` )
143
232
return null
144
233
}
145
234
146
235
if ( err ) {
147
236
this . log . silly ( 'PS err = %j' , err && ( err . stack || err ) )
148
- return failPowershell ( )
237
+ return failPowershell ( ` ${ err } ` . substring ( 0 , 40 ) )
149
238
}
150
239
151
240
let vsInfo
@@ -157,11 +246,16 @@ class VisualStudioFinder {
157
246
return failPowershell ( )
158
247
}
159
248
160
- if ( ! Array . isArray ( vsInfo ) ) {
249
+ if ( sanityOptions . checkIsArray && ! Array . isArray ( vsInfo ) ) {
161
250
this . log . silly ( 'PS stdout = %j' , stdout )
162
- return failPowershell ( )
251
+ return failPowershell ( 'Expected array as output of the PS script' )
163
252
}
253
+ return vsInfo
254
+ }
164
255
256
+ // Process parsed data containing information about VS installations
257
+ // Look for the required parts, extract and output them back
258
+ processData ( vsInfo , supportedYears ) {
165
259
vsInfo = vsInfo . map ( ( info ) => {
166
260
this . log . silly ( `processing installation: "${ info . path } "` )
167
261
info . path = path . resolve ( info . path )
@@ -175,11 +269,12 @@ class VisualStudioFinder {
175
269
this . log . silly ( 'vsInfo:' , vsInfo )
176
270
177
271
// Remove future versions or errors parsing version number
272
+ // Also remove any unsupported versions
178
273
vsInfo = vsInfo . filter ( ( info ) => {
179
- if ( info . versionYear ) {
274
+ if ( info . versionYear && supportedYears . indexOf ( info . versionYear ) !== - 1 ) {
180
275
return true
181
276
}
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 } "` )
183
278
return false
184
279
} )
185
280
@@ -438,6 +533,10 @@ class VisualStudioFinder {
438
533
439
534
return true
440
535
}
536
+
537
+ async execFile ( exec , args ) {
538
+ return await execFile ( exec , args , { encoding : 'utf8' } )
539
+ }
441
540
}
442
541
443
542
module . exports = VisualStudioFinder
0 commit comments