Checking if files exist in folders - powershell

I have a task to monitor if files exist in certain folders for longer then 5 minutes. I need the script to only send emails if files exist older then 5 minutes. Right now the script will run and email me however I am having trouble getting the output formatted correctly.
I'd like the email to include the first column of the CSV, Company, the path and the output of get-childitem. I will be removing the write-host's since the script will run via a scheduled task. The CSV has the following format..
\\some\share
\\some\share2
\\some\share3
Though I would like to have it as..
Company1,\\some\share
Company2,\\some\share2
Company3,\\some\share3
So that I can include the first header in the email
Here's what I have for the code so far..
$SmtpClient = new-object system.net.mail.smtpClient
$MailMessage = New-Object system.net.mail.mailmessage
$SmtpClient.Host = "smtp.some.address"
$MailMessage.from = ($ServerName + "#someaddress.com")
$MailMessage.To.add("me#someaddress.com")
$MailMessage.Subject = "File Monitor"
$date = (get-date).AddMinutes(-5)
foreach($path in get-content "C:\Scripts\servers_path.txt") {
if ((Test-Path -path $path)) {
$item = get-childitem $path *.* | where-object {$_.LastWriteTime -lt $date}
$item | Select FullName, Name, LastWriteTime
if ($item) {
$MailMessage.Body += "$path `n"
$MailMessage.Body += "$item `n`n"
$MailMessage.Body += "---------------------------------------------------------------------------------------------------------`n"
}
}
elseif (!(Test-Path $path)) {
Write-Host "The folder," $path "does not exist" -fore red
}
}
$SmtpClient.Send($MailMessage)
Thank you

I think originally I was confusing your input as output in the question. How about something like this then? Assuming the following input data is contained in "c:\temp\data.txt".
Company1,\\some\share
Company2,\\some\share2
Company3,\\some\share3
Read the comments in code to get a sense for what is going on there. This code would go in place of your ForEach loop that you already have in theory. You would need to play with the output to get your desired outcome. If I am off base be as verbose as possible.
$date = (get-date).AddMinutes(-5)
# Import the data as a csv into PowerShell specifying the headers since they dont exist.
Import-csv -Path "c:\temp\data.txt" -Header "Company","Path" | ForEach-Object{
# Check if the path is valid for this item.
If(Test-Path $_.Path){
# This folder exists. Check the contents for certain files.
$data = Get-ChildItem $_.Path | Where-Object {$_.LastWriteTime -lt $date}
If($data){
# Output the company name and path on a separate line.
$message = "`r`n$($_.Company)`r`n$($_.Path)`r`n"
# Output each file on its own line.
$message += "$(($data | Select-Object -ExpandProperty Name) -Join "`r`n")"
# Append built string to the body of the email.
$MailMessage.Body += $message
}
} Else {
# Path does not exist. Have message stating such.
$MailMessage.Body += "`r`n$($_.Company)`r`n$($_.Path) does not exist `r`n"
}
}
If you wanted to return more properties like LastWriteTime we could do that if we replace this one line
$message += "$(($data | Select-Object -ExpandProperty Name) -Join "`r`n")"
With something like this:
$data | ForEach-Object{
$message += "Name: '{0}' - Last Write Time: '{1}'" -f $_.Name, $_.LastWriteTime
}

Related

Create a Clickable Folder Link in Powershell

