سؤال

أقوم حاليًا بكتابة كود تمهيد صغير لخدمة يمكن تشغيلها في وحدة التحكم.يتلخص الأمر بشكل أساسي في استدعاء طريقة OnStart () بدلاً من استخدام ServiceBase لبدء الخدمة وإيقافها (لأنها لا تقوم بتشغيل التطبيق إذا لم يتم تثبيته كخدمة ويجعل تصحيح الأخطاء كابوسًا).

أستخدم الآن Debugger.IsAttached لتحديد ما إذا كان يجب علي استخدام ServiceBase.Run أو [service].OnStart، لكنني أعلم أن هذه ليست الفكرة الأفضل لأنه في بعض الأحيان يرغب المستخدمون النهائيون في تشغيل الخدمة في وحدة التحكم (لمعرفة ذلك الإخراج الخفي الوقت الحالى).

هل لديك أي أفكار حول كيفية تحديد ما إذا كانت وحدة التحكم في خدمة Windows قد بدأت "أنا" أو إذا كان المستخدم قد بدأ "أنا" في وحدة التحكم؟على ما يبدو البيئة.IsUserInteractive ليس هو الجواب.فكرت في استخدام وسيطات سطر الأوامر، لكن هذا يبدو "قذرًا".

يمكنني دائمًا رؤية بيان محاولة الالتقاط حول ServiceBase.Run، لكن هذا يبدو سيئًا.يحرر:حاول الصيد لا يعمل.

لدي حل:وضعه هنا لجميع المعبئين المهتمين الآخرين:

    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();
        }
    }

يحرر:كان هناك سؤال آخر على StackOverflow حيث واجه الرجل مشاكل مع Environment.CurrentDirectory كونه "C:\Windows\System32" يبدو أن هذا قد يكون الجواب.سأختبر اليوم.

هل كانت مفيدة؟

المحلول

مثل Ash، أكتب كل أكواد المعالجة الفعلية في مجموعة مكتبة فئة منفصلة، ​​والتي تمت الإشارة إليها بعد ذلك بواسطة خدمة Windows القابلة للتنفيذ، بالإضافة إلى تطبيق وحدة التحكم.

ومع ذلك، هناك مناسبات يكون من المفيد فيها معرفة ما إذا كانت مكتبة الفصل تعمل في سياق الخدمة القابلة للتنفيذ أو تطبيق وحدة التحكم.الطريقة التي أفعل بها ذلك هي التفكير في الفئة الأساسية لتطبيق الاستضافة.(عذرًا على VB، ولكني أتخيل أنه يمكن تحديد ما يلي بسهولة إلى حد ما):

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

نصائح أخرى

حل آخر ..لذلك يمكن تشغيله كـ WinForm أو كخدمة Windows

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);
}

أقوم عادةً بوضع علامة على خدمة Windows كتطبيق وحدة تحكم والذي يأخذ معلمة سطر أوامر "-console" لتشغيلها كوحدة تحكم، وإلا فسيتم تشغيلها كخدمة.لتصحيح الأخطاء، ما عليك سوى تعيين معلمات سطر الأوامر في خيارات المشروع على "-console" وستكون خارج الخدمة!

وهذا يجعل تصحيح الأخطاء أمرًا رائعًا وسهلاً ويعني أن التطبيق يعمل كخدمة افتراضيًا، وهو ما تريده.

ما يصلح لي:

  • يتم تشغيل الفصل الذي يقوم بعمل الخدمة الفعلي في موضوع منفصل.
  • يتم بدء هذا الموضوع من داخل طريقة OnStart()، ويتم إيقافه من OnStop().
  • يعتمد القرار بين وضع الخدمة ووحدة التحكم على Environment.UserInteractive

عينة من الرموز:

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() ;
    }
}

جوناثان، ليس بالضبط إجابة لسؤالك، لكنني انتهيت للتو من كتابة خدمة Windows ولاحظت أيضًا صعوبة تصحيح الأخطاء والاختبار.

تم حل المشكلة ببساطة عن طريق كتابة جميع أكواد المعالجة الفعلية في مجموعة مكتبة فئة منفصلة، ​​والتي تمت الإشارة إليها بعد ذلك بواسطة خدمة Windows القابلة للتنفيذ، بالإضافة إلى تطبيق وحدة التحكم وأداة الاختبار.

