move files with specific extension to folder in higher hierarchy - powershell

All my files are in specific folders:
17\1\1\PRO
17\1\2\PRO
17\2\1\PRO
xx\xx\xx\PRO
17 is the year (so 18 for next year etc)
the first 1 is the folder specifying the case number (can be up to 100).
The second 1 is the sub parts on the case number.
That last 1 has a folder PRO in it where all data resides.
We need to move these files, but the files need to stay inside their respective "PRO" folders.
For example:
a file in 17\1\1\pro\xxx\www\ needs to go to 17\1\1\pro\movies
a file in 17\2\2\pro\xxdfsdf\eeee\ needs to go to 17\2\2\pro\movies.
The movies folder should get created if there are files to move.
I need to get a part of the full name of a file and move the file there to the "movie" folder. The problem is I do not know how to split the full name, add \movies to it and move the files there.
This is my code so far:
Get-ChildItem -Path $mypath -Recurse -File -Filter $extension | select $_Fullname |
Move-Item -Force -Destination ($_Fullname.Split("pro"))

If the destination is always "movies subdirectory of the grandparent directory of the file's directory" you can build the destination path relative to the file's location:
Get-ChildItem ... | ForEach-Object {
$dst = Join-Path $_.Directory '..\..\movies'
if (-not (Test-Path -LiteralPath $dst -PathType Container)) {
New-Item -Type Directory -Path $dst | Out-Null
}
Move-Item $_.FullName -Destination $dst
}
If the PRO directory is your anchor you could use a regular expression replacement like this instead:
Get-ChildItem ... | ForEach-Object {
$dst = $_.Directory -replace '^(.*\\\d+\\\d+\\\d+\\PRO)\\.*', '$1\movies'
if (-not (Test-Path -LiteralPath $dst -PathType Container)) {
New-Item -Type Directory -Path $dst | Out-Null
}
Move-Item $_.FullName -Destination $dst
}

