The Get-ACL
cmdlet can be used to view and modify Access Control Lists in PowerShell. It works with any object you have a PSProvider for – be it files, folders, registry keys, Active Directory objects, and so on.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
PS> Get-Acl .\temp.txt Directory: C:\ Path Owner Access ---- ----- ------ temp.txt WALLY\rakhesh NT AUTHORITY\SYSTEM Allow FullC... # try with a registry key PS> Get-Acl HKLM:\Software Path Owner Access ---- ----- ------ Microsoft.PowerShell.Core\Registr... NT AUTHORITY\SYSTEM BUILTIN\Users Allow ReadKey... # import AD module and try with an AD user; prefix the AD:\ prvoider name before the distringuished name of the object PS> import-module ActiveDirectory PS> Get-Acl "AD:\$((Get-ADUser rakhesh).DistinguishedName)" Path Owner Access ---- ----- ------ ActiveDirectory:://RootDSE/CN=Sasidh... DOMAINNAME\Domain Admins NT AUTHORITY\Authenticated Users All... |
Viewing and setting the Owner
To find the owner of an object there are couple of ways. The easy way is to use the Owner
property:
1 2 |
PS> (Get-Acl .\temp.txt).Owner WALLY\rakhesh |
But there’s a GetOwner()
method too. This takes as input the type of output you want. The input must be of class System.Security.Principal.IdentityReference
. There are two such class: System.Security.Principal.NTAccount
gives you the user account while System.Security.Principal.SecurityIdentifier
gives you the SID. These two classes are worth remembering when you want to convert between a user account and SID or vice versa.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# view the definition PS> (get-acl .\temp.txt).GetOwner OverloadDefinitions ------------------- System.Security.Principal.IdentityReference GetOwner(type targetType) # get the user account name PS> (get-acl .\temp.txt).GetOwner([System.Security.Principal.NTAccount]) Value ----- WALLY\rakhesh # get the SID PS> (get-acl .\temp.txt).GetOwner([System.Security.Principal.SecurityIdentifier]) BinaryLength AccountDomainSid Value ------------ ---------------- ----- 28 S-1-5-21-3165787769-3955403704-19... S-1-5-21-3165787769-3955403704-1... |
You can set the owner too using the SetOwner()
method.
1 2 3 4 5 6 |
# view the definition; note this too takes an IdentityReference object PS> (Get-Acl .\temp.txt).SetOwner OverloadDefinitions ------------------- void SetOwner(System.Security.Principal.IdentityReference identity) |
Simply doing a SetOwner() won’t do the trick though because what Get-Acl
returns is an ACL object and so all we are really doing is setting the ownership info in that object but not applying it back to the file or folder whose ACL it is.
1 2 3 4 |
# notice how I specify the username by typecasting it to the correct IdentityReference class PS> (Get-Acl .\temp.txt).SetOwner([System.Security.Principal.NTAccount]"WALLY\rakhesh") PS> (Get-Acl .\temp.txt).Owner WALLY\Guest |
The correct way would be thus:
1 2 3 4 5 |
PS> $acl = Get-Acl .\temp.txt PS> $acl.SetOwner([System.Security.Principal.NTAccount]"WALLY\Rakhesh") PS> Set-Acl .\temp.txt $acl PS> (Get-Acl .\temp.txt).Owner WALLY\Rakhesh |
Setting the owner – gotchas
For reasons I don’t know, the following doesn’t work:
1 |
PS> (Get-Acl .\temp.txt).SetOwner([System.Security.Principal.NTAccount]"WALLY\Rakhesh") | Set-Acl .\temp.txt |
It could be because of this:
1 2 3 4 5 6 7 |
PS> $acl = Get-Acl .\temp.txt PS> $acl ... # you get some output PS> $acl = (Get-Acl .\temp.txt).SetOwner([System.Security.Principal.NTAccount]"WALLY\rakhesh") PS> $acl # nothing! |
When you set the owner directly, the object is empty. It could be because the SetOwner
method doesn’t return anything so $acl
is assigned nothing. But when you set $acl
to the ACL and then set its owner, it works.
Another gotcha is that Set-Acl
cannot be set to give ownership to another user. It can only be used to take ownership. Have a look at this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
PS> (Get-Acl .\temp.txt).owner WALLY\rakhesh PS> $acl = (Get-Acl .\temp.txt) # set owner to a test user PS> $acl.SetOwner([System.Security.Principal.NTAccount]"WALLY\tester") # do the actual setting of ACL - fails! PS> Set-Acl .\temp.txt $acl Set-Acl : The security identifier is not allowed to be the owner of this object. At line:1 char:1 + Set-Acl .\temp.txt $acl + ~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (C:\Users\rakhesh\Code\temp.txt:String) [Set-Acl], InvalidOp erationException + FullyQualifiedErrorId : System.InvalidOperationException,Microsoft.PowerShell.Commands.SetAclCommand |
I couldn’t find much but this Hey, Scripting Guy! post briefly mentions the following:
… although you can take ownership of a file using Windows PowerShell, we don’t believe that you can give ownership of a file to someone else. To transfer ownership to another user you’ll need to use the Windows Resource Kit utility Subinacl.exe.
Yet another gotcha is that built-in groups like “Users” and “Administrators” need to be referred to differently:
1 2 3 4 5 6 7 8 9 |
PS> $acl.SetOwner([System.Security.Principal.NTAccount]"WALLY\Users") Exception calling "SetOwner" with "1" argument(s): "Some or all identity references could not be translated." At line:1 char:1 + $acl.SetOwner([System.Security.Principal.NTAccount]"WALLY\Users") + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : IdentityNotMappedException PS> $acl.SetOwner([System.Security.Principal.NTAccount]"BUILTIN\Users") |
I discovered this looking at this page of well known SIDs, finding the SID of the built-in “Users” group, and translating it via PowerShell:
SID: S-1-5-32-545
Name: Users
Description: A built-in group. After the initial installation of the operating system, the only member is the Authenticated Users group. When a computer joins a domain, the Domain Users group is added to the Users group on the computer.
1 2 3 4 5 |
PS> ([System.Security.Principal.SecurityIdentifier]"S-1-5-32-45").Translate([System.Security.Principal.NTAccount]) Value ----- BUILTIN\Users |
In fact, the error Exception calling "SetOwner" with "1" argument(s): "Some or all identity references could not be translated."
is a good way of checking if a user account exists.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# if an account doesn't exist, trying to get its SID will result in an error PS> ([System.Security.Principal.NTAccount]"WALLY\NoUser").Translate([System.Security.Principal.SecurityIdentifier]) Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated." At line:1 char:1 + ([System.Security.Principal.NTAccount]"WALLY\NoUser").Translate([System.Security ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : IdentityNotMappedException # define a function that tests if the user name is valid; this uses a Try/Catch block to check for exception errors # and I cast the output to [void] so successful results don't return anything PS> function Check-User ([string]$User) { $result = $true; try { [void]([System.Security.Principal.NTAccount]$user).Translate([System.Security.Principal.SecurityIdentifier]) } catch { $result = $false }; return $result } PS C:\Users\rakhesh\Code> Check-User -User "WALLY\rakhesh" True PS C:\Users\rakhesh\Code> Check-User -User "WALLY\NoUser" False |
More later!