Copy folders from server to another - Powershell - powershell

I am trying to come up with a script to copy folders from one server to another. I might be going about this wrong, but I'm try to copy the directories from one server into an array, copy the directories from the second server into an array, compare them and then create the folders needed in the server that doesn't have them:
[array]$folders = Get-ChildItem -Path \\spesety01\TGT\TST\XRM\Test -Recurse -Directory -Force -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName
[array]$folders2 = Get-ChildItem -Path \\sutwove02\TGT\TST\XRN -Recurse -Directory -Force -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName
$folders | ForEach-Object {
if ($folders2 -notcontains "$_") {
New-Item "$_" -type directory
}
}
The issue is that the "$_" (in the ForEach loop)refers to the server in "$folders" and when I run the script, I get an error that the folder already exists. Is there some way to specify to copy the folders to the new server? I accept that my approach might be completely off on this and I might be making it harder than it needs to be.

<#
.SYNOPSIS
using path A as reference, make any sub directories that are missing in path B
#>
Param(
[string]$PathA,
[string]$PathB
)
$PathADirs = (Get-ChildItem -Path $PathA -Recurse -Directory).FullName
$PathBDirs = (Get-ChildItem -Path $PathB -Recurse -Directory).FullName
$PreList = Compare-Object -ReferenceObject $PathADirs -DifferenceObject $PathBDirs.replace($PathB,$PathA) |
Where-Object -Property SideIndicator -EQ "<=" |
Select-Object -ExpandProperty 'InputObject'
$TargetList = $PreList.Replace($PathA,$PathB)
New-Item -Path $TargetList -ItemType 'Directory'

Related

Powershell: how to loop through folders and execute the same code for each of the folders

