Powershell Workflows - powershell

I wrote a PowerShell workflow that analyses the Excel data and triggers a mail based on the values in Excel.
I have developed a workflow which triggers mail but I'm having trouble in analysing the Excel data, where I couldn’t call the Send-Mail workflow.
Note: column “E-remainder 1” and “F-remainder 2” contains the date of the remainder mail to be sent.
Workflow test {
$worksheet = InlineScript {
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
$WorkBook = $objExcel.Workbooks.Open(#filepath)
$worksheet = $workbook.Sheets.Item(#sheetname)
$currentdate = (Get-Date).ToString()
$rownumber = #contains rownumber from excel whose details has to be mailed
foreach ($i in $rownumbers) {
if ($worksheet.Range("B$i").Text -eq $currentdate) {
$output = [PSCustomObject][ordered]#{
ComputerName = $WorkSheet.Range("C$i").Text
Fromaddress = $WorkSheet.Range("D$i").Text
Toaddress = $WorkSheet.Range("E$i").Text
}
# I need to call a workflow which sends a mail
# (workflow which I have to trigger mail)
Send-Mail -To $output.Toaddress -From $output.Fromaddress -Name $output.ComputerName
} elseif ($worksheet.Range("F$i").Text -eq $currentdate) {
$output = [pscustomobject][ordered]#{
ComputerName = $WorkSheet.Range("C$i").Text
Fromaddress = $WorkSheet.Range("D$i").Text
Toaddress = $WorkSheet.Range("E$i").Text
}
# I need to call a workflow which sends a mail
# (Based on my knowledge I know that we couldn't call a workflow inside the inline script)
Send-Mail -To $output.Toaddress -From $output.Fromaddress -Name $output.ComputerName
}
} # foreach ends
} # Inline ends
} # workflow ends
Im getting the following error:
ERROR:The term "Send-Mail" is not recognized as the name of the cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

Related

How to find path for outlook so I can send emails with scripts

I have created a powershell script to send csv files automatically using Task Scheduler. I feel as if I am making a silly mistake with my pathing as nothing is sending.
I have testing the script below.
if($args.Count -lt 1)
{
Write-Host "Use: SendMail.ps1 <"C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Office\Microsoft Outlook 2010.lnk">"
Write-Host
Write-Host "<"C:\CSV">"
Write-Host
exit
}
$FullPath=$args[0]
#Get an Outlook application object
$o = New-Object -com Outlook.Application
$mail = $o.CreateItem(0)
#2 = High importance message
$mail.importance = 2
$mail.subject = "CSV File"
$mail.body = "Here is the CSV file."
#separate multiple recipients with a ";"
$mail.To = <---->
#$mail.CC = <OTHER RECIPIENT 1>;<OTHER RECIPIENT 2>
# Iterate over all files and only add the ones that have an .csv extension
$files = Get-ChildItem $FullPath
for ($i=0; $i -lt $files.Count; $i++) {
$outfileName = $files[$i].FullName
$outfileNameExtension = $files[$i].Extension
# if the extension is the one we want, add to attachments
if($outfileNameExtension -eq ".csv")
{
$mail.Attachments.Add($outfileName);
}
}
$mail.Send()
# give time to send the email
Start-Sleep 20
# quit Outlook
$o.Quit()
#end the script
exit
I believe that the following is incorrect:
Write-Host "Use: SendMail.ps1 <"C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Office\Microsoft Outlook 2010.lnk">"
But I am unsure as to what the path should be.
The path should be something like:
C:\Program Files (x86)\Microsoft Office\root\Office16\Outlook.exe
This link has a few methods of finding the path to the executable of a running process:
http://www.softwareok.com/?seite=faq-Windows-10&faq=152

Read attachments from Outlook which are from a particular sender using wildcards

