Flatten Files in Subfolders on Windows 7 (cmd or PowerShell) - powershell

I have a large volume of files organized in a very hierarchical folder structure. In this structure, the file that I care about is always located in the lowest level of the folders. As such, I'd like to flatten the directory so that it's easier to access the files that I care about. However, I'd like to preserve the 2 higher levels (Person & Project) of the folder structure.
Here's an example of the EXISTING folder directory:
Directory
Tom
Project 1
Subfolder Level A
FileA
FileB
Project 2
Subfolder Level C
FileC
FileD
Jerry
Project 1
Subfolder Level E
FileE
Here's an example of the DESIRED folder directory:
Directory
Tom
Project 1
FileA
FileB
Project 2
FileC
FileD
Jerry
Project 1
FileE
I have tried doing something like this, however this flattens all of the files into a single directory:
for /r %f in (*) do #copy "%f" .
However, this produces:
Directory
FileA
FileB
FileC
FileD
FileE
I'd appreciate any guidance that you can offer. Thanks a lot!

Here is a Powershell approach. It gets a list of the folders at the level that you want. Then it moves all the sub files up to that level. it will also remove the sub folders.
$Rootfolder = Dir directory\*\* -Directory
ForEach($folder in $Rootfolder)
{
Dir $folder.fullname -Recurse -File | Copy-Item -Destination $folder.fullname
Dir $folder.fullname -Recurse -Directory | Remove-Item -Force -Recurse -WhatIf
}
If you want it to delete, remove the -WhatIf from the last line.

Throw a couple of extra for loops around the one that works then.
e.g. First change to the name folder, then the project folder, looping through both levels.
for /D %n in (*) do (
pushd %n
for /D %p in (*) do (
pushd %p
for /r %f in (*) do #copy "%f" .
popd
)
popd
)
If you put this in a bat file, remember to replace % with %%

#echo off
pushd yourDirectory
for /d %%A in (*) do for /d %%B in ("%%A\*") do for /d %%C in ("%%B\*") do (
pushd "%%C"
for /r %%F in (*) do move /y "%%F" "%%B" >nul
popd
rd /q /s "%%C"
)
popd
%%A contains something like "yourDirectory\Tom"
%%B contains something like "yourDirectory\Tom\Project 1"
%%C contains something like "yourDirectory\Tom\Project 1\subdirectory1"
%%F contains a file to move, to any depth
At first I thought I could eliminate PUSHD/POPD and use
for /r "%%C" %%F in (*) do ... THIS DOES NOT WORK
But that doesn't work - the value after /R cannot use a FOR variable or delayed expansion because of how the command is parsed.

I tweaked #ScottC's answer and used the following code:
for /D %%n in (*) do (
pushd %%n
for /D %%p in (*) do (
pushd %%p
for /r %%f in (*.ppt) do (
move "%%f" "[ROOT_PATH_THAT_I_WANT]\%%n\%%p".
)
popd
)
popd
)
I ran this solution as a .bat file, which is why I used %% instead of %.
%%n = name (aka C:\Directory\Name)
%%p = project (aka C:\Directory\Name\Project)
%%f = file to be moved (recursively drilling through the folders and moving them up to the project level)
Ultimately, I wasn't able to get #dbenham's suggestion of deleting the empty folders to work, so I ended up using this utility: http://www.jonasjohn.de/red.htm. So far it seems pretty intuitive and like it's taking care of the problem without much effort from me :)
Thanks for the help everybody!

Related

Searching for files with the same name and 3 letter extensions

So for my task i have to find all files on my C: partition that start with the word 'printer'and they have to have an extension that contains exactly 3 letters i also have to do the same for the .dll extension only
That is a lot of work for PowerShell (my old opinion). :)
If you do it in the native operating system it is:
dir printer*.??? /s /A-D
or
dir printer*.* /s /A-D
(In both cases from the root as /s denotes subfolders. You could add some other stuff for hidden files or folders but /? will give that to you. /A-D removes directories.)
You can pull that in to PowerShell with: cmd /r dir printer*.??? /s /A-D
Now if you do want to use PowerShell Natively you can read up but it'll be like this:
Get-ChildItem -Path C:\ -Include printer*.??? -File -Recurse -ErrorAction SilentlyContinue
You can swap out *.??? for *.dll in the code to get the results you want.

Changing Multiple Filenames in multiple Folders with Batch

