whoami /all in Windows: read your access token correctly

whoami does not ask Windows who you are. It prints what is already inside the access token that Windows built for your session at logon, and nothing else. Microsoft’s own wording for /all is exact about this: it displays “all information in the current access token, including the current user name, security identifiers (SID), privileges, and groups that the current user belongs to.”

That distinction is the whole reason the command matters to an administrator. Every access check Windows performs is made against the token, not against Active Directory and not against the local Administrators group. So when a file refuses to open, a service refuses to start, or a script that worked yesterday now fails, the token is the primary evidence, and whoami is the cheapest way to read it.

The reference page documents eleven parameters and shows exactly one line of example output. It shows nothing at all for /all. This article fills that gap with Microsoft’s own published captures, and then does the part the documentation never does: it shows which readings of that output are wrong, and proves each one against the captured text.

Applies to: Windows 10, Windows 11, Windows Server 2016 and later


Quick answer

Three questions, in the order an administrator usually needs them: which account is this process running as, which groups are in its token, and which privileges does it hold. None of them require elevation to run.

rem Account name plus the SID that every ACL is actually compared against
whoami /user

rem Groups in the token. Read the Attributes column, not just the Group Name column
whoami /groups

rem Privileges present in this token, with their enabled/disabled state
whoami /priv

And the question those three are usually a detour around: is this prompt elevated? The integrity level SID answers it in one line, and it is the check used throughout this site. It sets errorlevel to 0 when the process is running at High integrity and 1 when it is not.

rem S-1-16-12288 is the High integrity level. Present only in an elevated token
whoami /groups | findstr /c:"S-1-16-12288"
Warning: Do not replace that check with a search for BUILTIN\Administrators. On a non-elevated prompt belonging to an administrator, that group name is present in the output and the search succeeds, which is the opposite of the answer you wanted. The section on elevation below proves it against Microsoft’s own capture.

What whoami actually reads

When you sign in, the Local Security Authority builds an access token: your account SID, the SIDs of every group the logon carried, the set of privileges granted to those groups, and one integrity level SID. Every process you start inherits a copy. whoami formats that copy for the console. It performs no directory lookup and no re-evaluation, which is why its answers are instant and why they can be out of date.

whoami /all prints the token in three labelled sections, in this order: USER INFORMATION, GROUP INFORMATION, then PRIVILEGES INFORMATION. Each section has its own header row and its own row of = characters, and each is a separate table with a different column count. That matters later, when something tries to parse it.

ParameterWhat it returns
/upnThe user name in user principal name (UPN) format
/fqdnThe user name in fully qualified domain name (FQDN) format
/logonidThe logon ID of the current user
/userThe current domain and user name, and the security identifier (SID)
/groupsThe user groups to which the current user belongs
/claimsThe claims for the current user: claim name, flags, type and values
/privThe security privileges of the current user
/allEverything in the current access token: user name, SIDs, privileges and groups
/fo <format>Output format: table (the default), list or csv
/nhSuppresses the column header. Valid only for table and CSV formats
/?Displays help at the command prompt

The first three are mutually exclusive with each other. The middle four combine freely, so whoami /user /priv is legal and prints two sections. /all is the union of the combinable set.

Note: The reference page’s Examples section shows the output of a bare whoami (one line, DOMAIN1\administrator) and then says “To display all of the information in the current access token, type: whoami /all” with no output block after it. That is not a rendering problem on your end. The source file for the page carries no example output for /all either.

Before the first example

Four things to have straight before running anything below.

1. There is nothing to install. whoami.exe ships in System32 on every supported Windows client and server. Confirm the binary you are about to run is that one and not something earlier on your PATH.

where whoami
C:\Windows\System32\whoami.exe

2. Open two prompts, not one. One ordinary Command Prompt and one started with Run as administrator. The central point of this article is a difference between those two that most of the output does not show, so every example below is worth running in both windows and comparing.

