I wrote my first PowerShell script this morning; it wasn’t something I’d planned to do today — or for that matter, at any point in the near future — because while I have nothing against PowerShell, I really haven’t had enough interest in it to make spending time with it a priority.
Over the weekend, though, I migrated a couple of CIFS shares from one server to another, and while I’d copied ACLs on all of the files and subdirectories, the permissions on the top-level folders housing each shares weren’t set correctly. Once users came in this morning, they were able to access their existing files, but not browse the shares. One of my coworkers beat me in to work today, and due to a difference in the default behavior of Vista and older versions of Windows, he managed to clobber all of the permissions on the file shares, replacing them with a single set of ACLs for all files in each share.
Once I arrived, we kicked back and forth a couple of ideas on how best to fix the problem without disrupting the users’ work, and finally decided that my best bet was to write a script to traverse the original filesystems, check the ACLs on each file/directory, and apply those to any existing files in the new filesystems that had the same name. I was vaguely familiar with using perl and setacl.exe to do this type of thing, and wasn’t looking forward to the headache, so we started looking at alternatives, and PowerShell came up as an option; its Get-Acl and Set-Acl cmdlets (offtopic: who the hell thought “cmdlet” was a good name!?) are naturally suited to duplicating ACLs. The only problem? My aforementioned unfamiliarity with PowerShell.
Actually, it turns out that I had another problem — PowerShell wasn’t even installed on the machine that I was using to connect to all of the relevant shares. After installing it — along with its requisite specific version of .Net Framework — I was ready to go… and had no idea how to start.
I won’t bore you with the details of how I figured out all the little bits of syntax that went into making my script, other than to say that Google yet again saved my bacon. I wasn’t able to find a script that did exactly what I wanted, but since walking though file trees and setting ACLs are both fairly common tasks, it wasn’t too hard to come up with this first version of the ‘tree-copy-acls.ps1′ script:
# get paths
$src = $args[0]
$dest = $args[1]
# walk the source file tree and copy acls to files in dest
Dir $src -recurse | ForEach-Object {
$path = $_.FullName.Replace($src, $dest)
get-acl $_.FullName | set-acl -path $path
}
invoking this as “& .\tree-copy-acls.ps1 <SOURCE> <DEST>” spewed out errors on nearly every file — changing the ownership of the files is part of the set-acl process, and by default your choices for the new owner are limited to the user account making the change and the “Administrators” group. Processes running with the “SeRestorePrivilege” security token, however, have no such limitation. Looking for a way to get myself of one of these tokens, I headed back to Google, and discovered the “PowerShell Community Extensions” or PSCX, a set of PowerShell helpers that allows (among many, many other things) a script — running with appropriate access, of course — to manipulate security tokens. Evidently, this specific task is one that PSCX gets used for quite a bit, because their website states that they’re planning to add a one-liner that allows access to modify ownership; the process for the current version, however, isn’t too bad:
$SeRestorePrivilege = New-Object -typename \
PSCX.Interop.TokenPrivilege -argumentlist \
“SeRestorePrivilege”, $true
Set-Privilege $SeRestorePrivilege
Note: I have no idea whether the “\” convention I’ve used above to note line continuation works in PowerShell — so unless you’re more experienced with PowerShell than I and know the answer, just make sure that the first three lines of the above snippet are actually a single line, with no “\” characters in it.
Installing PSCX and adding this into the script made everything work, so ‘tree-copy-acls.ps1′ version 0.2 looks like this:
# get paths
$src = $args[0]
$dest = $args[1]
# allow script to set ownership
$SeRestorePrivilege = New-Object -typename \ PSCX.Interop.TokenPrivilege -argumentlist \
“SeRestorePrivilege”, $true
Set-Privilege $SeRestorePrivilege
# walk the source file tree and copy acls to files in dest
Dir $src -recurse | ForEach-Object {
$path = $_.FullName.Replace($src, $dest)
get-acl $_.FullName | set-acl -path $path
}
Obviously, there are a few tweaks that could make the script cleaner — checking for proper input, printing a usage message in case of invalid arguments, and sanity checking each file during the ForEach loop to avoid trying to set ACLs on nonexistent files in the destination tree would make the script a little more user friendly, but these should be fairly straightforward tasks for another day.
