1

Using Powershell I retrieve all third party drivers installed using:

$PNPDrivers = PNPUtil /Enum-Drivers

The format of the information retrieved are as followed:

Microsoft PnP Utility

Published Name:     oem19.inf
Original Name:      apollolakesystem.inf
Provider Name:      INTEL
Class Name:         Systemenheter
Class GUID:         {4d36e97d-e325-11ce-bfc1-08002be10318}
Driver Version:     07/18/1968 10.1.17.1
Signer Name:        Microsoft Windows Hardware Compatibility Publisher

Published Name:     oem20.inf
Original Name:      avotonsystem.inf
Provider Name:      INTEL
Class Name:         Systemenheter
Class GUID:         {4d36e97d-e325-11ce-bfc1-08002be10318}
Driver Version:     07/18/1968 10.1.3.1
Signer Name:        Microsoft Windows Hardware Compatibility Publisher

I'm trying to make a foreach loop so that each driver are added to an array with PSObject so that I can fetch required information later on in the script.

I have tried so many combinations but never succeeded. Right now I have tried a regex expression and group each row in order to specify each value but I don't get any data what so ever.

Below is the Regex expression I have used which seems to work?

enter image description here

After that I've tried to follow the information from this link: Parsing Text with PowerShell Just changing the information to match my code:

$Pattern = "
^(?<PublishedName>Published Name:.*)
^(?<OriginalName>Original Name:.*)
^(?<ProviderName>Provider Name:.*)
^(?<ClassName>Class Name:.*)
^(?<ClassGUID>Class GUID:.*)
^(?<DriverVersion>Driver Version:.*)
^(?<SignerName>Signer Name:.*)
"

$PNPDrivers |
    Select-String -Pattern $Pattern |
    Foreach-Object {
        # here we access the groups by name instead of by index
        $PublishedName, $OriginalName, $ProviderName, $ClassName, $ClassGUID, $DriverVersion, $SignerName = $_.Matches[0].Groups['PublishedName', 'OriginalName', 'ProviderName', 'ClassName', 'ClassGUID', 'DriverVersion', 'SignerName'].Value
        [PSCustomObject] @{
            PublishedName = $PublishedName
            OriginalName = $OriginalName
            ProviderName = $ProviderName
            ClassName = $ClassName
            ClassGUID = $ClassGUID
            DriverVersion = $DriverVersion
            SignerName = $SignerName
        }
    }

But no data is printed out so I must be missing some but I don't manage to find what. How can I do to retrieve the data and for each driver use it?

Second try

The code below gives me all values (I think) but it splits it to one row each. I guess this is because it doesn't match all rexeg:s at the same time. Can this be changed?

enter image description here

$Pattern = "(^(?<PublishedName>^Published Name:\s*([^\n\r]*)))|(^(?<OriginalName>^Original Name:\s*([^\n\r]*)))|(^(?<ProviderName>^Provider Name:\s*([^\n\r]*)))|(^(?<ClassName>^Class Name:\s*([^\n\r]*)))|(^(?<ClassGUID>^Class GUID:\s*([^\n\r]*)))|(^(?<DriverVersion>^Driver Version:\s*([^\n\r]*)))|(^(?<SignerName>^Signer Name:\s*([^\n\r]*)))"

$PNPDrivers |
    Select-String -Pattern $Pattern -AllMatch |
    Foreach-Object {
        # here we access the groups by name instead of by index
        $PublishedName, $OriginalName, $ProviderName, $ClassName, $ClassGUID, $DriverVersion, $SignerName = $_.Matches[0].Groups['PublishedName', 'OriginalName', 'ProviderName', 'ClassName', 'ClassGUID', 'DriverVersion', 'SignerName'].Value
        [PSCustomObject] @{
            PublishedName = $PublishedName -replace "Published Name:     ",""
            OriginalName = $OriginalName -replace "Original Name:      ",""
            ProviderName = $ProviderName -replace "Provider Name:      ",""
            ClassName = $ClassName -replace "Class Name:         ",""
            ClassGUID = $ClassGUID -replace "Class GUID:         ",""
            DriverVersion = $DriverVersion -replace "Driver Version:     ",""
            SignerName = $SignerName -replace "Signer Name:        ",""
        }
    }

Third try

Almost there! So close! You are the man !

Since I am going to loop through a package of drivers in our SCCM enviroment working with [PSCustomObject] seems to be the right way to do it.

Although the problem that persists seems to be that some rows get "out of sync" and displays wrong value for the column.

