Move files to subfolders if they match in Powershell etc? - powershell

I've googled for a while now but I'm unable to find any solution for this.
So I have a bunch of files in a folder, and in this folder I have subfolders.
I want to move those files to the subfolders if they match any of these.
Like this:
some random text yellow bananas more text.txt -> \yellow bananas
some other text red apples this is text.txt -> \red apples
Example - files:
Propulsion_mål_2020.jpg
Axevalla Vivid Wise As Goop.jpg
Dagens stjärna Cyber Lane.jpg
640px Elian Web heat.jpg
...
Example - directories:
Propulsion
Vivid Wise As
Cyber Lane
Vitruvio
...
Target:
1st file goes to 1st directory
2nd file goes to 2nd directory
3rd file goes to 3rd directory
4th file match no directory and goes nowhere
Is it doable?
Btw, it's possible that more than one subfolder matches the filename. If so, it doesn't matter which subfolder the file is moved to.

here's one way to do the job ... [grin] there is nearly zero error checking or handling, so you may need to add that. nor is there any record of what was done/not-done.
what it does ...
sets the constants
all one of them. [grin]
creates the files & dirs to work with
when you are ready to work with your own data, remove the entire #region/#endregion block.
gets a list of the dirs in the target location
creates a regex OR of the names of those dirs
gets a list of files in the target dir
iterates thru those files
tests for a match of the .BaseName property of each file against the dir name regex from earlier
if YES, creates a full dir name & moves the file
if NO, writes a warning to the warning stream
that is on by default, so you otta see it when such a file is found.
finishes iterating thru the file list
the code ...
$SourceDir = "$env:TEMP\user3764769"
#region >>> create some files & dirs to work with
# when ready to do this for real, remove this entire block
if (-not (Test-Path -LiteralPath $SourceDir))
{
# the $Null suppresses unwanted "what was done" output
$Null = New-Item -Path $SourceDir -ItemType 'Directory' -ErrorAction 'SilentlyContinue'
}
#'
Propulsion_mal_2020.jpg
Axevalla Vivid Wise As Goop.jpg
Dagens stjarna Cyber Lane.jpg
640px Elian Web heat.jpg
'# -split [System.Environment]::NewLine |
ForEach-Object {
$Null = New-Item -Path $SourceDir -Name $_ -ItemType 'File' -ErrorAction 'SilentlyContinue'
}
#'
Propulsion
Vivid Wise As
Cyber Lane
Vitruvio
'# -split [System.Environment]::NewLine |
ForEach-Object {
$Null = New-Item -Path $SourceDir -Name $_ -ItemType 'Directory' -ErrorAction 'SilentlyContinue'
}
#endregion >>> create some files & dirs to work with
$DirList = Get-ChildItem -LiteralPath $SourceDir -Directory
# the "|" is what regex uses for `-or`
$RegexDL = $DirList.Name -join '|'
$FileList = Get-ChildItem -LiteralPath $SourceDir -File
foreach ($FL_Item in $FileList)
{
# the matched value is stored in $Matches[0]
if ($FL_Item.BaseName -match $RegexDL)
{
$DirName = $Matches[0]
$FullDirName = Join-Path -Path $SourceDir -ChildPath $DirName
Move-Item -LiteralPath $FL_Item.FullName -Destination $FullDirName
}
else
{
Write-Warning ''
Write-Warning ( 'No matching directory was found for [ {0} ].' -f $FL_Item.Name)
Write-Warning ' The file was not moved.'
}
} # end >>> foreach ($FL_Item in $FileList)
output with one file that does not match any dir in the list ...
WARNING:
WARNING: No matching directory was found for [ 640px Elian Web heat.jpg ].
WARNING: The file was not moved.

Related

How to pipe Rename-Item into Move-Item (powershell)

