18

Searching the web, I found 2 scripts that are able to change the owner of files and folders. When testing this, it functions perfectly in PowerShell 1.0. Now I'm trying to combine both so they work recursively, because we have folders with over 500 sub directories and files in them. And it's a tremendous job to do..

We want to:

  • Run one script on \\server\C$\Folder (without using external tools)
  • to change the owner of all files and subfolders to BUILTIN\Administrators

The problem:

  • Each script only works for 1 file or 1 folder. How can this be combined in one script so it does all the subfoldes and files all together? Putting it in 2 different functions maybe and loop through it or..

Script1 : Change FILE owner to Admin

$File = "\\server\c$\Users\dir\Downloads\Target\TargetFile.txt"
$Account = New-Object System.Security.Principal.NTAccount("BUILTIN\Administrators")
$FileSecurity = new-object System.Security.AccessControl.FileSecurity
$FileSecurity.SetOwner($Account)
[System.IO.File]::SetAccessControl($File, $FileSecurity)

Script2 : Change FOLDER owner to Admin

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

 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 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
$Folder = Get-Item "C:\Users\dir\Downloads\Target"
[void][TokenManipulator]::AddPrivilege("SeRestorePrivilege") 
[void][TokenManipulator]::AddPrivilege("SeBackupPrivilege") 
[void][TokenManipulator]::AddPrivilege("SeTakeOwnershipPrivilege") 
$NewOwnerACL = New-Object System.Security.AccessControl.DirectorySecurity
$Admin = New-Object System.Security.Principal.NTAccount("BUILTIN\Administrators")
$NewOwnerACL.SetOwner($Admin)
$Folder.SetAccessControl($NewOwnerACL)
DarkLite1
  • 13,637
  • 40
  • 117
  • 214
  • I recommend doing this manually. In Windows 8 you can go into the folder properties, security tab, Advanced button, "Change" owner link, supply a new owner and hit ok, check the check box "Replace owner on subcontainers and objects", hit Apply. This is alot faster and safer than trying to use PowerShell. – Ronald Oct 01 '16 at 02:00
  • 1
    Running cmd.exe commands worked for me as a simpler solution. `takeown.exe /F $dest /R` to set my current user as owner in all files and subdirectories, and `icacls $dest /t /grant "Everyone:(F)"` to give full access to Everyone. `$dest` variable had destination directory path. Powershell was run as admin. This works great for local folders , eg: `c:\myfolder` but I have not tested them for remote server paths `\\server\C$\Folder` – Sahil Singh Sep 19 '21 at 17:14

3 Answers3

23

You can use the SetOwner() method for folders, just like for files.

# Define the owner account/group
$Account = New-Object -TypeName System.Security.Principal.NTAccount -ArgumentList 'BUILTIN\Administrators';

# Get a list of folders and files
$ItemList = Get-ChildItem -Path c:\test -Recurse;

# Iterate over files/folders
foreach ($Item in $ItemList) {
    $Acl = $null; # Reset the $Acl variable to $null
    $Acl = Get-Acl -Path $Item.FullName; # Get the ACL from the item
    $Acl.SetOwner($Account); # Update the in-memory ACL
    Set-Acl -Path $Item.FullName -AclObject $Acl;  # Set the updated ACL on the target item
}
  • This doesn't work, because PowerShell on it's own is not able to set the owner because of the lost permissions. That's why I need to use the scripts I found above. I tried your suggestion and it errors out with: "Set-Acl : Attempted to perform an unauthorized operation. At .. Set-Acl <<<< -Path $Item.FullName -AclObject $Acl: #Set the updated ACL on the target item + CatgegoryInfo: PermissionDenied..' It would be nice if I could feed both scripts with files and folders to do it's thing. That would be ideal. – DarkLite1 Apr 11 '14 at 06:48
  • 1
    This doesn't work for me either - `Attempted to perform an unauthorized operation` – kaybee99 Jul 29 '15 at 14:28
  • 1
    It does work, the reason some people get access denied is because the PowerShell command was not running as administrator but trying to work with "BUILTIN\Administrator" which is blocked by Windows UAC. Try it with a normal account and it will work. – Tony Wall Nov 17 '16 at 16:51
  • Works perfectly for me, thanks! (Windows 10, elevated Powershell console) – maoizm Jun 05 '17 at 04:30
  • It does indeed work under elevated prompt everywhere. Make sure you use `Set-Acl` and not something like `(Get-Item $Path).SetAccessControl($Acl)` – Programmierus Sep 20 '19 at 09:46
  • It does not work for RoamingUserProfile folders. Even with an elevated commad prompt. – M46 Feb 22 '21 at 17:31
  • Your Security software may be preventing you modifying ACLs. Also, RoamingUserProfile Folders may have different administrators on them. So you will need to assign the permission take ownership to the folder/parent folder. – Keybonesabi Mar 31 '21 at 18:49