Value out of sync

Any thoughts on how to correct that?

Fourth try

Working code!!

$List = New-Object System.Collections.ArrayList
((PNPUtil /Enum-Drivers | 
Select-Object -Skip 2) | 
Select-String -Pattern 'Published Name:' -Context 0,7) | 
ForEach {
if($PSItem.Context.PostContext[4] -like "*Class Version:*"){
$ClassVersion = $PSItem.Context.PostContext[4] -replace '.*:\s+'
$DriverVersion = $PSItem.Context.PostContext[5] -replace '.*:\s+'
$SignerName = $PSItem.Context.PostContext[6] -replace '.*:\s+'
}else{
$ClassVersion = "N/A"
$DriverVersion = $PSItem.Context.PostContext[4] -replace '.*:\s+'
$SignerName = $PSItem.Context.PostContext[5] -replace '.*:\s+'
}
    $y = New-Object PSCustomObject
        $y | Add-Member -Membertype NoteProperty -Name PublishedName -value (($PSitem | Select-String -Pattern 'Published Name:' ) -replace '.*:\s+')
        $y | Add-Member -Membertype NoteProperty -Name OriginalName -value (($PSItem.Context.PostContext[0]) -replace '.*:\s+')
        $y | Add-Member -Membertype NoteProperty -Name ProviderName -value (($PSItem.Context.PostContext[1]) -replace '.*:\s+')
        $y | Add-Member -Membertype NoteProperty -Name ClassName -value (($PSItem.Context.PostContext[2]) -replace '.*:\s+')
        $y | Add-Member -Membertype NoteProperty -Name ClassGUID -value (($PSItem.Context.PostContext[3]) -replace '.*:\s+')
        $y | Add-Member -Membertype NoteProperty -Name ClassVersion -value $ClassVersion
        $y | Add-Member -Membertype NoteProperty -Name DriverVersion -value $DriverVersion
        $y | Add-Member -Membertype NoteProperty -Name SignerName -value $SignerName
        $z = $List.Add($y)
}

1 Answers1

2

Let's say you only what monitors then you can just do this, without using a PSCustomObject at all.

Clear-Host
(PNPUtil /Enum-Drivers /class Display | 
Select-Object -Skip 2) | 
Select-String -Pattern 'Monitors' -Context 3,4
# Results
<#
    Published Name:     oem...
    Original Name:      tp...
    Provider Name:      L...
