I need to match the extension to ".*" to return all files in a given source folder that have the LastWriteTime of $lwt as shown in code. The user can either provide a specific extension like ".csv" but one is not provided then the script will simply search all files. But i can't match the extension with ".*" to return all files.
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)][string]$source,
[Parameter(Mandatory=$true)][string]$destination,
[string]$exten=".*",
[int]$olderthandays = 30
)
$lwt = (get-date).AddDays(-$olderthandays)
if(!(Test-Path $source)){
Write-Output "Source directory does not exist."
exit
}
if(!(Test-Path $destination)){
Write-Output "Source directory does not exist."
exit
}
if($olderthandays -lt 0){
Write-Output "You can't provide a negative number of days. If you want everything deleted simply provide 0 for number of days."
exit
}
if(!$exten.StartsWith(".")){
$exten = "."+$exten
$exten
}
try{
Get-ChildItem $source -ErrorAction Stop | ?{$_.LastWriteTime -lt $lwt -AND $_.Extension -eq $exten} | foreach {
Write-Output $_.FullName
# Move-item $_.FullName $destination -ErrorAction Stop
}
}
catch{
Write-Output "Something went wrong while moving items. Aborted operation."
}
How can this be achieved ?
The Extension of a file will never be .*.
You could try:
$exten = "*.$exten"
Get-ChildItem $source -ErrorAction Stop -Filter $exten | ?{$_.LastWriteTime -lt $lwt} | foreach { ... }
Move your extension filter back into child item and use . or *.
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true)][string]$source,
[Parameter(Mandatory=$true)][string]$destination,
[string]$exten="*.*",
[int]$olderthandays = 30
)
$lwt = (get-date).AddDays(-$olderthandays)
if(!(Test-Path $source)){
Write-Output "Source directory does not exist."
exit
}
if(!(Test-Path $destination)){
Write-Output "Source directory does not exist."
exit
}
if($olderthandays -lt 0){
Write-Output "You can't provide a negative number of days. If you want everything deleted simply provide 0 for number of days."
exit
}
if(!$exten.StartsWith('*.')){
$exten = "*."+$exten
$exten
}
try{
Get-ChildItem $source -Filter $exten -ErrorAction Stop | ?{$_.LastWriteTime -lt $lwt} | foreach {
Write-Output $_.FullName
# Move-item $_.FullName $destination -ErrorAction Stop
}
}
catch{
Write-Output "Something went wrong while moving items. Aborted operation."
}
Related
I have the following code:
$items = Get-ChildItem -Path 'D:\Myoutput\'
$items | ForEach-Object
{
$lastWrite = ($_).LastWriteTime
$timespan = New-Timespan -days 3 -hours 0 -Minutes 0
if(((get-date) - $lastWrite) -gt $timespan) {
$name = $_.Name
$isDir = $_.PSIsContainer
if(!$isDir) {
$_ | Compress-Archive -DestinationPath "D:\Myoutput\Archive\$name.zip"
if (**above_line** is success) {
echo "$name is zipped"
$_ | Remove-Item
}
}
}
}
Please help, how I can find out if '$_ | Compress-Archive -DestinationPath "D:\Myoutput\Archive$name.zip"' is success or not.
Compress-Archive will throw exceptions if something goes wrong, and it will delete partially created archives (source). So, you can do two things to make sure, it was successful:
Catch exceptions
Test if the archive exists
Example:
$items = Get-ChildItem -Path 'D:\Myoutput\'
$items | ForEach-Object
{
$lastWrite = ($_).LastWriteTime
$timespan = New-Timespan -days 3 -hours 0 -Minutes 0
if(((get-date) - $lastWrite) -gt $timespan) {
$name = $_.Name
$isDir = $_.PSIsContainer
if(!$isDir) {
try {
$_ | Compress-Archive -DestinationPath "D:\Myoutput\Archive\$name.zip"
if (Test-Path -Path "D:\Myoutput\Archive\$name.zip") {
Write-Host "$name is zipped"
$_ | Remove-Item
} else {
Write-Host "$name is NOT zipped" -ForegroundColor Red
}
} catch {
Write-Host "$name is NOT zipped" -ForegroundColor Red
}
}
}
}
Compress-Archive already throws an error if it failed, you can just catch it before deleting your original file. For example, I use continue to skip the rest of the commands. You can also skip checking for folders by using Get-ChildItem -File:
Foreach ($file in (Get-Item C:\temp\ -File)) {
Try { $file | Compress-Archive -DestinationPath C:\BadPath\test.zip }
Catch { Write-Warning ("Skipping file due to error: " + $file.FullName); continue }
Remove-Item $file
}
Here's what the output looks like when I use the Bad path above:
WARNING: Skipping file due to error: C:\temp\test1.txt
WARNING: Skipping file due to error: C:\temp\test2.txt
And those files don't get deleted.
I'm using this code to delete files older than 30 days
Function Remove_FilesCreatedBeforeDate {
$Path = "\\servername\path"
$Date = (Get-Date).AddDays(-30)
$ValidPath = Test-Path $Path -IsValid
If ($ValidPath -eq $True) {
"Path is OK and Cleanup is now running"
Get-ChildItem -Path $path -Recurse | Where-Object { $_.LastWriteTime -lt $Date } | Remove-Item -Recurse -force -Verbose
}
Else {
"Path is not a ValidPath"
}
}
Remove_FilesCreatedBeforeDate
Now I want to log which files were deleted, and also whether there was an error or the path isn't valid. Can anyone help me here?
//EDIT
Im Now using this Code (Thanks to Efie for helping)
[Cmdletbinding()]
param(
[Parameter()]$LogPath = 'C:\Admin\scripts\Clean_Folder\Log\log.txt',
[Parameter(ValueFromPipeline)]$Message
)
process {
$timeStampedMessage = "[$(Get-Date -Format 's')] $Message"
$timeStampedMessage | Out-File -FilePath $LogPath -Append
}
}
Function Remove-FilesCreatedBeforeDate {
[Cmdletbinding()]
param(
[Parameter()]$Path = '\\servername\path\',
[Parameter()]$Date = $(Get-Date).AddDays(-30)
)
process {
if(-not (Test-Path $Path -IsValid)) {
"Path $Path was invalid" | Write-MyLog
return
}
"Path $Path is OK and Cleanup is now running" | Write-MyLog
try {
Get-ChildItem -Path $Path -Recurse |
Where-Object {
$_.LastWriteTime -lt $Date
} | Remove-Item -recurse -force -verbose | Write-MyLog
}
catch {
"Remove-Item failed with message $($_.Exception.Message)" | Write-MyLog
}
}
}
Write-MyLog
Remove-FilesCreatedBeforeDate
Two files getting deleted but i just see this in my Log
[2021-07-22T16:27:53] Path \\servername\path\ is OK and Cleanup is now running
I dont see which files getting deleted sadly
A simple implementation for your example would be something like this:
Function Remove-FilesCreatedBeforeDate {
[Cmdletbinding()]
param(
[Parameter(Mandatory)]$Path = '\some\default\path',
[Parameter()]$Date = $(Get-Date).AddDays(-30)
)
process {
if(-not (Test-Path $Path -IsValid)) {
"Path $Path was invalid" | Write-MyLog
return
}
"Path $Path is OK and Cleanup is now running" | Write-MyLog
try {
Get-ChildItem -Path $Path -Recurse |
Where-Object {
$_.LastWriteTime -lt $Date
} | Remove-Item -Recurse -Force -Verbose
}
catch {
"Remove-Item failed with message $($_.Exception.Message)" | Write-MyLog
}
}
}
function Write-MyLog {
[Cmdletbinding()]
param(
[Parameter()]$LogPath = 'default\log\path\log.txt',
[Parameter(ValueFromPipeline)]$Message
)
process {
$timeStampedMessage = "[$(Get-Date -Format 's')] $Message"
$timeStampedMessage | Out-File -FilePath $LogPath -Append
}
}
Some notes:
Advanced Functions
process { }, [Cmdletbinding()], and [Parameter()] are what turn your function into an 'advanced' function. You get to use loads of built in features normally reserved for compiled cmdlets this way.
For example, you could now suppress errors with $ErrorActionPreference = 'SilentlyContinue' like you're used to doing with native Powershell cmdlets.
You can pipe your messages to your logging function by adding ValueFromPipelin to your parameter.
Those really just brush the surface of the extra capabilities you get.
Here is some information. I would recommend getting in the habit of writing them like this if you plan to use them in the future.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_advanced?view=powershell-7.1
Error Handling
I'd recommend looking into this documentation by Microsoft on error handling:
https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-exceptions?view=powershell-7.1
Naming Conventions
I would also recommend taking a look at this about PowerShell function naming conventions:
https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands?view=powershell-7
By PowerShell standards it would make more sense to name your function Remove-FilesCreatedBeforeDate with the dash separating verb-action instead of the underscore.
Logging
If you want a little more control and a few more features for logging your functions, here is some information on a tried and true solution for PowerShell using PSFramework:
https://adamtheautomator.com/powershell-logging/
Good luck! Hope some of that helps.
In Unix its Simple
find /var/log/hive -type f -mtime +30 -delete
Could Start-transcript with Try and catch be your solution here?
Start-Transcript logs everything that you do and the errors.
I tried this and this does what you want
Start-Transcript -Path "$PSScriptRoot\RemoveAccountLog.txt" -Force -Append
Get-Date -Format "yyyy-mm-dd HH:MM"
Try
{ # Start Try
$Path = "\\servername\path"
$Date = (Get-Date).AddDays(-30)
$TestPath = Test-Path -Path $Path -PathType Container
If ( $TestPath -Eq $Null )
{ # Start If
Write-Host "The $TestPath String is empty, Path is not a valid"
} # End If
Else
{ # Start Else
Write-host "Path is OK and Cleanup is now running... 0%"
$GetFiles = Get-ChildItem -Path $Path -Recurse -Force |
Where-Object { $_.LastWriteTime -lt $Date } |
Remove-Item -Recurse -force -Verbose |
Write-host "Path is OK and Cleanup is now running... 100%" -ForegroundColor Green
} # End Else
} # End Try
Catch
{ # Start Catch
Write-Warning -Message "## ERROR## "
Write-Warning -Message "## Script could not start ## "
Write-Warning $Error[0]
} # End Catch
Screenshot:
I am trying to write a script which takes a source-path as input and copies all files that have been changed since a given date, including their directory structure:
param([string]$source,[string]$datum)
(Get-ChildItem $source -Recurse | Where-Object { $_.LastWriteTime -ge $datum }) | Copy-Item -Destination C:\tmp -Recurse
It works. The problem is, that it copies the files, including their directory structure, BUT it also copies all files from the source directories to the base path of the destination folder.
Where is the error?
First and foremost you should be using robocopy for something like this. It has switches that likely handle your query:
/e Copies subdirectories. Note that this option includes empty directories. For additional information, see Remarks.
/maxage: Specifies the maximum file age (to exclude files older than N days or date).
/l Specifies that files are to be listed only (and not copied, deleted, or time stamped).
So knowing that I would start experimenting with robocopy $source $destination /e /maxage:30 /l which should show all files in the directory tree that were modified in the last 30 days. /maxage might not be the switch you are looking for but its a fair guess.
The issue with your current logic is likely that Get-ChildItem $source -Recurse will be returning folders as well as files. So you could be passing a folder to Copy-Item which is where all the extra files are coming from. You should be able to mitigate that with the -File switch.
Get-ChildItem $source -Recurse -File
Here is an overly complicated solution. It has a lot of error handling with 5 retry attempts. You should be able to apply it toward your needs.
I would look at Line 21 to add your Where-Object and test it.
Example Use: Copy-Files -Source "C:\Path\" -Destination "C:\Path2\"
Function Retry-Command {
$Script:Counter = $Script:Counter + 1
Write-Host "Attempt #" $Script:Counter "out of 5"
if ($Script:Counter -ge 5) {
PAUSE
}
else {
Start-Sleep -Seconds 5 -Verbose
}
}
Function Copy-Files ([string]$Source, [string]$Destination ) {
[System.Collections.ArrayList]$Folder_Content = #()
[string]$Folder_Name = Split-Path $Source -Leaf
[string]$Folder_Path = ($Source -replace [regex]::Escape($Source), ($Destination + $Folder_Name + "\"))
[int]$Script:Counter = 0
while (($Folder_Content.Count -eq "0") -and ($Counter -ne 5)) {
try {
Write-Host ""
Write-Host "------------------------------" -ForegroundColor Cyan
Write-Host "Getting Content of" $Folder_Name -ForegroundColor Yellow
[System.Collections.ArrayList]$Folder_Content = #(Get-ChildItem -Path $Source -Recurse -Force -ErrorVariable Child_Error)
if ((-Not $Child_Error) -and ($Folder_Content.Count -ne 0) ) {
Write-Host "Finished Getting Content of" $Folder_Name -ForegroundColor Green
[int]$Script:Counter = 0
}
else {
Write-Host "Failed to get Content of" $Folder_Name -ForegroundColor Red
Retry-Command
}
}
catch {
Write-Host "Unexpected Error getting Content of" $Folder_Name -ForegroundColor Red
Retry-Command
}
}
if (-Not $Folder_Content.Count -eq 0) {
while ((Test-Path -Path $Folder_Path) -eq $false) {
try {
Write-Host ""
Write-Host "Creating" $Folder_Name "Folder" -ForegroundColor Yellow
Copy-Item -Path $Source -Destination $Folder_Path -Force -ErrorVariable Folder_Error
if (-Not $Folder_Error) {
Write-Host "Finished Creating" $Folder_Name "Folder" -ForegroundColor Green
[int]$Script:Counter = 0
}
else {
Write-Host "Failed to create" $Folder_Name "Folder" -ForegroundColor Red
Retry-Command
}
}
catch {
Write-Host "Unexpected Error Creating" $Folder_Name "Folder" -ForegroundColor Red
Retry-Command
}
}
foreach ($Index in $Folder_Content) {
[string]$Software_File_Name = Split-Path $Index.FullName -Leaf
[string]$Software_File_Path = ($Index.FullName -replace [regex]::Escape($Source), ($Destination + (Split-Path $Source -Leaf) + "\"))
while ((Test-Path -Path $Software_File_Path) -eq $false) {
try {
Write-Host ""
Write-Host "Copying" $Index.FullName -ForegroundColor Yellow
Copy-Item -Path $Index.FullName -Destination $Software_File_Path -Force -ErrorVariable File_Error
if (-Not $File_Error) {
Write-Host "Finished Copying" $Index.FullName -ForegroundColor Green
[int]$Script:Counter = 0
}
else {
Write-Host "Failed to Copy" $Index.FullName -ForegroundColor Red
Retry-Command
}
}
catch {
Write-Host "Unexpected Error Copying" $Index.FullName -ForegroundColor Red
Retry-Command
}
}
}
}
}
I'm trying to copy files from a source folder to a destination folder, and rename the files in the process.
$Source = "C:\Source"
$File01 = Get-ChildItem $Source | Where-Object {$_.name -like "File*"}
$Destination = "\\Server01\Destination"
Copy-Item "$Source\$File01" "$Destination\File01.test" -Force -
Confirm:$False -ErrorAction silentlyContinue
if(-not $?) {write-warning "Copy Failed"}
else {write-host "Successfully moved $Source\$File01 to
$Destination\File01.test"}
The problem is that since Get-ChildItem doesn't throw an error message if the file is not found, but rather just gives you a blank, I end up with a folder called File01.test in destination if no file named File* exists in $Source.
If it does exist, the copy operation carries out just fine. But I don't want a folder to be created if no matching files exist in $Source, rather I just want an error message logged in a log file, and no file operation to occur.
This shouldn't matter what the file name is, but it won't account for files that already exist in the destination. So if there is already File01.txt and you're trying to copy File01.txt again you'll have problems.
param
(
$Source = "C:\Source",
$Destination = "\\Server01\Destination",
$Filter = "File*"
)
$Files = `
Get-ChildItem -Path $Source `
| Where-Object -Property Name -Like -Value $Filter
for ($i=0;$i -lt $Files.Count;$i++ )
{
$NewName = '{0}{1:D2}{3}' -f $Files[$i].BaseName,$i,$Files[$i].Extension
$NewPath = Join-Path -Path $Destination -ChildPath $NewName
try
{
Write-Host "Moving file from '$($Files[$i].FullName)' to '$NewPath'"
Copy-Item -Path $Files[$i] -Destination
}
catch
{
throw "Error moving file from '$($Files[$i].FullName)' to '$NewPath'"
}
}
You can add an "if" statement to ensure that the code to copy the files only runs when the file exists.
$Source = "C:\Source"
$Destination = "\\Server01\Destination"
$File01 = Get-ChildItem $Source | Where-Object {$_.name -like "File*"}
if ($File01) {
Copy-Item "$Source\$File01" "$Destination\File01.test" -Force -Confirm:$False -ErrorAction silentlyContinue
if(-not $?) {write-warning "Copy Failed"}
else {write-host "Successfully moved $Source\$File01 to
$Destination\File01.test"}
} else {
Write-Output "File did not exist in $source" | Out-File log.log
}
In the "if" block, it will check to see if $File01 has anything in it, and if so, then it'll run the subsequent code. In the "else" block, if the previous code did not run, it'll send the output to the log file "log.log".
I am unable to delete zip folders older than 14 days from a directory. The rest of my script works fine, however the following code block is failing:
It has not thrown any errors, the path has been sanity checked, however, I placed old documents in to see if they are cleared up to no avail. It prints the statement that the folder path exists however does not delete anything from the folder.
if (!(Test-Path $folderpath)) {
New-Item -ItemType Directory -Path $folderpath
Write-Host "$folderpath created"
} else {
Write-Host "$folderpath already exists, will remove old items"
$HowOld = -14
Get-ChildItem $Path -Recurse |
where {$_.LastWriteTime -lt (Get-Date).AddDays($HowOld) -and -not $_.PSIsContainer} |
% {Remove-Item $_.FullName -Force -WhatIf}
}
Here is the full script for completeness:
#Get backup folder drive letter
$letter = [System.IO.DriveInfo]::getdrives() | Where-Object {$_.DriveType -eq 'Fixed' -and $_.Name -notlike '*C*'} | Select-Object -Property Name
[string] $drive = "$letter"
$Drive2 = $null
$Drive2 = $drive.Substring($drive.IndexOf('=')+1)
$BackupDrive = $Drive2.Substring(0, $Drive2.IndexOf('}'))
#Check the programname log path.
$logspath = "C:\ProgramData\programname\programname4.0\Logs"
If (!(test-path $logspath))
{
$IQlogspath = "C:\ProgramData\programname\iQSonar4.0\Logs"
}
Else
{
$IQlogspath = "C:\ProgramData\programname\programname4.0\Logs"
}
#check if backup folder exists, clean if exists and if not create it.
$folderpath = $BackupDrive + "programname_logs"
If(!(test-path $folderpath))
{
New-Item -ItemType Directory -Path $folderpath
write-host "$folderpath created"
}
Else
{
write-host "$folderpath already exists, will remove old items"
$HowOld = -14
get-childitem $Path -recurse | where {$_.lastwritetime -lt (get-date).adddays($HowOld) -and -not $_.psiscontainer} |% {remove-item $_.fullname -force -whatif}
}
#check if todays folder exists, if not create it
$todaysdate = (Get-Date).ToString('dd-MM-yyyy')
$todaysbackup = $folderpath + "\Logs_Backup_" + $todaysdate
$todaysbackupzip = $todaysbackup + ".zip"
If(!(test-path $todaysbackup))
{
New-Item -ItemType Directory -Path $todaysbackup
write-host "$todaysbackup created, now moving items"
#select and move files to backup folder
get-childitem -Path $IQlogspath |
where-object {$_.LastWriteTime -lt (get-date).AddDays(-7) -and $_.Name -notmatch "Service"} |
move-item -destination $todaysbackup
Start-Sleep -s 10
write-host "checking for zip"
If(Test-path $todaysbackupzip) {
write-host "$todaysbackupzip already exists, changing name"
$todaysbackupzip = $null
$todaysbackupzip = $folderpath + "\Logs_Backup_" + $todaysdate + ".re-run" + ".zip"
Add-Type -assembly "system.io.compression.filesystem"
[io.compression.zipfile]::CreateFromDirectory($todaysbackup, $todaysbackupzip)
write-host "$todaysbackupzip created"
Remove-Item -Recurse -Force $todaysbackup
write-host "$todaysbackup removed"
exit
}
write-host "creating zip folder, $todaysbackupzip"
Add-Type -assembly "system.io.compression.filesystem"
[io.compression.zipfile]::CreateFromDirectory($todaysbackup, $todaysbackupzip)
write-host "$todaysbackupzip created"
Remove-Item -Recurse -Force $todaysbackup
write-host "$todaysbackup removed"
exit
}
Else
{
write-host "$todaysbackup already exists, attempting to zip"
#check if zip backup folder already exists
If(Test-path $todaysbackupzip) {
write-host "$todaysbackupzip already exists, changing name"
$todaysbackupzip = $null
$todaysbackupzip = $folderpath + "\Logs_Backup_" + $todaysdate + ".re-run" + ".zip"
[io.compression.zipfile]::CreateFromDirectory($todaysbackup, $todaysbackupzip)
write-host "$todaysbackupzip created"
Remove-Item -Recurse -Force $todaysbackup
write-host "$todaysbackup removed"
exit
}
Else
{
Start-Sleep -s 10
write-host "creating zip folder, $todaysbackupzip"
Add-Type -assembly "system.io.compression.filesystem"
[io.compression.zipfile]::CreateFromDirectory($todaysbackup, $todaysbackupzip)
write-host "$todaysbackupzip created"
Remove-Item -Recurse -Force $todaysbackup
write-host "$todaysbackup removed"
exit
}
}
#Get OS disk label
$OSDisk = Get-WmiObject Win32_OperatingSystem | Select-Object WindowsDirectory
[string] $drive = "$OSDisk"
$Drive2 = $null
$Drive2 = $drive.Substring($drive.IndexOf('=')+1)
$OSDrive = $Drive2.Substring(0, $Drive2.IndexOf('\'))
$OSDrive2 = "'$OSDrive'"
$disk = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID=$OSDrive2"| Select-Object FreeSpace
[INT] $FreeSpace = $disk.FreeSpace /1GB
If ($FreeSpace -gt 5) {
write-host "no additional backup requirement, exiting script"
exit
}
Else {
write-host "Running one off clear"
$OneOff = $IQlogspath + "\Logs_One_Off_" + $todaysdate
$OneOffZip = $OneOff + ".zip"
If(!(test-path $OneOff))
{
New-Item -ItemType Directory -Path $OneOff
write-host "$OneOff created"
}
Else {
write-host "folder already exists"
}
#select and move files to backup folder
write-host "moving logs to $OneOff"
get-childitem -Path $IQlogspath |
where-object {$_.Name -notmatch "Service"} |
move-item -destination $OneOff
Start-Sleep -s 10
write-host "creating zip folder, $todaysbackupzip"
Add-Type -assembly "system.io.compression.filesystem"
[io.compression.zipfile]::CreateFromDirectory($OneOff, $OneOffZip)
write-host "$todaysbackupzip created"
Remove-Item -Recurse -Force $todaysbackup
write-host "$todaysbackup removed"
exit
}