Sorting files into directories with powershell - windows

I have the following problem and I would really appreciate it if I could get some help on that front. I am getting a constant flow of xml files into a folder. A XML file name can look like this. It only goes up to 1005.
1001.order-asdf1234.xml
1002.order-asdf4321.xml
I want to sort the files into uniquely named folders that are not based on the file names. A example for that would be
C:\Directory Path...\Peter (All files starting with 1001 go in there)
C:\Directory Path...\John (All files starting with 1002 go there)
How can I create a batch or a powershell script to continuously sorts files into the specified folders? Since I only have 5 folders I would like to simply specify the target folders for each and not have elaborate loops but I don't know how to do that.

The easiest way is to create a lookup Hashtable where you define which prefix ('1001' .. '1005') maps to which destination folder:
# create a Hasthable to map the digits to a foldername
$folderMap = #{
'1001' = 'Peter'
'1002' = 'John'
'1003' = 'Lucretia'
'1004' = 'Matilda'
'1005' = 'Henry'
}
# set source and destination paths
$rootFolder = 'X:\Where\the\files\are'
$destination = 'Y:\Where\the\files\should\go'
# loop over the files in the root path
Get-ChildItem -Path $rootFolder -Filter '*.xml' -File |
Where-Object { $_.BaseName -match '^\d{4}\.' } |
ForEach-Object {
$prefix = ($_.Name -split '\.')[0]
$targetPath = Join-Path -Path $destination -ChildPath $folderMap[$prefix]
$_ | Move-Item -Destination $targetPath -WhatIf
}
Remove the -WhatIf safety-switch if you are satisfied with the results shown on screen

You could use a switch statement to decide on the target folder based on the first part of the file name:
$files = Get-ChildItem path\to\folder\with\xml\files -Filter *.xml
switch($files)
{
{$_.Name -like '1001*'} {
$_ |Move-Item -Destination 'C:\path\to\Peter'
}
{$_.Name -like '1002*'} {
$_ |Move-Item -Destination 'C:\path\to\John'
}
{$_.Name -like '1003*'} {
# etc...
}
default {
Write-Warning "No matching destination folder for file '$($_.Name)'"
}
}
If you change your mind about loops, my preference would be to store the mapping in a hashtable and loop over the entries for each file:
$files = Get-ChildItem path\to\folder\with\xml\files -Filter *.xml
$targetFolders = #{
'1001' = 'C:\path\to\Peter'
'1002' = 'C:\path\to\John'
'1003' = 'C:\path\to\Paul'
'1004' = 'C:\path\to\George'
'1005' = 'C:\path\to\Ringo'
}
foreach($file in $files){
$targetFolder = $targetFolders.Keys.Where({$file.Name -like "${_}*"}, 'First')
$file |Move-Item -Destination $targetFolder
}

Related

Create an exe script to select and move all images on my computer

