Getting Monitor Hardware Information
Ok, so I forgot how to post to this blog for a couple of years. I finally had a reason to track it down.
First, I’d like to acknowledge an unknown scripter who created this script. I tried to track down the original author of the VBScript I started with. This particular script is posted on a variety of forums with a number of edits along the way—presumably added by multiple people, and I was unable to locate the original source, but to this unknown person, thank you.
Now, I took this quite complex script which collects EDID information from the registry. I converted it to PowerShell. Like most VBScript to PowerShell conversions, the new script is greatly less complicated and more readable.
This script utilizes PowerShell Remoting (https://technet.microsoft.com/en-us/library/dd347744.aspx) if a computer list is supplied on the pipeline, so you would need to make sure that you have configured PowerShell for remoting. If you don’t supply a computer list, the script block will be executed on the local computer.
There is an optional LogPath parameter. You would run it as follows:
- .\Get-MonitorInfo.ps1
- "computer1","computer2","computer3" | .\Get-MonitorInfo.ps1 -LogPath
.\monitorlist.csv
Param ([string] $LogPath)
$ComputerList = @()
# Computer names should be piped into the script (comma separated)
$input | ForEach-Object {$ComputerList += $_}
Function Main {
If ($ComputerList){
# If a list of computers was specified on the pipeline, execute
# the scriptblock remotely against all in a single job
$Job = Invoke-Command -ScriptBlock $ScriptBlock -AsJob `
-JobName EDIDInfo -ComputerName $ComputerList
# Wait for the job to complete
Wait-Job -Job $Job
# Receive data from the job
$Data = Receive-Job $Job
# Once we have received the data, we can remove the job
Remove-Job -Job $Job
} Else {
# If a computer list was not specified, we don't need a job.
# We can get the data directly by running the scriptblock
Write-Warning ("A computer list was not specified. The script " `
+ "will run against the local computer.")
$Data = (& $ScriptBlock)
}
If ($LogPath){
# If a log path was specified (using the -LogPath parameter),
# export the data to it.
$Data | Export-Csv $LogPath -NoTypeInformation
}
# Display the collected data in the default host (usually the console)
$Data | Format-Table PSComputerName,VendorID,ModelName,DeviceID `
,SerialNumber,ManufactureDate,EDIDVersion
}
$ScriptBlock = {
# This script block will always run locally on the target
# $EDIDByteArrays will hold all of the collected EDID values from the
# registry.
$EDIDByteArrays = @()
# $KeysWithControl will contain the path to the key for each display
# device whose "Control" subkey has an ActiveService value--meaning
# that the device is currently being used.
$KeysWithControl = (
dir HKLM:\system\currentcontrolset\enum\display\*\*\control\ `
| ForEach-Object{
$_.PsParentPath
}
)
$KeysWithControl | ForEach-Object{
# Enumerate the "Device Parameters" subkey for each key
# identified above
dir "$($_)\device parameters*" | ForEach-Object {
# The "EDID" value of the "Device Parameters" key contains
# the EDID data (byte array)
$EDIDByteArray = $_.GetValue("EDID")
# Add the current value to $EDIDByteArrays
# Since the value we're adding is a byte array and don't want
# to append it to the current array, we need to add a new
# element first and then assign the whole value to the new element
$EDIDByteArrays += ""
$EDIDByteArrays[($EDIDByteArrays.GetUpperBound(0))] = $EDIDByteArray
}
}
# Now we will parse each EDID value. For information about the data structure,
# see https://en.wikipedia.org/wiki/Extended\_display\_identification\_data
# $Descriptors will contain the four prescribed descriptors
$Descriptors = @("","","","")
# A serial number descriptor starts with: 00 00 00 ff
[Object[]] $SerialNumberHeader = ([byte]0x00,[byte]0x00,[byte]0x00,[byte]0xFF)
# A model name descriptor starts with: 00 00 00 fc
[Object[]] $ModelNameHeader = ([byte]0x00,[byte]0x00,[byte]0x00,[byte]0xFC)
$EDIDByteArrays | Foreach-Object {
$EDIDValue = $_
# If this is a valid EDID value...
If($EDIDValue){
# Populate the four descriptors with each subset of bytes
$Descriptors[0] = $EDIDValue[54..71]
$Descriptors[1] = $EDIDValue[72..89]
$Descriptors[2] = $EDIDValue[90..107]
$Descriptors[3] = $EDIDValue[108..125]
# Initialize Values to "Not Found"
$SerialNumber = "Not Found"
$ModelName = "Not Found"
$ManufacturerID = "Not Found"
$DeviceID = "Not Found"
$ManufactureDate = "Not Found"
$EDIDVersion = "Not Found"
$Descriptors | ForEach-Object {
# Get the header of the current descriptor to determin if it
# is a model name or serial number. These are two of several
# types of optional descriptors, and they may be in any order
# If the header is a match, the value will be at offset 5
$DescriptorHeader = $_[0..3]
If(($DescriptorHeader -join " ") -eq ($SerialNumberHeader -join " ")){
$SerialNumber =
[System.Text.Encoding]::ASCII.GetString($_[5..17]).Trim()
}
If(($DescriptorHeader -join " ") -eq ($ModelNameHeader -join " ")){
$ModelName =
[System.Text.Encoding]::ASCII.GetString($_[5..17]).Trim()
}
}
# The manufacture date is stored as follows: Week is byte 16.
# Year (as offset from 1990) is byte 17.
$ManufactureWeek = $EDIDValue[16]
$ManufactureYear = 1990 + $EDIDValue[17]
# Format "MM/yyyy"
$ManufactureDate = (get-date "1/1/$($ManufactureYear)").AddDays(
7 * $ManufactureWeek).ToString("MM/yyyy")
# EDID version is stored in bytes 18 & 19
$EDIDMajorVersion = $EDIDValue[18].ToString()
$EDIDRevision = $EDIDValue[19].ToString()
$EDIDVersion = "$($EDIDMajorVersion).$($EDIDRevision)"
# The manufacturerID is a three character id stored in a two
# bytes (8 & 9)
$ManufacturerIDEncoded = $EDIDValue[8..9]
# The easiest way to work with it is to convert it to a
# concatenated binary string
$ManufacturerIDBinaryString = ($ManufacturerIDEncoded | ForEach-Object{
[System.Convert]::ToString($_,2).PadLeft(8,"0")
}) -join ""
# Parse out the three five-byte character codes (1=A) and add
# 64 to get the real character code
$ManufacturerFirstLetterCode =
[system.convert]::ToInt32($ManufacturerIDBinaryString.substring(1,5),2) + 64
$ManufacturerSecondLetterCode =
[system.convert]::ToInt32($ManufacturerIDBinaryString.substring(6,5),2) + 64
$ManufacturerThirdLetterCode =
[system.convert]::ToInt32($ManufacturerIDBinaryString.substring(11,5),2) + 64
# Concatenate the three character codes into a byte array
[byte[]] $ManufacturerIDByteArray =
(
$ManufacturerFirstLetterCode,
$ManufacturerSecondLetterCode,
$ManufacturerThirdLetterCode
)
# Convert the byte array to a string
$ManufacturerID =
[system.text.encoding]::Default.GetString($ManufacturerIDByteArray)
# The device id is stored in bytes 10 and 11 (16-bit little endian)
$DeviceID = ("{0:X}" -f $EDIDValue[11]).PadLeft(2,"0") `
+ ("{0:X}" -f $EDIDValue[10]).PadLeft(2,"0")
# Put all of the values into a new custom object on the pipeline
$Properties = @{
VendorID=$ManufacturerID;
DeviceID=$DeviceID;
ManufactureDate=$ManufactureDate;
SerialNumber=$SerialNumber;
ModelName=$ModelName;
EDIDVersion=$EDIDVersion
}
New-Object psobject -Property $Properties
}
}
# end script block
}
Main
Comments
- Anonymous
March 23, 2014
Thanks for sharing this. It's really helped me out converting a ConfigMgr custom inventory script from VBScript to PowerShell. - Anonymous
April 29, 2014
Seems like on Windows 8 / 8.1 the "Control" Key is no longer maintained in the Registry - Anonymous
May 15, 2014
Any news for Windows 8 / 8.1 about the "Control" key missing ?
What can we do to retreive the serial number ???