Changing Ownership of File or Folder Using PowerShell

While working on a project recently, I needed to find an easy way to take ownership of a profile folder and its subfolders to allow our support staff to either delete the profile or be able to traverse the folder to help troubleshoot issues. Typically, one could use Explorer to find the folder and then take ownership and be done with it. But the goal was to come up with a command line solution that not only worked quickly, but didn’t miss out on a file or folder.

The brief background on this is that roaming profiles sometimes would become inaccessible to our support staff in that only the user account and System would have access to the profile folder and its sub-folders and files. Also, ownership of those objects were by the user account. This created issues with deleting accounts and troubleshooting profile related issues.

Before showing the solution that I came up with, I will run down a list of attempts which never quite met my requirements and why.

Using Takeown.exe

This was actually my initial idea as I allows for recursive actions and lets me specify to grant ownership to Builtin\Administrators. Sure it wasn’t a PowerShell approach, but it met the requirements of what I wanted to do…or so I thought.

image

The first problem is that it is slow. I kicked it off on my own profile (because it is always more fun to test on yourself than others) and found that it would take upwards of 10 minutes vs. the ~2 minute UI approach. Obviously this is an issue if I expect to have this used as part of my project for others to take ownership on profiles which would more than likely have more items than my profile. I still decided to press forward with this and later found the second issue: takeown.exe would not reliably grant ownership completely down the tree of subfolders. This was a huge issue and would not be acceptable with the customer.

Take Ownership using PowerShell and Set-ACL

The next idea was to grab the ACL object of a folder elsewhere in the user’s home directory that had good permissions and then change the owner in that ACL object to ‘Builtin\Administrators” and the apply it to the profile folder.

$ACL = Get-ACL .\smithb
$Group = New-Object System.Security.Principal.NTAccount("Builtin", "Administrators")
$ACL.SetOwner($Group)
Set-Acl -Path .\smithb\profile.v2 -AclObject $ACL

Sounds good, right? Well, not really due to some un-foreseen issues. Because the accounts do not have the proper user rights (seTakeOwnershipPrivilege, SeRestorePrivilege and SeBackupPrivilege), this would fail right away with an ‘Access Denied’ error. Fine, I can add those privileges if needed and continue on from there. Well, it doesn’t quite work that way either because only the directories would propagate these permissions but the files wouldn’t get ownership.

Set-Owner Function

The final thing that I came up with followed a similar idea as my second attempt, but makes sure to allow for recursion and files and folders as well as allowing either ‘Builting\Administrators’ or another account to have ownership of files and folders. To do this I dove into the Win32 API to first allow the account to elevate the tokens that I have mentioned before.

Try {
[void][TokenAdjuster]
} Catch {
$AdjustTokenPrivileges = @"
using System;
using System.Runtime.InteropServices;

public class TokenAdjuster
{
    [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 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;
        }
    }
}
"@
Add-Type $AdjustTokenPrivileges
}

#Activate necessary admin privileges to make changes without NTFS perms
[void][TokenAdjuster]::AddPrivilege("SeRestorePrivilege") #Necessary to set Owner Permissions
[void][TokenAdjuster]::AddPrivilege("SeBackupPrivilege") #Necessary to bypass Traverse Checking
[void][TokenAdjuster]::AddPrivilege("SeTakeOwnershipPrivilege") #Necessary to override FilePermissions

This allows me to traverse the directory tree and set ownership on the files and folders. If I cannot take ownership on a file or folder (because inheritance is not allowed from the parent folder), then it moves up a level to grant Full Control to to parent folder, thus allowing me to take ownership on the folder or file below it.