I have trying to extract attachments from Outlook which are matching the wildcard of senderemailaddress attribute. As can be seen in the below code, I was trying out with two filters but to no avail.
When I use uncommented filter currently active in the code, the code doesn't throw any errors nor does it download the attachments matching the test case. However if I activate the commented filter and run it, I get the following error.
Exception calling "Restrict" with "1" argument(s): "Cannot parse condition. Error at
"like"."
At C:\Users\acer\Desktop\outlook.ps1:42 char:2
+ $filteredItems = $folder.items.Restrict($filter)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
Code:
$filepath = "C:\folder\subfolder\subsubfolder\"
function downloadFiles {
$filter = "[UnRead]=true AND [SenderEmailAddress] -match #example"
#$filter = "[UnRead]=true AND [SenderEmailAddress] -like '*#example*'"
Add-Type -Assembly "Microsoft.Office.Interop.Outlook" | Out-Null
$olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]
$outlook = New-Object -ComObject Outlook.Application
$namespace = $outlook.GetNameSpace("MAPI")
$folder = $namespace.GetDefaultFolder($olFolders::olFolderInBox)
#$folder.Items | select SenderEmailAddress
$filteredItems = $folder.Items.Restrict($filter)
foreach ($objMessage in $filteredItems) {
$intCount = $objMessage.Attachments.Count
if ($intCount -gt 0) {
for ($i=1; $i -le $intCount; $i++) {
$objMessage.Attachments.Item($i).SaveAsFile($filepath+$objMessage.Attachments.Item($i).FileName)
}
}
$objMessage.Unread = $false
}
$outlook.Close
}
downloadFiles
Edit1 : Thanks everyone for the suggestions.
I was able to do it by filtering with unread = true and pattern matching the senderemailaddress from the properties of the filtered mails.
Adding the modified code:
$filepath = "C:\folder\subfolder\subsubfolder\"
function downloadFiles {
$filter="[UnRead]=true"
$emailfilter = "*#xyz.co.in"
$subjectfilter = "test file*"
Add-Type -Assembly "Microsoft.Office.Interop.Outlook" | Out-Null
$olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]
$outlook = New-Object -ComObject Outlook.Application
$namespace = $outlook.GetNameSpace("MAPI")
$folder = $namespace.GetDefaultFolder($olFolders::olFolderInBox)
#$folder.Items | select SenderEmailAddress
$filteredItems = $folder.Items.Restrict($filter)
foreach ($objMessage in $filteredItems) {
$subject = $objMessage.Subject
$emailaddress = $objMessage.SenderEmailAddress
if(($emailaddress -like $emailfilter) -and ($subject -like $subjectfilter)){
$intCount = $objMessage.Attachments.Count
if ($intCount -gt 0) {
for ($i=1; $i -le $intCount; $i++) {
$objMessage.Attachments.Item($i).SaveAsFile($filepath+$objMessage.Attachments.Item($i).FileName)
}
}
$objMessage.Unread = $false
}
else {continue}
}
$outlook.Close
}
downloadFiles
Now the problem is scheduling this script? When I run this script using the powershell path in command prompt it's working fine. But when I schedule the same it's not completing. I could see the outlook process generated by the task scheduer in TaskManager and have to manually kill the process to terminate the same. Any ideas?
I'd use EWS. Saves having to allow programmatic access to Outlook.
Easiest way is to download from nuget. You can do this in PowerShell by first downloading nuget:
$sourceNugetExe = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
$targetNugetExe = "D:\Program Files\nuget\nuget.exe" # chaneg path to suit
Invoke-WebRequest $sourceNugetExe -OutFile $targetNugetExe
Set-Alias nuget $targetNugetExe -Scope Global -Verbose
Then download the EWS nuget package:
Set-Location D:\Temp # change to suit
nuget install 'Microsoft.Exchange.WebServices'
Now you can start using :)
# load the assembly
[void][Reflection.Assembly]::LoadFile("D:\Temp\Microsoft.Exchange.WebServices.2.2\lib\40\Microsoft.Exchange.WebServices.dll")
# set ref to exchange - may need to change the version
$s = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2)
# replace with your email address
$email = "your.email#domain.com"
# grab your own credentials
$s.UseDefaultCredentials = $true
# discover the url from your email address
$s.AutodiscoverUrl($email)
# get a handle to the inbox
$inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($s,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
#create a property set (to let us access the body & other details not available from the FindItems call)
$psPropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text
$items = $inbox.FindItems(100) # change to suit
# loop through the emails - at this point, we don't have full info - we have to Load the email
# restrict on what we do know - if the email is read and if it has attachments
$items | where { !$_.IsRead -and $_.HasAttachments } | ForEach-Object {
# load the email, so we can get to other properties, like attachments, sender, etc
$_.Load()
# does the sender match our wildcard?
if ($_.Sender -like '*lmnopqr*') {
# loop through all file attachments
$_.Attachments | Where-Object { $_ -is [Microsoft.Exchange.WebServices.Data.FileAttachment] } | ForEach-Object {
# save them (yes, Load = Save in this instance!)
$_.Load("D:\Temp\$($_.Name)")
}
}
}
See the EWS link for more info on how to interact with EWS.
Also, see my other SO post which is out of date for where to get the EWS assembly from, but does have some useful info on extra EWS methods/properties. It also has details on how to use alternative credentials, if you're not using your own (or the process runing PowerShell doesn't have an Exchange account).
The provider does not allow the use of Like in the filter for this method. From this MSDN article:
There is no way to perform a "contains" operation. For example, you
cannot use Find or Restrict to search for items that have a particular
word in the Subject field. Instead, you can use the AdvancedSearch
method, or you can loop through all of the items in the folder and use
the InStr function to perform a search within a field.

