Thursday, July 29, 2010

Setting the owner on an ACL in Powershell.

I was trying to set the owner of an ACL in powershell using the following code:


PS C:\Users\Johan> $acl = get-acl c:\temp\acltest
PS C:\Users\Johan> $principal = New-Object Security.Principal.NTAccount "$env:computername\ownertestaccount"
PS C:\Users\Johan> $acl.psbase.SetOwner($principal)
PS C:\Users\Johan> set-acl -Path C:\Temp\acltest -AclObject $acl
Set-Acl : The security identifier is not allowed to be the owner of this object.
At line:1 char:8
+ set-acl <<<<  -Path C:\Temp\acltest -AclObject $acl
    + CategoryInfo          : InvalidOperation: (C:\Temp\acltest:String) [Set-Acl], InvalidOperationException
    + FullyQualifiedErrorId : System.InvalidOperationException,Microsoft.PowerShell.Commands.SetAclCommand


PS C:\Users\Johan>


So setting this ACL doesn't work. Why? I am an administrator! OK, what happens when I start the shell with Run As Administrator?... The same result.


So now we go to google and search a bit and I found the following KB article. What it says is that we need to have the rights to do the deed. Hey, not too surprising. By default Administrators and Backup Administrators have the Restore files and directories (SeRestorePrivilege) User Right. To set the owner we need to have this right, but in the scenario above I was executing the code with a account with all this privilege.


The problem here is that the privilege is not enabled in the access token, we need to call a function called AdjustTokenPrivileges() to adjust the current access token of our process. So we need to call this function, pass in a variable saying that we need to enable a privilege and pass in the privilege we want to enable. To call this function we need to do a little bit of PInvoke. I borrowed the base of my code from pinvoke.net.


So here is my little Set-Owner function.



function Set-Owner {
param(
[System.Security.Principal.IdentityReference]$Principal=$(throw "Mandatory parameter -Principal missing."),
$File=$(throw "Mandatory parameter -File missing.")
)
if(-not (Test-Path $file)){
throw "File $file is missing."
}
if($Principal -eq $null){
throw "Principal is NULL"
}


$code = @"
using System;
using System.Runtime.InteropServices;


namespace CosmosKey.Utils
{
public class TokenManipulator
{


[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("kernel32.dll", ExactSpelling = true)]
internal static extern IntPtr GetCurrentProcess();


[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_DISABLED = 0x00000000;
internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
internal const int TOKEN_QUERY = 0x00000008;
internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;


public const string SE_ASSIGNPRIMARYTOKEN_NAME = "SeAssignPrimaryTokenPrivilege";
public const string SE_AUDIT_NAME = "SeAuditPrivilege";
public const string SE_BACKUP_NAME = "SeBackupPrivilege";
public const string SE_CHANGE_NOTIFY_NAME = "SeChangeNotifyPrivilege";
public const string SE_CREATE_GLOBAL_NAME = "SeCreateGlobalPrivilege";
public const string SE_CREATE_PAGEFILE_NAME = "SeCreatePagefilePrivilege";
public const string SE_CREATE_PERMANENT_NAME = "SeCreatePermanentPrivilege";
public const string SE_CREATE_SYMBOLIC_LINK_NAME = "SeCreateSymbolicLinkPrivilege";
public const string SE_CREATE_TOKEN_NAME = "SeCreateTokenPrivilege";
public const string SE_DEBUG_NAME = "SeDebugPrivilege";
public const string SE_ENABLE_DELEGATION_NAME = "SeEnableDelegationPrivilege";
public const string SE_IMPERSONATE_NAME = "SeImpersonatePrivilege";
public const string SE_INC_BASE_PRIORITY_NAME = "SeIncreaseBasePriorityPrivilege";
public const string SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege";
public const string SE_INC_WORKING_SET_NAME = "SeIncreaseWorkingSetPrivilege";
public const string SE_LOAD_DRIVER_NAME = "SeLoadDriverPrivilege";
public const string SE_LOCK_MEMORY_NAME = "SeLockMemoryPrivilege";
public const string SE_MACHINE_ACCOUNT_NAME = "SeMachineAccountPrivilege";
public const string SE_MANAGE_VOLUME_NAME = "SeManageVolumePrivilege";
public const string SE_PROF_SINGLE_PROCESS_NAME = "SeProfileSingleProcessPrivilege";
public const string SE_RELABEL_NAME = "SeRelabelPrivilege";
public const string SE_REMOTE_SHUTDOWN_NAME = "SeRemoteShutdownPrivilege";
public const string SE_RESTORE_NAME = "SeRestorePrivilege";
public const string SE_SECURITY_NAME = "SeSecurityPrivilege";
public const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
public const string SE_SYNC_AGENT_NAME = "SeSyncAgentPrivilege";
public const string SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege";
public const string SE_SYSTEM_PROFILE_NAME = "SeSystemProfilePrivilege";
public const string SE_SYSTEMTIME_NAME = "SeSystemtimePrivilege";
public const string SE_TAKE_OWNERSHIP_NAME = "SeTakeOwnershipPrivilege";
public const string SE_TCB_NAME = "SeTcbPrivilege";
public const string SE_TIME_ZONE_NAME = "SeTimeZonePrivilege";
public const string SE_TRUSTED_CREDMAN_ACCESS_NAME = "SeTrustedCredManAccessPrivilege";
public const string SE_UNDOCK_NAME = "SeUndockPrivilege";
public const string SE_UNSOLICITED_INPUT_NAME = "SeUnsolicitedInputPrivilege";        


public static bool AddPrivilege(string privilege)
{
try
{
bool retVal;
TokPriv1Luid tp;
IntPtr hproc = GetCurrentProcess();
IntPtr htok = IntPtr.Zero;
retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
tp.Count = 1;
tp.Luid = 0;
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;
}
catch (Exception ex)
{
throw ex;
}


}
public static bool RemovePrivilege(string privilege)
{
try
{
bool retVal;
TokPriv1Luid tp;
IntPtr hproc = GetCurrentProcess();
IntPtr htok = IntPtr.Zero;
retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
tp.Count = 1;
tp.Luid = 0;
tp.Attr = SE_PRIVILEGE_DISABLED;
retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
return retVal;
}
catch (Exception ex)
{
throw ex;
}


}
}
}
"@


$errPref = $ErrorActionPreference
$ErrorActionPreference= "silentlycontinue"
$type = [CosmosKey.Utils.TokenManipulator]
$ErrorActionPreference = $errPref
if($type -eq $null){
add-type $code
}
$acl = Get-Acl $File
$acl.psbase.SetOwner($principal)
[void][CosmosKey.Utils.TokenManipulator]::AddPrivilege([CosmosKey.Utils.TokenManipulator]::SE_RESTORE_NAME)
set-acl -Path $File -AclObject $acl -passthru
[void][CosmosKey.Utils.TokenManipulator]::RemovePrivilege([CosmosKey.Utils.TokenManipulator]::SE_RESTORE_NAME)
}




If we run this function then we will see a very different result:

PS C:\Users\Johan> get-acl C:\Temp\acltest





    Directory: C:\Temp




Path                                    Owner                                   Access
----                                    -----                                   ------
acltest                                 MyLaptop\OldTest                         BUILTIN\Administrators Allow  FullCo...




PS C:\Users\Johan> set-owner $(new-object security.principal.ntaccount "$env:computername\NewTest") C:\Temp\acltest




    Directory: C:\Temp




Path                                    Owner                                   Access
----                                    -----                                   ------
acltest                                 MyLaptop\NewTest                         BUILTIN\Administrators Allow  FullCo...




PS C:\Users\Johan>

In the end we got there but it took me some time... :)