3. /upn and /fqdn are directory name formats. They ask for the user principal name and the fully qualified name, which are properties of a directory account. A purely local account has neither, so on a workgroup machine those two switches have nothing to return. Use /user instead, which always works.

4. Every block of whoami output on this page is Microsoft’s, not ours. The captures come from the Microsoft Entra troubleshooting article “No local administrator group privileges on Microsoft Entra joined device”, which publishes a full whoami /all capture and a full whoami /groups capture taken on the same machine minutes apart. Not one of them was typed by hand, which is what makes the parsing results further down worth anything.


Practical examples

1. Confirm which account a process is really running as

The problem: A scheduled task or a service writes to a share and gets access denied, but signing in as the same account by hand works fine.

The solution: Have the failing context print its own token identity rather than trusting the configured account name. /user returns the SID as well as the name, and the SID is what the ACL is compared against.

rem Run this INSIDE the failing context, not from your own prompt
rem The SID is the useful half: names get renamed, SIDs do not
whoami /user

The USER INFORMATION section is two columns wide. One oddity in Microsoft’s capture is worth flagging so you do not think you have misread it: the two halves of the name are printed the other way round from the DOMAIN1\administrator shown on the reference page.

USER INFORMATION
----------------

User Name             SID
===================== ==================================================
someuser\contoso.corp S-1-12-3687709483-1112055202-2756941246-4106396469
Note: The SID prefix is useful triage on its own. An account SID beginning S-1-5-21- was issued by a domain or by the local machine, which is the ordinary case. S-1-5-18 is documented as “a special account used by the operating system”, better known as LocalSystem. The capture above begins S-1-12-, which is what a cloud account looks like on an Entra joined device. If a service you expected to run under a domain account reports S-1-5-18, the logon account is the bug and no ACL change will fix it.

2. Check a group membership that actually decides something

The problem: A user was added to a security group that grants access to a file share, the change replicated hours ago, and they still cannot open the folder.

The solution: Ask the token, not the directory. whoami /groups lists what the logon actually carried. Filter it rather than reading twenty rows by eye.

rem /c: treats the whole string as one literal, so spaces in the group name are safe
rem Without /c: findstr would search for each space-separated word independently
whoami /groups | findstr /c:"FIN-Reports-RW"

An empty result means the group was not in the token when this session was created. That is a different fault from “the group does not grant access”, and it has a different fix: a new logon, covered in example 4.

Warning: A match is necessary but not sufficient. The row can be present and still be inert, which is what the Attributes column is for. The next section is entirely about that case.

3. Check whether you hold a specific privilege

The problem: A tool documents that it needs a named user right, for example fsutil file setvaliddata, which requires Perform volume maintenance tasks. Local Administrators do not hold that one by default, so “I am an admin” does not answer the question.

The solution: whoami /priv lists the privileges present in this token and their state. Search for the constant name, which is stable, rather than the English description, which is not.

rem The Se...Privilege constant is the same on every Windows language build
rem The Description column is localised, so never match on that
whoami /priv | findstr /c:"SeManageVolumePrivilege"

Here is the PRIVILEGES INFORMATION section from Microsoft’s capture of a standard, non-elevated session. Five privileges, which is a normal count for a filtered token.

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                          State
=======================       ==================================== ========
SeShutdownPrivilege           Shut down the system                 Disabled
SeChangeNotifyPrivilege       Bypass traverse checking             Enabled
SeUndockPrivilege             Remove computer from docking station Disabled
SeIncreaseWorkingSetPrivilege Increase a process working set       Disabled
SeTimeZonePrivilege           Change the time zone                 Disabled

Two things to read off it. This list is the token’s privilege set, not a catalogue of everything Windows defines: a privilege you were never granted is simply absent, so for /priv a name match genuinely is an existence test. And State is per-process, not per-account. Disabled means the privilege is in the token but not currently switched on, which is the normal resting state; a program that needs it enables it for itself when it runs.

