Copy files located in multiple subfolder, using a mildmatch search - powershell

I have tried googling my problems for some time now, without a solution that suits my needs. So hopefully you can help me.
I have some subfolders that contain millions of files. I would like to search these subfolders for filenames containing somerandomtext.
This is what I have, but it does not copy anything to my $Newlocation folder.
$Include = #"
20190421
20190422
20190423
20190424
"#
$BaseFolder = "D:\FoldersContainingFiles"
$NewLocation = "D:\FolderForCopy\"
Get-ChildItem -Path $BaseFolder -Recurse -Include * |
Where-Object {$Include -contains $_.BaseName } |
Copy-Item -Destination $NewLocation
No error code, only no files are being moved.

Your code will only give you files that are named exactly as the example in your variable, which is:
20190421
20190422
20190423
20190424
And that is under no circumstances a valid filename.
I'm not sure what you mean by mildmatch. If you mean, that the filename of your files contains a date amongst other things, this would solve your task:
$Include = "20190421|20190422|20190423|20190424"
$BaseFolder = "D:\FoldersContainingFiles"
$NewLocation = "D:\FolderForCopy\"
Get-ChildItem -Path $BaseFolder -recurse |
Where-Object {$_.BaseName -match $Include }|
Copy-Item -Destination $NewLocation
If your filename is exactly the same as included in your variable, you can use this regex instead:
$Include = "^(20190421|20190422|20190423|20190424)$"
The rest of the code is the same.

Related

Excluding Folders with Get-ChildItem - Need Help Debugging a Script

I've searched through both StackOverflow and SuperUser to try to figure this out, and I'm still getting plagued by a problem I can't figure out how to fix. I know it's something simple, but after playing with it for an hour I'm still stumped. Simple question: how the heck do I tell Get-Childitem to exclude folders?
Right up front here's the code that doesn't work:
$sourceDir="E:\Deep Storage"
$targetDir="W:\Deep Storage"
$excludeThese = 'Projects2','Projects3','Projects4';
Get-ChildItem -Path $sourceDir -Directory -Recurse |
where {$_.fullname -notin $excludeThese} |
Get-ChildItem -Path $sourceDir | ForEach-Object {
$num=1
$nextName = Join-Path -Path $targetDir -ChildPath $_.name
while(Test-Path -Path $nextName)
{
$nextName = Join-Path $targetDir ($_.BaseName + "_$num" + $_.Extension)
$num+=1
}
$_ | Move-Item -Destination $nextName -Force -Verbose -WhatIf
}
}
The underlying concept here already works:
$sourceDir="E:\Deep Storage"
$targetDir="W:\Deep Storage"
Get-ChildItem -Path $sourceDir -File -Recurse | ForEach-Object {
$num=1
$nextName = Join-Path -Path $targetDir -ChildPath $_.name
while(Test-Path -Path $nextName)
{
$nextName = Join-Path $targetDir ($_.BaseName + "_$num" + $_.Extension)
$num+=1
}
$_ | Copy-Item -Destination $nextName -Verbose
}
Basically what this does is it moves folders from one place to another, and if files exist in both places, it renames files coming in. It helps keep my archive drive clear. But there are three folders there that I want to exclude because I still pull assets from them regularly, so I don't need those files moved.
Hence the difference between the two code samples: in the first one, I'm trying to get Get-Childitem to exclude a specific trio of folders, while this second one just grabs everything all at once.
I tried just doing a straight -Exclude with $excludeThese as the variable, without success; I tried skipping the variable approach altogether and just putting the folder names in after -Exclude. Still didn't work. I also tried putting in the entire path to the folders I wanted to exclude. No good--no matter what I did, the -WhatIf showed that the script was trying to move everything, including the folders I was theoretically excluding.
The last trick I tried was one I came across here on SO, and that was to go a gci with the exclude argument first, then do another gci after it. That still failed, so now I have to turn to the experts for help.
I would use a regex string created from the (escaped) directory names to exclude to make sure files withing these folders are ignored.
Also, by using a lookup Hashtable of all file names already present in the target folder, figuring out if a file with a certain name already exists is extremely fast.
$sourceDir = 'E:\Deep Storage'
$targetDir = 'W:\Deep Storage'
$excludeThese = 'Projects2','Projects3','Projects4';
# create a regex string with all folder names to exclude combined with regex OR (|)
$excludeDirs = ($excludeThese | ForEach-Object { [Regex]::Escape($_) }) -join '|'
# create a lookup Hashtable and store the filenames already present in the destination folder
$existingFiles = #{}
Get-ChildItem -Path $targetDir -File | ForEach-Object { $existingFiles[$_.Name] = $true }
Get-ChildItem -Path $sourceDir -File -Recurse |
Where-Object {$_.DirectoryName -notmatch $excludeDirs} |
ForEach-Object {
# construct the new filename by appending an index number if need be
$newName = $_.Name
$count = 1
while ($existingFiles.ContainsKey($newName)) {
$newName = "{0}_{1}{2}" -f $_.BaseName, $count++, $_.Extension
}
# add this new name to the Hashtable so it exists in the next run
$existingFiles[$newName] = $true
# use Join-Path to create a FullName for the file
$newFile = Join-Path -Path $targetDir -ChildPath $newName
$_ | Move-Item -Destination $newFile -Force -Verbose -WhatIf
}
Assuming the excluded directories are at the top:
$sourceDir="E:\Deep Storage"
$excludeThese = 'Projects2','Projects3','Projects4'
get-childitem $sourcedir -exclude $excludethese | get-childitem -recurse

