1

Am I missing something?

I can start the debug process with F5, but I cannot end it, and I cannot step through code or do normal debugging.

I assume this is due to the fact that the code is hanging off Register-ObjectEvent ?

(Watching a file system event....)

What is the method to run this code and keep the debugger attached to what is going on?

The code:

$folder_to_watch = 'C:\Users\demouser\Downloads\'
$file_name_filter = '*.aac'
# to archive .aac files
$destination = 'c:\temp\test\arc\'  
$DestinationDirMP3 = 'C:\data\personal\hinative-mp3'
$Watcher = New-Object IO.FileSystemWatcher $folder_to_watch, $file_name_filter -Property @{ 
    IncludeSubdirectories = $false
    NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
$VLCExe = 'C:\Program Files\VideoLAN\VLC\vlc.exe' 

$onCreated = Register-ObjectEvent $Watcher -EventName Created -SourceIdentifier FileCreated -Action {
   $path = $Event.SourceEventArgs.FullPath
   $name = $Event.SourceEventArgs.Name
   $changeType = $Event.SourceEventArgs.ChangeType
   $timeStamp = $Event.TimeGenerated
   Write-Host "The file '$name' was $changeType at $timeStamp"
   Write-Host $path

   # File Checks
    while (Test-LockedFile $path) {
      Start-Sleep -Seconds .2
    }
    # Move File
    Write-Host "moving $path to $destination"
    Move-Item $path -Destination $destination -Force -Verbose
    # build the path to the archived .aac file
    $SourceFileName = Split-Path $path -Leaf
    $DestinationAACwoQuotes = Join-Path $destination $SourceFileName
    $DestinationAAC = "`"$DestinationAACwoQuotes`""
    $MP3FileName = [System.IO.Path]::ChangeExtension($SourceFileName,".mp3")
    $DestinationMP3woQuotes = Join-Path $DestinationDirMP3 $MP3FileName
    $DestinationMP3 = "`"$DestinationMP3woQuotes`""
    $VLCArgs = "-I dummy -vvv $DestinationAAC --sout=#transcode{acodec=mp3,ab=48,channels=2,samplerate=32000}:standard{access=file,mux=ts,dst=$DestinationMP3} vlc://quit"
    Write-Host "args $VLCArgs"
    Start-Process -FilePath $VLCExe -ArgumentList $VLCArgs
    

}


function Test-LockedFile {
    param ([parameter(Mandatory=$true)][string]$Path)  
    $oFile = New-Object System.IO.FileInfo $Path
    if ((Test-Path -Path $Path) -eq $false)
    {
      return $false
    }
  
    try
    {
      $oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
        if ($oStream)
        {
          $oStream.Close()
        }
        $false
    }
    catch
    {
      # file is locked by a process.
      return $true
    }
  }
Jonesome Reinstate Monica
  • 6,618
  • 11
  • 65
  • 112

1 Answers1

2

From the official documentation of Register-ObjectEvent (notes section)

Events, event subscriptions, and the event queue exist only in the current session. If you close the current session, the event queue is discarded and the event subscription is canceled.

Everything above belong to your session, which is terminated when the process exit. Even if it was somewhere else, your .net FileSystemWatcher is part of that process and is terminated as soon your session exit.

Now, when you debug through VSCode / ISE, your session is created beforehand and does not terminate after you exit the script, which allow you to evaluate variable of the last execution. It also mean your subscriptions and event callbacks and the associated .net objects remain in memory and active at this time.

That being said, your debugger also detached at the moment your script exited. It also mean that if you were not debugging and just running the script, your session would exit immediately after the script execution and thus, your listener, events callback and everything else would immediately exit without a chance to process anything.

To keep the debugger attached, be able to debug and also to have the script working in a normal context at all, you need to somewhat keep the process alive.

The usual way to ensure that is to add a loop at the end of the script.

# At the end of the script.
while ($true) {
    Start-Sleep -Seconds 1
}

Note that events are raised to the main thread, which mean that if your script is sleeping, it won't be processed immediately. Therefore, in the example above, if 10 events were to occurs within the 1 second period, they would all get processed at the same time, when the thread stop sleeping.

Debugging note: To deal with event already registered error during debugging Register-ObjectEvent : Cannot subscribe to the specified event. A subscriber with the source identifier 'FileCreated' already exists.., you can add cleanup code in the Finally part of a Try / Catch / Finally block.

try {
    # At the end of the script... 
    while ($true) {
        Start-Sleep -Seconds 1
    }    
}
catch {}
Finally {
    # Work with CTRL + C exit too !
    Unregister-Event -SourceIdentifier FileCreated 
}

References:

Register-ObjectEvent

Sage Pourpre
  • 9,932
  • 3
  • 27
  • 39
  • Do you mean that the registered event disappears when the script exits? Where is this documented? (I thought registered events are persistent until either unregistered or the OS shuts down.) – Jonesome Reinstate Monica Mar 07 '22 at 05:03
  • @JonesomeReinstateMonica I added a link to the **Register-ObjectEvent** documentation. It states that "When the subscribed event is raised, it is added to the event queue in your session." It does not refer to anything external that would suggest that the subscription outlives its process. It lives within the session, thus the Powershell process that host it. Just like your `FileSystemWatcher` does live within the memory allocated by the current session. – Sage Pourpre Mar 07 '22 at 05:48
  • @JonesomeReinstateMonica When the process exits, everything that is managed by this process get killed of. That is not totally homologous to the script exiting, since when you debug, the process remains alive after the script execution, and so everything within the allocated memory (variables, .net objects, event subscriptions, etc...). That is why keeping the script alive is important, since without the debugger, the script exiting also mean that the process will exit and your listener will immediately stop. – Sage Pourpre Mar 07 '22 at 05:52
  • Sage, Thank you. I hear and understand everything you are saying. I believe the deeper answer is that in the MS doc linked below, the key language is: "event queue in your session" I somehow thought the event engine was deeper in the OS, and that registering an objectevent pushed the action into a magical "inside the OS" zone to be run. However , from you and this reference, I understand that no, everything stays in session. Correct? https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/register-objectevent?view=powershell-7.2#description – Jonesome Reinstate Monica Mar 08 '22 at 16:39
  • @JonesomeReinstateMonica Exact. The event queue / callback happen within the process and as such, terminate once it does. The same is true for the `FileSystemWatcher` so even if the event queue was located elsewhere, the latter would stop triggering events as soon as your process exited. – Sage Pourpre Mar 08 '22 at 20:38
  • OK, one more question: The Try loop has a 1 second sleep. Will a triggered event pre-empt the sleep? IOW Will the event action fire immediately or must it wait for the sleep to exit? – Jonesome Reinstate Monica Mar 08 '22 at 20:55
  • @JonesomeReinstateMonica The callback is done on the main thread. It will execute after the sleep period. So if you put a 1 second sleeps and 10 *.aac files are created during that time period, they will all be processed at once when the threads is not sleeping again. – Sage Pourpre Mar 08 '22 at 21:08
  • I edited my answer to include those bits ^^ in the answer itself. Also, if you look at the quote I put at the top, it is even more explicit than the one I initially commented with. – Sage Pourpre Mar 08 '22 at 21:31