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?)
}
Related
I am trying to create a PowerShell script to copy new and modified files from the source folder to the destination folder. I am able to copy the new file with the given script but also want to add the condition for the modified file also. Can anyone help me to achieve this.
$Sourcefolder = "C:\Users\parveen.kumar\Downloads\Source"
$Desifolder = "C:\Users\parveen.kumar\Downloads\desi"
$GetFiles = Get-ChildItem -Path $Sourcefolder
$BackUpImagesFiles = (Get-ChildItem -Path $Desifolder).Name
foreach($image in $GetFiles)
{
$fileName = $image.Name;
if($BackUpImagesFiles -notcontains $fileName)
{
Copy-Item $image.FullName -Destination $Desifolder
}
}
You can use Get-Item to find if there is a file with that name already in the destination folder or not.
If not OR the file you found is older that the one in the source folder, copy the file.
Something like this:
$Sourcefolder = "C:\Users\parveen.kumar\Downloads\Source"
$Destfolder = "C:\Users\parveen.kumar\Downloads\desi"
Get-ChildItem -Path $Sourcefolder -File | ForEach-Object {
# test if there already is a file with that name in the destination folder
$existingFile = Get-Item -Path (Join-Path -Path $Destfolder -ChildPath $_.Name) -ErrorAction SilentlyContinue
# if not existing or the existing file is older than the one in the source folder, do the copy
if (!$existingFile -or $existingFile.LastWriteTime -lt $_.LastWriteTime) {
$_ | Copy-Item -Destination $Destfolder -Force
}
}
Based on your comment, if you want to keep a copy of the file that was already in the destination folder, you can change to:
$Sourcefolder = "C:\Users\parveen.kumar\Downloads\Source"
$Destfolder = "C:\Users\parveen.kumar\Downloads\desi"
Get-ChildItem -Path $Sourcefolder -File | ForEach-Object {
# test if there already is a file with that name in the destination folder
$existingFile = Get-Item -Path (Join-Path -Path $Destfolder -ChildPath $_.Name) -ErrorAction SilentlyContinue
# if a file already exists AND is older than the one in the source folder, do the copy
if ($existingFile -and $existingFile.LastWriteTime -lt $_.LastWriteTime) {
# rename the existing file first before you overwrite with a newer file from the source folder
# for demo, add the file's last modified date to its name
$newName = '{0}_{1:yyyy-MM-dd HHmmss}{2}' -f $existingFile.BaseName,
$existingFile.LastWriteTime,
$existingFile.Extension
$existingFile | Rename-Item -NewName $newName -Force
$_ | Copy-Item -Destination $Destfolder -Force
}
elseif (!$existingFile) {
$_ | Copy-Item -Destination $Destfolder -Force
}
}
Another way as you suggested is to Move the existing files into another backup folder instead of renaming them first:
$Sourcefolder = "C:\Users\parveen.kumar\Downloads\Source"
$Destfolder = "C:\Users\parveen.kumar\Downloads\desi"
$BackupofDestfolder = "C:\Users\parveen.kumar\Downloads\just"
# make sure the destination and backup folders exist before trying to copy or move files there
$null = New-Item -Path $Destfolder -ItemType Directory -Force
$null = New-Item -Path $BackupofDestfolder -ItemType Directory -Force
Get-ChildItem -Path $Sourcefolder -File | ForEach-Object {
# test if there already is a file with that name in the destination folder
$existingFile = Get-Item -Path (Join-Path -Path $Destfolder -ChildPath $_.Name) -ErrorAction SilentlyContinue
# if a file already exists AND is older than the one in the source folder, do the copy
if ($existingFile -and $existingFile.LastWriteTime -lt $_.LastWriteTime) {
# move the existing file first before you overwrite with a newer file from the source folder
$existingFile | Move-Item -Destination $BackupofDestfolder -Force
$_ | Copy-Item -Destination $Destfolder -Force
}
elseif (!$existingFile) {
$_ | Copy-Item -Destination $Destfolder -Force
}
}
I have a small script that can successfully copy all the files from folders and subfolders and append the creation time, but the files in the subfolders do not have the creation time appended to their names.
How can I append the creation date to all files in a folder and the subfolders?
My current script is:
$path = "C:\test1"
$destination = "C:\test2"
Get-ChildItem -path $path | ForEach-Object{
$newname = $_.CreationTime.toString("yyyy-MM-dd") + $_.BaseName +$_.Extension
(Copy-Item -Recurse -Path $_.FullName -Destination ( Join-Path $destination $newname))
}
You were really close, but the -Recurse switch should have been on Get-ChildItem and within the loop you need to make sure the destination subfolder paths exist.
Try
$source = "C:\test1"
$destination = "C:\test2"
Get-ChildItem -Path $source -File -Recurse | ForEach-Object {
# create the new target folderpath for the copy
$targetPath = Join-Path -Path $destination -ChildPath $_.DirectoryName.Substring($source.Length)
# make sure the target path exists, if not create it
$null = New-Item -ItemType Directory -Path $targetPath -Force
# create a new filename with CreationDate prefixed
$newName = '{0:yyy-MM-dd}{1}{2}' -f $_.CreationTime, $_.BaseName, $_.Extension
# copy the file
$_ | Copy-Item -Destination (Join-Path -Path $targetPath -ChildPath $newname) -Force
}
While you could create your own recursive method to copy files and rename them as you go, it would be easier to use Copy-Item recursively and rename the files and folders afterwards:
$Source = "src"
$Destination = "dst"
Copy-Item -Recurse $Source $Destination
foreach ($Item in (Get-ChildItem -Recurse -File $Destination)) {
Rename-Item $Item ($Item.Name + "-" + $Item.CreationTime.toString("yyyy-MM-dd"))
}
I am in need of some assistance, I am new to PowerShell and am trying to use it to make some of my work easier. I am writing a PowerShell script to copy JPG files from one location (C:\Pictures\People\People) and moving them to a new location.
The issue is that in this new location I need to create a folder with the same name as the JPG and then another subfolder with the same name again as the JPG.
So I need to move images from C:\Pictures\People\People which I will call JPG_Image to C:\Pictures\JPG_Name\JPG_Name\'JPG_Image'
So far I found and have been working with this:
$SourceFolder = "C:\Pictures\People\People"
$TargetFolder = "C:\Pictures\"
# Find all files matching *.JPG in the folder specified
Get-ChildItem -Path $SourceFolder -Filter *.jpg |
ForEach-Object {
$ChildPath = Join-Path -Path $_.Name.Replace('.jpg','') -ChildPath $_.Name
[System.IO.FileInfo]$Destination = Join-Path -Path $TargetFolder -ChildPath $ChildPath
# Create the directory if it doesn't already exits
if( -not ( Test-Path -Path $Destination.Directory.FullName ) ){
New-Item -ItemType Directory -Path $Destination.Directory.FullName
}
Copy-Item -Path $_.FullName -Destination $Destination.FullName
}
You are making this harder on yourself than needs be.
Some enhancements to your code:
Add switch -File to the Get-ChildItem cmd so you do not also get DirectoryInfo objects
To get the filename without extension, there is a property .BaseName
Join-Path returns a string, no need to cast that into a [System.IO.FileInfo] object
If you add -Force to the New-Item cmd, there is no need to check if a folder already exists, because that will make the cmdlet either create a new folder or return the existing DirectoryInfo object.
Because we don't need that object (and the console output from it), we can just throw that away using $null = New-Item ...
Putting it all together:
$SourceFolder = "C:\Pictures\People\People"
$TargetFolder = "C:\Pictures"
# Find all files matching *.JPG in the folder specified
Get-ChildItem -Path $SourceFolder -Filter '*.jpg' -File |
ForEach-Object {
# Join-Path simply returns a string containing the combined path
# The BaseName property is the filename without extension
$ChildPath = Join-Path -Path $_.BaseName -ChildPath $_.BaseName
$Destination = Join-Path -Path $TargetFolder -ChildPath $ChildPath
# Create the directory if it doesn't already exits
# Using -Force will not give an error if the folder already exists
$null = New-Item -Path $Destination -ItemType Directory -Force
$_ | Copy-Item -Destination $Destination
}
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
}
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.