Rename files in subfolders

I am trying to rename files in subfolders in a certain pattern, but I am stuck.
The situation is as follows: I have multiple folders which are sometimes named as the target filename depending on the length, but the name does not really matter.
In each folder are always 2 files: the Target-File with a random name and the correct extension, and the Source-File which is always the correct BaseName with a txt-extension.
For example:
Folder1\7393028473.docx
Folder1\January.txt
Folder2\9373930843.pdf
Folder2\February.txt
My goal is to rename every not-txt-file with the Basename of the txt-file. Executed, it should be like:
Folder1\January.docx
Folder1\January.txt
Folder2\February.pdf
Folder2\February.txt
With gci I was able to create both lists but didn't find a good way for the renaming.
$SourceName = gci -File -Recurse | Where {$_.Extension -ne ".txt"}
$TargetName = gci -File -Recurse | Where {$_.Extension -eq ".txt"}
I did also try to use gci for renaming, but was not able to tell it to use the newname based on the txt-file:
gci -File -Recurse | Where {$_.Extension -ne ".txt"} | Rename-Item -NewName {$_.extension -eq ".txt"}
This only renamed the .docx-file to "FALSE" because the filename already exists.
What I did not try (but would be ok) is to not only rename the file, but also move it to the parent directory.
This is one way to do it but it would fail as soon as there are 2 or more files with a different extension than .txt but having the same extension. It would also fail as soon as one folder has more than one .txt file.
# Get all folders under 'TargetDirectory'
Get-ChildItem TargetDirectory -Directory -Recurse | ForEach-Object {
# For each sub-folder, get their files
$childs = $_.EnumerateFiles()
# Filter and split the child files by their extension
$txt, $notTxt = $childs.Where({ $_.Extension -eq '.txt' }, 'Split')
# Use the BaseName of the '.txt' File but the Extension of
# the file being renamed
$notTxt | Rename-Item -NewName { $txt.BaseName + $_.Extension }
}
Thanks for your reply and sorry for my late reply.
I tried your code but its not working correctly:
The NewName is created correctly, but the problem is the rename-function or rather the notTxt list because it only contains the item itself but not hte full path.
When I copy the file which should be renamed into the parent-directory your code does work in the file in the parent-directory.
There was another answer which apperntly was deleted but did work.
I also tried a foreach-loop in one of my tries but didn't get the NewName to work.
I don't know why, but I didn't consider creating the NewName with a variable, which was done in the deleted answer:
$folders = gci -Directory -Recurse
foreach ($folder in $folders) {
$targetFile = gci $folder | Where {$_.Extension -ne ".txt"}
$sourceFile = gci $folder | Where {$_.Extension -eq ".txt"}
$newName = $sourceFile.BaseName + $targetFile.Extension
Rename-Item $targetFile.FullName $newName
}
Of course you can try and get your code to work, but I can make do with this code.
Thank you very much for your help.

Exclude multiple folders from get-childitem

