Invoke or call C# console app from C# web service?

11,601

Solution 1

The easiest method to call a .Net console application from another .Net application is to link in the assembly, and invoke the static Main method directly. But if there is any reason you can't execute commands (permissions), then you'll have the same problems with this method.

If permissions are the problem, then you could:

  • Change the account ASP.Net uses to host your web service
  • Create a Windows service that hosts a WCF or .Net Remoting listener. Design the listener to spawn the processes you need to run. Run that service with the permissions you require

Also, as John Kalberer mentioned, it could be related to the inability of these services to interact with the desktop. If this is the problem, then see this question.

Solution 2

If it's possible from within a web service, you'll need to do one of two things:

  • Use impersonation to execute the console application
  • Use an account in IIS that can execute the application.

Assuming that one of the above works and you're able to execute the console app, there are also a few other things you'll need to look into:

  • You may need to change the execute permissions under the Home Directory tab in IIS
  • You may need to suppress the console dialog, as this may raise some flags

I had to do this once before, and standard impersonation didn't work for me. I had to handle impersonation a little differently. I don't know if you'll run into the same obstacles that I did, but here is a class that I created for impersonating programmatically through the Windows API:

EDIT

Changed to an instance class implementing IDisposable - thanks @John Saunders

Added an overloaded constructor for easier implementation with using statement, and added boolean property Impersonating to check whether the instance is currently impersonating. There are also BeginImpersonationContext and EndImpersonationContext methods for alternative use.

/// <summary>
/// Leverages the Windows API (advapi32.dll) to programmatically impersonate a user.
/// </summary>
public class ImpersonationContext : IDisposable
{
    #region constants

    private const int LOGON32_LOGON_INTERACTIVE = 2;
    private const int LOGON32_PROVIDER_DEFAULT = 0;

    #endregion

    #region global variables

    private WindowsImpersonationContext impersonationContext;
    private bool impersonating;

    #endregion

    #region unmanaged code

    [DllImport("advapi32.dll")]
    private static extern int LogonUserA(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool RevertToSelf();

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    private static extern bool CloseHandle(IntPtr handle);

    #endregion

    #region constructors

    public ImpersonationContext()
    {
        impersonating = false;
    }

    /// <summary>
    /// Overloaded constructor and begins impersonating.
    /// </summary>
    public ImpersonationContext(string userName, string password, string domain)
    {
        this.BeginImpersonationContext(userName, password, domain);
    }

    #endregion

    #region impersonation methods

    /// <summary>
    /// Begins the impersonation context for the specified user.
    /// </summary>
    /// <remarks>Don't call this method if you used the overloaded constructor.</remarks>
    public void BeginImpersonationContext(string userName, string password, string domain)
    {
        //initialize token and duplicate variables
        IntPtr token = IntPtr.Zero;
        IntPtr tokenDuplicate = IntPtr.Zero;

        if (RevertToSelf())
        {
            if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) != 0)
            {
                if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                {
                    using (WindowsIdentity tempWindowsIdentity = new WindowsIdentity(tokenDuplicate))
                    {
                        //begin the impersonation context and mark impersonating true
                        impersonationContext = tempWindowsIdentity.Impersonate();
                        impersonating = true;
                    }
                }
            }
        }

        //close the handle to the account token
        if (token != IntPtr.Zero)
            CloseHandle(token);

        //close the handle to the duplicated account token
        if (tokenDuplicate != IntPtr.Zero)
            CloseHandle(tokenDuplicate);
    }

    /// <summary>
    /// Ends the current impersonation context.
    /// </summary>
    public void EndImpersonationContext()
    {
        //if the context exists undo it and dispose of the object
        if (impersonationContext != null)
        {
            //end the impersonation context and dispose of the object
            impersonationContext.Undo();
            impersonationContext.Dispose();
        }

        //mark the impersonation flag false
        impersonating = false;
    }

    #endregion

    #region properties

    /// <summary>
    /// Gets a value indicating whether the impersonation is currently active.
    /// </summary>
    public bool Impersonating
    {
        get
        {
            return impersonating;
        }
    }

    #endregion

    #region IDisposable implementation

    ~ImpersonationContext()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);               
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (impersonationContext != null)
            {
                impersonationContext.Undo();
                impersonationContext.Dispose();
            }
        }
    }

    #endregion    
}

EDIT

Lastly, here is an example of how to implement the class:

using (ImpersonationContext context = new ImpersonationContext("user", "password", "domain"))
{
    if (context.Impersonating)
    {
        Process.Start(@"/Support/SendFax/SendFax.exe");
    }
}
Share:
11,601
NandNand
Author by

NandNand

Updated on July 29, 2022

Comments

  • NandNand
    NandNand almost 2 years

    Due to my problem that I am unable to run dos command via my web service.

    C# web service running batch file or dos command?

    Since I cannot make my web service run dos command directly, I am now thinking about creating C# console app that will run my dos command, then the console app will be invoked by web service.

    Is it possible to do so?

  • NandNand
    NandNand over 12 years
    Change the account ASP.Net uses to host your web service - could you please tell me how to do it?
  • NandNand
    NandNand over 12 years
    I am interested in creating a Windows service and call it from my web service. Is it possible to do? How can I invoke windows service from web service. (sorry if i asked too much. i am very new to .NET.)
  • NandNand
    NandNand over 12 years
    Zillion thanks to you James! but i still have a silly question that i really don't know. could you please tell me how to start this console app within my web service? (i mean it can be called by xxx.start(console app) or it can be added as project reference or something?)
  • John Saunders
    John Saunders over 12 years
    Nice-looking class, but perhaps it should be an instance class and implement IDisposable?
  • James Johnson
    James Johnson over 12 years
    Process.Start will run the console application. You won't be able to see the console if that's what you're asking, because it will be running behind the scenes on the server.
  • NandNand
    NandNand over 12 years
    OK i think i found the solution, using server.mappath(app); Thanks you again! i will try asap!
  • Merlyn Morgan-Graham
    Merlyn Morgan-Graham over 12 years
    @NandNand: You can change permissions on the web site in IIS - inetmgr.exe. There are also samples for WCF and .Net remoting on MSDN. I'd read those samples and follow the instructions in there. The Windows service would be your service host, and your existing web service would be a service client.
  • Merlyn Morgan-Graham
    Merlyn Morgan-Graham over 12 years
    @NandNand: I think impersonation might solve the problem you were having on your previous question, too. After you get this solution working, you might want to try putting the console application's code directly in your web service, and add this impersonation to that code. If that works, you can get rid of the console application altogether, and just leave this functionality in your webservice.
  • James Johnson
    James Johnson over 12 years
    @John Saunders: Thanks for pointing that out. See edited answer for the restructured class. Let me know if I missed anything.
  • NandNand
    NandNand over 12 years
    I have tried impersonation by making the service run in Local System and added <identity impersonate="true"userName="myPCusername"password="password" /> under web.config. After doing this, I can see from task manager that .exe is running under Local System, but it seems like it is never been excecuted for some reason. Did i miss something?
  • James Johnson
    James Johnson over 12 years
    Impersonation through the web.congif works a little differently, I think. Besides, with this solution you won't need to do that. This class will allow you impersonate programmatically on a per-instance basis.