powershell v2 : WebClient/UploadString never connect - powershell

with powershell v2 and pushbullet, I try to send push notification when a file in modified
$folder = 'c:\path\to\file'
$filter = '*.*'
$user = "pushbullet_token"
$url = "https://api.pushbullet.com/v2/pushes"
$fsw = New-Object IO.FileSystemWatcher $folder, $filter
$fsw.IncludeSubdirectories = $true
$fsw.NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
$onCreated = Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
$name = $Event.SourceEventArgs.Name
$path = $Event.SourceEventArgs.FullPath
Out-File -FilePath c:\path\to\file\outlog.txt -Append -InputObject "$path"
$title = $path
Add-Type -AssemblyName System.Web
$title = [System.Web.HttpUtility]::UrlEncode($title)
$data = "type=note&title=" + $title + "&body=body"
$webclient = new-object System.Net.WebClient
$webclient.Credentials = new-object System.Net.NetworkCredential($user, "")
Out-File -FilePath c:\path\to\file\outlog.txt -Append -InputObject "$data"
$result = $webclient.UploadString($url, "POST", $data)
Out-File -FilePath c:\path\to\file\outlog.txt -Append -InputObject "$result"
}
#Unregister-Event FileCreated
for check the script a outlog.txt file is write, but only the two first messages are writen and the notification never is submitted.
when I launch uploadstring manually
$user = "pushbullet_token"
$url = "https://api.pushbullet.com/v2/pushes"
$data = "type=note&title=title&body=body"
$webclient = new-object System.Net.WebClient
$webclient.Credentials = new-object System.Net.NetworkCredential($user, "")
$result = $webclient.UploadString($url, "POST", $data)
work ok.

The global variable $url is not available inside your event handler script block. Change your Register-ObjectEvent like so:
$onCreated = Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -MessageData $url -Action {
$name = $Event.SourceEventArgs.Name
$path = $Event.SourceEventArgs.FullPath
$url = $Event.MessageData
...
}

The call to $webclient.UploadString(...) in the event handler is throwing an exception, which terminates the EventJob context it's running in. You can verify this by typing:
Get-Job
and looking for the failed job. You can then do Recieve-Job on the failed job to get the error message. Its probably an authentication error. By putting a valid authentication token, I was able to get your code to work.
If you want to have event handler continue even in the event of an error you'll have to use a try/catch, for example:
$result = try { $webclient.UploadString($url, "POST", $data) }
catch { $_.Exception.InnerException.Response }

Related

How to register an event in Powershell so it stays permanent?

