0

I'm writing a Windows Service to scan a set of directories for new PDF files and convert them to TIFF with Ghostscript.NET. When I'd compiled and ran the code as a normal program it functioned perfectly, but when I used the same code as a Service the output TIFF never appears. I've set the destination directory to allow writing for Everyone, and the original PDF is being removed as it's supposed to, so it shouldn't be a permissions issue for the "Local System" user. Auditing the directory for access Failures and Successes just shows a list of Successes.

There is a function that reads the color population of the PDF to determine if it's a color document, or B&W scanned as color. That part works, so there isn't an issue accessing and reading the PDF.

I've also tried removing '-q' from the Ghostscript switches and I don't have any errors reported, and "-dDEBUG" outputs so much garbage I don't know what it's saying - but nothing is tagged as an error.

public static void ConvertPDF(string file, GSvalues gsVals)
{

    gsProc = new Ghostscript.NET.Processor.GhostscriptProcessor();
    System.Collections.Generic.List<string> switches = new System.Collections.Generic.List<string>();
    switches.Add("-empty"); // GS.NET ignores the first switch
    switches.Add("-r" + gsVals.Resolution); // dpi
    switches.Add("-dDownScaleFactor=" + gsVals.ScaleFactor); // Scale the image back down
    switches.Add("-sCompression=lzw"); // Compression
    switches.Add("-dNumRenderingThreads=" + Environment.ProcessorCount);
    switches.Add("-c \"30000000 setvmthreshold\"");
    switches.Add("-dNOGC");

    string device;
    if (_checkPdf(file, gsVals.InkColorLevels, gsVals))
    {
        gsVals.WriteLog("Color PDF");
        device = "-sDEVICE=tiffscaled24"; // 24bit Color TIFF
    }
    else
    {
        gsVals.WriteLog("Grayscale PDF");
        device = "-sDEVICE=tiffgray"; // grayscale TIFF
    }    
    switches.Add(device);

    // Strip the filename out of the full path to the file
    string filename = System.IO.Path.GetFileNameWithoutExtension(file);
    // Set the output file tag
    string oFileName = _setFileName(oPath + "\\" + filename.Trim(), GSvalues.Extension);
    string oFileTag = "-sOutputFile=" + oFileName;
    switches.Add(oFileTag);
    switches.Add(file);

    // Process the PDF file
    try
    {
        string s = string.Empty;
        foreach (string sw in switches) s += sw + ' ';
            gsVals.DebugLog("Switches:\n\t" + s);

        gsProc.StartProcessing(switches.ToArray(), new GsStdio());
        while (gsProc.IsRunning) System.Threading.Thread.Sleep(1000);
    }
    catch (Exception e)
    {
         gsVals.WriteLog("Exception caught: " + e.Message);
         Console.Read();
    }

    gsVals.DebugLog("Archiving PDF");
    try
    {
        System.IO.File.Move(file, _setFileName(gsVals.ArchiveDir + "\\" + filename, ".pdf"));
    }
    catch (Exception e)
    {
        gsVals.WriteLog("Error moving PDF: " + e.Message);
    }
}


private static string _setFileName(string path, string tifExt)
    {
        if (System.IO.File.Exists(path + tifExt)) return _setFileName(path, 1, tifExt);
        else return path + tifExt;
    }

private static string _setFileName(string path, int ctr, string tifExt)
    {
        // Test the proposed altered filename. It it exists, move to the next iteration
        if(System.IO.File.Exists(path + '(' + ctr.ToString() + ')' + tifExt)) return _setFileName(path, ++ctr, tifExt);
        else return path + '(' + ctr.ToString() + ')' + tifExt;
    }

This is a sample output of the generated switches (pulled from the output log):

Switches: -empty -r220 -dDownScaleFactor=1 -sCompression=lzw -dNumRenderingThreads=4 -c "30000000 setvmthreshold" -dNOGC -sDEVICE=tiffscaled24 -sOutputFile=\\[servername]\amb_ops_scanning$\Test.tiff \\[servername]\amb_ops_scanning$\Test.pdf 

Settings are read in an XML file and stored in a class, GSVals. The class also handles writing to the System log for output, or to a text file in the normal Program version. GSSTDIO is a class for handling GS input and output, which just redirects all the output to the same logs as GSVals. The only code changes between the Program version and the Service version is the Service handling code, and the output is changed from a text file to the system logs. Nothing about the Ghostscript processing was changed.

This is being compiled as x86 for portability, but is being run on x64. GS 9.15 is installed, both x86 and x64 versions. GS.NET is version 4.0.30319 installed via NuGet into VS 2012. ILMerge 2.13.0307 is being used to package the GS.NET dll into the exe, also for portability. None of these things changed between the normal EXE and the Windows Service versions, and as I said the normal EXE works without any issues.

Scott
  • 23
  • 4
  • Chances are it requires session 0 for rendering. Windows Services block session 0 for security purposes. – Aron Nov 17 '14 at 17:23
  • Looking into that, thanks... "ghostscript session 0" in Google now shows this page as the first hit :) – Scott Nov 17 '14 at 17:28
  • Have you ticked "Allow service to interact with desktop."? Might be worth a try. http://stackoverflow.com/q/4237225/397817 Also, "Windows Services block session 0" in Google, first result: http://stackoverflow.com/q/17364932/397817 :) – Stephen Kennedy Nov 17 '14 at 17:31
  • Interacting with Desktop didn't help. I found a MS [White Paper](http://msdn.microsoft.com/en-us/library/windows/hardware/dn653293%28v=vs.85%29.aspx) that lays out the changes in Service 0 in Vista+. Looks like all services are Session 0, and user sessions are 1+. "Because Session 0 is no longer a user session, services that are running in Session 0 do not have access to the video driver. This means that any attempt that a service makes to render graphics fails." – Scott Nov 17 '14 at 17:36
  • Are you sure your windows service has access to \\[servername]\? Can you try to set input and output file to your local drive? – HABJAN Nov 17 '14 at 19:50
  • Also, try to log Ghostscript stdio messages this way: http://pastebin.com/1F5wCu8M – HABJAN Nov 17 '14 at 20:02
  • It can open the source PDF, which is in the same directory as the output path, and it archives the PDF (removes it) without errors. So that should indicated that it has R/W access to the server share. – Scott Nov 18 '14 at 18:24
  • I'll have to tinker with redirecting the GS output from GS.NET, but I got it working by creating an additional process - see my answer. – Scott Nov 18 '14 at 18:25
  • I can't get to your image, my company's draconian web filter hates actual productivity. But I'd redirected the Ghostscript.NET output using the GsStdio class, which just prints any output to the logs. I never saw anything but "Press any key to continue" types of messages. Is your link something like "-sstdout=[filename]"? – Scott Nov 18 '14 at 18:32

1 Answers1

1

I got it working by using CreateProcessAsUser() from advapi32.dll, using code from this article.

I also had to restructure the order of the switches:

switches.Add("-c 30000000 setvmthreshold -f\"" + file + "\"")

The original source I'd used for speeding up the conversion left out the '-f' part, and the fact that the -f was the tag marking the file. I don't know why this worked in GS.NET, but with normal gswin32c.exe I got an error saying that it was an invalid file, until I set the switch this way.

Oddly, the processes this method creates are still Session 0, but it actually works. I'll keep tinkering, but for now it's working.

Scott
  • 23
  • 4
  • -f doesn't 'mark the file', it closes direct PostScript input, which is started with '-c'. If you aren't using -c, you don't need -f.... – KenS Nov 19 '14 at 08:16