This PowerShell script is intended to copy photos from one or more source directories to a destination directory, organizing them by image type (JPEG, PNG, BMP, and HEIC).
It asks the user to enter the paths of the source directories and the source directories to exclude, as well as the path of the destination directory. These paths are stored in the variables $sources, $excluded, and $destination, respectively.
The script then converts the string contained in $sources into an array using the -split instruction, which allows for splitting a string based on a specified separator (here, a comma). If the user did not specify any source directories to exclude, the $excluded variable is initialized to an empty array using the Where-Object instruction.
The path of the destination directory is added to the $excluded array in order to avoid copying the photos to the destination directory itself.
The script then checks if the destination directory exists, and creates it if it does not using the New-Item instruction. It also creates destination directories for each file type (JPEG, PNG, BMP, and HEIC).
Finally, the script searches the computer's file system for files and copies the found photos to the destination directories according to their extension. It uses the Get-ChildItem instruction to retrieve the files in the source directories and the Copy-Item command to copy these files to the destination directories. The Where-Object clause is used to verify that the file is not in one of the excluded source directories.
$sources = Read-Host "Please enter the paths of the source directories (separated by commas) :"
$excluded = Read-Host "Please enter the paths of the source directories to exclude (separated by commas) :"
$destination = Read-Host "Please enter the path of the destination directory :"
$sources = $sources -split ","
$excluded = $excluded -split "," | Where-Object {$_}
$excluded += $destination
if (!(Test-Path $destination)) { New-Item -ItemType Directory -Path $destination }
"JPG", "PNG", "BMP", "HEIC" | ForEach-Object { New-Item -ItemType Directory -Path "$destination\$_" }
foreach ($source in $sources) {
if ($excluded -notcontains $source) {
"jpg", "png", "bmp", "heic" | ForEach-Object {
$extension = $_.ToUpper()
Get-ChildItem -Recurse $source -Filter "*.$_" | Where-Object {$_.Directory.FullName -notcontains $excluded} | Copy-Item -Destination "$destination\$extension"
}
}
}
This script works very well except that it still adds the photos of the excluded folders! Why ?
Replace:
$_.Directory.FullName -notcontains $excluded
with the following, which reverses the operands passed to the -notcontains operator:
$excluded -notcontains $_.Directory.FullName
Alternatively, use the related -notin operator:
$_.Directory.FullName -notin $excluded
Both -contains / -notcontains and -in / -notin test an array (collection) for containing a scalar (single object), through element-by-element equality testing. They only differ by the order of operands:
# -contains / -notcontains: array is the *LHS* (left-hand side) operand
#(1, 2, 3) -contains 2 # -> $true
# -in / -notin: array is the *RHS* (right-hand side)
2 -in #(1, 2, 3) # -> $true
I would use a regex for this to exclude the folders.
Also I'd use Group-Object to group the found files by extension, making it easier to copy to the destination folders:
$sources = Read-Host "Please enter the paths of the source directories (separated by commas) :"
$excluded = Read-Host "Please enter the paths of the source directories to exclude (separated by commas) :"
$destination = Read-Host "Please enter the path of the destination directory :"
$sources = #($sources -split "," | Where-Object {$_ -match '\S'}).Trim()
$excluded = #($excluded -split "," | Where-Object {$_ -match '\S'}).Trim() + $destination
# create a regex of the folders to exclude
# each folder will be Regex Escaped and joined together with the OR symbol '|'
$notThese = ($excluded | ForEach-Object { [Regex]::Escape($_) }) -join '|'
# enumerate the paths for the specified files
Get-ChildItem -Path $sources -Recurse -File -Include "*.JPG", "*.PNG", "*.BMP", "*.HEIC" |
Where-Object{ $_.DirectoryName -notmatch $notThese } |
Group-Object Extension |
ForEach-Object {
$targetPath = Join-Path -Path $Destination -ChildPath $_.Name.TrimStart(".").ToUpper()
# create the target path if this does not already exist
$null = New-Item -Path $targetPath -ItemType Directory -Force
# next copy the files in this group
$_.Group | Copy-Item -Destination $targetPath -Force
}

Is there a way to rename files in bulk by the name of their folder?

