I am working in a windows environment.
I have a project that requires a short script to determine if a file with a modified date of today exists in a folder. If the file exists, it should copy it, if a file does not exist, it should return an error code.
I prefer to not use 3rd party apps. I am considering powershell.
I can pull a list to visually determine if the file exists, but I am having trouble batching to return an error if the count is zero.
Get-ChildItem -Path C:\temp\ftp\archive -Recurse | Where-Object { $_.lastwritetime.month -eq 3 -AND $_.lastwritetime.year -eq 2013 -AND $_.lastwritetime.day -eq 21}
Any help is greatly appreciated!
You can compare the current date against the date part only of each file LastWriteTime short date:
Get-ChildItem -Path C:\temp\ftp\archive -Recurse | Where-Object {
$_.LastWriteTime.ToShortDateString() -eq (Get-Date).ToShortDateString()
}
Get-ChildItem $path -r | % {if((!($_.psiscontianer))-and(Get-Date $_.LastWriteTime -Uformat %D)-eq(Get-Date -UFormat %D)){$_.FullName}else{Write-Warning 'No from Today'}}
F.Y.I. when doing large jobs, like if you'll be going through TB of files, use a foreach-object. It's faster then Where-Object. This method processes the objects collected in the array directly when available and doesn't wait until all objects are collected.
In summary, there always a lot of different ways to achieve the same result in PowerShell. I advocate using what is easiest for you to remember. At the same time, PowerShell can provide some big performance differences between the approaches – and it pays to know more!
You can still make the line a little more efficient by calculating the date
$date = (Get-Date -UFormat %D)
Get-ChildItem $path -r | % {if((!($_.psiscontianer))-and(Get-Date $_.LastWriteTime -Uformat %D)-eq$date){$_.FullName}else{Write-Warning 'No from Today'}}
I was able to use the following script:
$Date = Get-Date
$Date = $Date.adddays(-1)
$Date2Str = $Date.ToString("yyyMMdd")
$Files = gci "C:\\Temp\\FTP\\Archive"
ForEach ($File in $Files){
$FileDate = $File.LastWriteTime
$CTDate2Str = $FileDate.ToString("yyyyMMdd")
if ($CTDate2Str -eq $Date2Str) {Copy-Item $File.Fullname "C:\\Temp\\FTP"; exit}
}
Throw "No file was found to process"
To test if there are no files:
$out = Get-ChildItem -Path C:\temp\ftp\archive -Recurse | Where-Object {
$_.LastWriteTime.ToShortDateString() -eq (Get-Date).ToShortDateString()
};
if ($out.Count -gt 0)
//do something with your output
else
//sorry no file
Related
I have a simple PowerShell script that writes to a file a list of recently added files.
$fpath = "\\DS1821\video\movies"
$file1 = "C:\Users\brian.w.williams\Desktop\RecentMovie.txt"
if (Test-Path $file1) {Remove-Item -Path $file1}
Get-ChildItem -Path "$fpath" -File -Recurse -Include "*.eng.srt" |
ForEach-Object {
$movie = $_.BaseName -replace ".eng",""
if ( ($_.LastWriteTime.Month -ge 7) -and ($_.LastWriteTime.Year -ge 2021) ) {
Write-Host $movie " = " $_.LastWriteTime
Write-Output $movie | Out-file $file1 -append;
}
}
The script works fine. But I noticed that the script runs much faster (a couple of minutes) when run within Visual Code (i.e. "Run without debugging"). When I run the script standalone (i.e. "Run with PowerShell") the script can take hours to complete. Why the difference? Is there anything I can do to speed it up? I have tried mapping the network folder, but that made no difference.
There is improvement to be made to help the code speed up.
First of all, using Write-Host and Write-Output to append to a file inside the ForEach loop is very time consuming.
Then you're using parameter -Include on Get-ChildItem, where you really want to use -Filter. A Filter is MUCH faster than Include, because the latter will only filter the filenames afterwards. -Filter however can only work on one single filename pattern, but this is exactly what you are doing here.
The if(..) can also be improved to have it compare only one value (a datetime) instead of two separate properties from LastWriteTime.
Try:
$sourcePath = '\\DS1821\video\movies'
# set a variable to an output file on your desktop
$outputFile = Join-Path -Path ([Environment]::GetFolderPath("Desktop")) -ChildPath 'RecentMovies.txt'
# set the reference date in a variable for faster comparison later
$refdate = (Get-Date -Year 2021 -Month 7 -Day 1).Date
# get the files, filter on the name ending in '*.eng.srt'
# filter more with Where-Object so you'll get only files newer than or equal to the reference date
# output objects with one calculated property ('MovieName') and the LastWriteTime
$movies = Get-ChildItem -Path $sourcePath -File -Recurse -Filter '*.eng.srt' |
Where-Object { $_.LastWriteTime -ge $refdate } |
Select-Object #{Name = 'MovieName'; Expression = {$_.BaseName -replace '\.eng\.srt$', '.srt'}},
LastWriteTime
# now output what you have collected to file.
# Set-Content creates a new file or overwrites an existing file
$movies.MovieName | Set-Content -Path $outputFile
# and to screen
$movies | ForEach-Object { Write-Host ('{0} = {1}' -f $_.MovieName, $_.LastWriteTime.ToString()) }
# or to Out-GridView if you prefer
$movies | Out-GridView -Title 'My Movies'
1. I've changed some variable names to make the code better readable
2. Since -replace uses regex, you have to escape the dots with a backslash. The regex also anchors the string to the end of the BaseName with the dollar sign ($) at the end
I'm writing a script that I can run once every two weeks to clear out folders and files that haven't been accessed in two weeks or longer. I've written most of the script, and it works well until I add the following line of code:
Where-Object { $_.LastAccessTime –lt $RefDate } |
For some reason, this code prevents $condition from having an output [see code below].
I use $condition later in a do-while to recursively delete folders, but it won't loop due to this, or export the data to a CSV folder anymore. [Removing this line lets it work again]
Here's the key sections that the line is used in/relevant to it:
$dPath = "C:\Users\my.name\Desktop\PowTest2\*\"
$RefDate = (Get-Date).AddHours(-1);
$condition = Get-ChildItem $dPath -recurse -force |
Where-Object { $_.LastAccessTime –lt $RefDate } |
Where-Object {$_.PSIsContainer -eq $True} |
Where-Object{$_.GetFileSystemInfos().Count -eq 0} |
select FullName
write-output $RefDate;
write-output $condition
Above, $RefDate outputs as expected, $condition outputs nothing unless I remove the problematic line of code.
Edit:
Hi all,
Olaf made a good point, and asked me to check if the property is tracked for the folder. It appears it isn't, which would explain my issue. I'll update after more research and testing.
Not sure if its been answered, but i was doing some reasearch and found a 7 month old post where it was answered, i changed it around to match what youd like "It deletes all files older than 13 days, so not sure if that works lol". Let me know if this help
OG POST: Powershell delete folder files based on date range criteria
$path = "C:\Users\XXXXXXXX\Desktop\test"
foreach($file in (GEt-childitem -Path $path -Recurse | Where-Object {($_.LastWriteTime
-lt (Get-Date).AddDays(-14))} ))
{
if (($file.lastwritetime.Date.Day -in 1,15,30 ) -or ($file -like '*weekly*'))
{continue}
Remove-Item -Path $file.FullName}
I've created a script to find files that are a certain amount of days old. I'm using it to find files that are 30 days old but I'm trying to leave it open should I ever need to plug in a different amount of time.
After you feed this part of the script the information you'd like, it is supposed to create a text file of all the files that meet the criteria. I can find files that are -lt and -gt or -le or -ge but when I attempt to use -eq, I get no results. Any thoughts on what's wrong with the portion of my script listed below?
$Path = Read-Host "What path should I look at?"
$DaysOld = Read-Host "How many days old should the files I'm looking for be?"
$Currentdate = Get-Date
$Targetdate = $Currentdate.AddDays(-$DaysOld).ToString('MM-dd-yyyy')
Write-Host "The target date is $targetdate"
$SourceFolder = $Path
$files = Get-ChildItem $Path -Recurse |
Where-Object { $_.LastWriteTime -eq $Targetdate } |
Where-Object { $_.PSIsContainer -eq $false } |
ForEach-Object { $_.FullName } |
Out-File $outfileCopy
The problem with using -eq is that datetime objects are down to the second, so is 8/22/2017 at 11:59:27 AM -eq to 8/22/2017 at 00:00:00 AM? No, no it isn't. What you could do to defeat that is to use the .ToShortDateString() method which outputs a string such as 8/22/2017.
$Path = Read-Host "What path should I look at?"
$DaysOld = read-host "How many days old should the files I'm looking for be?"
$Currentdate = get-date
$Targetdate = $currentdate.AddDays(-$daysOLD).ToShortDateString()
Write-Host "The target date is $targetdate"
$SourceFolder = $path
$files = Get-ChildItem $path -Recurse| Where-Object {$_.lastwritetime.ToShortDateString() -eq $targetdate}|Where-Object {$_.PSIsContainer -eq $false} | ForEach-Object {$_.fullname}| out-file $outfileCopy
This approach should only be used when trying to match things from the same day, ignoring the time of day, and should not be used when looking for things that are less than, or more than (including -le and -ge) because it uses string evaluation instead of date evaluation.
Edit: I've been doing it wrong for years, and didn't even know. Many thanks to #Matt for pointing out the .date property of a [DateTime] object, which retains the object type, but zeros out the time aspects of it. Better answer: Use the .Date property to compare, and this should work for greater than and less than evaluations as well.
$Path = Read-Host "What path should I look at?"
$DaysOld = read-host "How many days old should the files I'm looking for be?"
$Currentdate = get-date
$Targetdate = $currentdate.AddDays(-$daysOLD).Date
Write-Host "The target date is $targetdate"
$SourceFolder = $path
$files = Get-ChildItem $path -Recurse| Where-Object {$_.lastwritetime.Date -eq $targetdate}|Where-Object {$_.PSIsContainer -eq $false} | ForEach-Object {$_.fullname}| out-file $outfileCopy
I am a PowerShell newbie. I have a .csv file of users that I pulled a list of home directories for using the following:
$hdirpath = Get-Content C:\Temp\UserList.csv | ForEach {Get-ADUser $_ -properties HomeDirectory | Select HomeDirectory}
Output example of the above looks something like this:
HomeDirectory
\servername\Users\roc03\username
\servername\Users\nov01\username
\servername\Users\roc05\username
Now I want to check if a folder exists in each users path and if it exists, i want to add today's date to the end of that folder name. I know there's a If-Exist-Then command that I might be able to use but I'm not sure how to use it with the information saved in $hdirpath.
Any help would be greatly appreciated. Thanks in advance.
Newbie myself so please forgive any mistakes.
From the top of my head I've got this in mind:
foreach ($path in $hdirpath) {
$folders = Get-ChildItem $_ -Recurse | Where-Object {$_.PSIsContainer -eq $True} | Select-Object FullName
$folders | if ($_.Name -eq "Folder Name") {Rename-Item "Folder Name" + Get-Date}
}
Someone will probably correct me, but if they don't give it a go and let me know if you have any problems.
Also - just for future reference, make sure you include code you have tried before posting a question.
EDIT
So, I've now got the following:
$hdirpath = Get-Content C:\temp\dir.txt
$fullDate = Get-Date
$date = $($fullDate.ToShortDateString())
$day = $date -replace "/", "."
foreach ($path in $hdirpath) {
Get-ChildItem -Path $_ -Recurse | Where-Object {$_.PSIsContainer -eq $true} |
Rename-Item -NewName {if ($_.Name.StartsWith('target') -and $_.Name.EndsWith('folder')) {$_.Name + " " + $($day)}}
}
This returns multiple errors saying that it Cannot evaluate parameter 'NewName' because its argument input did not produce any output., but it works and appends the folder name with the current date.
Line 3-5 get the date and format it with "." rather than "\". You can format this however works for you obviously.
My test structure was "C:\temp\roc6\username1" - with multiple subfolders and multiple 'usernameX'. My test folder to re-name was called "targetfolder". End result ended with "targetfolder 08.03.2017".
Revision
This would be my final run of the script - with my limited knowledge this is the best I can do.
$ErrorActionPreference = "SilentlyContinue"
$hdirpath = Get-Content C:\temp\dir.txt
$fullDate = Get-Date
$day = $($fullDate.ToShortDateString()) -replace "/", "."
foreach ($path in $hdirpath) {
Get-ChildItem -Path $_ -Recurse | Where-Object {$_.PSIsContainer -eq $true} |
Rename-Item -NewName {if ($_.Name.StartsWith('target') -and $_.Name.EndsWith('folder')) {$_.Name + " " + $($day)}}
}
Current situation
I've been working on a script that should copy .txt files containing specific words and with an age of 7 days maximum into a folder, once. So far I've only been able to get code to copy files to the destination folder if the file doesn't exist already.
Code
$path = "c:\PS1\*.txt"
$Destination = "c:\PS2\"
$filter = "thisisatest"
$logfile = "C:\PS2\testlog_$(get-date -format `"MMyyyy`").txt"
#Picking files with certain words and modified within the last 7 days
Foreach($file in (Get-ChildItem $path | Where-Object {$_.name -Match $filter}))
{
If($file.LastWriteTime -gt (Get-Date).adddays(-7).date)
#Logging
{
function log($string, $color)
{
if ($Color -eq $null) {$color = "white"}
write-host $string -foregroundcolor $color
$string | out-file -Filepath $logfile -append
}
#Comparing folder content and copying those not present in destination folder
Compare-Object $path $Destination -Property Name, Length | Where-Object {$_.SideIndicator -eq "=>"} | ForEach-Object {Copy-Item -Path $file.fullname -Destination $Destination -Force}
}
log $file.fullname
}
In conclusion
I have tried finding code which would make it possible to do the following:
Compare path folder content to .txt log for reoccurring names and only copy those not present in list, if a file name is present in the list, move on to next file to copy.
Log only files that have just been copied in a .txt file, if log doesn't exist, create.
Delete log content older than 30 days
Some of my code is probably obsolete or lacking parts, it is made up from bits and pieces I have found while looking for examples.
I know most of it is probably doable with Robocopy, but I hope it can be done in powershell,
Hope you can help me
Ok, moved parameters inside function, made the $string parameter mandatory, gave the option of specifying a logfile (it will default to the global variable $logfile, and gave the color validation so people can IntelliSense it or tab complete it. I also moved it to the beginning of the script, since that's where you usually find functions and it just made more sense to me. Also made sure that the log file isn't a folder, and that it has an extension (it adds .log if it doesn't and isn't an existing file that you're just adding to). I think I may have overdone it on the function. It's very functional, and versatile in case it's needed for another script, but its kind of overkill.
Then I looked at how files were being selected, and then filtered, and revamped things a bit. Now the Get-ChildItem (alias GCI) filters the file name against $Filter, the last write time against Get-Date -7 days, it makes sure the FullName is not in the log file, and it makes sure that it's a file, not a folder.
I also added a line to clean up old logs.
function log{
Param(
[string]$string=$(Throw 'You must provide a string to enter into the log'),
$LogFile = $Global:LogFile,
[ConsoleColor]
$color = "white")
If((gci $logfile -ea SilentlyContinue).psiscontainer){"Specified log file is a folder, please specify a file name.";break}
If($logfile -notmatch "\.[^\\]*$" -and !(test-path $LogFile)){$LogFile = $LogFile + ".log"}
write-host $string -foregroundcolor $color
$string | out-file -Filepath $logfile -append
}
$path = "c:\PS1\*.txt"
$Destination = "c:\PS2\"
$filter = "thisisatest"
$logfile = $Destination+"testlog_$(get-date -format `"MMyyyy`").txt"
gci $Destination -filter "testlog_*.txt" |?{$_.LastWriteTime -le (get-date).AddDays(-42)}| Remove-Item -Force
$AllLogs = gc "$($Destination)testlog_*.txt"
#Picking files with certain words, was modified within the last 7 days, and not already in a logfile
Foreach($file in (Get-ChildItem $path | Where-Object {!$_.PSIsContainer -and $_.LastWriteTime -gt (Get-Date).adddays(-7) -and $AllLogs -notcontains $_.FullName -and $_.Name -inotcontains $filter}))
{
$File|?{!(Test-Path $Destination+$File.Name)}|Copy-Item -Destination $Destination
log $file.fullname
}