Process {
    ForEach ($Item in $Path) {
        Write-Verbose "FullName: $Item"
        #The ACL objects do not like being used more than once, so re-create them on the Process block
        $DirOwner = New-Object System.Security.AccessControl.DirectorySecurity
        $DirOwner.SetOwner([System.Security.Principal.NTAccount]$Account)
        $FileOwner = New-Object System.Security.AccessControl.FileSecurity
        $FileOwner.SetOwner([System.Security.Principal.NTAccount]$Account)
        $DirAdminAcl = New-Object System.Security.AccessControl.DirectorySecurity
        $FileAdminAcl = New-Object System.Security.AccessControl.DirectorySecurity
        $AdminACL = New-Object System.Security.AccessControl.FileSystemAccessRule('Builtin\Administrators','FullControl','ContainerInherit,ObjectInherit','InheritOnly','Allow')
        $FileAdminAcl.AddAccessRule($AdminACL)
        $DirAdminAcl.AddAccessRule($AdminACL)
        Try {
            $Item = Get-Item -LiteralPath $Item -Force -ErrorAction Stop
            If (-NOT $Item.PSIsContainer) {
                If ($PSCmdlet.ShouldProcess($Item, 'Set File Owner')) {
                    Try {
                        $Item.SetAccessControl($FileOwner)
                    } Catch {
                        Write-Warning "Couldn't take ownership of $($Item.FullName)! Taking FullControl of $($Item.Directory.FullName)"
                        $Item.Directory.SetAccessControl($FileAdminAcl)
                        $Item.SetAccessControl($FileOwner)
                    }
                }
            } Else {
                If ($PSCmdlet.ShouldProcess($Item, 'Set Directory Owner')) {                        
                    Try {
                        $Item.SetAccessControl($DirOwner)
                    } Catch {
                        Write-Warning "Couldn't take ownership of $($Item.FullName)! Taking FullControl of $($Item.Parent.FullName)"
                        $Item.Parent.SetAccessControl($DirAdminAcl) 
                        $Item.SetAccessControl($DirOwner)
                    }
                }
                If ($Recurse) {
                    [void]$PSBoundParameters.Remove('FullName')
                    Get-ChildItem $Item -Force | Set-Owner @PSBoundParameters
                }
            }
        } Catch {
            Write-Warning "$($Item): $($_.Exception.Message)"
        }
    }
}
End {  
    #Remove priviledges that had been granted
    [void][TokenAdjuster]::RemovePrivilege("SeRestorePrivilege") 
    [void][TokenAdjuster]::RemovePrivilege("SeBackupPrivilege") 
    [void][TokenAdjuster]::RemovePrivilege("SeTakeOwnershipPrivilege")
}

Using this approach, I was able to accurately take ownership on all of the items as well as not facing major slowdown (it was roughly 30 seconds slower than the UI approach). Seemed like a good tradeoff to me.

Here are a couple of examples of the function in action:

Set-Owner -Path .\smithb\profile.v2 -Recurse -Verbose

image

Set-Owner -Path .\smithb\profile.v2 -Recurse -Verbose -Account 'WIN-AECB72JTEV0\proxb'

image

The function is available to download from the following link:

http://gallery.technet.microsoft.com/scriptcenter/Set-Owner-ff4db177

This entry was posted in powershell and tagged , , . Bookmark the permalink.