I'm trying to write a PowerShell script that goes into every folder in a certain directory, and every child with the parent.
Example:
Z:\Folder1\File.txt
Z:\Folder1\Picture.jpeg
Z:\Folder1\Data.csv
Z:\Folder2\File.txt
Z:\Folder2\Picture.jpeg
Z:\Folder2\Data.csv
=
Z:\Folder1\Folder1.txt
Z:\Folder1\Folder1.jpeg
Z:\Folder1\Folder1.csv
Z:\Folder2\Folder2.txt
Z:\Folder2\Folder2.jpeg
Z:\Folder2\Folder2.csv
I have this so far
dir | rename-item -NewName {$_.name -replace *,"Folder1"}
Please let me know how I can loop through every folder, and how I can rename them based on the folder name.
Thanks!
This should do what you're asking:
Get-ChildItem Z:\Folder* -Directory | Get-ChildItem -File | Rename-Item -NewName { $_.DirectoryName.split('\')[-1] + $_.Extension } -WhatIf
Adjust the folder name filter (.\Folder*) as required and remove the -WhatIf if you're satisfied with the changes it will make.
Obviously this doesn't account for where you might have a name clash if there are two files in the folder with the same extension. The following would do that:
$FilesToRename = Get-ChildItem Z:\Folder* -Directory | Get-ChildItem -File
foreach ($FileToRename in $FilesToRename) {
$NewName = $FileToRename.DirectoryName.split('\')[-1] + $FileToRename.Extension
$NewFile = Join-Path $FileToRename.Directory $NewName
$i = 0
While (Test-Path $NewFile) {
$i = $i++
$NewName = $FileToRename.DirectoryName.split('\')[-1] + "[$i]" + $FileToRename.Extension
$NewFile = Join-Path $FileToRename.Directory $NewName
}
$FileToRename | Rename-Item -NewName $NewName
}

how to copy files based on filename lenght using powershell

I need to batch copy a sort of file extensions from subfolders to specific folders for each location.
All folders have a 6 digit number
let's say folder Rood folder: BATCH
Subfolder 1: 000000
Subfolder 2: 111111
despite their extensions, most files have the name as the subfolder but some of them may have extra alphanumeric characters, therefore the script should grab only the ones that are not larger than 6 digits.
Example Subfolder1: 000000.pdf 000000.eps the script would need to grab all pdf within subfolders and export them to a PDF exclusive new folder, and the same would apply for eps files.
I know nothing about powershell but I know that something like this would work for an specific subfolder but I'm still missing the parts where it distributes them to a new PDFONLY and EPSONLY folders and the fact the I want to apply this to all the folders whiting the root folder.
Get-ChildItem -Path "C:\BATCH\*" -Include *.pdf,*.eps -Recurse | Copy-Item -Destination D:\
You can use Group-Object to group all the files by their extension and then loop over each group of objects, create a new folder with the desired name and lastly copy all objects of each group to their folders.
$target = 'D:\' # Set destination here
Get-ChildItem -Path C:\BATCH\* -Include *.py, *.ps1 -Recurse |
Group-Object Extension | ForEach-Object {
# Example of `$folderName` would be `PDF ONLY`
$folderName = '{0} ONLY' -f $_.Name.TrimStart('.').ToUpper()
$destination = Join-Path $target -ChildPath $folderName
# If the `$destination` folder doesn't exist
if(-not (Test-Path $destination)) {
# Create it
$null = New-Item $destination -ItemType Directory
}
Copy-Item -LiteralPath $_.Group.FullName -Destination $destination
}
Similar to Santiago's answer but with an extra filter and without grouping you can do this:
$destination = 'X:\somewhere'
Get-ChildItem -Path "C:\BATCH" -Include '*.pdf','*.eps' -File -Recurse |
Where-Object {$_.BaseName -match '^\d{6}$' } | # filter files with a BaseName of just 6 digits
ForEach-Object {
$targetPath = Join-Path -Path $destination -ChildPath ('{0}ONLY' -f $_.Extension.TrimStart(".").ToUpper())
# make sure the target path exists
# for directories, using the -Force switch either creates a new folder
# or returns the DirectoryInfo object of an existing folder.
$null = New-Item -Path $targetPath -ItemType Directory -Force
$_ | Copy-Item -Destination $targetPath
}

Rename all folders in Directory to single digit or character with Powershell

I have a directory in Windows 10 where all the files have names that are too long for windows to handle.
I want to delete these files.
I have discovered that renaming the folder allows me to reduce the path name enough to delete the folder.
For example Changing "Desktop" and it's subfolders to "1" (so the path is 1/1/1/1/file.filetype)
What I have tried is:
Get-ChildItem $path -Recurse -Filter * | Rename-Item -NewName { $_.name -replace *, '1'} -verbose
However this seems to throw an error on *
Is there an easy way to do this or what can I change?
Edit:
Dir | %{Rename-Item $_ -NewName ("{0}" -f $nr++)}
Works on one level but I am having trouble making that recursive through the child folders
Get-ChildItem $path -Recurse | %{Rename-Item $_ -NewName ("{0}" -f $nr++)}
throws this error among others:
Rename-Item : Cannot rename because item at 'designable.nib' does not exist.
If you know you will not loose anything by renaming all folders, this would help. This does not change filenames. This will recursively call the method to rename the folder until the process is able to find the next number.
NOTE: If you take away -Directory, it will update the filenames as well.
function RenameToLeastNumbers ($item, $number) {
try {
$newName = $item.Name -replace $item.Name, $number
Rename-Item $item.FullName -NewName $newName -ErrorAction Stop
}
catch
{
$number = $number + 1
if ($number -gt 100) {
return
}
RenameToLeastNumbers $item $number
}
}
Get-ChildItem -Directory -Recurse -Path C:\Temp\Dates | % { RenameToLeastNumbers $_ 1 }
All the folders under Dates will be renamed starting with 1. If Dates have three folders, they will be renamed to 1, 2, 3. Same thing will happen at each of their childrens and so on.
what folders look like
dir C:\temp\Dates -Name -Recurse
1
2
3
1\1
1\1\This is a text document.txt
2\1
3\1
I tried this process and did not once see the error for many many directories nested in 100s of folders. (created a test folder with nothing but directories and kept copying them within each).
function RenameFolderAndSubFolders {
param($item, $number)
$subfolders = Get-ChildItem $item.FullName -Directory
foreach ($folder in $subfolders) {
RenameFolderAndSubFolders $folder 1
}
while ($true){
try {
Write-Output "Renaming: $($item.FullName)"
Rename-Item $item.FullName -NewName $number -ErrorAction Stop
return
}
catch {}
$number = $number + 1
}
}
Get-ChildItem -Directory -Path C:\Temp\Dates | % { RenameFolderAndSubFolders -item $_ -number 1 }
Let me know how this works.

Folder deleting after script ends

I am currently writing a script that takes a folder of files, moves the first file to a folder with a specific name, then move the rest to another folder with a number for a name.
My script works however it also moves the folder and renames it too. Which section of the code is causing this?
$path = "C:\Users\User1\Desktop\MergeTest\_First\"
$FileCount = Get-ChildItem -Path $path -File | Measure-Object | %{$_.Count}
$FirstFile = Get-ChildItem -Path $path -Force -File | Select-Object -First 1
$FinalReport = "C:\Users\User1\Desktop\MergeTest\___Final\TestOutput.xlsx"
Move-Item "C:\Users\User1\Desktop\MergeTest\_First\$FirstFile" $FinalReport
$Counter = 0;
Write-host $FileCount
for($Counter = 0; $Counter -lt $FileCount; $Counter++)
{
$FileInWork = Get-ChildItem -Path $path -Force -File | Select-Object -First 1
move-item "C:\Users\User1\Desktop\MergeTest\_First\$FileInWork" "C:\Users\User1\Desktop\MergeTest\__Second\$Counter.xlsx"
Write-host "File Moved"
}
What you could do is specify the -Include *.txt condition to your move-item commands so it is only to move just .txt, .log, or whatever file type you're moving and leave the folder how it is.
I believe your code could do with some cleaning up. Now you are executing Get-ChildItem 3 times, where using it once is enough.
Also, you should try and use the Join-Path rather than constructing the path and filenames yourself.
Especially where you do "C:\Users\User1\Desktop\MergeTest\_First\$FileInWork", you should realize that Get-ChildItem returns FileInfo and/or DirectoryInfo objects; not strings.
Anyway, the below code should do what you want:
# define the path where all other paths are in
$rootPath = "C:\Users\User1\Desktop\MergeTest"
# create the working paths using the common root folder path
$filesPath = Join-Path -Path $rootPath -ChildPath '_First'
$firstDestination = Join-Path -Path $rootPath -ChildPath '___Final'
$secondDestination = Join-Path -Path $rootPath -ChildPath '__Second'
# test if the destination folders exist and if not create them
if (!(Test-Path -Path $firstDestination -PathType Container)) {
Write-Host "Creating folder '$firstDestination'"
$null = New-Item -Path $firstDestination -ItemType Directory
}
if (!(Test-Path -Path $secondDestination -PathType Container)) {
Write-Host "Creating folder '$secondDestination'"
$null = New-Item -Path $secondDestination -ItemType Directory
}
# get an array of all FileInfo objects in $filesPath
# you could consider adding -Filter '*.xlsx' here..
$allFiles = Get-ChildItem -Path $filesPath -Force -File
Write-Host 'Total number of files found: {0}' -f $allFiles.Count
# move the files
for ($i = 0; $i -lt $allFiles.Count; $i++) {
if ($i -eq 0) {
# the first file should go in the $firstDestination folder with specified name
$target = Join-Path -Path $firstDestination -ChildPath 'TestOutput.xlsx'
}
else {
# all other files go to the $secondDestination folder
# each file should have the index number as name
$target = Join-Path -Path $secondDestination -ChildPath ('{0}.xlsx' -f ($i + 1))
}
$allFiles[$i] | Move-Item -Destination $target -Force -WhatIf
}
Hope that helps
Remove the -WhatIf if you are satisfied with whatever the output on console shows.
P.S. I really think you should edit your question and change its title, because nothing in the question has to do with Folder deleting after script ends..

Resources