Copy directory structure with access permissions

by i255d at 2013-02-06 11:22:52

This seems like a fun one. I have a user share that has a folder for each one of my users that only they have access to. When I run Get-Childitem -Path \Server1\users -directory, I get a list of all of the user directories. I want to make another set of these directories on a different server, but have the same access controls. Only the individual user will have access to the folder. I don’t want to transfer any data. Here is an example of the test I did to copy just the directories:

Get-Childitem -Path \Server1\users -Directory -Filter d* | Copy-Item -Destination \Server2\share

The question is how do I get the same list of directories with the same access permissions for the individual users?
by poshoholic at 2013-02-06 11:32:29
Just asking the obvious question first: have you looked at Get-Acl and Set-Acl?
by i255d at 2013-02-06 12:21:56
No, no I haven’t. I just read through the full help of both. I am guessing that I would create two scripts, one to copy the directories over and a second one to copy the access control permissions? Does that sound right? Or, would I use the name of the folder, it is the same as the username, and foreach that string into set-acl in some way to make that person the owner and give them the level of access I desire. Never user Get or Set-ACL, any examples or a place to start testing would be greatly appreciated.
by poshoholic at 2013-02-06 12:42:10
I think I would do it in one pipeline, using ForEach-Object to copy the folder over first and then copy the permissions from the source folder to the target folder. Something like:

Get-ChildItem -Path SomePath | ForEach-Object {
$newFolder = Copy-Item -LiteralPath $.FullName -Destination DestinationSharePath -PassThru
Get-Acl -Path $
.FullName | Set-Acl -Path $newFolder.FullName
}

For what you are trying to do, I think the examples in the help for Set-Acl should be sufficient if the script sketched out above doesn’t do the job.
by i255d at 2013-02-06 13:45:20
I do see a vary simular example in number 3 in the help examples. Can you set a variable "$newFolder" at the same time you are copying an item?
by poshoholic at 2013-02-06 14:07:59
Yes, that sample should work, more or less (it’s untested), as long as you use the -PassThru parameter of Copy-Item (which returns the item that is copied, i.e. the destination item).
by i255d at 2013-02-06 18:41:25
No, it apears, that there is nothing passed through the pipe after the Copy-item command:
PS T:> Copy-item -Path C:\test\Users -Destination C:\Test2 | Get-Member
Get-Member : No object has been specified to the get-member cmdlet.
At line:1 char:55
+ Copy-item -Path C:\test\Users -Destination C:\Test2 | Get-Member
+ ~~~~~~~~~~
+ CategoryInfo : CloseError: (:slight_smile: [Get-Member], InvalidOperationException
+ FullyQualifiedErrorId : NoObjectInGetMember,Microsoft.PowerShell.Commands.GetMemberCommand
by poshoholic at 2013-02-06 19:28:18
Quoted from above:

…as long as you use the -PassThru parameter of Copy-Item

:slight_smile:

Make sure you use -PassThru in Copy-Item and you’ll get the output you want.

Get-Help Copy-Item -Parameter PassThru
by i255d at 2013-02-07 07:26:13
Ok, thanks for your patience, pretty new at this. I am trying to get this to work in a test, but I can’t get the piping to the Get-ACL and Set-ACL to work:

Get-ChildItem -Path C:\Test\users | ForEach-Object { $newFolder = Copy-Item -LiteralPath $.FullName -Destination C:\Test2\users -PassThru | Get-Acl -Path $.FullName | Set-Acl -Path $newFolder.FullName }

Get-Acl : The input object cannot be bound to any parameters for the command either because the command does not take pipeline
input or the input and its properties do not match any of the parameters that take pipeline input.
At C:\Users\diverso\AppData\Local\Temp\Untitled5.ps1:23 char:142
+ … rs -PassThru | Get-Acl -Path $.FullName | Set-Acl -Path $newFolder.FullName }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (C:\Test2\users\dbight:PSObject) [Get-Acl], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.Commands.GetAclCommand

Set-Acl : Cannot bind argument to parameter ‘Path’ because it is null.
At C:\Users\diverso\AppData\Local\Temp\Untitled5.ps1:23 char:184
+ … Set-Acl -Path $newFolder.FullName }
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:slight_smile: [Set-Acl], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.SetAclCommand

Set-Acl : Cannot bind argument to parameter ‘Path’ because it is null.
At C:\Users\diverso\AppData\Local\Temp\Untitled5.ps1:23 char:184
+ … Set-Acl -Path $newFolder.FullName }
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:slight_smile: [Set-Acl], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.SetAclCommand
by poshoholic at 2013-02-07 10:57:02
You’re close. You can’t do it all on one line though.

Here’s the logic, explained in plain English:

Get all child folders under C:\Test\users. For each folder, create a copy of it on C:\Test2\users, and then copy the permissions from the original folder to the destination folder.

That script looks like this:
# Get all child folders under C:\Test\users
Get-ChildItem -Path C:\Test\users -Directory | ForEach-Object {
# Copy the folder to C:\Test2\users
$newFolder = Copy-Item -LiteralPath $
.FullName -Destination C:\Test2\users -PassThru
# Copy the permissions from the original folder to the new folder
Get-Acl -Path $.FullName | Set-Acl -Path $newFolder.FullName
}

