Sort photos based on name in powershell - powershell

I have used Advanced Renamer to rename all my photos by date, so they all have names such as:
2017-Oct-14_8;39_kyd.jpg
Where "8;39" is the time and "kyd" is a string of three random characters to reduce eliminate duplicate names. I would like to write a powershell script to sort them all into folders such as:
C:\Pictures\2017\Oct
Where the first directory would be the year and the second directory would be the month. If a photo does not have date taken metadata, it's name would be:
"--_;_kyd.jpg"
and I would like to sort it into a "MANUAL_SORT" folder located is C:\Pictures. I am trying to use powershell to do this and this is what I have so far:
$SourceFolder = 'C:\Pictures\Test'
$DestinationFolder = 'C:\Pictures\Test_Output'
Get-ChildItem -Path $SourceFolder -Filter *.jpg | ForEach-Object
{
$filename = $_.Name.Substring(0,7);
if ($filename.Substring(0,3) = "--_;")
{
Move-Item -Path $SourceFolder -Destination "$DestinationFolder\MAUNAL_SORT"
}
else
{
$Year = $filename.Substring(0,3)
$Month = $filename.Substring(5,7)
Move-Item -Path $SourceFolder -Destination $DestinationFolder\$Year\$Month
}
}
But I can't figure out how to use the ForEach-Object command to cycle through each picture. Can anyone suggest a method to accomplish this? Thank you!

It looks like you're using Move-Item incorrectly. Use $_.FullName as the argument to the -Path parameter. As written, you're repeatedly trying to move the entire source folder into the destination.
Your string comparison operations are wrong. Use -eq.
Your substring calls are getting one too few characters. The parameters are index and count. Index is zero-based, of course, but count is the actual number of characters you want.
Also, the $filename variable is only accomplishing an extra call to Substring(), it's not useful in the rest of the script.
gci $SourceFolder -Filter *.jpg | foreach {
$YYYY = $_.Name.Substring(0,4)
if ($YYYY -eq '--_;') {
mv $_.FullName $DestinationFolder\MANUAL_SORT\
} else {
$MM = $_.Name.Substring(5,3)
mv $_.FullName $DestinationFolder\$YYYY\$MM\
}
}

A couple of issues:
substring of name while retrieving filename, removed that code
if condition comparison - it should be '-eq'
Also please verify folder exists or not added #TODO
Get-ChildItem -Path $SourceFolder -Filter *.jpg | ForEach-Object {
$filename = $_.Name;
if ($filename.Substring(0,3) -eq "--_")
{
Move-Item -Path $_.FullName -Destination "$DestinationFolder\MAUNAL_SORT\"
}
else
{
$Year = $filename.Substring(0,3)
$Month = $filename.Substring(5,7)
#TODO: test path if directory exists else create it
Move-Item -Path $_.FullName -Destination $DestinationFolder\$Year\$Month
}
}

Related

Recursive search for string within files on a drive

