Using Ghostscript in a Webapplication (PDF Thumbnails) - c#

i am using the ghostscriptsharp wrapper for c# and ghostscript. I want to generate thumbnails out of pdf-files.
There are different Methods imported form the ghostscript-c-dll "gsdll32.dll".
[DllImport("gsdll32.dll", EntryPoint = "gsapi_new_instance")]
private static extern int CreateAPIInstance(out IntPtr pinstance,
IntPtr caller_handle);
[DllImport("gsdll32.dll", EntryPoint = "gsapi_init_with_args")]
private static extern int InitAPI(IntPtr instance, int argc, IntPtr argv);
//...and so on
I am using the GhostscriptWrapper for generating the thumbnails in a webapplication (.net 2.0). This class uses the methods imported above.
protected void Page_Load(object sender, EventArgs e){
GhostscriptWrapper.GeneratePageThumb("c:\\sample.pdf", "c:\\sample.jpg", 1, 100, 100);
}
When i debug the Web-Application in Visual Studio 2008 by hitting key "F5" it works fine (a new instance of webserver is generated). When i create a WindowsForm Application it works too. The thumbnails get generated.
When i access the application with the webbrowser directly (http://localhoast/mywebappliation/..) it doesn't work. No thumbnails are generated. But there is also no exception thrown.
I placed the gsdll32.dll in the system32-folder of windows xp. The Ghostscript Runtime is installed too. I have given full access in the IIS-Webproject (.Net 2.0).
Does anybody know why i can't access Ghostscript from my webapplication? Are there any security-issues for accessing dll-files on the IIS-Server?
Greetings
Klaus

Try changing the current directory
string workingDirectory = #"C:\tmp";
Directory.SetCurrentDirectory(workingDirectory);
GhostscriptWrapper.GeneratePageThumb("c:\\sample.pdf", "c:\\sample.jpg", 1, 100, 100);

I now have a workaround. I am not accessing Ghostscript with the GhostscriptWrapper-Class. Instead i access the cmd.exe on the server directly. The following method takes a command (ghostscript syntax) and runs it in cmd.exe. I used the following method for doing this:
public static string runCommand(string workingDirectory, string command)
{
// Create the ProcessInfo object
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo("cmd.exe");
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = true;
psi.RedirectStandardError = true;
psi.WorkingDirectory = workingDirectory;
// Start the process
System.Diagnostics.Process proc = System.Diagnostics.Process.Start(psi);
// Attach the output for reading
System.IO.StreamReader sOut = proc.StandardOutput;
// Attach the in for writing
System.IO.StreamWriter sIn = proc.StandardInput;
sIn.WriteLine(command);
// strm.Close();
// Exit CMD.EXE
string stEchoFmt = "# {0} run successfully. Exiting";
// sIn.WriteLine(String.Format(stEchoFmt, targetBat));
sIn.WriteLine("EXIT");
// Close the process
proc.Close();
// Read the sOut to a string.
string results = sOut.ReadToEnd().Trim();
// Close the io Streams;
sIn.Close();
sOut.Close();
// Write out the results.
string fmtStdOut = "<font face=courier size=0>{0}</font>";
return String.Format(fmtStdOut, results.Replace(System.Environment.NewLine, "<br>"));
}

Its possible that the identity you are running the web site under does not have write permissions for c:\

Related

Why does the batch file not copy files when invoked from Windows Forms app but it works from Console app?

I have a Console app and a Winforms app that do the same. The common functionality is in a class being reused by both.
The CopyRequiredFile, starts a windows batch file which uses xcopy to copy files from a network folder to a local drive. But, when called from the Windows Forms app it doesn't copy the files.
I am a novice developer trying to develop framework and some internal tools for UI automation.
Why does it work to copy the files when I invoke the functionality from the Console application, but not from the Windows Forms Application?
My Console App:
public class Program
{
private static readonly Action<string> OutputAction = s => Console.WriteLine(s);
private static readonly IProgress<string> Progress = new Progress<string>(OutputAction);
public static void Main(string[] args)
{
HelpersCopy.CreateRequiredDirectories(Progress);
HelpersCopy.CopyRequiredFiles(Progress, true);
HelpersCopy.StartHub(Progress);
HelpersCopy.StartNode(Progress);
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
My Windows Forms app: Only code that is relevant to this question.
private void button1_Click(object sender, EventArgs e)
{
Action<string> outputAction = s => txtOutput.InvokeEx(t => t.Text += s + Environment.NewLine);
IProgress<string> progress = new Progress<string>(outputAction);
txtOutput.Clear();
HelpersCopy.CreateRequiredDirectories(progress);
HelpersCopy.CopyRequiredFiles(progress, true);
HelpersCopy.StartHub(progress);
HelpersCopy.StartNode(progress);
}
InvokeEx is an extension method to invoke the action if required. Help from stackoverflow!
Unfortunately, I cannot post images because I don't have the required points. So, please see the output images here: https://www.flickr.com/photos/61600076#N05/sets/72157649781440604/
Helpers Class Code Please let me know if this code is not required in the question.
public class HelpersCopy
{
public static void CopyRequiredFiles(IProgress<string> progress, bool hideWindow = false)
{
progress.Report(string.Format("Copying latest version of executables...{0}", Environment.NewLine));
var currentDirectory = Directory.GetCurrentDirectory();
ExecuteCommand(String.Format(#"{0}\Copy latest Selenium files.bat", currentDirectory), progress, hideWindow: hideWindow);
progress.Report(string.Format("\r\nLatest version of executables copied successfully{0}", Environment.NewLine));
}
private static void ExecuteCommand(string fileName, IProgress<string> progress, string command = null, bool hideWindow = true)
{
if (hideWindow)
{
var processInfo = new ProcessStartInfo(fileName, command)
{
CreateNoWindow = true,
UseShellExecute = false,
// *** Redirect the output ***
RedirectStandardError = true,
RedirectStandardOutput = true
};
var process = new Process { StartInfo = processInfo, EnableRaisingEvents = true };
process.ErrorDataReceived += (sender, args) => progress.Report(args.Data);
process.OutputDataReceived += (sender, args) => progress.Report(args.Data);
var started = process.Start();
progress.Report(string.Format("process started: {0}", started));
progress.Report(string.Format("process id: {0}", process.Id));
progress.Report(string.Format("process start info: {0} {1}", process.StartInfo.FileName, process.StartInfo.UserName));
process.BeginErrorReadLine();
process.BeginOutputReadLine();
process.WaitForExit();
int ExitCode = process.ExitCode;
progress.Report(string.Format("ExitCode: {0}{1}", ExitCode, Environment.NewLine));
process.Close();
}
else
{
var process = Process.Start(fileName, command);
if (process.HasExited)
{
throw new Exception(string.Format("Process exited. Exit code: {0}", process.ExitCode));
}
}
}
}
My batch file
#echo off
echo Deleting existing mappings...
net use z: /delete /yes
echo Mapping network drives...
net use z: \\company-filestore\Selenium /user:company-filestore\Automation Selen1um
z:
cd "Hub and Node Executables"
echo Copying latest Selenium jars...
xcopy "z:\Hub and Node Executables" "C:\Selenium\" /R /Y /S /Z
echo Finished copying latest Selenium jars...
echo All done
The issue in winforms app (although, there was never any issue in console app using the same code) was being caused by a bug in xcopy in that when you redirect its output you need to redirect its input as well. So, adding this line to the ProcessInfo object in my code, fixed the issue.
RedirectStandardInput = true
More information on the issue is here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/ab3c0cc7-83c2-4a86-9188-40588b7d1a52/processstart-of-xcopy-only-works-under-the-debugger?forum=netfxbcl
Hope this helps someone.
Have you tested placing the batch file in the same folder where the Windows Forms exe is available?
Have you tried running the windows forms app with Admin rights?
Just to knockout the possibility of insufficient permissions.
have you also verified the user context with which the code is executed and if the folder has permissions for the user context?

Invoking Astyle on a string or a file via C#

I am generating C++ code via C#, for some reason after applying astyle my generated code compiles. So is there a way I can invoke astyle from within my C# windows application?
Astyle is a command line tool, so using Process class you can call it externally and ask it to format the C++ source file.
I have done similar projects in the past, such as
http://alex.codeplex.com
I finally figured it out a few days ago, so thought i would share my function to astyle via c#
'
private void astyleDirectory(string target_path)
{
System.Diagnostics.Process pProcess = new System.Diagnostics.Process();
//Enter Path to get Astyle.exe here
pProcess.StartInfo.FileName=System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + #"\Astyle.exe";
pProcess.StartInfo.Arguments = "--options=none --style=ansi --recursive *.h *.cpp";
pProcess.StartInfo.UseShellExecute = false;
pProcess.StartInfo.RedirectStandardOutput = true;
pProcess.StartInfo.RedirectStandardError = true;
pProcess.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(target_path);
try
{
pProcess.Start();
string strOutput = pProcess.StandardOutput.ReadToEnd();
string strError = pProcess.StandardError.ReadToEnd();
pProcess.WaitForExit();
}
catch { }
}
'

SHGetKnownFolderPath / Environment.GetFolderPath() returning wrong value for public documents

I got a somewhat strange error when trying to resolve the CommonDocuments directory.
It keeps resolving to the wrong directory, after the CommonDocuments directory has been redirected / moved to a new location using Windows Explorer (Properties->Path from the context menu).
a minimal working piece of code would be:
namespace CommonDocumentsTest
{
class Program
{
private static readonly Guid CommonDocumentsGuid = new Guid("ED4824AF-DCE4-45A8-81E2-FC7965083634");
[Flags]
public enum KnownFolderFlag : uint
{
None = 0x0,
CREATE = 0x8000,
DONT_VERFIY = 0x4000,
DONT_UNEXPAND= 0x2000,
NO_ALIAS = 0x1000,
INIT = 0x800,
DEFAULT_PATH = 0x400,
NOT_PARENT_RELATIVE = 0x200,
SIMPLE_IDLIST = 0x100,
ALIAS_ONLY = 0x80000000
}
[DllImport("shell32.dll")]
static extern int SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out IntPtr pszPath);
static void Main(string[] args)
{
KnownFolderFlag[] flags = new KnownFolderFlag[] {
KnownFolderFlag.None,
KnownFolderFlag.ALIAS_ONLY | KnownFolderFlag.DONT_VERFIY,
KnownFolderFlag.DEFAULT_PATH | KnownFolderFlag.NOT_PARENT_RELATIVE,
};
foreach (var flag in flags)
{
Console.WriteLine(string.Format("{0}; P/Invoke==>{1}", flag, pinvokePath(flag)));
}
Console.ReadLine();
}
private static string pinvokePath(KnownFolderFlag flags)
{
IntPtr pPath;
SHGetKnownFolderPath(CommonDocumentsGuid, (uint)flags, IntPtr.Zero, out pPath); // public documents
string path = System.Runtime.InteropServices.Marshal.PtrToStringUni(pPath);
System.Runtime.InteropServices.Marshal.FreeCoTaskMem(pPath);
return path;
}
}
}
Expected behaviour:
Output is D:\TestDocuments
Actual behaviour:
Output is C:\Users\Public\Documents
None; P/Invoke==>C:\Users\Public\Documents
DONT_VERFIY, ALIAS_ONLY; P/Invoke==>
NOT_PARENT_RELATIVE, DEFAULT_PATH; P/Invoke==>C:\Users\Public\Documents
The correct value is stored in the Windows Registry (HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Common Documents), but it is not returned by SHGetKnownFolderPath (or Environment.GetFolderPath)
OS: Windows 7 Professional x64
.NET Framework v4.0.30319
Application is compiled for x86 CPU
What I tried so far:
restarting my application
restarting the computer
calling Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments);
direct calls to Win32-API SHGetKnownFolderPath
EDIT 2
Steps to reproduce:
deactivate UAC on your computer [and restart!]
go to C:\Users\Public\
right click on "Public Documents" folder and select
Properties
select the 'Path' tab
click 'Move...' and select a (new) folder on drive D: called TestDocuments
click 'Apply'
accept to move all files to the new location start the minimal
application above
tl;dr: Behaviour is by design and only appears when you're running an assembly that was compiled for x86 CPUs on a x64 OS
Longer version:
Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments) accesses the 32-bit hive of the Windows Registry.
The actual path to the folder is stored in the 64-bit hive.
The issue has been forwarded to the Windows team and may be fixed in a future version of Windows.
For a bit more information see the Microsoft connect report
Workaround
create a console application with the following code and compile it for ANY CPU
static void Main(string[] args)
{
Console.WriteLine("{0}", Environment.GetFolderPath(System.Environment.SpecialFolder.CommonDocuments));
}
then call it from your main application:
Process proc = new Process();
ProcessStartInfo info = new ProcessStartInfo("myConsoleApp.exe");
// allow output to be read
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.UseShellExecute = false;
proc.StartInfo = info;
proc.Start();
proc.WaitForExit();
string path = proc.StandardOutput.ReadToEnd();
this will launch the ANY CPU executable, which only prints out the desired path to the standard output. The output then is read in the main application and you get the real path.

Execute a command line utility in ASP.NET

I need some advice regarding the use of a command line utility from a C#/ASP.NET web application.
I found a 3rd party utility for converting files to CSV format. The utility works perfectly and it can be used from the command line.
I have been looking on the web for examples on how to execute the command line utility and found this example.
The problem is this is not very good. When I try to us the example code with my utility, I get a prompt asking me to install the utility on the client machine. This is not what I want. I do not want the user to see what is going on in the background.
Is it possible to execute the command server side and processing the file from there?
Any help would be greatly appreciated.
I've done something like this several times in the past, and here's what's worked for me:
Create an IHttpHandler implementation (easiest to do as an .ashx file) to handle a convert. Within the handler, use System.Diagnostics.Process and ProcessStartInfo to run your command line utility. You should be able to redirect the standard output to the output stream of your HTTP response. Here's some code:
public class ConvertHandler : IHttpHandler
{
#region IHttpHandler Members
bool IHttpHandler.IsReusable
{
get { return false; }
}
void IHttpHandler.ProcessRequest(HttpContext context)
{
var jobID = Guid.NewGuid();
// retrieve the posted csv file
var csvFile = context.Request.Files["csv"];
// save the file to disk so the CMD line util can access it
var filePath = Path.Combine("csv", String.Format("{0:n}.csv", jobID));
csvFile.SaveAs(filePath);
var psi = new ProcessStartInfo("mycsvutil.exe", String.Format("-file {0}", filePath))
{
WorkingDirectory = Environment.CurrentDirectory,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (var process = new Process { StartInfo = psi })
{
// delegate for writing the process output to the response output
Action<Object, DataReceivedEventArgs> dataReceived = ((sender, e) =>
{
if (e.Data != null) // sometimes a random event is received with null data, not sure why - I prefer to leave it out
{
context.Response.Write(e.Data);
context.Response.Write(Environment.NewLine);
context.Response.Flush();
}
});
process.OutputDataReceived += new DataReceivedEventHandler(dataReceived);
process.ErrorDataReceived += new DataReceivedEventHandler(dataReceived);
// use text/plain so line breaks and any other whitespace formatting is preserved
context.Response.ContentType = "text/plain";
// start the process and start reading the standard and error outputs
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
// wait for the process to exit
process.WaitForExit();
// an exit code other than 0 generally means an error
if (process.ExitCode != 0)
{
context.Response.StatusCode = 500;
}
}
}
#endregion
}
The command is running server side. Any code is running on the server. The code in the example that you give works. You just need to make sure that the utility is set up properly on the server and that you have permissions to the directory/file.

How can I programmatically run the ASP.Net Development Server using C#?

I have ASP.NET web pages for which I want to build automated tests (using WatiN & MBUnit). How do I start the ASP.Net Development Server from my code? I do not want to use IIS.
This is what I used that worked:
using System;
using System.Diagnostics;
using System.Web;
...
// settings
string PortNumber = "1162"; // arbitrary unused port #
string LocalHostUrl = string.Format("http://localhost:{0}", PortNumber);
string PhysicalPath = Environment.CurrentDirectory // the path of compiled web app
string VirtualPath = "";
string RootUrl = LocalHostUrl + VirtualPath;
// create a new process to start the ASP.NET Development Server
Process process = new Process();
/// configure the web server
process.StartInfo.FileName = HttpRuntime.ClrInstallDirectory + "WebDev.WebServer.exe";
process.StartInfo.Arguments = string.Format("/port:{0} /path:\"{1}\" /virtual:\"{2}\"", PortNumber, PhysicalPath, VirtualPath);
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
// start the web server
process.Start();
// rest of code...
From what I know, you can fire up the dev server from the command prompt with the following path/syntax:
C:\Windows\Microsoft.NET\Framework\v2.0.50727\Webdev.WebServer.exe /port:[PORT NUMBER] /path: [PATH TO ROOT]
...so I could imagine you could easily use Process.Start() to launch the particulars you need through some code.
Naturally you'll want to adjust that version number to whatever is most recent/desired for you.
Building upon #Ray Vega's useful answer, and #James McLachlan's important update for VS2010, here is my implementation to cover VS2012 and fallback to VS2010 if necessary. I also chose not to select only on Environment.Is64BitOperatingSystem because it went awry on my system. That is, I have a 64-bit system but the web server was in the 32-bit folder. My code therefore looks first for the 64-bit folder and falls back to the 32-bit one if necessary.
public void LaunchWebServer(string appWebDir)
{
var PortNumber = "1162"; // arbitrary unused port #
var LocalHostUrl = string.Format("http://localhost:{0}", PortNumber);
var VirtualPath = "/";
var exePath = FindLatestWebServer();
var process = new Process
{
StartInfo =
{
FileName = exePath,
Arguments = string.Format(
"/port:{0} /nodirlist /path:\"{1}\" /virtual:\"{2}\"",
PortNumber, appWebDir, VirtualPath),
CreateNoWindow = true,
UseShellExecute = false
}
};
process.Start();
}
private string FindLatestWebServer()
{
var exeCandidates = new List<string>
{
BuildCandidatePaths(11, true), // vs2012
BuildCandidatePaths(11, false),
BuildCandidatePaths(10, true), // vs2010
BuildCandidatePaths(10, false)
};
return exeCandidates.Where(f => File.Exists(f)).FirstOrDefault();
}
private string BuildCandidatePaths(int versionNumber, bool isX64)
{
return Path.Combine(
Environment.GetFolderPath(isX64
? Environment.SpecialFolder.CommonProgramFiles
: Environment.SpecialFolder.CommonProgramFilesX86),
string.Format(
#"microsoft shared\DevServer\{0}.0\WebDev.WebServer40.EXE",
versionNumber));
}
I am hoping that an informed reader might be able to supply the appropriate incantation for VS2013, as it apparently uses yet a different scheme...
You can easily use Process Explorer to find complete command line options needed for manually start it.
Start Process Explorer while debugging your website. For VS2012, expand 'devenv.exe' node. Right-click on 'WebDev.WebServer20.exe' and from there you can see Path and Command Line values.

Categories