File Sorting Based on Similar File and Folder Names - powershell

Im still generally new to powershell, and I am trying to create a program that will take files based on their name, and move them into folders that have a similar name but not exactly the same.
For example, Lets say I have 3 files, Apples.txt, Grapes.txt, and Oranges.txt. And I want to move them into corresponding folders, ApplesUSA, GrapesNY, OrangesFL.
I could just hard code it using a loop and a If-Then Statement. i.e If Apples.txt exists move to ApplesUSA. But I want it to be dynamic, so if other files and folders are added later I dont have to update the code. Is there a way to write a statement that would say if FileA and FolderB are similar in name (both contain apples in the name somewhere) then move fileA to FolderB and so on.
Any help appreciated. Thanks!!!!

try Something like this
$PathWithFile="C:\temp\Test"
$PathWithDir="C:\temp\Test"
Get-ChildItem $PathWithFile -file -Filter "*.txt" | %{
$CurrentFile=$_
$Dirfounded=Get-ChildItem $PathWithDir -Directory | where {$_ -match $CurrentFile.BaseName} | select FullName -First 1
if ($Dirfounded -ne $null)
{
move-Item $CurrentFile.FullName -Destination $Dirfounded.FullName -WhatIf
}
}

A oneliner similar to #Esperento's
gci *.txt -af|%{$File=$_.FullName;gci "$($_.BaseName)*" -ad|%{Move $File -Dest $($_.FullName) -whatif}}
The verbose version:
PushD "X:\path\to\base\folder"
Get-ChildItem *.txt -File | ForEach-Object{
$File = $_.FullName
Get-ChildItem "$($_.BaseName)*" -Directory | ForEach-Object {
Move-Item $File -Destination $_.FullName -whatif
}
}
PopD
Both versions require PowerShell V3 for the -File and -Directory parameters (and their aliases -af/-ad) This can be substituted by an additional |Where-Object{ $_.PSIsContainer} respective | Where-Object{!$_.PSIsContainer}

Related

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.

Windows 10, Rename all *.jpg files in all subdirectories, with number starting from 1

I want a solution for the same problem, but in Windows 10.
Recursively rename .jpg files in all subdirectories
I tried with following powershell command,
Get-ChildItem -Recurse -Include *.jpg | % { Rename-Item $_ -NewName ('{0:D1}.jpg' -f $i++)}
but it renames the files in sequential order without resetting the index to 1 in every sub folder.
I think you need two separate Get-ChildItem cmdlets for this. The first will gather all subdirectories and when looping though that, the second will gather the files in each directory:
Get-ChildItem -Path 'X:\RootFolder\where\the\files\are' -Recurse -Directory | ForEach-Object {
$count = 1 # reset the counter for this subdir to 1
Get-ChildItem -Path $_.FullName -Filter '*.jpg' -File | ForEach-Object {
$_ | Rename-Item -NewName ('{0:D1}.jpg' -f $count++) -WhatIf
}
}
Remove the -WhatIf if you are satisfied with the results shown in the console.
P.S. the title says *.png, but your code deals with *.jpg. Doesn't matter, as long as you set your filter to the correct extension and adjust the new name in the code accordingly
As of my knowledge you have to do use it as a nested foreach:
Foreach ($directory in (Get-ChildItem -Directory)){
$i = 1
Get-ChildItem $directory.Fullname -Recurse -Include *.jpg | % { Rename-Item $_ -NewName ('{0:D1}.jpg' -f $i++)}
}
I tested it and it worked :)
If it worked for you, please mark it as the accepted answer.

Delete a list of folders in temp

I want to delete a list of folders in temp.
Name of folders are numeric- 2, 3, 4 etc..
PS C:\Users\sos$> Get-ChildItem -path C:\Temp
Is there a PowerShell way to get this scripted
Soheil Hashemi opened the bag on answering this question so lets have a look at a basic option.
You need to have all the files that are just a number from the temp directory. Let use Where-Object to match the files we are looking for.
$Path = "C:\temp"
Get-ChildItem $Path -Directory | Where-Object{$_.BaseName -notmatch "\D"} | Remove-Item -Confirm:$false -WhatIf
Get all the files from the $path and match any files where the file name (without extension) only contains numbers. Then pipe the results into Remove-Item. Remove the -WhatIf when you are sure it is finding the right files.
If you don't have PowerShell 3.0 then you can change the Where clause and remove the -Directory switch.
Get-ChildItem $Path | Where-Object{$_.BaseName -notmatch "\D" -and $_.PSIsContainer} | Remove-Item -Confirm:$false -WhatIf
your question is not complete but first create list like me
$a = #'
1.txt
2.txt
3.txt
4.txt
'#
or $a = (get-content c:\filelist.txt)
then
$a | foreach { Remove-Item c:\temp\$_ }

Get-ChildItem results looks like relative paths in Powershell