I am trying to do a search for specific file within a directory with the following command:
gci -recurse -path "E:\" | select-string "searchContent" | select path
doing so gave me an insufficient memory error. I have seen other posts recommending piping it into foreach-object, but I couldn't figure out how to get it to work in my scenario. Any assistance appreciated!
When reading the file as a whole (single multiline string), your seach can be much faster than by testing line-by-line.
Also, you could speed up significantly if you could use a filename pattern as filter for the Get-ChildItem cmdlet. If you for instance only want to search through .txt files, add -Filter '*.txt'.
In any case, append switch -File so Get-ChildItem won't try to pass DirectoryInfo objects to the rest of the code.
Try:
# since we use regular expression operator `-match`, escape the word or phrase you need to find
$searchContent = [regex]::Escape('whateveryouarelookingfor')
$result = Get-ChildItem -Path 'E:\' -Recurse -File | ForEach-Object {
if ((Get-Content -Path $_.FullName -Raw) -match $searchContent) { $_.FullName }
}
A bit faster than using ForEach-Object{..} would be to use a foreach() instead (skipping processing time needed to pipe results)
# since we use regular expression operator `-match`, escape the word or phrase you need to find
$searchContent = [regex]::Escape('whateveryouarelookingfor')
$result = foreach ($file in (Get-ChildItem -Path 'E:\' -Recurse -File)) {
if ((Get-Content -Path $file.FullName -Raw) -match $searchContent) { $file.FullName }
}
Now you can display the full path and filenames on screen
$result
and save it as text file on disk
$result | Set-Content -Path ('X:\FilesContaining_{0}.txt' -f $searchContent)
Just assign it to a variable, and then have a foreach loop that assigns each one to another variable.
$files = gci -recurse -path "E:\"
foreach ($fileName in $files)
{
if ($fileName.Name -like "*searchContent*")
{
write-host $fileName.Name
}
}
I feel this should consume less memory. Can't tell for sure but you can let me know. The concept is the same but using [System.IO.StreamReader].
Note: This will keep on looking for all files it can find, if you need the loop to stop at first finding then a new condition should be added.
foreach($file in Get-ChildItem -Recurse -path "E:\" -File)
{
$reader = [System.IO.StreamReader]::new($file.FullName)
while(-not $reader.EndOfStream)
{
if($reader.ReadLine() -match 'searchContent')
{
$file.FullName
break
}
}
$reader.Dispose()
}

Moving Files based on filename

Im looking to move files based on the last half of the filename. Files look like this
43145123_Stuff.zip
14353135_Stuff.zip
2t53542y_Stuff.zip
422yg3hh_things.zip
I am only looking to move files that end in Stuff.zip
I have this in PowerShell so far but it only will move files according to the first half of a file name.
#set Source and Destination folder location
$srcpath = "C:\Powershelltest\Source"
$dstpath = "C:\Powershelltest\Destination"
#Set the files name which need to move to destination folder
$filterLists = #("stuff.txt","things")
#Get all the child file list with source folder
$fileList = Get-ChildItem -Path $srcpath -Force -Recurse
#loop the source folder files to find the match
foreach ($file in $fileList)
{
#checking the match with filterlist
foreach($filelist in $filterLists)
{
#$key = $file.BaseName.Substring(0,8)
#Spliting value before "-" for matching with filterlists value
$splitFileName = $file.BaseName.Substring(0, $file.BaseName.IndexOf('-'))
if ($splitFileName -in $filelist)
{
$fileName = $file.Name
Move-Item -Path $($file.FullName) -Destination $dstpath
}
}
}
There seems to be some differences between the state goal and what the code actually does. This will move the files to the destination directory. When you are confident that the files will be moved correctly, remove the -WhatIf from the Move-Item command.
$srcpath = "C:\Powershelltest\Source"
$dstpath = "C:\Powershelltest\Destination"
Get-ChildItem -File -Recurse -Path $srcpath |
ForEach-Object {
if ($_.Name -match '.*Stuff.zip$') {
Move-Item -Path $_.FullName -Destination $dstpath -WhatIf
}
}
Actually this can be written in PowerShell very efficiently (I hope I got the details right, let me know):
Get-ChildItem $srcpath -File -Force -Recurse |
where { ($_.Name -split "_" | select -last 1) -in $filterLists } |
Move-Item $dstpath
Alternatively, if you only want to look for this one particular filter, you can specify that directly, using wildcards:
Get-ChildItem $srcpath -Filter "*_Stuff.zip"

How to find a file and get the path using powershell?

I have some folder, the total of the folder is always different. Each folder has two file named as "ID" and "ID_DONE", for the file "ID_DONE" generate by other process. I want to remove file "ID" once "ID_DONE" generate to each folder.
I tried this, but I can not remove the "ID" file if I have more than one folder to check.
Anyone can help, please.
if(Test-Path -Path "$OpJob_Path\*\ID_DON"){
$Path_ID_DON = Get-ChildItem -Path "$OpJob_Path\*\ID_DON"
$Split = Split-Path -Path $Path_ID_DON
$Split
if(Test-Path -Path "$Split\ID")
{
Write-Host "Remove"
Remove-Item -Path "$Split\ID"
}
else{
Write-Host "Not Found"
}
}
else{
Write-Host "Continue..........."
}
Given that you're matching files across multiple directories with wildcard expression "$OpJob_Path\*\ID_DON", $Path_ID_DON will likely contain an array of files ([System.IO.FileSystem] items).
Therefore,
$Split = Split-Path -Path $Path_ID_DON
results in $Split containing an array of the parent-directory paths of the files stored in $Path_ID_DON.
If you want to remove a file named ID from all these directories, if present, use the following:
$Split | ForEach-Object {
$file = Join-Path $_ 'ID'
if (Test-Path $file) { Remove-Item $file -WhatIf }
}
-WhatIf previews the operation. Remove it once you're sure it will do what you want.
Something like this ?
Get-ChildItem "$OpJob_Path" -File -Filter "ID_DON" -Recurse | %{Remove-Item "$($_.DirectoryName)\ID" -ErrorAction SilentlyContinue}

Copy-Item by header & timestamp

I am using the code below to filter out files depending on the headers in the file.
It works like a charm, but I have a problem with that it takes all the files in the $InputDirectory.
I would like to limit it so it only takes files that are 1-2 hours old.
There are two ways where I can get the date for this process.
Filename contains timestamp = XXXXXXXXXXX_XXXXXXXX_valuereport_YYYYMMDDhhmmss.csv
The timestamp the file was created (please note we are talking about 800K-1M files in the directory and more is added every hour, so the fastest way would be appreciated.
So how do I insert something in my code, so it besides the header, only takes files that are <1-2 hours old?
Sorry about the code example, I am new to this site and not sure how to get it in the right order.
Nothing yet.
foreach ($FilePath in (Get-ChildItem $InputDirectory -File) | Select-Object -ExpandProperty FullName) {
$Header = Get-Content $FilePath -First 1
# test for a string in the header line that distincts it from the other files
if ($Header -match ';energy,Wh,') {
# the substring ';energy,Wh,' defines this file as a 'HeatMeter' file
Copy-Item -Path $FilePath -Destination $OutputPathHeat
} elseif ($Header -match ';fabrication-no,,inst-value,0,0,0;datetime,,inst-value,0,0,0;volume,m3') {
# the substring ';datetime,,inst-value,0,0,0;volume,m3' defines this file as a 'WaterMeter' file
Copy-Item -Path $FilePath -Destination $OutputPathWater
} else {
# if all key substrings above did not match, move to the 'Other' directory
Copy-Item -Path $FilePath -Destination $OutputPathOther
}
There are several ways to filter a directory listing. The easiest way is to pipe the result of Get-ChildItem through Where-Object like:
Get-ChildItem -Path $InputDirectory -File |
Where-Object { $_.CreationTime -gt (Get-Date).AddHours(-2) } |
Select-Object -ExpandProperty FullName |
ForEach-Object {
$FilePath = $_
$Header = Get-Content $FilePath -First 1
# test for a string in the header line that distincts it from the other files
if ($Header -match ';energy,Wh,') {
# the substring ';energy,Wh,' defines this file as a 'HeatMeter' file
Copy-Item -Path $FilePath -Destination $OutputPathHeat
}
elseif ($Header -match ';fabrication-no,,inst-value,0,0,0;datetime,,inst-value,0,0,0;volume,m3') {
# the substring ';datetime,,inst-value,0,0,0;volume,m3' defines this file as a 'WaterMeter' file
Copy-Item -Path $FilePath -Destination $OutputPathWater
}
else {
# if all key substrings above did not match, move to the 'Other' directory
Copy-Item -Path $FilePath -Destination $OutputPathOther
}
}
It checks that the CreationTime is greater than now - 2h. Note that the last modified (LastWriteTime) timestamp may also be suitable for your use case.

Move File : How to combine date and extension in powershell script?

I want to move file to another folder based on date and extension(eg: .zip).For now, my script can successfully move the file to another folder based on date. Unfortunately, I do not have the idea how to do the script based on extension. Can someone help me to figure out this thing ? How to combine both date and extension in one script in order to move file ? Here is my code based to move file based on date.
$fs = dir C:\move_test_3\ | Where-Object { $_.LastWriteTime.date -lt (((get-
date).addDays(-1)).date) }
if ($fs)
{
move-item -path $fs -destination "C:\move_test_4"
}
I have tried this, but it is not right.
$fs = dir C:\move_test_3\ | Where-Object { $_.LastWriteTime.date -lt (((get-
date).addDays(-1)).date) }
if ($fs -and $file.Extension.Equals('.zip'))
{
move-item -path $fs -destination "C:\move_test_4"
}
You can do this many ways. Here is a one-liner:
Get-ChildItem c:\Move_test_3\*.zip |
where {$_.lastwritetime -lt (get-date).adddays(-1)} |
move-item -destination c:\move_test_4\
Checking for file extension is done later in the code below. As you already know which extension you are interested in, you might as well specify that in the get-childitem
if ($fs) {
foreach ($file in $fs) {
if ($file.Extension -eq '.zip') {
//move $file
}
}
}
You might also just filter by .zip at the beginning
dir C:\move_test_3\ -include '.zip'