SysInfo

SysInfo

Diese Klasse ist für Debug- und System-Informationen gedacht, insbesondere für Cross-Platform Entwicklung. So wird die aktuelle Runtime (.Net Framework, .Net Core, Mono Framework), das aktuelle Betriebssystem (Windows, Linux, MacOS, ReactOS) inklusive Version ermittelt. Auch bietet diese Klasse diverse Hilfsfunktionen, wie ein Replacement für Environment.NewLine, was fälschlicherweise auch unter Linux CrLf zurück gibt, sowohl unter Mono alsauch unter .Net Core… Weitere Shortcuts sind ebenfalls enthalten, alles in allem also eine sinnvolle Ergänzung für jede Anwendung.

' LICENSE (MIT)
'
' Copyright 2019 Thomas Baumann - tightDev.Net
' 
' Permission is hereby granted, free of charge, to any person obtaining a copy of this
' software and associated documentation files (the "Software"), to deal in the Software
' without restriction, including without limitation the rights to use, copy, modify, merge,
' publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to
' whom the Software is furnished to do so, subject to the following conditions:
'
' The above copyright notice and this permission notice shall be included in all copies or
' substantial portions of the Software.
'
' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
' EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
' MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
' NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
' HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
' IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
' IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
' SOFTWARE.

Imports System.Globalization
Imports System.Reflection
Imports System.Runtime
Imports System.Runtime.InteropServices