I'm in the process of writing up a PowerShell script that can take a bunch of .TIF images, rename them, and place them in a new folder structure depending on the original file name.
For example, a folder containing the file named:
ABC-ALL-210316-0001-3001-0001-1-CheckInvoice-Front.TIF
would be renamed to "00011CIF.TIF", and placed in the following folder:
\20220316\03163001\
I've been trying to put together a code to perform this task, and I got one to work where I had two different "ForEach" methods. One would do a bunch of file renaming to remove "-" and shorten "CheckInvoiceFront" to "CIF" and such. Then the second method would again pull all .TIF images, create substrings of the image names, and create folders from those substrings, and then move the image to the new folder, shortening the file name. Like I said, it worked... but I wanted to combine the ForEach methods into one process. However, each time I try to run it, it fails for various reasons... I've tried to change things around, but I just can't seem to get it to work.
Here's the current (non-working) code:
# Prompt user for directory to search through
$sorDirectory = Read-Host -Prompt 'Input source directory to search for images: '
$desDirectory = Read-Host -Prompt 'Input target directory to output folders: '
Set-Location $sorDirectory
# Check directory for TIF images, and close if none are found
Write-Host "Scanning "$sorDirectory" for images... "
$imageCheck = Get-ChildItem -File -Recurse -Path $sorDirectory -include '*.tif'
$imageCount = $imageCheck.count
if ($imageCount -gt 0) {
Write-Host "Total number of images found: $imageCount"
""
Read-Host -Prompt "Press ENTER to continue or CTRL+C to quit"
$count1=1;
# Rename all images, removing "ABCALL" from the start and inserting "20", and then shorten long filetype names, and move files to new folders with new names
Clear-Host
Write-Host "Reformatting images for processing..."
""
Get-ChildItem -File -Recurse -Path $sorDirectory -include '*.tif' |
ForEach-Object {
Write-Progress -Activity "Total Formatted Images: $count1/$imageCount" -Status "0--------10%--------20%--------30%--------40%--------50%--------60%--------70%--------80%--------90%-------100" -CurrentOperation $_ -PercentComplete (($count1 / $imageCount) * 100)
Rename-Item $_ -NewName $_.Name.Replace("-", "").Replace("ABCALL", "20").Replace("CheckInvoiceFront", "CIF").Replace("CheckInvoiceBack", "CIB").Replace("CheckFront", "CF").Replace("CheckBack", "CB") |Out-Null
$year = $_.Name.SubString(0, 4)
$monthday = $_.Name.Substring(4,4)
$batch = $_.Name.SubString(12, 4)
$fulldate = $year+$monthday
$datebatch = $monthday+$batch
$image = $_.Name.SubString(16)
$fullPath = "$desDirectory\$fulldate\$datebatch"
if (-not (Test-Path $fullPath)) { mkdir $fullPath |Out-Null }
Move-Item $_ -Destination "$fullPath\$image" |Out-Null
$count1++
}
# Finished
Clear-Host
Write-Host "Job complete!"
Timeout /T -1
}
# Closes if no images are found (likely bad path)
else {
Write-Host "There were no images in the selected folder. Now closing..."
Timeout /T 10
Exit
}
Usually this results in an error stating that it's can't find the path of the original file name, as if it's still looking for the original non-renamed image. I tried adding some other things, but then it said I was passing null values. I'm just not sure what I'm doing wrong.
Note that if I take the everything after the "Rename-Item" (starting with "$year =") and have that in a different ForEach method, it works. I guess I just don't know how to make the Rename-Item return its results back to "$_" before everything else tries working on it. I tried messing around with "-PassThru" but I don't think I was doing it right.
Any suggestions?
As Olaf points out, situationally you may not need both a Rename-Item and a Move-Item call, because Move-Item can rename and move in single operation.
That said, Move-Item does not support implicit creation of the target directory to move a file to, so in your case you do need separate calls.
You can use Rename-Item's -PassThru switch to make it output a System.IO.FileInfo instance (or, if a directory is being renamed, a System.IO.DirectoryInfo instance) representing the already renamed file; you can directly pass such an instance to Move-Item via the pipeline:
Get-ChildItem -File -Recurse -Path $sorDirectory -include '*.tif' |
ForEach-Object {
# ...
# Use -PassThru with Rename-Item to output a file-info object describing
# the already renamed file.
$renamedFile = $_ | Rename-Item -PassThru -NewName $_.Name.Replace("-", "").Replace("ABCALL", "20").Replace("CheckInvoiceFront", "CIF").Replace("CheckInvoiceBack", "CIB").Replace("CheckFront", "CF").Replace("CheckBack", "CB")
# ...
# Pass $renamedFile to Move-Item via the pipeline.
$renamedFile | Move-Item -Destination "$fullPath\$image"
# ...
}
As for your desire to:
make the Rename-Item return its results back to "$_"
While PowerShell doesn't prevent you from modifying the automatic $_ variable, it is better to treat automatic variables as read-only.
Therefore, a custom variable is used above to store the output from Rename-Item -PassThru
You need -passthru and -destination:
rename-item file1 file2 -PassThru | move-item -Destination dir1