> Class Name:         Monitors
    Class GUID:         {4d...
    Driver Version:     11...
    Signer Name:        Micr...

    Published Name:     oe...
    Original Name:      tp...
    Provider Name:      L...
> Class Name:         Monitors
    Class GUID:         {4d36...
    Driver Version:     06...
    Signer Name:        Micros...
#>

# The cherry-pick as needed by index, string, string value, etc..
Clear-Host
((PNPUtil /Enum-Drivers /class Display | 
Select-Object -Skip 2) | 
Select-String -Pattern 'Monitors' -Context 3,4)[0]
# Results
<#
  Published Name:     oe...
  Original Name:      tpl...
  Provider Name:      L...
> Class Name:         Monitors
  Class GUID:         {4d36e...
  Driver Version:     11/...
  Signer Name:        Microso...
#>

Clear-Host 
(PNPUtil /Enum-Drivers /class Display | 
Select-Object -Skip 2 | 
Select-String -Pattern 'Monitors' -Context 3,4 | 
ForEach-Object {$PSItem.Context.PreContext[0]})
# Results
<#
Published Name:     oem...
Published Name:     oem...
#>

Clear-Host 
(PNPUtil /Enum-Drivers /class Display | 
Select-Object -Skip 2 | 
Select-String -Pattern 'Monitors' -Context 3,4 | 
ForEach-Object {$PSItem.Context.PreContext[0] -replace '.*:\s+'})
# Results
<#
oem...
oem...
#>

The above response is from a similar to this one that I and others have already responded to.

Parse pnputil output to published name for specific class

Are you guys in the same course/class, or in the same company doing the same thing to see who can get there first? ;-}

If You want to get just one driver's info at a time, just change that filter string and cherry-pick the line you are after.

Yet, if you really, really, want to use [PSCustomObject], then you can still use the above and refactor to this.

Clear-Host
((PNPUtil /Enum-Drivers /class Display | 
Select-Object -Skip 2) | 
Select-String -Pattern 'Class Name:' -Context 3,4) | 
ForEach {
    [PSCustomObject]@{
        PublishedName = $PSItem.Context.PreContext[0] -replace '.*:\s+'
        OriginalName  = $PSItem.Context.PreContext[1] -replace '.*:\s+'
        ProviderName  = $PSItem.Context.PreContext[2] -replace '.*:\s+'
        ClassName     = ($PSitem | Select-String -Pattern 'Class Name:') -replace '.*:\s+'
        ClassGUID     = $PSItem.Context.PostContext[0] -replace '.*:\s+'
        DriverVersion = $PSItem.Context.PostContext[1] -replace '.*:\s+'
        SignerName    = $PSItem.Context.PostContext[2] -replace '.*:\s+'   
    }
}
# Results
<#
PublishedName : oe...
OriginalName  : am...
ProviderName  : A...
ClassName     : Syst...
ClassGUID     : {4d36...
DriverVersion : 02/...
SignerName    : Microso...
...
PublishedName : oem...
OriginalName  : w...
ProviderName  : W...
ClassName     : W...
ClassGUID     : {849...
DriverVersion : 11/...
SignerName    : Microsof...
#>

Or table format:

Clear-Host
((PNPUtil /Enum-Drivers /class Display | 
Select-Object -Skip 2) | 
Select-String -Pattern 'Class Name:' -Context 3,4) | 
ForEach {
    [PSCustomObject]@{
      PublishedName = $PSItem.Context.PreContext[0] -replace '.*:\s+'
      OriginalName  = $PSItem.Context.PreContext[1] -replace '.*:\s+'
      ProviderName  = $PSItem.Context.PreContext[2] -replace '.*:\s+'
      ClassName     = ($PSitem | Select-String -Pattern 'Class Name:') -replace '.*:\s+'
      ClassGUID     = $PSItem.Context.PostContext[0] -replace '.*:\s+'
      DriverVersion = $PSItem.Context.PostContext[1] -replace '.*:\s+'
      SignerName    = $PSItem.Context.PostContext[2] -replace '.*:\s+'   
    }
} | 
Format-Table -AutoSize
# Results
<#
PublishedName OriginalName   ProviderName                   ClassName                         ClassGUID                              DriverVersion         SignerName                        
------------- ------------   ------------                   ---------                         ---------                              -------------         ----------                        
...
#>

Update as per your comment...

sometimes, some drivers apparently also displays a Class Version just below Class GUID. When it does that row gets out of sync. Is it possible to, I don't know, using a IF-statement that if PostContext[3] exists change the order for the PostContext?

... and my response to it.

I played with this use case a bit more can refactored my code to address these potential custom/dynamic fields that can happen per driver.

On my system, I had two customer driver properties:

Extension ID and Class Version.

Context-wise, they in the same place, so, I addressed them that way. So, the code pulls the data and uses a custom field to hold the value of either or none. So, this means you need to first discover any such items, and alter the code as needed. Note: I used a Switch block to handle that and IF/then to finalize based on the fields needed in the case.

Clear-Host
# Parse/format PnpUtil output based on JSON details, customize for dynamic fields
$PNPDrivers            = PNPUtil /Enum-Drivers
$StringDataRegEx       = '.*:\s+'
$RemoveClassNamesRegEx = 'Extension ID|Class Version'

$CSvHeaders = @(
    'PublishedName', 
    'OriginalName', 
    'ProviderName', 
    'ClassName', 
    'ClassGUID', 
    'ClassVersion_or_ExtensionGUID', 
    'DriverVersion', 
    'SignerName' 
)

$ClassVersionElements = @(
    'Extension ID', 
    'Class Version', 
    'Signer Name'
)

$ClassVersionElements |
ForEach {
    $ElementString = $PSItem
    $ContextID     = If ($ElementString -match $RemoveClassNamesRegEx)
                    {5} 
                    Else {6}

    $PNPDrivers | 
    Select-String -Pattern $ElementString -Context $ContextID | 
    ForEach {
                $DriverData = $PSItem
                switch ($ElementString)
                {
                    'Extension ID' 
                    {
                        [PSCustomObject]@{
                            PublishedName = $DriverData.Context.PreContext[0] -replace $StringDataRegEx
                            OriginalName  = $DriverData.Context.PreContext[1] -replace $StringDataRegEx
                            ProviderName  = $DriverData.Context.PreContext[2] -replace $StringDataRegEx
                            ClassName     = $DriverData.Context.PreContext[3] -replace $StringDataRegEx
                            ClassGUID     = $DriverData.Context.PreContext[4] -replace $StringDataRegEx
                            ClassVersion_or_ExtensionID  = (
                                                             $DriverData | 
                                                             Select-String -Pattern $ElementString
                                                           ) -replace $StringDataRegEx 
                            DriverVersion = $DriverData.Context.PostContext[0] -replace $StringDataRegEx
                            SignerName    = $DriverData.Context.PostContext[1] -replace $StringDataRegEx
                        }            
                    }
                    'Class Version' 
                    {
                        [PSCustomObject]@{
                            PublishedName = $DriverData.Context.PreContext[0] -replace $StringDataRegEx
                            OriginalName  = $DriverData.Context.PreContext[1] -replace $StringDataRegEx
                            ProviderName  = $DriverData.Context.PreContext[2] -replace $StringDataRegEx
                            ClassName     = $DriverData.Context.PreContext[3] -replace $StringDataRegEx 
                            ClassGUID     = $DriverData.Context.PreContext[4] -replace $StringDataRegEx
                            ClassVersion_or_ExtensionID  = (
                                                             $DriverData | 
                                                             Select-String -Pattern $ElementString
                                                           ) -replace $StringDataRegEx 
                            DriverVersion = $DriverData.Context.PostContext[0] -replace $StringDataRegEx
                            SignerName    = $DriverData.Context.PostContext[1] -replace $StringDataRegEx
                        }                      
                    }
                    'Signer Name' 
                    {
                        If ($DriverData -NotMatch $RemoveClassNamesRegEx)
                        {
                            [PSCustomObject]@{
                                PublishedName = $DriverData.Context.PreContext[0] -replace $StringDataRegEx
                                OriginalName  = $DriverData.Context.PreContext[1] -replace $StringDataRegEx
                                ProviderName  = $DriverData.Context.PreContext[2] -replace $StringDataRegEx
                                ClassName     = $DriverData.Context.PreContext[3] -replace $StringDataRegEx
                                ClassGUID     = $DriverData.Context.PreContext[4] -replace $StringDataRegEx
                                ClassVersion_or_ExtensionID  = 'Property not valid for this driver' 
                                DriverVersion = $DriverData.Context.PreContext[5] -replace $StringDataRegEx
                                SignerName    = (
                                                    $DriverData | 
                                                    Select-String -Pattern $ElementString
                                                ) -replace $StringDataRegEx   
                            }
                        }                  
                    }

              }
        }
} | 
Format-Table -AutoSize
postanote
  • 15,138
  • 2
  • 14
  • 25
  • So close, some rows get out of synd though. Any ideas on how to prevent that? See my **Third try** picture that shows it. – Markus Sacramento Mar 12 '21 at 09:49
  • I don't have access to SCCM, well, really did not use it much in my past either. So, no way to repro what you may be seeing. So, I am not sure what you mean by `some rows get out of synd though`. The format-table is just a view vs list. The wider the string the wide the table and truncation happen. Format-Tale has a `-Wrap` switch, but really once you get beyond 5 properties, it's really better to stick with the default list. Especially if you never know how long a string will be. You can just pull down a raw list, using only the non-formatting code segment, see what is coming down. – postanote Mar 12 '21 at 10:10
  • I ment get of of sync ;) I found out that sometimes, some drivers apparently also displays a Class Version just below Class GUID. When it does that row gets out of sync. Is it possible to, I don't know, using a IF-statement that if PostContext[3] exists change the order for the PostContext? – Markus Sacramento Mar 12 '21 at 10:15
  • As for. `I ment get of of sync ;) `, no worries, I kind'a figured that. ;-}, it was the later that I was not getting. Yes, in the MOF for Windows objects there can be more fields/properties. Sou, yep, you have to use conditional logic to try and deal with that. Yet, the real issue, is that need to expand or shrink could vary and you'd have to code for all of them. Hence my reason, for not suggesting using `[PSCustomObject]`, and just pull the raw data then cherry-pick which drivers you want to work with and move forward from there. You could just use a placeholder as well in all of them,. – postanote Mar 12 '21 at 19:20
  • @Markus Sacramento, I could not let this go, and I to dig a bit more to get you a result to deal with `some drivers apparently also displays a Class Version just below Class GUID.` The deal though is that, depending on where you do this, your driver listing can have more than one of these driver-specific fields. On my system I had two. See my uptdate for you to see what I mean, and the final code to deal with them. – postanote Mar 15 '21 at 07:43