Merging two WMI Object tables

Hello,

Just wondering how I could merge the two tables in the following script:

Script 1: Get Dock information

$DockInfo = Get-WmiObject -Namespace “root\Lenovo\Dock_Manager” -Query “SELECT * FROM DockDevice” | Select-Object MachineType, MacAddress, SerialNumber, FWVersion, AvailableFWVersion

Script 2: Get USB controller device information

$Manufacturer = (Get-WmiObject win32_computersystem).Manufacturer
if ($Manufacturer -like “Lenovo”)
{
try
{
$USBDevices = Get-WmiObject Win32_USBControllerDevice -ErrorAction Stop | ForEach-Object { wmi } | Where-Object {
$.Name -like “Displaylink*” -or
$
.Name -like “ThinkPad USBdock” -or
$.DeviceID -like “usb\vid_17ef&pid_1010” -or
$
.DeviceID -like “usb\vid_17ef&pid_1012
} | Sort-Object Manufacturer, Description, DeviceID | Select-Object Manufacturer, Description, Service, DeviceID, Name

    Write-Host "USB Controller Device Information:"
    $USBDevices | Format-Table -AutoSize

    # Ensure both $DockInfo and $USBDevices are arrays
    if (-not $DockInfo) { $DockInfo = @() }
    if (-not $USBDevices) { $USBDevices = @() }

    # Merge Dock and USB device information
    $CombinedInfo = @()
    $CombinedInfo += $DockInfo
    $CombinedInfo += $USBDevices

    # Display the combined information in a table
    $CombinedInfo | Format-Table -AutoSize
}
catch
{
    Write-Host $Error[0]
}

}

The result of the script is

Thankyou in advance for the help.

Kohnman123,
Welcome to the forum. :wave:t3:

Some general tips at first:

When you post code, sample data, console output or error messages please format it as code using the preformatted text button ( </> ). Simply place your cursor on an empty line, click the button and paste your code.

Thanks in advance

How to format code in PowerShell.org 1 <---- Click :point_up_2:t4: :wink:

( !! Sometimes the preformatted text button hides behind the settings gear symbol. :wink: )

… and please do not post images of code, console oputput or sample data. Instead post the plain text and format it as code.

You should not use Get-WMIObject anymore. Instead use its successor Get-CimInstance!

To combine the properties of two or more different objects/queries you can use a [PSCustomObject].

The following snippet is pseudo code! It is not meant to work this way! It is to show how you combine the results of two (or more) queries into one object!

$DockInfo = Get-WmiObject -Namespace 'root\Lenovo\Dock_Manager' -Query 'SELECT * FROM DockDevice' 
$USBDevices = Get-WmiObject Win32_USBControllerDevice -ErrorAction Stop 

[PSCustomObject]@{
    MachineType  = $DockInfo.MachineType
    MACAddress   = $DockInfo.MACAddress
    Manufacturer = $USBDevices.Manufacturer
    Description  = $USBDevices.Description
    Name         = $USBDevices.Name
}
1 Like

Hi Olaf, thank you for the in-depth response and crash course. I had a few issues with your response with the values coming out as “null”. I managed to find a workaround. I’m trying to merge this with a third script to get monitor screen information as well. The whole script is to get Dock information and Monitor Screen Information. I’m having trouble in joinging to arrays together from the variable $DockInformation and $Monitor_Obj do ou have any suggestions for this? The script output is producing what it is meant to it’s just the format really.

<#
.SYNOPSIS
This PowerShell function gets information about the monitors attached to any computer. It uses EDID information provided by WMI. If this value is not specified, it pulls the monitors of the computer that the script is being run on.

.DESCRIPTION
The function begins by looping through each computer specified. For each computer, it gets a list of monitors.
It then gets all of the necessary data from each monitor object, converts and cleans the data, and places it in a custom PSObject. It then adds the data to an array. At the end, the array is displayed.

.PARAMETER ComputerName
Use this to specify the computer(s) from which you'd like to retrieve information about monitors.

.EXAMPLE
PS C:/> Get-Monitor.ps1 -ComputerName SSL1-F1102-1G2Z

Manufacturer Model    SerialNumber AttachedComputer
------------ -----    ------------ ----------------
HP           HP E241i CN12345678   SSL1-F1102-1G2Z 
HP           HP E241i CN91234567   SSL1-F1102-1G2Z 
HP           HP E241i CN89123456   SSL1-F1102-1G2Z

.EXAMPLE
PS C:/> $Computers = @("SSL7-F108F-9D4Z","SSL1-F1102-1G2Z","SSA7-F1071-0T7F")
PS C:/> Get-Monitor.ps1 -ComputerName $Computers

