Friday, August 3, 2012

Taking ownership of DCOM registry objects using PowerShell

Recently I’ve installed the latest SharePoint Server 2013 Preview bits and found that there are still the same DCOM Local Activation issues like in Office SharePoint Server 2007 and SharePoint Server 2010. Any SharePoint skilled IT professional knows how to solve those issues using Component Services snap-in, and there is also number of blog posts describing how to fix the System log error with Event ID 10016 on the Windows server 2008 R2 and later operating systems.

All of those blog posts and official support articles provide guidance on how to make the permission changes in RegEdit and Component Services snap-in manually. I wanted to make this procedure as automated as possible for following few reasons:
  • Need of automated SharePoint Server 2013 end‑to‑end installation testing.
  • The traditional approach for fixing Event ID 10016 errors has too many manual steps on each server in the farm.
  • SharePoint Server 2013 seems to fire those errors not just for IIS WAMREG admin Service DCOM object.
It is questionable whether it is good practice to fix those errors or not. Searching the Internet you can find some articles explaining that taking ownership of the DCOM registry object and granting the farm account with Local Activation permission on that object is breaking the Least Privilege Principle. On the other hand you never know what the dependencies are in such complex ecosystem, like SharePoint is, and what other issues you can face if you don’t give to SharePoint what it requests. I rather have clean event log and believe, that there are usually also other security sub‑systems in the IT environment protecting the farm account from being misused. If those security sub‑systems fail, it is already too late.

This first blog post is focused on how to automate taking ownership and modify permissions on registry object using PowerShell. The later post will describe how to modify the DCOM Local Activation permission using PowerShell and WMI.

The error with Event ID 10016 appears in the System event log within few hours after completing the SharePoint Server 2013 configuration on a server in the farm. This error emerges in two flavors referring to the DCOM object {61738644‑F196‑11D0‑9953‑00C04FD919C1}, which is the IIS WAMREG admin Service object, and also the {000C101C‑0000‑0000‑C000‑000000000046} object. The {000C101C‑0000‑0000‑C000‑000000000046} DCOM object is related to the MSIServer service. In both cases the error description states that the farm account has not been granted with the Local Activation permission on the above mentioned objects.

Note: You may find out that in the Application log are multiple warnings with Event ID 1015 related to MSI Installer. These warnings are generated by Product Version Job timer job checking the versions of the installed SharePoint components. Granting the farm account with the Local Activation permission on the MSIServer service DCOM object does not remove the Event ID 1015 warnings from the Application log. This blog post intention is not to repeat what was already written somewhere else, but if you are interested in more details on this topic, you should look at the blog posts by Tristan Watkins, which are referred at the end of this article.

Before we can grant the farm account with Local Activation permission on a DCOM object, we have to take ownership of this object and get rights to be able to modify configuration of this object. The current process, under which is running the PowerShell session, does not have privilege to take ownership and therefore you get “Requested registry access is not allowed.” error, if you try to modify the DCOM object related registry object ACL without elevating the privileges.

To elevate the current process to enable it with take ownership privilege represented by the “SeTakeOwnershipPrivilege” constant, you need to execute following code, which is based on the code from the Lee Holmes post Adjusting Token Privileges in PowerShell.
function Enable-ProcessPrivilege {
param(
[ValidateSet(
"SeAssignPrimaryTokenPrivilege", "SeAuditPrivilege", "SeBackupPrivilege",
"SeChangeNotifyPrivilege", "SeCreateGlobalPrivilege", "SeCreatePagefilePrivilege",
"SeCreatePermanentPrivilege", "SeCreateSymbolicLinkPrivilege",
"SeCreateTokenPrivilege", "SeDebugPrivilege", "SeEnableDelegationPrivilege",
"SeImpersonatePrivilege", "SeIncreaseBasePriorityPrivilege",
"SeIncreaseQuotaPrivilege", "SeIncreaseWorkingSetPrivilege", "SeLoadDriverPrivilege",
"SeLockMemoryPrivilege", "SeMachineAccountPrivilege", "SeManageVolumePrivilege",
"SeProfileSingleProcessPrivilege", "SeRelabelPrivilege", "SeRemoteShutdownPrivilege",
"SeRestorePrivilege", "SeSecurityPrivilege", "SeShutdownPrivilege",
"SeSyncAgentPrivilege", "SeSystemEnvironmentPrivilege", "SeSystemProfilePrivilege",
"SeSystemtimePrivilege", "SeTakeOwnershipPrivilege", "SeTcbPrivilege",
"SeTimeZonePrivilege", "SeTrustedCredManAccessPrivilege", "SeUndockPrivilege",
"SeUnsolicitedInputPrivilege")]
$Privilege,
$ProcessId = $pid,
[Switch] $Disable
)

$definition = @'
using System;
using System.Runtime.InteropServices;

public class AdjPriv
{
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall,ref
TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool LookupPrivilegeValue(string host, string name, ref long
pluid);
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
}
internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
internal const int TOKEN_QUERY = 0x00000008;
internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
public static bool EnablePrivilege(long processHandle, string privilege, bool disable)
{
bool retVal;
TokPriv1Luid tp;
IntPtr hproc = new IntPtr(processHandle);
IntPtr htok = IntPtr.Zero;
retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
tp.Count = 1;
tp.Luid = 0;
if(disable)
{
tp.Attr = SE_PRIVILEGE_DISABLED;
}
else
{
tp.Attr = SE_PRIVILEGE_ENABLED;
}
retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
return retVal;
}
}
'@

$processHandle = (Get-Process -id $ProcessId).Handle
$type = Add-Type $definition -PassThru
$type[0]::EnablePrivilege($processHandle, $Privilege, $Disable)
}

Enable-ProcessPrivilege -Privilege SeTakeOwnershipPrivilege