Note: The constant-to-description mapping is documented. SeBackupPrivilege is “Back up files and directories”, SeRestorePrivilege is “Restore files and directories”, SeDebugPrivilege is “Debug programs”, SeTakeOwnershipPrivilege is “Take ownership of files or other objects” and SeManageVolumePrivilege is “Perform volume maintenance tasks”. Those English strings are exactly the names you will find in Local Security Policy under User Rights Assignment.

4. Work out why a group change had no effect

The problem: The group is correct in Active Directory, replication is healthy, and the user still cannot reach the resource. Locking and unlocking the workstation changed nothing.

The solution: Accept that the token is a snapshot. Microsoft states the rule plainly for the VPN case: “After Windows creates the user security context, it does not update the context until the next time that the user signs in.” The same article notes that runas is not a workaround, because it reuses the same credential information to start the new session.

rem Capture the token before the sign-out so you have something to compare against
whoami /all > C:\bat\test\token-before.txt

rem Sign out and sign back in, then capture again from a fresh session
whoami /all > C:\bat\test\token-after.txt

rem fc reports only the lines that differ, which is the group that arrived
fc C:\bat\test\token-before.txt C:\bat\test\token-after.txt
Result: If the new group appears only in the second file, the change was fine all along and the stale token was the entire fault. If it appears in neither, the problem is upstream of the workstation and belongs to Active Directory, not to this machine.

Kerberos adds a second cache in front of this one. If the token is current but a network resource still refuses, the service ticket is the next thing to purge, which is covered in the klist guide to the Kerberos ticket cache.

5. Hand the whole token to someone else

The problem: You are asking a vendor or a colleague to look at a permissions fault on a machine you cannot reach, and a screenshot of one filtered line is not enough.

The solution: Send the whole token. /all into a file is the complete picture, and it is small. Add the machine name so the file is still useful a week later.

rem %COMPUTERNAME% keeps the file identifiable once it leaves this machine
rem 2>&1 captures any error text into the same file instead of losing it
whoami /all > C:\bat\test\token-%COMPUTERNAME%.txt 2>&1

rem Same content, machine-readable, for anything that has to process it
whoami /all /fo csv > C:\bat\test\token-%COMPUTERNAME%.csv 2>&1
Warning: The output contains every group SID the account carries, which for a domain account is a readable map of that user’s entitlements. Treat a whoami /all capture as sensitive and send it the way you would send any other security artefact.

Am I actually an administrator?

This is the question that sends most people to whoami, and it is the one the output is easiest to get wrong about. The trap is that “administrator” means two different things, and only one of them is a group.

Below is Microsoft’s whoami /groups capture from the Entra article, taken after the fix that the article is about. Note what it is: the successful outcome, the state Microsoft is telling you to expect once the role assignment has landed. The account is a local administrator on that device.

GROUP INFORMATION
-----------------

Group Name                                Type             SID                                                 Attributes
========================================= ================ =================================================== ==================================================
Mandatory Label\Medium Mandatory Level    Label            S-1-16-8192
Everyone                                  Well-known group S-1-1-0                                             Mandatory group, Enabled by default, Enabled group
BUILTIN\Remote Desktop Users              Alias            S-1-5-32-555                                        Mandatory group, Enabled by default, Enabled group
BUILTIN\Users                             Alias            S-1-5-32-545                                        Mandatory group, Enabled by default, Enabled group
BUILTIN\Administrators                    Alias            S-1-5-32-544                                        Group used for deny only
NT AUTHORITY\REMOTE INTERACTIVE LOGON     Well-known group S-1-5-14                                            Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\INTERACTIVE                  Well-known group S-1-5-4                                             Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\Authenticated Users          Well-known group S-1-5-11                                            Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\This Organization            Well-known group S-1-5-15                                            Mandatory group, Enabled by default, Enabled group
LOCAL                                     Well-known group S-1-2-0                                             Mandatory group, Enabled by default, Enabled group
                                          Unknown SID type S-1-12-1-788341310-1134859379-3309005462-3346259773 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\Cloud Account Authentication Well-known group S-1-5-64-36                                         Mandatory group, Enabled by default, Enabled group

