Jump to content

PowerShell copy folder to home dir of users in a text file


Recommended Posts

Posted (edited)

Hi,

 

I'm using the following code to grab the homedirectory path of users

Get-Content $UserFile | Foreach {Get-ADUser $_ -Properties homedirectory | Copy-Item -path $FolderName -Force -Recurse

 

Don't worry, I'm aware this is flawed at the Copy-Item section but not sure where to go next. Basically the $UserFile variable has been set from a standard .NET Framework class function where you can select a txt file that contains domain users, each on a separate line. I want this command to find/use the home directory of each user in the file and copy a folder ($FolderName set by another class function) to each home dir.

 

I know I need to set the homedirectory before the copy-item as this is currently just showing this property but a little lost at the moment. Any help mucho appreciated.

Edited by randle
Posted

Posting from tapatalk at the moment but this should give you a rough idea of what to do:

 

 
Get-content $UserFile | Foreach { $User = Get-ADUser $_ - Properties homedirectory 
Copy-item -path $Foldername -destination $user.homedirectory -force -recurse
} 

  • Thanks 1
Posted

Following on from this I want an output to show where it's uploaded files to. I have:

 

Get-Content $UserFile | Foreach {Write-Host -Foregroundcolor Cyan "File(s)/Folder(s) uploaded to $_ homedrive"}

 

This gives me the username but not the homedirectory path. I've tried using "$User", "$User.homedirectory" & "$_.homedirectory" in place of "$_" but this just gives me the Distinguished name of the user. Any ideas?

Posted
Following on from this I want an output to show where it's uploaded files to. I have:

 

Get-Content $UserFile | Foreach {Write-Host -Foregroundcolor Cyan "File(s)/Folder(s) uploaded to $_ homedrive"}

 

This gives me the username but not the homedirectory path. I've tried using "$User", "$User.homedirectory" & "$_.homedirectory" in place of "$_" but this just gives me the Distinguished name of the user. Any ideas?

 

Fun thing with using $_.HomeDrive in strings is that it parses $_ as the variable but assumes the rest is a string as normal. So the solution is to use $($_.Homedrive) and it will evaluate the value inside the brackets as a variable first and then push it to the string.

  • Thanks 1
Posted

Fun indeed ;) but good to know. Thanks for that.

 

I ended up adding it into your code above to save repeating a process.

 

Get-Content $UserFile | Foreach {$User = Get-ADUser $_ -Properties homedirectory 
Copy-item -path $FolderName -destination $User.homedirectory -force -recurse
Write-Host -Foregroundcolor Cyan "File(s)/Folder(s) uploaded to $($User.homedirectory)"
}

Posted (edited)

Ok, last bit of help required.....maybe ;)

 

I now want a script to delete the same folder uploaded in the first one so thought I could use pretty much the same process but with remove-item instead of copy-item. So far I have:

 

Function Get-FolderName()
{   
   Add-Type -AssemblyName System.Windows.Forms
   $FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -Property @{
       SelectedPath = 'I:\Temp\Exam data files\’
       ShowNewFolderButton = $false
       Description = "Select folder to upload to user's My Documents"
   }

   [void]$FolderBrowser.ShowDialog()
   $FolderBrowser.SelectedPath
} #end function Get-FolderName