''' <summary>Shared class providing several informations about the system.</summary>
''' <remarks>
'''     <para>This Class is compatible with (at least) the .Net Framework 2.0 And .Net Core 2.0.</para>
'''     <para>Validated successfully with:</para>
'''     <list type="bullet">
'''         <item><description>Windows 7 (FW2 x86, x64, Core2 x86, x64)</description></item>
'''         <item><description>ReactOS 0.4.11 (FW2 x86)</description></item>
'''         <item><description>MacOS 10.13 - High Sierra (Mono x64, Core2 x86, x64)</description></item>
'''         <item><description>Debian 9 (Mono x64, Core2 x86, x64)</description></item>
'''         <item><description>Raspberry Pi - Raspbian, like Debian 9 (Mono ARM, Core2 ARM)</description></item>
'''         <item><description><i>more to follow</i></description></item>
'''     </list>
'''     <para>Not validated:</para>
'''     <list type="bullet">
'''         <item><description>Android (any version) - no device to test</description></item>
'''         <item><description>iOS (any version) - no device to test</description></item>
'''         <item><description>Windows Mobile (any version)</description></item>
'''     </list>
'''     <para>Not working:</para>
'''     <list type="bullet">
'''         <item><description>ReactOS 0.4.11 (Core version) - .Net Core unsupported by OS. Use real framework instead.</description></item>
'''     </list>
'''     <para>
'''         <b>System requirements / dependencies:</b>
'''         <br/>Linux: apt-get install mono-runtime, mono-vbnc
'''         <br/>ReactOS: .Net Framework 2 (recommended) or Mono Runtime 4.0.3 (newer versions not supported yet by ReactOS).
'''         <br/>Warning! Mono on ReactOS requires an old version. But this version does not include Microsoft.VisualBasic.dll,
'''         hence all VisualBasic projects which dependences on it will not work. It can be avoided, but this is a very complex task.
'''         Use the real framework instead!
'''         <br/><br/>Note: For *nix systems (Linux, MacOS, ...) you have to mark the .exe file as executable. chmod 744 ./file.exe
'''     </para>
''' </remarks>
Public NotInheritable Class SysInfo

	''' <summary>Contains the runtime which executes the current application.</summary>
	Public Shared ReadOnly Runtime As Runtimes

	''' <summary>Contains the runtime version which executes the current application.</summary>
	Public Shared ReadOnly RuntimeVersion As Version

	''' <summary>Contains the runtime description which executes the current application.</summary>
	Public Shared ReadOnly RuntimeDescription As String

	''' <summary>Contains the CPU architecture the application is running on.</summary>
	Public Shared ReadOnly AppArchitecture As Architectures

	''' <summary>Contains the real CPU architecture the application is running on.</summary>
	Public Shared ReadOnly CpuArchitecture As Architectures

	''' <summary>Contains the number of CPU threads.</summary>
	Public Shared ReadOnly CpuThreads As Integer

	''' <summary>Contains the operating system the application runs on.</summary>
	Public Shared ReadOnly OperatingSystem As OperatingSystems

	''' <summary>Contains a string which describes the operating system.</summary>
	Public Shared ReadOnly OperatingSystemDescription As String

	''' <summary>Returns some hardware relevant information. Strongly dependent on hardware.</summary>
	Public Shared ReadOnly HwInfo As String

	''' <summary>True if this application is running in a wine environment (also affects CrossOver), False otherwise.</summary>
	Public Shared ReadOnly IsInWine As Boolean

	''' <summary>True if this application is running in a live environment (Linux Boot CD, Windows PE), False otherwise.</summary>
	Public Shared ReadOnly IsLive As Boolean

	''' <summary>The new line sequence for this system (cause Environment.NewLine always returns CrLf, which is wrong).</summary>
	Public Shared ReadOnly NewLine As String

	''' <summary>The name of the machine this code is running on.</summary>
	Public Shared ReadOnly MachineName As String

	''' <summary>The domain name of this machine, if joined to a domain. Otherwise this equals to MachineName.</summary>
	Public Shared ReadOnly DomainName As String

	''' <summary>The name of the user which is currently logged on and executes this application.</summary>
	Public Shared ReadOnly UserName As String

	''' <summary>The default language of the operating system as IETF language code (like en-US).</summary>
	Public Shared ReadOnly DefaultLanguage As String

	''' <summary>Debugging messages, only to be used if this class fails in some way.</summary>
	Public Shared ReadOnly Property dmsg As String



	' Deny access to CreateInstance for this shared class
	Private Sub New()
	End Sub

	''' <summary>Initializes all shared variables</summary>
	Shared Sub New()

		' Will be set correctly later, but might be required for dmsg.
		NewLine = Char.ConvertFromUtf32(13) & Char.ConvertFromUtf32(10) ' vbCrLf

		' Try to get .Net Core runtime info -- This will NOT work for portable releases! However, it returns the real runtime version.
		'If Runtime = Runtimes.Unknown Then ' Not required. First try, always unknown.
		Try
			Dim asm As Assembly = GetType(GCSettings).Assembly
			Dim path As String() = asm.CodeBase.Split(New Char() {"/"c, "\"c}, StringSplitOptions.RemoveEmptyEntries)
			Dim ix As Integer = Array.IndexOf(path, "Microsoft.NETCore.App")
			If ix > 0 AndAlso ix < path.Length - 2 Then
				Runtime = Runtimes.NetCore
				RuntimeVersion = New Version(path(ix + 1))
				Dim fva As AssemblyFileVersionAttribute = DirectCast(GetCustomAttribute(Of AssemblyFileVersionAttribute)(asm), AssemblyFileVersionAttribute)
				RuntimeDescription = ".Net Core " & RuntimeVersion.ToString & " (" & fva.Version & ")"
			End If
		Catch ex As Exception
			_dmsg &= ".cctor~GetRuntimeCore:" & NewLine
			_dmsg &= ex.ToString & NewLine
		End Try
		'End If

		' Try to get .Net Core runtime info -- Instable but the only way for portable.
		If Runtime = Runtimes.Unknown Then
			Try
				Dim asm As Assembly = GetType(GCSettings).Assembly
				Dim path As String() = asm.CodeBase.Split(New Char() {"/"c, "\"c}, StringSplitOptions.RemoveEmptyEntries)
				If Array.IndexOf(path, "System.Private.CoreLib.dll") > 0 Then
					' We're on .Net Core (for sure). Portable build, so the Core version is that one which this app is compiled for.
					Runtime = Runtimes.NetCore
					Dim fva As AssemblyFileVersionAttribute = DirectCast(GetCustomAttribute(Of AssemblyFileVersionAttribute)(asm), AssemblyFileVersionAttribute)
					asm = Assembly.GetExecutingAssembly
					For Each obj As Object In asm.GetCustomAttributes(False)
						Dim attr As Attribute = DirectCast(obj, Attribute)
						If attr.GetType.Name = "TargetFrameworkAttribute" Then
							Dim desc As String = DirectCast(attr.GetType.GetProperty("FrameworkName").GetValue(attr, Nothing), String)
							RuntimeVersion = New Version(desc.Substring(desc.LastIndexOf("=v") + 2))
							RuntimeDescription = ".Net Core " & RuntimeVersion.ToString & " (" & fva.Version & ")"
						End If
					Next
				End If
			Catch ex As Exception
				_dmsg &= ".cctor~GetRuntimeCorePortable:" & NewLine
				_dmsg &= ex.ToString & NewLine
			End Try
		End If

		' Try to get Mono runtime info
		If Runtime = Runtimes.Unknown Then
			Try
				Dim tMono As Type = Type.GetType("Mono.Runtime")
				If tMono IsNot Nothing Then
					Runtime = Runtimes.MonoFramework
					Dim displayName As MethodInfo = DirectCast(tMono.GetMethod("GetDisplayName", BindingFlags.NonPublic Or BindingFlags.Static), MethodInfo)
					If displayName IsNot Nothing Then
						Dim tmp As String = DirectCast(displayName.Invoke(Nothing, Nothing), String)
						If tmp.Contains(" "c) Then tmp = tmp.Substring(0, tmp.IndexOf(" "c))
						RuntimeVersion = New Version(tmp.Trim)
						RuntimeDescription = "Mono " & tmp
					End If
				End If
			Catch ex As Exception
				_dmsg &= ".cctor~GetRuntimeMono:" & NewLine
				_dmsg &= ex.ToString & NewLine
			End Try
		End If

		' Fallback, get .Net Framework runtime info
		Try
			If Runtime = Runtimes.Unknown Then
				Runtime = Runtimes.NetFramework
				RuntimeVersion = Environment.Version
				RuntimeDescription = "Microsoft .Net Framework " & RuntimeVersion.ToString
			End If
		Catch ex As Exception
			_dmsg &= ".cctor~GetRuntimeNet:" & NewLine
			_dmsg &= ex.ToString & NewLine
		End Try

		' Get the App architecture
		Try
			Select Case Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE").ToUpperInvariant
				Case "X86" : AppArchitecture = Architectures.x86
				Case "AMD64" : AppArchitecture = Architectures.x64
				Case "IA64" : AppArchitecture = Architectures.IA64
				Case "EM64T" : AppArchitecture = Architectures.EM64T
				Case Else : AppArchitecture = Architectures.Unknown
			End Select
		Catch ex As Exception
			_dmsg &= ".cctor~GetAppArch:" & NewLine
			_dmsg &= ex.ToString & NewLine
		End Try

		' Get the CPU architecture and threads
		Try
			Dim sinfo As New NativeTypes.SYSTEM_INFO
			Call NativeMethods.GetNativeSystemInfo(sinfo)
			Select Case sinfo.wProcessorArchitecture
				Case NativeTypes.PROCESSOR_ARCHITECTURE.Intel : CpuArchitecture = Architectures.x86
				Case NativeTypes.PROCESSOR_ARCHITECTURE.ARM : CpuArchitecture = Architectures.ARM
				Case NativeTypes.PROCESSOR_ARCHITECTURE.IA64 : CpuArchitecture = Architectures.IA64
				Case NativeTypes.PROCESSOR_ARCHITECTURE.AMD64 : CpuArchitecture = Architectures.x64
				Case NativeTypes.PROCESSOR_ARCHITECTURE.ARM64 : CpuArchitecture = Architectures.ARM64
				Case NativeTypes.PROCESSOR_ARCHITECTURE.Unknown : CpuArchitecture = Architectures.Unknown
				Case Else : CpuArchitecture = Architectures.Unknown
			End Select
			CpuThreads = sinfo.dwNumberOfProcessors
		Catch ex As Exception
			_dmsg &= ".cctor~GetCpuArchDefault:" & NewLine
			_dmsg &= ex.ToString & NewLine
		End Try
		' Maybe running on mono, above code will fail.
		If CpuArchitecture = Architectures.Unknown Then
			Try
				Dim Temp As String = ReadAllFromProc("uname -m") ' Linux
				Dim IsArm As Boolean = Temp.ToLowerInvariant.Contains("arm")
				Temp = ReadAllFromProc("getconf LONG_BIT")
				Select Case Temp.Trim
					Case "64" : CpuArchitecture = If(IsArm, Architectures.ARM64, Architectures.x64)
					Case "32" : CpuArchitecture = If(IsArm, Architectures.ARM, Architectures.x86)
				End Select
			Catch ex As Exception
				_dmsg &= ".cctor~GetCpuArchÜnix:" & NewLine
				_dmsg &= ex.ToString & NewLine
			End Try
		End If
		If AppArchitecture = Architectures.Unknown Then
			Select Case CpuArchitecture
				Case Architectures.ARM
					AppArchitecture = Architectures.ARM
				Case Architectures.x86
					AppArchitecture = Architectures.x86
				Case Architectures.ARM64 ' Untested (yet)
					If Marshal.SizeOf(GetType(IntPtr)) = 4 Then
						AppArchitecture = Architectures.ARM ' Usually never reached if compiled for AnyCpu.
					Else
						AppArchitecture = Architectures.ARM64
					End If
				Case Architectures.EM64T ' Untested, very rare. But should work
					If Marshal.SizeOf(GetType(IntPtr)) = 4 Then
						AppArchitecture = Architectures.x86 ' Usually never reached if compiled for AnyCpu.
					Else
						AppArchitecture = Architectures.EM64T
					End If
				Case Architectures.IA64 ' Untested, very rare. But should work
					If Marshal.SizeOf(GetType(IntPtr)) = 4 Then
						AppArchitecture = Architectures.x86 ' Usually never reached if compiled for AnyCpu.
					Else
						AppArchitecture = Architectures.IA64
					End If
				Case Architectures.x64
					If Marshal.SizeOf(GetType(IntPtr)) = 4 Then
						AppArchitecture = Architectures.x86 ' Usually never reached if compiled for AnyCpu.
					Else
						AppArchitecture = Architectures.x64
					End If
			End Select
		End If
		If CpuThreads = 0 Then
			Try
				CpuThreads = CInt(ReadAllFromProc("nproc"))
			Catch ex As Exception
				_dmsg &= ".cctor~GetCpuThreadsÜnix:" & NewLine
				_dmsg &= ex.ToString & NewLine
			End Try
		End If

		' Get operating system and description
		Dim Info As Dictionary(Of String, String)
		Try
			'If Info.Count = 0 Then
			Info = ReadKVFromProc("cat /etc/os-release") ' *nix
			If Info.Count > 0 Then
				If TryGet(Info, "NAME").Contains("Linux") Then OperatingSystem = OperatingSystems.Linux
				OperatingSystemDescription = TryGet(Info, "PRETTY_NAME")
				If Info.ContainsKey("ID_LIKE") Then OperatingSystemDescription &= " like " & Info("ID_LIKE")
			End If
			'End If
			If Info.Count = 0 Then
				Info = ReadKVFromProc("sw_vers") ' MacOS
				If Info.Count > 0 Then
					If TryGet(Info, "ProductName") = "Mac OS X" Then OperatingSystem = OperatingSystems.MacOS
					OperatingSystemDescription = TryGet(Info, "ProductName") & " " & GetNameMacOS(TryGet(Info, "ProductVersion"))
				End If
			End If
			If Info.Count = 0 Then
				Info = ReadKVFromProc("CMD /C VER") ' ReactOS, Windows
				If Info.Count > 0 Then
					If Info.ContainsKey("ReactOS") Then
						OperatingSystem = OperatingSystems.ReactOS
						OperatingSystemDescription = "ReactOS " & TryGet(Info, "Version")
					End If
					If Info.ContainsKey("Microsoft") Then
						OperatingSystem = OperatingSystems.Windows
						Dim ver As String = Info("Microsoft").Substring(Info("Microsoft").IndexOf("["c) + 9).TrimEnd("]"c)
						OperatingSystemDescription = GetNameWindows(ver)
					End If
				End If
			End If
		Catch ex As Exception
			_dmsg &= ".cctor~GetOS&Desc:" & NewLine
			_dmsg &= ex.ToString & NewLine
		End Try

		' Check for Live / WinPE
		Try
			If Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SYSTEM\ControlSet001\Control\MiniNT") IsNot Nothing Then
				IsLive = True
			End If
		Catch ex As Exception
			_dmsg &= ".cctor~GetIsLive:" & NewLine
			_dmsg &= ex.ToString & NewLine
		End Try
		'TODO: Try to get it working on Linux, too.

		' Get the new line sequence for this operating system. Yup, Environment.NewLine does NOT work, it always returns CrLf...
		If _
		  OperatingSystem = OperatingSystems.Windows OrElse
		  OperatingSystem = OperatingSystems.ReactOS OrElse
		  OperatingSystem = OperatingSystems.Unknown Then
			NewLine = Char.ConvertFromUtf32(13) & Char.ConvertFromUtf32(10) ' vbCrLf
		Else
			NewLine = Char.ConvertFromUtf32(10) ' vbLf
		End If

		' Get hardware info
		'If String.IsNullOrEmpty(HwInfo) Then ' Not required, first call, hence always empty
		Try
			HwInfo = ReadAllFromProc("cat /proc/device-tree/model")
		Catch ex As Exception
			_dmsg &= ".cctor~GetHWInfo~Raspberry:" & NewLine
			_dmsg &= ex.ToString & NewLine
		End Try
		'End If
		If String.IsNullOrEmpty(HwInfo) Then
			Try
				Dim tmp1 As Dictionary(Of String, String) = ReadKVFromProc("lscpu")
				Dim tmp2 As String = TryGet(tmp1, "Model name")
				Do While tmp2.Contains("  ")
					tmp2 = tmp2.Replace("  ", " ")
				Loop
				HwInfo = tmp2
			Catch ex As Exception
				_dmsg &= ".cctor~GetHWInfo~Ünix:" & NewLine
				_dmsg &= ex.ToString & NewLine
			End Try
		End If
		If String.IsNullOrEmpty(HwInfo) Then
			Try
				Dim sinfo As New NativeTypes.SYSTEM_INFO
				Call NativeMethods.GetNativeSystemInfo(sinfo)
				HwInfo = sinfo.wProcessorArchitecture.ToString &
				  " Level " & sinfo.wProcessorLevel.ToString &
				  " Revision " & sinfo.wProcessorRevision.ToString
			Catch ex As Exception
				_dmsg &= ".cctor~GetHWInfo~Windows:" & NewLine
				_dmsg &= ex.ToString & NewLine
			End Try
		End If
		If String.IsNullOrEmpty(HwInfo) Then
			HwInfo = "(unknown)"
		End If

		' Check Wine / CrossOver environment
		Try
			Dim Temp As String = Environment.GetEnvironmentVariable("WINESERVER", EnvironmentVariableTarget.Process)
			If Not String.IsNullOrEmpty(Temp) Then IsInWine = True
		Catch ex As Exception
			_dmsg &= ".cctor~CheckWine:" & NewLine
			_dmsg &= ex.ToString & NewLine
		End Try

		' Some other basic stuff (works everywhere, but why not?)
		MachineName = Environment.MachineName
		DomainName = Environment.UserDomainName
		UserName = Environment.UserName
		DefaultLanguage = CultureInfo.CurrentUICulture.IetfLanguageTag

	End Sub



	''' <summary>Returns a string containing all fields and their values.</summary>
	''' <returns>A string containing all fields and their values.</returns>
	Public Shared Function Dump() As String

		Dim RetVal As String = "Dump of SysInfo:" & NewLine
		For Each fi As FieldInfo In GetType(SysInfo).GetFields
			If fi.Name = "dmsg" Then
				' skip debug message string for this.
			ElseIf fi.Name = "NewLine" Then
				Dim val As String = fi.GetValue(Nothing).ToString
				Dim tmp As String = "0x"
				For Each c As Char In val.ToCharArray
					tmp &= Convert.ToInt16(c).ToString("X2")
				Next
				RetVal &= "  " & fi.Name & ": " & tmp & NewLine
			Else
				RetVal &= "  " & fi.Name & ": " & If(fi.GetValue(Nothing) IsNot Nothing, fi.GetValue(Nothing).ToString, "(unknown)") & NewLine
			End If
		Next
		Return RetVal.Substring(0, RetVal.Length - NewLine.Length)

	End Function

	''' <summary>Helper routine to get a custom attribute from an assembly.</summary>
	''' <typeparam name="T">The type of the attribute to retrieve.</typeparam>
	''' <param name="asm">The assembly to get the attribute from.</param>
	''' <returns>The selected attribute if found, null/Nothing otherwise.</returns>
	Private Shared Function GetCustomAttribute(Of T)(asm As Assembly) As Attribute

		For Each attr As Attribute In asm.GetCustomAttributes(False)
			If TypeOf attr Is T Then Return attr
		Next
		Return Nothing

	End Function

	''' <summary>Returns a dictionary of key values from a process standard output stream.</summary>
	''' <param name="Proc">The process file name to execute.</param>
	''' <returns>A dictionary containing all key value entries.</returns>
	Private Shared Function ReadKVFromProc(Proc As String) As Dictionary(Of String, String)

		Dim Entries As New Dictionary(Of String, String)
		Try
			Using p As New Process
				p.StartInfo.CreateNoWindow = True
				If Proc.Contains(" "c) Then
					p.StartInfo.FileName = Proc.Substring(0, Proc.IndexOf(" "c))
					p.StartInfo.Arguments = Proc.Substring(Proc.IndexOf(" "c) + 1)
				Else
					p.StartInfo.FileName = Proc
				End If
				p.StartInfo.RedirectStandardOutput = True
				p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
				p.StartInfo.UseShellExecute = False
				p.Start()
				Do While Not p.StandardOutput.EndOfStream
					Dim Line As String = p.StandardOutput.ReadLine
					If Line.Contains(":"c) Then ' *nix way
						Entries.Add(
						  Line.Substring(0, Line.IndexOf(":"c)).Trim,
						  Line.Substring(Line.IndexOf(":"c) + 1).Trim
						)
					ElseIf Line.Contains("="c) Then ' MacOS way
						Entries.Add(
						  Line.Substring(0, Line.IndexOf("="c)).Trim,
						  Line.Substring(Line.IndexOf("="c) + 1).Trim
						)
					ElseIf Line.Contains(" "c) Then ' Windows way
						Entries.Add(
						  Line.Substring(0, Line.IndexOf(" "c)).Trim,
						  Line.Substring(Line.IndexOf(" "c) + 1).Trim
						)
					End If
				Loop
			End Using
		Catch ex As Exception
			_dmsg &= "ReadKVFromProc:" & NewLine
			_dmsg &= ex.ToString & NewLine
		End Try
		Return Entries

	End Function

	''' <summary>Returns a string with the output from a process.</summary>
	''' <param name="Proc">The process file name to execute.</param>
	''' <returns>A string containing the output of the process.</returns>
	Private Shared Function ReadAllFromProc(Proc As String) As String

		Dim Entries As String = ""
		Try
			Using p As New Process
				p.StartInfo.CreateNoWindow = True
				If Proc.Contains(" "c) Then
					p.StartInfo.FileName = Proc.Substring(0, Proc.IndexOf(" "c))
					p.StartInfo.Arguments = Proc.Substring(Proc.IndexOf(" "c) + 1)
				Else
					p.StartInfo.FileName = Proc
				End If
				p.StartInfo.RedirectStandardOutput = True
				p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
				p.StartInfo.UseShellExecute = False
				p.Start()
				Entries = p.StandardOutput.ReadToEnd
			End Using
		Catch ex As Exception
			_dmsg &= "ReadAllFromProc:" & NewLine
			_dmsg &= ex.ToString & NewLine
		End Try
		Return Entries

	End Function

	''' <summary>Gets a value from a dictionary, regardless if it exists or not.</summary>
	''' <param name="Dict">The dictionary to search in.</param>
	''' <param name="Key">The key to search for.</param>
	''' <returns>If found the value will be returned, if not String.Empty.</returns>
	Private Shared Function TryGet(Dict As Dictionary(Of String, String), Key As String) As String

		If Dict.ContainsKey(Key) Then Return Dict(Key)
		Return String.Empty

	End Function

	''' <summary>Returns the name for the MacOS version.</summary>
	''' <param name="Version">The MacOS version to get the name from.</param>
	''' <returns>The name for the MacOS version.</returns>
	Private Shared Function GetNameMacOS(Version As String) As String

		If Version.StartsWith("10.0.") Then
			Return "Cheetah (" & Version & ")"
		ElseIf Version.StartsWith("10.1.") Then
			Return "Puma (" & Version & ")"
		ElseIf Version.StartsWith("10.2.") Then
			Return "Jaguar (" & Version & ")"
		ElseIf Version.StartsWith("10.3.") Then
			Return "Panther (" & Version & ")"
		ElseIf Version.StartsWith("10.4.") Then
			Return "Tiger (" & Version & ")"
		ElseIf Version.StartsWith("10.5.") Then
			Return "Leopard (" & Version & ")"
		ElseIf Version.StartsWith("10.6.") Then
			Return "Snow Leopard (" & Version & ")"
		ElseIf Version.StartsWith("10.7.") Then
			Return "Lion (" & Version & ")"
		ElseIf Version.StartsWith("10.8.") Then
			Return "Mountain Lion (" & Version & ")"
		ElseIf Version.StartsWith("10.9.") Then
			Return "Mavericks (" & Version & ")"
		ElseIf Version.StartsWith("10.10.") Then
			Return "Yosemite (" & Version & ")"
		ElseIf Version.StartsWith("10.11.") Then
			Return "El Captain (" & Version & ")"
		ElseIf Version.StartsWith("10.12.") Then
			Return "Sierra (" & Version & ")"
		ElseIf Version.StartsWith("10.13.") Then
			Return "High Sierra (" & Version & ")"
		ElseIf Version.StartsWith("10.14.") Then
			Return "Mojave (" & Version & ")"
		Else
			Return " (" & Version & ")"
		End If

	End Function

	''' <summary>Returns the name for the Windows version.</summary>
	''' <param name="Version">The Windows version to get the name from.</param>
	''' <returns>The name for the Windows version.</returns>
	Private Shared Function GetNameWindows(Version As String) As String

		If Version.StartsWith("10.") Then
			Return "Windows 10 / Server 2016 / Server 2019 (" & Version & ")"
		ElseIf Version.StartsWith("6.3.") Then
			Return "Windows 8.1 / Server 2012 R2 (" & Version & ")"
		ElseIf Version.StartsWith("6.2.") Then
			Return "Windows 8 / Server 2012 (" & Version & ")"
		ElseIf Version.StartsWith("6.1.") Then
			Return "Windows 7 / Server 2008 R2 (" & Version & ")"
		ElseIf Version.StartsWith("6.0.") Then
			Return "Windows Vista / Server 2008 (" & Version & ")"
		ElseIf Version.StartsWith("5.2.") Then
			Return "Windows XP x64 / Server 203 (R2) (" & Version & ")"
		ElseIf Version.StartsWith("5.1.") Then
			Return "Windows XP (" & Version & ")"
		ElseIf Version.StartsWith("5.0.") Then
			Return "Windows 2000 (" & Version & ")"
		ElseIf Version.StartsWith("4.9.") Then
			Return "Windows ME (" & Version & ")"
		ElseIf Version.StartsWith("4.10.") Then
			Return "Windows 98 (" & Version & ")"
		ElseIf Version.StartsWith("4.0.") Then
			Return "Windows 95 / NT 4.0 (" & Version & ")"
		Else
			Return " (" & Version & ")"
		End If

	End Function



	''' <summary>Native methods used inside this class.</summary>
	Private Class NativeMethods

		' Deny CreateInstance for this shared class
		Private Sub New()
		End Sub


		''' <summary>
		'''     Retrieves information about the current system to an application running under WOW64. If the
		'''     function is called from a 64-bit application, or on a 64-bit system that does not have an Intel64
		'''     or x64 processor (such as ARM64), it is equivalent to the GetSystemInfo function.
		''' </summary>
		''' <param name="lpSystemInfo">
		'''     A pointer to a <see cref="NativeTypes.SYSTEM_INFO">SYSTEM_INFO</see> structure that receives the information.
		''' </param>
		<DllImport("kernel32.dll")>
		Friend Shared Sub GetNativeSystemInfo(<Out> ByRef lpSystemInfo As NativeTypes.SYSTEM_INFO)
		End Sub

	End Class


	''' <summary>Native types used inside this class.</summary>
	Private Class NativeTypes

		' Deny CreateInstance for this shared class
		Private Sub New()
		End Sub


		''' <summary>
		'''     Contains information about the current computer system. This includes the architecture and type of the
		'''     processor, the number of processors in the system, the page size, and other such information.
		''' </summary>
		<StructLayout(LayoutKind.Sequential)>
		Friend Structure SYSTEM_INFO

			''' <summary>The processor architecture of the installed operating system.</summary>
			Friend wProcessorArchitecture As PROCESSOR_ARCHITECTURE

			''' <summary>This member is reserved for future use.</summary>
			Friend wReserved As UInt16

			''' <summary>
			'''     The page size and the granularity of page protection and commitment. This is the page
			'''     size used by the VirtualAlloc function.
			''' </summary>
			Friend dwPageSize As Integer

			''' <summary>A pointer to the lowest memory address accessible to applications and dynamic-link libraries (DLLs).</summary>
			Friend lpMinimumApplicationAddress As IntPtr

			''' <summary>A pointer to the highest memory address accessible to applications and DLLs.</summary>
			Friend lpMaximumApplicationAddress As IntPtr

			''' <summary>A mask representing the set of processors configured into the system. Bit 0 is processor 0; bit 31 is processor 31.</summary>
			Friend dwActiveProcessorMask As IntPtr

			''' <summary>
			'''     The number of logical processors in the current group. To retrieve this value, use the
			'''     GetLogicalProcessorInformation function.
			''' </summary>
			Friend dwNumberOfProcessors As Int32

			''' <summary>
			'''     An obsolete member that is retained for compatibility. Use the wProcessorArchitecture,
			'''     wProcessorLevel, and wProcessorRevision members to determine the type of processor.
			''' </summary>
			<Obsolete> Friend dwProcessorType As Int32

			''' <summary>
			'''     The granularity for the starting address at which virtual memory can be allocated. For
			'''     more information, see VirtualAlloc.
			''' </summary>
			Friend dwAllocationGranularity As Int32

			''' <summary>
			'''     The architecture-dependent processor level. It should be used only for display purposes.
			'''     To determine the feature set of a processor, use the IsProcessorFeaturePresent function.
			'''     <br/>If wProcessorArchitecture Is PROCESSOR_ARCHITECTURE_INTEL, wProcessorLevel Is defined by the CPU vendor.
			'''     <br/>If wProcessorArchitecture Is PROCESSOR_ARCHITECTURE_IA64, wProcessorLevel Is Set To 1.
			''' </summary>
			Friend wProcessorLevel As Int16

			''' <summary>
			'''     The architecture-dependent processor revision. The following table shows how the revision
			'''     value is assembled for each type of processor architecture.
			''' </summary>
			Friend wProcessorRevision As Int16

		End Structure


		''' <summary>The processor architecture of the installed operating system.</summary>
		Friend Enum PROCESSOR_ARCHITECTURE As UInt16

			''' <summary>x86</summary>
			Intel = 0

			''' <summary>ARM</summary>
			ARM = 5

			''' <summary>Intel Itanium-based.</summary>
			IA64 = 6

			''' <summary>x64 (AMD or Intel)</summary>
			AMD64 = 9

			''' <summary>ARM64</summary>
			ARM64 = 12

			''' <summary>Unknown architecture.</summary>
			Unknown = &HFFFF

		End Enum

	End Class

End Class



''' <summary>Enumeration of possible CPU architectures.</summary>
Public Enum Architectures

	''' <summary>Not implemented CPU architecture or unable to detect it.</summary>
	Unknown = 0

	''' <summary>The CPU is a common 32 bit processor.</summary>
	x86 = 1

	''' <summary>The CPU is a common 64 bit processor.</summary>
	x64 = 2

	''' <summary>The CPU is a Itanium 64 bit processor.</summary>
	''' <remarks>Very rarely used.</remarks>
	IA64 = 3

	''' <summary>The CPU is a Intel 64 bit processor.</summary>
	''' <remarks>Very rarely used.</remarks>
	EM64T = 4

	''' <summary>The CPU is an ARM 32 bit processor.</summary>
	''' <remarks>Only supported by .Net Core.</remarks>
	ARM = 5

	''' <summary>The CPU is an ARM 64 bit processor.</summary>
	''' <remarks>Only supported by .Net Core.</remarks>
	ARM64 = 6

End Enum


''' <summary>Enumeration of possible runtimes which hosts this application.</summary>
Public Enum Runtimes

	''' <summary>Unknown runtime or error during detecting it.</summary>
	Unknown = 0

	''' <summary>Microsoft .Net Framework.</summary>
	NetFramework = 1

	''' <summary>Microsoft .Net Core Framework.</summary>
	NetCore = 2

	''' <summary>Mono Framework.</summary>
	MonoFramework = 3

End Enum


''' <summary>Enumeration which defines the operating system.</summary>
Public Enum OperatingSystems

	''' <summary>Unknown operating system or error during detecting it.</summary>
	Unknown = 0

	''' <summary>Microsoft Windows.</summary>
	Windows = 1

	''' <summary>ReactOS.</summary>
	ReactOS = 2

	''' <summary>Apple MacOS X.</summary>
	MacOS = 3

	''' <summary>Linux.</summary>
	Linux = 4

End Enum