Two rows carry the answer, and neither of them is the presence of BUILTIN\Administrators.

The Administrators row is there, and its Attributes value is Group used for deny only. That flag means the SID may be used to match a deny entry in an ACL but never an allow entry. The group is in the token and contributes nothing. Microsoft’s ADMT troubleshooting article describes the same output in one sentence: although the user is “a member of the built-in Administrators group, they don’t have permissions to perform administrative tasks with this token.”

The first row says the same thing from the other direction. Mandatory Label\Medium Mandatory Level with SID S-1-16-8192 is the integrity level, and Medium is the level a filtered, non-elevated process runs at. An elevated process carries S-1-16-12288 instead.

Common mistake: Running whoami /groups | findstr /c:"BUILTIN\Administrators" against the capture above returns a match. A script using that as its elevation gate would conclude it is elevated, then run the privileged work and fail somewhere less obvious. The same capture returns nothing for findstr /c:"S-1-16-12288", which is the correct answer.

Three candidate checks, all run against that one capture. Only the third reports the truth.

CheckResult on the captureCorrect?
findstr /c:"BUILTIN\Administrators"1 match, so “elevated”No, a false positive
findstr /c:"S-1-5-32-544"1 match, so “elevated”No, same row, same fault
findstr /c:"S-1-16-12288"0 matches, so “not elevated”Yes

The .NET side agrees with the third and disagrees with the first two. Microsoft documents the behaviour of WindowsPrincipal.IsInRole without ambiguity: “The code returns false if you are in the standard user role, even if you are in the Built-in Administrators group.” So on that same session, the CMD name check says yes and the PowerShell role check says no. The PowerShell answer is the one that matches what the machine will actually let you do.

Integrity levels are defined by RID, and Microsoft publishes the RIDs in hexadecimal while whoami prints the SID in decimal. Both forms are below so the conversion is not something you have to do at a prompt.

RID constantHex, as Microsoft writes itSIDMeaning
SECURITY_MANDATORY_UNTRUSTED_RID0x00000000S-1-16-0Untrusted
SECURITY_MANDATORY_LOW_RID0x00001000S-1-16-4096Low integrity
SECURITY_MANDATORY_MEDIUM_RID0x00002000S-1-16-8192Medium integrity
SECURITY_MANDATORY_MEDIUM_PLUS_RID0x00002000 + 0x100S-1-16-8448Medium high integrity
SECURITY_MANDATORY_HIGH_RID0x00003000S-1-16-12288High integrity
SECURITY_MANDATORY_SYSTEM_RID0x00004000S-1-16-16384System integrity
SECURITY_MANDATORY_PROTECTED_PROCESS_RID0x00005000S-1-16-20480Protected process
Note: Microsoft’s own integrity design article does one of these conversions for you, which is a useful sanity check on the whole column: “An example of a medium integrity level SID is this string: S-1-16-8192. The RID value of 8192 is the decimal equivalent of 0x2000.” The same page names four of the labels in the form whoami prints them: Mandatory Label\Low, \Medium, \High and \System Mandatory Level.

The Attributes column has a small vocabulary, and it is worth knowing all of it, because three of the four values look reassuring and one of them is not.

Attributes valueWhat it means for access checks
Mandatory group, Enabled by default, Enabled groupFully active. Matches both allow and deny entries
Group used for deny onlyMatches deny entries only. Grants nothing
Group owner (appended)This group is the token’s default owner for new objects
emptyThe integrity label row, which is not a group and has no attributes

Parsing the output without getting a wrong answer

The table form is display output. It is laid out for a human reading a console, and it changes shape depending on what is in it. The two captures in the Entra article make this measurable, because they come from the same command on the same machine a few minutes apart.

Reading the column widths off the row of = characters in each capture: in the first, the SID column is 12 characters wide and Attributes begins at offset 72. In the second, one long cloud SID is present, so the SID column is 51 characters wide and Attributes begins at offset 111. A parser that takes Substring(72) because that worked on Monday returns nothing but spaces on Tuesday.

