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:
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.
Since I have not been able to obtain a solution using WMI or Windows API, I have turned to integrated Powershell.
Required packages:
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
...