Search code examples
powershellevent-handlingfilesystemwatcher

Powershell - Nested loops seeming to ignore if statement


I have a feeling this may be a fairly simple one. Basically, I have an application that, during runtime execution, generates temporary xml files. These can be useful for troubleshooting purposes. However, as just stated, they are temporary. They exist for a few seconds at most in a given path, and then are deleted.

I am working on automating a script for customer use (as opposed to explaining to them how to create a quick forloop batch file). The current piece I am stuck on is below. What it is supposed to do is check a directory and waiting for the xml file to be created. It will sit and constantly check until the file exists. Once it does, it breaks out of that loop and runs a copy-item to copy the xml file to a new directory.

The part I am struggling to integrate is that I want the user to be able to end the entire script by pressing a key. In this example, it's the left ctrl key. Theoretically, they should be able to end it at any time after they've started the copy process (they get a pop-up to click OK to start it, just before the sample code I am showing).

However, I haven't been able to figure out exactly now to integrate this correctly. The "press a button to end" code block works on its own. But how do I integrate?

First, the working block:

$key = [System.Windows.Input.Key]::LeftCtrl
Write-Warning "Press the $key to end the copy process."
do {
    $isCtrl = [System.Windows.Input.Keyboard]::IsKeyDown($key)
    if ($isCtrl) {
        Write-Host "`nYou pressed $key. Stopping the copy process." -ForegroundColor Green; break
    }
    Write-Host "." -NoNewline
    Start-Sleep -Milliseconds 200
} while ($true)

As stated, the above works just fine. However, below is my attempt to integrate it. It doesn't work. Once the xml file exists in the directory (this has to happen in order to break out of the first do-until loop, though that could of course be removed for testing purposes), you see the copy actions written (verbose enabled), but when you enable the debugger in Powershell, the script is either still on the copy-item, or has looped back around to it (I am not sure)...

cls
#set key variable and write message on how to end process
$key = [System.Windows.Input.Key]::LeftCtrl
Write-Warning "Press the $key key to end the copy process.`n"

#write waiting for file generation
Write-Host "Waiting for temp files to be generated..`n"

#master loop, copying files until we tell it to stop
do {
    #search for new files, but loop doing nothing until they exist
    do {
        $files = Get-ChildItem $temppath | Where-Object {( New-TimeSpan $_.LastWriteTime).Hours -le 24 }
        Start-Sleep -Milliseconds 10
    } until ( $files -ne $null )

    #copy new files to new directory
    Copy-Item $files -Destination $destination -Recurse -Verbose -ErrorAction SilentlyContinue

    #look for kill-key
    $isCtrl = [System.Windows.Input.Keyboard]::IsKeyDown($key)
    if ($isCtrl) {
        Write-Host "`nYou pressed $key. Stopping the copy process." -ForegroundColor Green; break
    }

} while ( $true )

Any suggestions are appreciated.


Solution

  • I suggest you use a FileSystemWatcher to handle the copying of the files when a new file is created in the source directory, this way you can get rid of that inner loop.

    Add-Type -AssemblyName PresentationCore, WindowsBase
    
    # the directory where the XML files are created
    $source = 'path\to\source\directory'
    # the directory where to copy the XML files
    $destination = 'path\to\destination\folder'
    # `*.xml` filters only files with that extension,
    # this filter can be changed or removed altogether
    $watcher = [System.IO.FileSystemWatcher]::new($source, '*.xml')
    $watcher.EnableRaisingEvents = $true
    
    $evt = Register-ObjectEvent -InputObject $watcher -EventName 'Created' -Action {
        param($s, [System.IO.FileSystemEventArgs] $e)
    
        # when the `Created` event is raised,
        # this handler copies the file to the destination
        Copy-Item $e.FullPath -Destination $destination
        # and give details of the file to the console
        Write-Host "'$($e.Name)' was copied to '$destination'"
    }
    
    $key = [System.Windows.Input.Key]::LeftCtrl
    Write-Warning "Press the $key key to end the copy process.`n"
    
    #write waiting for file generation
    Write-Host "Waiting for temp files to be generated..`n"
    
    try {
        while (-not [System.Windows.Input.Keyboard]::IsKeyDown($key)) {
            Start-Sleep -Milliseconds 200
        }
    }
    finally {
        Unregister-Event -SourceIdentifier $evt.Name
    }