I have the below script, that is just supposed to monitor a folder, and when a .xlsx file is created, then it should convert it to a .csv file in another folder. It works on the first file created, but it doesn't work on the next file being created in the folder. How can I get the event to stay permanent?
$ErrorActionPreference = 'Stop'
$folder = 'c:\Users\exgtcl\hotfolder'
$destination = 'c:\Users\exgtcl\targetfolder'
$filter = '*.xlsx'
Function Convert-Csv
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)][String]$FullPath
)
$ExcelFiles = Get-ChildItem $FullPath
Write-Host "Working on file '$FullPath' "
$excelApp = New-Object -ComObject Excel.Application
$excelApp.DisplayAlerts = $false
$excelApp.Visible = $false
$workbook = $excelApp.Workbooks.Open($ExcelFiles.FullName)
$newName = "$destination\output.csv"
$workbook.SaveAs($newName, [Microsoft.Office.Interop.Excel.XlFileFormat]::xlCSV)
$workbook.Close()
# Release Excel Com Object resource
$excelApp.Workbooks.Close()
Start-Sleep 2
$excelApp.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excelApp) | Out-Null
}
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{
IncludeSubdirectories = $false
NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
$action = {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Convert-Csv -FullPath $path
Start-Sleep 3
Write-Host "moving files"
Move-Item $path -Destination $destination -Force -Verbose
}
$onCreated = Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action $action

New-Object System.IO.FileSystemWatcher multiple servers

I've got a script based on New-Object System.IO.FileSystemWatcher that watches a folder on my home file server for new files, and runs an external app, but now i'm looking to expand on the usage of New-Object System.IO.FileSystemWatcher, to monitor a set of multiple servers (input from a .csv).
I've got the below code working as far as detecting the events, but it generates an alert for a new file multiple times when new files are detected. Any idea how I could get it just generate one alert? I'm thinking it's how my loop is structured?
Any help appreciated!
$Servers = import-csv "C:\Scripts\Servers.csv"
while($true) {
ForEach ($Item in $Servers) {
# Unregister-Event $changed.Id -EA 0
$Server = $($Item.Server)
write-host "Checking \\$Server\c$\Scripts now"
#$folder = "c\Scripts"
$filter = "*.html"
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "\\$Server\c$\Scripts\"
$watcher.Filter = "*.html"
$watcher.IncludeSubdirectories = $False
$watcher.EnableRaisingEvents = $true
$created = Register-ObjectEvent $watcher "Created" -Action {
write-host "A new file has been created on $Server $($eventArgs.FullPath) -ForegroundColor Green
}
} #ForEach
write-host "Monitoring for new files. Sleeping for 5 seconds"
Start-Sleep -s 5
} #While
Here is the single server version of my script, basically, i want to do the same thing, except running against a bunch of servers:
$SleepTimer = 15
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "\\FILESERVER\NEWSTUFF\"
$watcher.Filter = "*.html"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
### DEFINE ACTIONS AFTER A EVENT IS DETECTED
$action = {
$path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$logline = "$changeType, $path"
write-host "$LogLine created"
**RUN EXTERNAL PROGRAM HERE**
add-content -Value $LogLine -path "\\Fileserver\Log.txt"
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED + SET CHECK FREQUENCY
$created = Register-ObjectEvent $watcher "Created" -Action $action
while ($true) {
write-warning "no new files detected. Sleeping for $SleepTimer seconds ..."
start-sleep -s $SleepTimer
}
I think each time the while loop is executing , new file system watcher is created which is generating the multiple alert .which is not happening in your single server version of script
can you check this :
$Servers = import-csv "C:\Scripts\Servers.csv"
ForEach ($Item in $Servers) {
# Unregister-Event $changed.Id -EA 0
$Server = $($Item.Server)
write-host "Checking \\$Server\c$\Scripts now"
#$folder = "c\Scripts"
$filter = "*.html"
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "\\$Server\c$\Scripts\"
$watcher.Filter = "*.html"
$watcher.IncludeSubdirectories = $False
$watcher.EnableRaisingEvents = $true
$created = Register-ObjectEvent $watcher "Created" -Action {
write-host "A new file has been created on $Server $($eventArgs.FullPath)" -ForegroundColor Green
}
}
while ($true) {
write-host "Monitoring for new files. Sleeping for 5 seconds"
Start-Sleep -s 5
} #While

FileSystemWatcher detect when file is moved to folder

I'm having trouble with a script that's monitoring a folder.
FileSystemWatcher only seems to detect when a file is being copied to the folder, not when it's just being moved to it from the same drive.
Is there a way to detect this?
$desFolder = "H:\Media"
$ExFolder = "H:\Monitor"
$Filter = '*.*'
$fsw = New-Object IO.FileSystemWatcher $ExFolder, $Filter
$fsw.IncludeSubdirectories = $true
$fswOnChange = Register-ObjectEvent -InputObject $fsw -EventName Changed -SourceIdentifier FileUpdated -Action {
$File = Get-Item $EventArgs.FullPath
if($File.Name -imatch '\.(?:mp4|mkv)$' -and $file.Name -match '.*?S\d+E+'){
Start-Sleep -s 1
if(Test-FileReady $File.FullName){
Move-Files $File
}
}
}
function global:Test-FileReady {
Param([parameter(Mandatory=$true)][string]$Path)
if (Test-Path -LiteralPath $Path) {
trap {
return $false
}
$stream = New-Object system.IO.StreamReader $Path
if ($stream) {
$stream.Close()
return $true
}
}
}
function global:Move-Files{
Param([parameter(Mandatory=$true)][System.IO.FileInfo]$File)
Write-Host $File.Name
}
Try using Renamed and Created events as well.
BTW, the IO.FileSystemWatcher docs say:
The component will not watch the specified directory until the Path is set, and EnableRaisingEvents is true
$fsw.EnableRaisingEvents = $true

$Event.SourceEventArgs.<attribute> is returning a null or empty object

I have a FileSystemWatcher program that whenever an event is raised it sends an email notifying me of where the change happened.
However, $Event.SourceEventArgs.FullPath returns empty, and the other attributes returns $null.
Relevant code:
Function Watch{
$global:FileChanged = $false
$folder = "\\foo\boo\here\is\folder"
$filter = "*this is the filter*"
$watcher = New-Object IO.FileSystemWatcher $folder, $filter -Property #{
IncludeSubdirectories = $true
EnableRaisingEvents = $true
}
# Start-Sleep -Seconds 1
Register-ObjectEvent $watcher "Changed" -Action {$global:FileChanged = $true} > $null
# Register-ObjectEvent $watcher "Created" -Action {$global:FileChanged = $true} > $null
# Register-ObjectEvent $watcher "Deleted" -Action {$global:FileChanged = $true} > $null
# $watcher.EnableRaisingEvents = $true
while($true){
while($global:FileChanged -eq $false){
Start-Sleep -Milliseconds 250
}
$global:FileChanged = $false
$paths = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.FileName
$changetype = $Event.SourceEventArgs.ChangeType
$TimeChanged = $Event.TimeGenerated
RunScript $paths, $name, $changetype, $TimeChanged
}
}
Function RunScript
{
param($paths, $name, $changetype, $TimeChanged)
Write-Host "This should work $paths $name $changetype $TimeChanged"
$From = "email"
$ToAddress = "email"
$MessageSubject = "Form Submitted by $paths"
$MessageBody = "File Path: $Event.SourceEventArgs.FullPath
Name: $name
Change Type?: $changetype
Time Changed: $timechanged"
$SendingServer = "999.999.999.9.9"
$SMTPMessage = New-Object System.Net.Mail.MailMessage $From, $ToAddress,
$MessageSubject, $MessageBody
$SMTPClient = New-Object System.Net.Mail.SMTPClient $SendingServer
$SMTPClient.Send($SMTPMessage)
}
Watcher
Updated example. Note: I haven't tested this at all so be aware there are likely bugs. I've added some comments and a couple of different approaches for other sections of the code.
Function Watch {
$folder = "\\foo\boo\here\is\folder"
$filter = "*this is the filter*"
$watcher = New-Object IO.FileSystemWatcher $folder, $filter -Property #{
IncludeSubdirectories = $true
EnableRaisingEvents = $true
}
# Start-Sleep -Seconds 1
Register-ObjectEvent $watcher "Changed" > $null
# Register-ObjectEvent $watcher "Created" > $null
# Register-ObjectEvent $watcher "Deleted" > $null
# $watcher.EnableRaisingEvents = $true
while($true){
Wait-Event | Get-Event | ForEach-Object {
# $paths = $_.SourceEventArgs.FullPath
# $name = $_.SourceEventArgs.FileName
# $changetype = $_.SourceEventArgs.ChangeType
# $TimeChanged = $_.TimeGenerated
# If you comma-delimit this list you pass all of the arguments into "RunScript".
# Either name the parameters or make them positional.
# Positional:
# RunScript $paths $name $changetype $TimeChanged
# Named:
# RunScript -Paths $paths -Name $name -ChangeType $changetype -TimeChanged $TimeChanged
# Or use Splatting:
$params = #{
Paths = $_.SourceEventArgs.FullPath
Name = $_.SourceEventArgs.FileName
ChangeType = $_.SourceEventArgs.ChangeType
TimeChanged = $_.TimeGenerated
}
RunScript #params
}
}
}
Function RunScript {
param($paths, $name, $changetype, $TimeChanged)
Write-Host "This should work $paths $name $changetype $TimeChanged"
# $From = "email"
# $ToAddress = "email"
# $MessageSubject = "Form Submitted by $paths"
# $MessageBody = "File Path: $Event.SourceEventArgs.FullPath
# Name: $name
# Change Type?: $changetype
# Time Changed: $timechanged"
# $SendingServer = "999.999.999.9.9"
# $SMTPMessage = New-Object System.Net.Mail.MailMessage $From, $ToAddress,
# $MessageSubject, $MessageBody
# $SMTPClient = New-Object System.Net.Mail.SMTPClient $SendingServer
# $SMTPClient.Send($SMTPMessage)
# Is there a reason you're not using Send-MailMessage?
# Splatting again :)
# The body of the email isn't going to be very pretty, but it's a start.
$params = #{
From = "email"
To = "email"
Subject = "Form Submitted by $paths"
Body = "File Path: $paths
Name: $name
Change Type?: $changetype
Time Changed: $timechanged"
"
SmtpServer = "999.999.999.9.9"
}
Send-MailMessage #params
}
Now you get a caveat. The FileSystemWatcher is useful, but because of how Windows works you may well receive two event notifications for a single change (that's a deep Windows problem as opposed to a code problem). Try it and see.
Chris
Edit: Forgot to remove the action parameter. It's gone now.

Powershell variable getting "lost"

I'm working on a script that monitors an error folder and sends e-mail when an error occurs. For each error we get two files in the folder, one text file and one xml. In the e-mail I want to include the entire content of the text file and part of the content of the xml file.
In the following script I get only the content of the text file. (Lines for sending the email are commented out for testing purposes). I have read about variables and foreach and I guess this may be the problem, but I'm still not sure how to fix this in my script. Or maybe there is a much easier way to do this?
$Foldertowatch = 'C:\lpos\invalidArts'
$FilterXML = '*.xml'
$FilterTXT = '*.txt'
Unregister-Event -SourceIdentifier CreatedEventXML
$fswXML = New-Object System.IO.FileSystemWatcher $Foldertowatch, $FilterXML
$fswXML.EnableRaisingEvents=$true
$fswXML.IncludeSubdirectories=$false
Register-ObjectEvent -InputObject $fswXML -EventName Created -SourceIdentifier CreatedEventXML -Action {
$Filename = $Event.SourceEventArgs.Name
[XML]$XMLFile = (Get-Content $Foldertowatch\$Filename)
$XMLtmp = foreach ($user in $XMLFile.POSLog.POSLogTransaction)
{
$user.RetailStoreID
$user.WorkStationID
$user.OperatorID
$user.SequenceNumber
}
}
Unregister-Event -SourceIdentifier CreatedEventTXT
$fswTXT = New-Object System.IO.FileSystemWatcher $Foldertowatch, $FilterTXT
$fswTXT.EnableRaisingEvents=$true
$fswTXT.IncludeSubdirectories=$false
Register-ObjectEvent -InputObject $fswTXT -EventName Created -SourceIdentifier CreatedEventTXT -Action {
$FilenameTXT = $Event.SourceEventArgs.Name
$ContentTXT = (Get-Content $Foldertowatch\$FilenameTXT) -join "`n"
write-host $user.retailstoreid
Write-Host $contenttxt
<#$From = "from#mail.com"
$To = "to#mail.com"
$SMTPServer = "mailserver.com"
$subject = $env:COMPUTERNAME + " contains invalid ARTSXML"#>
<#Write-Host "Store:" + $ContentXML.RetailStoreID + "`n" + "Workstation:" + $ContentXML.WorkStationID + "`n" +"Operator:" + $ContentXML.OperatorID + "`n" +"Receiptnumber:" + $ContentXML.sequencenumber + "`n" + "`n" + "Filelocation: $Foldertowatch\$FilenameTXT" + "`n" + "`n" + <#$ContentTXT##>
<#$smtp = New-Object System.Net.Mail.SmtpClient($SMTPServer);
$smtp.Send($From, $To, $subject, $body);#>
}
The $ContentXML is from an early try, it should be $user now.
I manage to get content from XML if I run first part of script like this:
$Foldertowatch = 'C:\lpos\invalidArts' <# Folder to watch #>
$FilterXML = '*.xml' <# Use *.xml #>
$FilterTXT = '*.txt' <# Use *.txt for poslog2arts, use *.log for POS Reporting #>
Unregister-Event -SourceIdentifier CreatedEventXML
$fswXML = New-Object System.IO.FileSystemWatcher $Foldertowatch, $FilterXML
$fswXML.EnableRaisingEvents=$true
$fswXML.IncludeSubdirectories=$false
Register-ObjectEvent -InputObject $fswXML -EventName Created -SourceIdentifier CreatedEventXML -Action {
$Filename = $Event.SourceEventArgs.Name
[XML]$XMLFile = (Get-Content $Foldertowatch\$Filename)
$XMLtmp = foreach ($user in $XMLFile.POSLog.POSLogTransaction)
{
write-host $user.RetailStoreID
write-host $user.WorkStationID
write-host $user.OperatorID
write-hosts $user.SequenceNumber
}
}
I'm not sure of your intent, but I see a possible solutions. First, this code:
$XMLtmp = foreach ($user in $XMLFile.POSLog.POSLogTransaction)
{
$user.RetailStoreID
$user.WorkStationID
$user.OperatorID
$user.SequenceNumber
}
You never seem to use $XMLtmp again. Also, it's just going to contain an array of strings, not something that has properties like $user.RetailStoreID
What you can do is this:
$users = #()
Register-ObjectEvent -InputObject $fswXML -EventName Created -SourceIdentifier CreatedEventXML -Action {
$Filename = $Event.SourceEventArgs.Name
[XML]$XMLFile = (Get-Content $Foldertowatch\$Filename)
foreach ($user in $XMLFile.POSLog.POSLogTransaction) {
$users += $user
}
}
Then later you can index that array. Are there more than one user? Do you want to send more than one email?
At that point you can do the following:
$users | % { Write-Host $_.RetailStoreId }
The $user variable you create in the foreach () {} goes out of scope (aka "disappears") when the } is hit. The $XMLtmp is unnecessary. Declaring $users outside of the scope of the Action and foreach ensures it is global to the entire script.