32 Responses to Changing Ownership of File or Folder Using PowerShell

  1. Ryan B says:

    I’m happy to report that I had the same issue about nothing happening, but after trial-and-error I was able to make it run. First let me say thanks to Boe for doing the heavy lifting for the script and also the clear explanation.

    The syntax that worked for me is:
    . .\Set-Owner.ps1; Set-Owner -Path .\mypath -Recurse -Verbose -Account ‘mydomain\myaccount’

    In the front, there is a dot and a space before the dot backslash, and a semicolon between the ps1 file and the function, specifying which function to be executed from that file. I think PowerShell changed over the years but the actual code works great.

    Before running it, I granted permissions to run the unsigned script with:
    Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

  2. chad says:

    It seems to apply correctly but when I browse the folder it still tells me I need READ rights to view it

    VERBOSE: FullName: \filersvr01\X$\myuser\Downloads
    VERBOSE: Performing the operation “Set Directory Owner” on target “\filersvr01\X$\myuser\Downloads”.

  3. mgb1979 says:

    I’m fixing a big problem on an old EMC Celerra share and this saved me a lot of time today, a lot of time. I looked at a number of options with icacls, takeown and unix command but this work beautifully. Thank you Sir.

  4. Pingback: Powershell Grant User Access To Folder | Liyongbak

  5. Dan Tenenbaum says:

    I’m having the same issue as some others. No output whatsoever, prompt comes back right away. Also, I’m not sure if this will do what I really want, which is to grant ownership to a different user, one who does not have any admin privileges. If not, then I don’t need to bother finding out why it’s not working. Thanks!

    • Patrick Puorro says:

      Dan, Did you ever figure out why it just returns to a prompt without working? Its doing the same thing for me.

      • Dan Tenenbaum says:

        No, I gave up and found that icacls.exe worked well for me.

        • Garrett says:

          I have the same issue of no output. I have tried the script on multiple devices. Anyone have suggestions? I would really love to be able to use this script.

          • Michael L says:

            Copy the entire contents of Set-Owner.ps1 and paste into your Powershell session and hit Enter a couple of times, the Set-Owner can be called directly as a known Function in powershell in your current session.
            Like others I’m using “\servername\c$\foldername” as -Path parameter, it is not always working using c:\foldername, at least not with the -Recurse option.
            However, I’m having trouble taking ownership of many files, but folders seems på be ok.
            This could be files with broken inheritance in security, I can manually fix them, but takes too long.
            But if running Set-Owner script with a User that has elevated administrator rights on the server and also has full access to the files with broken inheritance, then Set-Owner is able to change the owner.

    • rhumborl says:

      I had the same problem. This is due to how the cmdlet is called, in that it is a function inside of a ps1 file. I haven’t worked out how to call it directly from teh PS command line, but adding the call to Set-Owner to the end of SetOwner.ps1 itself worked.

      Set-owner.ps1

      Function Set-Owner {
          ....
      }
      
      Set-Owner -Path C:\Path\To\Own -Recurse -Verbose -Account 'MyDomain\MyUser'
      

      ** Commad Line **

      PS E:\_files\_temp> .\Set-Owner
      
  6. John says:

    Running the following command results in an exception “The security identifier is not allowed to be the owner of this object.”

    Set-Owner -Path C:\Script\Share\user -Account ‘domain\user’ -Recurse

    Changing the owner manually works.

    Any ideas?

    • Mike says:

      run you scripts against the UNC path (eg. \servername\share\directory)- Something blocks the local drives (eg c:\ or d:)

  7. Pingback: How To Load A Custom Function In PowerShell | Remarqable IT

  8. dgeddings says:

    Any way to use this with a source file with the paths in it? I have around a thousand separated user home directories I need to fix and I have all the paths in a csv/txt file. Once I can repair the permissions I can actually do something with all that wasted space.
    Thanks!

  9. Peter J says:

    One gotcha is if you are working in the wrong namespace. I couldn’t get this to work, getting ‘cannot find path’ even though the path was correct. After pulling my hair for a while i noticed the prompt: PS SQLSERVER:>
    A quick set-location C: later everything worked.

  10. adhe says:

    Is it weird to tell you that I love you? Because, right now, in this moment of triumph, I freaking LOVE you.

    Client wanted a user added to a share. Easy enough, right? Unless inheritance is borked and even our domain admin account can’t see the permissions. I tried everything but I was having to go to each and every folder and file that was jacked up and do this process:

    Properties > Security > Advanced > change ownership to Domain\Administrators > OK out > Go back in > continue button > disable inheritance > remove permissions > enable inheritance > OK > OK.

    And this is a law firm that saves everything and it’s all arranged in endless subfolders. So seeing the error list D:\cen….\2013 is NOT helpful because I assure you there are 100 folders named 2013. It was horrible. I tried just taking ownership of each and then trying to push down but that didn’t work.

    Finally, after 2 hours, I looked for a script again and found this. At first all the code made my eyes cross but I’m glad you had it for download. I just had to change your “Builtin” to their domain name, run it, and less than a second later, everything was working. I changed the parent folder’s permissions, got NO error messages, and I can confirm that all of the subfolders and files have the right ownership and permissions.

  11. mahesh1000h says:

    Thanks for nice post and script
    I am trying your script on 2008 R2 PowerShell admin console
    However nothing is happening, I am not getting any error neither message and also nothing is happening to directories
    Not sure where to look for
    OR
    Am I missing any basic step?
    I have done ExecutionPolicy to un restricted
    Any help is highly appreciated please

    Thanks
    Mahesh

  12. Matt Maguire says:

    Just tried using your function, but didn’t get anywhere. The server in questions is 2008 R2 running PowerShell 3.0. Do I need PowerShell 4 to get this to work? Thanks!

    • Matt Maguire says:

      More specifically, after adding the function to my System32 directory I ran ” set-owner.ps1 -path .\userprofile.V2 -recurse -verbose -account ‘domain\myusername'” under the domain admin account. There was no output, just a new Prompt. After logging in as myself to the server and trying to pen the folder, I found I could not browse or take ownership of the file. I presume I left out a necessary step or two. Any thoughts? Thanks!

  13. f1refoxy says:

    hello,
    Thanks for this Post!
    for my needs, I’m happy with the takeown.exe solution for that problem.
    I want to share my script – it changes all folder (testfolders) und subfolders owners to the “Adminisrators” Group.

    Get-ChildItem E:\testfolders |Where-Object {$.PSIsContainer -eq $true} | ForEach-Object {$workpath = $.FullName;Invoke-Expression -Command ‘takeown.exe /F “$workpath” /A /r /D N’ }

  14. DarkLite1 says:

    Thank you Boe, great function! I left a question on the Microsoft Scriptcenter, as I’m having some difficulties with the ‘-Recurse’ option for regular users.

  15. Johnny says:

    Great script, just one question: how can I write the verbose output to log file?

  16. Keith Wade says:

    I tried running your script on our system today and it said “Unable to find type [TokenAdjuster]. Make sure that the assembly that contains this type is loaded.”

    Any ideas as to why this would happen?

    • Boe Prox says:

      Hmm.. I am not sure as it should attempt to load the type and if it fails, then it will compile the C# code that contains the type. I won’t be able to do much investigating for a week or so but can take a look at it when I am available. Until then, have you tried to run through the code in chunks to make sure it is working properly?

      • Keith Wade says:

        I’m not quite sure that I ran your code the right way. Anyways, we just got Icacls working to do our ownership changes instead.

  17. An opportunity presented itself to test your script out 😉

Leave a reply to Keith Wade Cancel reply