وبصرف النظر عن منطق المؤقت الأساسي، فإن جميع عمليات المعالجة الأكثر تعقيدًا تتم في التجميع المشترك ويمكن اختبارها/تشغيلها عند الطلب بسهولة لا تصدق.

لقد قمت بتعديل ProjectInstaller لإلحاق معلمة سطر الأوامر/الخدمة، عندما يتم تثبيته كخدمة:

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();
        }
    }
}

يتم بعد ذلك تعديل ProjectInstaller.cs لتجاوز OnBeforeInstall() و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);
    }
}

هذا الموضوع قديم حقًا، لكنني اعتقدت أنني سأطرح الحل هناك.بكل بساطة، للتعامل مع هذا النوع من المواقف، قمت ببناء "أداة خدمة" يتم استخدامها في كل من حالات خدمة وحدة التحكم وWindows.كما هو مذكور أعلاه، يتم تضمين معظم المنطق في مكتبة منفصلة، ​​ولكن هذا مخصص أكثر للاختبار و"قابلية الارتباط".

لا يمثل الكود المرفق بأي حال من الأحوال "أفضل طريقة ممكنة" لحل هذه المشكلة، بل هو أسلوبي الخاص فقط.هنا، يتم استدعاء أداة تسخير الخدمة بواسطة تطبيق وحدة التحكم عندما تكون في "وضع وحدة التحكم" ومن خلال منطق "بدء الخدمة" الخاص بالتطبيق نفسه عندما يتم تشغيله كخدمة.من خلال القيام بذلك بهذه الطريقة، يمكنك الآن الاتصال

ServiceHost.Instance.RunningAsAService (منطقي)

من أي مكان في التعليمات البرمجية الخاصة بك للتحقق مما إذا كان التطبيق يعمل كخدمة أو ببساطة كوحدة تحكم.

هنا هو الرمز:

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);
    }
}

كل ما عليك فعله هنا هو استبدال هذا المظهر المشؤوم ReconcilationEngine مرجع بأي طريقة تعزز منطقك.ثم في التطبيق الخاص بك، استخدم ServiceHost.Instance.Start() و ServiceHost.Instance.Stop() طرق سواء كنت تعمل في وضع وحدة التحكم أو كخدمة.

ربما يتم التحقق مما إذا كان أصل العملية هو C:\Windows\system32\services.exe.

الطريقة الوحيدة التي وجدتها لتحقيق ذلك هي التحقق مما إذا كانت وحدة التحكم متصلة بالعملية في المقام الأول، عن طريق الوصول إلى أي خاصية لكائن وحدة التحكم (على سبيل المثال.العنوان) داخل كتلة المحاولة/الالتقاط.

إذا تم تشغيل الخدمة بواسطة SCM، فلن تكون هناك وحدة تحكم، وسيؤدي الوصول إلى الخاصية إلى ظهور خطأ System.IO.IOError.

ومع ذلك، نظرًا لأن هذا يشبه إلى حد كبير الاعتماد على تفاصيل خاصة بالتنفيذ (ماذا لو قرر SCM على بعض الأنظمة الأساسية أو في يوم من الأيام توفير وحدة تحكم للعمليات التي يبدأها؟)، فإنني أستخدم دائمًا مفتاح تبديل سطر الأوامر (-console ) في تطبيقات الإنتاج...

فيما يلي ترجمة لإجابة chksr على .NET، وتجنب الخطأ الذي يفشل في التعرف على الخدمات التفاعلية:

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;

يعد هذا بمثابة توصيل ذاتي إلى حد ما، ولكن لدي تطبيقًا صغيرًا يمكنه تحميل أنواع الخدمة الخاصة بك في تطبيقك عبر الانعكاس وتنفيذها بهذه الطريقة.لقد قمت بتضمين كود المصدر، حتى تتمكن من تغييره قليلاً لعرض الإخراج القياسي.

لا يلزم إجراء تغييرات على التعليمات البرمجية لاستخدام هذا الحل.لدي نوع Debugger.IsAttached من الحلول أيضًا وهو عام بما يكفي لاستخدامه مع أي خدمة.الرابط موجود في هذه المقالة:.NET مشغل خدمة ويندوز

حسنًا، هناك بعض التعليمات البرمجية القديمة جدًا (حوالي 20 عامًا أو نحو ذلك، ليست مني ولكنها موجودة في شبكة الويب الجامحة وفي لغة C وليس C#) والتي من المفترض أن تعطيك فكرة عن كيفية القيام بهذه المهمة:

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);
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top