10

After a week of playing around with PowerShell, I found the answer to my own question:

$Target = "\\domain.net\myFolder"
$TempFolder = 'C:\TempFolder'
$TempFile = 'C:\TempFile'

#region Load super powers
$AdjustTokenPrivileges = @"
using System;
using System.Runtime.InteropServices;

 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 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
[void][TokenManipulator]::AddPrivilege("SeRestorePrivilege") 
[void][TokenManipulator]::AddPrivilege("SeBackupPrivilege") 
[void][TokenManipulator]::AddPrivilege("SeTakeOwnershipPrivilege") 
#endregion

$BuiltinAdmin = New-Object System.Security.Principal.NTAccount("BUILTIN\Administrators")
$BuiltinAdminFullControlAcl = New-Object System.Security.AccessControl.FileSystemAccessRule($BuiltinAdmin,"FullControl","Allow")

#region Create temp folder with Admin owner and full control
$FolderBuiltinAdminOwnerAcl = New-Object System.Security.AccessControl.DirectorySecurity
$FolderBuiltinAdminOwnerAcl.SetOwner($BuiltinAdmin)

Remove-Item $TempFolder -EA Ignore
New-Item -Type Directory -Path $TempFolder

$TempFolderAcl = Get-Acl -Path $TempFolder
$TempFolderAcl.SetAccessRule($BuiltinAdminFullControlAcl)
#endregion

#region Change folder owners to Admin
$Folders = @(Get-ChildItem -Path $Target -Directory -Recurse)

foreach ($Folder in $Folders) {
    $Folder.SetAccessControl($FolderBuiltinAdminOwnerAcl)
    Set-Acl -Path $Folder -AclObject $TempFolderAcl
}
#endregion

#region Create temp file with Admin owner and full control
$FileBuiltinAdminOwnerAcl = New-Object System.Security.AccessControl.FileSecurity
$FileBuiltinAdminOwnerAcl.SetOwner($BuiltinAdmin)

Remove-Item $TempFile -EA Ignore
New-Item -Type File -Path $TempFile

$TempFileAcl = Get-Acl -Path $TempFile
$TempFileAcl.SetAccessRule($BuiltinAdminFullControlAcl)
#endregion

#region Change file owners to Admin
$Files = @(Get-ChildItem -Path $Target -File -Recurse)

foreach ($File in $Files) {
    $File.SetAccessControl($FileBuiltinAdminOwnerAcl)
    Set-Acl -Path $File -AclObject $TempFileAcl
}
#endregion

#region Clean-up
Remove-Item $TempFile, $TempFolder
#endregion

Thank you all again for your help. Hopefully, someone else can benefit from my PowerShell research. The only think left is making it a bit more verbose, but that's for another day. It does what it needs to do, and that in the most harsh conditions where permissions are really messed up.

DarkLite1
  • 13,637
  • 40
  • 117
  • 214
  • Thank you! I installed Windows on a new drive and had a bunch of permissions jacked up that the built in takeown utility couldn't fix. You are awesome! – Michael DiLeo Dec 10 '14 at 01:31
  • 2
    I was using this solution and I came across the additional information that the `[void][TokenManipulator]::AddPrivilege("SeRestorePrivilege")` is required only to set the owner to someone who is not yourself. I was able to avoid using the `[void][TokenManipulator]::AddPrivilege("SeBackupPrivilege")` altogether. Great answer! – Slogmeister Extraordinaire Mar 23 '17 at 19:53
0