Powershell dropping characters while creating folder names

I am having a strange problem in Powershell (Version 2021.8.0) while creating folders and naming them. I start with a number of individual ebook files in a folder that I set using Set-Location. I use the file name minus the extension to create a new folder with the same name as the e-book file. The code works fine the majority of the time with various file extensions I have stored in an array beginning of the code.
What's happening is that the code creates the proper folder name the majority of the time and moves the source file into the folder after it's created.
The problem is, if the last letter of the source file name, on files with the extension ".epub" end in an "e", then the "e" is missing from the end of the created folder name. I thought that I saw it also drop "r" and "p" but I have been unable to replicate that error recently.
Below is my code. It is set up to run against file extensions for e-books and audiobooks. Please ignore the error messages that are being generated when files of a specific type don't exist in the working folder. I am just using the array for testing and it will be filled automatically later by reading the folder contents.
This Code Creates a Folder for Each File and moves the file into that Folder:
Clear-Host
$SourceFileFolder = 'N:\- Books\- - BMS\- Books Needing Folders'
Set-Location $SourceFileFolder
$MyArray = ( "*.azw3", "*.cbz", "*.doc", "*.docx", "*.djvu", "*.epub", "*.mobi", "*.mp3", "*.pdf", "*.txt" )
Foreach ($FileExtension in $MyArray) {
Get-ChildItem -Include $FileExtension -Name -Recurse | Sort-Object | ForEach-Object { $SourceFileName = $_
$NewDirectoryName = $SourceFileName.TrimEnd($FileExtension)
New-Item -Name $NewDirectoryName -ItemType "directory"
$OriginalFileName = Join-Path -Path $SourceFileFolder -ChildPath $SourceFileName
$DestinationFilename = Join-Path -Path $NewDirectoryName -ChildPath $SourceFileName
$DestinationFilename = Join-Path -Path $SourceFileFolder -ChildPath $DestinationFilename
Move-Item $OriginalFileName -Destination $DestinationFilename
}
}
Thanks for any help you can give. Driving me nuts and I am pretty sure it's something that I am doing wrong, like always.
String.TrimEnd()
Removes all the trailing occurrences of a set of characters specified in an array from the current string.
TrimEnd method will remove all characters that matches in the character array you provided. It does not look for whether or not .epub is at the end of the string, but rather it trims out any of the characters in the argument supplied from the end of the string. In your case, all dots,e,p,u,b will be removed from the end until no more of these characters are within the string. Now, you will eventually (and you do) remove more than what you intended for.
I'd suggest using EndsWith to match your extensions and performing a substring selection instead, as below. If you deal only with single extension (eg: not with .tar.gz or other double extensions type), you can also use the .net [System.IO.Path]::GetFileNameWithoutExtension($MyFileName) method.
$MyFileName = "Teste.epub"
$FileExt = '.epub'
# Wrong approach
$output = $MyFileName.TrimEnd($FileExt)
write-host $output -ForegroundColor Yellow
#Output returns Test
# Proper method
if ($MyFileName.EndsWith($FileExt)) {
$output = $MyFileName.Substring(0,$MyFileName.Length - $FileExt.Length)
Write-Host $output -ForegroundColor Cyan
}
# Returns Tested
#Alternative method. Won't work if you want to trim out double extensions (eg. tar.gz)
if ($MyFileName.EndsWith($FileExt)) {
$Output = [System.IO.Path]::GetFileNameWithoutExtension($MyFileName)
Write-Host $output -ForegroundColor Cyan
}
You're making this too hard on yourself. Use the .BaseName to get the filename without extension.
Your code simplified:
$SourceFileFolder = 'N:\- Books\- - BMS\- Books Needing Folders'
$MyArray = "*.azw3", "*.cbz", "*.doc", "*.docx", "*.djvu", "*.epub", "*.mobi", "*.mp3", "*.pdf", "*.txt"
(Get-ChildItem -Path $SourceFileFolder -Include $MyArray -File -Recurse) | Sort-Object Name | ForEach-Object {
# BaseName is the filename without extension
$NewDirectory = Join-Path -Path $SourceFileFolder -ChildPath $_.BaseName
$null = New-Item -Path $NewDirectory -ItemType Directory -Force
$_ | Move-Item -Destination $NewDirectory
}

