PowerShell script to carry out action on file generated in a specific folder (works, but 'jumping the gun' sometimes!)

I have a legacy DOS App (no negative comments, there is a long and genuine story to why we are still running this!) that can print to file in PostScript format. I am successfully using the Open Source Ghostscript tool to convert to PDF (specifically calling on the 'ps2pdf' command in the Ghostscript directory, which generates a perfect PDF file).

I've tried creating a PowerShell script to automate this, which constantly monitors a specific folder and if a PostScript (*.ps) file appears in the set directory, works the conversion magic and loads up Adobe Reader with the completed PDF.

Here's the problem; the script only works 9 out of 10 times. I think the problem is the DOS App immediately generates a 0 kb .ps file when printing to file, and then pads out the content after completing generation in memory. It typically takes about 10 seconds to do the actual PostScript content generation, and then writes to the .ps file the content. As far as I can tell, it puts no lock on the file whilst this all happens. I've used the 'FileChanged' type command in my script, so most the time it ignores the initial .ps as it is created, and only does something with it on the second write to file (the change). But sometimes it jumps the gun, and converts to PDF immediately, creating a single blank page from the 0 kb file with nothing in it.

I've tried everything I can think of (or rather, I can find googling) but have failed to reliably fix this. Any ideas?

Here is my script code at present:

$folder = 'c:\Distill'
$filter = '*.ps'
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property @{IncludeSubdirectories = $false;}
 
Register-ObjectEvent $fsw Changed -SourceIdentifier FileChanged -Action {
cd "C:\Program Files\GPLGS\"     # changes to Ghostscript folder
.\ps2pdf "C:\Distill\Process.ps" "C:\PDFs\Process.pdf"     # runs PS to PDF conversion
del "C:\Distill\Process.ps"     # deletes no longer needed PostScript file
invoke-item "C:\PDFs\Process.pdf"     # loads PDF file into default PDF reader
}

Open in new window


Any thoughts? Whilst I'm an IT Pro, I must admit I'm pretty much a novice when it comes to scripting, certainly PowerShell, so sorry in advance if I'm slow on the uptake at all :-)

Hope someone can help - many thanks in advance
LVL 2
bluemercuryAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

footechCommented:
How about just introducing a pause?
You could insert the following at line 6, pushing the rest down.
Start-Sleep -seconds 10

Open in new window

bluemercuryAuthor Commented:
Thanks for the suggestion, but that's one of the things I already tried at different points inside the curly brackets - I commented it out in the code and didn't bother to include it on the post here to avoid clutter (there are a few comment outs!).

What I (think) I found was it paused some of the activity, but the 'jumping the gun' was still about 1 in 10 times. It's so frustrating, as the other 9 times it works a charm!
footechCommented:
Did you extend the pause to something like 60 seconds to see if it's just a timing thing?

I'd put some Write-Host commands in the event action to debug so you can see it working.  The question is if the problem is with whether the event is being triggered correctly, or with some interaction with the DOS app or ps2pdf.  Depending on how the ps2pdf app works, I could see how it could allow execution of the next line before it's done working.

Getting a complete picture of how the DOS app writes its files would be helpful.  For that I would create monitors of Created, Deleted, and Changed events.
https://gallery.technet.microsoft.com/scriptcenter/Powershell-FileSystemWatche-dfd7084b shows good examples.

It might help to set the NotifyFilter to just LastWrite or Size.  If the event is being triggered repeatedly there might be issues.  Notepad for example, when you save a file has two events for LastWrite.
Learn SQL Server Core 2016

This course will introduce you to SQL Server Core 2016, as well as teach you about SSMS, data tools, installation, server configuration, using Management Studio, and writing and executing queries.

bluemercuryAuthor Commented:
Many thanks for your reply, and apologies for the delay in counter-replying.

Some days back (before posting on here) I did indeed increase the pause to 30 seconds. the behaviour it exhibited was the same as everything else - 9 times out of 10 it was about 30 seconds from printing in WordStar (the DOS App) and getting the correct output. Then about 1 out of 10, it just fired up the blank PDF straight away. It suggested to me that, when the 'jump the gun' occurs, it disregards any such parameters set.

