So i have a directory full of folders that i want to move to another area, also i only want to move the folders that were created 30 days ago or more. I have a script that does what i need for files but it doesnt seem to work for folders. Script is below
Script for moving files
param (
[Parameter(Mandatory=$true)][string]$destinationRoot
)
$path = (Get-Item -Path ".\").FullName
Get-ChildItem -Recurse | ?{ $_.PSIsContainer }
Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-30)} |
Foreach-Object {
$content = $path + "\" + $_.Name
$year = (Get-Item $content).LastWriteTime.year.ToString()
$monthNumber = (Get-Item $content).LastWriteTime.month
$month = (Get-Culture).DateTimeFormat.GetMonthName($monthNumber)
$destination = $destinationRoot + "\" + $year + "\" + $month
New-Item -ItemType Directory -Force -Path $destination
Move-Item -Path $content -Destination $destination -force
}
The Get-ChildItem portion does not seem to pull directories in like it should.
So looking at the script i decided to change some things up
Function Move-FilesByAge(){
param (
[Parameter(Mandatory=$true)][string]$Source,
[Parameter(Mandatory=$true)][string]$Destination,
[Parameter(Mandatory=$true)][timespan]$AgeLimit
)
Get-ChildItem $Source -Directory -Recurse | ?{
$($_.CreationTimeUtc.Add($AgeLimit)) -lt $((Get-Date).ToUniversalTime())
} | %{
$Dpath = $Destination + "\" + $_.CreationTimeUtc.ToString("yyyy") + "\" + $_.CreationTimeUtc.ToString("MMMM")
New-Item -ItemType Directory -Force -Path $Dpath
Move-Item $_ -Destination $Dpath -Force
}
}
Move-FilesByAge -Source C:\Test -Destination C:\Test2 -AgeLimit (New-TimeSpan -days 30)
This can lead to a major issue. If a folder with the same name exists then it will pop a error that folder exists.
Since you are new to powershell lets go over some basics about this script. In Powershell we love Piping | which you did well in the original. We also a big fan of aliases Where-Object ?{}, Foreach-Object %{}.
Get-ChildItem has a built in switch for just returning directories -directory.
You are also using last LastWriteTime when you should be using CreationTime. CreationTimeUtc allows you to standardize your time across timezones by providing a base timezone.
Date.ToString(Date Format Here). IS a great way to shorten how you parse the date as a string. .ToString("yyyy") gets you the year in 4 numbers like 2018. .ToString("MMMM") will get the month by name like March.
Related
I've searched through both StackOverflow and SuperUser to try to figure this out, and I'm still getting plagued by a problem I can't figure out how to fix. I know it's something simple, but after playing with it for an hour I'm still stumped. Simple question: how the heck do I tell Get-Childitem to exclude folders?
Right up front here's the code that doesn't work:
$sourceDir="E:\Deep Storage"
$targetDir="W:\Deep Storage"
$excludeThese = 'Projects2','Projects3','Projects4';
Get-ChildItem -Path $sourceDir -Directory -Recurse |
where {$_.fullname -notin $excludeThese} |
Get-ChildItem -Path $sourceDir | ForEach-Object {
$num=1
$nextName = Join-Path -Path $targetDir -ChildPath $_.name
while(Test-Path -Path $nextName)
{
$nextName = Join-Path $targetDir ($_.BaseName + "_$num" + $_.Extension)
$num+=1
}
$_ | Move-Item -Destination $nextName -Force -Verbose -WhatIf
}
}
The underlying concept here already works:
$sourceDir="E:\Deep Storage"
$targetDir="W:\Deep Storage"
Get-ChildItem -Path $sourceDir -File -Recurse | ForEach-Object {
$num=1
$nextName = Join-Path -Path $targetDir -ChildPath $_.name
while(Test-Path -Path $nextName)
{
$nextName = Join-Path $targetDir ($_.BaseName + "_$num" + $_.Extension)
$num+=1
}
$_ | Copy-Item -Destination $nextName -Verbose
}
Basically what this does is it moves folders from one place to another, and if files exist in both places, it renames files coming in. It helps keep my archive drive clear. But there are three folders there that I want to exclude because I still pull assets from them regularly, so I don't need those files moved.
Hence the difference between the two code samples: in the first one, I'm trying to get Get-Childitem to exclude a specific trio of folders, while this second one just grabs everything all at once.
I tried just doing a straight -Exclude with $excludeThese as the variable, without success; I tried skipping the variable approach altogether and just putting the folder names in after -Exclude. Still didn't work. I also tried putting in the entire path to the folders I wanted to exclude. No good--no matter what I did, the -WhatIf showed that the script was trying to move everything, including the folders I was theoretically excluding.
The last trick I tried was one I came across here on SO, and that was to go a gci with the exclude argument first, then do another gci after it. That still failed, so now I have to turn to the experts for help.
I would use a regex string created from the (escaped) directory names to exclude to make sure files withing these folders are ignored.
Also, by using a lookup Hashtable of all file names already present in the target folder, figuring out if a file with a certain name already exists is extremely fast.
$sourceDir = 'E:\Deep Storage'
$targetDir = 'W:\Deep Storage'
$excludeThese = 'Projects2','Projects3','Projects4';
# create a regex string with all folder names to exclude combined with regex OR (|)
$excludeDirs = ($excludeThese | ForEach-Object { [Regex]::Escape($_) }) -join '|'
# create a lookup Hashtable and store the filenames already present in the destination folder
$existingFiles = #{}
Get-ChildItem -Path $targetDir -File | ForEach-Object { $existingFiles[$_.Name] = $true }
Get-ChildItem -Path $sourceDir -File -Recurse |
Where-Object {$_.DirectoryName -notmatch $excludeDirs} |
ForEach-Object {
# construct the new filename by appending an index number if need be
$newName = $_.Name
$count = 1
while ($existingFiles.ContainsKey($newName)) {
$newName = "{0}_{1}{2}" -f $_.BaseName, $count++, $_.Extension
}
# add this new name to the Hashtable so it exists in the next run
$existingFiles[$newName] = $true
# use Join-Path to create a FullName for the file
$newFile = Join-Path -Path $targetDir -ChildPath $newName
$_ | Move-Item -Destination $newFile -Force -Verbose -WhatIf
}
Assuming the excluded directories are at the top:
$sourceDir="E:\Deep Storage"
$excludeThese = 'Projects2','Projects3','Projects4'
get-childitem $sourcedir -exclude $excludethese | get-childitem -recurse
I have thousands of files of many years and I want to archive these files on year -> month basis. I want to keep latest 2 months of files and older than 2 months should be archived. The catch is to determine the year and month of a specific file I have to do it from the file name.
Filename format : ABCXYZ_20220715.xml
First 4 digits are year (2022), followed by 2 digits of month(07) and 2 digits of day(15).
These files not necessarily were created on the same date as given in the file name. Otherwise it would have been easy for me to achieve this using group by $_.LastWriteTime
Below is the desired output:
Script I wrote to achieve this, but using $_.LastWriteTime and NOT from the file name.
# Two months from the beginning of the month
$today = [datetime]::Today
$maxAge = $today.addMonths(-2)
$SourceFolder = "C:\Temp\sent"
$DestinationFolder = "C:\Temp\Archive"
$filesByMonth = Get-ChildItem -Path $SourceFolder -File |`
where LastWriteTime -LT $maxAge |`
group { $_.LastWriteTime.ToString("yyyy\\MM") }
foreach ($monthlyGroup in $filesByMonth) {
$archiveFolder = Join-Path $DestinationFolder $monthlyGroup.Name
New-Item -Path $archiveFolder -ItemType Directory -Force
$monthlyGroup.Group | Move-Item -Destination $archiveFolder
# $monthlyGroup.Group | Move-Item -Destination $_.fullName.Replace($SourceFolder, $archiveFolder)
#the second $archivefolder is the name for the ZIP file, the extensions is added automatically
Compress-Archive -Path $archiveFolder -DestinationPath $archiveFolder
Remove-Item $archiveFolder -Recurse
}
One solution is the following modification of your code. It just adds the custom "GroupableNameDate" with the date extracted from the files.
# Two months from the beginning of the month
$today = [datetime]::Today
$maxAge = $today.addMonths(-2)
$SourceFolder = "C:\Temp\sent"
$DestinationFolder = "C:\Temp\Archive"
$filesByMonth = Get-ChildItem -Path $SourceFolder -File |`
where LastWriteTime -LT $maxAge |`
Select *,#{
Name="GroupableNameDate"
Expression={
$d = $_.basename -split "_" | Select-Object -Last 1
[System.DateTime]::ParseExact($d,'yyyyMMdd',$null)
}
} |
group { $_.GroupableNameDate.ToString("yyyy\\MM") }
foreach ($monthlyGroup in $filesByMonth) {
$archiveFolder = Join-Path $DestinationFolder $monthlyGroup.Name
New-Item -Path $archiveFolder -ItemType Directory -Force
$monthlyGroup.Group | Move-Item -Destination $archiveFolder
# $monthlyGroup.Group | Move-Item -Destination $_.fullName.Replace($SourceFolder, $archiveFolder)
#the second $archivefolder is the name for the ZIP file, the extensions is added automatically
Compress-Archive -Path $archiveFolder -DestinationPath $archiveFolder
Remove-Item $archiveFolder -Recurse
}
Simple task. But can't realize.
I just need to take file from one directory (x) and move to another (z) + create there folder named "YYYY" (year of last edit), then, subfolder named "MM" (month of last edit).
So i find script and tried to adapt it for my needs. But, i can't run it succsesfully.
Here is code witch i took as example:
$files= Get-ChildItem -File «C:\Files\»
foreach ($file in $files) {
if ($file.lastwritetime -lt $lastweek) {
$file | Move-Item -Force -Destination { md («C:\Files\» + $_.LastWriteTime.ToString(«yyyy.MM») + «\» + $_.LastWriteTime.ToString(«yyyy.MM.dd»)) -Force}
}
}
So i made mine based on it:
$files= Get-childitem -path "c:\Files"
$files | foreach-object {Move-Item -Force -Destination { md ("C:\Files\" + $_.LastWriteTime.ToString("yyyy")+"\"+$_.LastWriteTime.ToString("MM"))}}
Please help me make it work!
Im not really good with powershell...
Better not construct paths by concatenating strings with +. To avoid errors, use the Join-Path cmdlet for that.
Also I would advice not trying to cram that much in one line of code, because when it fails, it is really hard to debug.
Try
$path = 'C:\Files'
Get-childitem -Path $path -File |
ForEach-Object {
$destination = Join-Path -Path $path -ChildPath ('{0}\{1:D2}' -f $_.LastWriteTime.Year, $_.LastWriteTime.Month)
$null = New-Item -Path $destination -ItemType Directory -Force
$_ | Move-Item -Destination $destination
}
You can shorten the code by creating tyhe destination path like this: $destination = Join-Path -Path $path -ChildPath ('{0:yyyy\\MM}' -f $_.LastWriteTime), but then you have to remember to escape the backslash in the -f Format template string by doubling it
I have the following code:
$dt = Get-Date -format "MM-dd-yyyy"
$logFolder = '\\192.168.20.151\user_backups\Veeam_Logs'
$source = "$logFolder\*.log"; $destination = "$logFolder\Backup Logs [$dt]"
New-Item -ItemType Directory -Path $destination -Force
Move-Item ($source | Where LastWriteTime -gt (Get-Date).AddDays(-7)) $destination -Force
I'm trying to move *.log files in its current directory (so not folders & not recursive) to its sub-folder but only those log files that are older than 7 days. For some reason the above isn't working as it's still copying files that are 20 days old. There's no error.
Also I don't want it to give me any errors if there are no *.log files to copy (or at least when there's no match). -ErrorAction SilentlyContinue didn't work for some reason.
Your help is appreciated.
To perform a condition against LastWriteTime attribute, you need to first return a FileInfoObject. A path string passed into Move-Item -Path won't have the attribute. You can do something like the following:
$dt = Get-Date -format "MM-dd-yyyy"
$logFolder = '\\192.168.20.151\user_backups\Veeam_Logs'
$destination = "$logFolder\Backup Logs [$dt]"
$source = Get-ChildItem -Path "$logFolder\*.log" | Where LastWriteTime -gt (Get-Date).AddDays(-7)
New-Item -ItemType Directory -Path $destination -Force
Move-Item -LiteralPath $source -Destination $destination -Force
$source here performs all the conditional logic. It is unclear if you want files newer or older than a certain date. If you want files that are newer than $date, you will want to use LastWriteTime -gt $date. If you want files older than $date, you will want to use LastWriteTime -lt $date.
You can have -Path value perform the logic, but you must pass in an expression.
$dt = Get-Date -format "MM-dd-yyyy"
$logFolder = '\\192.168.20.151\user_backups\Veeam_Logs'
$destination = "$logFolder\Backup Logs [$dt]"
$source = Get-ChildItem -Path "$logFolder\*.log"
New-Item -ItemType Directory -Path $destination -Force
Move-Item -LiteralPath ($source | Where LastWriteTime -gt (Get-Date).AddDays(-7)) -Destination $destination -Force
I am fairly new to powershell and have been tasked at cleaning up an archive server. I am trying to create a script to move files into a Year\Month folder structure by looking at it's LastWriteTime in Powershell.
I have the below however I do not know how to get it to look at the month the file was edited?
$Path = "D:\Data"
$NewPath = "D:\ArchiveData"
$Year = (Get-Date).Year
$Month = (Get-Date).Month
New-Item $NewPath\ -name $CurrentYear -ItemType Directory
New-Item $NewPath\$Year -Name $Month -ItemType Directory
Get-ChildItem -path $Path | Where-Object {$_.LastWriteTime -Contains (Get-Date).month} | Move-Item -Destination "$NewPath\$Year\$Month"
Any ideas of how I could do this would be appreciated?
Thanks
-contains is used to see if an array contains an item; it's not suitable here.
-eq is what you need. As per your variable $Month, you will need to get only the part you care about (i.e. the month):
($_.LastWriteTime).Month -eq (Get-Date).month
I think I would approach this from the other end. Directories can be created when needed.
When you are satisfied that the files would be moved correctly, remove the -WhatIf from the Move-Item cmdlet.
$Path = 'C:\src\t'
$NewPath = 'C:\src\tarch'
Get-ChildItem -File -Path $Path |
ForEach-Object {
$Year = $_.LastWriteTime.Year
$Month = $_.LastWriteTime.Month
$ArchDir = "$NewPath\$Year\$Month"
if (-not (Test-Path -Path $ArchDir)) { New-Item -ItemType "directory" -Path $ArchDir | Out-Null }
Move-Item -Path $_.FullName -Destination $ArchDir -WhatIf
}