Move every folder to top level including content recursively in Powershell

how do I recursively move all directories to the top level including all their sub directories.
The files in the directories should be copied as well.
And if a directory already exists its contents should be merged and all the files should remain (maybe via renaming the files)
e.g.
dir1
----img1
----img2
----dir2
--------img1
--------img2
------------dir1
------------img1
------------img2
------------img3
dir4
----img1
----img2
----img3
becomes
dir1
----img1
----img1_1
----img2
----img2_2
----img3
dir2
----img1
----img2
dir4
----img1
----img2
----img3
My approach was something like that.
Get-ChildItem $SOURCE_PATH -Recurse |
Foreach-Object {
$IS_DIR = Test-Path -Path $_.FullName -PathType Container
if ($IS_DIR) {
Move-Item $_.FullName -dest ($DESTPATH + "/" + $_.Name)
}
}
thanks.
Instead of moving directories I would move individual files to have control over destination name of each file.
This code is untested, it is just intended to give you an idea. Tweak as needed.
# Use parameter -File to operate on files only
Get-ChildItem $SOURCE_PATH -File -Recurse | Foreach-Object {
# Get name of parent directory
$parentDirName = $_.Directory.Name
# Make full path of destination directory
$destSubDirPath = Join-Path $DESTPATH $parentDirName
# Create destination directory if not exists (parameter -Force).
# Assignment to $null to avoid unwanted output of New-Item
$null = New-Item $destSubDirPath -ItemType Directory -Force
# Make desired destination file path. First try original name.
$destFilePath = Join-Path $destSubDirPath $_.Name
# If desired name already exists, append a counter until we find an unused name.
$i = 1
while( Test-Path $destFilePath ) {
# Create a new name like "img1_1.png"
$destFilePath = Join-Path $destSubDirPath "$($_.BaseName)_$i.$($_.Extension)"
$i++
}
# Move and possibly rename file.
Move-Item $_ $destFilePath
}

Powershell Move file to new destination based on trimmed file name