Thanks for the link to that script - I've read that a lot of times, and used some of the code from it in my own script. I can't recall, but I think I did try implementing that script pretty straight to begin with, but definitely worth a go again to see if it gives any further insight on the PowerShell command line.

Intrigued by idea of altering the NotifyFilter. So is there away I could code in something like "Only process this code if the .ps file size is greater than 5kb in size"? This is where my skill is very novice - any ideas of the code I could try putting in?

Your help is very much appreciated. Cheers! :-)
bluemercuryAuthor Commented:
Just to show the results of BigTeddy's script. Below is the output. I count 21 attempts at printing, 2 of them doing a double change as I've experienced (attempts 5 & 7). I'm fast concluding that the DOS App I'm printing from (WordStar) is behaving somewhat erratically, occasionally running a change immediately after a create, even though there is no real data in there yet (it remains at 0kb until the proper second change occurs).

So I suspect the only way to make this work properly is to tie in an additional 'if' parameter to 'FileChanged' to make it only enact if the .ps file is above a certain size. Any thoughts on the code for this?

Still doesn't answer why the 'Start-Sleep' command doesn't work when placed at the beginning of the code just after '-Action  {' . Although, remember I know very little and don't want to to assume anything!

I'll carry on googling along these lines, but if you have an inspiration, it would be massively appreciated :-)

Many thanks :-)  

The file 'PROCESS.PS' was Created at 03/06/2016 21:00:54
The file 'PROCESS.PS' was Changed at 03/06/2016 21:01:01

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:02:40
The file 'PROCESS.PS' was Created at 03/06/2016 21:02:40
The file 'PROCESS.PS' was Changed at 03/06/2016 21:02:47

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:03:11
The file 'PROCESS.PS' was Created at 03/06/2016 21:03:11
The file 'PROCESS.PS' was Changed at 03/06/2016 21:03:18

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:13:27
The file 'PROCESS.PS' was Created at 03/06/2016 21:13:27
The file 'PROCESS.PS' was Changed at 03/06/2016 21:13:35

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:13:43
The file 'PROCESS.PS' was Created at 03/06/2016 21:13:43
The file 'PROCESS.PS' was Changed at 03/06/2016 21:13:44
The file 'PROCESS.PS' was Changed at 03/06/2016 21:13:51

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:14:55
The file 'PROCESS.PS' was Created at 03/06/2016 21:14:55
The file 'PROCESS.PS' was Changed at 03/06/2016 21:15:03

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:15:16
The file 'PROCESS.PS' was Created at 03/06/2016 21:15:16
The file 'PROCESS.PS' was Changed at 03/06/2016 21:15:17
The file 'PROCESS.PS' was Changed at 03/06/2016 21:15:24

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:15:31
The file 'PROCESS.PS' was Created at 03/06/2016 21:15:31
The file 'PROCESS.PS' was Changed at 03/06/2016 21:15:39

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:15:45
The file 'PROCESS.PS' was Created at 03/06/2016 21:15:45
The file 'PROCESS.PS' was Changed at 03/06/2016 21:15:53

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:16:08
The file 'PROCESS.PS' was Created at 03/06/2016 21:16:08
The file 'PROCESS.PS' was Changed at 03/06/2016 21:16:16

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:16:24
The file 'PROCESS.PS' was Created at 03/06/2016 21:16:24
The file 'PROCESS.PS' was Changed at 03/06/2016 21:16:32

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:16:52
The file 'PROCESS.PS' was Created at 03/06/2016 21:16:52
The file 'PROCESS.PS' was Changed at 03/06/2016 21:17:00

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:17:04
The file 'PROCESS.PS' was Created at 03/06/2016 21:17:04
The file 'PROCESS.PS' was Changed at 03/06/2016 21:17:12

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:17:41
The file 'PROCESS.PS' was Created at 03/06/2016 21:17:41
The file 'PROCESS.PS' was Changed at 03/06/2016 21:17:49

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:18:22
The file 'PROCESS.PS' was Created at 03/06/2016 21:18:22
The file 'PROCESS.PS' was Changed at 03/06/2016 21:18:30

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:18:35
The file 'PROCESS.PS' was Created at 03/06/2016 21:18:35
The file 'PROCESS.PS' was Changed at 03/06/2016 21:18:43

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:19:06
The file 'PROCESS.PS' was Created at 03/06/2016 21:19:06
The file 'PROCESS.PS' was Changed at 03/06/2016 21:19:13

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:23:23
The file 'PROCESS.PS' was Created at 03/06/2016 21:23:23
The file 'PROCESS.PS' was Changed at 03/06/2016 21:23:31

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:23:37
The file 'PROCESS.PS' was Created at 03/06/2016 21:23:37
The file 'PROCESS.PS' was Changed at 03/06/2016 21:23:44

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:49:21
The file 'PROCESS.PS' was Created at 03/06/2016 21:49:21
The file 'PROCESS.PS' was Changed at 03/06/2016 21:49:29