Note that the -Directory switch on Get-ChildItem is new to v3. If you’re using v2, do this instead:
Get-ChildItem -Path C:\Test\users |Where-Object {$
.PSIsContainer} | ForEach-Object {
$newFolder = Copy-Item -LiteralPath $.FullName -Destination C:\Test2\users -PassThru
Get-Acl -Path $
.FullName | Set-Acl -Path $newFolder.FullName
}

That should have you covered.
by i255d at 2013-02-07 11:30:56
I think I am missing something:
Get-ChildItem -Path C:\Test\users | ForEach-Object { $newFolder = Copy-Item -LiteralPath $.FullName -Destination C:\Test2\users -PassThru | Get-Acl -Path $.FullName | Set-Acl -Path $newFolder.FullName }

Copy-Item : Parameter set cannot be resolved using the specified named parameters.
At C:\Users\diverso\AppData\Local\Temp\Untitled5.ps1:24 char:78
+ Get-ChildItem -Path C:\Test\users -Directory | ForEach-Object { $newFolder = Cop …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:slight_smile: [Copy-Item], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Microsoft.PowerShell.Commands.CopyItemCommand

Copy-Item : Parameter set cannot be resolved using the specified named parameters.
At C:\Users\diverso\AppData\Local\Temp\Untitled5.ps1:24 char:78
+ Get-ChildItem -Path C:\Test\users -Directory | ForEach-Object { $newFolder = Cop …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:slight_smile: [Copy-Item], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Microsoft.PowerShell.Commands.CopyItemCommand
by i255d at 2013-02-07 11:38:17
Get-ChildItem -Path C:\Test\users | Where-Object {$.PSIsContainer} | ForEach-Object { $newFolder = Copy-Item -LiteralPath $.FullName -Destination C:\Test2\users -PassThru Get-Acl -Path $.FullName | Set-Acl -Path $newFolder.FullName }

Copy-Item : Parameter set cannot be resolved using the specified named parameters.
At C:\Users\diverso\AppData\Local\Temp\Untitled5.ps1:25 char:101
+ Get-ChildItem -Path C:\Test\users | Where-Object {$
.PSIsContainer} | ForEach-Ob …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:slight_smile: [Copy-Item], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Microsoft.PowerShell.Commands.CopyItemCommand
by poshoholic at 2013-02-07 11:40:39
Wait, why does it seem like you’re running the contents of the ForEach-Object script block as a single line? How are you viewing the responses I send you? If you look at the last reply I sent, it shows a script with 7 lines (PowerShell 3 version, with comments) followed by a script with 4 lines (PowerShell 2 version). If your version doesn’t have that many lines, it won’t work. It looks to me like you’re missing a very important line break.
by i255d at 2013-02-07 11:44:26
I thought that the brackets of the foreach made it all one line.
by poshoholic at 2013-02-07 11:50:15
No, any script block in PowerShell can contain as many lines of script as you like, and PowerShell does not require a line termination character like other languages. You can use the semi-colon like in C# to indicate end of line, but it is not required. Simply finishing one line and starting another without explicitly using a line continuance character tells PowerShell that you have a new command. There are two lines in the ForEach-Object script block.

Also, the fact that script blocks can contain multiple lines is the whole reason why ForEach-Object is so useful. You can perform as many steps as you like right in the middle of a pipeline, allowing you to do things that otherwise could not be done in a single stage in a pipeline.
by i255d at 2013-02-07 12:09:23
So I learned at least two things, and now reading it makes more sense to me. I thought that information was being piped from the copy command to the Get-ACL. Now I see they are separate commands. Your right, this does open me up to new possibilities. Also, being able to create a variable and have it still run the command, I did not know. I am not sure what the Passthru gets me, or why the directory switch is necessary though…?

Also, when I said, “I am guessing that I would create two scripts, one to copy the directories over and a second one to copy the access control permissions?” I wasn’t two far off.

What helps or other things can I read that will help me better under stand and reinforce what I learned here?
by poshoholic at 2013-02-07 12:28:34
Glad it’s coming together for you.

-PassThru is necessary on Copy-Item if you want it to return the destination object so that you can then do something with it (like store it in a variable). By default, if you call Copy-Item it doesn’t return anything. Lots of cmdlets act that way. That’s why the -PassThru parameter exists, so that you can optionally get something back and do something with it in cases where many times, you don’t need to get back an object.

You’re right, you were definitely on the right track with your original guess.

As far as resources that you can read to help understand/reinforce what you learned here, it depends on where you are in your learning and how you want to learn more. If you like to learn from books, there are some really great PowerShell books out there that are worth the investment, especially if you expect to spend more time with PowerShell. If you’re doing IT administration, that’s a pretty safe bet. Check out this page for a lot of free and paid books that will help you learn more about PowerShell: https://powershell.org/powershell-books/.

There is also a lot of information in the help system that is built into PowerShell itself. Look at the results of this command:
get-help -category helpfile
Each of those contain information about one or more areas of PowerShell (get-help about_pipelines for example).

And there are 100’s of example scripts to solve various problems on PoshCode.org or in the TechNet Script Repository, and those are great learning resources too.

I think the answer to this question needs to come from you…where would you like to start?
by i255d at 2013-02-07 12:47:21
Thanks so much for your help.