Search code examples
c#.netpowershelldriverwmi

How to determine if a installed driver it's a third-party driver in .NET?


In C# or VB.NET, I would like to retrieve a list of third-party installed drivers. Only the third-party drivers.

I found these WMI classes from which in combination I think I could retrieve all the installed drivers and useful information including the directory path: Win32_SystemDriver, Win32_PnPSignedDriver, Win32_PnPEntity, Win32_PnPSignedDriverCIMDataFile

But I can't figure how to determine if a driver its a third-party driver or it is not.

My question is: being able to identify a installed driver by for example its ClassGuid property from Win32_PnPSignedDriver class, how can I determine whether that specific driver it's a third-party driver or it is not?.

By third-party drivers I mean drivers that are not built into the Windows system image (Install.wim). It is any driver that you download and install after you have installed Windows.

For example all these are my third-party installed drivers:

enter image description here


I found that the PowerShell's Get-WindowsDriver cmdlet serves to list all the installed drivers (driver name, type, version, path, etc) and it has the -All parameter:

-All

Displays information about default drivers. If you do not specify this parameter, only third-party drivers and listed.

If the -All parameter is not specified, Get-WindowsDriver it only lists the third-party installed drivers.

In the same way, I found these programs that will export/backup only the third-party installed drivers:

dism.exe /online /export-driver /destination:"full path of folder"

pnputil.exe /export-driver * "full path of folder"

I also found this C# code example with P/Invokes to the setupapi.dll, but I'm not sure if this API provides a function to determine if a driver it's third-party.


The simplest solution for me it would be to run and parse the Get-WindowsDriver cmdlet output, but I really would like to dig into WMI (or the exported functions from setupapi.dll) to be able mimic the same output as Get-WindowsDriver, and for doing that I need to understand how can I determine when a installed driver it's a third-party driver and when it is not.