The file 'PROCESS.PS' was Deleted at 03/06/2016 21:49:45
The file 'PROCESS.PS' was Created at 03/06/2016 21:49:45
The file 'PROCESS.PS' was Changed at 03/06/2016 21:49:53

Open in new window

bluemercuryAuthor Commented:
I read this question poised on StackOverflow: LastWrite FileSystemWatcher Powershell: notification

In the answer, it looks like even Windows Apps like notepad can write double creates or changes to a file, even when essentially only one has occured :-(

I'm now wondering if there is a different approach to this - or indeed if a criteria of a minimum size to the file acting as a trigger might work (e.g. only process script if file is bigger than 5kb). I'm not finding any documentation on how to do that....

I tried running script with just [IO.NotifyFilters]'LastWrite' , but after 10 runs the error occured again.

Thanks again for your input, and look forward to any thoughts you might have :-)
footechCommented:
What happens when the ghostscript tries to run against a file that isn't there?  I'm wondering if in the case of a double-trigger (or some other collision of circumstances) the file is getting deleted by one event, but another event has already launched another action, and when it gets to the conversion the file is gone.

Here's a sample (untested) with more extensive feedback on what is happening inside the action.  You will be able to see whether the sleep is actually taking place (I can't imagine any reason it wouldn't).  There's also a check for size.  It might be more efficient to have the notifyfilter set to size, rather than having a check in the action.
Register-ObjectEvent $fsw Changed -SourceIdentifier FileChanged -Action {
    Write-Host "$(Get-Date) :: Change detected.  Action started"
    Write-Host "$(Get-Date) :: Sleeping for 10 seconds"
    Start-Sleep -Seconds 10
    cd "C:\Program Files\GPLGS\"     # changes to Ghostscript folder
    If ( Test-Path "C:\Distill\Process.ps" )
    {
        If ( (Get-Item "C:\Distill\Process.ps").length -gt 1000 )
        {
            Write-Host "$(Get-Date) :: Converting file to .PDF"
            .\ps2pdf "C:\Distill\Process.ps" "C:\PDFs\Process.pdf"     # runs PS to PDF conversion
            Write-Host "$(Get-Date) :: Sleeping another 5 seconds"
            Start-Sleep -Seconds 5
            Write-Host "$(Get-Date) :: Deleting source file"
            del "C:\Distill\Process.ps"     # deletes no longer needed PostScript file
            Invoke-Item "C:\PDFs\Process.pdf"     # loads PDF file into default PDF reader
        }
        Else
        {
            Write-Host "$(Get-Date) :: File too small.  Stopping action."
            break
        }
    }
    Write-Host "$(Get-Date) :: Source file not found"
    Write-Host "$(Get-Date) :: Action exiting"
}

Open in new window

bluemercuryAuthor Commented:
It's incredibly good of you to be helping me to this extent - many thanks for your reply.

In response:

What happens when the ghostscript tries to run against a file that isn't there?  I'm wondering if in the case of a double-trigger (or some other collision of circumstances) the file is getting deleted by one event, but another event has already launched another action, and when it gets to the conversion the file is gone.
From what I've seen (and apologies, I've probably not explained it very well) I don't think this is happening. When it jumps the gun, it is clear that the file is there and not deleted at that point - but it is an empty file. So it registers in Windows Explorer as a 0kb file called Process.ps. I presume this is the process that WordStar goes through when creating the Process.ps file:

1) Creates empty (0kb) Process.ps file in preparation for adding PostScript generated data to.
2) Generates the PS data in some other temporary location.
3) On completion of generating complete PS data in temp location, WordStar drops it into the Process.ps file, causing a change (I can see from WordStar this happens as soon as 'Printing' disappears within the DOS App).

What happens when it all goes wrong is point 1 occurs, WordStar still has 'Printing' in the app, but the script acknowledges an immediate change after creation of PS file, reflected in the output I got from BigTeddy's script, pasted earlier. WordStar then finishes printing, and if I've closed the blank PDF generated quickly enough, it does then correctly generate the complete PDF (otherwise, there is a lock on the PDF as it is open in Adobe Reader, and nothing happens).

Many thanks for your script code - I've implemented that and it works, but I will keep running it at random intervals all day to see if it trips up at all. It will be great if it doesn't. Can I ask, does the line:

If ( (Get-Item "C:\Distill\Process.ps").length -gt 1000 )

Open in new window

define that the Process.ps file has to be certain data size in order to run? I tried an If statement nested in an If statement like this before, but with my lacking knowledge didn't know about the '.length' parameter. If it is what I think it is, that could be the absolute answer.

A lot of people online have said the IO.FileSystemWatcher is quite inconsistent as to what it detects as a change, but if a condition of a minimum file size is set, then that (in my mind) would resolve it, and which I think is probably the code you've given me above. Most people suggested using WMI to achieve the objective was more accurate, but I looked at code for this and was immediately completely out of my depth!

Thanks so much for your help :-)
footechCommented:
I noticed a little error (just a Write-Host command in the wrong spot).
Register-ObjectEvent $fsw Changed -SourceIdentifier FileChanged -Action {
    Write-Host "$(Get-Date) :: Change detected.  Action started"
    Write-Host "$(Get-Date) :: Sleeping for 10 seconds"
    Start-Sleep -Seconds 10
    cd "C:\Program Files\GPLGS\"     # changes to Ghostscript folder
    If ( Test-Path "C:\Distill\Process.ps" )
    {
        If ( (Get-Item "C:\Distill\Process.ps").length -gt 1000 )
        {
            Write-Host "$(Get-Date) :: Converting file to .PDF"
            .\ps2pdf "C:\Distill\Process.ps" "C:\PDFs\Process.pdf"     # runs PS to PDF conversion
            Write-Host "$(Get-Date) :: Sleeping another 5 seconds"
            Start-Sleep -Seconds 5
            Write-Host "$(Get-Date) :: Deleting source file"
            del "C:\Distill\Process.ps"     # deletes no longer needed PostScript file
            Invoke-Item "C:\PDFs\Process.pdf"     # loads PDF file into default PDF reader
        }
        Else
        {
            Write-Host "$(Get-Date) :: File too small.  Stopping action."
            break
        }
    }
    Else
    {
        Write-Host "$(Get-Date) :: Source file not found"
    }
    Write-Host "$(Get-Date) :: Action exiting"
}

Open in new window


If what you say is true about .PDF and Adobe Reader, then just removing the Invoke-Item command should avoid the issue.

I'm still not 100% convinced that the issue as I described wouldn't happen with the original code.  Just for my own curiosity, what does happen when you try to run the ghostscript against a non-existent file?  In any case, what I posted should avoid that scenario.

Yes, that line does check the size of the file (in bytes).  You could change the size as you see fit.
And feel free to confirm (or refute) that the Start-Sleep is working as expected.   Once we get something working, the sleep command can probably be removed.

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
bluemercuryAuthor Commented:
Hi - many thanks for your feedback.

If what you say is true about .PDF and Adobe Reader, then just removing the Invoke-Item command should avoid the issue.
Agreed, although this is an important aspect to have running. My co-workers need a solution that brings up the PDF once done - if it relied on them going to a folder and manually opening after a certain time period, I can imagine all sorts of confusion and support calls (they are not wonderfully tech savvy). And they would have a point that it wouldn't be as efficient as it loading up as a PDF for them to physically print (or otherwise) automatically.

