Powershell to Split huge folder in multiple folders - powershell

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$_"
}

Related

Move multiple files from folder A to folder B

We need to move .csv files from folder where the are stored down to external server using powershell.
this is what i've tried so far but for some reason i only get message not copying and name of the files:
$DestinationFolder = "C:\d1\"
$SourceFolder = "C:\s1\"
If (-not (Test-Path $DestinationFolder) ) {
New-Item -ItemType Directory -Force -Path $DestinationFolder
}
$EarliestModifiedTime = (Get-Date).AddMinutes(200).Date # get's current time + adds 30 min
$LatestModifiedTime = (Get-Date).Date
echo($DestinationFolder); # will check time
Get-ChildItem "C:\s1\*.*" |
ForEach-Object {
if ( ($_.CreationTime -ge $EarliestModifiedTime) -and ($_.CreationTime -lt $LatestModifiedTime) ){ # i.e., all day yesterday
Copy-Item $_ -Destination $DestinationFolder -Force
Write-Host "Copied $_" }
else {
Write-Host "Not copying $_"
}
}
does it work if you simplify it and just try for a test (e.g. pick one file rather than trying to run for a delta of every 30 mins / last day)? Just thinking first you need to see if the problem is with accessing (or the formatting) of your source / destination directories or the delta logic itself. Perhaps some more error conditions would help....

ZIP a folder of files into multiple "stand-alone" ZIP files of maximum size

