GetFirmwareBIOSorUEFI.psm1
<#
The function in this module determines the underlying system firmware (BIOS) type - either UEFI or Legacy BIOS. The function can use one of three methods to determine the firmware type: The first method relies on the fact that Windows setup detects the firmware type as a part of the Windows installation routine and records its findings in the setupact.log file in the \Windows\Panther folder. It's a trivial task to use Select-String to extract the relevant line from this file and to pick off the (U)EFI or BIOS keyword it contains. To do a proper job there are two choices; both involve using Win32 APIs which we call from PowerShell through a compiled (Add-Type) class using P/Invoke. For Windows 7/Server 2008R2 and above, the GetFirmwareEnvironmentVariable Win32 API (designed to extract firmware environment variables) can be used. This API is not supported on non-UEFI firmware and will fail in a predictable way when called - this will identify a legacy BIOS. On UEFI firmware, the API can be called with dummy parameters, and while it will still fail (probably!) the resulting error code will be different from the legacy BIOS case. For Windows 8/Server 2012 and above there's a more elegant solution in the form of the GetFirmwareType() API. This returns an enum (integer) indicating the underlying firmware type. Chris Warwick, @cjwarwickps, September 2013. (This version, November 2015) chrisjwarwick.wordpress.com See all my PS Gallery modules: Find-Module | Where Author -match 'Chris Warwick' #> #Requires -Version 2 #region Helper Functions # The folowing function works on all Windows versions: <# .Synopsis Determines underlying firmware (BIOS) type and return an object describing the Firmware. .DESCRIPTION This function seraches the windows Setup log file (setupact.log in the \Windows\Panther folder) to determine whether setup identified a UEFI environment or a legacy BIOS. .EXAMPLE Get-LogFileFirmwareType # Returns custom object describing firmware .OUTPUTS 'FirmwareType', object describing firmware type .FUNCTIONALITY Determines underlying system firmware type #> Function Get-LogFileFirmwareType { [OutputType('FirmwareType')] Param() # Template object to return, modified below... $FirmwareType = [PsCustomObject]@{ PsTypeName = 'FirmwareType' IsUEFI = $False IsBIOS = $False Undetermined = $True FirmwareType = 'Undetermined' # Set to 'BIOs' or 'UEFI' when found } # Extract the firmware type from the 'setupact.log' file - see what bios type was detected (EFI or BIOS) at install time $Panther = "$Env:windir\Panther\setupact.log" If (Test-Path -Path $Panther -PathType Leaf) { $FirmwareString = (Select-String 'Detected boot environment:' $Panther -AllMatches ).Line -replace '.*:\s+' Switch -Regex ($FirmwareString) { '^BIOS$' {$FirmwareType.IsBIOS = $True $FirmwareType.Undetermined = $False $FirmwareType.FirmwareType = 'BIOS' } '^U?EFI$' {$FirmwareType.IsUEFI = $True $FirmwareType.Undetermined = $False $FirmwareType.FirmwareType = 'UEFI' } Default {Break} # Can't determine f/w type, just return the 'Undetermined' object defined above } } $FirmwareType } <# (Windows 7/Server 2008R2 or above) Use the GetFirmwareEnvironmentVariable Win32 API. From MSDN (http://msdn.microsoft.com/en-ca/library/windows/desktop/ms724325%28v=vs.85%29.aspx): "Firmware variables are not supported on a legacy BIOS-based system. The GetFirmwareEnvironmentVariable function will always fail on a legacy BIOS-based system, or if Windows was installed using legacy BIOS on a system that supports both legacy BIOS and UEFI. "To identify these conditions, call the function with a dummy firmware environment name such as an empty string ("") for the lpName parameter and a dummy GUID such as "{00000000-0000-0000-0000-000000000000}" for the lpGuid parameter. On a legacy BIOS-based system, or on a system that supports both legacy BIOS and UEFI where Windows was installed using legacy BIOS, the function will fail with ERROR_INVALID_FUNCTION. On a UEFI-based system, the function will fail with an error specific to the firmware, such as ERROR_NOACCESS, to indicate that the dummy GUID namespace does not exist." From PowerShell, we can call the API via P/Invoke from a compiled C# class using Add-Type. In Win32 any resulting API error is retrieved using GetLastError(), however, this is not reliable in .Net (see blogs.msdn.com/b/adam_nathan/archive/2003/04/25/56643.aspx), instead we mark the pInvoke signature for GetFirmwareEnvironmentVariableA with SetLastError=true and use Marshal.GetLastWin32Error() Note: The GetFirmwareEnvironmentVariable API requires the SE_SYSTEM_ENVIRONMENT_NAME privilege. In the Security Policy editor this equates to "User Rights Assignment": "Modify firmware environment values" and is granted to Administrators by default. Because we don't actually read any variables this permission appears to be optional. #> <# .Synopsis Determines underlying firmware (BIOS) type and return an object describing the Firmware. .DESCRIPTION This function uses a complied Win32 API call to determine the underlying system firmware type. .EXAMPLE Get-FirmwareEnvironmentVariableAPI # Returns custom object describing firmware .OUTPUTS 'FirmwareType', object describing firmware type .FUNCTIONALITY Determines underlying system firmware type #> Function Get-FirmwareEnvironmentVariableAPI { [OutputType('FirmwareType')] Param() # Wrap the 'GetFirmwareEnvironmentVariableA' API... Add-Type -Language CSharp -TypeDefinition @' using System; using System.Runtime.InteropServices; public class CheckUEFI { [DllImport("kernel32.dll", SetLastError=true)] static extern UInt32 GetFirmwareEnvironmentVariableA(string lpName, string lpGuid, IntPtr pBuffer, UInt32 nSize); const int ERROR_INVALID_FUNCTION = 1; public static bool IsUEFI() { // Try to call the GetFirmwareEnvironmentVariable API. This is invalid on legacy BIOS. GetFirmwareEnvironmentVariableA("","{00000000-0000-0000-0000-000000000000}",IntPtr.Zero,0); if (Marshal.GetLastWin32Error() == ERROR_INVALID_FUNCTION) return false; // API not supported (INVALID_FUNCTION); this is a legacy BIOS else return true; // Call to API is supported. This is UEFI. } } '@ # Call API and return result If ([CheckUEFI]::IsUEFI()) { [PsCustomObject]@{ PsTypeName = 'FirmwareType' IsUEFI = $True IsBIOS = $False Undetermined = $False FirmwareType = 'UEFI' } } else { [PsCustomObject]@{ PsTypeName = 'FirmwareType' IsUEFI = $False IsBIOS = $True Undetermined = $False FirmwareType = 'BIOS' } } } <# (Windows 8/Server 2012 or above) Use GetFirmwareTtype() Win32 API. In Windows 8/Server 2012 and above there's an API that directly returns the firmware type and doesn't rely on a hack. GetFirmwareType() in kernel32.dll (http://msdn.microsoft.com/en-us/windows/desktop/hh848321%28v=vs.85%29.aspx) returns a pointer to a FirmwareType enum that defines the following: typedef enum _FIRMWARE_TYPE { FirmwareTypeUnknown = 0, FirmwareTypeBios = 1, FirmwareTypeUefi = 2, FirmwareTypeMax = 3 } FIRMWARE_TYPE, *PFIRMWARE_TYPE; Once again, this API call can be called in .Net via P/Invoke. #> <# .Synopsis Determines underlying firmware (BIOS) type and return an object describing the Firmware. .DESCRIPTION This function uses a complied Win32 API call to determine the underlying system firmware type. .EXAMPLE Get-FirmwareEnvironmentVariableAPI # Returns custom object describing firmware .OUTPUTS 'FirmwareType', object describing firmware type .FUNCTIONALITY Determines underlying system firmware type #> Function Get-FirmwareTypeAPI { [OutputType('FirmwareType')] Param() # Wrap the 'GetFirmwareType' API... Add-Type -Language CSharp -TypeDefinition @' using System; using System.Runtime.InteropServices; public class FirmwareType { [DllImport("kernel32.dll")] static extern bool GetFirmwareType(ref uint FirmwareType); public static uint GetFirmwareType() { uint firmwaretype = 0; if (GetFirmwareType(ref firmwaretype)) return firmwaretype; else return 0; // API call failed, just return 'unknown' } } '@ # Template object, modified below... $FirmwareType = [PsCustomObject]@{ PsTypeName = 'FirmwareType' IsUEFI = $False IsBIOS = $False Undetermined = $True FirmwareType = 'Undetermined' # Set to 'BIOs' or 'UEFI' when found } Switch ([FirmwareType]::GetFirmwareType()) { 0 {Break} # Can't determine f/w type, just return the 'Undetermined' object defined above 1 {$FirmwareType.IsBIOS = $True $FirmwareType.Undetermined = $False $FirmwareType.FirmwareType = 'BIOS' } 2 {$FirmwareType.IsUEFI = $True $FirmwareType.Undetermined = $False $FirmwareType.FirmwareType = 'UEFI' } } $FirmwareType } # Determine the version of the host OS # Windows 7, Server 2008R2 -> 6.1.x # Windows 8, Server 2012 -> 6.2.x # Windows 8.1, Server 2012R2 -> 6.3.x # Windows 10, Server 2016 -> 10.0.x Function Get-OSVersion { [OutputType([System.Version])] Param() [System.Version](Get-WmiObject -Query 'Select Version from WIN32_OperatingSystem').Version } #endregion Helper Functions # ------------------------------------------------------------------------------------------------------- # The following function wraps the helper functions above. If the 'QueryType' parameter isn't specified # the function will determine the most appropriate technique based on the current OS version <# .Synopsis This cmdlet determines the underlying system firmware (BIOS) type - either UEFI or Legacy BIOS. .Description This cmdlet determines the underlying system firmware (BIOS) type - either UEFI or Legacy BIOS. The function will use one of three methods to determine the firmware type: The first method relies on the fact that Windows setup detects the firmware type as a part of the Windows installation routine and records its findings in the setupact.log file in the \Windows\Panther folder. It's a trivial task to use Select-String to extract the relevant line from this file and to pick off the (U)EFI or BIOS keyword it contains. To do a proper job there are two choices; both involve using Win32 APIs which we call from PowerShell through a compiled (Add-Type) class using P/Invoke. For Windows 7/Server 2008R2 and above, the GetFirmwareEnvironmentVariable Win32 API (designed to extract firmware environment variables) can be used. This API is not supported on non-UEFI firmware and will fail in a predictable way when called - this will identify a legacy BIOS. On UEFI firmware, the API can be called with dummy parameters, and while it will still fail (probably!) the resulting error code will be different from the legacy BIOS case. For Windows 8/Server 2012 and above there's a more elegant solution in the form of the GetFirmwareType() API. This returns an enum (integer) indicating the underlying firmware type. .Example Get-FirmwareType Determines the firmware type of the current machine using the most appropriate technique based on OS version .Example Get-FirmwareType -Auto Determines the firmware type of the current machine using the most appropriate technique based on OS version .Example Get-FirmwareType -SetupLog Determines the firmware type of the current machine by reading the Setup log file .Example Get-FirmwareType -GetFirmwareType Determines the firmware type of the current machine by using the GetFirmwareType() API call. (Windows 8+ only) .Inputs None .Outputs ['FirmwareType'] PS Custom object describing the machine firmware type .Parameter QueryType Use this parameter to force a particular query type (if not specified this will default to 'Auto') Valid values are: SetupLog - look for the machine firmware type in the Windows Setup log file GetFirmwareEnvironmentVariable - uses the GetFirmwareEnvironmentVariable Win32 API call (Windows 7/Server 208R2 and above) GetFirmwareType - uses the GetFirmwareType Win32 API call (Windows 8/Server 2012R2 and above) Auto - uses the most appropriate technique depending on the underlying OS version .Notes Can only run against the local machine currently .Functionality Determine the firmware type of the current machine #> Function Get-FirmwareType { [CmdletBinding()] [OutputType('FirmwareType')] Param( [Parameter()] [ValidateSet( 'SetupLog', 'GetFirmwareEnvironmentVariable', 'GetFirmwareType', 'Auto' )] [String]$QueryType='Auto' ) # Convert the 'Auto' query type into one of the three supported types based on OS Version: If ($QueryType -eq 'Auto') { Switch (Get-OSVersion) { {$_ -ge [System.Version]'6.2.0.0'} { Write-Verbose "OS Version $OSVersion, Windows 8.0, Server 2012 or above -> Using GetFirmwareType() API." $QueryType = 'GetFirmwareType' Break } {$_ -ge [System.Version]'6.1.0.0'} { Write-Verbose "OS Version $OSVersion, Windows 7, Server 2008R2 or above -> Using GetFirmwareEnvironmentVariable() API." $QueryType = 'GetFirmwareEnvironmentVariable' Break } Default { Write-Verbose "OS Version $OSVersion, Windows Vista, Server 2008 or below -> Using $Env:windir\Panther\setupact.log file." $QueryType = 'SetupLog' Break } } } # Determine the Firmware type using the specified method... Switch ($QueryType) { GetFirmwareType { Get-FirmwareTypeAPI } GetFirmwareEnvironmentVariable { Get-FirmwareEnvironmentVariableAPI } SetupLog { Get-LogFileFirmwareType } } } # An electron is pulled-up for speeding. The policeman says, “Sir, do you realise you were travelling at 130mph?” The electron says, “Oh great, now I’m lost.” |