I have this script that compares 2 directories with each other if it matches it copies it to the other directory. But i need 2 folders to be excluded on the source folder because there are old files there. I excluded one folder but i can't add an second one. Can someone help me out? (I'm a beginner in Powershell) I know the foreach loop is empty, this is for testing purposes.
$aDir = "C:\Replace TEST SCRIPT\A"
$bDir = "C:\Replace TEST SCRIPT\Y"
$aFiles = Get-ChildItem -Path "$bDir\" -Exclude "Folder1","Folder2" | Get-ChildItem -Path "$bDir\*.pdf" -Recurse -File | select -exp FullName
ForEach ($file in $aFiles) {
$laatste = (Get-Item $file).LastWriteTime
$filenaam = Split-Path -Path "$file" -Leaf
if(Test-Path -Path "C:\Replace TEST SCRIPT\A\$filenaam") {
Write-Output "$filenaam exists in $aDir. Copying."
Copy-Item -Path "$file" -Recurse -Destination "$aDir"
} else {
}
}
You can do it the following way:
$aFiles = Get-ChildItem -Path "$bDir\" -Exclude "PDF","folder2" | Get-ChildItem -filter "*.pdf" -Recurse -File | select -exp FullName
Btw.
There is already a post to your certain question:
How can I exclude multiple folders using Get-ChildItem -exclude?
Please take a look at it and let us know.

Move Items relative to current location in Powershell

I need to traverse a folder (ParentLogFolder), find logs and move them from the relative folder (SUBLOG) to a subfolder (CompressedLogFolder)
ParentLogfolder
---SubLog
------CompressedLogFolder
---SubLog
------CompressedLogFolder
Folder structure, not limited to 2 SubLogfolders, can be 100+
$Path = "C:\ParentLogFolder"
$Pattern = "*.log"
$date = get-date
$AgeLimit = $date.AddDays(-31)
Get-ChildItem -Filter $Pattern -path $path -recurse | Where-Object
{$_.LastWriteTime -lt $AgeLimit} | Move-Item -Path
I can't figure out how to get the files of the parent folder into a variable and add the subfolder as destination in the Move-Item part.
Anyone out there, who can help answer this?
Don't mind the $Date part of the code, it works, it's just there because it needs to be in the solution.
Instead of onliner, consider emphasis on code clarity. That is, break the process in substeps by divide and conquer approach. It's easier to check variables for incorrect values when the pipeline isn't used too much. Like so,
$allFiles = Get-ChildItem -Filter $Pattern -path $path -recurse
$oldFiles = $allFiles | ? { $_.LastWriteTime -lt $ageLimit }
foreach($file in $oldFiles) {
$archiveFolder = join-path $file.DirectoryName 'someArchiveFolder'
$destination = join-path $archiveFolder $file.Name
move-item -whatif $file.FullName $destination
}
The -whatif switch will print what the move command would do. Remove it to actually move files.

Is it possible to increase the speed of this search and copy snippet?

I've got this script to search through a remote server for specific files I've listed in a CSV. However the process is very slow and I'm wondering if it's my code that makes it so. Can I improve it somehow? I've thought that simply making a directory list and searching it for the files in my CSV might be faster to avoid recursively looking through every file.
$source = "\\server1\Scanning_Maps"
$destination = "\\server1\Transfer\TEST"
$searchFiles = Import-CSV 'C:\Users\user1\Desktop\test.csv' -Header ("filename")
ForEach($File in $searchFiles)
{
Get-ChildItem -Path $source -Recurse | Where-Object { $_.Name -match $File.filename } | Copy-Item -Destination $destination
}
You would need to do some testing but on top of jisaak's findings you can reduce this even further by only returning files you want to copy as supposed to all files and then post processing.
I've thought that simply making a directory list and searching it for the files in my CSV might be faster to avoid recursively looking through every file.
Using -Include you can pass it the array of filenames and it should only search for and return the files that you want.
Also if you are adding the header anyway I going to guess that you have only a one column csv? If that is the case you don't even need to bother with that cmdlet and only use Get-Content.
$searchFiles = Get-Content 'C:\Users\user1\Desktop\test.csv'
If that is a CSV file and you are using header to just work the first column then ignore that tidbit. If it is a csv we should at least expand the column so you just have a string array instead of an object one.
$searchFiles = Import-CSV 'C:\Users\user1\Desktop\test.csv' -Header ("filename") | Select-Object -ExpandProperty filename
Now lets try using -Include. Note that it only works with -Recurse and might do nothing without it.
Get-ChildItem -Path $source -Include $searchFiles -Recurse | Copy-Item -Destination $destination
There, no more foreach loop required. That should be even faster.
You run the Get-ChildItem cmdlet for every file in $searchFiles you should put it outside the foreachloop:
$source = "\\server1\Scanning_Maps"
$destination = "\\server1\Transfer\TEST"
$searchFiles = Import-CSV 'C:\Users\user1\Desktop\test.csv' -Header ("filename")
$sourceList = Get-ChildItem -Path $source -Recurse
ForEach($File in $searchFiles)
{
$sourceList | Where-Object { $_.Name -match $File.filename } | Copy-Item -Destination $destination
}