I have a script that searches for a file name in a folder and gives a folder path. Does Powershell have a way to make these folder paths a clickable link? I want to bring up a list of file directory paths that I can click on.
Here is what the code looks like.
#Declare Variables.
$Software = #()
#Delcares Directories to search.
$Directories = #(
'\\Software\Unlicensed Software'
'\\Software\Licensed Software')
#Gets folder contents of directories at a depth of three.
Foreach($Directory in $Directories)
{
$Path = Get-ChildItem $Directory -Recurse -Depth 3| ?{ $_.PSIsContainer } | Select-Object FullName;
$Software += $Path
}
#Gets user string.
$target = Read-Host -Prompt 'Input a software name or part of a name. ("Exit" to quit)';
#Finds matches and adds them to Links.
while ($target -ne "exit")
{
$count = 0;
$Links = New-Object Collections.Generic.List[string];
Foreach ($line in $Software)
{
if($line -like "*$target*")
{
$Links.Add($line.FullName);
$count += 1;
}
#Stops code when results are greater than 100 entries.
if($count -gt 99)
{
Write-Output "Your search result yielded more than 100 entries. Try narrowing down your search."
Break
}
}
#Prints links.
ForEach($Link in $Links)
{
Write-Output $Link;
}
Write-Host `n$count" entries found`n";
#Asks users if they would like to continue.
$target = Read-Host -Prompt 'Input a software name or part of a name ("Exit" to quit)';
}
#Exits Program
Write-Host "Press any key to continue ...";
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown");
This is a quite simple way you could use to select a folder an open it, it will display an Out-GridView until you cancel or close the grid and open the folder once you make a selection. If you're looking for something better looking and more complex you would have to code your own WinForm app.
$directories = Get-ChildItem $env:USERPROFILE -Directory |
Select-Object Name, FullName
do
{
$selection = $directories |
Out-GridView -PassThru -Title 'Choose a Folder'
if($selection){ Invoke-Item $selection.FullName }
} while($selection)
You could make a HTML File:
Add-Type -AssemblyName System.Web
[System.Web.HttpUtility]::HtmlDecode((gci | select Name,#{n='Fullpath'; e={(("<a href='file://" +$_.fullname+"'>"+$_.Fullname+'</a>' ))}} | ConvertTo-Html)) | out-file c:\file.html
iex c:\file.html

Can someone help to add email function correctly

I am adding an email function to send an email if the file uploads success or failure, but when I upload the file, it sends a message as I write in the body for success but I am not sure if it captures the error function as well in the email.
I upload the file as per the naming convention and it moved successfully.
When I tried to upload the wrong file through this, it does not capture the catch statement in the log file.
I want to capture the backup copy name in logs as well.
Got an email on the successful event but didn't get an email on the catch statement.
Please see the below code.
$Source = 'c:\uploadtool\' # Source Location
$RetailSource = 'Retail P&C Sales Intelligence\*'
$GroupSource = 'Group P&C Sales Intelligence\*'
$RetailDest = 'D:\ToolUpload\Retail-EIP' # 1st Destination Location
$GroupDest = 'D:\ToolUpload\Group-EIP' # 2nd Destination location
$ArchiveData = 'D:\Backup\backup_{0:yyyyMMddHHmm}' -f (Get-Date)
$LogFolder = 'D:\logs'
# because of your wish to get a Table-style log, use CSV format you can open in Excel
$LogFile = 'D:\logs\uploadlog_{0:yyyyMMdd}.csv'-f (Get-Date)
$Sourcetest = Test-Path -Path '$RetailSource','$GroupSource' -PathType Leaf
$SmtpServer ='essexlake1.mail.protection.outlook.com' # SMTP Server name
$SmtpPort='25'
$Subject='File Upload Status'
$To="####domain.com"
$From="#####domain.com"
# make sure the output LogFolder exist
# by adding the -Force switch there is no need to use Test-Path first, because if
# the folder already exists, the cmdlet will return the DirectoryInfo of that,
# otherwise it will create a new folder. Since we dont want output, we use $null = ..
$null = New-Item -Path $LogFolder -ItemType Directory -Force
# loop through the files in the source folder and collect the outputted objects
$result = Get-ChildItem -Path $Sourcetest -Include '*Group-EIP*', '*Retail-EIP*' -File -Force -Recurse |
ForEach-Object {
Write-Host "Processing file '$($_.FullName)'"
# create an object with (for now) 3 empty properties
$out = $_ | Select-Object #{Name = 'Date'; Expression = {(Get-Date)}},
#{Name = 'Source'; Expression = {$_.FullName}},
#{Name = 'FileSize'; Expression = {$_.Length}},
Destination, # depends on the file name
#{Name = 'Archive'; Expression = {$ArchiveData}}, # initialize to Not Applicable
Result
# depending on its name, get the correct destination folder
$destFolder = if($_.Name -match "Retail-EIP") { $RetailDest } else { $GroupDest }
# create the backup destination folder if it didn't already exist
# the first file in column 'Source' is now responsible for creating the backup folder
$null = New-Item -Path $destFolder -ItemType Directory -Force
# get the full path and filename for the destination
$existingFile = Join-Path -Path $destFolder -ChildPath $_.Name
# add the destination folder to the output object
$out.Destination = $destFolder
try {
# if a file with that name already exists in the destination, move it to the Archive folder
if (Test-Path -Path $existingFile -PathType Leaf) {
# create the Archive folder if it didn't already exist
$null = New-Item -Path $ArchiveData -ItemType Directory -Force
Move-Item -Path $existingFile -Destination $ArchiveData -ErrorAction Stop
# add the archived file to the output object
$out.Archive = $existingFile
Write-Host "File '$existingFile' has been backed-up to '$ArchiveData'"
}
# next move the file from the source folder to its destination (either $RetailDest or $GroupDest)
$_ | Move-Item -Destination $destFolder -ErrorAction Stop
$out.Result = 'OK'
Write-Host "File '$($_.FullName)' has been moved to '$destFolder'"
$Body=" The File '$($_.FullName)' has been moved to '$destFolder"
Send-MailMessage -SmtpServer $SmtpServer -Port $Port -From $From -To $To -Subject $Subject -Body $Body
}
catch {
# ouch.. something went horribly wrong on a Move-Item action
Write-Warning "An error occurred: $_.Exception.Message"
$out.Result = "Error: $($_.Exception.Message)" | Add-Content -Path $LogFile -Force
}
# output the object so it gets collected in variable $result
$out
}
# now you can save the results as structured CSV file to open in Excel
$result | Export-Csv -Path $LogFile -UseCulture -NoTypeInformation -Append
# and display on screen using Out-GridView as the data will probably be too wide for Format-Table
$result | Out-GridView -Title 'Backup results
'
It's not going to send an email on-error because Send-MailMessage is the last statement in the Try{...} block. If anything errors before that the statement Send-MailMessage won't run.
If it were me, and from this starting point I'd populate $Body respective to the outcome (success/fail) and send the email afterward. Not only would this send with correct information regardless but avoids having a second Send-MailMessage command in the catch block. Obviously I can't rig up a test of such a big code segment, but it might look something like:
$Source = 'c:\uploadtool\' # Source Location
$RetailSource = 'Retail P&C Sales Intelligence\*'
$GroupSource = 'Group P&C Sales Intelligence\*'
$RetailDest = 'D:\ToolUpload\Retail-EIP' # 1st Destination Location
$GroupDest = 'D:\ToolUpload\Group-EIP' # 2nd Destination location
$ArchiveData = 'D:\Backup\backup_{0:yyyyMMddHHmm}' -f (Get-Date)
$LogFolder = 'D:\logs'
# because of your wish to get a Table-style log, use CSV format you can open in Excel
$LogFile = 'D:\logs\uploadlog_{0:yyyyMMdd}.csv'-f (Get-Date)
$Sourcetest = Test-Path -Path '$RetailSource','$GroupSource' -PathType Leaf
#Email Params:
$EmailParams = #{
SmtpServer ='essexlake1.mail.protection.outlook.com' # SMTP Server name
Port ='25'
Subject ='File Upload Status'
To ="####domain.com"
From ="#####domain.com"
}
# make sure the output LogFolder exist
# by adding the -Force switch there is no need to use Test-Path first, because if
# the folder already exists, the cmdlet will return the DirectoryInfo of that,
# otherwise it will create a new folder. Since we dont want output, we use $null = ..
$null = New-Item -Path $LogFolder -ItemType Directory -Force
# loop through the files in the source folder and collect the outputted objects
$result =
Get-ChildItem -Path $Sourcetest -Include '*Group-EIP*', '*Retail-EIP*' -File -Force -Recurse |
ForEach-Object {
Write-Host "Processing file '$($_.FullName)'"
# create an object with (for now) 3 empty properties
$out = $_ |
Select-Object #{Name = 'Date'; Expression = {(Get-Date)}},
#{Name = 'Source'; Expression = {$_.FullName}},
#{Name = 'FileSize'; Expression = {$_.Length}},
Destination, # depends on the file name
#{Name = 'Archive'; Expression = {$ArchiveData}}, # initialize to Not Applicable
Result
# depending on its name, get the correct destination folder
$destFolder = if($_.Name -match "Retail-EIP") { $RetailDest } else { $GroupDest }
# create the backup destination folder if it didn't already exist
# the first file in column 'Source' is now responsible for creating the backup folder
$null = New-Item -Path $destFolder -ItemType Directory -Force
# get the full path and filename for the destination
$existingFile = Join-Path -Path $destFolder -ChildPath $_.Name
# add the destination folder to the output object
$out.Destination = $destFolder
try
{ # if a file with that name already exists in the destination, move it to the Archive folder
if (Test-Path -Path $existingFile -PathType Leaf) {
# create the Archive folder if it didn't already exist
$null = New-Item -Path $ArchiveData -ItemType Directory -Force
Move-Item -Path $existingFile -Destination $ArchiveData -ErrorAction Stop
# add the archived file to the output object
$out.Archive = $existingFile
Write-Host "File '$existingFile' has been backed-up to '$ArchiveData'"
}
# next move the file from the source folder to its destination (either $RetailDest or $GroupDest)
$_ | Move-Item -Destination $destFolder -ErrorAction Stop
$out.Result = 'OK'
Write-Host "File '$($_.FullName)' has been moved to '$destFolder'"
$Body = " The File '$($_.FullName)' has been moved to '$destFolder"
}
catch
{ # ouch.. something went horribly wrong on a Move-Item action
$Body = " Error occured trying to move the file '$($_.FullName)' to '$destFolder. `n$($_.Exception.Message)"
Write-Warning "An error occurred: $_.Exception.Message"
$out.Result = "Error: $($_.Exception.Message)" | Add-Content -Path $LogFile -Force
}
Send-MailMessage #EmailParams -Body $Body
# output the object so it gets collected in variable $result
$out
}
# now you can save the results as structured CSV file to open in Excel
$result | Export-Csv -Path $LogFile -UseCulture -NoTypeInformation -Append
# and display on screen using Out-GridView as the data will probably be too wide for Format-Table
$result | Out-GridView -Title 'Backup results'
Forgive the reformat, that was for my own readability. I also added splatting to the email command. You were setting all those parameter vars early in the script any way...
Note: for all other parts of this question I think the straight forward answer is you have to add the instructions to tell the program what you want it to do. If you want to log on a specific condition, figure out where that's going to happen and add an Add-Content command etc...

Issues with Copy-item script

I have a script that needs to copy a list of files to ceraitn directories and locations on target servers.
I was able to understand that I need to creat an csv file as follows:
I need to understand from you how to make the files from source location to their adjacent file in target location. Any ideas?
My code looks like this:
# customize log file
$date = (Get-Date -Format d).ToString() | foreach {$_ -replace "/", "_"}
$time = (Get-Date)
$scriptDir = "D:\Scripts\ServerBuildToolkitT1\SingleFileUpfate\"
$logDir = "D:\Scripts\ServerBuildToolkitT1\Logs\SingleFileUpfate\"
$logFileName = "SingleFileUpfate $date.log"
$sources = #()
$destinsyions = #()
function CSV {
Import-Csv D:\Scripts_PS\SD.csv | ForEach-Object {
$sources += $_."Source Location"
$destinations += $_."Destination Location"
}
}
# this file contains the list of destination server that you want copy
# file/folder to
$computers = Get-Content "D:\Scripts_PS\ServerList.txt"
function main
{
foreach ($computer in $computers) {
foreach ($destination in $destinations) {
Write-Output "$([DateTime]::Now) Copying files update to $computer now" |
Out-File -FilePath "$logDir\$logFileName" -Append
Copy-Item $sources -Destination "\\$computer\$destination" -Force -Recurse -Verbose:$false
}
}
}
csv
main
Write-Output "$([DateTime]::Now) the operation SingleFileUpdate completed successefully" |
Out-File -FilePath "$logDir\$logFileName" -Append
i have updtaed the Script (as seen above) and now i am getting the following ERROR
"WARNING: One or more headers were not specified. Default names starting with "H" have been used in place of any missing headers."

Delete Files Older Than X Days: Presentation of Data/Logging Files

I'm new to Powershell, I'm creating a code to delete a file/s if more than "x" days.
I'm almost done. Need your help in representing my date (table) and should not produce a log file if no files will be delete.
Here's my code:
$max_days = "-30"
$curr_date = Get-Date
$del_date = $curr_date.AddDays($max_days)
$Path = "C:\Desktop\Code"
$DateTime = Get-Date -Format "D=yyyy-MM-dd_T=HH-mm-ss"
$itemsearch = Get-ChildItem C:\Test -Recurse | Where-Object { $_.LastWriteTime -lt $del_date}
Foreach ($item in $itemsearch)
{
Write "File:", $item.Name "Modified:", $item.LastWriteTime "Path:", $item.FullName "Date Deleted:" $del_date | Out-File "C:\Desktop\Code\Deleted\SFTP_DeleteFiles_WORKSPACE_$DateTime.txt" -append
$item | Remove-Item
}
Can anyone please help me? It's already working by the way.
Just need to present the data in table form and don't create a log file if there's nothing to delete.
Update:
Already solved the condition statement by doing:
if($itemsearch)
{
Foreach ($item in $itemsearch)
{
Write "File:", $item.Name "Modified:", $item.LastWriteTime "Path:", $item.FullName "Date Deleted:" $del_date | Out-File "C:\Desktop\Code\Deleted\SFTP_DeleteFiles_WORKSPACE_$DateTime.txt" -append
$item | Remove-Item
}
}
else
{
Write "No files will be deleted."
}
Thanks!
What I want to display it in Excel/Text file is like this one:
http://i59.tinypic.com/30wv33d.jpg
Anyone?
It returns me with this one:
IsReadOnly;"IsFixedSize";"IsSynchronized";"Keys";"Values";"SyncRoot";"Count"
False;"False";"False";"System.Collections.Hashtable+KeyCollection";"System.Collections.Hashtable+ValueCollection";"System.Object";"4"
False;"False";"False";"System.Collections.Hashtable+KeyCollection";"System.Collections.Hashtable+ValueCollection";"System.Object";"4"
False;"False";"False";"System.Collections.Hashtable+KeyCollection";"System.Collections.Hashtable+ValueCollection";"System.Object";"4"
False;"False";"False";"System.Collections.Hashtable+KeyCollection";"System.Collections.Hashtable+ValueCollection";"System.Object";"4"
In Excel. Do you have any idea? I have to search it though.
To introduce tabular logging I would use a CSV file as output by replacing your foreach block by this code:
$results = #()
foreach ($item in $itemsearch)
{
$success = $true
try
{
$item | Remove-Item
}
catch
{
$success = $false
}
if( $success -eq $true )
{
Write-Host $item.FullName 'successfully deleted.'
$results += [PSCustomObject]#{'File'=$item.Name;'Modified'=$item.LastWriteTime;'Path'=$item.FullName;'Date Deleted'=$del_date;'State'='SUCCESS'}
}
else
{
Write-Host 'Error deleting' $item.FullName
$results += [PSCustomObject]#{'File'=$item.Name;'Modified'=$item.LastWriteTime;'Path'=$item.FullName;'Date Deleted'=$del_date;'State'='ERROR'}
}
}
$results | Export-Csv -Path "C:\Desktop\Code\Deleted\SFTP_DeleteFiles_WORKSPACE_$DateTime.csv" -Encoding UTF8 -Delimiter ';' -NoTypeInformation
First an empty array is created ($results).
The try/catch block is here to detect if the deletion succeeded or not, then the appropriate line is added to $results.
At the end the $results array is exported to CSV with ';' separator so you can open it right away with Excel.

How do I modify this powershell script so it creates a log of the date time and names of all files that were deleted or previously deleted?

I wanted to make a PowerShell script that deleted any type of file in a certain folder when they were 7 days old. The problem I am having is creating a log file with the date, time, and names of all the files that were deleted or were previously deleted when the script ran.
I am wondering if there is a way of modifying the answer found on this page: https://stackoverflow.com/questions/12326171/powershell-script-to-delete-sub-folders-and-files-if-creation-date-is-7-days-bu?lq=1
I would like it so that instead of sending an email with the report it will create one log file of all the files deleted with the names, time, and dates in the folder that I am storing the script (Please note I'd like it to append the log file each time not overwrite it). I am sorry because I do not know how to code so I have been stuck trying to do this myself for a long time now and I have been asking questions and such but still can't seem to get it to work. So yea if anyone can modify that script it would be very appreciated! Thank you!
Here is what I did with the script (if it helps) , but it doesn't work (Again I do not know how to code):
$report_items = #()
# set folder path
$dump_path = "C:FileDeleter\AutoDeleteFilesInThisFolder"
# set min age of files
$max_days = "-7"
# get the current date
$curr_date = Get-Date
# determine how far back we go based on current date
$del_date = $curr_date.AddDays($max_days)
# get the sub directories
$sub_dirs = Get-ChildItem $dump_path | Where-Object {!$_.PSIsContainer }
$sub_dirs | % {
if (Get-ChildItem $_.FullName -Recurse | Where-Object { $_.LastWriteTime -gt $del_date } ) {
$report_items += New-Object -Type PSObject -Property #{
Time = Get-Date -f "hh:mm:ss"
Message = "Skipping " + $_.FullName + " because it contains items newer than " + $del_date
}
}
else {
Remove-Item $_.FullName -Recurse
$report_items += New-Object -Type PSObject -Property #{
Time = Get-Date -f "hh:mm:ss"
Message = "Deleting " + $_.FullName + " because it contains no items newer than " + $del_date
}
}
}
$report_items | out-file "C:\FileDeleter\PruningReport.txt"
This should do it:
Get-ChildItem -Path "C:\FileDeleter\AutoDeleteFilesInThisFolder" -Filter *.torrent -Recurse | Where { $_.CreationTime -lt (Get-Date).AddDays(-7) } | %{Remove-Item -Force; if ($?) {"$(Get-Date -Format 'MM-dd-yy HH:mm:ss.ff') $_.Name" | Add-Content 'C:\FileDeleter\PruningReport.txt'}}
(The reason for the if ($?) block is to only write a log entry if the file deletion was successful - you don't want to assume that it was.)
Not as succinct, but I'm a fan of not over-pipelining in PowerShell.
[String] $strBaseDir = "c:\temp";
[String] $strFileFilter = "*.txt";
[Int] $intDayThreshold = 7;
[System.IO.FileSystemInfo] $objFile = $null;
[Int] $intLastWriteDays = 0;
[String] $strLogFileName = "d:\data\yourlogfile.log";
Get-ChildItem -LiteralPath $strBaseDir -Filter $strFileFilter -Recurse | Where-Object{ ! $_.PSIsContainer } |
Foreach-Object {
$objFile = $_;
$intLastWriteDays = [Math]::Round((New-TimeSpan -Start $objFile.LastWriteTime -End (Get-Date)).TotalDays);
if ( $intLastWriteDays -ge $intDayThreshold ) {
Write-Output -InputObject ( "{0:G} : Deleting file [{1}] with last write time of [{2:G}], which is [{3}] day(s) ago." -f (Get-Date), $objFile.FullName, $objFile.LastWriteTime, $intLastWriteDays ) | Add-Content -Path $strLogFileName -PassThru;
$objFile | Remove-Item -Force -ErrorAction silentlyContinue;
if ( ! $? ) {
Write-Output -InputObject "ERROR : Failed to remove file." | Add-Content -Path $strLogFileName -PassThru;
} #if
} else {
Write-Output -InputObject ( "{0:G} : Skipping file [{1}] with last write time of [{2:G}], which is [{3}] day(s) ago." -f (Get-Date), $objFile.FullName, $objFile.LastWriteTime, $intLastWriteDays ) | Add-Content -Path $strLogFileName -PassThru;
} #else-if
} #Foreach-Object