Modifying Permissions on Windows Services Pt I

I'm going to jot down some quick notes on modifying the permissions on Windows services, because I don't think I have written anything about it here before.

Many times, we find ourselves wanting to delegate some administrative activity on a server to another admin or group of admins, but we don't want to give them full administrative control over the entire server.  We need to delegate only specific activity.  For example, we might want to give our delegated users the right to stop, start and restart only a specific Windows service.  Modifying the ACL on a Windows service is a little more involved than modifying the ACL on a file or folder, though.

You can do this with Group Policy if it's a domain-joined machine.

Group Policy System Services

If the computer is not domain joined or if you only want to do this with the local security policy of one or two computers, you can also accomplish this task using Security Templates on the local computer:

Local Security Templates

You can also use the sc.exe utility:

sc sdshow and sc sdset

The sc sdshow servicename command displays the access control list of the Windows service, in SDDL (security descriptor definition language) format.

The SDDL string looks crazy at first, but it’s pretty simple after you analyze it for a second. There is a D: part, and an S: part. The D: part stands for Discretionary ACL. This is what we usually think of when we think of an ACL on a file, etc. The S: part is the system ACL that is used for things like object access auditing, and is not usually modified as much or thought about as much as the DACL.

With the second command, I am setting the new ACL on the service with sc sdset. I have inserted one Access Control Entry into the D: part of the ACL, before the S: part. The SID I specified is of a non-administrative user. I would recommend creating a security group called “IIS Delegated Administrators” or something like that, and using the SID of that security group. I have granted that account the RP, WP, and DT privileges. (Start service, stop service, and pause service.)  The A stands for Allow, as opposed to a Deny ACE.  And different types of objects such as services, files, MSDTC components, etc., all have slightly different rights strings.  In other words, the "RP" right means something different for a Directory Service object than it does for a Windows service.  Here are the rights strings for Windows services:














You can find a lot more here.

Supersymmetry Outlook Add-In v1.1.3.17

You can see the original Supersymmetry 1.0 post here.

The Supersymmetry Outlook Add-In has been upgraded to version  (Similarity to "31337" or "1337" is coincidental!) But this release is significantly cooler than 1.0 anyway.

In case you're not familiar, the purpose of the Supersymmetry Outlook Add-In is to prevent you from accidentally sending email messages in Outlook if they contain an uneven or unmatched pair of quotation marks, parentheses, curly braces or square brackets.  Read more about it and see more early screenshots in the 1.0 post I linked to earlier.

Improvements in this release include:

  • A new text file named ignore.txt should be placed at %USERPROFILE%\Supersymmetry\ignore.txt.  In this text file, the user can put character sequences ("tokens") for Supersymmetry to ignore in a message.  This is very handy for ignoring things like emoticons, because emoticons usually have parentheses in them, and the use of emoticons can make the message as a whole appear as though it contains an uneven number of parentheses, when the message is actually fine if you ignore the emoticons. A sample ignore.txt is included in the download package.
  • A new text file named divider.txt should be placed at %USERPROFILE%\Supersymmetry\divider.txt.  In this text file, the user can put a character or string token that splits or divides an email thread into pieces, and Supersymmetry will only scan up to the first occurrence of that token. The token or divider can be a single character, or a special string.  This is very useful so that Supersymmetry does not include all of the previous messages in the email thread during its scan.  I recommend putting your special delimiter character or string cleverly in your email signature for both new messages and replies, so that Outlook automatically inserts the token into every message you draft.  I'll leave that up to you.  A sample divider.txt is included in the download package.
  • If either of the two aforementioned files does not exist in the right place on the file system, the add-in will still work, but you will really miss those features.  Additionally, you will see some warning notifications pop up when Outlook loads the add-in:

Supersymmetry cannot locate its files

  • Notice that the recommended installation directory has changed from %APPDATA%\Supersymmetry to %USERPROFILE%\Supersymmetry, just because it's a little simpler.  If the add-in is installed correctly, and the configuration files were read successfully, you'll see these popups instead:

Supersymmetry loaded correctly