Currently I have a script that will sort files in a folder (on their lastwritetime), keep the latest file and move the other files to a different folder. This works correctly:
Get-ChildItem "\\networkfolder\RawData\2_ActionData_Prep\CustomerA\" -Recurse -Filter "*.rpt" -File |
Sort-Object -Property LastWriteTime -Descending |
Select-Object -Skip 1 |
Move-Item -Force -Destination "\\networkfolder\RawData\_Archive\Archive_DataRetrieved\"
The problem is that I have several 'customer' folders and I want to execute the code above in each of those folders.
I tried the following:
$CustomerFolders = Get-ChildItem -Path "\\networkfolder\RawData\2_ActionData_Prep\" -Directory -Recurse
foreach ($folder in $CustomerFolders) {Get-ChildItem -Filter "*.rpt" -File | Sort-Object -Property LastWriteTime -Descending |
Select-Object -Skip 1 |
Move-Item -Force -Destination "\\networkfolder\RawData\_Archive\Archive_DataRetrieved\"}
When I execute this script, nothing happens. Also no error comes up. Hopefully someone could help me on this.
Santiago Squarzon noticed that a $folder was missing, so I added $folder in loop for Get-Childitem:
$CustomerFolders = Get-ChildItem -Path "\\networkfolder\RawData\2_ActionData_Prep\" -Directory -Recurse
foreach ($folder in $CustomerFolders) {Get-ChildItem $folder -Filter "*.rpt" -File | Sort-Object -Property LastWriteTime -Descending |
Select-Object -Skip 1 |
Move-Item -Force -Destination "\\networkfolder\RawData\_Archive\Archive_DataRetrieved\"}
Now I get an error message:
Get-ChildItem : Cannot find path '\networkfolder\CustomerA' because it does not exist.
It somehow misses the part \RawData\2_ActionData_Prep\ in the path, although I defined it in the $CustomerFolders variable?
You could do the process all with pipelines like this:
$base = "\\networkfolder\RawData\2_ActionData_Prep\"
$destination = "\\networkfolder\RawData\_Archive\Archive_DataRetrieved\"
Get-ChildItem -Path $base -Directory -Recurse | ForEach-Object {
$_ | Get-ChildItem -Filter "*.rpt" -File | Sort-Object LastWriteTime -Descending |
Select-Object -Skip 1 | Move-Item -Force -Destination $destination
}
To briefly explain why Get-ChildItem $folder... failed but $folder | Get-ChildItem ... worked, when we do Get-ChildItem $folder, $folder is being passed as argument for the -Path parameter and the parameter type for it is [string[]]. So, in your code when $folder (a DirectoryInfo instance) is passed as argument, it is being converted to a string and, very unfortunately in Windows PowerShell, when we type convert a DirectoryInfo (and a FileInfo too!) object to string what we get as a result is the Directory Name (this is not the case in PowerShell Core, where the string representation of this object becomes the Directory FullName (a.k.a. Absolute Path) so Get-ChildItem thinks it's being fed a relative path and it looking for the folders in your current location.
However when we do $folder | Get-ChildItem ..., $folder gets bound to the -LiteralPath parameter by Property Name on the PSPath ETS property, in other words, the cmdlet receives the object's provider path (you can think of it as the absolute path of the folder) hence why it works fine.

Copy all latest files from folders/sub to the same name folders/sub in powershell

I am trying to copy the latest file from every folder/sub-folder into a same file structure on a different drive.
Latest file from source copied to the same name corresponding destination.
The destination folder hierarchy already exists & cannot be copied over or recreated. This & other versions are not behaving. Can anyone help?
$sourceDir = 'test F Drive\Shares\SSRSFileExtract\'
$destDir = 'test X Drive\SSRSFileExtract\'
$date = Get-Date
$list = Get-ChildItem -Path $sourceDir | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1
foreach ($item in $list)
{
Copy-Item -Verbose -LiteralPath $item.FullName -Destination $destDir -Force |
Get-Acl -Path $item.FullName | Set-Acl -Path $destDir\$(Split-Path -Path $item.FullName -Leaf)
}
Get-ChildItem –Path $destDir -Recurse | Where-Object {($_.LastWriteTime -lt (Get-Date).AddDays(-5))} | Remove-Item -Verbose -Recurse -Force
I found a solution for this which copies/moves all the files from all sub folders in to all corresponding sub folders:
Powershell: Move all files from folders and subfolders into single folder
The way your code retrieves the list of files will only return one single object because of Select-Object -First 1. Also, because you don't specify the -File switch, Get-ChildItem will also return DirectoryInfo objects, not just FileInfo objects..
What you could do is get an array of FileInfo objects recursively from the source folder and group them by the DirectoryName property
Then loop over these groups of files and from each of these groups, select the most recent file and copy that over to the destination folder.
Try:
$sourceDir = 'F:\Shares\SSRSFileExtract'
$destDir = 'X:\SSRSFileExtract'
Get-ChildItem -Path $sourceDir -File -Recurse | Group-Object DirectoryName | ForEach-Object {
# the $_ automatic variable represents one group at a time inside the loop
$newestFile = $_.Group | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1
# construct the target sub directory
# you could also use $_.Name (the name of this group) instead of $newestFile.DirectoryName here, because
# we grouped on that DirectoryName file property.
$targetDir = Join-Path -Path $destDir -ChildPath $newestFile.DirectoryName.Substring($sourceDir.Length)
# if you're not sure the targetpath exists, uncomment the next line to have it created first
# if (!(Test-Path -Path $targetDir -PathType Container)) { $null = New-Item -Path $target -ItemType Directory }
# copy the file
$newestFile | Copy-Item -Destination $targetDir -Force -Verbose
# copy the file's ACL
$targetFile = Join-Path -Path $targetDir -ChildPath $newestFile.Name
$newestFile | Get-Acl | Set-Acl -Path $targetFile
}
Apparently you would also like to clean up older files in the destination folder
Get-ChildItem –Path $destDir -File -Recurse |
Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-5).Date} |
Remove-Item -Verbose -Recurse -Force
Be aware that the final code to remove older files could potentially remove all files from a subfolder if all happen to be older than 5 days..

Search multiple folders in multiple servers list

I'm trying to create a ps1 that can search multiple folders in multiple servers list, but seems didn't work. Something wrong with the *folder I guess. Sorry I'm very new to this.
$folders = get-content "C:\temp\folders.txt"
get-content c:\temp\servers.txt | Foreach {
Get-ChildItem -Path "c:\temp" -include *folders -Recurse -ErrorAction
silentlycontinue} | export-csv c:\Temp\results.csv
You're reading a textfile with (presumably) a list of server names to probe, but in your code you do nothing with that other than iterate this list..
Try
$folders = Get-Content 'C:\temp\folders.txt' # the list of foldernames to look for
Get-Content 'C:\temp\servers.txt' | ForEach-Object {
# construct a UNC path to the C:\Temp folder on the remote server (\\server\c$\temp)
# the $_ Automatic variable contains one servername in each iteration
$remotePath = "\\$_\C$\temp"
Get-ChildItem -Path $remotePath -Include $folders -Directory -Recurse -ErrorAction SilentlyContinue |
# select properties you need
Select-Object #{Name = 'ComputerName'; Expression = {$_}}, Name, FullName, CreationTime, LastAccessTime, LastWriteTime
} | Export-Csv 'C:\temp\results.csv' -NoTypeInformation
OR
Have the remote servers do the work and return the results to you. You may need to add -Credentials on Invoke-Command:
$folders = Get-Content 'C:\temp\folders.txt' # the list of foldernames to look for
Get-Content 'C:\temp\servers.txt' | ForEach-Object {
Invoke-Command -ComputerName $_ -ScriptBlock {
# this is running on the remote computer, so it uses it's own LOCAL path
# the $folders variable needs to be scoped '$using:folders', otherwise it is unknown in the scriptblock
Get-ChildItem -Path 'C:\temp' -Include $using:folders -Directory -Recurse -ErrorAction SilentlyContinue |
# select and output the properties you need
Select-Object #{Name = 'ComputerName'; Expression = {$env:COMPUTERNAME}}, Name, FullName, CreationTime, LastAccessTime, LastWriteTime
}
} | Export-Csv 'C:\temp\results.csv' -NoTypeInformation

Compare-Object Delete File if file does not exist on source

I have this PowerShell code that compares 2 directories and removes files if the files no longer exist in the source directory.
For example say I have Folder 1 & Folder 2. I want to compare Folder 1 with Folder 2, If a file doesn't exist anymore in Folder 1 it will remove it from Folder 2.
this code works ok but I have a problem where it also picks up file differences on the date/time. I only want it to pick up a difference if the file doesn't exist anymore in Folder 1.
Compare-Object $source $destination -Property Name -PassThru | Where-Object {$_.SideIndicator -eq "=>"} | % {
if(-not $_.FullName.PSIsContainer) {
UPDATE-LOG "File: $($_.FullName) has been removed from source"
Remove-Item -Path $_.FullName -Force -ErrorAction SilentlyContinue
}
}
Is there an extra Where-Object {$file1 <> $file2} or something like that.?
I am not sure how you are getting the information for $source and $destination I am assuming you are using Get-ChildItem
What i would do to eliminate the issue with date/time would be to not capture it in these variables. For Example:
$source = Get-ChildItem C:\temp\Folder1 -Recurse | select -ExpandProperty FullName
$destination = Get-ChildItem C:\temp\Folder2 -Recurse | select -ExpandProperty FullName
By doing this you only get the FullName Property for each object that is a child item not the date/time.
You would need to change some of the script after doing this for it to still work.
If I am not getting it wrong, the issue is your code is deleting the file with different time-stamp as compared to source:
Did you try -ExcludeProperty?
$source = Get-ChildItem "E:\New folder" -Recurse | select -ExcludeProperty Date
The following script can serve your purpose
$Item1=Get-ChildItem 'SourcePath'
$Item2=Get-ChildItem 'DestinationPath'
$DifferenceItem=Compare-Object $Item1 $Item2
$ItemToBeDeleted=$DifferenceItem | where {$_.SideIndicator -eq "=>" }
foreach ($item in $ItemToBeDeleted)
{
$FullPath=$item.InputObject.FullName
Remove-Item $FullPath -Force
}
Try something like this
In PowerShell V5:
$yourdir1="c:\temp"
$yourdir2="c:\temp2"
$filesnamedir1=(gci $yourdir1 -file).Name
gci $yourdir2 -file | where Name -notin $filesnamedir1| remove-item
In old PowerShell:
$yourdir1="c:\temp"
$yourdir2="c:\temp2"
$filesnamedir1=(gci $yourdir1 | where {$_.psiscontainer -eq $false}).Name
gci $yourdir2 | where {$_.psiscontainer -eq $false -and $_.Name -notin $filesnamedir1} | remove-item
If you want to compare files in multiple dir, use the -recurse option for every gci command.

Powershell compare-object

I trying to make a script which compare two directory ( source, destination) and if there are a difference on destination, copy files from source to destination.
The problem is that I don't know how copy the tree of files too.
Example:
$s = "C:\source\client"
$t = "C:\destination\client"
$target = Get-ChildItem $t -Recurse
$source = get-childitem $s -Recurse
Compare-Object $source $target -Property Name , Length |
Where-Object { $_.SideIndicator -eq '<=' } |
foreach-object -process{
copy-item $_.FullName -destination $t
}
If I have a file in source ( C:\source\client\bin\file.txt) and not in the destination folder, how is the code to copy the file in C:\destination\client\bin\file.txt ?
Thanks.
I am in the process of testing this more. From what i can see the logic of your code is sound.
Compare-Object $source $target -Property Name , Length |
Where-Object { $_.SideIndicator -eq '<=' } | Select-Object -ExpandProperty inputobject |
foreach-object -process{
copy-item $_.FullName -destination $t
}
Once you have the compare done pipe the results after the Where in Select-Object -ExpandProperty inputobject to extract the File item so that you can see the FullName property
copy-item has a -recurse parameter that will let you specify the root of a directory and then copy everything below it
copy-item c:\test d:\test -recurse -force
Edit:
The problem is for repeated tasks you can't stop it from trying to overwrite everything. You can add -force to make it do it, but it is not very efficient.
Alternatively (and probably a better and simpler way to go about this) you could call robocopy with the /mir switch
Thanks for sharing. Here is what I have done with everything I searched to compare MD5 and then copy only newly added and different files.
With [Compare contents of two folders using PowerShell Get-FileHash] from http://almoselhy.azurewebsites.net/2014/12/compare-contents-of-two-folders-using-powershell-get-filehash/
$LeftFolder = "D:\YFMS_Target"
$RightFolder = "D:\YFMS_Copy"
$LeftSideHash = #(Get-ChildItem $LeftFolder -Recurse | Get-FileHash -Algorithm MD5| select #{Label="Path";Expression={$_.Path.Replace($LeftFolder,"")}},Hash)
$RightSideHash = #(Get-ChildItem $RightFolder -Recurse | Get-FileHash -Algorithm MD5| select #{Label="Path";Expression={$_.Path.Replace($RightFolder,"")}},Hash)
robocopy $LeftFolder $RightFolder /e /xf *
Write-Host "robocopy LastExitCode: $LastExitCode"
if ($LastExitCode -gt 7) { exit $LastExitCode } else { $global:LastExitCode = $null }
Compare-Object $LeftSideHash $RightSideHash -Property Path,Hash | Where-Object { $_.SideIndicator -eq '<=' } | foreach { Copy-Item -LiteralPath (Join-Path $LeftFolder $_.Path) -Destination (Join-Path $RightFolder $_.Path) -verbose}