Monitor and set an alert for Invalid file names

I want to monitor the particular Folder where people are placing the excel sheets for some activities.
I want to make sure that people should take care of the below rules before placing the excel sheets.
The excel sheet should be in xls format (Excel 97-2003 Workboomk Format)
The excel file name should not contain any space
The length of the file name should not exceed 15 characters.
I want to set a email alert to me if the users placed a excel sheet without following the above 3 rules.
I need a VB or Powershell script or batch to monitor the folder. Kindly help me on this.
This sounds like two steps. Step 1, write a section of code that can be run to check files for the correct conditions. Step 2, email users the list of incorrectly named files. Step 1 is fairly simple.
Dim fso, scanFolder, files, n, fileList, fileArray
Set fso = CreateObject("Scripting.FileSystemObject")
Set scanFolder = fso.GetFolder("C:\Your\Folder\Here")
Set files = scanFolder.Files
fileList = ""
for each n in files
if len(n.name) > 15 or right(n.name, 3) <> "xls" or InStr(n.name, " ") > 0 then
fileList = fileList & n.name & "|"
end if
next
fileList = left(fileList, len(fileList) - 1)
fileArray = split(fileList, "|")
This will grab a list of all inappropriately named files.
Step 2. Unfortunately, as far as I know, there is no way for an FSO to get the owner name out of file. Best you can do is email a distribution list with the list of files named in error. I hope this helps.
Edit: this is the email code I use in my scripts.
Dim objEmail, strFileList, x
strFileList = ""
for each x in fileArray
strFileList = strFileList & x & vbNewLine
next
Set objEmail = CreateObject("CDO.Message")
with objEmail
.From = "From#email.com"
.To = ""
.CC = ""
.Subject = "List of wrong files"
.Body = strFileList
.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = 'you will need to get the code for this from IT'
.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
.Configuration.Fields.Update
end with
objEmail.Send
I have wrapped the functionality of FileSystemWatcher into a module and provided a small sample of how that can be used for your requirements.
# Module FileSystemWatcher
<#
.SYNOPSIS
Register to receive filesystemwatcher events for a file or folder.
#>
function Register-FileSystemEvent
{
[CmdLetBinding(DefaultParameterSetName="File")]
param
(
[Parameter(Mandatory=$true,Position=0,ParameterSetName="File")]
[string]$path,
[Parameter(Mandatory=$true,Position=0,ParameterSetName="Folder")]
[string]$folder,
[Parameter(Mandatory=$false,Position=1,ParameterSetName="Folder")]
[string]$filter="*.*",
[Parameter(Mandatory=$true,Position=2)]
[string]$event,
[scriptblock]$action={
# Action code here
$sourceidentifier = $Event.SourceIdentifier
$FileName = $Event.SourceEventArgs.FullPath
$EventType = $Event.SourceEventArgs.ChangeType
$EventTime = $Event.TimeGenerated
$message = ("File {0} {1} at {2:yyyyMMdd HH:mm:ss}" -f $FileName, $EventType, $EventTime)
Msg ([environment]::UserName) $message
},
$MessageData
)
if ($PSCMdLet.ParameterSetName -eq "File")
{
$folder = $path.Replace(([System.IO.Path]::GetFileName($path)), "")
$filter = [System.IO.Path]::GetFileName($path)
}
$watcher = New-Object System.IO.FileSystemWatcher -ArgumentList $folder, $filter
$uniqueId = ("{0:yyyyMMddHHmmss}" -f [DateTime]::Now)
$sourceIdentifier = ("File_{0}_{1}" -f $event, $uniqueId)
$job = Register-ObjectEvent -InputObject $watcher -EventName $event -SourceIdentifier $sourceIdentifier -Action $action -MessageData $MessageData
$Script:ActiveWatchers += #{$sourceIdentifier = $job}
}
<#
.SYNOPSIS
Unregister a FileSystem event or All
.PARAMETER All
Unregisters all events
#>
function Unregister-FileSystemEvent
{
param
(
$id,
[switch]$All
)
if ($All.IsPresent)
{
foreach ($item in $Script:ActiveWatchers.Keys)
{
Unregister-Event $item
Remove-Job $item
}
$Script:ActiveWatchers = #{}
}
else
{
Unregister-Event $id
Remove-Job $id
$Script:ActiveWatchers.Remove($id)
}
}
function Init
{
if ($Script:ActiveWatchers.Count -gt 0){Unregister-FileSystemEvent -All}
$Script:ActiveWatchers = #{}
}
Init
Export-ModuleMember -Function Register-FileSystemEvent, Unregister-FileSystemEvent `
-Variable ActiveWatchers
The basic idea with this module is the ability to specify a scriptblock to be executed once the file event is fired. The -MessageData is used to attach more data to the event once it is fired. In below sample it is used to send the smtpserver and the credentials.
Below the sample code to make use of the fileSystemWatcher module and send an email with the results every time a file is created.
Please note that the arguments to Send-MailMessage might not be exactly what is necessary for your mail server, but is what i used to send the email using office365 server.
# Demo using FileSystemWatcher to validate created files
Import-Module .\FileSystemWatcher
$smtpserver = Read-Host -Prompt "Specify your smtp server"
$mailcred = Get-Credential -Message "Specify credentials for your smtp server"
$eventid = Register-FileSystemEvent -folder C:\users\Jower\OneDrive\Documents -event Created -MessageData #{SmtpServer = $smtpserver;Credentials = $mailcred} -action {
function ValidateFile($filename)
{
$messages = #()
if ([System.IO.Path]::GetExtension($filename) -ne ".xls")
{
$messages += ("{0}: Only excel version 97 files allowed here" -f $filename)
}
if ([System.IO.Path]::GetFileName($filename).Contains(" "))
{
$messages += ("{0}: Spaces are not allowed in filename" -f $filename)
}
if ($filename.Length -gt 15)
{
$messages += ("{0}: Filenames of more than 15 characters is not allowed" -f $filename)
}
return $messages
}
$smtpserver = $Event.MessageData.SmtpServer
$credentials = $Event.MessageData.Credentials
$messages = ValidateFile $Event.SourceEventArgs.Name
if ($messages.Count -gt 0)
{
Send-MailMessage -From "filealerts#domain.com" -To "recipient#domain.com" -Subject "File validation" -SmtpServer $smtpserver -Body ([string]::Join("`n", $messages)) -port 587 -UseSsl -Credential $credentials
}
}
# To disable events again.
# Unregister-FileSystemEvent -id $eventid
# Can also use
# Unregister-FileSystemEvent -All