Once this is done and you get no error, the current process should have enough privileges to take the registry objects ownership and modify them. The registry objects, we want to modify, are located in the “HKEY_CURRENT_ROOT \AppID\” path. The steps we need to automate are following:
  • Get instance of the registry object with specifying the operation we want to apply on it. (TakeOwnership)
  • Get copy of ACL for this object. Set the owner for the object in its ACL copy. (In our case we set the BUILTIN\Administrators group as the owner.)
  • Apply the modified ACL to the registry object instance.
$key = [Microsoft.Win32.Registry]::ClassesRoot.OpenSubKey($keyPath, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,[System.Security.AccessControl.RegistryRights]::TakeOwnership)
$acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::None)
$acl.SetOwner([System.Security.Principal.NTAccount]$adminPrincipal)
$key.SetAccessControl($acl)
$key.Close()

Now we should have the BUILTIN\Administrators group set as the owner of the desired registry object. Presumption for success in this procedure is to execute it logged in with account, which is member of the local Administrators group. You could potentially set the ownership directly to your current account, but set it to a group is more flexible. The next steps will give the BUILTIN\Administrators group Full Control permission on the same registry object we set the ownership for:
  • Get instance of the registry object with specifying the operation we want to apply on it. (ChangePermissions)
  • Get copy of ACL for this object.
  • Define the access rule for the desired account. (In our case we set Full Control for the BUILTIN\Administrators.)
  • Set the access rule on the ACL object.
  • Apply the modified ACL to the registry object instance.
$key = [Microsoft.Win32.Registry]::ClassesRoot.OpenSubKey($keyPath,[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,[System.Security.AccessControl.RegistryRights]::ChangePermissions)
$acl = $key.GetAccessControl()
$rule = New-Object System.Security.AccessControl.RegistryAccessRule ($adminPrincipal,"FullControl","ContainerInherit","None","Allow")
$acl.SetAccessRule($rule)
$key.SetAccessControl($acl)
$key.Close()

Done! This part of the code is based on the examples from Tome Tanasovski post Controlling Registry ACL Permissions with Powershell.

The full script can be downloaded from this link EnableDCOMSecuritySettings.ps1.zip. If you look at the full script content you will notice that I’ve created an array for the registry object GUIDs, and loop through this array to apply the new permissions to each item. You can easily add more GUIDs to this array and apply the permissions to your new items, if needed.

We can check if the permissions were added to the registry objects by using “Get-Acl” cmdlet. Before we refer to the above mentioned registry objects in this cmdlet, we can create a new PowerShell drive for the “HKEY_CURRENT_ROOT” registry root, which is not available by default. I suggest naming the new drive “HKCR” to keep the same convention with the existing drives “HKLM” and “HKCU”.
New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT

The actual check can be done with simple code like this.
Get-Acl "HKCR:\AppID\{000C101C-0000-0000-C000-000000000046}" | Format-List

The result would look like this example:


We may require revert the registry object permissions to the original settings. Automation of this task is possible only partially, because I cannot leverage my current PowerShell session process to set the ownership for other than current account or the group in which my current account is member. In this case I cannot make the “NT SERVICE\TrustedInstaller” owner of the registry objects other way, than manually using RegEdit tool.

Here is the code for granting the local Administrators group with Read Only permission to the registry object.
$keyPath = "AppID\{000C101C-0000-0000-C000-000000000046}"
$key = [Microsoft.Win32.Registry]::ClassesRoot.OpenSubKey($keyPath,[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,[System.Security.AccessControl.RegistryRights]::ChangePermissions)
$acl = $key.GetAccessControl()
$rule = New-Object System.Security.AccessControl.RegistryAccessRule ("BUILTIN\Administrators","ReadKey","ContainerInherit","None","Allow")
$acl.SetAccessRule($rule)
$key.SetAccessControl($acl)
$key.Close()

Following these steps we can change back the ownership:
  • Open the RegEdit and navigate to the desired registry object under the “HKEY_CURRENT_ROOT\AppID\”.
  • Right click the registry object and select “Permissions…” option from the context menu.
  • Click the “Advanced” button and select the “Owner” tab on the “Advanced Security Settings for {GUID}” window.
  • Click “Other user or groups…” button.
  • Click “Locations…” on the “Select User, Computer, Service Account, or Group” window and pick the local machine from the location tree.
  • Click “OK”.
  • Type “NT SERVICE\TrustedInstaller” into the “Enter the object name to select (examples):” field, and click “Check Names” button.
  • Click “OK” to close the security principal selection window and click “Apply” to set the ownership.
  • Click twice “OK” to close the remaining window chain.
  • Close the RegEdit if you don’t plan to use it for other configuration at this moment.
We can use the following code to remove the elevated privilege from the current process running the PowerShell session:
Enable-ProcessPrivilege -Privilege SeTakeOwnershipPrivilege -Disable

References:
DCOM Security for SharePoint Administrators
Product Version Job: DCOM 10016 strikes again
Inside Manage Patch Status
Testing Manage Patch Status
Fix the SharePoint DCOM 10016 error on Windows Server 2008 R2

3 comments:

  1. Why can't you use powershell to revert the ownership back (keeping the ACL controls for Administrators) via PowerShell? Why did you revert to steps in RegEdit at the end?

    ReplyDelete
    Replies
    1. Thank you for the comment The Doctor What. In this post content you can find the answer to your question, which is "Automation of this task is possible only partially, because I cannot leverage my current PowerShell session process to set the ownership for other than current account or the group in which my current account is member. In this case I cannot make the “NT SERVICE\TrustedInstaller” owner of the registry objects other way, than manually using RegEdit tool."

      Delete
    2. Is there any harm leaving the owner as something other than TrustedInstaller?

      Delete