I want to move file to another folder based on date and extension(eg: .zip).For now, my script can successfully move the file to another folder based on date. Unfortunately, I do not have the idea how to do the script based on extension. Can someone help me to figure out this thing ? How to combine both date and extension in one script in order to move file ? Here is my code based to move file based on date.
$fs = dir C:\move_test_3\ | Where-Object { $_.LastWriteTime.date -lt (((get-
date).addDays(-1)).date) }
if ($fs)
{
move-item -path $fs -destination "C:\move_test_4"
}
I have tried this, but it is not right.
$fs = dir C:\move_test_3\ | Where-Object { $_.LastWriteTime.date -lt (((get-
date).addDays(-1)).date) }
if ($fs -and $file.Extension.Equals('.zip'))
{
move-item -path $fs -destination "C:\move_test_4"
}
You can do this many ways. Here is a one-liner:
Get-ChildItem c:\Move_test_3\*.zip |
where {$_.lastwritetime -lt (get-date).adddays(-1)} |
move-item -destination c:\move_test_4\
Checking for file extension is done later in the code below. As you already know which extension you are interested in, you might as well specify that in the get-childitem
if ($fs) {
foreach ($file in $fs) {
if ($file.Extension -eq '.zip') {
//move $file
}
}
}
You might also just filter by .zip at the beginning
dir C:\move_test_3\ -include '.zip'
Related
I am trying to read a CSV with a list of files including folder path and then delete them if they are older than x days.
I can do this is I list folders in the csv but cannot get it to work for just files.
CSV
FullName,,,,,,,,,,,,,
E:\$RECYCLE.BIN\S-1-5-21-352280589-691296097-1232828436-9414\$RCUCS3H.txt,,,,,,,,,,,,,
E:\$RECYCLE.BIN\S-1-5-21-352280589-691296097-1232828436-9414\$RWF5KKJ.txt,,,,,,,,,,,,,
E:\Account Lockout Files\Alockout.zip,,,,,,,,,,,,,
E:\Account Lockout Files\AlockoutXP.zip,,,,,,,,,,,,,
PowerShell contains.
$DatetoDelete = (Get-Date).AddDays(-3650)
Get-Content 'C:\delete\1.csv' | ForEach-Object { $_.Trim() } | Where-Object { $_.LastWriteTime -lt $DatetoDelete } | Remove-Item -Force
When I use the script I did above it just deletes the files even if they are older, please can any one assist? thanks.
You are almost there with your code, but you are missing the actual check for the last write time, if you use Get-Item to get the properties of the file, you will then have the LastWriteTime property for you to use in your Where-Object
The below should do what you need with just one extra command and a pipe
Get-Content 'C:\delete\1.csv' | ForEach-Object {
$_.Trim()
Get-Item -Path $_ |Where-Object { $_.LastWriteTime -lt $DatetoDelete } | Remove-Item -Force
}
Thanks for sharing an example of the input csv file. We now can be sure it actually is csv with headers and therefore, using Get-Content is the wrong cmdlet.
Try with Import-Csv instead:
$DatetoDelete = (Get-Date).AddDays(-3650).Date # set to midnight
Import-Csv -Path 'C:\delete\1.csv' | ForEach-Object {
$file = Get-Item -Path $_.FullName -ErrorAction SilentlyContinue
if (($file) -and $file.LastWriteTime -lt $DatetoDelete) {
$file | Remove-Item -Force
}
}
For some files in the csv you may not have permissions to delete, like perhaps the ones inside the E:\$RECYCLE.BIN\S-1-5-21-352280589-691296097-1232828436-9414..
I have used Advanced Renamer to rename all my photos by date, so they all have names such as:
2017-Oct-14_8;39_kyd.jpg
Where "8;39" is the time and "kyd" is a string of three random characters to reduce eliminate duplicate names. I would like to write a powershell script to sort them all into folders such as:
C:\Pictures\2017\Oct
Where the first directory would be the year and the second directory would be the month. If a photo does not have date taken metadata, it's name would be:
"--_;_kyd.jpg"
and I would like to sort it into a "MANUAL_SORT" folder located is C:\Pictures. I am trying to use powershell to do this and this is what I have so far:
$SourceFolder = 'C:\Pictures\Test'
$DestinationFolder = 'C:\Pictures\Test_Output'
Get-ChildItem -Path $SourceFolder -Filter *.jpg | ForEach-Object
{
$filename = $_.Name.Substring(0,7);
if ($filename.Substring(0,3) = "--_;")
{
Move-Item -Path $SourceFolder -Destination "$DestinationFolder\MAUNAL_SORT"
}
else
{
$Year = $filename.Substring(0,3)
$Month = $filename.Substring(5,7)
Move-Item -Path $SourceFolder -Destination $DestinationFolder\$Year\$Month
}
}
But I can't figure out how to use the ForEach-Object command to cycle through each picture. Can anyone suggest a method to accomplish this? Thank you!
It looks like you're using Move-Item incorrectly. Use $_.FullName as the argument to the -Path parameter. As written, you're repeatedly trying to move the entire source folder into the destination.
Your string comparison operations are wrong. Use -eq.
Your substring calls are getting one too few characters. The parameters are index and count. Index is zero-based, of course, but count is the actual number of characters you want.
Also, the $filename variable is only accomplishing an extra call to Substring(), it's not useful in the rest of the script.
gci $SourceFolder -Filter *.jpg | foreach {
$YYYY = $_.Name.Substring(0,4)
if ($YYYY -eq '--_;') {
mv $_.FullName $DestinationFolder\MANUAL_SORT\
} else {
$MM = $_.Name.Substring(5,3)
mv $_.FullName $DestinationFolder\$YYYY\$MM\
}
}
A couple of issues:
substring of name while retrieving filename, removed that code
if condition comparison - it should be '-eq'
Also please verify folder exists or not added #TODO
Get-ChildItem -Path $SourceFolder -Filter *.jpg | ForEach-Object {
$filename = $_.Name;
if ($filename.Substring(0,3) -eq "--_")
{
Move-Item -Path $_.FullName -Destination "$DestinationFolder\MAUNAL_SORT\"
}
else
{
$Year = $filename.Substring(0,3)
$Month = $filename.Substring(5,7)
#TODO: test path if directory exists else create it
Move-Item -Path $_.FullName -Destination $DestinationFolder\$Year\$Month
}
}
I am using the code below to filter out files depending on the headers in the file.
It works like a charm, but I have a problem with that it takes all the files in the $InputDirectory.
I would like to limit it so it only takes files that are 1-2 hours old.
There are two ways where I can get the date for this process.
Filename contains timestamp = XXXXXXXXXXX_XXXXXXXX_valuereport_YYYYMMDDhhmmss.csv
The timestamp the file was created (please note we are talking about 800K-1M files in the directory and more is added every hour, so the fastest way would be appreciated.
So how do I insert something in my code, so it besides the header, only takes files that are <1-2 hours old?
Sorry about the code example, I am new to this site and not sure how to get it in the right order.
Nothing yet.
foreach ($FilePath in (Get-ChildItem $InputDirectory -File) | Select-Object -ExpandProperty FullName) {
$Header = Get-Content $FilePath -First 1
# test for a string in the header line that distincts it from the other files
if ($Header -match ';energy,Wh,') {
# the substring ';energy,Wh,' defines this file as a 'HeatMeter' file
Copy-Item -Path $FilePath -Destination $OutputPathHeat
} elseif ($Header -match ';fabrication-no,,inst-value,0,0,0;datetime,,inst-value,0,0,0;volume,m3') {
# the substring ';datetime,,inst-value,0,0,0;volume,m3' defines this file as a 'WaterMeter' file
Copy-Item -Path $FilePath -Destination $OutputPathWater
} else {
# if all key substrings above did not match, move to the 'Other' directory
Copy-Item -Path $FilePath -Destination $OutputPathOther
}
There are several ways to filter a directory listing. The easiest way is to pipe the result of Get-ChildItem through Where-Object like:
Get-ChildItem -Path $InputDirectory -File |
Where-Object { $_.CreationTime -gt (Get-Date).AddHours(-2) } |
Select-Object -ExpandProperty FullName |
ForEach-Object {
$FilePath = $_
$Header = Get-Content $FilePath -First 1
# test for a string in the header line that distincts it from the other files
if ($Header -match ';energy,Wh,') {
# the substring ';energy,Wh,' defines this file as a 'HeatMeter' file
Copy-Item -Path $FilePath -Destination $OutputPathHeat
}
elseif ($Header -match ';fabrication-no,,inst-value,0,0,0;datetime,,inst-value,0,0,0;volume,m3') {
# the substring ';datetime,,inst-value,0,0,0;volume,m3' defines this file as a 'WaterMeter' file
Copy-Item -Path $FilePath -Destination $OutputPathWater
}
else {
# if all key substrings above did not match, move to the 'Other' directory
Copy-Item -Path $FilePath -Destination $OutputPathOther
}
}
It checks that the CreationTime is greater than now - 2h. Note that the last modified (LastWriteTime) timestamp may also be suitable for your use case.
I am using the below script to search for credit card numbers inside a folder that contains many subfolders:
Get-ChildItem -rec | ?{ findstr.exe /mprc:. $_.FullName }
| select-string "[456][0-9]{15}","[456][0-9]{3}[-| ][0-9]{4} [-| ][0-9]{4}[-| ][0-9]{4}"
However, this will return all instances found in every folder/subfolder.
How can I amend the script to skip the current folder on the first instance found? meaning that if it finds a credit card number it will stop processing the current folder and move to the next folder.
Appreciate you answers and help.
Thanks in advance,
You could use this recursive function:
function cards ($dir)
Get-ChildItem -Directory $dir | % { cards($_.FullName) }
Get-ChildItem -File $dir\* | % {
if ( Select-String $_.FullName "[456][0-9]{15}","[456][0-9]{3}[-| ][0-9]{4} [-| ][0-9]{4}[-| ][0-9]{4}" ) {
write-host "card found in $dir"
return
}
}
}
cards "C:\path\to\base\dir"
It'll keep going through subdirectories of the top level directory you specify. Whenever it gets to a directory with no subdirectories, or its been through all the subdirectories of the current directory, it'll start looking through the files for the matching regex, but will bail out of the function when the first match is found.
So really what you want is the first file in every folder that has a credit card number in the contents.
Break it into two parts. Get a list of all your folders, recursively. Then, for each folder, get the list of files, non-recursively. Search each file until you find one that matches.
I don't see any easy way to do this with pipes alone. That means more traditional programming techniques.
This requires PowerShell 3.0. I've eliminated ?{ findstr.exe /mprc:. $_.FullName } because all I can see that it does is eliminate folders (and zero length files) and this already handles that.
Get-ChildItem -Directory -Recurse | ForEach-Object {
$Found = $false;
$i = 0;
$Files = $_ | Get-ChildItem -File | Sort-Object -Property Name;
for ($i = 0; ($Files[$i] -ne $null) -and ($Found -eq $false); $i++) {
$SearchResult = $Files[$i] | Select-String "[456][0-9]{15}","[456][0-9]{3}[-| ][0-9]{4} [-| ][0-9]{4}[-| ][0-9]{4}";
if ($SearchResult) {
$Found = $true;
Write-Output $SearchResult;
}
}
}
Didn't have the time to test it fully, but I thought about something like this:
$Location = 'H:\'
$Dirs = Get-ChildItem $Location -Directory -Recurse
$Regex1 = "[456][0-9]{3}[-| ][0-9]{4} [-| ][0-9]{4}[-| ][0-9]{4}"
$Regex2 = "[456][0-9]{15}"
Foreach ($d in $Dirs) {
$Files = Get-ChildItem $d.FullName -File
foreach ($f in $Files) {
if (($f.Name -match $Regex1) -or ($f.Name -match $Regex2)) {
Write-Host 'Match found'
Return
}
}
}
Here is another one, why not, the more the merrier.
I'm assuming that your Regex is correct.
Using break in the second loop will skip looking for a credit card in the remaining files if one is found and continue to the next folder.
$path = '<your path here>'
$folders = Get-ChildItem $path -Directory -rec
foreach ($folder in $folders)
{
$items = Get-ChildItem $folder.fullname -File
foreach ($i in $items)
{
if (($found = $i.FullName| select-string "[456][0-9]{15}","[456][0-9]{3}[-| ][0-9]{4} [-| ][0-9]{4}[-| ][0-9]{4}") -ne $null)
{
break
}
}
}
I think the intention was to look inside each file for the PII data right?
If so, you need to open the load the file and search each line. The code you posted will only run a regex on the name of the file.
I want to delete all the content and the children of a given root folder, however i want to keep some files (the logs), which all reside in a logs folder
What is the elegant way of doing this in powershell.
Currently i am doing it in multile steps, surely this can be done easily... i have a feeling my oo-ness/language bias is getting in the way
eg
c:\temp
c:\temp\logs
c:\temp\logs\mylog.txt
c:\temp\logs\myotherlog.log
c:\temp\filea.txt
c:\temp\fileb.txt
c:\temp\folderA...
c:\temp\folderB...
after delete should just be
c:\temp
c:\temp\logs
c:\temp\logs\mylog.txt
c:\temp\logs\myotherlog.log
this should be simple; i think 12:47am-itis is getting me...
Thanks in advance
RhysC
dir c:\temp | where {$_.name -ne 'logs'}| Remove-Item -Recurse -force
Here is a general solution:
function CleanDir($dir, $skipDir) {
write-host start $dir
Get-ChildItem $dir |
? { $_.PsIsContainer -and $_.FullName -ne $skipDir } |
% { CleanDir $_.FullName $skipDir } |
? { $skipDir.IndexOf($_, [System.StringComparison]::OrdinalIgnoreCase) -lt 0 } |
% { Remove-Item -Path $_ }
Get-ChildItem $dir |
? { !$_.PsIsContainer } |
Remove-Item
$dir
}
CleanDir -dir c:\temp\delete -skip C:\temp\delete\logs
It works recursivelly. The first parameter to CleanDir is the start directory. The second one ($skipDir) is the directory that should not be deleted (and its content shouldn't be as well).
Edited: corrected confusing example ;)