driverquery prints the list of device drivers Windows has registered, with no module to import and no elevation required on the local machine. It is the fastest way to answer “which drivers are on this box, where do their files live, and which of them are unsigned” from a prompt you already have open.
The part that trips people up is that driverquery does not show one inventory. The default view and /v describe driver services: the kernel modules the Service Control Manager knows about, keyed on a module name. /si describes something else entirely, PnP devices and the INF file that installed each one. The two views share no column, so you cannot join them, and neither of them prints a driver version number anywhere.
Microsoft’s reference lists the switches and stops there. This page covers what the output actually contains, where the table view silently truncates values, how to parse the CSV in batch without losing columns, and which tool to reach for when the question is really “what version is installed”. It picks up where systeminfo leaves off: that report describes the operating system’s view of the hardware, and this one describes the drivers behind it.
Applies to: Windows 10 / 11, Windows Server 2016 / 2019 / 2022 / 2025
Quick answer
Three commands cover most of what people open driverquery for. The first is the inventory, the second is the signing audit, and the third is the one worth remembering: CSV output is the only format that is both complete and machine-readable.
rem every registered driver service, four columns, table format
driverquery
rem signing status per PnP device - a completely different row set
driverquery /si
rem full detail without truncation, safe to redirect into a spreadsheet
driverquery /v /fo csv > C:\perf\drivers.csv
findstr and expect full driver names to match. The Display Name column is cut to 22 characters. See the first hidden gem.
What driverquery does
The tool reads the driver entries Windows already maintains and formats them. Nothing is scanned, nothing is loaded, and no state changes: it is a reporting command, which is why it is safe to run on a production host in the middle of an incident.
This is the syntax line from Microsoft’s Windows Commands reference. Note that /v and /si sit on either side of a pipe, which means they are alternatives, not a switch and its modifier.
driverquery [/s <system> [/u [<domain>\]<username> [/p <password>]]] [/fo {table | list | csv}] [/nh] [/v | /si]
| Switch | What it does | Worth knowing |
|---|---|---|
/s <system> | Runs against a remote computer by name or IP | No leading backslashes. \\SRV-PROD-01 is rejected, SRV-PROD-01 works |
/u [<domain>\]<user> | Runs under different credentials | Cannot be used unless /s is also given |
/p <password> | Password for the /u account | Cannot be used unless /u is given. Puts a password on the command line |
/fo table | Fixed-width table. The default | Pads and truncates every column to a fixed width |
/fo list | One field per line, blank line between records | Full values, no truncation. Best for reading one driver |
/fo csv | Quoted comma-separated values | Full values, no truncation. Best for parsing |
/nh | Drops the header row | Not valid with /fo list |
/v | Verbose: 15 fields per driver service | Microsoft’s reference states /v is not valid for signed drivers |
/si | Signature information per PnP device | Four completely different columns. Not a superset of the default view |
driverquery /s SRV-PROD-01 /u CORP\svc-inv /p Passw0rd writes the password onto the command line, where it lands in the console history and in any process listing taken while the command runs. Use /s on its own with your current credentials, or run the query from a session that already holds the right token.
Two inventories, one command
This is the single most useful thing to understand before using the output for anything. The default view and /v answer “which driver services exist and what state are they in”. /si answers “which devices are present and which INF installed them”. Those are different questions about different objects, and the column lists prove it.
The default table prints four columns. Row values depend entirely on the host, but the shape is fixed; this sample comes from a Windows machine published on the MoonPoint command reference.
Module Name Display Name Driver Type Link Date
============ ====================== ============= ======================
1394ohci 1394 OHCI Compliant Ho Kernel 11/20/2010 5:44:56 AM
ACPI Microsoft ACPI Driver Kernel 11/20/2010 4:19:16 AM
AcpiPmi ACPI Power Meter Drive Kernel 11/20/2010 4:30:42 AM
AFD Ancillary Function Dri Kernel 5/30/2014 2:45:48 AM
RasAcd Remote Access Auto Con Kernel 7/15/2016 8:29:11 PM
/si against the same kind of machine prints these columns instead. Nothing carries over: no Module Name, no Driver Type, no Link Date.
DeviceName InfName IsSigned Manufacturer
============================== ============= ======== =========================
Microsoft ISATAP Adapter nettun.inf TRUE Microsoft
Generic volume volume.inf TRUE Microsoft
Generic volume shadow copy volsnap.inf TRUE Microsoft
Volume Manager machine.inf TRUE (Standard system devices)
UMBus Root Bus Enumerator umbus.inf TRUE Microsoft
User Mode Driver Frameworks Pl N/A FALSE N/A
| Property | Default and /v | /si |
|---|---|---|
| What one row is | A registered driver service | A PnP device |
| Key column | Module Name, unique per row | DeviceName plus InfName, repeats freely |
| Fields per record | 4 in the default view, 15 with /v | 4 |
| Field names shared with the other view | 0 | 0 |
| Tells you the driver state | Yes: Start Mode, State, Status | No |
| Tells you the file path | Yes, with /v | No, only the INF name |
| Tells you whether it is signed | No | Yes: IsSigned |
| Tells you the driver version | No | No |
Add the two field lists together and you get 19 distinct field names across both views. Not one of them contains a version number. That is not an omission in the table above; it is the reason the PowerShell section further down exists.
/si rows repeat on purpose. Ten identical volumes produce ten Generic volume rows, because each is a separate device using the same INF. A row count from /si is a device count, not a driver count.
Practical examples
Before the first example
driverquery.exe ships with Windows and needs no installation. Confirm that the one on your PATH is the Windows binary and not something else answering to the same name, which matters more than it sounds on a host with development tooling installed.
rem prints every match in PATH order - the first line is the one that runs
where driverquery
C:\Windows\System32\driverquery.exe
Every example below that writes a file uses C:\perf\, which does not exist on a clean install. The multi-host examples read a plain list of machine names from C:\perf\hosts.txt, one per line. Create both now so nothing later fails on a missing path.
rem 2>nul swallows the "already exists" error so this is safe to re-run
md C:\perf 2>nul
rem one host name per line, no backslashes, no quotes. Keep the space
rem before the redirection symbol: a digit touching > or >> is read as a
rem stream handle, so "SRV-FILE-02>>" would append to stderr instead
echo SRV-PROD-01 > C:\perf\hosts.txt
echo SRV-FILE-02 >> C:\perf\hosts.txt
type C:\perf\hosts.txt
for /f loops below use %%H, which is the form required inside a .bat or .cmd file. At an interactive prompt use a single percent sign, %H. Copying a batch-file loop straight into a console window is the most common reason one of these lines does nothing.
pnputil documentation explicitly says to open the prompt with Run as administrator.
1. Take a driver inventory you can actually open
The problem: a vendor support case asks for the full driver list from SRV-FILE-02, including file paths and load state. Copying the console table out of an RDP session loses the end of every long name.
The solution: /v /fo csv emits all 15 fields per driver with every value intact, quoted, and ready for a spreadsheet or a diff against another host.
This writes one file and prints nothing. On a typical server it finishes in a second or two, and the file is a few hundred kilobytes at most. The second command tells you how many records landed without opening it.
rem /v gives the 15-field record, /fo csv is the only format that neither
rem truncates values nor needs a fixed-width parser on the far end
driverquery /v /fo csv > C:\perf\SRV-FILE-02-drivers.csv
rem /v inverts the match and the search string is empty, which find never
rem treats as present on a line - so every line qualifies and /c prints the
rem total. The header row is counted, so the driver count is one less
find /c /v "" C:\perf\SRV-FILE-02-drivers.csv
"Module Name","Display Name","Description","Driver Type","Start Mode". If you see only four column names, /v did not take effect: check that you did not also pass /si.
2. Find the unsigned drivers on a host
The problem: a hardening baseline requires evidence that no unsigned drivers are installed, and you need the answer for fifty machines this afternoon rather than a Device Manager walk per host.
The solution: /si puts a literal TRUE or FALSE in the IsSigned column, so a single filter answers the question. This pairing comes from Microsoft’s own AskPerf post on the tool.
Expect either no output at all, which is the good result, or one line per unsigned device. Use /c: so the string is matched literally rather than as a list of space-separated search terms.
rem /c: treats FALSE as one literal string, not as a search-term list
driverquery /si | findstr /c:"FALSE"
rem one file per host, so a finding is attributable to a machine. /nh drops
rem the header, which means an empty file reads as "nothing unsigned here"
for /f %%H in (C:\perf\hosts.txt) do @driverquery /s %%H /si /nh /fo csv | findstr /c:"FALSE" > C:\perf\%%H-unsigned.csv
findstr /c:"FALSE" filter against /v output instead of /si. The verbose record has two other columns holding TRUE or FALSE, Accept Stop and Accept Pause, and neither has anything to do with signing. The filter returns rows, so it looks like it worked.
3. Find out where a driver file actually lives
The problem: a bug check names nvlddmkm.sys and you need the full path, the load state and the link date of that exact module before deciding whether the file on the box is the one the vendor asked you to install.
The solution: /v /fo list prints one field per line with no truncation, which is the right format when you want everything about one driver rather than a little about all of them.
Page through it to read records in full. To isolate one driver, filter the CSV instead: findstr works a line at a time, and in CSV format one driver is one line, so the whole record survives the filter.
rem list format keeps full values - the table view would cut Path and
rem Display Name to fit their columns
driverquery /v /fo list | more
rem one driver, one line, every field present. Filtering /fo list here
rem would return the single matching line and discard the 14 around it
driverquery /v /fo csv | findstr /c:"nvlddmkm"
This is the verbatim shape of one /fo list record, from the same published sample as the tables above. Fifteen labels, one per line, with a blank line before the next record.
Module Name: 1394ohci
Display Name: 1394 OHCI Compliant Host Controller
Description: 1394 OHCI Compliant Host Controller
Driver Type: Kernel
Start Mode: Manual
State: Running
Status: OK
Accept Stop: TRUE
Accept Pause: FALSE
Paged Pool(bytes): 4,096
Code(bytes): 200,704
BSS(bytes): 0
Link Date: 11/20/2010 5:44:56 AM
Path: C:\Windows\system32\drivers\1394ohci.sys
Init(bytes): 4,096
4. Inventory a remote server without configuring remoting
The problem: you need the driver list from SRV-PROD-01, PowerShell remoting is not enabled on it, and getting WinRM turned on is a change request rather than an afternoon.
The solution: /s uses the same remote administration path systeminfo /s and tasklist /s use, so it works wherever those already work, with the credentials you are already holding.
Output is identical to a local run. When it fails it fails on access rather than on syntax, and the most common self-inflicted cause is a leading backslash on the host name.
rem no backslashes on the host name - Microsoft's reference is explicit
rem about this, and \\SRV-PROD-01 is rejected
driverquery /s SRV-PROD-01 /v /fo csv > C:\perf\SRV-PROD-01-drivers.csv
rem one file per host, named after the host, from the list created above
for /f %%H in (C:\perf\hosts.txt) do @driverquery /s %%H /v /fo csv > C:\perf\%%H-drivers.csv
5. Parse the CSV in batch without losing columns
The problem: a scheduled batch job needs the module name and driver type for every driver, and the obvious for /f loop over /v /fo csv returns values that look plausible but belong to the wrong column.
The solution: parse the four-column default view in batch, and leave the verbose view to a parser that understands quoting. for /f splits on delimiter characters with no knowledge of quotes at all.
Save this as C:\bat\driver-list.bat and run it. It prints two fields per driver and writes nothing.
@echo off
rem the default view's four values rarely carry a comma, which is what makes
rem delims=, workable here and unsafe for /v output - see the hidden gems
rem %%~A strips the surrounding quotes that /fo csv adds to every field
for /f "tokens=1,3 delims=," %%A in ('driverquery /fo csv /nh') do (
echo %%~A [%%~B]
)
1394ohci [Kernel]
ACPI [Kernel]
AcpiPmi [Kernel]
AFD [Kernel]
tokens=1,3 assigns the first requested token to %%A and the next requested one to %%B, so %%B holds column 3. The variable letters advance per requested token, not per column, which is why there is no %%C here.
For the verbose view, hand the parsing to ConvertFrom-Csv, which is quote-aware. This selects three of the fifteen fields and sorts by the driver file path.
# driverquery is a CMD tool but its CSV parses cleanly in PowerShell.
# ConvertFrom-Csv respects the quoting, so a comma inside a value stays
# inside that value instead of shifting every later column.
driverquery /v /fo csv |
ConvertFrom-Csv |
Where-Object State -eq 'Running' |
Select-Object 'Module Name', 'Start Mode', Path |
Sort-Object Path
Hidden gems
The table view truncates, and findstr will miss the driver
Table output is fixed-width, and the widths are visible in the = separator line: 12 characters for Module Name, 22 for Display Name, 13 for Driver Type, 22 for Link Date. Values longer than their column are cut rather than wrapped, and nothing marks the cut.
In the five-row sample above, four of the five Display Name cells reach all 22 characters with no trailing space, which is the signature of truncation rather than coincidence. 1394 OHCI Compliant Host Controller is 35 characters and renders as 1394 OHCI Compliant Ho, losing the last 13. So the first of these returns nothing on a host where the driver is present and running, and the second finds it.
rem returns nothing - the rendered cell stops at "1394 OHCI Compliant Ho"
driverquery | findstr /c:"1394 OHCI Compliant Host Controller"
rem works, because csv output carries the full value
driverquery /fo csv | findstr /c:"1394 OHCI Compliant Host Controller"
/si has the same problem with different widths: 30 for DeviceName, 13 for InfName, 8 for IsSigned, 25 for Manufacturer. In the /si sample above, User Mode Driver Frameworks Pl fills all 30 characters of DeviceName and (Standard system devices) fills all 25 of Manufacturer, both of them cut mid-value. IsSigned is the one field that is always safe to filter on, because TRUE and FALSE both fit inside 8 characters.
netavpna.inf at 12 characters survives and anything longer is clipped without warning. Filter /fo csv output instead and the width stops mattering.
for /f does not understand quotes, and fails silently
CSV output quotes every field, but for /f splits on the characters in delims= wherever they appear, including inside a quoted value. In the verbose record three fields carry byte counts formatted with thousands separators, which the published record above shows as 4,096 and 200,704.
Reimplementing that split against that exact record makes the damage measurable. The line holds 15 CSV fields. A comma-delimited split produces 18 tokens, because three values each contribute an extra one. Field 14 is the driver Path; tokens=14 returns 0, which is the BSS byte count from field 12, and the Path has moved to token 16.
/v /fo csv record : 15 CSV fields -> 18 comma-delimited tokens
csv field 14 : C:\Windows\system32\drivers\1394ohci.sys (Path)
for /f tokens=14 : 0 (BSS bytes)
Path lands at : token 16
Nothing errors. The loop runs, every iteration produces a value, and the value comes from the wrong column. The same rule reaches the default four-column view the moment a vendor ships a driver whose Display Name contains a comma: that one row shifts and the rest do not, which is the hardest version of this bug to spot.
The byte columns do survive arithmetic in PowerShell
Those thousands separators look like they need cleaning before any arithmetic, and the instinct is to strip them with -replace. Measured in PowerShell 7.4.6, that turns out to be unnecessary. PowerShell converts a numeric string using the invariant culture rather than the session culture, so the group separator is handled and the same expression returns the same answer under en-US, de-DE, ru-RU and fr-FR.
# '200,704' is a string with a group separator, yet this returns 196
$row = driverquery /v /fo csv | ConvertFrom-Csv | Select-Object -First 1
$row.'Code(bytes)' / 1KB
# and the cast is not culture-sensitive - all four of these print 200704
foreach ($c in 'en-US', 'de-DE', 'ru-RU', 'fr-FR') {
[System.Threading.Thread]::CurrentThread.CurrentCulture = $c
[double]'200,704'
}
[int]($row.'Code(bytes)' -replace '[^0-9]','') gives the same 200704 and cannot be affected by a culture change at all.
Neither view has a version, and the one that does misleads on ordering
Across all 19 field names in both views there is no driver version. The number lives in Win32_PnPSignedDriver, whose class definition lists DriverVersion next to DriverDate, Signer and InfName. The catch is the type: DriverVersion is documented as a String, so the obvious sort is a text sort.
Measured in PowerShell 7.4.6 against four realistic version strings, the two sorts disagree completely. A text sort puts 10.0.19041.1 first and 9.21.13.5017 last, because the character 1 sorts before 9.
Sort-Object DriverVersion : 10.0.19041.1 < 12.19.1.39 < 214.0.0.5 < 9.21.13.5017
Sort-Object { [version]$_.DriverVersion } : 9.21.13.5017 < 10.0.19041.1 < 12.19.1.39 < 214.0.0.5
'9.21.13.5017' -lt '10.0.19041.1' : False
[version]'9.21.13.5017' -lt [version]'10.0.19041.1' : True
[version] before comparing or sorting. Not every driver version string parses as a [version], so filter out anything that does not match a four-part number before the cast, or wrap it in a try.
PowerShell and the driver store
There is no cmdlet that replaces driverquery, because each of the alternatives answers a different question. Two are cmdlets and one, pnputil, is another CMD tool. Picking the wrong one is how a driver audit ends up with row counts nobody can reconcile.
| Question | Reach for | What one row is |
|---|---|---|
| Which driver services exist and what state are they in | driverquery /v | A registered driver service |
| What version is installed for this device | Get-CimInstance Win32_PnPSignedDriver | A PnP device with its signed-driver detail |
| Which driver packages are staged in the driver store | Get-WindowsDriver -Online | An INF-based driver package |
| Which driver store packages can I export or remove | pnputil /enum-drivers | A published driver package |
Win32_PnPSignedDriver is the closest match to driverquery /si, with the version added. This is the query worth keeping: it drops the rows carrying no version at all, which is most of the in-box device entries, and sorts what is left by device.
# DeviceName and InfName are the same two fields /si prints, so this output
# lines up with a /si audit while adding the number /si cannot give you
Get-CimInstance Win32_PnPSignedDriver |
Where-Object { $_.DriverVersion -and $_.Manufacturer -notlike 'Microsoft*' } |
Select-Object DeviceName, DriverVersion, DriverDate, InfName, IsSigned |
Sort-Object DeviceName |
Export-Csv -Path C:\perf\driver-versions.csv -NoTypeInformation
Get-WindowsDriver comes from the DISM module and, in Microsoft’s words, “displays information about driver packages in the online or offline Windows image”. That is the driver store, not the service list, which is why its output is so much shorter. Without -All it lists third-party drivers only, so the bare form is usually what a “what did we install” audit actually wants.
# -Online targets the running OS. Bare, this is the third-party list;
# -All adds the in-box packages and makes it far longer.
Get-WindowsDriver -Online | Format-Table -AutoSize
Get-WindowsDriver -Online -All | Measure-Object | Select-Object Count
# the cmdlet reference names its output type but not the property names,
# so read them off the object once rather than guessing
Get-WindowsDriver -Online | Get-Member -MemberType Property
The underlying fields are documented, just not in the cmdlet reference. Microsoft’s DismDriverPackage structure lists PublishedName, OriginalFileName, InBox, CatalogFile, ClassName, BootCritical, DriverSignature, ProviderName, Date, and the version split across MajorVersion, MinorVersion, Build and Revision. That split is worth noting: at this layer there is no single version string to sort on at all.
.exe files are not supported, so a vendor driver delivered as a setup executable may not appear as a driver package here even though its driver service shows up in driverquery.
Where this matters
- Post-incident forensics. A bug check names a
.sysfile anddriverquery /v /fo listgives you its path, load state and link date without opening a debugger. - Build comparison. Two identically specified servers behave differently; a CSV from each, diffed on Module Name and Path, shows which one carries an extra or older driver.
- Hardening evidence.
driverquery /si | findstr /c:"FALSE"is a one-line, auditable answer to “are there unsigned drivers on this host”. - Hosts you cannot remote into.
/sworks on machines where WinRM was never enabled, which is common on older infrastructure and on appliances. - Vendor support cases. Support asks for the driver inventory, and a CSV from
/v /fo csvanswers the first three follow-up questions before they are asked. - Golden image drift. Running the same query on the image and on a deployed machine shows what the deployment added, which is the fastest route to an unexpected third-party driver.
Tips and limitations
- Querying the local machine needs no elevation.
/sagainst another host needs whatever rights that host requires for remote administration. /vand/siare alternatives. The syntax line separates them with a pipe, and Microsoft’s reference adds that/vis not valid for signed drivers./nhis not valid with/fo list, which has no header row to drop. It works with table and CSV./stakes a bare name or IP address. Leading backslashes are not accepted.- Link Date describes the binary, not the installation. Use
DriverDatefromWin32_PnPSignedDriverwhen you need a driver release date. - Nothing here changes system state. The only artefacts are the files you redirect to, and
del C:\perf\SRV-FILE-02-drivers.csvremoves one of them. - Use CSV or list format for anything a script or a person will read closely. Table format is for a quick look at a console and nothing more.
Official documentation
- driverquery, Windows Commands, Microsoft Learn
- Win32_PnPSignedDriver class, Microsoft Learn
- Get-WindowsDriver, DISM module, Microsoft Learn
- DismDriverPackage structure, Microsoft Learn
- PnPUtil command syntax, Microsoft Learn
- Two Minute Drill: DriverQuery.exe, Microsoft Community Hub
Related tools
- System tools: the browser-side utilities for the same kind of local Windows inventory work.
- Event log analyzer: paste the event a driver failure raised and get the meaning behind the ID.
Related guides
- systeminfo in Windows: the OS-level report this page hands off from, with the same CSV parsing problem.
- sc.exe in Windows:
sc query type= driverreaches the same driver services from the service side. - findstr and find in Windows: what
/c:does, and why it matters when filtering driver output. - WMIC vs PowerShell: why
Get-CimInstanceis the right way to reachWin32_PnPSignedDriver. - DISM for Windows image repair: the servicing stack
Get-WindowsDriverbelongs to. - tasklist and taskkill in Windows: the same
/sand/fo csvswitch family on the process side. - Windows Command Line (CMD) Cheat Sheet: quick lookup for the CMD switches used above.
- PowerShell Commands Cheat Sheet: the cmdlet side of the same inventory work.