Attach file to Outlook with PowerShell

Currently I'm writing a GUI Powershell script to run PSR.exe so that the helpdesk can capture the user problems then email the output to the help desk with minimal input.
The problem I'm having is attaching a file that was generated using environment variables. It works if I use static file name as shown in the example, however no computer name has the same hostname and user at the same time. How do I tell to attach the file from the two variables used to create the file?
#Directory storage
$DIR = "D:\Reports"
#Max number of recent screen captures
$MAX = "100"
#Captures Screen Shots from the recording
$SC = "1"
#Turn GUI mode on or off
$STR = "0"
#Caputres the current computer name
$PCName = "$ENV:COMPUTERNAME"
#Use either the local name or domain name
#$User = "$ENV:UserDomainName"
$User = "$ENV:UserName"
#Work in progress.
$File = "D:\Reports\LabComputer-Tester01.zip"
$buttonStart_Click={
New-Item -ItemType directory -Path D:\Reports
psr.exe /start /output $DIR\$PCName-$User.zip /maxsc $MAX /sc $SC /gui $STR
}
$buttonStop_Click={
psr.exe /stop
}
$buttonEmail_Click={
#TODO: Place custom script here
$Outlook = New-Object -ComObject Outlook.Application
$Mail = $Outlook.CreateItem(0)
$Mail.To = "deaconf19#gmail.com"
$Mail.Subject = "Capture Report"
$Mail.Body = "Something in here"
$Mail.Attachments.Add($File)
$Mail.Send()
}
I changed $File = "D:\Reports\LabComputer-Tester01.zip" to $File '\$DIR\\$PCName-$User.zip' I added the single tick and another backslash to the variable
I changed $File = "D:\Reports\LabComputer-Tester01.zip" to $File '\$DIR\\$PCName-$User.zip' I added the single tick and another backslash to the variable

Cryptolocker Honeypot FileSystemWatcher

