i´ve got little bit stucked with my powershell script.
I would like to run through multiple folders, grab files based on their last modified date and copy them to a new location.
There i have to rename them to a specific convention based on it´s original Filename.
What i wrote only runs through the first part and copy files successfully but not rename them afterwards. Of course when i run the script a second time it renames the files...
File convention is:
120_00001_000_002222_202201_20220124_121833_Formular - Copy.pdf
result should be
2222_120_Memory 01-2022_012022.pdf
this is what i got already
$path = "G:\Temp"
$Target = "K:\Local"
$Max_days = "-60" #Max Days past
$Curr_date = Get-Date
$files = get-childitem $Target *.pdf
Get-ChildItem -Path $path -Recurse -Filter 120_*.pdf |
Where-Object {
$_.LastWriteTime `
-gt (Get-Date $Curr_date.AddDays($Max_days)) `
} | ForEach-Object { $_ | Copy-Item -Destination $Target -Force -PassThru }
foreach($pdf in $files)
{
$split = $pdf.name -replace ".pdf" -split "_"
$newname = "$($split[3].TrimStart("0"))_$($split[0])_$("Memory") $($split[4].Substring($split[4].Length - 2, 2))-$($split[5].Substring(0,4))_$($split[4].Substring($split[4].Length - 2, 2))$($split[5].Substring(0,4))$($pdf.Extension)"
write-verbose "Original: $($pdf.name)" -verbose
write-verbose "NewName: $($newname)" -verbose
Rename-Item $pdf.FullName -NewName $newname -verbose
}
Thanks in adavnced
Edited the Question to more precision.
As commented, you could do this in one loop and rename the file while copying.
Try below:
$path = 'G:\Temp'
$Target = 'K:\Local'
$Max_days = -60 # Max Days in the past
$refDate = (Get-Date).AddDays($Max_days).Date # set to midnight
# get the files of interest
Get-ChildItem -Path $path -Recurse -Filter '120_*_*_*_*_*_*_*.pdf' -File |
Where-Object { $_.LastWriteTime -gt $refDate } |
ForEach-Object {
# rename the file to match the new file naming convention
$split = $_.BaseName -split "_"
# just for clarity, using this example:
# '120_00001_000_002222_202201_20220124_121833_Formular - Copy.pdf'
# $split[0] --> 120 used unchanged
# $split[1] --> 00001 unused
# $split[2] --> 000 unused
# $split[3] --> 002222 used without leading zeros
# $split[4] --> 202201 used, only the last two digits (month)
# $split[5] --> 20220124 used, only the first four digits (year)
# $split[6] --> 121833 unused
# $split[7] --> Formular - Copy unused
# these elements are used more than once, so for convenience store in separate variables
$month = $split[4].Substring($split[4].Length - 2, 2)
$year = $split[5].Substring(0,4)
# construct the new file name
$newName = '{0}_{1}_Memory {2}-{3}_{2}{3}{4}' -f $split[3].TrimStart("0"),
$split[0],
$month,
$year,
$_.Extension
# construct the complete target path and filename
$targetFile = Join-Path -Path $Target -ChildPath $newName
# now copy the file with a new name to the target folder
$_ | Copy-Item -Destination $targetFile -Force
}
I've used the -f Format operator to construct the new filename, because I believe this makes the code easier to read.
I did not take into consideration that naming collisions might occur (file with that new name already in the target folder).
If that can happen, you need to tell us what strategy to use.
Perhaps append an index number to the file in brackets like Windows does?
Related
I was trying to follow the solution given by #StephenP in this post:
Renaming and Moving Files Powershell
I am trying to Move a renamed file to the Output Folder, but it didn't move.
What could go wrong?
Here's my code:
$Files = GCI "$ParentFolder" | ?{$_.Extension -Match "png?"}
$Date = Get-Date -Format "yyyymmddhhmmss"
$Dest = ".\Output"
$Files | ForEach-Object {
# Get the File BaseName and Select the Screen Title only
$FileName = $_.BaseName
$NameCount = $FileName.length
$ScreenTitle = $FileName.substring(0,$NameCount -21)
# Set the New File Name as Variable
$NewFileName = "$($Date)_[$($ScreenTitle)]"
# Start Renaming
$GetName = $_.FullName -replace "$FileName","$NewFileName"
Rename-Item $_ $GetName
# Move the renamed file
Move-Item $GetName -Destination $Dest
}
Thank you for helping :)
First of all, you don't need to rename the file first and then move, because you can do this using Move-Item at the same time.
Use -Filter '*.png' instead of a Where-Object afterwards. The Filter is much more efficient.
Your code does not check if the length of the file BaseName is actually more than 21 characters long, so this $FileName.Substring(0, $NameCount -21) can throw exceptions. However, since you didn't provide any filename examples, I left that in.
Try
$Files = Get-ChildItem -Path $ParentFolder -Filter '*.png' -File
$Date = Get-Date -Format "yyyymmddhhmmss"
$Dest = ".\Output"
$Files | ForEach-Object {
# Get the File BaseName and Select the Screen Title only
$FileName = $_.BaseName
$NameCount = $FileName.Length
# very tricky this.. could throw error if $FileName is less than 21 characters..
$ScreenTitle = $FileName.Substring(0, $NameCount -21)
# Set the New File Name as Variable
$NewFileName = '{0}_[{1}]{2}' -f $Date, $ScreenTitle, $_.Extension
# Move the file with a new name to the destination
$_ | Move-Item -Destination (Join-Path -Path $Dest -ChildPath $NewFileName)
}
As aside, using square brackets in filenames could cause you problems and to do more PowerShell on these files, you need to always remember to use -LiteralPath instead of -Path on cmdlets that support it like Get-ChildItem
My PowerShell script creates a log file, but when I run the script for the second time, it tells me that the testfile1.log file already exists.
How do I make the script if it finds testfile1.log, it creates testfile2.log, and if this also exists, it creates testfile3.log, and so on..
New-Item -Path $path -Name "testfile1.log" -ItemType "file"
You could do it this way, first get all the files in the desired path and sort them by the ending digits on their name. If no files are found create the testfile1.log, if there were files found, get the last sorted file (the one with the highest ending digit) extract the ending digits and add +1 to the count and use it to create the new file.
$files = Get-ChildItem $path -Filter testfile*.log | Sort-Object {
$_.BaseName -replace '\D' -as [int]
}
if(-not $files)
{
New-Item -Path $path -Name "testfile1.log" -ItemType File
}
else
{
[int]$number = $files[-1].BaseName -replace '\D'
$number++
New-Item -Path $path -Name "testfile$number.log" -ItemType File
}
An alternative method, based on this answer could be
$path = 'D:\Test'
$log = 'testfile'
$index = ((Get-ChildItem -Path $path -Filter "$log*.log" -File |
Where-Object { $_.BaseName -match "$log\d+$" } |
Select-Object #{Name = 'index'; Expression = {[int]($_.BaseName -replace '\D')}}).index |
Measure-Object -Maximum).Maximum + 1
# create the new file
New-Item -Path (Join-Path -Path $path -ChildPath "$log${index}.log") -ItemType File
A concise solution that also builds on this answer (see there for an explanation of the core technique):
$path = '.' # Output dir.
$nameTemplate = 'testfile{0}.log' # {0} is the sequence-number placeholder
New-Item -ItemType File -Path $path -Name (
$nameTemplate -f (1 + (
# Find all existing log files
Get-ChildItem (Join-Path $path $nameTemplate.Replace('{0}', '*')) |
Measure-Object -Maximum {
# Extract the embedded sequence number.
$_.Name -replace [regex]::Escape($nameTemplate).Replace('\{0}', '(\d+)'), '$1'
}
).Maximum)
) -WhatIf
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.
Note:
The above uses a complex -replace operation to reliably extract the sequence number from existing file names; if you know that that only one number is present in each given file name, $_.BaseName -replace '\D' (removing all non-digit characters) will do in the Measure-Object call above.
If you wanted to use zero-padded, fixed-width sequence numbers, you can adjust (all occurrences of) the {0} placeholder accordingly; e.g, to create sequence numbers 01, 02, ... 99, use {0:00} - see the Composite formatting help topic, which describes the string formatting language also used by PowerShell's -foperator
I am trying to make simple powershell script that is archiving files coming in daily. Every file has date at the beggining of its name, for example: 20211220_Something.csv, 20211220_SomethingElse.txt, 20211219_Something.csv, 20211219_SomethingElse.txt etc...
I would like to make script that collects all files with extensions (*.txt, *.csv, *.xslx) from specifict directories which are:
\\Main\Files and \\Main\Files\SecondaryFiles
and archives all files with above extensions to for example \\Main\Files\archive\2021\12\20.12.zip
where 2021, 12 and 20.12 are elements of date provided in file name prefix. Inside 20.12.zip we have all files from \\Main\Files with directory named "SecondaryFiles" in which theres all files from the \\Main\Files\SecondaryFiles. After archiving i would like to delete all the files that i just zipped.
Right now i have this piece of code which loop through all files in the \Main\ dir and extracts date prefix. I have tried using [Datetime]::parseexact() method but it doesnt work since my loop returns whole path. Anybody has any idea how to approach this?
$Date = Get-Date
$Day = $Date.Day
$Month = Date.Month
$Year = $Date.Year
$directoryPath = "\\Main\Files\archive'"+$Year+"\"+$Month
$files = Get-ChildItem -Path "\\Main\Files" -Include *.txt, *.csv, *.xlsx -Recurse
for ($i=0; $i -lt $files.Count; $i++){
$temp = $files[$i].FullName.split("_")[1]
}
if(!Test-Path -path $directoryPath){
New-Item -ItemType directory -Path $directoryPath
}
Compress-Archive -Path "\\Main\Files", "\\Main\Files\*.txt", "\\Main\Files\*.csv", "\\Main\Files\*.xlsx", "\\Main\Files\SecondaryFiles\*.txt", "\\Main\Files\SecondaryFiles\*.csv", "\\Main\Files\SecondaryFiles\*.xlsx" -Update -DestinationPath "\\Main\Files\archive\$Year\$Month\$Day.$Month.zip"
Then i am removing items from the original directory.
Also one thing worth mentioning is that I cant be sure if folder contains only files from todays date. So script should work fine when theres files from like all week lets say 20211214 till 20211220.
So again i would like to Compress-Archive files like i did above but instead todays date the path would contain extracted date from file name prefix.
Use Group-Object to group all files having the same date prefix together and use that to create the output subdirectories, the final .zip file and also to remove the original files after zipping.
$sourcePath = '\\Main\Files'
$destination = '\\Main\Files\archive'
Get-ChildItem -Path $sourcePath -Include '*.txt', '*.csv', '*.xlsx' -Recurse |
# select only files that start with 8 digits followed by an underscore
Where-Object { $_.BaseName -match '^\d{8}_' } |
# group the files on the date part and loop trhough these groups
Group-Object { $_.BaseName.Substring(0,8) } | ForEach-Object {
# split the date part into variables. Automatic variable $_ represents one Group,
# so we can take that group's Name to split into date parts
$year, $month, $day = $_.Name -split '(\d{4})(\d{2})(\d{2})' -ne ''
# construct the target folder path for the zip file
$targetPath = Join-Path -Path $destination -ChildPath ('{0}\{1}' -f $year, $month)
# create the new sub directory if it does not yet exist
$null = New-Item -Path $targetPath -ItemType Directory -Force
# create the full path and filename for the zip file
$zip = Join-Path -Path $targetPath -ChildPath ('{0}.{1}.zip' -f $day, $month)
# compress the files in the group
Compress-Archive -Path $_.Group.FullName -DestinationPath $zip -Update
# here is where you can delete the original files after zipping
$_.Group | Remove-Item -WhatIf
}
Note I have added switch -WhatIf to the Remove-Item cmdlet. This is a safety switch, so you are not actually deleting anything yet. The cmdlet now only displays what would be deleted. Once you are happy with this output, remove that -WhatIf switch so the files are deleted.
I am currently taking a CSV File and renaming it to a .txt file. I Currently save the name as yyyymmdd.txt, I am trying to check the folder for the txt file and append "-01" to the file. (yyyy-mm-dd-01.txt,..-02,..-03,..) each time the script checks it should add 01 to the file. My current problem is the file gets renamed to yyyymmdd.txt01 and if it checks it just puts yyyymmdd.txt11
I've tried a few ways, but have failed to succeed.
$filevalue = 'C:\test\test_notepad.csv'
$path = 'C:\test\'
$file = 'test.txt'
$file2 = 'test_notepade.csv'
$date = Get-Date -Format "yyyy-mm-dd-"
$filename = "$date.txt"
Rename-Item $filevalue $filename
$originalFiles = Get-ChildItem "C:\test\text" -Filter *.txt
$x = 1
ForEach ($originalFile in $originalFiles) {
$x++
Rename-Item -Path $originalFile.FullName -NewName ($originalFile.Name -replace "^", "1")
}
here's a way to do what you want. the stuff before the ForEach-Object is to make a testing file - and to ensure there is only the one since i didn't include any error checking. [grin]
what it does ...
iterates thru 17 times to test the code
gets the file info
splits the .BaseName on the unique _ character
grabs the last item in the resulting array
converts that number string to an [int]
increments it
pads it to two digits
replaces the old seq number in the file name with the new one
renames the file
here's the code ...
$Today = Get-Date -Format 'yyyy-MM-dd'
# delete any existing test file
$Null = Remove-Item -Path "$env:TEMP\$($Today)_*.txt" -ErrorAction SilentlyContinue
# create a file to work with
$Null = New-Item -Path $env:TEMP -Name "$($Today)_07.txt" -ItemType File -ErrorAction SilentlyContinue
# pretend to access the listing 17 times
1..17 |
ForEach-Object {
$FileInfo = Get-ChildItem -LiteralPath $env:TEMP -Filter "$Today*.txt"
$OldSeqNumber = $FileInfo.BaseName.Split('_')[-1]
$NewSeqNumber = '{0:D2}' -f ([int]$OldSeqNumber + 1)
$NewFileName = $FileInfo.Name.Replace("_$OldSeqNumber", "_$NewSeqNumber")
Rename-Item -LiteralPath $FileInfo.FullName -NewName $NewFileName
}
original file name = 2019-05-07_07.txt
last new file name = 2019-05-07_24.txt
The action I am supposed to do:
Read a file from a folder
Extract a date from the 1st line
Change it to 'yyyymmdd' format
Rename all the file by removing first 4 characters and place this format date at the beginning.
Example FILE.FILE will be changed to 20180725.FILE (The date will be in the file)
I am able to accomplish this to a single folder in Newpath by the following code:
$path="\\Data\DEV\DevDat\Arun\DXSL\Newpath\20170601"
$files=Get-ChildItem $path | Select-Object -first 1
$data = Get-Content "$path\$files" -first 1
foreach($line in $data)
{
$arr = $line.substring(52,4)+$line.substring(46,2)+$line.substring(49,2)
}
get-childitem $path | rename-item -newname { $arr +[string]($_.name).substring(4)}
However, I am not able to replicate this action to the other folders in a loop. The Newpath folder has several sub-folders. I need to rename the files inside each sub-folder inside Newpath. Is there any way to achieve this?
FYI I'm using Version 4 of powershell.
A PowerShell script as suggested in my comment.
It does not check if the file to rename to already exists.
The Rename-Item has the parameer -WhatIf appended, so it only shows what would be done.
If there are more numbers in the first line, the script will match the first pattern.
The script also does not check if the file already is renamed to the pattern.
## Q:\Test\2018\07\25\SO_51522205.ps1
#Requires -Version 3.0
$BasePath = "\\Data\DEV\DevDat\Arun\DXSL\Newpath"
$RE = [RegEx]'(?<Month>\d{2}).(?<Day>\d{2}).(?<Year>\d{4})'
ForEach ($Folder in (Get-ChildItem -Path "$BasePath\*" -Directory)){
ForEach ($File in (Get-ChildItem -Path "$($Folder.FullName)\*" -File)){
$Line1 = (Get-Content $File.FullName | Select-Object -First 1)
If ($File.BaseName.Length -gt 4){
$BaseName = $File.BaseName.SubString(4)
} else {
$BaseName = ''
}
If ($Line1 -match $RE){
$NewName = ("{0}{1}{2}{3}{4}" -f `
$Matches.Year,
$Matches.Month,
$Matches.Day,
$BaseName,
$File.Extension)
$File | Rename-Item -NewName $NewName -WhatIf
} Else {
"{0} doesn't have a proper date in 1st line" -f $File.FullName
}
}
}
Sample tree before,
> tree /F
└───Newpath
└───20170601
blahblah.txt
FILE.FILE
after running the script.
> tree /F
└───Newpath
└───20170601
20180724blah.txt
20180725.FILE