I am finding it difficult to make the following code (which uses the Pastel Accounting SDK) go any faster. Currently it takes a few hours to update :(
Some background information:
The code selects records from a local database, then updates 3 separate databases all on the same server. When updating the 3 other databases, it does them in single updates.
After updating the records in all 3 database, it them updates the local database with a fresh copy of the data.
It's basically updating prices, and saving the old price.
I tried implementing Multi-threading, but could not get it to work :(
Here is my code:
public string ExportPrices(bool _restorePrices = false)
{
System.IO.File.AppendAllText(System.Web.HttpContext.Current.Server.MapPath("~/Logs/PriceUpdateErrorList.txt"), "Error Messages" + Environment.NewLine);
var db = new UniStockContext();
//fetch all databases to export to
IList<AccDatabase> accDBCol = db.AccDatabases.Where(x => (x.Active == true) && (x.Deleted == false)).ToList();
//fetch all inventory list
IList<Domain.Tables.Inventory> inventoryDBCol = db.Inventories.Where(x => (x.Active == true) && (x.Deleted == false)).ToList();
if (inventoryDBCol.Count > 0)
{
//string xmlResult = "<InventoryExportResponse>";
//loop through databases and export
foreach (AccDatabase accDB in accDBCol)
{
//check database type and call appropriate accounting system method
if ((accDB.Type == AccDatabaseType.SageEvolution) && string.IsNullOrEmpty(accDB.EvolutionCommon))
{
//////////////////////////////////////////
//////////////Sage Evolution//////////////
//////////////////////////////////////////
foreach (AccDatabase accDB2 in accDBCol)
{
if ((accDB2.Type == AccDatabaseType.SageEvolution) && !string.IsNullOrEmpty(accDB2.EvolutionCommon))
{
//pass db and common db to export method for Sage Evolution
try
{
if (!string.IsNullOrEmpty(accDB.DBName))
{
inventoryDBCol = _sageInventory.ExportPrices(accDB, accDB2, inventoryDBCol, restorPrices: false);//this call updates 13000 records
}
}
catch (Exception exExportPrices)
{
System.IO.File.AppendAllText(System.Web.HttpContext.Current.Server.MapPath("~/Logs/_sageInventoryExportPricesCallError.txt"), exExportPrices.ToString());
}
break;
}
}
}
}
//end foreach
//return (xmlResult + "</InventoryExportResponse>");
}
UpdatePricesFromInventoryListBulk(_inventoryCollection);//this call will update 40000 records in local DB.
return "Stock prices synched to accounting system!";
}
public void UpdatePricesFromInventoryListBulk(IList<Domain.Tables.Inventory> invList)
{
var db = new UniStockContext();
db.Configuration.AutoDetectChangesEnabled = false;
foreach (var inventory in invList)
{
Domain.Tables.Inventory _inventory = db.Inventories
.Single(x => x.InventoryID == inventory.InventoryID);
if (inventory.Cost.HasValue)
_inventory.Cost = inventory.Cost.Value;
else
_inventory.Cost = 0;
foreach (var inventoryPrices in inventory.AccInventoryPrices)
{
foreach (var _inventoryPrices in _inventory.AccInventoryPrices)
{
if (_inventoryPrices.AccInventoryPriceID == inventoryPrices.AccInventoryPriceID)
{
_inventoryPrices.ApplyDiscount = inventoryPrices.ApplyDiscount;
_inventoryPrices.ApplyMarkup = inventoryPrices.ApplyMarkup;
if (inventoryPrices.Price.HasValue)
_inventoryPrices.Price = inventoryPrices.Price.Value;
else
_inventoryPrices.Price = _inventory.Cost;
if (inventoryPrices.OldPrice.HasValue)
{
_inventoryPrices.OldPrice = inventoryPrices.OldPrice;
}
}
}
}
db.Inventories.Attach(_inventory);
db.Entry(_inventory).State = System.Data.Entity.EntityState.Modified;
}
db.SaveChanges();
db.Dispose();
}
I've edited my code to add multi-threading. But the process does not seem to run at all.
public string ExportPrices(bool _restorePrices = false)
{
System.IO.File.AppendAllText(System.Web.HttpContext.Current.Server.MapPath("~/Logs/PriceUpdateErrorList.txt"), "Error Messages" + Environment.NewLine);
var db = new UniStockContext();
//fetch all databases to export to
IList<AccDatabase> accDBCol = db.AccDatabases.Where(x => (x.Active == true) && (x.Deleted == false)).ToList();
//fetch all inventory list
IList<Domain.Tables.Inventory> inventoryDBCol = db.Inventories.Where(x => (x.Active == true) && (x.Deleted == false)).ToList();
if (inventoryDBCol.Count > 0)
{
//string xmlResult = "<InventoryExportResponse>";
//loop through databases and export
foreach (AccDatabase accDB in accDBCol)
{
//check database type and call appropriate accounting system method
if ((accDB.Type == AccDatabaseType.SageEvolution) && string.IsNullOrEmpty(accDB.EvolutionCommon))
{
//////////////////////////////////////////
//////////////Sage Evolution//////////////
//////////////////////////////////////////
foreach (AccDatabase accDB2 in accDBCol)
{
if ((accDB2.Type == AccDatabaseType.SageEvolution) && !string.IsNullOrEmpty(accDB2.EvolutionCommon))
{
//pass db and common db to export method for Sage Evolution
try
{
if (!string.IsNullOrEmpty(accDB.DBName))
{
Thread thread = new Thread(()
=> inventoryDBCol = _sageInventory.ExportPrices(accDB, accDB2, inventoryDBCol, restorPrices: false)
);
thread.Start();
//inventoryDBCol = _sageInventory.ExportPrices(accDB, accDB2, inventoryDBCol, restorPrices: false);//this call updates 13000 records
}
}
catch (Exception exExportPrices)
{
System.IO.File.AppendAllText(System.Web.HttpContext.Current.Server.MapPath("~/Logs/_sageInventoryExportPricesCallError.txt"), exExportPrices.ToString());
}
break;
}
}
}
}
//end foreach
//return (xmlResult + "</InventoryExportResponse>");
}
UpdatePricesFromInventoryListBulk(inventoryDBCol);//this call will update 40000 records in local DB.
return "Stock prices synched to accounting system!";
}
The code, as written with Thread.Start(...) does likely work, but Thread.Start just starts the process - if it was taking hours before, it's probably going to take hours now. Adding a thread to the mix won't suddenly make the databases complete the work faster.
It's likely the the issue is in the .ExportPrices(...) function, but you say you can't profile it, so we'll never know. If you can't use any tools to find out where the program runs slowly, then we can't know where the program runs slowly.
To me, anything that wraps a request that handles thousands of rows between multiple databases in a single function is not thinking well about the work going on. The operations that are taking place are key, and because they're not included in the question, we can't possibly know if they're the issue or not.
You need to carefully consider the database operations you're wrapping. If that's where the performance issue is, then it's likely you need to rework what's in that algorithm, not what's here in this short, simple function.
If the database operations aren't hitting any one database more than once, then it's possible you could also use Thread.Start to run those operations and use Thread.Join to wait on their completion, but this will only allow more than one to run at once, not reduce the time it takes to complete one.
What I want to happen: Pass the method a domain as a string and have it return true if the domain resolves. False if it does not. The underlying goal is to see if a domain exists.
What happens: Most valid domain strings return true. Some, however, return false despite resolving with nslookup.
I don't understand why certain domains are failing to resolve when they look fine when using command prompt nslookup and nslookup sites. (I've used https://centralops.net/ , http://www.kloth.net/services/nslookup.php , and http://network-tools.com/nslook/)
Method (C#):
//no blank domains
public bool CheckDomain(string sDomain)
{
if (string.IsNullOrWhiteSpace(sDomain)) return false;
for (int i = 1; i <= mQueryRetry; i++)
{
try
{
System.Net.IPAddress dnsCli = System.Net.IPAddress.Parse("8.8.8.8")
DnsClient myClient = new DnsClient(dnsCli);
DnsMessage dnsMessage = myClient.Resolve(ARSoft.Tools.Net.DomainName.Parse(sDomain), RecordType.A);
if ((dnsMessage == null) || ((dnsMessage.ReturnCode != ReturnCode.NoError) && (dnsMessage.ReturnCode != ReturnCode.NxDomain)))
{
throw new Exception("DNS request failed");
}
else
{
foreach (DnsRecordBase dnsRecord in dnsMessage.AnswerRecords)
{
ARecord aRecord = dnsRecord as ARecord;
if (aRecord != null)
{
return true;
}
}
}
}
catch (Exception ex)
{
// Back off and try again after 1 seconds
if (i != mQueryRetry)
{
System.Threading.Thread.Sleep(1000);
}
else
{
System.Diagnostics.Trace.WriteLine(string.Format("Domain: {0} error: {1}", sDomain, ex.Message));
}
}
}
System.Diagnostics.Trace.Flush();
return false;
}
If you plan to test it, I suggest replacing the dnsCli IPAddress with one of these. I've left the IP for the google DNS server in there as an example but I am using my company's DNS server in my production code. I've found that changing the DNS server in no way impacts the method's behavior.
I am using the latest version of Arsoft.Tools.Net (2.2.8) for the DnsClient, DnsMessage, DomainName, DnsRecordBase, and ARecord, classes/objects. We had the same problem with an older version (1.8.1).
Some of the domains that are failing to resolve are:
appraisallinks-amc.com
resurgenstech.com
orpheusvr.com
trovvit.com
Additional info: I've tried some ludicrously long query timeout limits, upwards of 5 minutes, and they made no difference. Therefore, I am certain that this is not a timeout issue.
"you can try and use a different library, the DnsClient (nuget) instead, see dnsclient.michaco.net. The domain names in question seem to work just fine " -
#MichaC
The problem was in fact the Arsoft.Tools.Net library I was using. Switching to DnsClient.Net fixed the problem.
I've written a Windows Service that exposes a WCF service to a GUI installed on the same machine. When I run the GUI, if I can't connect to the service, I need to know if it's because the service app hasn't been installed yet, or if it's because the service is not running. If the former, I'll want to install it (as described here); if the latter, I'll want to start it up.
Question is: how do you detect if the service is installed, and then having detected that it's installed, how do you start it up?
Use:
// add a reference to System.ServiceProcess.dll
using System.ServiceProcess;
// ...
ServiceController ctl = ServiceController.GetServices()
.FirstOrDefault(s => s.ServiceName == "myservice");
if(ctl==null)
Console.WriteLine("Not installed");
else
Console.WriteLine(ctl.Status);
You could use the following as well..
using System.ServiceProcess;
...
var serviceExists = ServiceController.GetServices().Any(s => s.ServiceName == serviceName);
Actually looping like this:
foreach (ServiceController SC in ServiceController.GetServices())
may throw Access Denied exception if the account under which your application is running doesn't have rights to view service properties. On the other hand, you can safely do this even if no service with such name exist:
ServiceController SC = new ServiceController("AnyServiceName");
But accessing its properties if service doesn't exist will result in InvalidOperationException. So here's a safe way to check if a service is installed:
ServiceController SC = new ServiceController("MyServiceName");
bool ServiceIsInstalled = false;
try
{
// actually we need to try access ANY of service properties
// at least once to trigger an exception
// not neccessarily its name
string ServiceName = SC.DisplayName;
ServiceIsInstalled = true;
}
catch (InvalidOperationException) { }
finally
{
SC.Close();
}
For non-linq, you can just iterate thru the array like this:
using System.ServiceProcess;
bool serviceExists = false
foreach (ServiceController sc in ServiceController.GetServices())
{
if (sc.ServiceName == "myServiceName")
{
//service is found
serviceExists = true;
break;
}
}
I think this is the best answer for this question. There is no need to add extra processing to verify if the service exists, since it will throw an exception if it doesn't. You just need to catch it. You also do not need to close() the connecting if you wrap the entire method in using().
using (ServiceController sc = new ServiceController(ServiceName))
{
try
{
if (sc.Status != ServiceControllerStatus.Running)
{
sc.Start();
sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 10));
//service is now Started
}
else
//Service was already started
}
catch (System.ServiceProcess.TimeoutException)
{
//Service was stopped but could not restart (10 second timeout)
}
catch (InvalidOperationException)
{
//This Service does not exist
}
}
private bool ServiceExists(string serviceName)
{
ServiceController[] services = ServiceController.GetServices();
var service = services.FirstOrDefault(s => string.Equals(s.ServiceName, serviceName, StringComparison.OrdinalIgnoreCase));
return service != null;
}
I am currently writing a little bootstrap code for a service that can be run in the console. It essentially boils down to calling the OnStart() method instead of using the ServiceBase to start and stop the service (because it doesn't run the application if it isn't installed as a service and makes debugging a nightmare).
Right now I am using Debugger.IsAttached to determine if I should use ServiceBase.Run or [service].OnStart, but I know that isn't the best idea because some times end users want to run the service in a console (to see the output etc. realtime).
Any ideas on how I could determine if the Windows service controller started 'me', or if the user started 'me' in the console? Apparantly Environment.IsUserInteractive is not the answer. I thought about using commandline args, but that seems 'dirty'.
I could always see about a try-catch statement around ServiceBase.Run, but that seems dirty. Edit: Try catch doesn't work.
I have a solution: putting it up here for all the other interested stackers:
public void Run()
{
if (Debugger.IsAttached || Environment.GetCommandLineArgs().Contains<string>("-console"))
{
RunAllServices();
}
else
{
try
{
string temp = Console.Title;
ServiceBase.Run((ServiceBase[])ComponentsToRun);
}
catch
{
RunAllServices();
}
}
} // void Run
private void RunAllServices()
{
foreach (ConsoleService component in ComponentsToRun)
{
component.Start();
}
WaitForCTRLC();
foreach (ConsoleService component in ComponentsToRun)
{
component.Stop();
}
}
EDIT: There was another question on StackOverflow where the guy had problems with the Environment.CurrentDirectory being "C:\Windows\System32" looks like that may be the answer. I will test today.
Another workaround.. so can run as WinForm or as windows service
var backend = new Backend();
if (Environment.UserInteractive)
{
backend.OnStart();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Fronend(backend));
backend.OnStop();
}
else
{
var ServicesToRun = new ServiceBase[] {backend};
ServiceBase.Run(ServicesToRun);
}
I usually flag my Windows service as a console application which takes a command line parameter of "-console" to run using a console, otherwise it runs as a service. To debug you just set the command line parameters in the project options to "-console" and you're off!
This makes debugging nice and easy and means that the app functions as a service by default, which is what you'll want.
What works for me:
The class doing the actual service work is running in a separate thread.
This thread is started from within the OnStart() method, and stopped from OnStop().
The decision between service and console mode depends on Environment.UserInteractive
Sample code:
class MyService : ServiceBase
{
private static void Main()
{
if (Environment.UserInteractive)
{
startWorkerThread();
Console.WriteLine ("====== Press ENTER to stop threads ======");
Console.ReadLine();
stopWorkerThread() ;
Console.WriteLine ("====== Press ENTER to quit ======");
Console.ReadLine();
}
else
{
Run (this) ;
}
}
protected override void OnStart(string[] args)
{
startWorkerThread();
}
protected override void OnStop()
{
stopWorkerThread() ;
}
}
Like Ash, I write all actual processing code in a separate class library assembly, which was then referenced by the windows service executable, as well as a console app.
However, there are occasions when it is useful to know if the class library is running in the context of the service executable or the console app. The way I do this is to reflect on the base class of the hosting app. (Sorry for the VB, but I imagine that the following could be c#-ified fairly easily):
Public Class ExecutionContext
''' <summary>
''' Gets a value indicating whether the application is a windows service.
''' </summary>
''' <value>
''' <c>true</c> if this instance is service; otherwise, <c>false</c>.
''' </value>
Public Shared ReadOnly Property IsService() As Boolean
Get
' Determining whether or not the host application is a service is
' an expensive operation (it uses reflection), so we cache the
' result of the first call to this method so that we don't have to
' recalculate it every call.
' If we have not already determined whether or not the application
' is running as a service...
If IsNothing(_isService) Then
' Get details of the host assembly.
Dim entryAssembly As Reflection.Assembly = Reflection.Assembly.GetEntryAssembly
' Get the method that was called to enter the host assembly.
Dim entryPoint As System.Reflection.MethodInfo = entryAssembly.EntryPoint
' If the base type of the host assembly inherits from the
' "ServiceBase" class, it must be a windows service. We store
' the result ready for the next caller of this method.
_isService = (entryPoint.ReflectedType.BaseType.FullName = "System.ServiceProcess.ServiceBase")
End If
' Return the cached result.
Return CBool(_isService)
End Get
End Property
Private Shared _isService As Nullable(Of Boolean) = Nothing
#End Region
End Class
Jonathan, not exactly an answer to your question, but I've just finished writing a windows service and also noted the difficulty with debugging and testing.
Solved it by simply writing all actual processing code in a separate class library assembly, which was then referenced by the windows service executable, as well as a console app and a test harness.
Apart from basic timer logic, all more complex processing happened in the common assembly and could be tested/run on demand incredibly easily.
I have modified the ProjectInstaller to append the command-line argument parameter /service, when it is being installed as service:
static class Program
{
static void Main(string[] args)
{
if (Array.Exists(args, delegate(string arg) { return arg == "/install"; }))
{
System.Configuration.Install.TransactedInstaller ti = null;
ti = new System.Configuration.Install.TransactedInstaller();
ti.Installers.Add(new ProjectInstaller());
ti.Context = new System.Configuration.Install.InstallContext("", null);
string path = System.Reflection.Assembly.GetExecutingAssembly().Location;
ti.Context.Parameters["assemblypath"] = path;
ti.Install(new System.Collections.Hashtable());
return;
}
if (Array.Exists(args, delegate(string arg) { return arg == "/uninstall"; }))
{
System.Configuration.Install.TransactedInstaller ti = null;
ti = new System.Configuration.Install.TransactedInstaller();
ti.Installers.Add(new ProjectInstaller());
ti.Context = new System.Configuration.Install.InstallContext("", null);
string path = System.Reflection.Assembly.GetExecutingAssembly().Location;
ti.Context.Parameters["assemblypath"] = path;
ti.Uninstall(null);
return;
}
if (Array.Exists(args, delegate(string arg) { return arg == "/service"; }))
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[] { new MyService() };
ServiceBase.Run(ServicesToRun);
}
else
{
Console.ReadKey();
}
}
}
The ProjectInstaller.cs is then modified to override a OnBeforeInstall() and OnBeforeUninstall()
[RunInstaller(true)]
public partial class ProjectInstaller : Installer
{
public ProjectInstaller()
{
InitializeComponent();
}
protected virtual string AppendPathParameter(string path, string parameter)
{
if (path.Length > 0 && path[0] != '"')
{
path = "\"" + path + "\"";
}
path += " " + parameter;
return path;
}
protected override void OnBeforeInstall(System.Collections.IDictionary savedState)
{
Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service");
base.OnBeforeInstall(savedState);
}
protected override void OnBeforeUninstall(System.Collections.IDictionary savedState)
{
Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service");
base.OnBeforeUninstall(savedState);
}
}
This thread is really old, but I thought I would throw my solution out there. Quite simply, to handle this type of situation, I built a "service harness" that is used in both the console and Windows service cases. As above, most of the logic is contained in a separate library, but this is more for testing and "linkability".
The attached code by no means represents the "best possible" way to solve this, just my own approach. Here, the service harness is called by the console app when in "console mode" and by the same application's "start service" logic when it is running as a service. By doing it this way, you can now call
ServiceHost.Instance.RunningAsAService (Boolean)
from anywhere in your code to check if the application is running as a service or simply as a console.
Here is the code:
public class ServiceHost
{
private static Logger log = LogManager.GetLogger(typeof(ServiceHost).Name);
private static ServiceHost mInstance = null;
private static object mSyncRoot = new object();
#region Singleton and Static Properties
public static ServiceHost Instance
{
get
{
if (mInstance == null)
{
lock (mSyncRoot)
{
if (mInstance == null)
{
mInstance = new ServiceHost();
}
}
}
return (mInstance);
}
}
public static Logger Log
{
get { return log; }
}
public static void Close()
{
lock (mSyncRoot)
{
if (mInstance.mEngine != null)
mInstance.mEngine.Dispose();
}
}
#endregion
private ReconciliationEngine mEngine;
private ServiceBase windowsServiceHost;
private UnhandledExceptionEventHandler threadExceptionHanlder = new UnhandledExceptionEventHandler(ThreadExceptionHandler);
public bool HostHealthy { get; private set; }
public bool RunningAsService {get; private set;}
private ServiceHost()
{
HostHealthy = false;
RunningAsService = false;
AppDomain.CurrentDomain.UnhandledException += threadExceptionHandler;
try
{
mEngine = new ReconciliationEngine();
HostHealthy = true;
}
catch (Exception ex)
{
log.FatalException("Could not initialize components.", ex);
}
}
public void StartService()
{
if (!HostHealthy)
throw new ApplicationException("Did not initialize components.");
try
{
mEngine.Start();
}
catch (Exception ex)
{
log.FatalException("Could not start service components.", ex);
HostHealthy = false;
}
}
public void StartService(ServiceBase serviceHost)
{
if (!HostHealthy)
throw new ApplicationException("Did not initialize components.");
if (serviceHost == null)
throw new ArgumentNullException("serviceHost");
windowsServiceHost = serviceHost;
RunningAsService = true;
try
{
mEngine.Start();
}
catch (Exception ex)
{
log.FatalException("Could not start service components.", ex);
HostHealthy = false;
}
}
public void RestartService()
{
if (!HostHealthy)
throw new ApplicationException("Did not initialize components.");
try
{
log.Info("Stopping service components...");
mEngine.Stop();
mEngine.Dispose();
log.Info("Starting service components...");
mEngine = new ReconciliationEngine();
mEngine.Start();
}
catch (Exception ex)
{
log.FatalException("Could not restart components.", ex);
HostHealthy = false;
}
}
public void StopService()
{
try
{
if (mEngine != null)
mEngine.Stop();
}
catch (Exception ex)
{
log.FatalException("Error stopping components.", ex);
HostHealthy = false;
}
finally
{
if (windowsServiceHost != null)
windowsServiceHost.Stop();
if (RunningAsService)
{
AppDomain.CurrentDomain.UnhandledException -= threadExceptionHanlder;
}
}
}
private void HandleExceptionBasedOnExecution(object ex)
{
if (RunningAsService)
{
windowsServiceHost.Stop();
}
else
{
throw (Exception)ex;
}
}
protected static void ThreadExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
log.FatalException("Unexpected error occurred. System is shutting down.", (Exception)e.ExceptionObject);
ServiceHost.Instance.HandleExceptionBasedOnExecution((Exception)e.ExceptionObject);
}
}
All you need to do here is replace that ominous looking ReconcilationEngine reference with whatever method is boostrapping your logic. Then in your application, use the ServiceHost.Instance.Start() and ServiceHost.Instance.Stop() methods whether you are running in console mode or as a service.
Maybe checking if the process parent is C:\Windows\system32\services.exe.
The only way I've found to achieve this, is to check if a console is attached to the process in the first place, by accessing any Console object property (e.g. Title) inside a try/catch block.
If the service is started by the SCM, there is no console, and accessing the property will throw a System.IO.IOError.
However, since this feels a bit too much like relying on an implementation-specific detail (what if the SCM on some platforms or someday decides to provide a console to the processes it starts?), I always use a command line switch (-console) in production apps...
Here is a translation of chksr's answer to .NET, and avoiding the bug that fails to recognize interactive services:
using System.Security.Principal;
var wi = WindowsIdentity.GetCurrent();
var wp = new WindowsPrincipal(wi);
var serviceSid = new SecurityIdentifier(WellKnownSidType.ServiceSid, null);
var localSystemSid = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
var interactiveSid = new SecurityIdentifier(WellKnownSidType.InteractiveSid, null);
// maybe check LocalServiceSid, and NetworkServiceSid also
bool isServiceRunningAsUser = wp.IsInRole(serviceSid);
bool isSystem = wp.IsInRole(localSystemSid);
bool isInteractive = wp.IsInRole(interactiveSid);
bool isAnyService = isServiceRunningAsUser || isSystem || !isInteractive;
This is a bit of a self-plug, but I've got a little app that will load up your service types in your app via reflection and execute them that way. I include the source code, so you could change it slightly to display standard output.
No code changes needed to use this solution. I have a Debugger.IsAttached type of solution as well that is generic enough to be used with any service. Link is in this article:
.NET Windows Service Runner
Well there's some very old code (about 20 years or so, not from me but found in the wild, wild web, and in C not C#) that should give you an idea how to do the job:
enum enEnvironmentType
{
ENVTYPE_UNKNOWN,
ENVTYPE_STANDARD,
ENVTYPE_SERVICE_WITH_INTERACTION,
ENVTYPE_SERVICE_WITHOUT_INTERACTION,
ENVTYPE_IIS_ASP,
};
enEnvironmentType GetEnvironmentType(void)
{
HANDLE hProcessToken = NULL;
DWORD groupLength = 300;
PTOKEN_GROUPS groupInfo = NULL;
SID_IDENTIFIER_AUTHORITY siaNt = SECURITY_NT_AUTHORITY;
PSID pInteractiveSid = NULL;
PSID pServiceSid = NULL;
DWORD dwRet = NO_ERROR;
DWORD ndx;
BOOL m_isInteractive = FALSE;
BOOL m_isService = FALSE;
// open the token
if (!::OpenProcessToken(::GetCurrentProcess(),TOKEN_QUERY,&hProcessToken))
{
dwRet = ::GetLastError();
goto closedown;
}
// allocate a buffer of default size
groupInfo = (PTOKEN_GROUPS)::LocalAlloc(0, groupLength);
if (groupInfo == NULL)
{
dwRet = ::GetLastError();
goto closedown;
}
// try to get the info
if (!::GetTokenInformation(hProcessToken, TokenGroups,
groupInfo, groupLength, &groupLength))
{
// if buffer was too small, allocate to proper size, otherwise error
if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
dwRet = ::GetLastError();
goto closedown;
}
::LocalFree(groupInfo);
groupInfo = (PTOKEN_GROUPS)::LocalAlloc(0, groupLength);
if (groupInfo == NULL)
{
dwRet = ::GetLastError();
goto closedown;
}
if (!GetTokenInformation(hProcessToken, TokenGroups,
groupInfo, groupLength, &groupLength))
{
dwRet = ::GetLastError();
goto closedown;
}
}
//
// We now know the groups associated with this token. We want
// to look to see if the interactive group is active in the
// token, and if so, we know that this is an interactive process.
//
// We also look for the "service" SID, and if it's present,
// we know we're a service.
//
// The service SID will be present iff the service is running in a
// user account (and was invoked by the service controller).
//
// create comparison sids
if (!AllocateAndInitializeSid(&siaNt,
1,
SECURITY_INTERACTIVE_RID,
0, 0, 0, 0, 0, 0, 0,
&pInteractiveSid))
{
dwRet = ::GetLastError();
goto closedown;
}
if (!AllocateAndInitializeSid(&siaNt,
1,
SECURITY_SERVICE_RID,
0, 0, 0, 0, 0, 0, 0,
&pServiceSid))
{
dwRet = ::GetLastError();
goto closedown;
}
// try to match sids
for (ndx = 0; ndx < groupInfo->GroupCount ; ndx += 1)
{
SID_AND_ATTRIBUTES sanda = groupInfo->Groups[ndx];
PSID pSid = sanda.Sid;
//
// Check to see if the group we're looking at is one of
// the two groups we're interested in.
//
if (::EqualSid(pSid, pInteractiveSid))
{
//
// This process has the Interactive SID in its
// token. This means that the process is running as
// a console process
//
m_isInteractive = TRUE;
m_isService = FALSE;
break;
}
else if (::EqualSid(pSid, pServiceSid))
{
//
// This process has the Service SID in its
// token. This means that the process is running as
// a service running in a user account ( not local system ).
//
m_isService = TRUE;
m_isInteractive = FALSE;
break;
}
}
if ( !( m_isService || m_isInteractive ) )
{
//
// Neither Interactive or Service was present in the current
// users token, This implies that the process is running as
// a service, most likely running as LocalSystem.
//
m_isService = TRUE;
}
closedown:
if ( pServiceSid )
::FreeSid( pServiceSid );
if ( pInteractiveSid )
::FreeSid( pInteractiveSid );
if ( groupInfo )
::LocalFree( groupInfo );
if ( hProcessToken )
::CloseHandle( hProcessToken );
if (dwRet == NO_ERROR)
{
if (m_isService)
return(m_isInteractive ? ENVTYPE_SERVICE_WITH_INTERACTION : ENVTYPE_SERVICE_WITHOUT_INTERACTION);
return(ENVTYPE_STANDARD);
}
else
return(ENVTYPE_UNKNOWN);
}
Seems I am bit late to the party, but interesting difference when run as a service is that at start current folder points to system directory (C:\windows\system32 by default). Its hardly unlikely user app will start from the system folder in any real life situation.
So, I use following trick (c#):
protected static bool IsRunAsService()
{
string CurDir = Directory.GetCurrentDirectory();
if (CurDir.Equals(Environment.SystemDirectory, StringComparison.CurrentCultureIgnoreCase))
{
return true;
}
return (false);
}
For future extension, additional check make be done for System.Environment.UserInteractive == false (but I do not know how it correlates with 'Allow service to interact with desktop' service settings).
You may also check window session by System.Diagnostics.Process.GetCurrentProcess().SessionId == 0 (I do not know how it correlates with 'Allow service to interact with desktop' service settings as well).
If you write portable code (say, with .NetCore) you may also check Environment.OSVersion.Platform to ensure that you are on windows first.