I'm using the Send-MailMessgae Cmdlet to send an automated message with content each quarter.
The actual email works as expected etc however I want to save a copy of this email to a network share.
I've tried using Tee-Object to place it in a variable and then out-file to save it but this did not work.
I'm I'm thinking the only way i'm going to be able to do this is to just create a Here String with the details in it them output that.
Ideally I would like it to be in the .eml format as its a conformation that we are notifying users for the project the email is for.
I hope this makes sense.
Kind Regards,
Nigel Tatschner
You could actually use Tee-Object to send the data straight to the file, instead of trying to use both Out-File and Tee-Object.
$logfile = 'C:\data\logfiles'
<data> | Tee-Object $logfile -Append
You can use System.Net.Mail.MailMessage
# Based on MailMessageExt
# By Allan Eagle | 11 Jan 2009
# http://www.codeproject.com/KB/IP/smtpclientext.aspx
######################################
function createMailMessage([string]$from, [string]$to, [string]$subject) {
[System.Net.Mail.MailMessage]$msg = New-Object -TypeName "System.Net.Mail.MailMessage" -ArgumentList $from, $to
[System.Text.Encoding]$enc = [System.Text.Encoding]::UTF8
$msg.SubjectEncoding = $enc
$msg.Subject = $subject
$msg.BodyEncoding = $enc
return $msg
}
######################################
function mailAddAttachment([System.Net.Mail.MailMessage]$msg, [string]$filePath) {
$fileExists = Test-Path -path $filePath -pathtype leaf
if ($fileExists) {
$fileName = Split-Path $filePath -Leaf
[System.Net.Mail.Attachment]$att = New-Object -TypeName "System.Net.Mail.Attachment" -ArgumentList $filePath
$att.ContentType.MediaType = [System.Net.Mime.MediaTypeNames+Application]::Octet
$att.ContentType.Name = $fileName
$att.ContentDisposition.FileName = $fileName
$att.ContentDisposition.DispositionType = [System.Net.Mime.DispositionTypeNames]::Attachment
$att.ContentDisposition.Inline = $false
$att.TransferEncoding = [System.Net.Mime.TransferEncoding]::Base64
$msg.Attachments.Add($att)
$att = $null
return $true
} else {
$txt = "File ""$filePath"" not found"
Write-Output $txt
return $false
}
}
######################################
function saveMailMessage([System.Net.Mail.MailMessage]$msg, [string]$filePath) {
$binding = [System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic
$mailMessageType = $msg.GetType()
$emlFilePath = $filePath
[System.Type]$scType = [System.Type]::GetType("System.Net.Mail.SmtpClient")
[System.Net.Mail.SmtpClient]$smtpClient = New-Object -TypeName "System.Net.Mail.SmtpClient"
$scType = $smtpClient.GetType()
[System.Type]$booleanType = [System.Type]::GetType("System.Boolean")
[System.Reflection.Assembly]$assembly = $scType.Assembly
[System.Type]$mailWriterType = $assembly.GetType("System.Net.Mail.MailWriter")
[System.IO.FileStream]$fileStream = New-Object -TypeName "System.IO.FileStream" -ArgumentList ($emlFilePath,[System.IO.FileMode]::Create)
[System.Array]$typeArray = ([System.Type]::GetType("System.IO.Stream"))
[System.Reflection.ConstructorInfo]$mailWriterConstructor = $mailWriterType.GetConstructor($binding ,$null, $typeArray, $null)
[System.Array]$paramArray = ($fileStream)
$mailWriter = $mailWriterConstructor.Invoke($paramArray)
#get MailMessage.Send(MailWriter,Boolean,Boolean)
$doubleBool = $true
[System.Array]$typeArray = ($mailWriter.GetType(),$booleanType,$booleanType)
[System.Reflection.MethodInfo]$sendMethod = $mailMessageType.GetMethod("Send", $binding, $null, $typeArray, $null)
if ($null -eq $sendMethod) {
$doubleBool = $false
[System.Array]$typeArray = ($mailWriter.GetType(),$booleanType)
[System.Reflection.MethodInfo]$sendMethod = $mailMessageType.GetMethod("Send", $binding, $null, $typeArray, $null)
}
#get MailWriter.Close()
[System.Array]$typeArray = #()
[System.Reflection.MethodInfo]$closeMethod = $mailWriterType.GetMethod("Close", $binding, $null, $typeArray, $null)
#execute MailMessage.Send(MailWriter,Boolean,Boolean)
[System.Array]$sendParams = ($mailWriter,$true)
if ($doubleBool) {
[System.Array]$sendParams = ($mailWriter,$true,$true)
}
$sendMethod.Invoke($msg,$binding,$null,$sendParams,$null)
#execute MailWriter.Close()
[System.Array]$closeParams = #()
$closeMethod.Invoke($mailWriter,$binding,$null,$closeParams,$null)
}
######################################
function Get-ScriptDirectory {
Split-Path -Parent $PSCommandPath
}
######################################
$smtpServer = "smtp.here.com"
$smtpPort = 25
######################################
#$fileDir = Convert-Path "."
$fileDir = Get-ScriptDirectory
$fileDir
[System.Text.Encoding]$enc = [System.Text.Encoding]::UTF8
$dt = Get-Date
$dtStart = Get-Date $dt -Hour 0 -Minute 0 -Second 0
$baseName = $fileDir+"\report_"+$dtStart.toString("yyyy-MM-dd")
$repName = $baseName+".csv"
$emlName = $baseName+".eml"
[System.Collections.ArrayList]$rep = #()
$rep.Add("This is a report")
$rep.Add("----------------")
for ($i=0; $i -lt 100; $i++) {
$index = $rep.Add("Line $i")
}
Set-Content $repName $rep
$from = "Sender <Me#here.com>"
$to = "Recipient <Them#there.com>"
$subject = "This is a report generated here on "+$dtStart.toString("yyyy-MM-dd")
$body = "$subject`n`n$repName"
[System.Net.Mail.MailMessage]$msg = createMailMessage $from $to $subject
$msg.IsBodyHtml = $false
$msg.Body = $body
mailAddAttachment $msg $repName
$emlName
[System.Net.Mail.SmtpClient]$smtp = New-Object -TypeName "System.Net.Mail.SmtpClient" -ArgumentList $smtpServer, $smtpPort
# $smtp.Send($msg)
saveMailMessage $msg $emlName
$msg.Dispose()
Remove-Item $repName
Pause
Related
I'm using FileSystemWatcher to monitor a folder where documents are scanned to. When a new file is detected, it will send an email to notify someone. It's working as is, but sometimes (not every file) it will trigger 2 or 3 times on a new file and send the email 2-3 times for the same file. I'm guessing it has to do with the way the file is created by the scanner or something like that.
I'm trying to figure out a way to protect against this happening, to ensure it only sends one email per file. Any suggestions would be greatly appreciated.
$PathToMonitor = "\\path\to\folder"
$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
$FileSystemWatcher.Path = $PathToMonitor
$FileSystemWatcher.Filter = "*.*"
$FileSystemWatcher.IncludeSubdirectories = $false
$FileSystemWatcher.EnableRaisingEvents = $true
$Action = {
if ($EventArgs.Name -notlike "*.pdf" -and $EventArgs.Name -notlike "*.tif") {
return
}
$details = $event.SourceEventArgs
$Name = $details.Name
$Timestamp = $event.TimeGenerated
$text = "{0} was submitted on {1}." -f $Name, $Timestamp
$FromAddress = "Email1 <email1#email.com>"
$ToAddress = "Email2 <Email2#email.com>"
$Subject = "New File"
$SMTPserver = "123.4.5.678"
Send-MailMessage -From $FromAddress -To $ToAddress -Subject $Subject -Body $text -SmtpServer $SMTPserver
}
$handlers = . {
Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Created -Action $Action -SourceIdentifier FSCreateConsumer
}
try {
do {
Wait-Event -Timeout 5
} while ($true)
}
finally {
Unregister-Event -SourceIdentifier FSCreateConsumer
$handlers | Remove-Job
$FileSystemWatcher.EnableRaisingEvents = $false
$FileSystemWatcher.Dispose()
}
This may be because you listen too many notifications. The default is LastWrite, FileName, and DirectoryName
FileName is sufficient for your need and may prevent your issue.
$FileSystemWatcher.NotifyFilter = [System.IO.NotifyFilters]::FileName
As a remark, I don't know why you use Wait-Event -Timeout 5. Script is working fine without the try{} block.
EDIT: Add a ConcurrentDictionary to avoid duplicate events
Try this sample code. I've included only the beginning part of your script. End is untouched.
$PathToMonitor = "\\path\to\folder"
$KeepFiles = 5 #minutes
$MonitoredFiles = New-Object -TypeName 'System.Collections.Concurrent.ConcurrentDictionary[[System.String],[System.DateTime]]'
$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
$FileSystemWatcher.Path = $PathToMonitor
$FileSystemWatcher.Filter = "*.*"
$FileSystemWatcher.IncludeSubdirectories = $false
$FileSystemWatcher.NotifyFilter = [System.IO.NotifyFilters]::FileName
$FileSystemWatcher.EnableRaisingEvents = $true
$Action = {
if ($EventArgs.Name -notlike "*.pdf" -and $EventArgs.Name -notlike "*.tif") {
return
}
#Cleaning events -gt 5mn
$Now = [System.DateTime]::Now
$OriginEventDate = [System.DateTime]::MinValue
foreach($MonitoredFile in [System.Linq.Enumerable]::ToList(($MonitoredFiles.Keys))) {
if ($MonitoredFiles.TryGetValue($MonitoredFile, [ref]$OriginEventDate)) {
if ($OriginEventDate.AddMinutes($KeepFiles) -gt $Now) {
try {
[void]$MonitoredFiles.Remove($MonitoredFile)
}
catch {}
}
}
}
$ProcessEvent = $false
# any same file creation event within 5mn are discarded
$OriginEventDate = [System.DateTime]::MinValue
if ($MonitoredFiles.TryGetValue($event.SourceEventArgs.Name, [ref]$OriginEventDate)) {
if ($OriginEventDate -ne [System.DateTime]::MinValue -and $OriginEventDate.AddMinutes($KeepFiles) -le $Now) {
return
}
else {
$ProcessEvent = $true
}
}
else {
#not successful means a concurrent event was successful, so discard this one.
if ($MonitoredFiles.TryAdd($event.SourceEventArgs.Name, $event.SourceEventArgs.TimeGenerated)) {
$ProcessEvent = $true
}
else {
return
}
}
if ($ProcessEvent) {
$details = $event.SourceEventArgs
$Name = $details.Name
$Timestamp = $event.TimeGenerated
$text = "{0} was submitted on {1}." -f $Name, $Timestamp
$FromAddress = "Email1 <email1#email.com>"
$ToAddress = "Email2 <Email2#email.com>"
$Subject = "New File"
$SMTPserver = "123.4.5.678"
Send-MailMessage -From $FromAddress -To $ToAddress -Subject $Subject -Body $text -SmtpServer $SMTPserver
}
}
I had to adjust these two things to make it work:
if ($MonitoredFiles.TryAdd($Event.SourceEventArgs.Name, $Event.TimeGenerated))
if ($OriginEventDate -ne [System.DateTime]::MinValue -and $OriginEventDate.AddMinutes($KeepFiles) -ge $Now)
I need to download some webcontent from many servers in parallel as part of a scheduled job, but I cannot find a correct way to run the download in parallel/async. How can this be done?
Without any parallelism I can do it this way, but it is very slow:
$web = [System.Net.WebClient]::new()
[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# $srvList is a list of servers of viariable length
$allData = ""
foreach ($srv in $srvList) {
$url = "https:\\$srv\MyWebPage"
$data = $web.DownloadString($url)
$allData += $data
}
But how to do this in parallel via "$web.DownloadStringAsync"?
I found this snippet, but I dont see how to get the result of each call and how to concatenate it:
$job = Register-ObjectEvent -InputObject $web -EventName DownloadStringCompleted -Action {
Write-Host 'Download completed'
write-host $EventArgs.Result
}
$web.DownloadString($url)
Does someone know, how to get this solved in a short & smart way?
The best and fastest way is using runspaces:
Add-Type -AssemblyName System.Collections
$GH = [hashtable]::Synchronized(#{})
[System.Collections.Generic.List[PSObject]]$GH.results = [System.Collections.Generic.List[string]]::new()
[System.Collections.Generic.List[string]]$GH.servers = #('server1','server2');
[System.Collections.Generic.List[string]]$GH.functions = #('Download-Content');
[System.Collections.Generic.List[PSObject]]$jobs = #()
#-----------------------------------------------------------------
function Download-Content {
#-----------------------------------------------------------------
# a function which runs parallel
param(
[string]$server
)
$web = [System.Net.WebClient]::new()
[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$url = "https:\\$server\MyWebPage"
$data = $web.DownloadString($url)
$GH.results.Add( $data )
}
#-----------------------------------------------------------------
function Create-InitialSessionState {
#-----------------------------------------------------------------
param(
[System.Collections.Generic.List[string]]$functionNameList
)
# Setting up an initial session state object
$initialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
foreach( $functionName in $functionNameList ) {
# Getting the function definition for the functions to add
$functionDefinition = Get-Content function:\$functionName
$functionEntry = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $functionName, $functionDefinition
# And add it to the iss object
[void]$initialSessionState.Commands.Add($functionEntry)
}
return $initialSessionState
}
#-----------------------------------------------------------------
function Create-RunspacePool {
#-----------------------------------------------------------------
param(
[InitialSessionState]$initialSessionState
)
$runspacePool = [RunspaceFactory]::CreateRunspacePool(1, ([int]$env:NUMBER_OF_PROCESSORS + 1), $initialSessionState, $Host)
$runspacePool.ApartmentState = 'MTA'
$runspacePool.ThreadOptions = "ReuseThread"
[void]$runspacePool.Open()
return $runspacePool
}
#-----------------------------------------------------------------
function Release-Runspaces {
#-----------------------------------------------------------------
$runspaces = Get-Runspace | Where { $_.Id -gt 1 }
foreach( $runspace in $runspaces ) {
try{
[void]$runspace.Close()
[void]$runspace.Dispose()
}
catch {
}
}
}
$initialSessionState = Create-InitialSessionState -functionNameList $GH.functions
$runspacePool = Create-RunspacePool -initialSessionState $initialSessionState
foreach ($server in $GH.servers)
{
Write-Host $server
$job = [System.Management.Automation.PowerShell]::Create($initialSessionState)
$job.RunspacePool = $runspacePool
$scriptBlock = { param ( [hashtable]$GH, [string]$server ); Download-Content -server $server }
[void]$job.AddScript( $scriptBlock ).AddArgument( $GH ).AddArgument( $server )
$jobs += New-Object PSObject -Property #{
RunNum = $jobCounter++
JobObj = $job
Result = $job.BeginInvoke() }
do {
Sleep -Seconds 1
} while( $runspacePool.GetAvailableRunspaces() -lt 1 )
}
Do {
Sleep -Seconds 1
} While( $jobs.Result.IsCompleted -contains $false)
$GH.results
Release-Runspaces | Out-Null
[void]$runspacePool.Close()
[void]$runspacePool.Dispose()
Finally I found a simple solution via events. Here is my code-snippet:
cls
Remove-Variable * -ea 0
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$srvList = #('srv1','srv2','srv3')
$webObjList = [System.Collections.ArrayList]::new()
$eventList = [System.Collections.ArrayList]::new()
$resultList = [System.Collections.ArrayList]::new()
$i=0
foreach ($srv in $srvList) {
$null = $webObjList.add([System.Net.WebClient]::new())
$null = $eventList.add($(Register-ObjectEvent -InputObject $webObjList[$i] -EventName DownloadStringCompleted -SourceIdentifier $srv))
$null = $resultList.add($webObjList[$i].DownloadStringTaskAsync("https://$srv/MyWebPage"))
$i++
}
do {sleep -Milliseconds 10} until ($resultList.IsCompleted -notcontains $false)
foreach ($srv in $srvList) {Unregister-Event $srv}
# show all Results:
$resultList.result
I have a piece of code comparing two values, and if the condition is satisfied it sends out an email. But it is not working, help is appreciated.
code:
$filesize = Get-ChildItem $filename | Select-Object Length | Format-Wide
$filesize
$num=1265
$num
if("$filesize" -gt "$num")
{
$SMTPServer = "10.20.19.94"
$SMTPPort = 25
$username = "vcenter#somosadc.com"
#Define the receiver of the report
$to = "jeevan.m2#hcl.com"
$subject = "VM Snapshot Report"
$body = "VM Snapshot Report"
$attachment = new-object Net.Mail.Attachment($filename)
$message = New-Object System.Net.Mail.MailMessage
$message.subject = $subject
$message.body = $body
$message.to.add($to)
$message.from = $username
$message.attachments.add($attachment)
$smtp = New-Object System.Net.Mail.SmtpClient($SMTPServer, $SMTPPort);
$smtp.EnableSSL = $false
#$smtp.Credentials = New-Object System.Net.NetworkCredential($Username, $Password);
$smtp.send($message)
write-host "Mail Sent"
}
output:
1262
1265
Mail Sent
Why is it sending email if $filesize=1262 is less than $num=1265. It is killing me.
Because you're not comparing two numbers, you're comparing two strings.
Remove the Format-Wide command from the first pipeline, and remove the quotes around the arguments in your if condition:
$filesize = Get-ChildItem $filename | Select-Object Length
$num = 1265
if($filesize.Length -gt $num) {
<# ... #>
}
I like to change a GUI element (windows-form in powershell ISE), when a new file is created. Therefore I set up a form and start a filesystemwatcher in another runspace (MWE):
# this function should be called when a new file is created
function foobar(){
$form.BackColor = "black"
}
# set up runspace for async FileSystemWatcher
$Runspace = [runspacefactory]::CreateRunspace()
$PowerShell = [System.Management.Automation.PowerShell]::Create()
$PowerShell.runspace = $Runspace
$Runspace.Open()
[void]$PowerShell.AddScript({
$logFile = 'C:\powershell\test.log'
$dirName = 'C:\powershell\'
$hotFolder = New-Object IO.FileSystemWatcher $dirName -Property #{IncludeSubdirectories = $false;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $hotFolder Created -SourceIdentifier FileCreated -Action {
$name = $Event.SourceEventArgs.Name
$path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Out-File -FilePath $logFile -Append -InputObject "The file '$name' was $changeType at $timeStamp"
# this call does not work
foobar
}
})
$AsyncObject = $PowerShell.BeginInvoke()
# set up form
$form = New-Object System.Windows.Forms.Form
$form.ShowDialog()
The FileSystemWatcher works (log-file is written), but the call of "foobar" is ignored / does not work.
My first try was to Register the FileSystemWatcher within the form, which does not work (similar to this: FileSystemWatcher and GUI). I found this thread FileSystemWatcher kommt nicht mit Form zurecht (german only), which suggests the use of runspaces.
The runspace solves the stuck GUI-problem, but I need a way to trigger events in the form, when the fileSystemWatcher registers a new file. How can I achive that?
in short:
1) how can the FileSystemWatcher trigger a change of a GUI-element
2) are runspaces the right approach in this case
I'm not an expert in powershell (still learning). Any help and suggestions are appreciated.
Thanks in advance.
I did some research and found a solution:
added a synchronized hashtable to share variables between runspaces
added a button of the form to the hashtable (this button will be hidden in the final version drawing.Size(0,0) )
fileSystemWatcher makes use of performclick() to click the button
button calls desired function on click
There is still a point which feel awkward:
to unregister the fileSystemWatcher I set a shared variable to 1 and trigger the fileSystemWatcher by generating a file
Is there a more elegant way to do this?
Do I miss some points where the code is unnecessarily complicated?
Any comments are appreciated.
Here is a MWE. To use it set the $dir variable to your need. (MWE does not work in not updated powershell which ships with Win7)
# set working dir
$dir = 'C:\Downloads'
Write-Host 'working dir' $dir
# create function which is called when new file is created
function showPath($path){
$label.Text = $path
}
# set up runspace for async FileSystemWatcher
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.Open()
# synchronized hashtable and hashtable elements
$sync = [Hashtable]::Synchronized(#{})
$sync.path = 1 # random start value
$sync.exit = 0 # switch: if set to 1 fileSystemWatcher will be unregistert when next event occurs
$sync.dir = $dir
$btnNewFile= New-Object System.Windows.Forms.Button
$btnNewFile.Location = New-Object System.Drawing.Size(220,10)
$btnNewFile.Size = New-Object System.Drawing.Size(150,23)
$btnNewFile.Text = "do not click - fake button"
$btnNewFile.Add_Click({
$newPath = $sync.path
$form.text = $newPath
showPath($newPath)
})
$sync.btnNewFile = $btnNewFile
$Runspace.SessionStateProxy.SetVariable("sync", $sync)
$PowerShell = [System.Management.Automation.PowerShell]::Create()
$PowerShell.runspace = $Runspace
[void]$PowerShell.AddScript({
$logFile = Join-Path $sync.dir test.log
$dirName = $sync.dir
$hotFolder = New-Object IO.FileSystemWatcher $dirName -Property #{IncludeSubdirectories = $false;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $hotFolder Created -SourceIdentifier FileCreated -Action {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
# check if exit condition is met
if($sync.exit -eq 1){
Out-File -FilePath $logFile -Append -InputObject "Exit file: '$name'"
Unregister-Event FileCreated
}
else{
Out-File -FilePath $logFile -Append -InputObject "The file '$name' was $changeType at $timeStamp"
# set path to synchroniszed variable
$sync.path = $path
# click Button to trigger function call
$sync.btnNewFile.PerformClick()
}
}
})
$AsyncObject = $PowerShell.BeginInvoke()
# GUI setup
$labelHeader = New-Object System.Windows.Forms.Label
$labelHeader.Location = New-Object System.Drawing.Size(10,50)
$labelHeader.Size = New-Object System.Drawing.Size(100,23)
$labelHeader.Text = 'path to new file:'
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Size(110,50)
$label.Size = New-Object System.Drawing.Size(200,23)
$label.Text = 'no file created'
$global:fileCounter = 0
$btnCreateFile = New-Object System.Windows.Forms.Button
$btnCreateFile.Location = New-Object System.Drawing.Size(10,10)
$btnCreateFile.Size = New-Object System.Drawing.Size(100,23)
$btnCreateFile.Text = "New File"
$btnCreateFile.Add_Click({
$global:fileCounter+=1
$fileName = "$global:fileCounter.txt"
$newFile = Join-Path $dir $fileName
New-Item $newFile -ItemType file
})
$btnExit = New-Object System.Windows.Forms.Button
$btnExit.Location = New-Object System.Drawing.Size(110,10)
$btnExit.Size = New-Object System.Drawing.Size(100,23)
$btnExit.Text = "&Exit"
$btnExit.Add_Click({
$sync.Exit = 1
$btnCreateFile.PerformClick()
$Powershell.Dispose()
$form.Close()
})
# set up form
$form = New-Object System.Windows.Forms.Form
$form.Width = 400
$form.Height = 120
$form.Controls.Add($btnCreateFile)
$form.Controls.Add($btnExit)
$form.Controls.Add($labelHeader)
$form.Controls.Add($label)
$form.Controls.Add($sync.btnNewFile)
$form.ShowDialog()
I really like your solution, but the problem, like you say, is not working for PS2.0, including the service pack 1 for Win7, where I need.
My solution for the GUI update works both in PS2(win7) and in PS3(win10), it is based in Windows Presentation Framework(WPF) instead Windows Forms, because with WPF we can use Data Binding and the INotifyPropertyChanged interface. I have based my work in Trevor Jones web How-To ,with some tricks to get work.
For your question about Unregister the system I have translated, from VB to PS, a post from Mike Ober in SpiceWorks, in which his concept to Register and Unregister the system based in global variables and possible errors inspired me.
Here is my code:
Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase
Function Create-WPFWindow {
Param($Hash)
# Create a window object
$Window = New-Object System.Windows.Window
$Window.Width = '600'
$Window.Height = '300'
$Window.Title = 'WPF-CONTROL'
$window.WindowStartupLocation = [System.Windows.WindowStartupLocation]::CenterScreen
$Window.ResizeMode = [System.Windows.ResizeMode]::NoResize
# Create a Label object
$Label = New-Object System.Windows.Controls.Label
$Label.Height = 40
$Label.HorizontalContentAlignment = 'Left'
$Label.VerticalContentAlignment = 'Center'
$Label.FontSize = 15
$Label.Content = 'Actividad:'
$Hash.Label = $Label
# Create a TextBlock object
$TextBlock = New-Object System.Windows.Controls.TextBlock
$TextBlock.Height = 150
$TextBlock.FontSize = 20
$TextBlock.TextWrapping = 'Wrap'
$Hash.TextBlock = $TextBlock
# Create a Button1 object
$Button1 = New-Object System.Windows.Controls.Button
$Button1.Width = 300
$Button1.Height = 35
$Button1.HorizontalContentAlignment = 'Center'
$Button1.VerticalContentAlignment = 'Center'
$Button1.FontSize = 20
$Button1.Content = 'Iniciar'
$Hash.Button1 = $Button1
# Assemble the window
$StackPanel1 = New-Object System.Windows.Controls.StackPanel
$StackPanel1.Margin = '150,20,5,5'
$StackPanel1.Orientation = 'Horizontal'
$StackPanel1.Children.Add($Button1)
$StackPanel2 = New-Object System.Windows.Controls.StackPanel
$StackPanel2.Margin = '5,5,5,5'
$StackPanel2.Orientation = 'Vertical'
$StackPanel2.Children.Add($Label)
$StackPanel2.Children.Add($TextBlock)
$StackPanel = New-Object System.Windows.Controls.StackPanel
$StackPanel.Margin = '5,5,5,5'
$StackPanel.Children.Add($StackPanel1)
$StackPanel.Children.Add($StackPanel2)
$Window.Content = $StackPanel
# Stop the service and release the resources
$Window.Add_Closing({
$Hash.On = $false
$global:p.BeginInvoke()
$global:p.Dispose()})
$Hash.Window = $Window
}
$Hash = [hashtable]::Synchronized(#{})
# Create a WPF window and add it to a Hash table
Create-WPFWindow $Hash | Out-Null
# Create a datacontext for the TextBlock, we add it to the synchronized $Hash to update the GUI from the FileSystemWatcher Event.
$DataContext = New-Object System.Collections.ObjectModel.ObservableCollection[Object]
$Text = [string]'Pulse el botón para iniciar el sistema.'
$DataContext.Add($Text)
$Hash.TextBlock.DataContext = $DataContext
$Hash.DataContext = $DataContext
$Hash.path='C:\POWERSHELL_PROJECT\Result'
# These two vars are for my needs, you can obviate them or delete
$Hash.urlLOGIN=''
$Hash.urlLOTE=''
$Hash.fileWatcher = $null
$Hash.LOG='C:\POWERSHELL_PROJECT\Result\LOG.log'
$Hash.firstEvent = $false
$Hash.On=$false
$Hash.msg=''
# Create and set a binding on the TextBlock object
$Binding = New-Object System.Windows.Data.Binding -ArgumentList '[0]'
$Binding.Mode = [System.Windows.Data.BindingMode]::OneWay
[void][System.Windows.Data.BindingOperations]::SetBinding($Hash.TextBlock,[System.Windows.Controls.TextBlock]::TextProperty, $Binding)
# Add an event for the Button1 click to Register FileSystemWatcher and Unregister it
$Hash.Button1.Add_Click({
if ($Hash.On -eq $true){
$Hash.On = $false
$Hash.Button1.Background = 'Green'
$Hash.Button1.Content = 'Iniciar'
}else{
$Hash.On = $true
$Hash.Button1.Background = 'Red'
$Hash.Button1.Content = 'Detener'
}
$p.BeginInvoke() | Out-Null
})
# Multithreading runspaces for FileSystemWatcher
$rs_dForm = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$rs_dForm.ApartmentState = 'STA'
$rs_dForm.ThreadOptions = 'ReuseThread'
$rs_dForm.Open()
$rs_dForm.SessionStateProxy.SetVariable('Hash', $Hash)
$p = [PowerShell]::Create().AddScript({
Function global:OnFileSystemWatcherError {
FileEventListener -Path $Hash.path
}
# With simple function we can refresh the Textbox and Log
Function global:Refresh-WPF-and-LOG {
$Hash.DataContext[0] = $Hash.msg
echo $Hash.msg >> $Hash.LOG
}
Function global:FileEventListener ($Path){
if ($Hash.On){
$Hash.fileWatcher = New-Object System.IO.FileSystemWatcher
$Hash.fileWatcher.Path = $Path
$Hash.fileWatcher.Filter = '*.xml'
$Hash.fileWatcher.IncludeSubdirectories = $false
$Hash.fileWatcher.InternalBufferSize = 32768
$Hash.fileWatcher.EnableRaisingEvents=$true
Register-ObjectEvent -InputObject $Hash.fileWatcher -EventName Changed -SourceIdentifier File.Changed -Action {
$Global:t = $event
if (!$Hash.firstEvent){
try{
# For example you can:
$Hash.msg = '[' + $(Get-Date -Format u | foreach {$_ -replace ' ','-'}).ToString() + ']--' + $event.SourceEventArgs.Name
}catch{
$Hash.msg = '[' + $(Get-Date -Format u | foreach {$_ -replace ' ','-'}).ToString() + ']--' + $_.Exception.Message + ' ' + $_.Exception.ItemName
}finally{
Refresh-WPF-and-LOG
}
$Hash.firstEvent=$true
}else{
$Hash.firstEvent=$false
}
}
# With this Register we control the errors from the FileSystemWatcher system, and reinit it this case
Register-ObjectEvent -InputObject $Hash.fileWatcher -EventName Error -SourceIdentifier File.Error -Action {
$Global:t = $event
$Hash.On = $false
OnFileSystemWatcherError
}
$Hash.msg = '[' + $(Get-Date -Format u | foreach {$_ -replace ' ','-'}).ToString() + ']--' + 'Servicio INICIADO.'
}else{
if ( $Hash.fileWatcher -ne $null ){
$Hash.fileWatcher.EnableRaisingEvents=$false
Unregister-Event File.Changed
Unregister-Event File.Error
$Hash.fileWatcher.Dispose()
$Hash.fileWatcher=$null
$Hash.msg='[' + $(Get-Date -Format u | foreach {$_ -replace ' ','-'}).ToString() + ']--' + 'Sistema DETENIDO.'
}
}
Refresh-WPF-and-LOG
}
FileEventListener -Path $Hash.path
})
$p.Runspace = $rs_dForm
# Show the window
$Hash.Window.ShowDialog() | Out-Null
I hope to help.
#http://stackoverflow.com/a/24036900/175063
$user = "uuuu"
$pwd = "pppp"
$source = "http://1.1.1.1/manager/jmxproxy?get=java.lang:type=Memory&att=HeapMemoryUsage"
$destination = "D:\Work\ps\test.xml"
$wc = new-object System.Net.WebClient
$p = New-Object System.Net.WebProxy 'http://proxy:8080'
$p.UseDefaultCredentials = $true
$wc.proxy = $p
$credCache = New-Object System.Net.CredentialCache
$creds = New-Object System.Net.NetworkCredential($user, $pwd)
$credCache.Add($source, "Basic", $creds)
$wc.Credentials = $credCache
$wc.DownloadFile($source, $destination)
# max=1445462016, used=898674904
# free
foreach ($thing in Get-Content $destination) {
$max = $thing.split("max=")
$used = $thing.split("used=")
Write-Host $max
Write-Host $used
}
#$free = $max - $used
#Write-Host $free
The string the file that is downloaded is a one-liner:
OK - Attribute get 'java.lang:type=Memory' - HeapMemoryUsage= javax.management.openmbean.CompositeDataSupport(compositeType=javax.management.openmbean.CompositeType(name=java.lang.management.MemoryUsage,items=((itemName=committed,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)),(itemName=init,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)),(itemName=max,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)),(itemName=used,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)))),contents={committed=1444478976, init=1494220800, max=1445462016, used=868228272})
And all I really want from it is:
max=1445462016
used=868228272
to be:
1445462016-868228272=577233744
I would extract the values from the contents={...} portion of the string with a regular expression, replace the commas with newlines and convert the result to a hashtable. Then you just need to cast the values to integers for the calculation.
Get-Content $destination | Where-Object {
$_ -match ',contents=\{(.+?)\}'
} | ForEach-Object {
$values = $matches[1] -replace ', ', "`n" | ConvertFrom-StringData
$free = [int]$values['max'] - [int]$values['used']
'Max: {0}' -f $values['max']
'Used: {0}' -f $values['used']
'Free: {0}' -f $free
}
I think I interpreted this correctly, do you want to display this in the console or store it in a variable, I'm assuming console here:
write-host "$max-$used="($max-$used)
I have figured out my own solution... I know it may not be the best way, but seems to work..
#http://stackoverflow.com/a/24036900/175063
$user="uuuu"
$pwd="pppp"
$source="http://1.1.1.1/manager/jmxproxy?get=java.lang:type=Memory&att=HeapMemoryUsage"
$destination="D:\Work\ps\test.xml"
$wc=new-object System.Net.WebClient
$p = New-Object System.Net.WebProxy 'http://proxy:8080'
$p.UseDefaultCredentials = $true
$wc.proxy=$p
$credCache=new-object System.Net.CredentialCache
$creds=new-object System.Net.NetworkCredential($user,$pwd)
$credCache.Add($source, "Basic", $creds)
$wc.Credentials=$credCache
$wc.DownloadFile($source, $destination)
# max=1445462016, used=898674904
# free
foreach ($thing in Get-Content $destination) {
# , max=1445462016, used=696318832})
# $a = $a.substring(2,3)
# MID: https://technet.microsoft.com/en-us/library/ee176901.aspx
# LEN: https://technet.microsoft.com/en-us/library/ee176895.aspx
# Instr: https://technet.microsoft.com/en-us/library/ee176876.aspx
$len = $thing.length
$maxst = $thing.indexof("max=")
$usedst = $thing.indexof("used=")
$max=$thing.substring($maxst+4,$len-$usedst-6)
$used=$thing.substring($usedst+5,$len-$usedst-7)
$free=$max-$used
# , max=1445462016, used=696318832})
write-host $len
write-host $maxst
write-host $usedst
write-host $max
write-host $used
write-host $free
}