99using System . Linq ;
1010using System . Runtime . InteropServices ;
1111using System . Text ;
12+ using System . Text . RegularExpressions ;
1213using Microsoft . Management . Infrastructure ;
1314using Microsoft . PowerShell . CrossCompatibility . Data ;
1415using Microsoft . PowerShell . CrossCompatibility . Utility ;
@@ -29,6 +30,76 @@ public class PlatformInformationCollector : IDisposable
2930 "/etc/os-release" ,
3031 } ;
3132
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+
32103 private readonly Lazy < Hashtable > _lazyPSVersionTable ;
33104
34105 private readonly Lazy < PowerShellVersion > _lazyPSVersion ;
@@ -129,16 +200,17 @@ public OperatingSystemData GetOperatingSystemData()
129200 {
130201 var osData = new OperatingSystemData ( )
131202 {
203+ Description = GetOSDescription ( ) ,
132204 Architecture = GetOSArchitecture ( ) ,
133205 Family = GetOSFamily ( ) ,
134- Name = GetOSName ( ) ,
135206 Platform = GetOSPlatform ( ) ,
136207 Version = GetOSVersion ( ) ,
137208 } ;
138209
139210 switch ( osData . Family )
140211 {
141212 case OSFamily . Windows :
213+ osData . Name = osData . Description ;
142214 if ( ! string . IsNullOrEmpty ( Environment . OSVersion . ServicePack ) )
143215 {
144216 osData . ServicePack = Environment . OSVersion . ServicePack ;
@@ -147,53 +219,83 @@ public OperatingSystemData GetOperatingSystemData()
147219 break ;
148220
149221 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 ( ) ;
154232 break ;
155233 }
156234
157235 return osData ;
158236 }
159237
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 ( )
166239 {
167- var dict = new Dictionary < string , string > ( ) ;
168-
169- foreach ( string path in s_releaseInfoPaths )
240+ try
170241 {
171- try
242+ using ( var spProcess = new Process ( ) )
172243 {
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" ;
179249
180- if ( string . IsNullOrWhiteSpace ( line ) || line . StartsWith ( "#" ) )
181- {
182- continue ;
183- }
250+ spProcess . Start ( ) ;
251+ spProcess . WaitForExit ( ) ;
184252
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 ;
193255 }
194256 }
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+ }
195273
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" ;
197299 }
198300
199301 private OSFamily GetOSFamily ( )
@@ -310,46 +412,6 @@ private uint GetWinSkuId()
310412 return ( uint ) WindowsSku . Undefined ;
311413 }
312414
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-
353415 private static CurrentVersionInfo ReadCurrentVersionFromRegistry ( )
354416 {
355417 using ( RegistryKey currentVersion = Registry . LocalMachine . OpenSubKey ( @"SOFTWARE\Microsoft\Windows NT\CurrentVersion" ) )
@@ -360,13 +422,18 @@ private static CurrentVersionInfo ReadCurrentVersionFromRegistry()
360422 }
361423 }
362424
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+ }
370437
371438 private static Win32OSCimInfo GetWin32OperatingSystemInfo ( )
372439 {
@@ -443,6 +510,15 @@ private static string Dequote(string s)
443510 return sb . ToString ( ) ;
444511 }
445512
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+
446522 #region IDisposable Support
447523 private bool disposedValue = false ; // To detect redundant calls
448524
0 commit comments