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
Related
This is a little difficult to explain, but I will do my best. I am writing some code to import AD contacts to users' mailboxes through EWS using Powershell.
I have a Main.ps1 file that calls all the other scripts that do work in the background (for example 1 imports the AD modules) another imports O365 modules.
I have 1 script container that connect to EWS. The code looks like this:
#CONFIGURE ADMIN CREDENTIALS
$userUPN = "User#domain.com"
$AESKeyFilePath = ($pwd.ProviderPath) + "\ConnectToEWS\aeskey.txt"
$SecurePwdFilePath = ($pwd.ProviderPath) + "\ConnectToEWS\password.txt"
$AESKey = Get-Content -Path $AESKeyFilePath -Force
$securePass = Get-Content -Path $SecurePwdFilePath -Force | ConvertTo-SecureString -Key $AESKey
#create a new psCredential object with required username and password
$adminCreds = New-Object System.Management.Automation.PSCredential($userUPN, $securePass)
Try
{
[Reflection.Assembly]::LoadFile("\\MBX-Server\c$\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll") | Out-Null
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1)
$service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($userUPN,$adminCreds.GetNetworkCredential().Password)
$service.Url = new-object Uri("https://outlook.office365.com/EWS/Exchange.asmx");
return $service
}
Catch
{
Write-Output "Unable to connect to EWS. Make sure the path to the DLL or URL is correct"
}
The output of that code prints out the Service connection, but I want the information for that output stored in a variable such as $service.
Then I would pass that variable to another script that binds to the mailbox I want...
The problem I am having is $service doesn't seem to be storing that information. It only print it out once when I return it from the script above, but it doesn't append that information in the main script. When I print out $service it prints out once, but then it clears itself.
Here is my main script
CLS
#Root Path
$rootPath = $pwd.ProviderPath #$PSScriptRoot #$pwd.ProviderPath
Write-Host "Importing all necessary modules."
#******************************************************************
# PREREQUISITES
#******************************************************************
#Nuget - Needed for O365 Module to work properly
if(!(Get-Module -ListAvailable -Name NuGet))
{
#Install NuGet (Prerequisite) first
Install-PackageProvider -Name NuGet -Scope CurrentUser -Force -Confirm:$False
}
#******************************************************************
#Connect w\ Active Directory Module
& $rootPath\AD-Module\AD-module.ps1
#Load the O365 Module
& $rootPath\O365-Module\O365-module.ps1
#Clear screen after loading all the modules/sessions
CLS
#******************************************************************
# PUT CODE BELOW
#******************************************************************
#GLOBAL VARIABLES
$global:FolderName = $MailboxToConnect = $Service = $NULL
#Connect to EWS
& $rootPath\ConnectToEWS\ConnectToEWS.ps1
#Debug
$Service
#Create the Contacts Folder
& $rootPath\CreateContactsFolder\CreateContactsFolder.ps1
#Debug
$service
$ContactsFolder
#Clean up Sessions after use
if($NULL -ne (Get-PSSession))
{
Remove-PSSession *
}
[GC]::Collect()
The first time I output the $service variable, it prints fine. In the 2nd Debug output it doesn't print out anymore, and I believe that it why the script is failing when I launch "CreateContactsFolder.ps1"
Here is the content of "CreateContactsFolder.ps1"
CLS
Try
{
$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxToConnect);
$RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
$RootFolder.Load()
#Check to see if they have a contacts folder that we want
$FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
$ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where-Object {$_.DisplayName -eq $FolderName}
if($ContactsFolderSearch)
{
$ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
#If folder exists, connect to it. Clear existing Contacts, and reupload new (UPDATED) Contact Info
Write-Output "Folder alreads exists. We will remove all contacts under this folder."
# Attempt to empty the target folder up to 10 times.
$tries = 0
$max_tries = 0
while ($tries -lt 2)
{
try
{
$tries++
$ErrorActionPreference='Stop'
$ContactsFolder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true)
$tries++
}
catch
{
$ErrorActionPreference='SilentlyContinue'
$rnd = Get-Random -Minimum 1 -Maximum 10
Start-Sleep -Seconds $rnd
$tries = $tries - 1
$max_tries++
if ($max_tries -gt 100)
{
Write-Output "Error; Cannot empty the target folder; `t$EmailAddress"
}
}
}
}
else
{
#Contact Folder doesn't exist. Let's create it
try
{
Write-Output "Creating new Contacts Folder called $FolderName"
$ContactsFolder = New-Object Microsoft.Exchange.WebServices.Data.ContactsFolder($service);
$ContactsFolder.DisplayName = $FolderName
$ContactsFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
}
catch
{
Write-Output "Error; Cannot create the target folder; `t$EmailAddress"
}
}
}
Catch
{
Write-Output "Couldn't connect to the user's mailbox. Make sure the admin account you're using to connect to has App Impersonization permissions"
Write-Output "Check this link for more info: https://help.bittitan.com/hc/en-us/articles/115008098447-The-account-does-not-have-permission-to-impersonate-the-requested-user"
}
return $ContactsFolder
In the Main script, capture the returned variable from the EWS script like
$service = & $rootPath\ConnectToEWS\ConnectToEWS.ps1
Or dot-source that script into the Main script, so the variables from EWS.ps1 are local to the Main script, so you don't need to do return $service in there:
. $rootPath\ConnectToEWS\ConnectToEWS.ps1
and do the same for the CreateContactsFolder.ps1 script
OR
define the important variables in the called scripts with a global scope $global:service and $global:ContactsFolder
See About_Scopes
I need to find new files in a folder which are over 10 MB in size, and then send a mail with the name of the files.
The tricky part: Mail should be send when there is a new file arrives in the folder, so I have to always keep track and differentiate between the old file and new file.
Problem: I have written the following code and am not able to build the logic for mail. How can I identify the new file comes and trigger the mail?
$namearray = #()
$n = gci 'C:\Users\RF\local\ReuseLibrary\FamilySaveDirectory' | % {get-item $.FullName| ? { $.length -gt 10mb }}
foreach($a in $n) {
$namearray += $a.name
}
$namearray
Send-MailMessage -To *#gmail.com -From '****#*.com' -Subject "Add User for $namearray NX License" -Body "Script execute $namearray successfully.." -SmtpServer 'mail.****.de'
As already pointed out in the comments, you can use the FileSystemWatcher object to monitor your filesystem. It got a little more tricky than just using the object, because you also need to be able to get the files which are already in the folder, not only the newly created ones.
Should the script really trigger the mail every time it has a new item in the $namearray? Maybe you'll get a flood of e-mails then.
It's better to use a function instead of a script to do your task because you have more flexibility, so you can check for only newly created files, and also for all files inside your folder.
Please try the function. I couldn't test it very well, because I don't have a folder where 10 MB large files get created all the time.
Usage:
To get all files (the wildcard * in the filter is important!):
Get-Files -filter '*.txt' -folder 'C:\yourfolder' -AllFiles
To monitor only new created files:
Get-Files -filter '*.txt' -folder 'C:\yourfolder' -NewFiles
Function:
Please edit the Send-MailMessage part so that you will receive the mail on your account.
function Get-Files() {
param(
[string]$filter,
[string]$folder,
[Parameter(ParameterSetName='AllFiles')]
[switch]$AllFiles,
[Parameter(ParameterSetName='NewFiles')]
[switch]$NewFiles
)
# Preparing the Name array
[string[]]$namearray = #()
if(!$NewFiles.IsPresent) {
# Getting all files which are already inside the
# folder and more than 10 MB
$files = gci $folder | % { get-item $_.FullName |
? { $_.length -gt 10mb -and $_.Extension -like $filter} } |
% { $namearray += $_.FullName }
# Send E-Mail
$secondnamearray = $namearray | out-string
Send-MailMessage -To '*#gmail.com' -From '****#*.com' -Subject "Add User for NameArray NX License" -Body $secondnamearray -SmtpServer 'mail.****.de'
$namearray = ""
}
# Monitoring of the Files
$monitoring = New-Object System.IO.FileSystemWatcher
$monitoring.Filter = $filter
$monitoring.path = $folder
$monitoring.EnableRaisingEvents = $true
$event = Register-ObjectEvent -InputObject $monitoring -EventName Created -Action {
# Checking for filesize
$x = (get-item $eventArgs.FullPath).length / 10mb
if ($x -ge 1) {
Write-Host "New Item found: $($eventArgs.FullPath). Sending E-Mail!"
$namearray += $eventArgs.FullPath
$newnamearray = $namearray | out-string
Send-MailMessage -To '*#gmail.com' -From '****#*.com' -Subject "Add User for NameArray NX License" -Body $newnamearray -SmtpServer 'mail.****.de'
$namearray = ""
}
}
}
Inside my register event action I have an if statement that checks to see if the paths match, if they do I set the email to $smtpTo to the proper email address. But I get an error "The parameter 'to' cannot be an empty string. I know the paths are correct as they Write to console in the if statement.
$MonitorFolder = Get-Content "C:\Desktop\ScanFTPDeptClients\OutgoingPathlist.txt"
$MonitorStopFile = "monitor.die"
$smtpServer = "mail.test.org"
$smtpFrom = "SYSTEMFUNCTION#test.org"
$smtpSubject = "Completed files have arrived in FTP"
$smtpTo= ""
$SourceID = "MonitorFiles"
foreach ($path in $MonitorFolder){
$i+=1
$watcher = New-Object System.IO.FileSystemWatcher $path
#Files only. Default is files + directory
$watcher.NotifyFilter = [System.IO.NotifyFilters]'FileName,LastWrite'
#Using a thread-safe collection (in global scope so Action-block can reach it) to store the log just to be safe.
$global:newFiles = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList))
$newFileSubscription = Register-ObjectEvent $watcher Created -SourceIdentifier $i+"NewFileCreated" -Action {
Write-Host "New file named '$($Event.SourceEventArgs.Name)' arrived in $(split-path $Event.SourceEventArgs.FullPath)"
#Check the path
$deptClient= "$(split-path $Event.SourceEventArgs.FullPath)"
#set the email based on folder path
if ("$($deptClient)" -eq "\\vavm\FTP\C\O\RuthWebster"){
Write-host "$($deptClient)"
$smtpTo = "test#test.org"
}
#add files to content of body email
$global:newFiles.Add("`n[$(Get-Date -Format HH:mm:ss)]`t $($Event.SourceEventArgs.Name)has been completed and arrived in $(split-path $Event.SourceEventArgs.FullPath) ")
if($Event.SourceEventArgs.Name -eq $MonitorStopFile) {
Write-Host "Monitoring stopped"
#Stop monitoring
Unregister-Event -SubscriptionId $newFileSubscription.Id
#Dispose FileSystemWatcher
$watcher.Dispose()
}
}
}
$smtp = New-Object -TypeName "Net.Mail.SmtpClient" -ArgumentList $smtpServer
while ($watcher.EnableRaisingEvents -or $global:newFiles.Count -gt 0) {
#Sleep
Start-Sleep -Seconds 10
if($global:newFiles.Count -gt 0) {
#Convert list of strings to single string (multiline)
$smtpbody = $global:newFiles
$smtp.Send($smtpFrom, $smtpTo, $smtpSubject, $smtpBody)
#Mail sent, Empty array
$global:newFiles.Clear()
}
}
Try with $smtpTo as a global variable. Replace all $smtpTo with $global:smtpTo.
Btw. you don't need to wrap split-path in a string and subexpression. Try:
$deptClient= split-path $Event.SourceEventArgs.FullPath
#set the email based on folder path
if ($deptClient -eq "\\vavm\FTP\C\O\RuthWebster"){
Write-host $deptClient
$smtpTo = "test#test.org"
}
TL:DR actual question is at the bottom
I'm trying to troubleshoot a Powershell v1.0 script issue. The script basically downloads a file from an FTP site and puts it on a remote server via UNC and emails the success or failure of the task.
The script runs as a task with a generic ID that is a Domain Admin but is not used to log into systems so the server it runs off of does not contain a profile for it.
If I do a runas for that user and execute the script via command line it works flawlessly. However, if I try to run it as a task it runs then exits instantly. If I open a runas command prompt and run the scheduled task vi at he command line all I get back is:
SUCCESS: Attempted to run the scheduled task "Task Name".
I've tried writing variable values to a text file to see what is going on but it never writes even when I write them as the very first step of execution.
What I want to do is capture any script error messages you would normally see when trying to run the script and/or write the variable information to a text file.
Is there any way to do this? BTW I doing via calling powershell with the following arguments:
-file -ExecutionPolicy Bypass "d:\datscript\myscript.ps1"
-I've tried -command instead of -file.
-I've tried "d:\datscript\myscript.ps1 5>&1 test.txt"
-I've tried "d:\datscript\myscript.ps1 9>&1 test.txt"
-I've tried "d:\datscript\myscript.ps1 | out-file d:\datscript\test.txt"
Nothing worked. I'm sure I can fix whatever bug I have but I'm banging my head against the wall trying to get some kind of failure info.
--Update: Here is a copy of the script minus details--
#-------------------------------------------------------------------------------------------------------------------------------------------------------------
#
#Variable Declaration
#
#$path = Path on local server to downlaod DAT to
#$olddat = Old/last DAT downloaded
#$currentdat = Next DAT number
#$ftpsite = McAfee FTP site. Update if path changes
#$ftpuser = FTP user (anon login)
#$ftppass = FTP password (anon login)
#$tempstring = Manipulation variable
#$gotdat = Boolean if updated DAT exists
#$success = Status if a new DAT exists and has been downloaded (used for email notification).
#$thetime = Variable use dto hold time of day manipulation.
$path = "\\myservername\ftproot\pub\mcafee\datfiles\"
$olddat = ""
$currentdat =""
$ftpsite = "ftp://ftp.nai.com/virusdefs/4.x/"
$ftpuser = "something"
$ftppass = "anything"
$tempstring =""
$gotdat = "False"
$success = ""
$thetime = ""
#
#Normalized functions handles UNC paths
#
function Get-NormalizedFileSystemPath
{
<#
.Synopsis
Normalizes file system paths.
.DESCRIPTION
Normalizes file system paths. This is similar to what the Resolve-Path cmdlet does, except Get-NormalizedFileSystemPath also properly handles UNC paths and converts 8.3 short names to long paths.
.PARAMETER Path
The path or paths to be normalized.
.PARAMETER IncludeProviderPrefix
If this switch is passed, normalized paths will be prefixed with 'FileSystem::'. This allows them to be reliably passed to cmdlets such as Get-Content, Get-Item, etc, regardless of Powershell's current location.
.EXAMPLE
Get-NormalizedFileSystemPath -Path '\\server\share\.\SomeFolder\..\SomeOtherFolder\File.txt'
Returns '\\server\share\SomeOtherFolder\File.txt'
.EXAMPLE
'\\server\c$\.\SomeFolder\..\PROGRA~1' | Get-NormalizedFileSystemPath -IncludeProviderPrefix
Assuming you can access the c$ share on \\server, and PROGRA~1 is the short name for "Program Files" (which is common), returns:
'FileSystem::\\server\c$\Program Files'
.INPUTS
String
.OUTPUTS
String
.NOTES
Paths passed to this command cannot contain wildcards; these will be treated as invalid characters by the .NET Framework classes which do the work of validating and normalizing the path.
.LINK
Resolve-Path
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('PSPath', 'FullName')]
[string[]]
$Path,
[switch]
$IncludeProviderPrefix
)
process
{
foreach ($_path in $Path)
{
$_resolved = $_path
if ($_resolved -match '^([^:]+)::')
{
$providerName = $matches[1]
if ($providerName -ne 'FileSystem')
{
Write-Error "Only FileSystem paths may be passed to Get-NormalizedFileSystemPath. Value '$_path' is for provider '$providerName'."
continue
}
$_resolved = $_resolved.Substring($matches[0].Length)
}
if (-not [System.IO.Path]::IsPathRooted($_resolved))
{
$_resolved = Join-Path -Path $PSCmdlet.SessionState.Path.CurrentFileSystemLocation -ChildPath $_resolved
}
try
{
$dirInfo = New-Object System.IO.DirectoryInfo($_resolved)
}
catch
{
$exception = $_.Exception
while ($null -ne $exception.InnerException)
{
$exception = $exception.InnerException
}
Write-Error "Value '$_path' could not be parsed as a FileSystem path: $($exception.Message)"
continue
}
$_resolved = $dirInfo.FullName
if ($IncludeProviderPrefix)
{
$_resolved = "FileSystem::$_resolved"
}
Write-Output $_resolved
}
} # process
} # function Get-NormalizedFileSystemPath
#
#Get the number of the exisiting DAT file and increment for next DAT if the DAT's age is older than today.
# Otherwise, exit the program if DATs age is today.
#
$tempstring = "xdat.exe"
$env:Path = $env:Path + ";d:\datscript"
$path2 ="d:\datscript\debug.txt"
add-content $path2 $path
add-content $path2 $olddat
add-content $path2 $currentdat
add-content $path2 $success
add-content $path2 " "
$path = Get-NormalizedFileSystemPath -Path $path
Set-Location -Path $path
$olddat = dir $path | %{$_.Name.substring(0, 4) }
$olddatfull = "$olddat" + "$tempstring"
if ( ((get-date) - (ls $olddatfull).LastWriteTime).day -lt 1)
{
#***** Commented out for testing!
# exit
}
$currentdat = [INT] $olddat
$currentdat++
$currentdat = "$currentdat" + "$tempstring"
add-content $path2 $olddat
add-content $path2 $currentdat
add-content $path2 $success
add-content $path2 " "
#
#Connect to FTP site and get a current directory listing.
#
[System.Net.FtpWebRequest]$ftp = [System.Net.WebRequest]::Create($ftpsite)
$ftp.Method = [System.Net.WebRequestMethods+FTP]::ListDirectoryDetails
$response = $ftp.getresponse()
$stream = $response.getresponsestream()
$buffer = new-object System.Byte[] 1024
$encoding = new-object System.Text.AsciiEncoding
$outputBuffer = ""
$foundMore = $false
#
# Read all the data available from the ftp directory stream, writing it to the
# output buffer when done. After that the buffer is searched to see if it cotains the expected
# lastest DAT.
#
do
{
## Allow data to buffer for a bit
start-sleep -m 1000
## Read what data is available
$foundmore = $false
$stream.ReadTimeout = 1000
do
{
try
{
$read = $stream.Read($buffer, 0, 1024)
if($read -gt 0)
{
$foundmore = $true
$outputBuffer += ($encoding.GetString($buffer, 0, $read))
}
} catch { $foundMore = $false; $read = 0 }
} while($read -gt 0)
} while($foundmore)
$gotdat = $outputbuffer.Contains($currentdat)
$target = $path + $currentdat
#
# Downloads DATs and cleans up old DAT file. Returns status of the operation.
# Return 1 = success
# Return 2 = Latest DAT not found and 4pm or later
# Return 3 = DAT available but did not download or is 0 bytes
# Return 4 = LatesT DAT not found and before 4pm
#
$success = 0
if ($gotdat -eq "True")
{
$ftpfile = $ftpsite + $ftppath + $currentdat
write-host $ftpfile
write-host $target
$ftpclient = New-Object system.Net.WebClient
$uri = New-Object System.Uri($ftpfile)
$ftpclient.DownloadFile($uri, $target)
Start-Sleep -s 30
if ( ((get-date) - (ls $target).LastWriteTime).days -ge 1)
{
$success = 3
}
else
{
$testlength = (get-item $target).length
if( (get-item $target).length -gt 0)
{
Remove-Item "$olddatfull"
$success = 1
}
else
{
$success = 3
}
}
}
else
{
$thetime = Get-Date
$thetime = $thetime.Hour
if ($thetime -ge 16)
{
$success = 2
}
else
{
$success = 4
exit
}
}
#
# If successful download (success = 1) run push bat
#
if ($success -eq 1)
{
Start-Process "cmd.exe" "/c c:\scripts\mcafeepush.bat"
}
#Email structure
#
#Sends result email based on previous determination
#
#SMTP server name
$smtpServer = "emailserver.domain.com"
#Creating a Mail object
$msg = new-object Net.Mail.MailMessage
#Creating SMTP server object
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$msg.From = "email1#domain.com"
$msg.ReplyTo = "email2#domain.com"
$msg.To.Add("email2#domain.com")
switch ($success)
{
1 {
$msg.subject = "McAfee Dats $currentdat successful"
$msg.body = ("DAT download completed successfully. Automaton v1.0")
}
2 {
$msg.subject = "McAfee DATs Error"
$msg.body = ("Looking for DAT $currentdat on the FTP site but I coud not find it. Human intervention may be required. Automaton v1.0")
}
3 {
$msg.subject = "McAfee DATs Error"
$msg.body = ("$currentdat is available for download but download has failed. Human intervention will be required. Automaton v1.0")
}
default {
$msg.subject = "DAT Automaton Error"
$msg.body = ("Something broke with the McAfee automation script. Human intervention will be required. Automaton v1.0")
}
}
#Sending email
$smtp.Send($msg)
#Needed to keep the program from exiting too fast.
Start-Sleep -s 30
#debugging stuff
add-content $path2 $olddat
add-content $path2 $currentdat
add-content $path2 $success
add-content $path2 " "
Apparently you have an error in starting Powershell, either because execution policy is different on the Powershell version you start, or on the account, or there is an access error on the scheduled task. To gather actual error, you can launch a task like so:
cmd /c "powershell.exe -file d:\datscript\myscript.ps1 test.txt 2>&1" >c:\windows\temp\test.log 2&>1
This way if there would be an error on starting Powershell, it will be logged in the c:\windows\temp\test.log file. If the issue is in execution policy, you can create and run (once) a task with the following:
powershell -command "Get-ExecutionPolicy -List | out-file c:/windows/temp/policy.txt; Set-ExecutionPolicy RemoteSigned -Scope LocalMachine -Force"
Running a task under the account you plan to run your main task will first get the policies in effect (so that if setting machine-level policy won't help, you'll know what scope to alter) and set machine-level policy to "RemoteSigned", the least restrictive level beyond allowing every script (highly not recommended, there are encoder scripts written on Powershell that can ruin your data).
Hope this helps.
UPDATE: If that's not policy, there might be some errors in properly writing the parameters for the task. You can do this: Create a .bat file with the string that launches your script and redirects output to say test1.txt, then change the scheduled task to cmd.exe -c launcher.bat >test2.txt, properly specifying the home folder. Run the task and review both files, at least one of them should contain an error that prevents your script from launching.
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.