Solution

  • Since I have not been able to obtain a solution using WMI or Windows API, I have turned to integrated Powershell.

    Required packages:

    enter image description here

    NOTE: I think that at least .net 5.0 (version 7.1.7 of the related PowerShell Nuget packages) is required to run this. I tried it in .NET Framework 4.8 with Microsoft.PowerShell.5.ReferenceAssemblies package and it was unable to find the Get-WindowsDriver cmdlet.

    Also take into account that this was my very first time using these PowerShell automation APIs. Maybe the related PowerShell Automation code is not properly optimized.


    First of all these two enums and a class that will serve to represent the information of a driver:

    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    Imports System.IO
    Imports System.Management.Automation
    Imports System.Management.Automation.Runspaces
    Imports System.Text
    Imports Microsoft.PowerShell
    
    ''' <summary>
    ''' Specifies the behavior to get a driver when calling the <see cref="GetSystemDrivers()"/> function. 
    ''' </summary>
    <Flags>
    Public Enum GetDriverFlags As Short
    
        ''' <summary>
        ''' Get those drivers that are commonly known as default drivers. 
        ''' <para></para>
        ''' These drivers are included on the Windows distribution media 
        ''' and therefore are automatically installed as part of Windows.
        ''' <para></para>
        ''' This is the opposite behavior to <see cref="GetDriverFlags.NotInbox"/> flag. 
        ''' </summary>
        Inbox = 1 << 0
    
        ''' <summary>
        ''' Get those drivers that are commonly known as third-party drivers. 
        ''' <para></para>
        ''' These drivers are not included on the Windows distribution media 
        ''' and therefore are not automatically installed as part of Windows.
        ''' <para></para>
        ''' This is the opposite behavior to <see cref="GetDriverFlags.Inbox"/> flag. 
        ''' </summary>
        NotInbox = 1 << 1
    
        ''' <summary>
        ''' Get all the drivers (inbox and not-inbox drivers).
        ''' </summary>
        Any = GetDriverFlags.Inbox Or GetDriverFlags.NotInbox
    
    End Enum
    
    ''' <summary>
    ''' Specifies the signature status of a driver. 
    ''' <para></para>
    ''' This enum is used in <see cref="DismDriverInfo.Signature"/> property.
    ''' </summary>
    Public Enum DismDriverSignature As Integer
    
        ''' <summary>
        ''' The signature status of the driver is unknown. 
        ''' <para></para>
        ''' DISM only checks for a valid signature for boot-critical drivers.
        ''' </summary>
        Unknown = 0
    
        ''' <summary>
        ''' The driver is unsigned.
        ''' </summary>
        Unsigned = 1
    
        ''' <summary>
        ''' The driver is signed.
        ''' </summary>
        Signed = 2
    
    End Enum
    
    ''' <summary>
    ''' Represents basic information for a installed system driver.
    ''' </summary>
    <Serializable>
    Public Class DismDriverInfo
    
    #Region " Notes and research informartion "
    
        ' - DismDriverPackage structure:
        '   https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/dism/dismdriverpackage-structure
    
        ' - Useless DISM object properties that were not implemented in this class:
        '
        '   Path, WinPath, SysDrivePath, ScratchDirectory, LogPath, LogLevel,
        '   MajorVersion, MinorVersion, Build, Revision
        '   (those can be accessed via the "DismDriverInfo.Version" property)
    
    #End Region
    
    #Region " Properties "
    
        ''' <summary>
        ''' Gets the provider of the driver.
        ''' </summary>
        Public ReadOnly Property Provider As String
    
        ''' <summary>
        ''' Gets the class name of the driver.
        ''' </summary>
        Public ReadOnly Property ClassName As String
    
        ''' <summary>
        ''' Gets the description of the driver class.
        ''' <para></para>
        ''' Note: the description is spelled using the current system language.
        ''' </summary>
        Public ReadOnly Property ClassDescription As String
    
        ''' <summary>
        ''' Gets the class global unique identifier (GUID) of the driver.
        ''' </summary>
        Public ReadOnly Property ClassGuid As Guid
    
        ''' <summary>
        ''' Gets the .inf file name of the driver.
        ''' </summary>
        Public ReadOnly Property DriverFile As String
    
        ''' <summary>
        ''' Gets the driver version.
        ''' </summary>
        Public ReadOnly Property Version As Version
    
        ''' <summary>
        ''' Gets the manufacturer's build date of the driver. 
        ''' </summary>
        Public ReadOnly Property BuildDate As Date
    
        ''' <summary>
        ''' Gets a value that indicates the driver signature status.
        ''' </summary>
        Public ReadOnly Property Signature As DismDriverSignature
    
        ''' <summary>
        ''' Gets a value indicating whether the driver is included on the Windows distribution media 
        ''' and automatically installed as part of Windows. 
        ''' </summary>
        Public ReadOnly Property Inbox As Boolean
    
        ''' <summary>
        ''' Gets a value indicating whether the driver is located on the operating system 
        ''' that is currently running on the local computer.
        ''' </summary>
        Public ReadOnly Property Online As Boolean
    
        ''' <summary>
        ''' Gets a value indicating whether the driver is boot-critical. 
        ''' </summary>
        Public ReadOnly Property BootCritical As Boolean
    
        ''' <summary>
        ''' Gets a value indicating whether the computer needs to be restarted to complete driver install/uninstall. 
        ''' </summary>
        Public ReadOnly Property RestartRequired As Boolean
    
        ''' <summary>
        ''' Gets the catalog file for the driver. 
        ''' </summary>
        Public ReadOnly Property CatalogFile As String
    
        ''' <summary>
        ''' Gets the original file name of the driver. 
        ''' </summary>
        Public ReadOnly Property OriginalFileName As String
    
        ''' <summary>
        ''' Gets the driver directory.
        ''' </summary>
        Public ReadOnly Property Directory As DirectoryInfo
            Get
                Return New DirectoryInfo(IO.Path.GetDirectoryName(Me.OriginalFileName))
            End Get
        End Property
    
    #End Region
    
    #Region " Private Fields "
    
        ''' <summary>
        ''' A reference to the underlying <see cref="PSObject"/> object of type: "Microsoft.Dism.Commands.BasicDriverObject".
        ''' </summary>
        Private ReadOnly dismDriverObject As PSObject
    
    #End Region
    
    #Region " Constructors "
    
        ''' <summary>
        ''' Prevents a default instance of the <see cref="DismDriverInfo"/> class from being created.
        ''' </summary>
        <DebuggerNonUserCode>
        Private Sub New()
        End Sub
    
        ''' <summary>
        ''' Initializes a new instance of the <see cref="DismDriverInfo"/> class.
        ''' </summary>
        ''' <param name="dismDriverObject">
        ''' A <see cref="PSObject"/> object whose <see cref="PSObject.BaseObject"/> property 
        ''' is of type: "Microsoft.Dism.Commands.BasicDriverObject"
        ''' <para></para>
        ''' This object is obtained from the return value of a call to <see cref="PowerShell.Invoke()"/> function 
        ''' that invoked the PowerShell's 'Get-WindowsDriver' cmdlet.
        ''' </param>
        ''' <remarks>
        ''' Get-WindowsDriver cmdlet reference: 
        ''' <see href="https://learn.microsoft.com/en-us/powershell/module/dism/get-windowsdriver"/>
        ''' </remarks>
        <DebuggerStepThrough>
        Public Sub New(dismDriverObject As PSObject)
    
            If Not dismDriverObject.BaseObject?.GetType().FullName.Equals("Microsoft.Dism.Commands.BasicDriverObject", StringComparison.Ordinal) Then
                Throw New ArgumentException("Invalid DISM driver object.", paramName:=NameOf(dismDriverObject))
            End If
    
            Me.dismDriverObject = dismDriverObject.Copy()
    
            Try
                Me.Provider = CStr(Me.dismDriverObject.Properties("ProviderName").Value)
                Me.ClassName = CStr(Me.dismDriverObject.Properties("ClassName").Value)
                Me.ClassDescription = CStr(Me.dismDriverObject.Properties("ClassDescription").Value)
                Me.ClassGuid = New Guid(CStr(Me.dismDriverObject.Properties("ClassGuid").Value).Trim("{}".ToCharArray()))
    
                Me.DriverFile = CStr(Me.dismDriverObject.Properties("Driver").Value)
                Me.Version = New Version(Me.dismDriverObject.Properties("Version").Value.ToString)
                Me.BuildDate = Date.Parse(CStr(Me.dismDriverObject.Properties("Date").Value))
                Me.Signature = DirectCast(Me.dismDriverObject.Properties("DriverSignature").Value, DismDriverSignature)
    
                Me.Inbox = CBool(Me.dismDriverObject.Properties("Inbox").Value)
                Me.Online = CBool(Me.dismDriverObject.Properties("Online").Value)
    
                Me.BootCritical = CBool(Me.dismDriverObject.Properties("BootCritical").Value)
                Me.RestartRequired = CBool(Me.dismDriverObject.Properties("RestartNeeded").Value)
    
                Me.CatalogFile = CStr(Me.dismDriverObject.Properties("CatalogFile").Value)
                Me.OriginalFileName = CStr(Me.dismDriverObject.Properties("OriginalFileName").Value)
    
            Catch ex As Exception
                Throw
    
            End Try
    
        End Sub
    
    #End Region
    
    #Region " Public Methods "
    
        ''' <summary>
        ''' Returns a String that represents the current <see cref="DismDriverInfo"/> object.
        ''' </summary>
        ''' <param name="expandedString">
        ''' If <see langword="True"/>, returns a single-line string; otherwise, a multi-line string.
        ''' </param>
        ''' <returns>
        ''' A string that represents the current <see cref="DismDriverInfo"/> object.
        ''' </returns>
        <EditorBrowsable(EditorBrowsableState.Always)>
        <DebuggerStepThrough>
        Public Shadows Function ToString(Optional singleLine As Boolean = True) As String
    
            Dim sb As New StringBuilder(capacity:=512)
    
            If singleLine Then
                sb.Append("["c)
                sb.Append($"{NameOf(Me.Provider)}:{Me.Provider}, ")
                sb.Append($"{NameOf(Me.ClassName)}:{Me.ClassName}, ")
                sb.Append($"{NameOf(Me.ClassDescription)}:{Me.ClassDescription}, ")
                sb.Append($"{NameOf(Me.ClassGuid)}:{Me.ClassGuid}, ")
                sb.Append($"{NameOf(Me.DriverFile)}:{Me.DriverFile}, ")
                sb.Append($"{NameOf(Me.Version)}:{Me.Version}, ")
                sb.Append($"{NameOf(Me.BuildDate)}:{Me.BuildDate.ToShortDateString()}, ")
                sb.Append($"{NameOf(Me.Signature)}:{Me.Signature}, ")
                sb.Append($"{NameOf(Me.Inbox)}:{Me.Inbox}, ")
                sb.Append($"{NameOf(Me.Online)}:{Me.Online}, ")
                sb.Append($"{NameOf(Me.BootCritical)}:{Me.BootCritical}, ")
                sb.Append($"{NameOf(Me.RestartRequired)}:{Me.RestartRequired}, ")
                sb.Append($"{NameOf(Me.CatalogFile)}:{Me.CatalogFile}, ")
                sb.Append($"{NameOf(Me.OriginalFileName)}:{Me.OriginalFileName}, ")
                sb.Append($"{NameOf(Me.Directory)}:{Me.Directory.FullName}")
                sb.Append("]"c)
            Else
                sb.AppendLine($"{NameOf(Me.Provider)}: {Me.Provider}")
                sb.AppendLine($"{NameOf(Me.ClassName)}: {Me.ClassName}")
                sb.AppendLine($"{NameOf(Me.ClassDescription)}: {Me.ClassDescription}")
                sb.AppendLine($"{NameOf(Me.ClassGuid)}: {Me.ClassGuid}")
                sb.AppendLine($"{NameOf(Me.DriverFile)}: {Me.DriverFile}")
                sb.AppendLine($"{NameOf(Me.Version)}: {Me.Version}")
                sb.AppendLine($"{NameOf(Me.BuildDate)}: {Me.BuildDate.ToShortDateString()}")
                sb.AppendLine($"{NameOf(Me.Signature)}: {Me.Signature}")
                sb.AppendLine($"{NameOf(Me.Inbox)}: {Me.Inbox}")
                sb.AppendLine($"{NameOf(Me.Online)}: {Me.Online}")
                sb.AppendLine($"{NameOf(Me.BootCritical)}: {Me.BootCritical}")
                sb.AppendLine($"{NameOf(Me.RestartRequired)}: {Me.RestartRequired}")
                sb.AppendLine($"{NameOf(Me.CatalogFile)}: {Me.CatalogFile}")
                sb.AppendLine($"{NameOf(Me.OriginalFileName)}: {Me.OriginalFileName}")
                sb.AppendLine($"{NameOf(Me.Directory)}: {Me.Directory.FullName}")
            End If
    
            Return sb.ToString()
    
        End Function
    
    #End Region
    
    End Class
    

    Secondly, this function which will serve to retrieve all the installed drivers:

    Public NotInheritable Class DriverUtil
    
        <DebuggerNonUserCode>
        Private Sub New()
        End Sub
    
        ''' <summary>
        ''' Get infomation about the drivers that are installed 
        ''' on the operating system that is currently running on the local computer.
        ''' </summary>
        ''' <param name="flags">
        ''' Flags combination that determine the function behavior.
        ''' </param>
        ''' <returns>
        ''' A <see cref="ReadOnlyCollection(Of DismDriverInfo)"/> object that represents the information about every driver found.
        ''' </returns>
        <DebuggerStepThrough>
        Public Shared Function GetSystemDrivers(flags As GetDriverFlags) As ReadOnlyCollection(Of DismDriverInfo)
    
            If Not flags.HasFlag(GetDriverFlags.Inbox) AndAlso
               Not flags.HasFlag(GetDriverFlags.NotInbox) Then
                Throw New InvalidEnumArgumentException(NameOf(flags), flags, GetType(GetDriverFlags))
            End If
    
            Dim driverInfoList As New List(Of DismDriverInfo)
    
            Dim sessionState As InitialSessionState = InitialSessionState.CreateDefault()
            sessionState.ExecutionPolicy = ExecutionPolicy.Unrestricted
            sessionState.ImportPSModule("Dism")
    
            Using runspace As Runspace = RunspaceFactory.CreateRunspace(sessionState),
                  ps As PowerShell = PowerShell.Create(runspace)
    
                Dim hasFlagInbox As Boolean = flags.HasFlag(GetDriverFlags.Inbox)
                Dim hasFlagNotInbox As Boolean = flags.HasFlag(GetDriverFlags.NotInbox)
    
                ' Save default runspace, if any.
                Dim previousDefaultRunSpace As Runspace = Runspace.DefaultRunspace
    
                ' Avoids running PowerShell scripts on new threads that has not unrestricted execution policy.
                Runspace.DefaultRunspace = runspace
                Runspace.DefaultRunspace.Open()
    
                Dim scratchDirectoryPath As String = Path.GetTempPath()
                Dim logFilePath As String = Path.Join(Path.GetTempPath(), "Get-WindowsDriver.log")
    
                ps.AddCommand("Get-WindowsDriver")
    
                ' Specifies that the action is to be taken on the operating system that is currently running on the local computer.
                ps.AddParameter("Online")
    
                ' Specifies the full path and file name to log to. If not set, the default is %WINDIR%\Logs\Dism\dism.log. 
                ps.AddParameter("LogPath", logFilePath)
                ps.AddParameter("LogLevel", "2") ' 2 = Errors and warnings
    
                ' Specifies a temporary directory that will be used when extracting files for use during servicing.
                ' The directory must exist locally.
                ps.AddParameter("ScratchDirectory", scratchDirectoryPath)
    
                If hasFlagInbox Then
                    ' Get information about default drivers and third-party drivers.
                    ' If you do not specify this parameter, only gets information about third-party drivers.
                    ps.AddParameter("All")
                End If
    
                Dim dismDriverObjects As Collection(Of PSObject)
                Try
                    dismDriverObjects = ps.Invoke()
                Catch ex As Exception
                    Throw
                End Try
    
                For Each dismDriverObject As PSObject In dismDriverObjects
    
                    Dim driverInfo As New DismDriverInfo(dismDriverObject)
                    If flags <> GetDriverFlags.Any Then
                        If (hasFlagInbox AndAlso Not driverInfo.Inbox) OrElse
                           (hasFlagNotInbox AndAlso driverInfo.Inbox) Then
                            Continue For
                        End If
                    End If
                    driverInfoList.Add(driverInfo)
    
                Next dismDriverObject
    
                Runspace.DefaultRunspace.Close()
    
                ' Restore default runspace, if any.
                Runspace.DefaultRunspace = previousDefaultRunSpace
    
            End Using
    
            Return driverInfoList.AsReadOnly()
    
        End Function
    
    End Class
    

    Third and last this code example that demonstrates how to differentiate between third-party drivers and default drivers:

    ' Get all drivers.
    Dim allDriversList As ReadOnlyCollection(Of DismDriverInfo) = DriverUtil.GetSystemDrivers(GetDriverFlags.Any)
    Debug.WriteLine($"{NameOf(allDriversList)} count: {allDriversList.Count}")
    
    ' Get only default (integrated) drivers.
    Dim defaultDriversList As ReadOnlyCollection(Of DismDriverInfo) = DriverUtil.GetSystemDrivers(GetDriverFlags.Inbox)
    Debug.WriteLine($"{NameOf(defaultDriversList)} count: {defaultDriversList.Count}")
    
    ' Get only third-party drivers.
    Dim thirdPartyDriversList As ReadOnlyCollection(Of DismDriverInfo) = DriverUtil.GetSystemDrivers(GetDriverFlags.NotInbox)
    Debug.WriteLine($"{NameOf(thirdPartyDriversList)} count: {thirdPartyDriversList.Count}")
    
    ' Iterate third-party drivers.
    For Each driverInfo As DismDriverInfo In thirdPartyDriversList
        Debug.WriteLine(driverInfo.ToString(singleLine:=False))
    Next driverInfo
    

    Output sample:

    ProviderName: Advanced Micro Devices, Inc
    ClassName: System
    ClassDescription: Dispositivos del sistema
    ClassGuid: 4d36e97d-e325-11ce-bfc1-08002be10318
    Driver: oem2.inf
    Version: 2.2.0.130
    Date: 11/03/2020
    IsSigned: True
    Inbox: False
    Online: True
    BootCritical: True
    RestartNeeded: False
    CatalogFile: amdgpio2.cat
    OriginalFileName: C:\Windows\System32\DriverStore\FileRepository\amdgpio2.inf_amd64_c72fc3523c1372d0\amdgpio2.inf
    Directory: C:\Windows\System32\DriverStore\FileRepository\amdgpio2.inf_amd64_c72fc3523c1372d0
    
    ProviderName: VMware, Inc.
    ClassName: Net
    ClassDescription: Adaptadores de red
    ClassGuid: 4d36e972-e325-11ce-bfc1-08002be10318
    Driver: oem16.inf
    Version: 14.0.0.5
    Date: 09/03/2021
    IsSigned: True
    Inbox: False
    Online: True
    BootCritical: False
    RestartNeeded: False
    CatalogFile: vmnetadapter.cat
    OriginalFileName: C:\Windows\System32\DriverStore\FileRepository\netadapter.inf_amd64_8e12d1edcc9e768d\netadapter.inf
    Directory: C:\Windows\System32\DriverStore\FileRepository\netadapter.inf_amd64_8e12d1edcc9e768d
    
    ...