If you don't know how many directories there are, I would do something like this:
Get-ChildItem -Path $mypath -Recurse -File -Filter $extension | ForEach-Object {
if ($_.FullName.IndexOf('\PRO\') -gt 0) {
$Destination = Join-Path -Path $_.FullName.Substring(0,$_.FullName.IndexOf('\PRO\') + 5) -ChildPath 'movies';
New-Item $Destination -ItemType Directory -ea Ignore;
$_ | Move-Item -Destination $Destination;
} else {
throw ("\PRO\ path not found in '$($_.FullName)'");
}
}
This will work fine as long as your paths only have \pro\ once. If they have it more than once like customer\pro\17\pro\17\1\1\pro\xx\yy\zz\www and you need the last index, then use $_.FullName.LastIndexOf('\pro\').
If you've got \pro\ directories both before and after the directory that .\pro\movies\ is in, well, you're in trouble. You'll probably have to find a different point of reference.

With a set of folders
17\1\1\PRO
17\1\2\PRO
17\2\1\PRO
You could try the following
$RootPaths = Get-ChildItem -Path C:\folder\*\*\*\pro
$RootPaths will then contain all 3 paths mentioned above and the code below will move all files to the appropriate directory.
ForEach( $Path in $RootPaths)
{
$Movies = Join-Path $Path -Child "Movies"
If( -not (Test-Path $Movies ) ) { New-Item -Path $Movies -ItemType Directory }
Get-ChildItem -Path $Path -Recurse -File -Filter $Extension |
Move-Item -Path $_.FullName -Destination "$( $Path )\Movies"
}
This way it doesn't matter how many levels down your files are. They always get moved to the same directory.

Related

Rename folders based off of newest pdf in folder

I currently have 20000+ folders that where given a random string of characters when created. I would like to rename each folder with the name of the last PDF modified within each folder. I'm definitely in over my head. The current script seems to just move the PDF and/or folder without renaming it or creating a folder with the PDF name.
Get-ChildItem -Path $SourceFolder -Filter *.pdf |
ForEach-Object {
$ChildPath = Join-Path -Path $_.Name.Replace('.pdf','') -ChildPath $_.Name
[System.IO.FileInfo]$Destination = Join-Path -Path $TargetFolder -ChildPath $ChildPat
if( -not ( Test-Path -Path $Destination.Directory.FullName ) ){
New-Item -ItemType Directory -Path $Destination.Directory.FullName
}
Copy-Item -Path $_.FullName -Destination $Destination.FullName
}
Welcome, Robert! There's a few things going on with your script:
There's a typo: $ChildPat
You don't need a FileInfo object to create the new directory, and you can't create one from a non-existent path. $Destination = Join-Path $_.Directory $_.BaseName will get the new folder name more reliably, in the unusual case where the file name has embedded '.pdf'
It doesn't get the latest PDF.
Assuming you only want to get folders that have a PDF, you should have a nested Get-ChildItem for each folder, as #Lee_Dailey recommended:
Push-Location $SourceFolder
Foreach ($dir in (Get-ChildItem *.pdf -Recurse | Group-Object Directory | Select Name )){
Push-Location $dir.Name
$NewestPDF = Get-ChildItem *.pdf | Sort-Object ModifiedDate | Select -Last 1
$Destination = Join-Path $dir.Name "..\$($NewestPDF.BaseName)"
If(!(Test-Path $Destination)){New-Item $Destination -ItemType Directory}
Copy-Item *.PDF $Destination
Pop-Location
#Remove-Item $dir.Name #uncomment to remove the old folder (is it empty?)
}

Powershell: Loop through sub-directories and move files

I'm targeting simple task.
I would like to create folder of constant name "jpg" in all subfolders of supplied root folder "D:Temp\IMG" and move all files in every subfolder with extension ".jpg" to that newly created "jpg" folder.
I thought I'll be able to solve this by myself without deep knowledge of powershell, but it seems I have to ask.
So far, I created this code
$Directory = dir D:\Temp\IMG\ | ?{$_.PSISContainer};
foreach ($d in $Directory) {
Write-Host "Working on directory $($d.FullName)..."
Get-ChildItem -Path "$($d.FullName)" -File -Recurse -Filter '*.jpg' |
ForEach-Object {
$Dest = "$($d.DirectoryName)\jpg"
If (!(Test-Path -LiteralPath $Dest))
{New-Item -Path $Dest -ItemType 'Directory' -Force}
Move-Item -Path $_.FullName -Destination $Dest
}
}
What I'm getting out of this is infinite loop of folder "jpg" creation in every subfolder.
Where is my code and logic failing here, please?
The following script would do the job.
$RootFolder = "F:\RootFolder"
$SubFolders = Get-ChildItem -Path $RootFolder -Directory
Foreach($SubFolder in $SubFolders)
{
$jpgPath = "$($SubFolder.FullName)\jpg"
New-Item -Path $jpgPath -ItemType Directory -Force
$jpgFiles = Get-ChildItem -Path $SubFolder.FullName -Filter "*.jpg"
Foreach($jpgFile in $jpgFiles)
{
Move-Item -Path $jpgFile.FullName -Destination "$jpgPath\"
}
}
This will accomplish what you are attempting, I'm pretty sure. Your original script doesn't actually recurse, despite specifying that you want it to (Get-ChildItem has some finicky syntax around that), so I fixed that. Also fixed my suggestion (I forgot that the Extension property includes the preceding dot, so 'FileName.jpg' has '.jpg' as the extension). I added in some checking, and have it throw warnings if the file already exists at the destination.
$Directory = dir D:\Temp\IMG\ -Directory
foreach ($d in $Directory) {
Write-Host "Working on directory $($d.FullName)..."
Get-ChildItem -Path "$($d.fullname)\*" -File -Recurse -filter '*.jpg' |
Where{$_.Directory.Name -ne $_.Extension.TrimStart('.')}|
ForEach-Object {
$Dest = join-path $d.FullName $_.Extension.TrimStart('.')
If (!(Test-Path -LiteralPath $Dest))
{New-Item -Path $Dest -ItemType 'Directory' -Force|Out-Null}
If(Test-Path ($FullDest = Join-Path $Dest $_.Name)){
Write-Warning "Filename conflict moving:`n $($_.FullName)`nTo:`n $FullDest"
}Else{
Move-Item -Path $_.FullName -Destination $Dest -Verbose
}
}
}

Coping files from multiple sub folders using powershell

Copy file from multiple sub-folder to another multiple sub-folder
example :
C:\Nani\Code\Relase4\database1\tables
C:\Nani\Code\Relase1\database1\tables
C:\Nani\Code\Relase2\database1\tables
C:\Nani\Code\Relase3\cycle1\database1\tables
C:\Nani\Code\Relase1\database1.02.tables
I have .sql files in above all folders and i want to copy to
C\Build\database1\tables
if database1\tables directory is not there , i have to create it too ,
$sourceFolder = "C:\Nani\Code"
$targetFolder = "C\Build"
Get-Childitem $sourceFolder -recurse -filter "*.sql" -Exclude $exclude | %{
#If destination folder doesn't exist
if (!(Test-Path $targetFolder -PathType Container)) {
#Create destination folder
New-Item -Path $targetFolder -ItemType Directory -Force
}
Copy-Item -Path $_.FullName -Destination $targetFolder -Recurse -force
}
above code is not creating sub folders in destination ,
I have kept the script very simple for your understanding and commented the sections.
Make sure you add all the validations for paths and error handling. Else if any of the files is giving any issue, then it wont proceed and will break the loop.
Script:
#Keeping all the sources in an array
$Sources = #("C:\Nani\Code\Relase4\database1\tables",
"C:\Nani\Code\Relase1\database1\tables",
"C:\Nani\Code\Relase2\database1\tables",
"C:\Nani\Code\Relase3\cycle1\database1\tables",
"C:\Nani\Code\Relase1\database1.02.tables")
$Destination="C\Build\database1\tables\"
#Iterating each source folder
foreach($source in $sources)
{
#Getting all the sql files under an iteration folder recursively
$files=Get-ChildItem -Path $source -Filter "*.sql" -Recurse
#Iterating all the files underneath a single source folder
foreach ($file in $files)
{
#Copying the files for a single folder to the destination
Copy-Item $file.PSPath -Destination ("$Destination" + ($file.PSParentPath | Split-Path -Leaf) + '_' + $file)
}
}
Hope it helps.
Try this, I am creating each folder first before copying files into it.
$sourceFolder = "C:\Nani\Code"
$targetFolder = "C:\Build"
$sources = Get-Childitem $sourceFolder -recurse -filter "*.sql" -Exclude $exclude | Select FullName, DirectoryName
foreach ($source in $sources)
{
$Releasepath = [regex]::match($source.DirectoryName,'C:\\Nani\\Code\\Release\d').Value
$split = $Releasepath.Replace("\","\\")
$targetfolderLeaf = $source.DirectoryName -split $split | select -Last 1
$targetfolderpath = $targetFolder+$targetfolderLeaf
if (!(Test-Path $targetfolderpath -PathType Container)) {
#Create destination folder
New-Item -Path $targetfolderpath -ItemType Directory -Force
}
Copy-Item -Path $source.FullName -Destination $targetfolderpath -Recurse -force
}

Copy-item Files in Folders and subfolders in the same directory structure of source server using PowerShell

I am struggling really hard to get this below script worked to copy the files in folders and sub folders in the proper structure (As the source server).
Lets say, there are folders mentioned below:
Main Folder: File aaa, File bbb
Sub Folder a: File 1, File 2, File 3
Sub Folder b: File 4, File 5, File 6
Script used:
Get-ChildItem -Path \\Server1\Test -recurse | ForEach-Object {
Copy-Item -LiteralPath $_.FullName -Destination \\server2\test |
Get-Acl -Path $_.FullName | Set-Acl -Path "\\server2\test\$(Split-Path -Path $_.FullName -Leaf)"
}
Output:
File aaa, File bbb
Sub Folder a (Empty Folder)
Sub Folder b (Empty Folder)
File 1, File 2, File 3, File 4, File 5, File 6.
I want the files to get copied to their respective folders (Like the source folders). Any further help is highly appreciated.
This can be done just using Copy-Item. No need to use Get-Childitem. I think you are just overthinking it.
Copy-Item -Path C:\MyFolder -Destination \\Server\MyFolder -recurse -Force
I just tested it and it worked for me.
edit: included suggestion from the comments
# Add wildcard to source folder to ensure consistent behavior
Copy-Item -Path $sourceFolder\* -Destination $targetFolder -Recurse
If you want to mirror same content from source to destination, try following one.
function CopyFilesToFolder ($fromFolder, $toFolder) {
$childItems = Get-ChildItem $fromFolder
$childItems | ForEach-Object {
Copy-Item -Path $_.FullName -Destination $toFolder -Recurse -Force
}
}
Test:
CopyFilesToFolder "C:\temp\q" "c:\temp\w"
one time i found this script, this copy folder and files and keep the same structure of the source in the destination, you can make some tries with this.
# Find the source files
$sourceDir="X:\sourceFolder"
# Set the target file
$targetDir="Y:\Destfolder\"
Get-ChildItem $sourceDir -Include *.* -Recurse | foreach {
# Remove the original root folder
$split = $_.Fullname -split '\\'
$DestFile = $split[1..($split.Length - 1)] -join '\'
# Build the new destination file path
$DestFile = $targetDir+$DestFile
# Move-Item won't create the folder structure so we have to
# create a blank file and then overwrite it
$null = New-Item -Path $DestFile -Type File -Force
Move-Item -Path $_.FullName -Destination $DestFile -Force
}
I had trouble with the most popular answer (overthinking). It put AFolder in the \Server\MyFolder\AFolder and I wanted the contents of AFolder and below in MyFolder. This didn't work.
Copy-Item -Verbose -Path C:\MyFolder\AFolder -Destination \\Server\MyFolder -recurse -Force
Plus I needed to Filter and only copy *.config files.
This didn't work, with "\*" because it did not recurse
Copy-Item -Verbose -Path C:\MyFolder\AFolder\* -Filter *.config -Destination \\Server\MyFolder -recurse -Force
I ended up lopping off the beginning of the path string, to get the childPath relative to where I was recursing from. This works for the use-case in question and went down many subdirectories, which some other solutions do not.
Get-Childitem -Path "$($sourcePath)/**/*.config" -Recurse |
ForEach-Object {
$childPath = "$_".substring($sourcePath.length+1)
$dest = "$($destPath)\$($childPath)" #this puts a \ between dest and child path
Copy-Item -Verbose -Path $_ -Destination $dest -Force
}
Here you go.
Function Backup-Files {
[CmdletBinding()]
Param (
[Parameter(Mandatory)]
[System.IO.FileInfo[]]$Source,
[Parameter(Mandatory)]
[String]$Destination
)
if (!(Test-Path $Destination)) {[void][System.IO.Directory]::CreateDirectory($Destination)}
ForEach ($File in $Source) {
$SourceRoot = $(Convert-Path $File.PSParentPath).split('\')[0]
$NewFile = $($File.FullName).Replace($SourceRoot,$Destination)
$NewDir = $($File.DirectoryName).Replace($SourceRoot,$Destination)
[void][System.IO.Directory]::CreateDirectory($NewDir)
Copy-Item -Path $File.FullName -Destination $NewFile -Force
}
}
Examples
<#
.SYNOPSIS
Copy FileInfo object or array to a new destination while retaining the original directory structure.
.PARAMETER Source
FileInfo object or array. (Get-Item/Get-ChildItem)
.PARAMETER Destination
Path to backup source data to.
.NOTES
Version (Date): 1.0 (2023-02-04)
Author: Joshua Biddle (thebiddler#gmail.com)
Purpose/Change: Initial script development.
Known Bugs:
.EXAMPLE
Backup-Files -Source $(Get-ChildItem -Path 'C:\Users\*\Documents' -Recurse -Force -Exclude 'My Music','My Pictures','My Videos','desktop.ini' -ErrorAction SilentlyContinue) -Destination "C:\Temp\UserBackup"
.EXAMPLE
Backup-Files -Source $(Get-ChildItem -Path 'C:\Users\*\Desktop' -Exclude "*.lnk","desktop.ini" -Recurse -Force -ErrorAction SilentlyContinue) -Destination "C:\Temp\UserBackup"
#>
I wanted a solution to copy files modified after a certain date and time which mean't I need to use Get-ChildItem piped through a filter. Below is what I came up with:
$SourceFolder = "C:\Users\RCoode\Documents\Visual Studio 2010\Projects\MyProject"
$ArchiveFolder = "J:\Temp\Robin\Deploy\MyProject"
$ChangesStarted = New-Object System.DateTime(2013,10,16,11,0,0)
$IncludeFiles = ("*.vb","*.cs","*.aspx","*.js","*.css")
Get-ChildItem $SourceFolder -Recurse -Include $IncludeFiles | Where-Object {$_.LastWriteTime -gt $ChangesStarted} | ForEach-Object {
$PathArray = $_.FullName.Replace($SourceFolder,"").ToString().Split('\')
$Folder = $ArchiveFolder
for ($i=1; $i -lt $PathArray.length-1; $i++) {
$Folder += "\" + $PathArray[$i]
if (!(Test-Path $Folder)) {
New-Item -ItemType directory -Path $Folder
}
}
$NewPath = Join-Path $ArchiveFolder $_.FullName.Replace($SourceFolder,"")
Copy-Item $_.FullName -Destination $NewPath
}

Copy a file including it's relative path

I need to copy a large number of files to a backup folder but I want to maintain their relative paths. I only need specific files; i.e.
C:\scripts\folder\File.ext1
C:\scripts\folder2\file2.ext2
C:\scripts\file3.ext1
But I only need to copy the ext1 files like so:
C:\backup\folder\File.ext1.bak
C:\backup\file3.ext1.bak
The source paths are of multiple depths.
This is what I have to copy the files:
$files = gci -path C:\scripts\ -recurse -include *.ext1
$files | % { Copy-Item $_ "$($_).bak"; move-item $_ -destination C:\backup\ }
This just dumps all the files into C:\backup\ and does not appear to get any of the paths. Not sure how that part would be done.
Something like this could work:
gci -path C:\scripts\ -recurse -include *.ext1 |
% { Copy-Item $_.FullName "$($_.FullName).bak"
move-item $_.FullName -destination ($_.FullName -replace 'C:\\scripts\\','C:\backup\') }
It is not clever, but it's quick & dirty and works without a lot of effort.
get-childitem returns absolute paths, but you can make them relative to the current working directory as follows:
resolve-path -relative
So to copy a filtered set of files from the current directory recursively to a destination directory:
$dest = "c:\dest"
$filter = "*.txt"
get-childitem -recurse -include $filter | `
where-object { !$_.PSIsContainer } | `
resolve-path -relative | `
% { $destFile = join-path $dest $_; new-item -type f $destFile -force | out-null; copy-item $_ $destFile; get-item $destfile; }
new-item is needed to create the parent directories
get-item provides a display of all the new files it created
Of course robocopy does all this, but there will be times when you want to do more special filtering or filename mangling...
Use robocopy.
robocopy c:\scripts c:\backup *.ext1 /s
Oops. I failed to notice you wanted to add the .bak extension too. I still think it is a good idea to use robocopy to copy the files then:
dir c:\backup -recurse -include *.ext1 | % { ren $_ "$_.bak" }
You can try this
Clear-Host
$from = "'C:\scripts\"
$to = "'C:\backup\"
$inc = #('*.ext1', '*.extx')
$files = get-childItem -path $from -include $inc -Recurse
$files | % {$dest = (Join-Path $to $($_.FullName+".bak").SubString($from.length)); $dum = New-Item -ItemType file $dest -Force; Copy-Item -Path $_ -Destination $dest -Recurse -Force }
the new-item is there in order to force path creation.
Jean Paul