PowerShell文件监视和文本转语音

时间:2018-12-10 06:29:46

标签: powershell filesystemwatcher

我是PowerShell的新手,我正在尝试帮助一位朋友编写一个脚本,该脚本将不断监视文件,并且每当文件更改时,都会大声读取文件中的新文本(不断编辑文本文件,并且所有其中的旧内容被替换为从电子邮件到达时获取的新内容)。

该脚本在将内容从文件中拉出并大声读取方面效果很好,但是我有一个小问题,它可以读取内容两次或四次,而我只需要读取一次即可。

此外,当PowerShell在讲内容时,它不会更新/排队文件中的更改,因此,如果在讲以前的更改时进行了两次更改,则第一个更改将被跳过,而仅是最近的更改大声朗读。有没有一种方法可以使它对文件的所有更改进行排队并按顺序读取它们?

我目前拥有的是

Add-Type -AssemblyName System.speech
$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
$speak.Rate = 0 # -10 is slowest, 10 is fastest

$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\Users\Dylan\Desktop\"
$watcher.Filter = "alarm.txt"
$watcher.IncludeSubdirectories = $false
$watcher.EnableRaisingEvents = $true

$AlarmLocation = "C:\Users\Dylan\Desktop\alarm.txt"

$changeAction = {
    $Alarm = (Get-Content $AlarmLocation)
    $speak.Speak($Alarm)
}

Register-ObjectEvent $watcher "Changed" -Action $changeAction
while ($true) {sleep 5}

我是否在这里遗漏了一些明显的东西,或者我必须包含其他功能?

谢谢

1 个答案:

答案 0 :(得分:0)

此处解释了多个事件的问题: https://blogs.msdn.microsoft.com/oldnewthing/20140507-00/?p=1053/

一种处理它的方法是跟踪LastWriteTime。

我们可以在其他线程中运行扬声器,因此不会阻塞观察者。这样,我们将在说话时检测文件是否更改。

像这样...

# When the file is changed,
# the content is stored in the queue,
# and the speaker is signaled.

# it breaks, if the file changes very rapidly.

# use a hashtable for all the vars
# for easier transport across scopes
$vars = [hashtable]::Synchronized(@{})
$vars.speakQueue = New-Object System.Collections.Queue
$vars.speakEvent = New-Object System.Threading.AutoResetEvent $false
$vars.speakLastWriteTime = [DateTime]::MinValue
$vars.speakRunning = $true

$vars.speakPS = [System.Management.Automation.PowerShell]::Create().AddScript({
    # this is the speaker thread
    Param (
        $vars
    )

    Add-Type -AssemblyName System.speech
    $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
    $speak.Rate = 0 # -10 is slowest, 10 is fastest

    # run until other thread sets running=false
    while($vars.speakRunning) {
        # other thread sets the event when content is available
        if($vars.speakEvent.WaitOne(100)) {
            # use System.Threading.Monitor to make queue thread safe
            [System.Threading.Monitor]::Enter($vars.SyncRoot)
            try {
                # get all alarms
                $alarm = while($vars.speakQueue.Count){ $vars.speakQueue.Dequeue() }
            }
            catch {
            }
            [System.Threading.Monitor]::Exit($vars.SyncRoot)

            # speak now
            $alarm | ForEach-Object { $speak.Speak($_) }
        }
    }
}).AddArgument($vars)

# start new thread
$vars.speakPSHandle = $vars.speakPS.BeginInvoke()

$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\Users\Dylan\Desktop\"
$watcher.Filter = "alarm.txt"
$watcher.IncludeSubdirectories = $false
$watcher.EnableRaisingEvents = $true
$watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite

$changeAction = {
    $vars = $event.MessageData

    # FullPath is the path of the changed file
    $item = Get-Item $event.SourceEventArgs.FullPath

    # only proceed, if LastWriteTime has changed
    if($item.LastWriteTime -ne $vars.speakLastWriteTime) {
        $vars.speakLastWriteTime = $item.LastWriteTime

        $alarm = Get-Content $event.SourceEventArgs.FullPath -Raw

        [System.Threading.Monitor]::Enter($vars.SyncRoot)
        try {
            # put content in queue
            $vars.speakQueue.Enqueue($alarm)
        }
        catch {
        }
        [System.Threading.Monitor]::Exit($vars.SyncRoot)

        # signal speaker in other thread
        $vars.speakEvent.Set()
    }
}

$job = Register-ObjectEvent $watcher "changed" -Action $changeAction -SourceIdentifier "FileChanged" -MessageData $vars

while($true) {
    Start-Sleep -Milliseconds 25
}

# clean-up, if ever needed...
Unregister-Event "FileChanged"
$vars.speakRunning = $false # leaves while-loop in thread
$vars.speakPS.EndInvoke($vars.speakPSHandle) # waits for thread to end