Download the package here: (296.8KB)

Make sure to Uninstall any old version of Supersymmetry first, by going to Programs and Features in your Control Panel and uninstalling Supersymmetry.  Also, make sure you've closed Outlook.  Then, unzip the package into your user profile directory, so that %USERPROFILE%\Supersymmetry exists and contains a file named setup.exe.  Next, run setup.exe.  In theory, that should help you download any required prerequisites such as .NET 4.5, and Visual Studio Tools for Office, and then install the add-in.  It's a "ClickOnce" deployment.  A great idea, when it works.


Simply go to Programs and Features (aka Add/Remove Programs) in Control Panel, find Supersymmetry, and uninstall it.

Have fun!

Supersymmetry in action!

Delegating the Permissions for Service Accounts to Dynamically Register Their Own SPNs #274

I often use this blog as my own personal scratch space... if any of my writings here end up helping anyone else, that is a major bonus, but I can't count the number of times I've been working on some technical issue and purposely searched through my own articles looking for a Powershell script or something that I vaguely remembered writing that might help with whatever I'm working on at the moment.

And that's why I was really surprised when I searched this blog today for SPN and servicePrincipalName and got nothing.  How have I not written anything about SPNs before!?  We're about to fix that...

Service Principal Names.  A really simple concept that seems, inexplicably, to blow some people's minds.  (Or maybe just make them doze off.)  And more often than not, when I look into Active Directory environments that use SQL Server, IIS, etc., the admins and owners have usually forgotten about SPNs.  And who cares about SPNs anyway? I mean, the application works fine without them, right?  I guess... if you don't mind having to use crummy old NTLM authentication when you could be using swanky Kerberos instead!

I'm going to focus specifically on Microsoft SQL Server and how it uses Service Principal Names today.  I wrote "#274" in the title of this post because there are many different ways to go about delegating these permissions, and I'm just going to present one possible way.  (A way that I think is better than how I have seen other people do it.)

So just as a quick recap, whenever the SQL service starts up, it attempts to register an SPN or service principal name in Active Directory.  An SPN is stored as an attribute on a user or computer account in Active Directory, depending on the security context in which the service is operating.  Even though the attribute name servicePrincipalName uses a singular tense, it's actually a multi-valued attribute that can and usually does contain many different SPNs for many different services.

So when you configure the SQL Server service (MSSQLSERVER) to run as Local System, the computer account for the computer that's running SQL needs the ability to write or update its own servicePrincipalName attribute on its computer account object in AD.  By default, computer accounts already have the permissions to write to their own servicePrincipalName attribute.  The name of the privilege is displayed "Validated write to service principal name:"

Write SPN permission

This is important that the computer only has the ability to write SPNs for itself, because it would be a major security concern for Active Directory accounts to be able to write SPNs on other accounts.

The validated write permission is restricted even further than just the regular ability to write to the servicePrincipalName attribute, as this causes the Directory Service Engine to reject updates that do not conform to the expected DNS FQDN and hostname format.  With just the basic write permissions, an account could theoretically write just any old invalid thing into the servicePrincipalName attribute, which is another security concern.  However, validated write to service principal name is only applicable to computer objects, not user objects.

So back to when the SQL Service starts up. When SQL is running as Local System, the computer account should have no problem registering an SPN for itself, like so:

Registered an SPN

However, most organizations run SQL services with a "service account," instead of Local System, and this is where things usually start to go pear-shaped:

SPN Registration Fail

You really should be using Managed Service Accounts for this, but the fact of the matter is that adoption of Managed Service Accounts is still very low and most organizations are still using what I would call "traditional" or "legacy" service accounts.  There are security advantages to using a service account to run SQL Server rather than using Local System. The main advantage being that if an attacker were to exploit SQL in some way, they could theoretically use that exploit to gain unlimited access to the entire system if SQL were running under the System account because the System account has unlimited access to the machine.  But the downside to using a regular user account as a "service account" is that regular user accounts do not, by default, have the permission to update SPNs, not even on themselves.

So let's fix that.