I have several Folder which contain from 5 to 20 Files with all different names. They get replaced every week, so the Name of each File also changes. But i Need them to have specific Names so i can upload them by using my SQL loader.
Is there a way to create a Batch file, which goes into every Folder that i specify, select all Files and changes all the names? Perfect Solution would just be a upcounting number like: file1.xml, file2.xml etc.
Since im a total newbie to Batch i searched around a bit and found following code, but it only changes the files in 1 specific Folder.
Dir *.xml | ForEach-Object -begin { $count=1 }
-process { rename-item $_ -NewName "$count.xml"; $count++ }
Update 1
I found the working code which allows me to rename the files in a Folder as i want them to be. I would just Need a code, that allows me to do this to several other Folders at the same time or automatically one after another.
#echo off & setlocal EnableDelayedExpansion
set a=1
for /f "delims=" %%i in ('dir /b *.xml') do (
ren "%%i" "!a!.xml"
set /a a+=1
)
#echo off & setlocal EnableDelayedExpansion
set a=1
rem make old name to prevent same name collision
for /D %%i in (*) do (
cd %%i
for /f "delims=" %%j in ('dir /b *.xml') do (
ren "%%j" "%%j-old.xml"
)
cd ..
)
rem rename process
for /D %%i in (*) do (
cd %%i
for /f "delims=" %%j in ('dir /b *.xml') do (
ren "%%j" "!a!.xml"
set /a a+=1
)
cd ..
)
echo Done
pause
Start .
hope it helps.
Here's a PowerShell code to do the same for several folders (sub-folders)
$path = 'Z:\'
Get-ChildItem -Path $path -Filter *.xml -Recurse | ForEach-Object -Begin {
[int]$count = '1'
} -Process {
Rename-Item -Path $_.FullName -NewName "$count.xml" -ErrorAction Stop
$count++
}

Need a script to delete certain type of files in a folder based on conditions

I am having some issues with OneNote overpopulating the drive with .onetoc2 files. I need a script or cmd command that deletes these files only if the folder that it's contained in does not have a .one file. I need this run for the entire directory.
I have a delete prompt that deletes all the files but I don't know how to get the conditional aspect of it accomplished.
DEL /S /Q c:\Folders \*.onetoc2
something like this could work in powershell
$folder
if (!(dir $folder *.one)) {
dir $folder *.onetoc2 | % {del $_.FullName -WhatIf}
}
for /f "delims=" %A in ('dir /b "c:\folder\*.onetoc2"') do if not exist "%~dpA%~nA.one" echo del "%A"
Use %%A in batch. Remove the echo statement to allow it to delete.

Moving files into sub folders based by average filesize

