Get PCI express link width / specification version Programmatically

Many people include me find how to get PCI express link width / specification version programmatically. But there’re too many useless articles. Like examples about NetworkAdapters(I applied “-NetworkAdapter” and 90% of search results are gone!). And some of the remaining results say it must be solved with drivers. Others say just give up windows and use linux(?).
Unfortunately now I don’t have any reference link, because of the catastrophic failure of my RAID1 storage.

1. WMI

Cannot do this. One of the answer from programdoc says, even WMI specialist can’t do this.
I found a crude hack to do this. Please aware it’s a hack. I don’t guarantee anything about next queries.

Firstly get DeviceID Property of drive.
ASSOCIATORS OF {Win32_DiskDrive.DeviceID=’\\.\PhysicalDrive(Number)’} WHERE ResultClass=Win32_PnPEntity
Then get DeviceID Property of controller.
ASSOCIATORS OF {Win32_PnPEntity.DeviceID='(DeviceID from above query result)’} WHERE AssocClass=Win32_SCSIControllerDevice
Get bus number(BusNum Property) of the controller.
ASSOCIATORS OF {Win32_PnPEntity.DeviceID=‘(DeviceID from above query result)’} WHERE AssocClass=Win32_DeviceBus
Lastly Find the bus number(BusNumber Property) in Win32_SystemSlot classes.
SELECT * FROM Win32_SystemSlot WHERE BusNumber='(BusNum from above query result)’

In this way, you can find link width with MaxDataWidth property. Since it is gathered from SMBIOS, +3 from its value and you can read the value in SMBIOS way.
But in this way you can’t get the specification version. Though SMBIOS specification has seperated value for 1.0, 2.0, 3.0 slot, mainboards like mine(B75M) only shows 1.0 value.
In short, the problem is that, the relationship between bus and slot is hard to find.

2. Driver

Read the PCI Express register. I didn’t tried this way. It needs too much efforts to do this small thing.

3. WMI + SetupAPI

Here comes the ultimate solution.
Firstly execute to the second query at ‘1. WMI’ section. Then you’ll get DeviceID of the controller.
Then open the class with SetupDiGetClassDevs function. You can find class GUID at Device Manager → Select Device and Open Properties → Details → Class GUID.
To find the device with the same DeviceID, iterate devices with SetupDiEnumDeviceInfo and get DeviceID with CM_Get_Device_IDW.
If you find the device, use SetupDiGetDeviceProperty with next keys.

DEVPKEY_PciDevice_CurrentLinkSpeed (pid: 9)
DEVPKEY_PciDevice_CurrentLinkWidth (pid: 10)
DEVPKEY_PciDevice_MaxLinkSpeed (pid: 11)
DEVPKEY_PciDevice_MaxLinkWidth (pid: 12)

GUID for them is {3ab22e31-8264-4b4e-9af5-a8d2d8e33e62}.
Max- means maximum values that device supports, Current- means negotiated values.
-LinkSpeed represents specification version, -LinkWidth represents number of lanes. Be aware of LinkWidth value is the raw value so don’t try to read this in SMBIOS way.