Function Get-FileName($initialDirectory)
{   
   [system.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
   $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
   $OpenFileDialog.Title = "Select text file that contains list of users"
   $OpenFileDialog.initialDirectory = $initialDirectory
   $OpenFileDialog.filter = "Text Files (*.txt)| *.txt" #"All files (*.*)| *.*"
   $OpenFileDialog.ShowDialog() | Out-Null
   $OpenFileDialog.filename
} #end function Get-FileName

#Select the folder to Delete
$FolderName = Get-FolderName

#Select user list text file to delete folder from
$UserFile = Get-FileName -initialDirectory "I:\Temp\Exam data files"

Get-Content $UserFile | Foreach {$User = Get-ADUser $_ -Properties homedirectory 
Remove-Item -path $User.homedirectory\$FolderName -force -recurse
Write-Host -Foregroundcolor Cyan "File(s)/Folder(s) removed from $($User.homedirectory)"
}

Pause

 

With the above I get the error:

Remove-Item : A positional parameter cannot be found that accepts argument '\I:\Temp\Exam data files\CIDA Mock 18 Nov 14'.

At I:\Scripts\PowerShell\Files - RemoveFilesFromUserMyDocs.ps1:34 char:1

+ Remove-Item -path $User.homedirectory\$FolderName -force -recurse

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ CategoryInfo : InvalidArgument: (:) [Remove-Item], ParameterBindingException

+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand

 

I was hoping that selecting the folder would simply set the folder name to the variable rather than the whole path but it looks like the $User.homedirectory is being ignored altogether in this instance. Any ideas?

Edited by randle
Posted

The problem you've got here is that $FolderName contains the full original path to the folder rather than just the name of the folder. The easiest way to get just the information you want would be to use:

 

($FolderName.split("\"))[-1]

 

This will take the $FolderName variable, put it in a temporary array based on where \ characters are and then take the last item in that array (which should be the folder you want) and return it as a string. Your Remove-Item command would then look like this:

 

Remove-Item -Path $User.HomeDirectory\($FolderName.split("\"))[-1] -Force -Recurse

 

If the folder is a few levels deeper than that (but always in the same place then just concatenate the path together like the other strings and you should be good to go.

Posted

Great stuff thanks however, I'm now getting the following:

Remove-Item : A positional parameter cannot be found that accepts argument '\'.

At I:\Scripts\PowerShell\Files - RemoveFilesFromUserMyDocs.ps1:32 char:1

+ Remove-Item -path $User.homedirectory\($FolderName.split("\"))[-1] -force -recur ...

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ CategoryInfo : InvalidArgument: (:) [Remove-Item], ParameterBindingException

+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand

Posted
Great stuff thanks however, I'm now getting the following:

 

The problem is that it's seeing the $User.HomeDirectory as a string and then everything after that as something extra. You'll need to do something like "$($User.HomeDirectory)\$(($FolderName.Split('\'))[-1])" as the path instead, it's a little ugly but should work. The other option would be to create a variable like $UserPath and set it to $User.HomeDirectory + "\" + ($FolderName.Split('\'))[-1] which should also work.

  • Thanks 1
Posted

We're getting somewhere now. I have the following now:

Remove-Item : A positional parameter cannot be found that accepts argument '\CIDA Mock 18 Nov 14'.

At I:\Scripts\PowerShell\Files - RemoveFilesFromUserMyDocs.ps1:32 char:1

+ Remove-Item -path $($User.homedirectory)\$(($FolderName.split('\'))[-1]) -force ...

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ CategoryInfo : InvalidArgument: (:) [Remove-Item], ParameterBindingException

+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand

This is now grabbing the correct folder name but not sure what it means by 'A positional parameter cannot be found that accepts argument....'

 

I appreciate your time and help thanks

Posted
It basically means that it's reading the value you've put in path as $($User.HomeDirectory) and then everything after that as a separate parameter. You need to wrap the whole path in " " marks to show it's a string and it will parse that all together and should work correctly.
Posted

I've had some luck using the other method of setting a variable as you mentioned:

 $UserPath = $User.HomeDirectory + "\" + ($FolderName.Split('\'))[-1]

I'd like to know why the other way didn't work but no worries if it's not obvious.

 

I really appreciate all the help you've provided and now have a script that's tidy and does what I want. The only steps I really have left is error collection/behaviour on hitting cancel and presenting a user friendly message if a user on the list doesn't exist in the domain.

Posted
It basically means that it's reading the value you've put in path as $($User.HomeDirectory) and then everything after that as a separate parameter. You need to wrap the whole path in " " marks to show it's a string and it will parse that all together and should work correctly.

Ah yes of course. Makes sense and is obvious when you explain it...!

Posted

I've now joined the two scripts together with a choice menu at the beginning to choose whether you want to upload or remove a directory. This is working fine but want the script to exit if cancel is chosen on either the folder browser or open file dialogs. I've added an if statement based on the $FolderBrowser.ShowDialog() & $OpenFileDialog.ShowDialog() properties which does work but now prompts for the folder/file twice! I'm not sure why it's prompting again from an If statement!? I've been researching on how to do this but doesn't appear to be much on the web about this!

 

Also I want to output a user friendly message when a user in the list cannot be found in the domain. I take it I should be using a Try/Catch method to do this?

 

Function Get-FolderName()
{   
   Add-Type -AssemblyName System.Windows.Forms
   $FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -Property @{
       SelectedPath = 'I:\Temp\Exam data files\’
       ShowNewFolderButton = $false
       Description = "Select folder to upload/remove"
   }

   [void]$FolderBrowser.ShowDialog()
   $FolderBrowser.SelectedPath
   If ($FolderBrowser.ShowDialog() -eq 2) {Exit}
} #end function Get-FolderName
Function Get-FileName($initialDirectory)
{   
   [system.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
   $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
   $OpenFileDialog.Title = "Select text file that contains list of users"
   $OpenFileDialog.initialDirectory = $initialDirectory
   $OpenFileDialog.filter = "Text Files (*.txt)| *.txt" #"All files (*.*)| *.*"
   $OpenFileDialog.ShowDialog() | Out-Null
   If ($OpenFileDialog.ShowDialog() -eq 2) {Exit}
   $OpenFileDialog.filename
} #end function Get-FileName

        [int]$xMenuChoiceA = 0
         while ( $xMenuChoiceA -lt 1 -or $xMenuChoiceA -gt 3 ){
         Write-host "1. Upload"
         Write-host "2. Remove"
         Write-host "3. Quit"
         [int]$xMenuChoiceA = read-host "Do you want to upload a folder or remove one? Select 1 to 3..." }
         Switch( $xMenuChoiceA ){
             1{
               #Select the folder to upload
               Write-Host "Select the folder to upload"
               $FolderName = Get-FolderName
               
               #Select user list text file to upload folder to
               Write-Host "Select text file that contains list of users"
               $UserFile = Get-FileName -initialDirectory "I:\Temp\Exam data files"

               Get-Content $UserFile | Foreach {$User = Get-ADUser $_ -Properties homedirectory 
               Copy-item -path $FolderName -destination $User.homedirectory -force -recurse
               Write-Host -Foregroundcolor Cyan "File(s)/Folder(s) uploaded to $($User.homedirectory)"}
              }
             2{
               #Select the folder to Remove
               Write-Host "Select the folder to remove"
               $FolderName = Get-FolderName

               #Select user list text file to upload folder to
               Write-Host "Select text file that contains list of users"
               $UserFile = Get-FileName -initialDirectory "I:\Temp\Exam data files"

               Get-Content $UserFile | Foreach {$User = Get-ADUser $_ -Properties homedirectory 
               <#
               The following can be used as an alternative to set the path of the folder to delete
                   $UserPath = $User.HomeDirectory + "\" + ($FolderName.Split('\'))[-1]
                   Remove-Item -path $UserPath -force -recurse
               #>
               Remove-Item -path "$($User.homedirectory)\$(($FolderName.split('\'))[-1])" -force -recurse
               Write-Host -Foregroundcolor Cyan "File(s)/Folder(s) removed from $($User.homedirectory)"}
              }
             3{Exit}
          default{Exit}
          }

Posted
I've now joined the two scripts together with a choice menu at the beginning to choose whether you want to upload or remove a directory. This is working fine but want the script to exit if cancel is chosen on either the folder browser or open file dialogs. I've added an if statement based on the $FolderBrowser.ShowDialog() & $OpenFileDialog.ShowDialog() properties which does work but now prompts for the folder/file twice! I'm not sure why it's prompting again from an If statement!? I've been researching on how to do this but doesn't appear to be much on the web about this!

 

The reason it's opening a second dialog box is that you are calling the ShowDialog method in the If statement, which is causing it to open it again and then check the results. There are two solutions to this, one is to remove the first ShowDialog method call (which is being piped to Out-Null) and the other solution is to change the If statement to:

 

if ($FolderBrowser.FileName -eq "")

or

if ($OpenFileDialog.FileName -eq "")

 

This means that the first call to ShowDialog will set the value of the FileName property and you are then checking that value. It's more readable that way and would be my preferred solution, since at some point either myself or someone else would have to go back and update the script (or reread what it does) and readability is pretty useful then.

 

Also I want to output a user friendly message when a user in the list cannot be found in the domain. I take it I should be using a Try/Catch method to do this?

 

The way I've done this in the past is to have a function which checks if a user exists in AD and then return either their AD object or 0 if they don't exist. I use that method as I'm usually passing the function the first name and surname of the pupil and usually year group to help narrow down the search for the user. I should probably update the Get-ADUser filter to actually use -Searchbase to specify OU rather than -Filter but I don't think it has much impact on performance and I doubt there is much difference for "best practice" purposes.

 



Function Check-ADUser{
[string]$Forename = $args[0]
[string]$Surname = $args[1]
[string]$YearGroup = $args[2]


if ($OU -contains "20") {
       $SecGroup = "CN=$OU,OU=$OU,OU=Students,OU=Users,DC=domain,DC=local"
   }
   ElseIf ($OU -contains "Teaching") {
       $SecGroup = "CN=All Staff,OU=Teaching Staff,OU=Users,DC=Domain,DC=local"
   }
$ADUser = Get-ADUser -Filter {GivenName -eq $Forename -and Surname -eq $Surname -and MemberOf -eq $SecGroup}


if ($ADUser) {
	return $ADUser
}
else {
	return 0
}
}

Posted

Just concentrating on the dialog functions for now. I already tried wrapping the first method in an if statement "If([void]$FolderBrowser.ShowDialog() -ne "1") {Exit}", which did quit the script when the cancel button was clicked but also quit the script when a folder was selected!

 

Unfortunately there's no property called 'FileName' for this class so can't use '$FolderBrowser.FileName' (https://msdn.microsoft.com/en-us/library/system.windows.forms.folderbrowserdialog(v=vs.110).aspx).

 

It seems that adding this If statement has just thrown it off and now isn't returning the selected path. I have tried adding the Else statement 'Else {Return $FolderBrowser.SelectedPath}' with this but results in the same outcome!

Posted

The method I mentioned above works fine for the $OpenFileDialog. For the Folder one I've done a bit of testing with it in the ISE and it looks like the button click is returned to the console (or at least as a general output), which means you can do something like this:

 

$SomeVariable = $FolderBrowser.ShowDialog()

 

This will mean that $FolderBrowser.SelectedPath is set to the value you want (assuming they click Ok) and then $SomeVariable is set to either "Ok" or "Cancel", which you can then run an If statement on.

Posted (edited)

I didn't try the OpenFileDialog function but can see there is a property for filename so my mistake. This works fine for me thanks.

 

I was having mixed results with the FolderBrowserDialog function but turns out it really didn't like the [void] prefix which when used as part of an if statement would make the script skip this function! I tried piping to Out-Null like the OpenFileDialog function which I know is the same thing but just a more PS way of doing it but it didn't like this either. Excluding this allows the script to run as intended so will leave it out for now unless you think it should be there?

 

The FolderBrowserDialog window, although opens at the specified SelectedPath, doesn't jump to the folder meaning that I have to scroll down to this which isn't the end of the world but would be nice! I've found on my research that this is just the way the class works so not too much that can be done but also, would be nice if the SelectedPath folder was already expanded! Adding '\' to the end of the path has no effect so any ideas?

 

I'll give the AD check a look now. Thanks as always.

Edited by randle
Posted

I was having mixed results with the FolderBrowserDialog function but turns out it really didn't like the [void] prefix which when used as part of an if statement would make the script skip this function! I tried piping to Out-Null like the OpenFileDialog function which I know is the same thing but just a more PS way of doing it but it didn't like this either. Excluding this allows this to run as intended so will leave it out for now unless you think it should be there?

 

I was testing it with just copy/pasting your code into the ISE and running the first few lines of the function to create the object and assign basic values to it, then tried to see what output you get from running the showdialog from the two buttons. Piping it to Out-Null would mean you get no output (other than any selected folder change made), so that's why I went with assigning the output to a variable so you can check it with an if statement, you could probably ignore the variable entirely and just put the showdialog call in the if statement and check for "Cancel".

 

The FolderBrowserDialog window, although opens at the specified SelectedPath, doesn't jump to the folder meaning that I have to scroll down to this which isn't the end of the world but would be nice! I've found on my research that this is just the way the class works so not too much that can be done but would be nice if the SelectedPath folder was already expanded! Adding '\' to the end of the path has no effect so any ideas?

 

I've not done enough with the GUI side of things (beyond a basic UI I built for making scripts easier to run without using the command line) so I'm not sure exactly how it's supposed to work or if there is a way to get that functionality.

Posted

No worries. I've not found much help on my searches in regards to this issue with the FolderBrowserDialog class. As I said, it's more of a niggle than an issue.

 

I was getting a little confused with how to incorporate your AD check so had a dabble myself and came up with the following:

Get-Content $UserFile | Foreach {$User = Get-ADUser -LDAPFilter "(sAMAccountName=$_)"
                 
        If($User -eq $Null) 
        {
               Write-Host -ForegroundColor Red "User $_ does not exist in AD. Exiting script"
        }
        Else {}
}

This is added just after the $Get-Filename function action and works fine, listing invalid users before going any further and giving the user a chance to amend the user list however, If I add 'Exit' after the Write-Host string this only outputs one invalid user rather than all. I've tried putting it in different places but doesn't like this. Any ideas?

Posted

I'd probably set a variable to check whether any of the users were invalid and then exit based on that value, something like:

 

$InvalidUsers = 0
Get-Content $UserFile | Foreach { $User = Get-ADUser -LDAPFilter "(sAMAccountName=$_)"
if ($User -eq $Null)
{
Write-Host -ForegroundCOlor Red "User $_ does not exist in AD."
$InvalidUsers += 1
}
}
If ($InvalidUsers -gt 0) 
{
Exit
}

  • Thanks 1
Posted

Well that's just smashing thanks. I couldn't have done it without your help and appreciate all the time you've given. I've learnt a lot along the way too. Watch out PowerShell......

 

Final script if anyone's interested:

#Shows folder browser dialog box and passes on selection
Function Get-FolderName()
{   
   Add-Type -AssemblyName System.Windows.Forms
   $FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -Property @{
       SelectedPath = 'I:\Temp\Exam data files\’
       ShowNewFolderButton = $false
       Description = "Select folder to upload/remove"
   }
   #If cancel is clicked the script will exit
   If ($FolderBrowser.ShowDialog() -eq "Cancel") {Exit}
   $FolderBrowser.SelectedPath
} #end function Get-FolderName

#Shows a file selector dialog box and passes on selection
Function Get-FileName($initialDirectory)
{   
   [system.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
   $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
   $OpenFileDialog.Title = "Select text file that contains list of users"
   $OpenFileDialog.initialDirectory = $initialDirectory
   $OpenFileDialog.filter = "Text Files (*.txt)| *.txt" #"All files (*.*)| *.*"
   $OpenFileDialog.ShowDialog() | Out-Null
   $OpenFileDialog.filename
   #If cancel is clicked the script will exit
   If ($OpenFileDialog.filename -eq "") {Exit}
} #end function Get-FileName

#Checks the user list against AD for invalid users and reports back then terminates the script
Function Check-Users()
{
   $InvalidUsers = 0
   Get-Content $UserFile | Foreach {$User = Get-ADUser -LDAPFilter "(sAMAccountName=$_)"
                  
       If($User -eq $Null)
       {
           Write-Host -ForegroundColor Red "User $_ does not exist in AD. Exiting script"
           $InvalidUsers +=1
       }
       }
       If($InvalidUsers -gt 0)
       {
       Pause
       Exit
       }
}

         #Choice menu for upload, remove or quit
         [int]$xMenuChoiceA = 0
         while ( $xMenuChoiceA -lt 1 -or $xMenuChoiceA -gt 3 ){
         Write-host "1. Upload"
         Write-host "2. Remove"
         Write-host "3. Quit"
         [int]$xMenuChoiceA = read-host "Do you want to upload a folder or remove one? Select 1 to 3..." }
         Switch( $xMenuChoiceA ){
             1{
               #Select user list text file to upload folder to
               Write-Host "Select text file that contains list of users`n"
               $UserFile = Get-FileName -initialDirectory "I:\Temp\Exam data files"
               
               #Checks the user list against AD for invalid users and reports back then terminates the script
               Check-Users                          
                              
               #Select the folder to upload
               Write-Host "`nSelect the folder to upload"
               $FolderName = Get-FolderName
               
               #Gets each user's homedirectory path and copies the above folder selection over to each of them
               Get-Content $UserFile | Foreach {$User = Get-ADUser $_ -Properties homedirectory 
               Copy-item -path $FolderName -destination $User.homedirectory -force -recurse
               Write-Host -Foregroundcolor Cyan "Folder `"$(($FolderName.split('\'))[-1])`" uploaded to $($User.homedirectory)"}
              }
             2{
               #Select user list text file to upload folder to
               Write-Host "Select text file that contains list of users`n"
               $UserFile = Get-FileName -initialDirectory "I:\Temp\Exam data files"
               
               #Checks the user list against AD for invalid users and reports back then terminates the script
               Check-Users  
               
               #Select the folder to Remove
               Write-Host "`nSelect the folder to remove"
               $FolderName = Get-FolderName
               
               #Gets each user's homedirectory path and removes the above folder selection from each of them
               Get-Content $UserFile | Foreach {$User = Get-ADUser $_ -Properties homedirectory 
               <#
               The following can be used as an alternative to set the path of the folder to delete
                   $UserPath = $User.HomeDirectory + "\" + ($FolderName.Split('\'))[-1]
                   Remove-Item -path $UserPath -force -recurse
               #>
               Remove-Item -path "$($User.homedirectory)\$(($FolderName.split('\'))[-1])" -force -recurse
               Write-Host -Foregroundcolor Cyan "Folder `"$(($FolderName.split('\'))[-1])`" removed from $($User.homedirectory)"}
              }
             3{Exit}
          default{Exit}
          }
Pause

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now



×
×
  • Create New...