I need the help of you programming savants in creating a batch script or powershell script that will move and divide a group of files from one directory into 4 subdirectories based on an average total filesize. After the sort, the sub-directories should be roughly equal in terms of folder size.
Why do I need this?
I have 4 computers that I would like to utilize for encoding via FFMPEG and it would be helpful for a script to divide a folder into 4 parts (sub-directories) based on a total average size.
So lets say there are an assortment of movie files with varying different file sizes totaling to 100 GB, the script would divy the movie files and move them into 4 sub folders; each folder having around 25 GB. Doing this will allow the 4 machines to encode the sum of the data equally and efficiently.
After all that encoding I'll have 2 files, XYZ.(original Extension) and XYZ.264, A script that could compare the 2 files and delete the larger file would be extremely helpful and cut down on manual inspection.
Thank you, I hope this is possible.
#ECHO Off
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
PUSHD "%sourcedir%"
:: number of subdirectories
SET /a parts=4
:: make subdirs and initialise totalsizes
FOR /L %%a IN (1,1,%parts%) DO MD "%destdir%\sub%%a" 2>nul&SET /a $%%a=0
:: directory of sourcefiles, sort in reverse-size order
FOR /f "tokens=1*delims=" %%a IN (
'dir /b /a-d /o-s * '
) DO (
REM find smallest subdir by size-transferred-in
SET /a smallest=2000000000
FOR /L %%p IN (1,1,%parts%) DO IF !$%%p! lss !smallest! SET /a smallest=!$%%p!&SET part=%%p
REM transfer the file and count the size
ECHO(MOVE "%%a" "%destdir%\sub!part!"
REM divide by 100 as actual filelength possibly gt 2**31
SET "size=%%~za"
IF "!size:~0,-2!" equ "" (SET /a $!part!+=1) ELSE (SET /a $!part!=!size:~0,-2! + $!part!)
)
popd
GOTO :EOF
I believe the remarks should explain the method. The principle is to record the length-transferred to each subdirectory and select the least-filled as the destination for the file (processed in reverse-size order)
Since batch has a limit of 2^31, I chose to roughly divide the filesize by 100 by lopping of the last 2 digits. For files <100 bytes, I arbitrarily recorded that as 100 bytes.
You would need to change the settings of sourcedir and destdir to suit your circumstances.
The required MOVE commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MOVE to MOVE to actually move the files. Append >nul to suppress report messages (eg. 1 file moved)
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "destdir=U:\destdir"
SET "spaces= "
FOR /f "delims=" %%a IN (
'dir /b /ad "%destdir%\*"'
) DO (
PUSHD "%destdir%\%%a"
FOR /f "delims=" %%f IN (
'dir /b /a-d "*.xyz" 2^>nul'
) DO (
IF EXIST "%%f.264" (
FOR %%k IN ("%%f.264") DO (
SET "sizexyz=%spaces%%%~zf"
SET "size264=%spaces%%%~zk"
IF "!sizexyz:~-15!" gtr "!size264:~-15!" (ECHO(DEL /F /Q "%%f") ELSE (ECHO(DEL /F /Q "%%f.264")
)
)
)
popd
)
GOTO :EOF
This second batch scans the directorynames into %%a then switches teporarily to the detination directory %destfile\%%a.
Once there, we look for .xyz files and for each one found, find the corresponding .xyz.264 file.
If that exists, then we find the sizes of the files (%%~zk or %%~zf) and append that to a long string of spaces. By comparing the last 15 characters of the result as a string, we can determine which is longer.
The required DEL commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(DEL to DEL to actually delete the files.
If the .264 file is filename.264 instead of filename.xyz.264 then replace each "%%f.264" with "%%~nf.264" (the ~n selects the name-part only).
To manually enter a source directoryname, use
SET /p "sourcedir=Source directory "
To accept the source directoryname as a parameter, use
SET "sourcedir=%%~1"
To process all files, except .h264 files, change
FOR /f "delims=" %%f IN (
'dir /b /a-d "*.xyz" 2^>nul'
) DO (
to
FOR /f "delims=" %%f IN (
'dir /b /a-d "*.*" 2^>nul'
) DO if /i "%%~xf" neq ".h264" (
where *.* means "all files" and the extra if statement checks whether the extension to the filename %%f (%%~xf) is not equal to (neq) .h264 and the /i directs "regardless of case (case-Insensitive)"
This might seem like a simple request, but exact partitioning is actually a really hard problem.
The easiest way to approximate a somewhat fair partitioning is simply to sort all files (from biggest to smallest) and then distribute them one-by-one into n groups (a bit like if you were giving out cards for a card game):
# Define number of subgroups/partitions
$n = 4
# Create your destination folders:
$TargetFolders = 1..$n |ForEach-Object {
mkdir "C:\path\to\movies\sub$_"
}
# Find the movie files sort by length, descending
$Files = Get-ChildItem "C:\path\to\movies" -Recurse |Where-Object {'.mp4','.mpg','.xyz' -contains $_.Extension} |Sort-Object Length -Descending
for($i = 0; $i -lt $Files.Count; $i++)
{
# Move files into sub folders, using module $n to "rotate" target folder
Move-Item $Files[$i].FullName -Destination $TargetFolders[$i % $n]
}
If you have multiple file types that you want to include, use a Where-Object filter instead of the Filter parameter with Get-ChildItem:
$Files = Get-ChildItem "C:\path\to\movies" -File -Recurse |Where-Object {'.mp4','.mpg','.xyz' -contains $_.Extension} |Sort-Object Length -Descending
#!/bin/bash
nbr_of_dirs=4
# Go to directory if specified, otherwise execute in current directory
if [ -n "$1" ]; then
cd $1
fi
# Create output directories and store them in an array
for i in $(seq 1 $nbr_of_dirs); do
dir=dir_$i
mkdir $dir
dirs[i]=$dir
done
# For every non-directory, in decreasing size:
# find out the current smallest directory and move the file there
ls -pS | grep -v / | while read line; do
smallest_dir=$(du -S ${dirs[#]} | sort -n | head -1 | cut -f2)
mv "$line" $smallest_dir
done
Remember to keep the script file in a different directory when executing this. The script iterates over every file, so if the script was in the directory too it would be moved to one of the sub-directories.

how to copy only selected folders using robocopy?

I have a directory in which there are 36 subfolders. I want to copy the last 18 folders using robocopy. How do I do that? Is there any option which I can use?
This batch file should skip 18 folders and use robocopy with each individual folder after that.
#echo off
for /f "skip=18 delims=" %%a in (' dir /a-d /b ') do (
robocopy "%%a" "target folder" switches
)