move files, depending on their first letter - powershell - powershell

I have 1 disk, on my home server, which has all my media in, and where my films download to. I wonder if you could suggest a quick way to move files, to different hard drives, on the letter they start with. E.g my H drive has all my media, I would like files beginning with A-F to drive D, files G-L to drive F and files M-Z to drive E. So far I have ‘Move-Item H:\done\C* D:\ -Force’ line for every letter of the alphabet. Just wondered if there was a quicker was of moving the files.
A work colleague provided me with a script, but it doesn't work, and not sure if its me doing something wrong or the script:
#get a list of files at the source
$files = gci H:\done
#loop through each item in the variable "$files" which will then on be referenced to using the variable "$file"
foreach($file in $files){
#assign the drive letter using a switch statement. Switch statements are a quick way to return a new value based on if a condition is met... in this case if the first letter of the input matches the regex statement.
$drive = switch -regex ($file.basename[0]) {
'[A-F]' {'D:\'}
'[G-L]' {'F:\'}
'[M-Z]' {'E:\'}
}
#quick way to show progress
write-host "Moving $($file.Name) to $drive" -f Cyan
#perform the move
Move-Item $file.FullName $drive -Force -Verbose
}

This should do the trick:
$files = dir C:\Users\Neko\Desktop #Whatever directory
$var1 = #(65..70);
$var2 = #(71..76);
$var3 = #(77..90);
echo work3
for($i=0;$i -lt $var1.length;$i++){ [char]$var1[$i] = $var1[$i]; }
for($i=0;$i -lt $var2.length;$i++){ [char]$var2[$i] = $var2[$i]; }
for($i=0;$i -lt $var3.length;$i++){ [char]$var3[$i] = $var3[$i]; }
foreach($file in $files){
if($file.name[0] -in $var1){$drive = 'D:\'}
if($file.name[0] -in $var2){$drive = 'F:\'}
if($file.name[0] -in $var3){$drive = 'E:\'}
write-host "Moving $($file.Name) to $drive" -f Cyan
MI -path "$file.fullname" -destination $drive -Force
}
This is very basic powershell coding. I was able to sort the files into $var1, $var2, and $var3 categories since they listed ascii characters for the letters you wanted and then converted them to letters using the for loops, a bit convoluted but this is the easiest way to do this in my opinion. You then loop through the $files variable where it takes everything from the directory you in the variable, in my case C:Users\Neko\Desktop\* and for you it would be H:\done\* and seperates them into categories from the $var1 $var2 $var3 we mentioned earlier and sets the $drive variable. It then moves the files.
ERRORS WITH YOUR COLLEAGUE'S CODE
[A-F] does is not the same as a number group like (1..100) and would not do anything except produce errors.
$file.basename isn't necessary compared to just the normal $file.name if you are only going to take the first character. Its just typing extra letters.
Overall, it was good, but not enough to accomplish your task. Full code for your case:
$files = dir H:\done
$var1 = #(65..70);
$var2 = #(71..76);
$var3 = #(77..90);
echo work3
for($i=0;$i -lt $var1.length;$i++){ [char]$var1[$i] = $var1[$i]; }
for($i=0;$i -lt $var2.length;$i++){ [char]$var2[$i] = $var2[$i]; }
for($i=0;$i -lt $var3.length;$i++){ [char]$var3[$i] = $var3[$i]; }
foreach($file in $files){
if($file.name[0] -in $var1){$drive = 'D:\'}
if($file.name[0] -in $var2){$drive = 'F:\'}
if($file.name[0] -in $var3){$drive = 'E:\'}
write-host "Moving $($file.Name) to $drive" -f Cyan
MI -path "$file.fullname" -destination $drive -Force
}

Related

Bulk renaming photos and adding letters to duplicate file names in Powershell

I have a question about a powershell script. I want to rename a bunch of photos within a folder. I have a .csv file of the old names and the new names. This is a section of that file:
OldFile NewFile
{5858AA5A-DB1B-475A-808E-0BFF0B885E5B}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Notes-20200828.jpeg
{FA1E4CEE-0AD8-4B40-A5AD-4BB22C0EE4F0}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Other-20200828.jpeg
{FD20FA44-B3D2-4A6A-B73D-F3BADC2DDE71}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
{E0DDA4CD-7783-417C-9BE0-705FFA08CD17}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
{76DC6315-942D-444C-BA04-92FC9B9FF1A5}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
{3C853453-0A0D-40B5-B3B7-B0F84F92D512}.jpeg 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
Many of the new file names will be duplicates. For those files, I want to add a letter (A,B,C, so on) in the middle of the name at an exact location.
For example, if the file, 975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg, is a duplicate, I want to add "A" right after "Vicinity", so that the file is called 975NNNN-AGUIRRESUGARASSOCSTACK-VicinityA-20200831.jpeg. The letter will always be at that exact same location (right before the third -).
This is the script I have so far. I know it's not right and I haven't been able to even attempt at adding the letter within the script. (I'm a complete Powershell newbie.)
$filesToRename = Import-CSV C:\Users\clair\OneDrive\Documents\JOA\batch_photos\Rename_Central_Aguirre.csv
foreach ($file In $filesToRename) {
if (Test-Path $file.NewFile) {
$letter = -begin { $count= 1 } -Process { Rename-Item $file.OldFile
"file-$([char](96 + $count)).jpeg"; $count++}
} else {
Rename-Item $file.OldFile $file.NewFile
}
}
Could I get some guidance on how to achieve this file naming system?
Thanks!!!
When renaming files using a character from the alphabet will mean you will only have 26 options. If that is enough for you, you can do the following:
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
$folderPath = 'D:\Test'
$filesToRename = Import-CSV C:\Users\clair\OneDrive\Documents\JOA\batch_photos\Rename_Central_Aguirre.csv
foreach ($file In $filesToRename) {
$oldFile = Join-Path -Path $folderPath -ChildPath $file.OldFile
if (Test-Path $oldFile -PathType Leaf) {
# split the new filename into workable parts
$newName = $file.NewFile
$extension = [System.IO.Path]::GetExtension($newName)
$parts = [System.IO.Path]::GetFileNameWithoutExtension($newName) -split '-'
$suffix = $parts[-1]
$prefix = $parts[0..($parts.Count -2)] -join '-'
$charToAppend = 0 # counter to go through the characters in the alphabet. 0..25
while (Test-Path (Join-Path -Path $folderPath -ChildPath $newName) -PathType Leaf) {
if ($charToAppend -gt 25) {
# bail out if al characters have been used up
throw "Cannot rename file '$($file.OldFile)', because all characters A-Z are already used"
}
$newName = '{0}{1}-{2}{3}' -f $prefix, $alphabet[$charToAppend++], $suffix, $extension
}
Rename-Item -Path $oldFile -NewName $newName
}
else {
Write-Warning "File '$($file.OldFile)' not found"
}
}
Before:
D:\TEST
{3C853453-0A0D-40B5-B3B7-B0F84F92D512}.jpeg
{5858AA5A-DB1B-475A-808E-0BFF0B885E5B}.jpeg
{76DC6315-942D-444C-BA04-92FC9B9FF1A5}.jpeg
{E0DDA4CD-7783-417C-9BE0-705FFA08CD17}.jpeg
{FA1E4CEE-0AD8-4B40-A5AD-4BB22C0EE4F0}.jpeg
{FD20FA44-B3D2-4A6A-B73D-F3BADC2DDE71}.jpeg
After:
D:\TEST
975NNNN-AGUIRRESUGARASSOCSTACK-Notes-20200828.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-Other-20200828.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-Vicinity-20200831.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-VicinityA-20200831.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-VicinityB-20200831.jpeg
975NNNN-AGUIRRESUGARASSOCSTACK-VicinityC-20200831.jpeg
I think you need to use the method.Insert(). This is a small example how it works:
I ve created a txt named 975NNNN-AGUIRRERM1-Vicinity-20200829.txt in C:\Test just for testing purpose, in your example the first code line is to identify the duplicate(s)
#Code to identify duplicates (insert your code instead of mine)
$files=Get-ChildItem -Path C:\Test -File -Name
#The following line indentifies the location of the last "-" (I understand you always have 3 "-" right?)
$DashPos=($files).LastIndexOf("-")
#This inserts on the position $DashPos, the letter "A")
$files.Insert($DashPos,"A")

Powershell remove-item won't delete files

I have a PowerShell 5.1 script that should delete some files. However, when I try to delete them, neither del nor remove-item appear to be working. I have alredy set the execution policy to Unrestricted, and launched the ISE in admin mode. Even moving the file with Move-Item didn't work.
My code:
$startROMFolderPath = "C:\Users\VincentGuttmann\Desktop\retropie-mount\roms" #Pleae enter the path to your current ROM folder here, ending in the roms folder. Note that this script will only work if the ROMs are inside the RetroPie folder.
$deleteROMFolder = $startROMFolderPath + "\delete"
Write-Host #"
Hello!
Welcome to my ROM Sorting program!
There are three main ROM types:
The ROMs that are "known good", which are marked with this tag: [!],
the bad dumps, so "known bad", which are marked [b].
Then there are the ROMs where we don't know.
The ROMs that are known good will be left in the folder.
The bad dumps will be deleted right away, as well as the ROMs which are neither German nor English
If you haven't put in the paths to your current ROM folder,
please exit the program now by pressing enter and then "n" without quotation marks.
"#
$inputted = Read-Host -Prompt 'Have you changed the paths accordingly? Yes: (y), No: anything else than (y)'
$i = 0
if($inputted -eq "y")
{
$foldersOld = #()
Get-ChildItem $startROMFolderPath -Attributes Directory |
foreach {
$foldersOld += $_
}
foreach ($name in $foldersOld) {
$currentROMFolderPath = $StartROMFolderPath + "\" + $name
$ROMList = #()
Get-ChildItem $currentROMFolderPath -File |
foreach {
$currentROMPath = $currentROMFolderPath + "\" + $_
$containsBad = $_ -match "\[b.\]"
$containsGood = $_ -match "\[!\]"
$containsLang = $_ -match ".*(\([UE]+\))|(\(U\).*\(E.*\))|(\(E.*\).*\(U\))|(\(E.*\))|(\([UKNG4A]+\))|(\(Unl\))"
if($containsBad)
{
write-host "====================================="
Write-Host $_
Write-Host "contains bad"
write-host "====================================="
Remove-Item -Path $currentROMPath -Force
}
#Put "!$containsGood -and !$containsLang" into the next bracket if you want to delete all ROMS that are neither good nor in the correct Language.
#For just keeping ROMs in the corect language, plase use "!$containsLang"
elseif(!$containsGood -and !$containsLang)
{
write-host "====================================="
Write-Host $_
Write-Host "Contains no good and no lang"
write-host $currentROMPath
write-host "====================================="
Remove-Item -Path $currentROMPath -Force
}
}
}
}
else
{
exit
}
exit

Looping through File Groups such as FileGroup159, FileGroup160, etc. in Powershell

So I got the code to work how I like it for individual files. Based on some of the suggestions below, I was able to come up with this:
$Path = "C:\Users\User\Documents\PowerShell\"
$Num = 160
$ZipFile = "FileGroup0000000$Num.zip"
$File = "*$Num*.txt"
$n = dir -Path $Path$File | Measure
if($n.count -gt 0){
Remove-Item $Path$ZipFile
Compress-Archive -Path $Path$File -DestinationPath $Path
Rename-Item $Path'.zip' $Path'FileGroup0000000'$Num'.zip'
Remove-Item $Path$File
}
else {
Write-Output "No Files to Move for FileGroup$File"
}
The only thing I need to do now is have $Num increment after the program finishes each time. Therefore the program will run, and then move $Num to 160, 161, etc. and I will not have to re initiate the code manually. Thanks for the help so far.
Your filename formatting should go inside the loop and you should use the Format operator -f to get the preceding zeros, like:
159..1250 | ForEach-Object {
$UnzippedFile = 'FileGroup{0:0000000000}' -f $_
$ZipFile = "$UnzippedFile.zip"
Write-Host "Unzipping: $ZipFile"
# Do your thing here
}

Regex for dirpaths named with letters

I have a large amount of folders that are named like this:
C:\Folders\A
C:\Folders\A\AB\ABC
C:\Folders\E\EA\EAB\EABA
C:\Folders\A\AZ\AZA
C:\Folders\B\BA\BAE
And in a PowerShell script I want to move some files (or create directory if it doesn't exist) to the right place. E.g FileABC.txt to C:\Folders\A\AB\ABC and FileBA.txt to C:\Folders\B\BA.
What I have now is this:
$naming just contains all the letter codes from a CSV.
if ($naming -match '^A$') {
$folderA = C:\Folders
New-Item -Name $naming -Path $folderA -Type Directory
}
Which creates C:\Folders\A. So I do the same for all the other top level folders (there are only 6 of them).
To match the 2 letter paths I do:
if ($naming -match '^A[A-Z]{1}$'){
$folderA2 = "C:\Folders\A\"
New-Item -Name $naming -Path $folderA2 -Type Directory
}
Which creates C:\Folders\A\AA, C:\Folders\A\AB etc.
The problem arises when I want to create the 3 or 4 letter directories:
I tried this:
if ($naming -match '^A[A-Z]{2}$') {
$folderA3 = 'C:\Folders\A\$($naming)\'
New-Item -Name $naming -Path $folderA3 -Type Directory
}
and:
if ($naming -match '^A[A-Z]{3}$') {
$folderA3 = 'C:\Folders\A\$($naming)\$($naming)\'
New-Item -Name $naming -Path $folderA3 -Type Directory
}
But files are not placed correctly. E.g FileABC.txt is moved to random places like C:\Folders\A\AK\ABC.
#edit
I also notice that folders are not created in the right place. Folders with 3 or 4 letter combos are placed at which seems random:
C:\Folders\E\EA\EBC
C:\Folders\A\AB\AZA
C:\Folders\E\EC\EFG\ECXA
I could do:
if ($naming -match '^AB[A-Z]{2}$')
if ($naming -match '^AC[A-Z]{2}$')
But then i would have to make one for each letter A-Z, which i feel should not be necessary.
Cannot totally discern the algorithm because your examples do not map to the same things, but perhaps with an approach like this, you can further refine it do to what you want. It does not use regex, but does do the mapping I believe to what you want, or close to it.
# E.g FileABC.txt to C:\Folders\A\AB\ABC and FileBA.txt to C:\Folders\B\BA.
function FileToPath($file)
{
$filePrefix = 'File'
$destPrefix = 'c:\Folders'
$fileParts = [System.IO.Path]::GetFileNameWithoutExtension($file) -split $filePrefix
$destPath = $destPrefix
$len = $fileParts[1].Length
for ($i = 0; $i -lt $len; $i++)
{
$destPath = Join-Path $destPath $fileParts[1].Substring(0,$i+1)
# or maybe the below?
#$destPath = Join-Path $destPath $fileParts[1][$i]
}
return (Join-Path $destPath $file)
}
'=== 1 ==='
$f = FileToPath 'FileABC.txt'
$f
Split-Path -Path $f -Parent
Split-Path -Path $f -Leaf
'=== 2 ==='
FileToPath 'FileBA.txt'
See also Test-Path, New-Item, Move-Item, etc.

Powershell to Split huge folder in multiple folders

I have a folder that contains many huge files. I want to split these files in 3 folders. The requirement is to get the count of files in main folder and then equally split those files in 3 child folders.
Example - Main folder has 100 files. When I run the powershell, 3 child folders should be created with 33, 33 and 34 files respectively.
How can we do this using Powershell?
I've tried the following:
$FileCount = (Get-ChildItem C:\MainFolder).count
Get-ChildItem C:\MainFolder -r | Foreach -Begin {$i = $j = 0} -Process {
if ($i++ % $FileCount -eq 0) {
$dest = "C:\Child$j"
md $dest
$j++
}
Move-Item $_ $dest
}
Here is another solution. This one accounts for the sub folders not existing.
# Number of groups to support
$groupCount = 3
$path = "D:\temp\testing"
$files = Get-ChildItem $path -File
For($fileIndex = 0; $fileIndex -lt $files.Count; $fileIndex++){
$targetIndex = $fileIndex % $groupCount
$targetPath = Join-Path $path $targetIndex
If(!(Test-Path $targetPath -PathType Container)){[void](new-item -Path $path -name $targetIndex -Type Directory)}
$files[$fileIndex] | Move-Item -Destination $targetPath -Force
}
If you need to split up the files into a different number of groups the use $groupCount of higher that 3. Can also work logic with a switch that would change $groupCount to something else if the count was greater that 500 for example.
Loop though the files one by one. Using $fileIndex as a tracker we determine the folder 0,1 or 2 in my case that the file will be put into. Then using that value we check to be sure the target folder exists. Yes, this logic could easily be placed outside the loop but if you have file and folder changes while the script is running you could argue it is more resilient.
Ensure the folder exists, if not make it. Then move that one item. Using the modulus operator, like in the other answers, we don't have to worry about how many files are there. Let PowerShell do the math.
This is super quick and dirty, but it does the job.
#Get the collection of files
$files = get-childitem "c:\MainFolder"
#initialize a counter to 0 or 1 depending on if there is a
#remainder after dividing the number of files by 3.
if($files.count % 3 -eq 0){
$counter = 0
} else {
$counter = 1
}
#Iterate through the files
Foreach($file in $files){
#Determine which subdirectory to put the file in
If($counter -lt $files.count / 3){
$d = "Dir1"
} elseif($counter -ge $files.count / 3 * 2){
$d = "Dir3"
} else {
$d = "Dir2"
}
#Create the subdirectory if it doesn't exist
#You could just create the three subdirectories
#before the loop starts and skip this
if(-Not (test-path c:\Child\$d)){
md c:\Child\$d
}
#Move the file and increment the counter
move-item $file.FullName -Destination c:\Child\$d
$counter ++
}
I think it's possible to do without doing the counting and allocating yourself. This solution:
Lists all the files
Adds a counter property which cycles 0,1,2,0,1,2,0,1,2 to each file
groups them into buckets based on the counter
moves each bucket in one command
There's scope for rewriting it in a lot of ways to make it nicer, but this saves doing the math, handling uneven allocations, iterating over the files and moving them one at a time, would easily adjust to different numbers of groups.
$files = (gci -recurse).FullName
$buckets = $files |% {$_ | Add-Member NoteProperty "B" ($i++ % 3) -PassThru} |group B
$buckets.Name |% {
md "c:\temp$_"
Move-Item $buckets[$_].Group "c:\temp$_"
}