move files by names (variable last letter) to folder - powershell

We have a systeem at work where we change the last letter of the name of a file if we make a new version. We change it alphabetic and O is the firstone and then A,B,C,...
But the older versions don't need to stay in that folder any more. I am looking for a "simpel" solution in powershell to move this older files to a folder "old".
I don't know how to start in powershell (except to move to the right folder) and don't know if it is possible.
Any suggestions can help.
Set-Location -Path "Z:\PDF\2018\18-00190 StBV THV Depret Franki\2D"
Get-ChildItem | Sort-Object -Property name

In PowerShell you tend to work a lot with pipelines, creating sequences of objects and filtering, projecting or otherwise manipulating them along the way. Your problem could be solved as follows in a few steps (whether they're simple or not remains to be seen, but your requirements necessitate some custom code):
First group all files by their base file name without the suffix
Get-ChildItem -File | Group-Object { $_.Basename -creplace '_[A-O](?=$|\.)' }
This creates a grouping key which is basically the file name without the last-changed suffix. Then we have for each group all revisions that have been created for that file. E.g. for your file names 1409-EM-M-PL-7000_A.dwg.pdf you'd get a group named 1409-EM-M-PL-7000.dwg.pdf containing all versions of that file.
I'm assuming here that no letter beyond O will actually be used, but you can adapt the regex if necessary.
Sort the revisions in order:
ForEach-Object {
$_.Group | Sort-Object { $_.Basename -creplace '_O(?=$|\.)', '_0' } -Descending
}
We replace the _O temporarily with _0 for sorting here to get the correct order since it's the oldest but would usually appear as the latest version.
We also sort descending here (so we get the latest versions first) to make the next step easier, since we actually want to grab the files we have to move, not those we want to retain.
Retain the latest n versions by grabbing every file except the latest 3 in this case:
Select-Object -Skip 3
Move the remaining files to old:
Move-Item -Destination old
Putting it all together:
Get-ChildItem -File |
Group-Object { $_.Basename -creplace '_[A-O](?=$|\.)' } |
ForEach-Object {
$_.Group |
Sort-Object { $_.Basename -creplace '_O(?=$|\.)', '_0' } -Descending |
Select-Object -Skip 3
} |
Move-Item -Destination old
Stick a -WhatIf on the MoveItem to see what's being done without actually changing anything. As an example:
H:\Stuff\54664753> ls
Directory: H:\Stuff\54664753
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2019-02-13 10:10 1 old
-a---- 2019-02-13 10:02 0 1409-EM-M-PL-7000_A.dwg.pdf
-a---- 2019-02-13 10:02 0 1409-EM-M-PL-7000_B.dwg.pdf
-a---- 2019-02-13 10:02 0 1409-EM-M-PL-7000_C.dwg.pdf
-a---- 2019-02-13 10:02 0 1409-EM-M-PL-7000_D.dwg.pdf
-a---- 2019-02-13 10:02 0 1409-EM-M-PL-7000_O.dwg.pdf
-a---- 2019-02-13 10:11 0 file2_A.pdf
-a---- 2019-02-13 10:11 0 file2_O.pdf
-a---- 2019-02-13 10:11 0 file3_O.xml
-a---- 2019-02-13 10:11 0 file_A.txt
-a---- 2019-02-13 10:11 0 file_B.txt
-a---- 2019-02-13 10:11 0 file_C.txt
-a---- 2019-02-13 10:11 0 file_O.txt
H:\Stuff\54664753> Get-ChildItem |
>>> Group-Object { $_.Basename -creplace '_[A-O](?=$|\.)' } |
>>> ForEach-Object {
>>> $_.Group |
>>> Sort-Object { $_.Basename -creplace '_O(?=$|\.)', '_0' } -Descending |
>>> Select-Object -Skip 2
>>> } |
>>> Move-Item -Destination old -Whatif
What if: Performing the operation "Move File" on target "Item: H:\Stuff\54664753\1409-EM-M-PL-7000_B.dwg.pdf Destination: H:\Stuff\54664753\old\1409-EM-M-PL-7000_B.dwg.pdf".
What if: Performing the operation "Move File" on target "Item: H:\Stuff\54664753\1409-EM-M-PL-7000_A.dwg.pdf Destination: H:\Stuff\54664753\old\1409-EM-M-PL-7000_A.dwg.pdf".
What if: Performing the operation "Move File" on target "Item: H:\Stuff\54664753\1409-EM-M-PL-7000_O.dwg.pdf Destination: H:\Stuff\54664753\old\1409-EM-M-PL-7000_O.dwg.pdf".
What if: Performing the operation "Move File" on target "Item: H:\Stuff\54664753\file_A.txt Destination: H:\Stuff\54664753\old\file_A.txt".
What if: Performing the operation "Move File" on target "Item: H:\Stuff\54664753\file_O.txt Destination: H:\Stuff\54664753\old\file_O.txt".
You can also try out the individual steps by shortening the pipeline appropriately, e.g. only the initial grouping:
H:\Stuff\54664753> Get-ChildItem |
>>> Group-Object { $_.Basename -creplace '_[A-O](?=$|\.)' }
Count Name Group
----- ---- -----
1 old {old}
5 1409-EM-M-PL-7000.dwg {1409-EM-M-PL-7000_A.dwg.pdf, 1409-EM-M-PL-7000_B.dwg.pdf, 1409-EM-M-PL-7000_C.dwg.pdf, 1409-EM-M-PL-7000_D.dwg.pdf...}
2 file2 {file2_A.pdf, file2_O.pdf}
1 file3 {file3_O.xml}
4 file {file_A.txt, file_B.txt, file_C.txt, file_O.txt}
Or grouping and sorting, but not the rest:
H:\Stuff\54664753> Get-ChildItem -File |
>>> Group-Object { $_.Basename -creplace '_[A-O](?=$|\.)' } |
>>> ForEach-Object {
>>> $_.Group |
>>> Sort-Object { $_.Basename -creplace '_O(?=$|\.)', '_0' } -Descending
>>> }
Directory: H:\Stuff\54664753
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2019-02-13 10:02 0 1409-EM-M-PL-7000_D.dwg.pdf
-a---- 2019-02-13 10:02 0 1409-EM-M-PL-7000_C.dwg.pdf
-a---- 2019-02-13 10:02 0 1409-EM-M-PL-7000_B.dwg.pdf
-a---- 2019-02-13 10:02 0 1409-EM-M-PL-7000_A.dwg.pdf
-a---- 2019-02-13 10:02 0 1409-EM-M-PL-7000_O.dwg.pdf
-a---- 2019-02-13 10:11 0 file2_A.pdf
-a---- 2019-02-13 10:11 0 file2_O.pdf
-a---- 2019-02-13 10:11 0 file3_O.xml
-a---- 2019-02-13 10:11 0 file_C.txt
-a---- 2019-02-13 10:11 0 file_B.txt
-a---- 2019-02-13 10:11 0 file_A.txt
-a---- 2019-02-13 10:11 0 file_O.txt

Related

Recursive Looping of Text Files to find lowest level

I have some text files, basically called jobs, where a job can contain another job or series of jobs, which can contain another job or series of jobs, etc... Essentially calling the job it contains.
The idea is to iterate through the top most file, step into the next lower file and so on until we reach the "lowest level".
The script I currently use is a loop, within a loop, within a loop. If I need to reach further down, I need to add another loop. In this example, we ignore the contents of the file having "JOB" in it.
Is there a better way to loop or iterate through all this without just adding more ForEach-Objects within ForEach-Objects?
cd C:\Files
$files = Get-ChildItem -Name
$files | ForEach-Object {
$filename1 = $_
write-host "$filename1"
$step1 = Get-Content $filename1
$step1 | ForEach-Object {
$filename2 = $_
write-host "`t$filename2" #We tab to show 2nd level job is inside 1st level job
$step2 = Get-Content $filename2
$step2 | ForEach-Object {
$filename3 = $_
$write-host "`t`t$filename3"
$step3 = Get-Content $filename3
$step3 | ForeEach-Object {#keep adding loops}
}
}
}
The jobs/files are contained all in a single directory as:
FILE1A
FILE1B
FILE1C
FILE2
FILE3
FILE4
etc..
Content of each file may look like:
JOB FILE1B
No limit to how many "JOB ???" may be contained inside the file.
There really is no convention to the naming of these files/jobs. When running the script, the result should look like this:
FILE1A
FILE1B
FILE1C
FILE2
FILE3
FILEXYZ
FILEZYX
FILE123
FILEABC
As the others in comments have mentioned, one might use recursion to accomplish this.
I hope I understood the question in my mock up.
I have some files that look like this below, some containing lines with paths to other files and some not (0 length empty files)
Directory: C:\temp\powershell\recursive_tests
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 18.08.2021 19:31 133 A1.txt
-a--- 18.08.2021 19:14 0 A1a.txt
-a--- 18.08.2021 19:31 133 A1b.txt
-a--- 18.08.2021 19:18 0 A1b1.txt
-a--- 18.08.2021 19:18 0 A1b2.txt
-a--- 18.08.2021 19:18 0 A1b3.txt
-a--- 18.08.2021 19:14 0 A1c.txt
-a--- 18.08.2021 19:31 133 B1.txt
-a--- 18.08.2021 19:31 133 B1a.txt
-a--- 18.08.2021 19:18 0 B1a1.txt
-a--- 18.08.2021 19:18 0 B1a2.txt
-a--- 18.08.2021 19:18 0 B1a3.txt
-a--- 18.08.2021 19:14 0 B1b.txt
-a--- 18.08.2021 19:31 133 B1c.txt
-a--- 18.08.2021 19:18 0 B1c1.txt
-a--- 18.08.2021 19:18 0 B1c2.txt
-a--- 18.08.2021 19:18 0 B1c3.txt
As an example A1.txt looks like this
C:\temp\powershell\recursive_tests\A1a.txt
C:\temp\powershell\recursive_tests\A1b.txt
C:\temp\powershell\recursive_tests\A1c.txt
I imagine the code to process through them could look something like this
function Get-FileInFile {
param(
[string[]]$Paths,
$Level = 0
)
foreach ($path in $Paths) {
Write-Host "$("`t" * $Level)$Path"
$lines = Get-Content -Path $Path
$newLevel = $Level + 1
$lines | ForEach-Object {
get-fileinfile -Path $_ -Level $newLevel
}
}
}
Output
PS C:\temp\powershell\recursive_tests> get-fileinfile 'C:\temp\powershell\recursive_tests\A1.txt', 'C:\temp\powershell\recursive_tests\B1.txt'
C:\temp\powershell\recursive_tests\A1.txt
C:\temp\powershell\recursive_tests\A1a.txt
C:\temp\powershell\recursive_tests\A1b.txt
C:\temp\powershell\recursive_tests\A1b1.txt
C:\temp\powershell\recursive_tests\A1b2.txt
C:\temp\powershell\recursive_tests\A1b3.txt
C:\temp\powershell\recursive_tests\A1c.txt
C:\temp\powershell\recursive_tests\B1.txt
C:\temp\powershell\recursive_tests\B1a.txt
C:\temp\powershell\recursive_tests\B1a1.txt
C:\temp\powershell\recursive_tests\B1a2.txt
C:\temp\powershell\recursive_tests\B1a3.txt
C:\temp\powershell\recursive_tests\B1b.txt
C:\temp\powershell\recursive_tests\B1c.txt
C:\temp\powershell\recursive_tests\B1c1.txt
C:\temp\powershell\recursive_tests\B1c2.txt
C:\temp\powershell\recursive_tests\B1c3.txt
Here I pass in only the 2 top level files A1.txt and B1.txt. Since all the files exist in the same folder I cannot use Get-ChildItem and just foreach through every file or files will be processed more than once, first being called initially by me and then again when their path is encountered in the sublevel files, so instead I just pass in the 2 that I know are the roots.

How can I rename files by prepending a sequence number to the original file names?

guys does anyone know how can i do this? I am trying to list some files in a numerical order by adding 1, 2, 3 and so on to the beginning of the file names while also keeping the files' original names.
Here are the codes i tried
$nr = 1
Dir -path C:\x\y\deneme | %{Rename-Item $_ -NewName (‘{0} $_.Name.txt’ -f $nr++ )}
dir | select name
This code just orders the files like 1, 2, 3... Without keeping the original names.
$n = 1
Get-ChildItem *.txt | Rename-Item -NewName { $_.Name -replace $_.Name ,'{0} $_.Name' -f $n++}
This one did not work like i thought.
Try the following, which renames all .txt files in the current dir. by prepending a sequence number to them:
$n = 1
Get-ChildItem *.txt |
Rename-Item -WhatIf -NewName { '{0} {1}' -f ([ref] $n).Value++, $_.Name }
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
The ([ref] $n).Value++ trick makes up for the fact that delay-bind script blocks run in a child scope of the caller, where the caller's variables are seen, but applying ++ (or assigning a value) creates a transient, local copy of the variable (see this answer for an overview of PowerShell's scoping rules).
[ref] $n in effect returns a reference to the caller's variable object, whose .Value property can then be updated.
As for what you tried:
'{0} $_.Name.txt', as a single-quoted string, is interpreted verbatim by PowerShell; you cannot embed variable references in such strings; for that you need double-quoting ("...", and you'd also need $(...) in order to embed an expression such as $_.Name) - see the bottom section of this answer for an overview of PowerShell's string literals.
So yeah, I agree with #Abraham, I don't see a scenario where you can rename the files but also retain the original files without copying them :)
This should do the trick:
$i = 0; Get-ChildItem x:\path\to\files | ForEach-Object {
$i++
$destPath = Join-Path $_.DirectoryName -ChildPath "$i $($_.Name)"
Copy-Item -Path $_.FullName -Destination $destPath
}
Example:
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 6/24/2021 7:08 PM 2 1 testfile0.txt
-a---- 6/24/2021 7:08 PM 2 2 testfile1.txt
-a---- 6/24/2021 7:08 PM 2 3 testfile2.txt
-a---- 6/24/2021 7:08 PM 2 4 testfile3.txt
-a---- 6/24/2021 7:08 PM 2 5 testfile4.txt
-a---- 6/24/2021 7:08 PM 2 testfile0.txt
-a---- 6/24/2021 7:08 PM 2 testfile1.txt
-a---- 6/24/2021 7:08 PM 2 testfile2.txt
-a---- 6/24/2021 7:08 PM 2 testfile3.txt
-a---- 6/24/2021 7:08 PM 2 testfile4.txt

Get file which is closest to a given date

I need a script which will get me the file which is closest to a given date, and need some help please.
For example:
$GivenDate = [datetime]"06/26/2017 10:30"
Get-ChildItem $backupDirectory -Filter "*.diff"
Output looks like this:
Mode LastWriteTime Length Name
-a---- 25.06.2017 15:30 506368 db1_backup_2017_06_25_153001_5520722.diff
-a---- 26.06.2017 7:30 1597952 db1_backup_2017_06_26_073001_6387310.diff
-a---- 26.06.2017 9:30 675840 db1_backup_2017_06_26_093001_6217913.diff
-a---- 26.06.2017 11:30 657408 db1_backup_2017_06_26_113001_1234104.diff
-a---- 26.06.2017 13:30 675328 db1_backup_2017_06_26_133000_9901392.diff
-a---- 26.06.2017 15:30 673792 db1_backup_2017_06_26_153001_5430241.diff
How can I select the file that is closest to to $givenDate?
Calculate a TimeSpan between the LastWriteTime property value and your $GivenDate, then sort on the absolute value (the duration) of the timespan:
$Closest = Get-ChildItem $backupDirectory -Filter *.diff |Sort {(New-TimeSpan $GivenDate $_.LastWriteTime).Duration()} |Select -First 1
variation of #Mathias R. Jessen solution (duration and timespan are not necessary)
Get-ChildItem $backupDirectory -file -Filter *.diff | sort {($GivenDate - $_.LastWriteTime)} | Select -First 1

modify script for duplicate renamed files

Using the code below in PowerShell, I'm renaming many pictures. How do I modify the code to manage duplicates? Note; many of the photos have the SAME CreationTime value. As they occur, I need them to be output as:
yyyyMMdd-HHmm-1
yyyyMMdd-HHmm-2
yyyyMMdd-HHmm-3
etc.
Script:
Dir | Rename-Item -NewName {$_.CreationTime.toString("yyyyMMdd-HHmm") + ".jpg"}
Add more to your scriptblock to check paths and cycle through until it finds something unique:
Dir | Rename-Item -NewName {
$NewName=$_.CreationTime.toString("yyyyMMdd-HHmm") + ".jpg";
$i=1
While(Test-Path ".\$NewName"){
$NewName=$_.CreationTime.toString("yyyyMMdd-HHmm") + "-$i.jpg"
$i++
}
$NewName
}
Edit: Hm, I'm not sure why it would do that unless you didn't copy my code right. I ran it against a test folder and ended up with:
PS C:\temp\test> gci
Directory: C:\temp\test
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 10/12/2016 11:13 AM 35228885 20161012-1113.jpg
-a---- 10/12/2016 11:28 AM 31413221 20161012-1128.jpg
-a---- 10/12/2016 11:37 AM 33243498 20161012-1137.jpg
-a---- 10/12/2016 4:42 PM 2110424 20161012-1642-1.jpg
-a---- 10/12/2016 4:42 PM 3300892 20161012-1642-2.jpg
-a---- 10/12/2016 4:42 PM 3295345 20161012-1642-3.jpg
-a---- 10/12/2016 4:42 PM 101138881 20161012-1642.jpg
Edit2: Ok, fixed the issue where it can't manage more than 10 of a file with the same datetime.
try this, but modify the path C:\temp with your path directory
$listcouple=gci -File -Path "C:\temp" | select name, fullname, #{Name="Datstring";Expression={$_.CreationTime.toString("yyyyMMdd-HHmm") }} | group Datstring
foreach ($item in $listcouple)
{
if ($item.Count -gt 1)
{
$compteur=0
foreach ($value in $item.Group)
{
$compteur++
$newname= $value.Datstring + "-" + $compteur.ToString() + ".jpg"
Rename-Item -path $value.fullname -newname $newname
}
}
else
{
$newname= $item.Group[0].Datstring + ".jpg"
Rename-Item -path $item.Group[0].fullname -newname $newname
}
}

Finding a file that has highest number in the filename using powershell

Sorry guys..I am new to powershell. Would be great if someone help with the following scenario:
I have couple of files in a folder c:\test
sample.x.x.1
sample.x.x.2
sample.x.x.3
sample.x.x.4
sample.x.x.5
I want to find the name of the file which has the highest number in its name in the given folder. In the above example, 5 is the highest number and the script should return the output filename as sample.x.x.5
Thanks in advance!
Sorting file names with numbers is quite a problem, as there are two ways. The first one sets them to alphabetical order. That is, 0, 1, 11, 111, 2,... The second one uses natural order. That is, 0, 1, 2, 11, 111.... This is surprisingly tricky and about every third programmer is confused with this.
There's a good answer already, which I'll refer like so,
# Create files 1..5
for($i=1;$i -le 5; ++$i) { set-content sample.x.x.$i -Value $null }
# Tricksy! Create file .10 to confuse asciibetic/natural sorting
set-content sample.x.x.10 -Value $null
ls # Let's see the files
Directory: C:\temp\test
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2015-09-28 10:29 0 sample.x.x.1
-a---- 2015-09-28 10:29 0 sample.x.x.10
-a---- 2015-09-28 10:29 0 sample.x.x.2
-a---- 2015-09-28 10:29 0 sample.x.x.3
-a---- 2015-09-28 10:29 0 sample.x.x.4
-a---- 2015-09-28 10:29 0 sample.x.x.5
# Define helper as per linked answer
$ToNatural = { [regex]::Replace($_, '\d+$', { $args[0].Value.PadLeft(20,"0") }) }
# Sort with helper and check the output is natural result
gci | sort $ToNatural -Descending | select -First 1
Directory: C:\temp\test
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2015-09-28 10:29 0 sample.x.x.10
Alphabetical sorting.
PS C:\Users\Gebb> #("sample.x.x.1", "sample.x.x.5", "sample.x.x.11") | sort
sample.x.x.1
sample.x.x.11
sample.x.x.5
Numerical sorting.
PS C:\Users\Gebb> #("sample.x.x.1", "sample.x.x.5", "sample.x.x.11") |
sort -Property #{Expression={[Int32]($_ -split '\.' | select -Last 1)}}
sample.x.x.1
sample.x.x.5
sample.x.x.11
Largest number.
PS C:\Users\Gebb> #("sample.x.x.1", "sample.x.x.5", "sample.x.x.11") |
sort -Property #{Expression={[Int32]($_ -split '\.' | select -Last 1)}} |
select -Last 1
sample.x.x.11