I am a novice when it comes to scripting so please bear with me. I am trying to create a script that will monitor a bait file that is added to all file shares on a server. When the script sees that the file was modified it will block access to the user that made the modification and send an email. The script seems to work ok other than the FileSystemWatcher. It will only monitor the last share. I have seen a similar post on here but was getting confused with the answer. Can someone please help me with the task of creating a FileSystemWatcher for each bait file? I would also like any input as to how I might improve upon the script in other ways. Your help is greatly appreciated.
$to = "joe#blow.com"
$File = "test.txt"
$FilePath = "C:\temp"
$md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
## SEND MAIL FUNCTION
function sendMail($s, $to) {
$smtpServer = "mail.nowhere.com"
$smtpFrom = "alert#nowhere.com"
$smtpTo = $to
$messageSubject = $s[0]
$message = New-Object System.Net.Mail.MailMessage $smtpfrom, $smtpto
$message.Subject = $messageSubject
$message.IsBodyHTML = $false
$message.Body = $s[1]
$smtp = New-Object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($message)
}
## Get a list of shares and Perform tasks on each location.
$cryptopaths = Get-WmiObject -Class win32_share -filter "Type=0 AND name like '%[^$]'" | ForEach ($_.Path) {
$cryptopath = $_.Path
## Copy the bait file to the share location
Copy $FilePath\$File $cryptopath\$File -Force
##Get files hash
Try {
$Origin = [System.BitConverter]::ToString($md5.ComputeHash([System.IO.File]::ReadAllBytes("$FilePath\$File")))
$Copy = [System.BitConverter]::ToString($md5.ComputeHash([System.IO.File]::ReadAllBytes("$CryptoPath\$File")))
}
##Error on reading hash
Catch {
echo "error reading $CryptoPath\$File"
}
## If files don't match, then Send messaged and quit
if (Compare-Object $Origin $Copy){
## files don't match
$subject = "Error logged on $CryptoPath\$File by $env:username on $env:computername"
$body = "The original file does not match the witness file. Aborting monitor script."
$email =#($subject,$body)
sendMail -s $email -to "ben22#nowhere.com"
Exit
}
## CREATE WATCHER ON DIRECTORY
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $CryptoPath
$watcher.Filter = $File
$watcher.IncludeSubdirectories = $false
$watcher.EnableRaisingEvents = $false
$watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite -bor [System.IO.NotifyFilters]::FileName
}
## Execute Watcher
while($TRUE){
$result = $watcher.WaitForChanged([System.IO.WatcherChangeTypes]::Changed `
-bor [System.IO.WatcherChangeTypes]::Renamed `
-bor [System.IO.WatcherChangeTypes]::Deleted `
-bor [System.IO.WatcherChangeTypes]::Created, 1000);
if ($result.TimedOut){
continue;
}
if ($result.Name -eq $File) {
### Make sure the files do not match
try {
$FileCheck = [System.BitConverter]::ToString($md5.ComputeHash([System.IO.File]::ReadAllBytes("$CryptoPath\$File")))
if (Compare-Object $Origin $FileCheck){
## files don't match
$body = "Witness file $FilePath\$File on $env:computername has been modified."
}
}
catch {
## file deleted
$body = "Witness file $FilePath\$File on $env:computername has been deleted"
}
finally {
## Deny owner of changed file access to shares and disconnect their open sessions. Send email alert
Get-Acl "$CryptoPath\$File" | foreach ($_.Owner) {
Get-SmbShare | Block-SmbShareAccess –AccountName $_.Owner
Close-SmbSession –ClientUserName $_.Owner
}
$subject = "EMERGENCY ON FILE SERVER -- $FilePath\$File by $env:username on $env:computername"
$email =#($subject,$body)
sendMail -s $email -to "ben22#nowhere.com"
sendMail -s $email -to "5555555555#txt.bell.ca"
Exit
}
}
}
The problem is that you create FileSystemWatcher instances in a loop (ForEach ($_.Path) {}), but you assign them to the same variable $watcher, overwriting the previous reference each time. Once outside the loop, you work with the $watcher variable, which references the last FileSystemWatcher instance you created and that's why you are receiving notifications for the last file only.
To get this working, you should use a type that allows storing multiple references -- that is, an array. Example:
$watchers = #();
...
$watcher = New-Object System.IO.FileSystemWatcher;
...
$watchers += $watcher;
Also, I would propose to use event handler/callback/delegate-based approach instead of waiting for a change using WaitForChanged(), because waiting for multiple file system watchers would call for a parallelized solution (well, ideally). Use Register-ObjectEvent to register an event handler and see this example in particular: http://gallery.technet.microsoft.com/scriptcenter/Powershell-FileSystemWatche-dfd7084b.
PowerShellPack also has a Start-FileSystemWatcher cmdlet that wraps this all up nicely, but I'm not sure about the status of PowerShellPack in general. It should be part of the Windows 7/8 Resource Kit, though.