I'm still not 100% convinced that the issue as I described wouldn't happen with the original code.  Just for my own curiosity, what does happen when you try to run the ghostscript against a non-existent file?  In any case, what I posted should avoid that scenario.
No probs - I didn't quite interpret your request properly before, think I follow now. What I should have mentioned, in all my testings of the script, is I have commented out any deletion commands / events previously, and still the problem with the 'jumping the gun' has happened. Now I understand your request, will try this shortly to quench any curiosity!!

Yes, that line does check the size of the file (in bytes).  You could change the size as you see fit.
And feel free to confirm (or refute) that the Start-Sleep is working as expected.   Once we get something working, the sleep command can probably be removed.
That's exactly what I hoped you'd say. When the error happens, the initial PostScript file that is blank is always 0kb in size. So having something that (rounded) is looking for a 1kb file or higher is perfect. Typically, these PS files are not smaller than 5kb in size. So this is the golden bit of code I've been searching for, and it looks to be working in the PowerShell script you kindly wrote for me. I'm going to implement this into a nested 'IF' statement in my original script, and see if I can make it work. Was already dropping the sleep command, but saw the value in you putting that in for testing, as it showed the mechanics of the script running with pauses.

I will post back when I've run the test with Ghostscript, and done multiple tests with my own script with the file size condition added - it looks very hopeful, and thanks again for your invaluable input!
footechCommented:
Checking back.  How's it coming?
bluemercuryAuthor Commented:
Hi Footech.

Really sorry for lack of feedback. I found that once I implemented the various IF statements like your script, in my own, the problem still occurred. Then there were some other IT troubles at work that completely pulled my attention away, but I will be getting back to this again asap (this week for sure).

Will reply properly in due course - many thanks :-)
bluemercuryAuthor Commented:
Hi Footech.

I think I have finally worked out what's occurring here. I would like to test my theory extensively, so will go through the tedious process of 100 manual prints from WordStar later today to see if I'm right. So I'll be back on here later - it's 02:15am in the UK, so calling it a night whilst I'm winning - but wanted to engage so you know this isn't question abandoned.

Hopefully, I'll come back with a full explanation and be issuing points later - thanks :-)
bluemercuryAuthor Commented:
Hi Footech.

This is what I've concluded:

I don't know the correct terminology, but the method provided by MS that assesses whether a file has changed may be a little questionable. A few things I read online point to this.

Equally, and possibly in addition, WordStar may be doing inconsistent things with how it is producing the initial PostScript file, which it ultimately fills up with data once WordStar finishes printing. This could be why the PowerShell script randomly detects a change when I perceive there isn't one - because there actually is a small change written to the file system, but it's random.

Finally, the most important point. I realised that when WordStar generates the initial 0kb PS file, it isn't actually 0kb! It was showing up in Windows as this, but I managed to do a quick refresh and copy, and it is actually about 5kb in size. The information immediately written on creation contains I guess what you might term the header info, which is then added to when the printing finishes. So, using your incredibly helpful -gt switch, even when the error occurs, it now blocks processing below a certain size, and carries on waiting. Thanks so much for that!

I'm tidying up my script, will test it works, and then will post it on here and award points :-)

Many thanks.
footechCommented:
Sounds like you've made some good progress.  This appears to be one of those cases where behavior may not always be consistent.  Gathering enough data so you can see behavior (and any variations that can occur) is the biggest step towards a resolution.

Glad I could help.
bluemercuryAuthor Commented:
The key bit of info I needed was the line in the script:

If ( (Get-Item "C:\Distill\Process.ps").length -gt 1000 )

This is what I'd been looking for via Google, with the 1000 representing minimum number of bytes for allowing the IF statement to run. As it turned out, for my script I needed to set this to about 5000, as the files being generated weren't 0kb as first thought, but about 4.5kb!

Thank you very much footech - you have been an awesome help, and I couldn't have resolved this problem without your input. Many thanks! :-)

P.S. Still refining my actual script - will post it on here when I'm happy with the final :-)
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Powershell

From novice to tech pro — start learning today.