Manufacturer Model      SerialNumber AttachedComputer
------------ -----      ------------ ----------------
HP           HP LA2405x CN12345678   SSL7-F108F-9D4Z
HP           HP E241i   CN91234567   SSL1-F1102-1G2Z 
HP           HP E241i   CN89123456   SSL1-F1102-1G2Z 
HP           HP E241i   CN78912345   SSL1-F1102-1G2Z
HP           HP ZR22w   CN67891234   SSA7-F1071-0T7F
#>


[CmdletBinding()]
PARAM (
    [Parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
    [String[]]$ComputerName = $env:ComputerName
)

# List of Manufacture Codes that could be pulled from WMI and their respective full names. Used for translating later down.
$ManufacturerHash = @{ 
    "AAC" =	"AcerView";
    "ACR" = "Acer";
    "AOC" = "AOC";
    "AIC" = "AG Neovo";
    "APP" = "Apple Computer";
    "AST" = "AST Research";
    "AUO" = "Asus";
    "BNQ" = "BenQ";
    "CMO" = "Acer";
    "CPL" = "Compal";
    "CPQ" = "Compaq";
    "CPT" = "Chunghwa Pciture Tubes, Ltd.";
    "CTX" = "CTX";
    "DEC" = "DEC";
    "DEL" = "Dell";
    "DPC" = "Delta";
    "DWE" = "Daewoo";
    "EIZ" = "EIZO";
    "ELS" = "ELSA";
    "ENC" = "EIZO";
    "EPI" = "Envision";
    "FCM" = "Funai";
    "FUJ" = "Fujitsu";
    "FUS" = "Fujitsu-Siemens";
    "GSM" = "LG Electronics";
    "GWY" = "Gateway 2000";
    "HEI" = "Hyundai";
    "HIT" = "Hyundai";
    "HSL" = "Hansol";
    "HTC" = "Hitachi/Nissei";
    "HWP" = "HP";
    "IBM" = "IBM";
    "ICL" = "Fujitsu ICL";
    "IVM" = "Iiyama";
    "KDS" = "Korea Data Systems";
    "LEN" = "Lenovo";
    "LGD" = "Asus";
    "LPL" = "Fujitsu";
    "MAX" = "Belinea"; 
    "MEI" = "Panasonic";
    "MEL" = "Mitsubishi Electronics";
    "MS_" = "Panasonic";
    "NAN" = "Nanao";
    "NEC" = "NEC";
    "NOK" = "Nokia Data";
    "NVD" = "Fujitsu";
    "OPT" = "Optoma";
    "PHL" = "Philips";
    "REL" = "Relisys";
    "SAN" = "Samsung";
    "SAM" = "Samsung";
    "SBI" = "Smarttech";
    "SGI" = "SGI";
    "SNY" = "Sony";
    "SRC" = "Shamrock";
    "SUN" = "Sun Microsystems";
    "SEC" = "Hewlett-Packard";
    "TAT" = "Tatung";
    "TOS" = "Toshiba";
    "TSB" = "Toshiba";
    "VSC" = "ViewSonic";
    "ZCM" = "Zenith";
    "UNK" = "Unknown";
    "_YV" = "Fujitsu";
}

# Takes each computer specified and runs the following code:
ForEach ($Computer in $ComputerName) {
  
    # Grabs the Monitor objects from WMI
    $Monitors = Get-WmiObject -Namespace "root\WMI" -Class "WMIMonitorID" -ComputerName $Computer -ErrorAction SilentlyContinue

    #Grabs the Dock objects from WMI
    $DockInfo = Get-CimInstance -Namespace 'root\Lenovo\Dock_Manager' -Query 'SELECT * FROM DockDevice'
    $DeviceNames = Get-PnpDevice -ErrorAction Stop | Where-Object { ($_.name -like "Displaylink*" -or $_.name -like "ThinkPad USB*dock*" -or $_.DeviceID -like "*usb\vid_17ef&pid_1010*" -or $_.DeviceID -like "*usb\vid_17ef&pid_1012*") -and $_.Status -like "OK" }

    
    # Creates an empty array to hold the data
    $Monitor_Array = @()
    
    # Takes each monitor object found and runs the following code:
    ForEach ($Monitor in $Monitors) {
      
        # Grabs respective data and converts it from ASCII encoding and removes any trailing ASCII null values
        If ([System.Text.Encoding]::ASCII.GetString($Monitor.UserFriendlyName) -ne $null) {
            $Mon_Model = ([System.Text.Encoding]::ASCII.GetString($Monitor.UserFriendlyName)).Replace("$([char]0x0000)","")
        } else {
            $Mon_Model = $null
        }
        $Mon_Serial_Number = ([System.Text.Encoding]::ASCII.GetString($Monitor.SerialNumberID)).Replace("$([char]0x0000)","")
        $Mon_Attached_Computer = ($Monitor.PSComputerName).Replace("$([char]0x0000)","")
        $Mon_Manufacturer = ([System.Text.Encoding]::ASCII.GetString($Monitor.ManufacturerName)).Replace("$([char]0x0000)","")
      
        # Filters out "non-monitors". Place any of your own filters here. These two are all-in-one computers with built-in displays. I don't need the info from these.
        If ($Mon_Model -like "*800 AIO*" -or $Mon_Model -like "*8300 AiO*") { Break }
      
        # Sets a friendly name based on the hash table above. If no entry found, sets it to the original 3 character code
        $Mon_Manufacturer_Friendly = $ManufacturerHash.$Mon_Manufacturer
        If ($Mon_Manufacturer_Friendly -eq $null) {
            $Mon_Manufacturer_Friendly = $Mon_Manufacturer
        }


      
        # Creates a custom monitor object and fills it with 4 NoteProperty members and the respective data
        $Monitor_Obj = [PSCustomObject]@{
            Manufacturer     = $Mon_Manufacturer_Friendly
            Model            = $Mon_Model
            SerialNumber     = $Mon_Serial_Number
            AttachedComputer = $Mon_Attached_Computer
        }

        #Creates a dock object 
  $DockInformation = [PSCustomObject]@{
DockName = $DeviceNames.name
DockMachineType = $DockInfo.MachineType
DockDockId = $Dockinfo.DockId
DockMacAddress = $DockInfo.MacAddress
DockSerialNumber = $DockInfo.SerialNumber
AttachedComputer = $Mon_Attached_Computer}


       # Appends the monitor object and dock information object to the array
        $Monitor_Array += $Monitor_Obj

    } # End ForEach Monitor
  
    # Outputs the Array
    $DockInformation
    $Monitor_Array

   
    
} # End ForEach Computer

Any suggestions would be greatly appreciated.

My first suggestion, but unrelated to your actual question, would be to change how you’re creating your array of monitor objects. When you use += to ‘add’ to an array what Powershell is actually doing in the background is destroying the array and recreating it with the original contents plus the new contents.
On a small scale this isn’t that much of a performance hit, but on a larger scale it can really slow things down, so it’s best to be aware of it now and start approaching this differently.
Instead of

# creating an empty array
$Monitor_Array = @()
# and adding stuff to it
$Monitor_Array += "Stuff"
$Monitor_Array += "Things"

Since your Foreach loop is only outputting the Monitor objects you’re creating, start your Foreach loop like this instead:

$Monitor_Array = ForEach ($Monitor in $Monitors) {
<#...#>
}

Now anything that it output during the loop is captured in the $Monitor_Array variable which automatically becomes an array as soon as multiple objects are received.

Now, your question:

An array is not constrained to only holding one type of object. Observe:

$Array = 1,2,3,"Stuff","Things"
# the output of it would look like this
PS > $Array
1
2
3
Stuff
Things

This is now an array that’s holding integers and strings. You can do the same thing with objects that have more properties.

$Array = @()
$Array += [PSCustomObject] @{
    Make    = "Toyota"
    Model   = "Land Cruiser"
    Color   = "Silver"
    Year    = "2024"
    Engine  = "Hybrid"
}
$Array += [PSCustomObject]@{
    FoodCategory    = "Breakfast"
    Item            = "Eggs"
    Drink           = "Coffee"
}

But the output of this would look like this:

PS > $Array


Make   : Toyota
Model  : Land Cruiser
Color  : Silver
Year   : 2024
Engine : Hybrid

FoodCategory : Breakfast
Item         : Eggs
Drink        : Coffee



This may not be what you want, and ultimately you shouldn’t do this. You should strive to only put like-objects in arrays with each other.
Right now your $Monitor_Obj only have 4 properties which means Powershell is automatically formatting them as a table which probably looks really nice in the output. 5 or more properties means Powershell would format it as a list (vertically), an Array containing even one object with 5 or more properties will make Powershell format the entire array as a list instead of a table.

Your dock object has 6 properties so the above scenario would apply to it. The question then is: what is your end goal? Do you want table output in the console?

Also consider that when making Powershell tools you should really only have your code produce one type of object. Right now you’re producing an object with dock information and objects with monitor information. If I wanted to do something with those objects after they are output from the code I’m kind of in a bind because they’re not like objects. I can’t easily pipe the output to something else (even just Export-Csv). If you really need the dock information on there as well you might consider making your objects contain ALL of the information you collect:

$Monitor_Obj = [PSCustomObject]@{
    AttachedComputer = $Mon_Attached_Computer
    Manufacturer     = $Mon_Manufacturer_Friendly
    Model            = $Mon_Model
    SerialNumber     = $Mon_Serial_Number
    DockName = $DeviceNames.name
    DockMachineType = $DockInfo.MachineType
    DockDockId = $Dockinfo.DockId
    DockMacAddress = $DockInfo.MacAddress
    DockSerialNumber = $DockInfo.SerialNumber
}

It wouldn’t automatically format as a table, but then you would have uniform objects to deal with and you could capture the output of your code in a variable, or pipe it, and do whatever you want with it.