Here is the code that I made for modifying the ownership for a mapped shared drive.

 $whitelist = @("[INSERT FolderName]","[INSERT FolderName2}")  #<------------Insert folder names, assume theyr are in the same directory
    $NewOwner = "[INSERT OWNER-NAME]" #<-------------- Insert New Owner Name
    
    Write-Host -ForegroundColor Cyan "`nSet Directory Ownership `n`n"
    Write-Host -ForegroundColor Yellow "NOTICE: This script may require Administrative Privleges.`n"
    
    $printList = $whitelist -join ", "
    Write-Host "`nTargets: $printList" 
    
    $choice = Read-Host "`n`nSet ownership of targets to $NewOwner (Y/N)"
    
    if ($choice -eq "y" -or $choice -eq "Y"){
        foreach($folder in $whitelist){
    
            #PROGRESS BAR & ERROR COUNT VARIABLES PER FOLDER
            $progress = 0
            $percentComplete = 0
            $errorCount = 0
    
            #INITIATE PROGRESS BAR PER FOLDER
            Write-Progress -Activity "Loading $folder" -Status "Progress: $percentComplete%" -PercentComplete $percentComplete
    
    <#MODIFY#>  if(Test-Path -Path \\PARENT-FOLDER\$folder -PathType Container){   #MODIFY THIS ALSO TO MAKE SURE THE PATH IS CORRECT
    
    
    
                $newOwnerIdentity = New-Object System.Security.Principal.NTAccount("$NewOwner")
    
                try{
    <#MODIFY#>  $path = "\\PARENT-FOLDER\$folder"  #MODIFY THIS ALSO TO MAKE SURE THE PATH IS CORRECT
                $parentACL = Get-Acl -Path $path
                $parentACL.SetOwner($newOwnerIdentity)
                Set-Acl -Path $path -AclObject $parentACL
                }
                catch [Exception]{
                        Write-Host "Parent $folder was skipped"
                        $errorCount++
    
                } 
                
    
                $items = Get-ChildItem -Path $path -Recurse -ErrorAction SilentlyContinue
                $itemCount = $items.Count
    
                
                Write-Host "# of $folder Items: $itemCount"
                Write-Host -NoNewLine "Setting $folder Ownership to -> $NewOwner.`n"
    
               
    
                #START RECURSIVE PROCEDURE ON CHILD FILES/FOLDERS
                if($itemCount -gt 0)
                {
                foreach ($item in $items){
                    try{
                        $progress++
                        $percentComplete = ($progress / $itemCount) * 100
                        Write-Progress -Activity "Changing directory ownership of '$folder' to $NewOwner " -Status "Progress: $percentComplete%" -PercentComplete $percentComplete
                        $itemACL = null
                        $itemACL = Get-Acl -Path $item.FullName -ErrorAction SilentlyContinue
                        $itemACL.SetOwner($newOwnerIdentity)
                        Set-Acl -Path $item.FullName -AclObject $itemACL -ErrorAction SilentlyContinue
    
                        }
                    catch [Exception]{
                        $errorCount++
                        #For error tracking
                        #$fileName = $item.FullName
                        #Write-Host -NoNewLine "$fileName caused an error occured:" 
                        #Write-Host -ForegroundColor Yellow " $($_.Exception.Message)"
                    } 
    
                }
                $errorPercent = ($errorCount/$itemCount) *100
                Write-Host "# of files skipped: $errorCount"
                Write-Host "Completed."
              }
              else{
              Write-Host "$folder is empty"
            }
            
        }
        else{
            Write-Host "$folder does not exist, please check spelling." #RESPONSE IF "White-listed" FOLDER DOES NOT EXIST, Likely spelling error.
    }
    
    }
    }
    <#MODIFY#>Set-Location \\QS-FS01\IT\Other\OwnershipScript #Return to Script Directory for convenience
    Write-Host "`nCompleted all Tasks"
    [System.Console]::Read() | Out-Null

Since there were only a few files that needed to be modified inside the folder, I created a white-list string array list containing each folders exact names.

It throws all exceptions and keeps count of any errors that may occur within the folder for you to dissect. You can also un-comment the following lines to get the exact location and error of the file.

        $errorCount++
        #For error tracking
        #$fileName = $item.FullName
        #Write-Host -NoNewLine "$fileName caused an error occured:" 
        #Write-Host -ForegroundColor Yellow "$($_.Exception.Message)"

I pride myself in my UI so I added some color text as well.