9
9
using System . Linq ;
10
10
using System . Runtime . InteropServices ;
11
11
using System . Text ;
12
+ using System . Text . RegularExpressions ;
12
13
using Microsoft . Management . Infrastructure ;
13
14
using Microsoft . PowerShell . CrossCompatibility . Data ;
14
15
using Microsoft . PowerShell . CrossCompatibility . Utility ;
@@ -29,6 +30,76 @@ public class PlatformInformationCollector : IDisposable
29
30
"/etc/os-release" ,
30
31
} ;
31
32
33
+ private static readonly IReadOnlyList < string > s_distributionIdKeys = new string [ ]
34
+ {
35
+ "ID" ,
36
+ "DISTRIB_ID"
37
+ } ;
38
+
39
+ private static readonly IReadOnlyList < string > s_distributionVersionKeys = new string [ ]
40
+ {
41
+ "VERSION_ID" ,
42
+ "DISTRIB_RELEASE"
43
+ } ;
44
+
45
+ private static readonly IReadOnlyList < string > s_distributionPrettyNameKeys = new string [ ]
46
+ {
47
+ "PRETTY_NAME" ,
48
+ "DISTRIB_DESCRIPTION"
49
+ } ;
50
+
51
+ private static readonly Regex s_macOSNameRegex = new Regex (
52
+ @"System Version: (.*?)(\(|$)" ,
53
+ RegexOptions . Multiline | RegexOptions . Compiled ) ;
54
+
55
+
56
+ /// <summary>
57
+ /// Collect all release info files into a lookup table in memory.
58
+ /// Overrides pre-existing keys if there are duplicates.
59
+ /// </summary>
60
+ /// <returns>A dictionary with the keys and values of all the release info files on the machine.</returns>
61
+ public static IReadOnlyDictionary < string , string > GetLinuxReleaseInfo ( )
62
+ {
63
+ var releaseInfoKeyValueEntries = new Dictionary < string , string > ( ) ;
64
+
65
+ foreach ( string path in s_releaseInfoPaths )
66
+ {
67
+ try
68
+ {
69
+ using ( FileStream fileStream = File . OpenRead ( path ) )
70
+ using ( var reader = new StreamReader ( fileStream ) )
71
+ {
72
+ while ( ! reader . EndOfStream )
73
+ {
74
+ string line = reader . ReadLine ( ) ;
75
+
76
+ if ( string . IsNullOrWhiteSpace ( line ) || line . StartsWith ( "#" ) )
77
+ {
78
+ continue ;
79
+ }
80
+
81
+ string [ ] elements = line . Split ( '=' ) ;
82
+ releaseInfoKeyValueEntries [ elements [ 0 ] ] = Dequote ( elements [ 1 ] ) ;
83
+ }
84
+ }
85
+ }
86
+ catch ( IOException )
87
+ {
88
+ // Different Linux distributions have different /etc/*-release files.
89
+ // It's more efficient (and correct timing-wise) for us to try to read the file and catch the exception
90
+ // than to test for its existence and then open it.
91
+ //
92
+ // See:
93
+ // - https://www.freedesktop.org/software/systemd/man/os-release.html
94
+ // - https://gist.github.com/natefoo/814c5bf936922dad97ff
95
+
96
+ // Ignore that the file doesn't exist and just continue to the next one.
97
+ }
98
+ }
99
+
100
+ return releaseInfoKeyValueEntries ;
101
+ }
102
+
32
103
private readonly Lazy < Hashtable > _lazyPSVersionTable ;
33
104
34
105
private readonly Lazy < PowerShellVersion > _lazyPSVersion ;
@@ -129,16 +200,17 @@ public OperatingSystemData GetOperatingSystemData()
129
200
{
130
201
var osData = new OperatingSystemData ( )
131
202
{
203
+ Description = GetOSDescription ( ) ,
132
204
Architecture = GetOSArchitecture ( ) ,
133
205
Family = GetOSFamily ( ) ,
134
- Name = GetOSName ( ) ,
135
206
Platform = GetOSPlatform ( ) ,
136
207
Version = GetOSVersion ( ) ,
137
208
} ;
138
209
139
210
switch ( osData . Family )
140
211
{
141
212
case OSFamily . Windows :
213
+ osData . Name = osData . Description ;
142
214
if ( ! string . IsNullOrEmpty ( Environment . OSVersion . ServicePack ) )
143
215
{
144
216
osData . ServicePack = Environment . OSVersion . ServicePack ;
@@ -147,53 +219,83 @@ public OperatingSystemData GetOperatingSystemData()
147
219
break ;
148
220
149
221
case OSFamily . Linux :
150
- IReadOnlyDictionary < string , string > lsbInfo = GetLinuxReleaseInfo ( ) ;
151
- osData . DistributionId = lsbInfo [ "DistributionId" ] ;
152
- osData . DistributionVersion = lsbInfo [ "DistributionVersion" ] ;
153
- osData . DistributionPrettyName = lsbInfo [ "DistributionPrettyName" ] ;
222
+ IReadOnlyDictionary < string , string > releaseInfo = GetLinuxReleaseInfo ( ) ;
223
+
224
+ osData . DistributionId = GetEntryFromReleaseInfo ( releaseInfo , s_distributionIdKeys ) ;
225
+ osData . DistributionVersion = GetEntryFromReleaseInfo ( releaseInfo , s_distributionVersionKeys ) ;
226
+ osData . DistributionPrettyName = GetEntryFromReleaseInfo ( releaseInfo , s_distributionPrettyNameKeys ) ;
227
+ osData . Name = osData . DistributionPrettyName ;
228
+ break ;
229
+
230
+ case OSFamily . MacOS :
231
+ osData . Name = GetMacOSName ( ) ;
154
232
break ;
155
233
}
156
234
157
235
return osData ;
158
236
}
159
237
160
- /// <summary>
161
- /// Collect all release info files into a lookup table in memory.
162
- /// Overrides pre-existing keys if there are duplicates.
163
- /// </summary>
164
- /// <returns>A dictionary with the keys and values of all the release info files on the machine.</returns>
165
- public IReadOnlyDictionary < string , string > GetLinuxReleaseInfo ( )
238
+ private string GetMacOSName ( )
166
239
{
167
- var dict = new Dictionary < string , string > ( ) ;
168
-
169
- foreach ( string path in s_releaseInfoPaths )
240
+ try
170
241
{
171
- try
242
+ using ( var spProcess = new Process ( ) )
172
243
{
173
- using ( FileStream fileStream = File . OpenRead ( path ) )
174
- using ( var reader = new StreamReader ( fileStream ) )
175
- {
176
- while ( ! reader . EndOfStream )
177
- {
178
- string line = reader . ReadLine ( ) ;
244
+ spProcess . StartInfo . UseShellExecute = false ;
245
+ spProcess . StartInfo . RedirectStandardOutput = true ;
246
+ spProcess . StartInfo . CreateNoWindow = true ;
247
+ spProcess . StartInfo . FileName = "/usr/sbin/system_profiler" ;
248
+ spProcess . StartInfo . Arguments = "SPSoftwareDataType" ;
179
249
180
- if ( string . IsNullOrWhiteSpace ( line ) || line . StartsWith ( "#" ) )
181
- {
182
- continue ;
183
- }
250
+ spProcess . Start ( ) ;
251
+ spProcess . WaitForExit ( ) ;
184
252
185
- string [ ] elements = line . Split ( '=' ) ;
186
- dict [ elements [ 0 ] ] = Dequote ( elements [ 1 ] ) ;
187
- }
188
- }
189
- }
190
- catch ( IOException )
191
- {
192
- // Do nothing - just continue
253
+ string output = spProcess . StandardOutput . ReadToEnd ( ) ;
254
+ return s_macOSNameRegex . Match ( output ) . Groups [ 1 ] . Value ;
193
255
}
194
256
}
257
+ catch
258
+ {
259
+ return null ;
260
+ }
261
+ }
262
+
263
+ private string GetOSDescription ( )
264
+ {
265
+ #if CoreCLR
266
+ // This key was introduced in PowerShell 6
267
+ return ( string ) PSVersionTable [ "OS" ] ;
268
+ #else
269
+ if ( _lazyWin32OperatingSystemInfo . IsValueCreated )
270
+ {
271
+ return _lazyWin32OperatingSystemInfo . Value . OSName ;
272
+ }
195
273
196
- return dict ;
274
+ return RegistryCurrentVersionInfo . ProductName ;
275
+ #endif
276
+ }
277
+
278
+ private string GetOSVersion ( )
279
+ {
280
+ #if CoreCLR
281
+ // On Linux, we want to record the kernel branch, since this can differentiate Azure
282
+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) )
283
+ {
284
+ return File . ReadAllText ( "/proc/sys/kernel/osrelease" ) ;
285
+ }
286
+ #endif
287
+ return Environment . OSVersion . Version . ToString ( ) ;
288
+ }
289
+
290
+ private string GetOSPlatform ( )
291
+ {
292
+ #if CoreCLR
293
+ if ( PSVersion . Major >= 6 )
294
+ {
295
+ return ( string ) PSVersionTable [ "Platform" ] ;
296
+ }
297
+ #endif
298
+ return "Win32NT" ;
197
299
}
198
300
199
301
private OSFamily GetOSFamily ( )
@@ -310,46 +412,6 @@ private uint GetWinSkuId()
310
412
return ( uint ) WindowsSku . Undefined ;
311
413
}
312
414
313
- private string GetOSName ( )
314
- {
315
- #if CoreCLR
316
- // This key was introduced in PowerShell 6
317
- if ( PSVersion . Major >= 6 )
318
- {
319
- return ( string ) PSVersionTable [ "OS" ] ;
320
- }
321
- #endif
322
- if ( _lazyWin32OperatingSystemInfo . IsValueCreated )
323
- {
324
- return _lazyWin32OperatingSystemInfo . Value . OSName ;
325
- }
326
-
327
- return RegistryCurrentVersionInfo . ProductName ;
328
- }
329
-
330
- private string GetOSVersion ( )
331
- {
332
- #if CoreCLR
333
- // On Linux, we want to record the kernel branch, since this can differentiate Azure
334
- if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) )
335
- {
336
- return File . ReadAllText ( "/proc/sys/kernel/osrelease" ) ;
337
- }
338
- #endif
339
- return Environment . OSVersion . Version . ToString ( ) ;
340
- }
341
-
342
- private string GetOSPlatform ( )
343
- {
344
- #if CoreCLR
345
- if ( PSVersion . Major >= 6 )
346
- {
347
- return ( string ) PSVersionTable [ "Platform" ] ;
348
- }
349
- #endif
350
- return "Win32NT" ;
351
- }
352
-
353
415
private static CurrentVersionInfo ReadCurrentVersionFromRegistry ( )
354
416
{
355
417
using ( RegistryKey currentVersion = Registry . LocalMachine . OpenSubKey ( @"SOFTWARE\Microsoft\Windows NT\CurrentVersion" ) )
@@ -360,13 +422,18 @@ private static CurrentVersionInfo ReadCurrentVersionFromRegistry()
360
422
}
361
423
}
362
424
363
- [ DllImport ( "kernel32.dll" ) ]
364
- private static extern bool GetProductInfo (
365
- int dwOSMajorVersion ,
366
- int dwOSMinorVersion ,
367
- int dwSpMajorVersion ,
368
- int dwSpMinorVersion ,
369
- out uint pdwReturnedProductType ) ;
425
+ private static string GetEntryFromReleaseInfo ( IReadOnlyDictionary < string , string > releaseInfo , IEnumerable < string > possibleKeys )
426
+ {
427
+ foreach ( string key in possibleKeys )
428
+ {
429
+ if ( releaseInfo . TryGetValue ( key , out string entry ) )
430
+ {
431
+ return entry ;
432
+ }
433
+ }
434
+
435
+ return null ;
436
+ }
370
437
371
438
private static Win32OSCimInfo GetWin32OperatingSystemInfo ( )
372
439
{
@@ -443,6 +510,15 @@ private static string Dequote(string s)
443
510
return sb . ToString ( ) ;
444
511
}
445
512
513
+ [ DllImport ( "kernel32.dll" ) ]
514
+ private static extern bool GetProductInfo (
515
+ int dwOSMajorVersion ,
516
+ int dwOSMinorVersion ,
517
+ int dwSpMajorVersion ,
518
+ int dwSpMinorVersion ,
519
+ out uint pdwReturnedProductType ) ;
520
+
521
+
446
522
#region IDisposable Support
447
523
private bool disposedValue = false ; // To detect redundant calls
448
524
0 commit comments