I have the following problem: I'm writing a Powershell script that, after generating a lot of PDF files (each in a separate subfolder of a main folder), needs to zip them.
Here is the difficulty/problem:
The destination where those ZIP files need to be unzipped (a Moodle website) has a filesize limit of 50MB, hence I need to generate multiple ZIP files of this maximum size.
Each ZIP file, however, needs to be "standalone", i.e. it must be unzipped by itself (automatically by the website), without requiring the presence of the other files.
Here below is what I've tried so far:
Direct Powershell approach:
Compress-Archive -Path "SourceFolder" -DestinationPath "Result.zip"
This however only generates a single ZIP file (of "huge" dimension).
7-Zip (command line tool) approach:
7za.exe" a -v50m "Result.zip" "SourceFolder"
This correctly generates a lot of 50MB zip files, however of the form "Result.zip.001 , Result.zip.002" which, taken alone, cannot be uncompressed as individual zip files.
Can somebody suggest me a way to achieve my goal, i.e. to separate those files into individual ZIP files of size no larger than 50MB (of course the ZIP will be in general smaller than 50MB, since the PDF files have arbitary size, but all < 50MB for sure)? The result should look like:
"Result1.zip" [49MB] , "Result2.zip" [47MB] , ... , Result16.zip [48MB], Result17.zip[7MB]
Thank you very much for any suggestion! :)
I believe you look for something like this:
$filesFolderPath = "Path of where the PDFs are"
$archivePath = "Path to where the archive will be"
$files = Get-ChildItem -Path $filesFolderPath -Recurse
foreach ($file in $files) {
$fileName = $file.Name
$filePath = $file.FullName
Compress-Archive -Path $filePath -DestinationPath "$($archivePath)\$($fileName).zip"
}
This script will basically get the list of the files (assuming you only have PDFs in the location) and will archive each one.
If you want to be sure it only archives the PDFs please change this line:
$files = Get-ChildItem -Path $filesFolderPath -Recurse | Where-Object { $_.Extension -eq ".pdf" }
UPDATE (changed the script to allow archive of multiple files if their combined size isn't over 50GB):
$filesFolderPath = "Path of where the PDFs are"
$archivePath = "Path to where the archive will be"
$files = Get-ChildItem -Path $filesFolderPath -Recurse | Where-Object { $_.Extension -eq ".pdf" }
$filesToArchive = #()
$archiveSize = 0
$counter = 0
foreach ($file in $files) {
$fileSize = [Math]::Round(($file.Length / 1MB), 2)
# Check if the combined size of the files to be archived exceeds 50 MB
if (($archiveSize + $fileSize) -gt 49) {
# Create the archive if the combined size exceeds 50 MB
$counter++
Compress-Archive -Path $filesToArchive.FullName -DestinationPath "$($archivePath)\Archive-$counter.zip"
$filesToArchive = #()
$archiveSize = 0
}
# Add the file to the list of files to be archived
$filesToArchive += $file
$archiveSize += $fileSize
}
# Create the final archive if there are any remaining files to be archived
if ($filesToArchive.Count -gt 0) {
$counter++
Compress-Archive -Path $filesToArchive.FullName -DestinationPath "$($archivePath)\Archive-$counter.zip"
}
UPDATE 2 (added the warning and archiving of a single file if exceeds the 50MB size).
All you have to do in with this update is to replace the foreach statement with the below code.
foreach ($file in $files) {
$fileSize = [Math]::Round(($file.Length / 1MB), 2)
# Check if the file is bigger than 49MB to archive it separately and write a warning
if ($fileSize -gt 49) {
$counter++
Compress-Archive -Path $file.FullName -DestinationPath "$($archivePath)\Archive-$counter.zip"
Write-Warning "The archive number '$($counter)' has a single file bigger than 50MB"
} else {
# Check if the combined size of the files to be archived exceeds 50 MB
if (($archiveSize + $fileSize) -gt 49) {
# Create the archive if the combined size exceeds 50 MB
$counter++
Compress-Archive -Path $filesToArchive.FullName -DestinationPath "$($archivePath)\Archive-$counter.zip"
$filesToArchive = #()
$archiveSize = 0
}
# Add the file to the list of files to be archived
$filesToArchive += $file
$archiveSize += $fileSize
}
}

Move Folders and files from folders/subfolders with same name (copies to be renamed)

(I have tried to rewrite this to be more clear)
I have a problem with having hundreds of folders and files (each start with ABC and then numbers 001-936) that need to be moved to a single folder with the same name. For example:
C:\folders\ABC001
C:\folders\random\ABC001\
C:\folders\files\ABC001.pdf
C:\folders\ABC002.pdf
C:\folders\ABC002\
C:\folders\needs sorting\ABC002\
That I would like to move all files and folders to a folder with the same name:
C:\folders\ABC\ABC001\
C:\folders\ABC\ABC002\
and so on...
However, sometimes the orginal folder and/or file will have a duplicate in the destination folder(or may already have since folders already exist), so I want it to add to the name (1), (2), etc.
C:\folders\ABC\ABC001\ABC001\
C:\folders\ABC\ABC001\ABC001 (1)\
C:\folders\ABC\ABC001\ABC001.pdf
C:\folders\ABC\ABC002\ABC002.pdf
C:\folders\ABC\ABC002\ABC002\
C:\folders\ABC\ABC002\ABC002 (1)\
Any help would be greatly appreciated, I have been trying to solve this for weeks (everytime I needed the files) but am new to scripting/code. I started with:
for /R "C:\folders" %x in (*abc123*.*) do move "%x" "D:\folders\abc\abc123"
Closest attempt (minor edits to another code):
function MoveFileToFolder ([string]$source,[string]$destination){
# get a list of source files
$files = Get-ChildItem -Path $source -Recurse | ?{($_.PSIsContainer)}
# verify if the list of source files is empty
if ($files -ne $null) {
foreach ($file in $files) {
$filename = $file.Name
$filebase = $file.BaseName
$fileext = $file.Extension
# verify if destination file exists
$filenameNU = $filename
if (Test-Path (Join-Path $destination $filename)) {
$n = 0
while ((Test-Path (Join-Path $destination $filenameNU)) -eq $true)
{
$filenameNU = $filebase + "(" + ++$n + ")" + $fileext
}
}
Move-Item $file.FullName (Join-Path $destination $filenameNU)
}
}
}
MoveFileToFolder C:\folders\ C:\folders\abc\abc123
(I would try and run this in Powershell for each subfolder, but it became very hard to keep up with)
Thank you for reading.

How can I compare the content of 3 directories in powershell

After looking into this for a while, I've found a lot of people trying to do the same thing but I haven't yet been able to find a complete answer so I'm hoping someone can help us!
What I would like to do is give 2 directories, A and B, and compare everything inside A to everything inside B and if there are any files or folders in B that are not in A then generate an output file detailing the path to the item that is in B and not A. From Here, Id like to use the list of items and say if any of the items in these paths contain something different to the files in 2 directories (the names will be the same so documents\folder\B.txt contains something different to desktop\folder\B.txt) generate another list showing what is different or showing the file path to items that are different if I cant show the text that is different.
I've looked into doing this with .hash but I'm not sure if that's to best way to go about it? something like:
$SourceDocs = Get-ChildItem –Path C:\Documents1 | foreach {Get-FileHash –Path $_.FullName} $DestDocs = Get-ChildItem –Path C:\Documents2 | foreach {Get-FileHash –Path $_.FullName}
In terms of what I have for the physical comparison, I originally was trying to do a 3-way comparison so the code below isn't accurate but its all I've got as yet.
$folderA = 'C:\Users\USERNAME\Desktop\Folder A'
$folderB = 'C:\Users\USERNAME\Desktop\Folder B'
$folderC = 'C:\Users\USERNAME\Desktop\Folder C'
$AChild = Get-ChildItem $folderA -Recurse
$BChild = Get-ChildItem $folderB -Recurse
$CChild = Get-ChildItem $folderC -Recurse
Compare-Object -ReferenceObject $AChild -DifferenceObject $BChild, $CChildcode
After looking into this further, Ive found out it might be better to use 3.0 rather than 2.0 so I can use -Dir more efficiently.
Any questions, let me know.
Any help greatly appreciated.
This is answer to what you want:
What I would like to do is give 2 directories, A and B, and compare everything inside A to everything inside B and if there are any files or folders in B that are not in A then generate an output file detailing the path to the item that is in B and not A.
$folder1 = "C:\Users\USERNAME\Desktop\Folder A"
$folder2 = "C:\Users\USERNAME\Desktop\Folder B"
# Get all files under $folder1, filter out directories
$firstFolder = Get-ChildItem -Recurse $folder1 | Where-Object { -not $_.PsIsContainer }
$failedCount = 0
$i = 0
$totalCount = $firstFolder.Count
$firstFolder | ForEach-Object {
$i = $i + 1
Write-Progress -Activity "Searching Files" -status "Searching File $i of $totalCount" -percentComplete ($i / $firstFolder.Count * 100)
# Check if the file, from $folder1, exists with the same path under $folder2
If ( Test-Path ( $_.FullName.Replace($folder1, $folder2) ) ) {
# Compare the contents of the two files...
If ( Compare-Object (Get-Content $_.FullName) (Get-Content $_.FullName.Replace($folder1, $folder2) ) ) {
# List the paths of the files containing diffs
$fileSuffix = $_.FullName.TrimStart($folder1)
$failedCount = $failedCount + 1
Write-Host "$fileSuffix is on each server, but does not match"
}
}
else
{
$fileSuffix = $_.FullName.TrimStart($folder1)
$failedCount = $failedCount + 1
Write-Host "$fileSuffix is only in folder 1"
$fileSuffix | Out-File "$env:userprofile\desktop\folder1.txt" -Append
}
}
$secondFolder = Get-ChildItem -Recurse $folder2 | Where-Object { -not $_.PsIsContainer }
$i = 0
$totalCount = $secondFolder.Count
$secondFolder | ForEach-Object {
$i = $i + 1
Write-Progress -Activity "Searching for files only on second folder" -status "Searching File $i of $totalCount" -percentComplete ($i / $secondFolder.Count * 100)
# Check if the file, from $folder2, exists with the same path under $folder1
If (!(Test-Path($_.FullName.Replace($folder2, $folder1))))
{
$fileSuffix = $_.FullName.TrimStart($folder2)
$failedCount = $failedCount + 1
Write-Host "$fileSuffix is only in folder 2"
$fileSuffix | Out-File "$env:userprofile\desktop\folder2.txt" -Append
}
}

Using PowerShell to move a matching set of files with same name but different extensions to two different destinations

I would like to use PowerShell to move a matching name set of files (1 job file and 1 trigger file both havening the same name just different extensions) from one directory to another. See example below.
Source directory contains job1.zip, job1.trg, job2.zip, and job2.trg. I would like to take matching job names job1.zip and job1.trg and move it to dest1folder, only if it is empty, if not move it to dest2folder. Then loop back to perform the same logic for job2.zip and job2.trg. One thing I also have to take into consideration is the Source directory may only contain job1.zip waiting for job1.trg to be transferred. I am a newbie to PowerShell and blown hours on trying to get it working with no success. Is it possible?
This is what I have so far. I get the files to move to each destination folder using IF logic, but it moves all of the files in the source directory.
$doirun = (get-childItem "d:\ftproot\pstest\").Count
$filecount = (get-childItem "d:\ftproot\ps2\").Count
if ($doirun -le 1) {exit}
$dir = get-childitem "d:\ftproot\pstest\" | Where-Object {($_.extension -eq ".zip") -or ($_.extension -eq ".trg")}
foreach ($file in $dir)
{
if ($filecount -le 2) {Move-item "d:\ftproot\pstest\$file" "d:\ftproot\ps2\"}
else {Move-item "d:\ftproot\pstest\$file" "d:\ftproot\ps3\"}
}
Not tested extensively, but I believe this should work:
$jobs = gci d:\ftproot\pstest\* -include *.zip,*.trg |
select -expand basename | sort -unique
$jobs |foreach-object {
if (test-path d:\ftproot\pstest\$_.zip -and test-path d:\ftproot\pstest\$_.trg){
if (test-path d:\ftproot\pstest\ps2\*){
move-item d:\ftproot\pstest\$_.zip d:\ftproot\pstest\ps3
move-item d:\ftproot\pstest\$_.trg d:\ftproot\pstest\ps3
}
else {
move-item d:\ftproot\pstest\$_.zip d:\ftproot\pstest\ps2
move-item d:\ftproot\pstest\$_.trg d:\ftproot\pstest\ps2
}
}