Common mistake: Splitting the row on runs of two or more spaces looks like the shape-independent fix. It is not. Run -split '\s{2,}' over the twelve data rows of the second capture and you get three different field counts: seven rows split into 3 fields, three into 4, and two into only 2. $fields[2] is the SID on some rows, the Attributes text on others, and out of range on the rest.

The reason is worth a sentence, because it is not bad luck. Each column is exactly as wide as its widest value, so any value that happens to be the widest leaves a single space after it and the separator disappears. Well-known group is 16 characters and the Type column is 16 wide. NT AUTHORITY\Cloud Account Authentication is 41 characters and the Group Name column is 41 wide. On that one row both gaps collapse at once, and the split yields two fields.

Four candidates, each run over the second capture’s twelve data rows.

ApproachMeasured resultVerdict
for /f "tokens=1" for the group name4 of 12 rows correct, 8 wrongFails on any name with a space
for /f "tokens=3" for the SID2 of 12 rows correct, 10 wrongThe token index shifts with the name’s word count
-split '\s{2,}'Field counts of 2, 3 and 4 in one captureSilently merges columns
/fo csv piped to ConvertFrom-CsvQuoted fields, stable namesCorrect, with the guard below

The tokens=1 failures are not edge cases. NT AUTHORITY\INTERACTIVE yields NT, the integrity label row yields Mandatory, and the row whose Group Name is blank yields Unknown, inventing a group that does not exist. Only Everyone, LOCAL, BUILTIN\Users and BUILTIN\Administrators survive it.

So use the CSV form, which quotes every field. One guard is worth adding: keep only the lines that begin with a double quote, so that anything printed above the header cannot be mistaken for the header.

# Keep only quoted lines, so a banner above the header cannot become a column name
$groups = whoami /groups /fo csv |
    Where-Object { $_ -like '"*' } |
    ConvertFrom-Csv

# The elevation answer, read from the token rather than from a name match
$level = $groups | Where-Object { $_.SID -like 'S-1-16-*' }
"Integrity: {0} ({1})" -f $level.'Group Name', $level.SID

# And the deny-only case, named rather than guessed at
# Emitting strings rather than objects keeps the output identical
# in a console, in a transcript and in a redirected file
$groups |
    Where-Object { $_.Attributes -eq 'Group used for deny only' } |
    ForEach-Object { "deny only: {0}  {1}" -f $_.'Group Name', $_.SID }
Warning: That guard is not decoration. Measured in PowerShell 7.4.6: feed ConvertFrom-Csv a stray line before the header and it does not throw. It takes that line as the one and only column name, returns one object per remaining line, and every property you ask for comes back empty. A failure that produces an object with no error is much worse than one that stops.

Hidden gems

The same check in CMD and in PowerShell can disagree on case. findstr /c: is case-sensitive; PowerShell’s -match and -like are not. All three measured against the Administrators row from the capture above, the PowerShell pair in 7.4.6: -match 'builtin\\administrators' returns True, -cmatch with the identical pattern returns False, and findstr /c:"builtin\Administrators" finds nothing even though only one character differs in case. If a check was ported from a batch file to a script and started behaving differently, this is usually why.

A privilege you do not hold is not printed at all. This makes /priv behave differently from /groups. For groups, presence does not imply effect, so you must read Attributes. For privileges, absence is the whole answer: if SeBackupPrivilege is not in the list, this token does not have it, and no amount of enabling will produce it.

Microsoft’s own capture has a misaligned separator line. In the published PRIVILEGES INFORMATION block, the Description header starts at column 30 and the longest privilege name, SeIncreaseWorkingSetPrivilege, is 29 characters, so the first run of = should be 29 long. The capture’s is 23, padded out with spaces to keep the totals right. Harmless to read. It matters only if you develop a parser against the copied text rather than against output from a real machine, which is a mistake that is easy to make and hard to spot.

