Problems writing to file inside cmdlet? - powershell

I only dabble in PS so I'm probably missing something obvious, but this seems like it should work lol
I've got a filewatcher, looks for a specific file in a specific folder, when found it triggers an action. The action first writes to screen, then to logfile then triggers a follow-up script.
Writing to screen works, writing to logfile doesn't, triggering follow up script works too. I actually tried the writing to logfile a few different ways, even building a function before this part and calling it, like a Write-Log instead of Write-Host but it doesn't work either.
Is there a special means to write to log in the manner I'm trying?
$folder = 'D:\InputFiles\'
$filter = 'data.csv'
$LogFile = "D:\APIRead\logs\master.log"
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{IncludeSubdirectories = $false;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier PaymentsMKFileCreated -Action {
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$Output = "The file '$name' was $changeType at $timeStamp"
Write-Host $Output
Out-File -FilePath $LogFile -InputObject $Output -Append
#Invoke-Item 'D:\APIRead\scripts\process.bat'
}

$LogFile = "D:\APIRead\logs\master.log"
Try moving this line inside of your Event -Action
It could be trying to expand $LogFile, but it is not defined in the Action's scope.

Grok42's helpful answer offers a pragmatic solution to the problem: defining $LogFile inside the -Action script block by definition makes it available there (but only there).
Indeed, a script block { ... } passed to the -Action parameter of the Register-ObjectEvent cmdlet does not generally see variables defined in the caller's scope (unless those variables happen to be defined in the global scope), because such a script block runs in a dynamic module.
However, you may still want to declare your $LogFile variable in the caller's scope while also allowing it to be referenced in the -Action script block, so that both scopes can act on it.
To that end, you can use Register-ObjectEvent's -MessageData parameter to pass a value to the -Action script block, which it can reference as $Event.MessageData, via the automatic $Event variable:
$folder = 'D:\InputFiles\'
$filter = 'data.csv'
$LogFile = "D:\APIRead\logs\master.log"
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{IncludeSubdirectories = $false; NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite' }
# Note the use of -MessageData
Register-ObjectEvent $fsw Created -SourceIdentifier PaymentsMKFileCreated -MessageData $LogFile -Action {
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$logFile = $Event.MessageData # Get the log file path from the -MessageData argument
$Output = "The file '$name' was $changeType at $timeStamp"
Write-Host $Output
Out-File -FilePath $logFile -InputObject $Output -Append
}
Note:
For an example of passing multiple values from the caller's scope to the -Action script block, using a hashtable, see this answer.
It's even possible to pass the caller's entire state to -MessageData, via $ExecutionContext.SessionState, which allows the -Action block to retrieve any variable from the caller's state via $Event.MessageData.PSVariable.GetValue('varName')

removed the ** after append and make sure you have permissions to the directory you are writing to.
Adding -Force makes it create the file if it doesn't exist.
Out-File -FilePath $LogFile -InputObject $Output -Append -Force

Related

How to detect Folder change (renaming in particular) inside a folder using Powershell file watcher

I am using powershell to detect and record a folder change (created, renamed, deleted etc) inside a folder.
This parent folder receives subfolders from another location in format TEMP_XYZ. Once this folder is copied to this parent folder, the process automatically renames it to XYZ (Removed suffix TEMP_)
This change (rename) had to be detected and recorded in a log file as
\\test\folderwatch\XYZ was Renamed at 7/28/2021 2:03:00 PM
Folder TEMP_XYZ was renamed to XYZ
However, I am not able to achieve this as the code below only works on files. (txt,bmp, zip etc)
Any help is appreciated.
Code:
# specify the path to the folder you want to monitor:
$Monitorpath ="\\test\folderwatch"
$Path = $Monitorpath
# specify which files you want to monitor
$FileFilter = '*'
# specify whether you want to monitor subfolders as well:
$IncludeSubfolders = $true
# specify the file or folder properties you want to monitor:
$AttributeFilter = [IO.NotifyFilters]::FileName, [IO.NotifyFilters]::LastWrite
try
{
$watcher = New-Object -TypeName System.IO.FileSystemWatcher -Property #{
Path = $Path
#Filter = $FileFilter
IncludeSubdirectories = $IncludeSubfolders
NotifyFilter = $AttributeFilter
}
$action = {
# change type information:
$details = $event.SourceEventArgs
$Name = $details.Name
$FullPath = $details.FullPath
$OldFullPath = $details.OldFullPath
$OldName = $details.OldName
$ChangeType = $details.ChangeType
$Timestamp = $event.TimeGenerated
$LogDate = Get-Date -format "dd-MMMM-yy"
# save information to a global variable for testing purposes
# so you can examine it later
# MAKE SURE YOU REMOVE THIS IN PRODUCTION!**************************DO NOT USE FOR PROD**************************
$global:all = $details
$text = "{0} was {1} at {2}" -f $FullPath, $ChangeType, $Timestamp
Write-Host ""
Write-Host $text -ForegroundColor DarkYellow
Add-content "\\test\folder_watch_logs\watchlog_$LogDate.txt" -value $text
switch ($ChangeType)
{
'Changed' { "CHANGE" }
'Created' { "CREATED"}
'Deleted' { "DELETED"
Write-Host "Deletion Handler Start" -ForegroundColor Gray
Start-Sleep -Seconds 4
Write-Host "Deletion Handler End" -ForegroundColor Gray
}
'Renamed' {
# this executes only when a file was renamed
$text = "Folder {0} was renamed to {1}" -f $OldName, $Name
Write-Host $text -ForegroundColor Yellow
Add-content "test\folder_watch_logs\watchlog_$LogDate.txt" -value $text
}
# any unhandled change types surface here:
default { Write-Host $_ -ForegroundColor Red -BackgroundColor White ;
Add-content "test\folder_watch_logs\watchlog_$LogDate.txt" -value $_ }
}
}
$handlers = . {
Register-ObjectEvent -InputObject $watcher -EventName Changed -Action $action
Register-ObjectEvent -InputObject $watcher -EventName Created -Action $action
Register-ObjectEvent -InputObject $watcher -EventName Deleted -Action $action
Register-ObjectEvent -InputObject $watcher -EventName Renamed -Action $action
}
# monitoring starts now:
$watcher.EnableRaisingEvents = $true
$LogDate = Get-Date -format "dd-MMMM-yy"
Write-Host "Watching for changes to $Path"
Add-content "test\folder_watch_logs\watcherstatus.txt" -value "Watching for changes to $Path"
# since the FileSystemWatcher is no longer blocking PowerShell
# we need a way to pause PowerShell while being responsive to
# incoming events. Use an endless loop to keep PowerShell busy:
do
{
Wait-Event -Timeout 1
# write a dot to indicate we are still monitoring:
#Write-Host "." -NoNewline
} while ($true)
}
finally
{
# stop monitoring
$watcher.EnableRaisingEvents = $false
# remove the event handlers
$handlers | ForEach-Object {
Unregister-Event -SourceIdentifier $_.Name
}
$handlers | Remove-Job
# properly dispose the FileSystemWatcher:
$watcher.Dispose()
$LogDate = Get-Date -format "dd-MMMM-yy"
Write-Warning "Event Handler disabled, monitoring ends."
Add-content "test\folder_watch_logs\watcherstatus.txt" -value "Event Handler disabled, monitoring ends."
}
//Jagbir
Adjust your watcher's NotifyFilter so that it is looking at directory names
$AttributeFilter = [IO.NotifyFilters]::FileName, [IO.NotifyFilters]::LastWrite, [IO.NotifyFilters]::DirectoryName
or if you are only interested in changes in directory names only specify
$AttributeFilter = [IO.NotifyFilters]::DirectoryName
If you are only interested in the renaming events do not register the others
$handlers = . {
# Remove Changed, Created, and Deleted if they are of no concern
# Register-ObjectEvent -InputObject $watcher -EventName Changed -Action $action
# Register-ObjectEvent -InputObject $watcher -EventName Created -Action $action
# Register-ObjectEvent -InputObject $watcher -EventName Deleted -Action $action
Register-ObjectEvent -InputObject $watcher -EventName Renamed -Action $action
}

Unregister a registered filewatcher event does not work

I want to watch a folder with powershell and I am a PS beginner.
That script works ONE time when I start the script.
But when I have to restart the script again because I changed some script code I get this error message:
Cannot subscribe to the specified event. A subscriber with the source identifier 'FileChanged' already exists.
I tried:
this at the top of the script:
Unregister-Event -SourceIdentifier FileChanged
does not work.
How do I correctly unregister the event so I can run my script as often I want and the previously registered event is disposed?
CODE
$folder = "C:\temp"
$Watcher = New-Object IO.FileSystemWatcher $folder -Property #{
IncludeSubdirectories = $true
NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
$onChanged = Register-ObjectEvent $Watcher Changed -SourceIdentifier FileChanged -Action {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Write-Host "The file '$name' was $changeType at $timeStamp"
Write-Host $path
#Move-Item $path -Destination $destination -Force -Verbose
}
Ok, looking at what your trying to achieve... to answer your original question, you need to do the following to unregistered the event.
Get-EventSubscriber -SourceIdentifier "filechanged" | Unregister-Event
I have to ask why are you having to make 1000 adjustments to the code. If you are trying to register 1000 different events to be monitored it would make more sense to loop and increment a variable using the ++ modifier.
I have achieved this already if this is what your tying to accomplish and can share some code if you need it.

Powershell + Robocopy Auto backup executing multiple times

I've put together this script to detect file changes in a directory, so that whenever the changes take effect the file(s) changed will get backed up right away.
I have also set up an email notification.
The backup works. I can see whenever a file changes it gets copied over to the desired destination, however I am receiving three emails and the robocopy log shows no changes, which leads me to think it's being written three times each time a file changes. So the last time it gets written there will of course be no changes.
Below you can see the code, hope you can help me figure out what's going on.
#The Script
$folder = 'C:\_Using Last Template Approach\' # Enter the root path you want to monitor.
$filter = '' # You can enter a wildcard filter here.
# In the following line, you can change 'IncludeSubdirectories to $false if required.
$fsw = New-Object IO.FileSystemWatcher $folder -Property #{IncludeSubdirectories = $true;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Changed -SourceIdentifier AutoBackUp -Action {
$path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$datestamp = get-date -uformat "%Y-%m-%d#%H-%M-%S"
$Computer = get-content env:computername
$Body = "Documents Folders have been backed up"
robocopy "C:\_Using Last Template Approach" G:\BackUp\ /v /mir /xo /log:"c:\RobocopyLog.txt"
Send-MailMessage -To "me#me.com" -From "jdoe#me.com" -Subject $Body -SmtpServer "smtp-mm.me.com" -Body " "
# To stop the monitoring, run the following commands (e.g using PowerShell ISE:
# Unregister-Event AutoBackUp
}
i do not change your monitor script just change send mail and copy with copy-item powershell command
$folder = 'c:\sites' # Enter the root path you want to monitor.
$filter = '*.*' # You can enter a wildcard filter here.
# In the following line, you can change 'IncludeSubdirectories to $true if required.
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{IncludeSubdirectories = $false;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Changed -SourceIdentifier FileChanged -Action {
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Write-Host "The file '$name' was $changeType at $timeStamp" -fore white
Out-File -FilePath c:\sites\filechange\outlog.txt -Append -InputObject "The file '$name' was $changeType at $timeStamp"
$username=”gmailaccount”
$password=”password”
$smtpServer = “smtp.gmail.com”
$msg = new-object Net.Mail.MailMessage
$smtp = New-Object Net.Mail.SmtpClient($SmtpServer, 587)
$smtp.EnableSsl = $true
$smtp.Credentials = New-Object System.Net.NetworkCredential( $username, $password )
$msg.From = "gmail"
$msg.To.Add(“mail should check notify”)
$msg.Body=”Please See archive for notification”
$msg.Subject = “backup information”
$files=Get-ChildItem “c:\sites\filechange\”
Foreach($file in $files)
{
Write-Host “Attaching File :- ” $file
$attachment = New-Object System.Net.Mail.Attachment –ArgumentList S:\sites\filechange\$file
$msg.Attachments.Add($attachment)
}
$smtp.Send($msg)
$attachment.Dispose();
$msg.Dispose();
Copy-Item c:\sites\$name C:\a\$name }
i check this script work for me if change file content of file first email log file then copy them to destination c:\a\ also you and that file changed to attachment of mail

FileSystemWatcher works in the PowerShell ISE but not when run

I want to monitor a folder and move files that match certain criteria, so I'm trying to use the FileSystemWatcher.
I have a function that will be called with each new file:
function ProcessFile()
{
param ([string]$filename)
Write-Host "Processing file '$filename' to $destination"
}
And then I set up a FSW:
Write-Host "Watching $source for new files..."
$fsw = New-Object IO.FileSystemWatcher $source, $filter -Property #{IncludeSubdirectories = $false; NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action
{
ProcessFile $Event.SourceEventArgs.FullPath
}
That works fine when I run it from the ISE, and any files I drop into the watched folder are correctly tracked, but if I start a PowerShell window and run the script with .\FileWatch.ps1 then nothing happens.
I see the "watching ..." message, but never see a "processing..." message
Here's the full script that works in the ISE but not in a shell...
$source = 'D:\Dev\PowerShell\FileWatch\Test\Source'
$filter = '*.*'
$destination = 'D:\Dev\PowerShell\FileWatch\Test\Found\'
function ProcessFile()
{
param ([string]$filename)
Write-Host "Processing file '$filename' to $destination"
}
Write-Host "Watching $source for new files..."
$fsw = New-Object IO.FileSystemWatcher $source, $filter -Property #{IncludeSubdirectories = $false; NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
ProcessFile $Event.SourceEventArgs.FullPath
}
The problem is that your function ProcessFile isn't loaded in powershell session.
Try loading you script in this way:
. .\myscript.ps1
In this way your code in my system works!
Read about Dot Sourcing a script in powershell.