I would like to scan and move folders (and sub folders or even deeper) from one folder to another using Powershell.
Currently I'm using this pipe of commands.
Get-ChildItem -recurse -path sub\WORK -filter "* OK" | Where-Object { $_.PSIsContainer } | foreach { Move-Item -path $_ -destination sub\OK }
Unfortunately it doesn't work because the found results are relative to .\sub\WORK, when trying to move them Move-Item complains that the folders are not in the current folder:
Move-Item : Cannot find path 'C:\TMP\2011-12-12 test 2 OK' because it does not exist.
I expect that $_ would contain: 'C:\TMP\sub\WORK\2011-12-12 test 2 OK' because these are objects in Powershell and no strings like in Linux.
In case you use Get-ChildItem, be very careful. The best way is to pipe the objects to Move-Item and you don't need to think about it more:
Get-ChildItem -recurse -path sub\WORK -filter "* OK" | Where-Object { $_.PSIsContainer } | Move-Item -destination sub\OK
(no need to use Foreach-Object)
The main reason why I'm answering is this one: Get-ChildItem constructs object differently, depending on the parameters. Look at examples:
PS C:\prgs\tools\Console2> gci -include * | % { "$_" } | select -fir 5
C:\prgs\tools\Console2\-verbose
C:\prgs\tools\Console2\1UpdateDataRepositoryServices.ps1
C:\prgs\tools\Console2\22-52-59.10o52l
C:\prgs\tools\Console2\2jvcelis.ps1
C:\prgs\tools\Console2\a
PS C:\prgs\tools\Console2> gci | % { "$_" } | select -fir 5
-verbose
1UpdateDataRepositoryServices.ps1
22-52-59.10o52l
2jvcelis.ps1
a
Then if you use $_ in a cycle and PowerShell needs to convert FileInfo from Get-ChildItem to string, it gives different results. That happened when you used $_ as argument for Move-Item. Quite bad.
I think there is a bug that reports this behaviour.
You are correct that objects are being piped down the pipeline instead of strings. This is good in that it is more flexible. The drawback is that if you don't explicitly tell the system which property of the object to use you are at the mercy of the system designers. See if explicitly telling the system the property that you want will help:
Get-ChildItem -recurse -path sub\WORK -filter "* OK" | Where-Object { $_.PSIsContainer } | foreach { Move-Item -path $_.Fullname -destination sub\OK }
I just learn't that the PSPath is automatically used in Copy-Item, Move-Item etc. when you don't specify the source in a pipeline, so something like:
gci .\sub\Work | move-item -Destination .\sub\OK
(simplified example)
would work and it would use the PSPath of the passed object to determine the source.
Since the Get-ChildItem returns objects like you said, you can use Get-Member to see what the object has to offer ( that is know about its properties and methods)
Get-ChileItem path | Get-Member
You could see that FullName is one of the properties that you could use.
Here is what worked for me.
Get-ChildItem -Path .\ -Recurse -filter "* OK" | %{Join-Path -Path $_.Directory -ChildPath $_.Name } | Move-Item -Destination sub\OK

Delete files defined in an array with Powershell

Is it possible to define an array of filenames (all files in different folders) and then in a loop delete them all, or do something else?
Actually I need to create a few symbolic links using mklink to one file, putting those links in a different folders, replacing the old links if there was any.
Deleting an array of filenames is simple:
Remove-Item foo.txt,c:\temp\bar.txt,baz\baz.txt
Or via a variable:
$files = 'foo.txt','c:\temp\bar.txt','baz\baz.txt'
Remove-Item $files
And then based on all files in different folders:
$folders = 'C:\temp','C:\users\joe\foo'
Get-ChildItem $folders -r | Where {!$_.PSIsContainer} | Remove-Item -WhatIf
Remove the -WhatIf to do the actual removal.
If you want to delete a file with a specific name you could use the -Filter parameter on Get-ChildItem. This would be the best performing approach:
$folders = 'C:\temp','C:\users\joe\foo'
Get-ChildItem $folders -r -filter foo.bak | Remove-Item -WhatIf
If the name requires more sophisticated matching then you can use a regex in a Where scriptblock e.g.:
$folders = 'C:\temp','C:\users\joe\foo'
Get-ChildItem $folders -r | Where {$_.Name -match 'f\d+.bak$'} |
Remove-Item -WhatIf
something like this should work:
can't test right now, sorry
$filenames = #('filename1.txt', 'filename2.txt', 'filename3.txt')
foreach($file in $filenames)
{
#GCI recursive to find all instances of this filename
$filesToDelete = Get-ChildItem -R | where {$_.Name -eq $file}
foreach($f in $filesToDelete)
{
#delete the file that matches, etc. here
# just using Write-Host to echo out the name for now
Write-Host $f.Name
}
}
As with most powershell, you can really compress this, but wanted to extend for explanation.
You could extend this to match your needs. For example if you needed all files that contain the word "delete", you could do gci | where {$_.Name -like "$file"}