In other similar articles that you'll find on the web, you might see the author advising you to create a security group, putting all your service accounts into that security group, and then delegating the "Write servicePrincipalName" permission to that group for the entire domain.  I would call that practice suboptimal at best.  And by suboptimal, I mean terrible.  The reason it's terrible is because that gives every member of the "Service Accounts" group the ability to write service principal names for themselves and every other account in the domain, which is certainly a security hazard.

So try this as a better alternative:

First, make a "Legacy Service Accounts" OU or otherwise organize your service accounts into an OU.  (You know, right there next to the "Managed Service Accounts" container that you should be using, but I know you won't...)

Legacy Service Accounts OU

Next, open up ADSI Edit and connect to your default naming context.  (Nothing worse than searching for something in AD Users and Computers for 15 minutes before you realize that you can only find what you're looking for with ADSI Edit.)  Right-click on your "Legacy Service Accounts" OU and go to Properties.  Then go to the Security tab.  Click Advanced.  Click Add.  Click "Select a principal" and type in SELF and click OK. Leave the Type on "Allow" and change the Applies to: "Descendant User objects."  Scroll way down and check the box that says "Write servicePrincipalName".

Click OK a couple times to confirm and apply your changes.

Now what you've done is you've configured each account in that OU to inherit the SELF: write servicePrincipalName permission so that it is allowed to write SPNs on itself, but not on other accounts.  You can validate this by viewing the "Effective Access" of the SELF principal (in ADSI Edit - Not in ADUC!) of any given account in that OU.  You'll also notice that SQL Server starts logging success messages regarding SPN registration instead of failure.

Removing Stale Remote Desktop Licensing Service Connection Points From Active Directory

I was doing some work with Remote Desktop Services today, and wanted to share a quick script I used that can keep your Remote Desktop license server service connection points tidy.

If you use Remote Desktop Services, formerly known as Terminal Services, then you have likely needed to deal with license servers and CALs, etc.  If you have a large and/or mature environment, chances are Remote Desktop License servers have come and gone in your environment.  The license servers get upgraded, or migrated, or retired, etc.  In any case, when you remove the Remote Desktop Licensing role from a server, it does not remove the corresponding "service connection point" from Active Directory.  Service Connection Points are simply objects in Active Directory that clients can use to auto-discover services throughout the domain, such as Exchange, SharePoint, Rights Management Services... and Remote Desktop Licensing servers.  When configuring a Remote Desktop Session Host, you might notice that when you go to point the session host to a license server, the list of available license servers is automatically populated from the SCP objects in Active Directory, and will contain stale/defunct license servers if the SCPs were never cleaned up.

So, how do we clean them up?  Simple:

$RDPSCPs = Get-ADObject -Properties * -Filter {(objectClass -EQ 'serviceConnectionPoint') -AND (Name -EQ 'TermServLicensing')}
Foreach ($SCP In $RDPSCPs)
    If ($(Get-ADComputer $SCP.serviceDNSName.Split('.')[0] -Properties LastLogonDate).LastLogonDate -LT (Get-Date).AddDays(-180))
        Write-Warning "Deleting Remote Desktop Licensing Service Connection Point for the stale server: $($SCP.serviceDNSName.Split('.')[0])."
        # Add the -Confirm:$False parameter to the line below if you do not want to be prompted for confirmation.
        Remove-ADObject $SCP.ObjectGUID

We simply find all the objects in Active Directory that are of the object class "serviceConnectionPoint" and with a name of "TermServLicensing."  That's important because there are many other types of SCPs other than just RDP License servers.  Then for each one of the SCPs found, we see if the server it refers to has not logged on to the domain in over 180 days.  If it has not, then we delete the SCP.

Powershell Code That Literally Writes Itself - Automaception

I amused myself with Powershell today and thought I'd share. I might have also named today's post, "Write-Host does have a use after all!"

Today, I had the task of synchronizing the country attributes of thousands of users from a non-Microsoft LDAP server into multiple Active Directories.  This non-Microsoft LDAP server stored the country attribute ("c" in LDAP parlance) of each user as an ISO 3166 Alpha-3 three-letter abbreviation.  I wanted to convert that into the ISO 3166 alpha-2 two-letter notation before I imported it into Active Directory, as well as fill out the rest of the country-related attributes at the same time.

As most AD administrators know, when you want to programmatically set a user's country, you have to make the change in three different places if you want to be thorough.  You have to change co (Text-Country,) c (Country-Name,) and countryCode (Country-Code.)  Those three fields are a free-form text entry, an ISO-3166 A2 or A3 abbreviation, and a numeric value, respectively.

So the first thing I do is track down the ISO 3166 list.  It looks like this:

AALAND ISLANDS                                  AX      ALA     248
AFGHANISTAN                                     AF      AFG     004
ALBANIA                                         AL      ALB     008
ALGERIA                                         DZ      DZA     012

And on and on... for 240 countries.

I was thinking I'd want a Switch statement in my script... the idea is that I Switch($x) where $x is the three-letter country abbreviation that came from the LDAP server. Visions flashed through my mind of me staying up all night writing this monotonous switch block with 240 cases in it.  And then I thought, "Hey, Powershell is the most powerful automation framework there is. Surely I can use it to automate the automation!"  And so I set out to have my Powershell script literally write itself.

First, save that ISO 3166 country code list to a text file. That the list is already in a fixed-width format is going to make this extremely simple.

$Countries = Get-Content C:\Users\Ryan\Desktop\3166.txt

Write-Host "Switch (`$Transaction.NewValue.ToUpper().Trim())" #E.g. 'USA' or  'BEL'
Write-Host "{"
Foreach ($Line In $Countries)
    [String]$CountryCode = $Line[64] + $Line[65] + $Line[66]
    [String]$ThreeLetter = $Line[56] + $Line[57] + $Line[58]
    [String]$TwoLetter   = $Line[48] + $Line[49]
    [String]$FreeForm    = $Line.Substring(0, 46).Trim().Replace("'", $Null)

    Write-Host "    '$ThreeLetter'"
    Write-Host "    {"
    Write-Host "        Set-ADUser -Identity `$Transaction.ObjectGUID -Replace @{ countryCode = $CountryCode } -ErrorAction Stop"
    Write-Host "        Set-ADUser -Identity `$Transaction.ObjectGUID -Replace @{ co = '$FreeForm' } -ErrorAction Stop"
    Write-Host "        Set-ADUser -Identity `$Transaction.ObjectGUID -Replace @{ c = '$TwoLetter' } -ErrorAction Stop"
    Write-Host "    }"
Write-Host "    Default { Write-Error `"`$(`$Transaction.NewValue) was not recognized as a country.`" }"
Write-Host "}"

When I ran the above script, it printed out all the code for my massive switch block, so that all I had to do was copy the text out of the Powershell window, and paste it into the middle of the script I was working on.  It came out looking like this:

Switch ($Transaction.NewValue.ToUpper().Trim()) #E.g. 'USA' or  'BEL'
        Set-ADUser -Identity $Transaction.ObjectGUID -Replace @{ countryCode = 248 } -ErrorAction Stop
        Set-ADUser -Identity $Transaction.ObjectGUID -Replace @{ co = 'AALAND ISLANDS' } -ErrorAction Stop
        Set-ADUser -Identity $Transaction.ObjectGUID -Replace @{ c = 'AX' } -ErrorAction Stop
    # ... 238 more countries ...
        Set-ADUser -Identity $Transaction.ObjectGUID -Replace @{ countryCode = 716 } -ErrorAction Stop
        Set-ADUser -Identity $Transaction.ObjectGUID -Replace @{ co = 'ZIMBABWE' } -ErrorAction Stop
        Set-ADUser -Identity $Transaction.ObjectGUID -Replace @{ c = 'ZW' } -ErrorAction Stop
    Default { Write-Error "$($Transaction.NewValue) was not recognized as a country." }

And there we have it.  Now I don't have any employees from Burkina Faso, but it's nice to know that I'd be able to classify them if I did.  Now with all the time I saved myself from having to type all that out by hand, I figured I'd write a blog entry about it.