Trigger script when new folder has been added to a location

I'm automating a process and already made a powershell script for that. Now I need to make something which will call that script everytime a new folder has been added to a specific location i.e. a new build is dropped.
What should I use for this. Is WCF too much? And if not, got any leads for that? Any useful links.
Or is another powershell script better for that?
Keep in mind I need to check subfolders too.
Thanks.
Personnaly I'ld use System.IO.FileSystemWatcher
$folder = 'c:\myfoldertowatch'
$filter = '*.*'
$fsw = New-Object IO.FileSystemWatcher $folder, $filter
$fsw.IncludeSubdirectories = $true
$fsw.NotifyFilter = [IO.NotifyFilters]'DirectoryName' # just notify directory name events
$onCreated = Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action { ... do my stuff here } # and only when is created
Use this to stop watching event
Unregister-Event -SourceIdentifier FileCreated
Try this:
$fsw = New-Object System.IO.FileSystemWatcher -Property #{
Path = "d:\temp"
IncludeSubdirectories = $true #monitor subdirectories within the specified path
}
$event = Register-ObjectEvent -InputObject $fsw –EventName Created -SourceIdentifier fsw -Action {
#test if the created object is a directory
if(Test-Path -Path $EventArgs.FullPath -PathType Container)
{
Write-Host "New directory created: $($EventArgs.FullPath)"
# run your code/script here
}
}