Losoft Command line reference
Object: SERVICE / DRIVER
Interface: SCM
EN DE

Windows Service Permissions from the Command Line: DACL and SDDL Masks

A Windows service is a securable object, which means access to it is decided by a discretionary access control list rather than by group membership alone. Changing Windows service permissions from the command line is therefore a matter of editing that list, and the whole difficulty is that the list is written in access masks, not in verbs.

Diagram of a Windows service security descriptor showing owner, discretionary access control list and individual access entries.

What a service DACL actually contains

Every service and every kernel driver registered with the service control manager carries a security descriptor. The descriptor holds an owner, a group, a system access control list used for auditing, and the discretionary access control list that decides who may do what. Only the last of those is what people mean when they talk about service permissions.

The default descriptor is deliberately narrow. Administrators hold full control. Power Users, on the editions that still have that group, may start and stop. Everyone holds a small read mask so that ordinary code can ask whether a service is running. Nothing in the default grants a normal user the ability to start a service, which is the single most common reason an administrator ends up editing a DACL at all.

There are three recurring situations. A service exists but should only run on demand, and the people who need it are not administrators. A user-mode program needs to load or unload a specific device driver to work. Or an application installs a helper service and then expects unprivileged code to control it. All three are solved by granting a precise mask to a specific group, and all three are made worse by granting full control because it was quicker.

Descriptor at a glance

Stored under
HKLM\SYSTEM\CurrentControlSet\Services\<name>
Default owner
NT AUTHORITY\SYSTEM
Read the descriptor
sc sdshow <service>
Write the descriptor
sc sdset <service> <sddl>
Estate-wide equivalent
Security policy, System Services node
Applies at
every service start, and immediately for new access checks

The access mask, letter by letter

The mask is the part that resists memorisation, because a service has twelve meaningful rights and four conventional combinations of them. The tool that documented this most compactly was scacl.exe, a small freeware utility for Windows NT that gave each right a single letter and let a caller compose them into a string. Its letters are a useful teaching notation even now, because each one maps exactly onto a Win32 constant and an SDDL token that current tooling still uses.

Select a combination below to see which single rights it contains and what the resulting grant looks like. The composites are not shorthand invented for convenience: they are the groupings the Win32 service API itself defines, which is why the same four keep appearing in vendor documentation.

H Query config
G Change config
Q Query status
L Enum depends
S Start
E Stop
P Pause
I Interrogate
U User control
D Delete
C Read security
O Write security

No composite selected — all twelve single rights are listed individually below.

R = CHLIQ — Everything that only reads: the configuration, the status, the dependency list, the access control list, and interrogate, which asks the service itself to report.

X = CPSEU — Operate the service without being able to reconfigure it — the mask an operator account actually needs.

W = CG — Change the configuration but not the security descriptor. Grant it deliberately: it includes the binary path.

F = HGQLSEPIUDCO — Every right above, including deleting the service and rewriting its access control list.

Single rights
LetterPermitsDetail
H Query config
G Change config
Q Query status
L Enum depends
S Start
E Stop
P Pause
I Interrogate
U User control
D Delete
C Read security
O Write security

Reading a descriptor before changing it

The first command on any service should be a read. A descriptor printed with its security identifiers resolved tells you both who currently holds what and whether the entries even resolve on this machine. The older syntax listed the owner and then each entry with its mask in brackets:

C:\> scacl \\AMARETTO tcpip /S

Security settings of service tcpip on \\AMARETTO:
Owner: NT-AUTHORITY\SYSTEM (S-1-5-18)
DACL:
00: [RIU] \World (S-1-1-0)
01: [XR]  (null)\(null) (S-1-5-32-547)
02: [F]   BUILTIN\Administrators (S-1-5-32-544)
03: [F]   BUILTIN\Server-Operators (S-1-5-32-549)
04: [XR]  NT-AUTHORITY\SYSTEM (S-1-5-18)

Two things in that output are worth pausing on. Entry 01 has no name because the Power Users group does not exist on the machine that printed it, yet the entry is still live and still grants a mask. And World holds RIU rather than nothing, which is why unprivileged code can normally discover that a service exists.

The equivalent read today is sc sdshow tcpip, which returns the same information as a single SDDL string. It is denser and harder to read aloud, but it is the exact form sc sdset expects back, so a read-modify-write cycle never has to translate between two notations.

Granting and denying a mask from the command line

Setting Windows service permissions from the command line is a two-argument problem: which principal, and which mask. The dangerous part is neither of those. It is the difference between editing and replacing. A tool that replaces writes exactly the entries you named and discards everything else, including the entry that grants you the right to edit it again. Both the old and the current tooling default to replacing, which is why the edit flag matters more than any single right.

C:\> scacl \\AMARETTO tcpip /E /G BUILTIN\Users:SE /D Tom:SE

Do you really want to apply the new security settings [Yes/No]: y
Successfully applied new security settings to service tcpip

That single line grants the Users group start and stop, and denies the same two rights to one account. Deny entries are always evaluated first regardless of where they sit in the list, so the deny wins for that account even though the group grant would otherwise cover it. When the descriptor is printed back, a deny entry is marked to distinguish it from a grant:

00: -[SE] BISTRO\Tom (S-1-5-21-859451957-1360023216-1846434929-1003)
01: [RIU] \World (S-1-1-0)
06: [SE]  BUILTIN\USERS (S-1-5-32-545)

Order of evaluation