17 comments:

  1. Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!

    ReplyDelete
    Replies
    1. I concur.
      Thank u! Thank u! Thank u! Thank u! Thank u!
      Thank u! Thank u! Thank u! Thank u! Thank u!
      Thank u! Thank u! Thank u! Thank u! Thank u!
      Thank u! Thank u! Thank u! Thank u! Thank u!
      Thank u! Thank u! Thank u! Thank u! Thank u!
      Thank u! Thank u! Thank u! Thank u! Thank u!
      Thank u! Thank u! Thank u! Thank u! Thank u!
      Thank u! Thank u! Thank u! Thank u! Thank u!
      Thank u! Thank u! Thank u! Thank u! Thank u!

      Delete
  2. Hi!
    Maybe a dumb question, but how do I convert the code to a CMD-LET?

    ReplyDelete
  3. Brilliant! I am very greatfull!

    ReplyDelete
  4. Many thanks, because it works in standard C# code, too... :-)

    ReplyDelete
  5. I second marcel saying:
    "Maybe a dumb question, but how do I convert the code to a CMD-LET? "

    ReplyDelete
  6. Can this work for subdirectories too?

    ReplyDelete
  7. I found that the original method does work, but only if you use a UNC path instead of the local path. For instance, using the hidden drive share \\computername\c$\temp\acltest as the path.

    See my blog post for my script and sample output.

    http://fixingit.wordpress.com/2011/07/08/set-owner-with-powershell-%E2%80%9Cthe-security-identifier-is-not-allowed-to-be-the-owner-of-this-object%E2%80%9D/

    ReplyDelete
    Replies
    1. Thanks for this comment, i changed my script to the UNC and it worked. No need to complicate things. Thanks.

      Delete
  8. What about using takeown.exe?

    http://technet.microsoft.com/en-us/library/cc753024(WS.10).aspx

    ReplyDelete
  9. This works great as a cmdlet, but no longer works if used as a function inside of a bigger script:

    "2011-09-06 16:54:09 ERROR: Unable to find type [CosmosKey.Utils.TokenManipulator]: make sure that the assembly containing this type is loaded."

    I don't know any VB, so I have no clue why it goes wrong. My guess is it has to do with scoping.

    ReplyDelete
  10. Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!

    ReplyDelete
  11. Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u!
    Thank u! Thank u! Thank u! Thank u! Thank u! And THANKS AGAIN

    ReplyDelete
  12. great, thank you a lot. I don't belive it, but works perfectly.

    ReplyDelete
  13. Very nice blog Johan, and a great example of how to embed C# in a PowerShell script. :-)

    Cheers,
    Jeremy.

    ReplyDelete
  14. This comment has been removed by the author.

    ReplyDelete