I have a folder where files get dropped, I wish to pull the files from that folder and move to a new folder based on part of the file name. If the new folder is missing then create it.
I have attempted to put together the below, however it throws an error about the path already existing and doesn't move the file.
File names can be any thing with out pattern except the last 16 characters of the file, I am removing these and using the remaining as the folder name.
I am really new to scripting so if i have made a silly mistake explanations are appreciated.
Edit
I have played with different orders of operations, added a "-Force" to the new item command, tried with using "Else" and not "If (!(".
I am now at the point where it proudly displays the new directory and then stops.
Could i add the move-item part to a new for each loop so it is processed after the dir is created and tested? If so how do you arrange the { } parts?
Edit 2
I finally have it working, updated script below, the movie-item command was having issues when running into special characters in file names, in my case it was square brackets. The -literalpath switch fixed that for me.
Thanks every one for your input.
Updated script 3.0
#Set source folder
$source = "D:\test\source\"
#Set destination folder (up one level of true destination)
$dest = "D:\test\dest\"
#Define filter Arguments
$filter = "*.txt"
<#
$sourcefile - finds all files that match the filter in the source folder
$trimpath - leaves $file as is, but gets just the file name.
$string - gets file name from $trimpath and converts to a string
$trimmedstring - Takes string from $trimfile and removes the last 16 char off the end of the string
Test for path, if it exists then move on, If not then create directory
Move file to new destination
#>
pushd $source
$sourcefile = Get-ChildItem $source -Filter $filter
foreach ($file in $sourcefile){
$trimpath = $file | split-path -leaf
$string = $trimpath.Substring(0)
$trimmedstring = $string.Substring(0,$string.Length-16)
If(!(Test-Path -path "$dest\$trimmedstring")){New-Item "$dest\$trimmedstring" -Type directory -Force}
move-Item -literalpath "$file" "$dest\$trimmedstring"
}
You may have to tweak the paths being used but the below should work.
$sourcefiles = ((Get-ChildItem $source -Filter $filter).BaseName).TrimEnd(16)
foreach ($file in $sourcefiles)
{
if(!(Test-Path "$dest\$file")){
New-item -ItemType directory -path "$dest\$file"
}
Move-Item "$source\$file" "$dest\file"
}
I finally have it working, updated script below, the movie-item command was having issues when running into special characters in file names, in my case it was square brackets. The -literalpath switch fixed that for me. Thanks every one for your input.
#Set source folder
$source = "D:\test\source\"
#Set destination folder (up one level of true destination)
$dest = "D:\test\dest\"
#Define filter Arguments
$filter = "*.txt"
<#
$sourcefile - finds all files that match the filter in the source folder
$trimpath - leaves $file as is, but gets just the file name.
$string - gets file name from $trimpath and converts to a string
$trimmedstring - Takes string from $trimfile and removes the last 16 char off the end of the string
Test for path, if it exists then move on, If not then create directory
Move file to new destination
#>
pushd $source
$sourcefile = Get-ChildItem $source -Filter $filter
foreach ($file in $sourcefile){
$trimpath = $file | split-path -leaf
$string = $trimpath.Substring(0)
$trimmedstring = $string.Substring(0,$string.Length-16)
If(!(Test-Path -path "$dest\$trimmedstring")){New-Item "$dest\$trimmedstring" -Type directory -Force}
move-Item -literalpath "$file" "$dest\$trimmedstring"
}

Powershell move files with special characters

How can I move a file with special characters in it? I am not allowed to rename the file.
My file is: File.Server.Windows.2003.[SP2].01232005.txt
# dFold = The destination folder in the format of \\drive\folder\SubFolder\
# tDir = Root Target Directory on NAS
# sFold = The name of the subfolder that the files will go into
$dFold = "$tDir$sFold"
# sDir = Source Directory
# $File = Original file name as seen in the source Directory
Move-Item -Path $sDir$File -Destination $dFold -force
When I try to execute the above code it does not move the file. I can add some Write-Host statements and it says it moves the file, but it really don't.
Write-Host "Now moving " $File "to " $dFold"\"
Move-Item -Path $sDir$File -Destination $dFold -force
# Now we just write put what went where or not
Write-Host $File "Was Moved to:" $dFold
Output:
Now moving File.Server.Windows.2003.[SP2].01232005.txt to \\NAS\Inventory\Servers\
File.Server.Windows.2003.[SP2].01232005.txt Was Moved to: \\NAS\Inventory\Servers
Did you try:
Move-Item -LiteralPath $sDir$File -Destination $dFold
Move-Item allows wildcard matches when using the -Path parameter, so a substring [SP2] is interpreted as a single character 'S', 'P', or '2' instead of the string '[SP2]'. Using the parameter -LiteralPath instead of -Path prevents that.