Switches are not applied in the order they are typed. The sequence below is the order the older tool used, and it is the same order that any correct read-modify-write cycle has to follow, because taking ownership and restoring the default both discard what came before.

Quiet
suppress the confirmation prompt for every following operation
Take ownership
the local Administrators group becomes the owner
Restore default
the descriptor the system writes at install time is restored, and becomes the base for what follows
Edit
subsequent changes modify the current list instead of replacing it
Verbose
print the resulting list
Remove, replace, grant, deny
applied last, in that order

Writing the same grant as an SDDL string with sc sdset

Windows service permissions from the command line have outlived the tool that first made them legible. Nothing about the object model has changed, so the reasoning transfers directly. What changed is the notation: instead of composing letters, a caller composes an SDDL string and hands the whole descriptor back. A grant of start, stop and query status to the Users group, appended to the default descriptor, looks like this.

C:\> sc sdshow Spooler
D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)...

C:\> sc sdset Spooler "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWRPWPLORC;;;BU)..."

The inserted entry reads as an allow entry with no inheritance flags, carrying query config, query status, enumerate dependents, start, stop, interrogate and read control, for the built-in Users group. Comparing it against the mask grid above is the fastest way to check an SDDL string that someone else wrote: every two-letter token in the mask position is one row of that table.

A practical caution about sc sdset: it takes the descriptor you give it literally and does not merge. Read the current string, insert your entry into it, and set the whole thing back. Writing only your own entry is the modern spelling of forgetting the edit flag, and it has the same consequence.

Where the older syntax still earns its place

Two reasons, both practical. Anyone who needs to change Windows service permissions inherits a body of writing that predates SDDL, and the letter notation is the key to it. First, that notation is legible: a colleague can read /G BUILTIN\Users:SE across a desk and know what it does, which is not true of a forty-character SDDL string. Second, a great deal of surviving documentation, forum answers and internal runbooks were written in it, and translating them requires the table above rather than guesswork. Knowing that X means control-but-not-reconfigure is what tells you whether a fifteen-year-old answer is safe to copy.

It is also the shortest description of intent. "Grant the operators group X on this service" states a policy; the SDDL equivalent states an encoding of that policy. When a change to Windows service permissions has to be reviewed by someone who did not write it, the policy form is the one that gets reviewed accurately.

The tools themselves are of their time. scacl.exe was published as freeware for Windows NT and Windows 2000 and is not maintained; on a current system the supported route is sc, PowerShell, or a security policy. One detail stops people before they reach the descriptor at all: sc wants the service key name, not the display name the console shows. sc sdshow Spooler works, sc sdshow "Print Spooler" does not, and the error says the service does not exist rather than that the name is the wrong one of the two.

Questions about Windows service permissions

Which right actually lets a non-administrator start a service?

SERVICE_START (0x0010, SDDL RP) on its own is enough to start a service, but a caller that can only start it cannot read its state, so most tooling appears to fail. In practice the useful grant is start, stop and query status together, which is SEQ in the letter notation. Close to the X composite, but not the same thing: X carries read security, start, stop, pause and user control, and leaves query status out.

Why does granting change-config quietly grant code execution?

SERVICE_CHANGE_CONFIG includes the binary path. A principal that can rewrite ImagePath can point the service at any executable, and the service control manager will then run that executable under the service account. Commonly LocalSystem. This is why change-config is treated as an administrative right rather than an operational one.

What does an empty DACL do to a service?

An empty discretionary access control list is not the same as no access control list. Empty means every access check fails, including the service control manager reading the configuration, so the service becomes unmanageable without first taking ownership. A missing DACL means the opposite: everything is permitted.

How do I recover a service after locking myself out?

Take ownership and restore the default descriptor. The old tool spelled this scacl.exe \\HOST service /T /I; the modern equivalent is to reset the descriptor with sc sdset using a known-good SDDL string, which requires WRITE_OWNER or administrator rights on the machine.

Are the SDDL letters for services the same as for files?

No. SDDL reuses one set of abbreviations across object types and resolves them by numeric value, so the same token means different things depending on what it is attached to. On a service RP is SERVICE_START; on a directory object the same token is read-property. Reading a service descriptor with file semantics in mind is the most common way to misread one.

Does changing a service DACL survive a reboot?

Yes. The descriptor is stored with the service registration under HKLM\SYSTEM\CurrentControlSet\Services and is applied at each start. It does not survive the service being deleted and reinstalled by a product installer, which is the usual reason a carefully built grant disappears after a patch.

Can Group Policy do this instead?

Yes, and for more than a handful of machines it should. The System Services node of a security policy writes the same descriptor to every machine in scope and reapplies it on refresh, which fixes the reinstall problem above. The command line remains the faster route for one machine and for reading what is actually configured.

Why did the old tool print accounts as (null)\(null)?

Because some of the built-in accounts named in a default service descriptor do not exist on every machine, and an unresolvable security identifier has no name to print. S-1-5-32-547 is the Power Users group, absent on a domain controller; S-1-5-32-549 is Server Operators, present only on one. The identifiers are still valid entries in the list.

What is the difference between interrogate and query status?

Query status reads the state the service control manager already holds. Interrogate asks the service itself to report, which forces a running service to answer and can therefore reveal a process that is hung rather than merely idle.

Is there any reason to grant user-defined control?

Only when the service documents a control code. Codes 128 to 255 are handled entirely inside the service, so the right means "may send this service its own commands" and nothing more. Granting it to a service that implements none has no effect at all.

← Previous: Utilities Reviewed: 2026-08-21 Next: Remote shutdown →