The combination that answers most permission tickets is two commands, not one. whoami /groups tells you what the token carries; gpresult tells you which policy was applied to that same session. When they disagree about a group, the token is the one that decides access, and the difference points at a logon that happened before the change.

/fo list is the form to read, not the form to parse. Each field prints as Name: value on its own line, which is far easier to scan than the 161-character table rows in the capture above. Note the documented limit while you are there: /nh applies only to the table and CSV formats, so it has no effect on the list form.


Cross-shell equivalent

PowerShell can read the same token directly through .NET, with no text to parse. This is the better route inside a script, because every value comes back typed.

# GetCurrent() returns the token of the process running this line, not of the account
$id = [Security.Principal.WindowsIdentity]::GetCurrent()

$id.Name          # DOMAIN\user, the same string bare whoami prints
$id.User.Value    # the account SID, the same value whoami /user prints
$id.Groups.Count  # group SIDs in the token, unresolved

The elevation check has a first-class form. IsInRole evaluates the current token, so it answers the question the BUILTIN\Administrators name search only appeared to answer.

# False on a non-elevated prompt even for an account in Administrators,
# because the filtered token carries that group as deny-only
$p = [Security.Principal.WindowsPrincipal]::new(
        [Security.Principal.WindowsIdentity]::GetCurrent())
$p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

$id.Groups returns SecurityIdentifier objects rather than names. Translating them is one call, and it is the step that turns a list of SIDs into something a human can read.

# Translate() throws for a SID with no name on this machine, so guard every one
# Written this way it also runs on Windows PowerShell 5.1
$id.Groups | ForEach-Object {
    $name = '(unresolved)'
    try { $name = $_.Translate([Security.Principal.NTAccount]).Value } catch { }
    [pscustomobject]@{ SID = $_.Value; Name = $name }
}
Note: Get-LocalGroupMember Administrators answers a different question. It reads the local account database and will happily list an account that is a member on paper while the running token carries that membership as deny-only. Group membership and effective membership are not the same query, and only whoami and the token APIs answer the second one.

Where this matters

  • Scripts that gate themselves on elevation. A batch file that checks for the Administrators group name instead of the integrity SID passes its own gate on a non-elevated prompt and then fails somewhere far less obvious.
  • Permission tickets that have already been “fixed”. The group is right in the directory, the ACL is right on the share, and the user’s token predates both. whoami /groups settles it in one line.
  • Service accounts that behave unlike the interactive account. Running whoami /all inside the service context, rather than from your own prompt, is usually where the real difference turns up.
  • Tools that need a named user right rather than admin rights. fsutil, backup software and database engines all ask for privileges that Administrators do not hold by default, and whoami /priv is the direct answer.
  • Remote and VPN sessions. If the workstation could not reach a domain controller at sign-in, the token was built from cached information and will stay that way until the next sign-in.
  • Audit and handover evidence. A dated whoami /all capture is a complete, self-contained record of what an account could do on a machine at a point in time.

Tips and limitations

  • whoami needs no elevation. Every switch runs from an ordinary prompt, and running it elevated changes the answer rather than enabling the command.
  • It reads the token of the process that runs it, and nothing else. There is no switch to inspect another user, another session or another machine.
  • The Description column in /priv is localised. Match on the Se...Privilege constant, which is not.
  • /upn and /fqdn need a directory account. On a local account, use /user.
  • /nh suppresses only the column header row, and only for the table and CSV formats. It does nothing for /fo list.
  • Table column widths are computed per run. Never hard-code a character offset taken from an earlier capture.
  • An empty Group Name cell is legal. Unresolvable SIDs print with the Type Unknown SID type and no name at all.
  • A whoami /all capture from a domain account discloses that account’s full group map. Handle it accordingly.

Official documentation


Related tools

  • Event Log Analyzer: the token is rebuilt at sign-in, so the sign-in events are how you date it when a group change appears not to have taken effect.
  • nltest Command Builder: when whoami /upn or /fqdn returns nothing useful, the next question is what this machine thinks its domain and domain controller are.

Related guides