В настоящее время я пишу небольшой код начальной загрузки для службы, которую можно запустить в консоли. По сути, это сводится к вызову метода OnStart () вместо использования ServiceBase для запуска и остановки службы (поскольку он не запускает приложение, если оно не установлено как служба, и отладка становится кошмаром).
Прямо сейчас я использую Debugger.IsAttached, чтобы определить, следует ли мне использовать ServiceBase.Run или [service] .OnStart, но я знаю, что это не лучшая идея, потому что иногда конечные пользователи хотят запускать службу в консоли (чтобы увидеть вывод и т.д. в реальном времени).
Есть идеи, как я могу определить, запустил ли контроллер службы Windows «меня», или пользователь запустил «меня» в консоли? Очевидно, Environment.IsUserInteractive - не ответ. Я думал об использовании аргументов командной строки, но это кажется «грязным».
Я всегда мог увидеть инструкцию try-catch вокруг 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», похоже, это может быть ответ. Сегодня протестирую.
Не то чтобы IsUserInteractive вернет нет false для консольных приложений, как было указано в приведенной вами ссылке - по крайней мере, не в целом. Я использую его для этой цели, и у меня никогда не было с этим проблем.
Отвечает ли это на ваш вопрос? Определить, работает ли код как служба
@MarkovskI Я обычно голосую за новый вопрос как за дубликат этого старого и, если могу, добавляю ответ на старый (этот вопрос в данном случае), если это полезно.
@MarkSchultheiss, если я вас правильно понял, я добавил ответ на этот, он был удален :)
@MarkovskI Да, я бы добавил здесь ваш полный ответ, а затем пометил бы другой вопрос как дубликат, но я делаю эту практику только как более старый вопрос, который я считаю более «заслуживающим» ни по какой другой причине, что они старше, но это только я возможно.





Я обычно помечаю свою службу Windows как консольное приложение, которое принимает параметр командной строки «-console» для запуска с помощью консоли, в противном случае оно запускается как служба. Для отладки вы просто установите параметры командной строки в параметрах проекта на «-console», и все готово!
Это делает отладку приятной и простой и означает, что приложение по умолчанию функционирует как служба, что вам и нужно.
Я тоже так делаю. Работает очень хорошо; единственная проблема с отладкой - это безопасность (какая учетная запись) и рабочая папка, которые легче кодировать.
Джонатан, не совсем ответ на ваш вопрос, но я только что закончил писать службу Windows и также отметил трудности с отладкой и тестированием.
Решил ее, просто записав весь фактический код обработки в отдельную сборку библиотеки классов, на которую затем ссылались исполняемый файл службы Windows, а также консольное приложение и тестовый жгут.
Помимо базовой логики таймера, вся более сложная обработка происходила в общей сборке, и ее можно было невероятно легко протестировать / запустить по запросу.
Это очень полезная информация, я думаю, это «правильный» способ сделать это. Я бы хотел, чтобы вы приняли два ответа :).
Нет проблем, Джонатан, рад, что это было полезно. Сейчас я стараюсь следовать этому подходу (отдельная сборка логики приложения) для всех приложений. Таким образом, службу Windows можно рассматривать как еще один вид приложения. Я предполагаю, что это шаблон контроллера представления модели.
Единственный способ добиться этого, который я нашел, - это в первую очередь проверить, подключена ли консоль к процессу, обратившись к любому свойству объекта Console (например, Title) внутри блока try / catch.
Если служба запускается SCM, консоли нет, и доступ к свойству вызовет ошибку System.IO.IOError.
Однако, поскольку это слишком похоже на то, как полагаться на детали, специфичные для реализации (что, если SCM на некоторых платформах или когда-нибудь решит предоставить консоль для процессов, которые он запускает?), Я всегда использую переключатель командной строки (-console ) в производственных приложениях ...
Это что-то вроде самоподключения, но у меня есть небольшое приложение, которое загружает ваши типы сервисов в ваше приложение через отражение и выполняет их таким образом. Я включил исходный код, чтобы вы могли немного изменить его для отображения стандартного вывода.
Для использования этого решения не требуется никаких изменений кода. У меня также есть решение типа Debugger.IsAttached, которое достаточно универсально для использования с любой службой. Ссылка в этой статье: .NET Windows Service Runner
На самом деле я написал для них базовый класс, который имеет метод Start (), так что мне не нужно прибегать к отражению. Тем не менее, спасибо за совет.
Это разработано как автономный способ запуска любой службы вне среды служб Windows без изменения кода. Просто дважды щелкните бегунок, выберите свой сервис .exe или .dll и нажмите «ОК». Если вы запустите бегун для командной строки, вы увидите стандартный ввод-вывод.
Что мне подходит:
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() ;
}
}
Спасибо за подсказку gyrolf, но, к сожалению, Environment.UserInteractive верно только для приложений Windows Forms :(.
Насколько я понимаю документацию и пример кода в ней, нет никаких ограничений для приложений Windows Forms. Успешно использую в обычных консольных приложениях.
Могу подтвердить, что это правильно. Environment.UserInteractive имеет значение True при работе с консолью и False при работе в качестве службы.
В некоторых случаях этот код не работает - если вы запускаете это приложение из планировщика Windows в качестве задачи, тогда Environment.UserInteractive устанавливается в значение false, даже если это не служба. Если вы планируете запускать приложение из планировщика, подумайте о более надежном решении.
@ pg0xC Приведенный выше класс является производным от ServiceBase. Я не думаю, что кто-то попытается запустить это как задачу. Первоначальный вопрос касался приложения, которое можно было запускать на консоли или в качестве службы. Я согласен с voithos, что Environment.UserInteractive работает в желаемых случаях. Если вы запускаете приложение как задачу, оно ДОЛЖНО возвращать false.
Как и Эш, я пишу весь фактический код обработки в отдельной сборке библиотеки классов, на которую затем ссылается исполняемый файл службы Windows, а также консольное приложение.
Однако бывают случаи, когда полезно знать, работает ли библиотека классов в контексте исполняемого файла службы или консольного приложения. Я делаю это, размышляя о базовом классе приложения для хостинга. (Извините за VB, но я полагаю, что следующее можно довольно легко изменить на C#):
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
Я не понимаю, как это могло бы работать, если бы одна и та же сборка могла быть запущена как консольное приложение или как служба Windows ... Assembly.GetEntryAssembly () и Assembly.EntryPoint возвращают одинаковые значения в обоих случаях. Я предполагаю, что это работает только в том случае, если вы запускаете разные сборки в двух случаях.
@DanPorts: я никогда не пытался запустить один и тот же сборка как консольное приложение и как службу Windows. Однако иногда бывает полезно скомпилировать один и тот же набор классов в приложение каждого типа, и в этом случае приведенный выше класс может быть полезен для определения того, в каком контексте он используется.
Я получаю "System.Object" вместо значения "System.ServiceProcess.ServiceBase" в качестве возвращаемого значения ..ReflectedType.BaseType.FullName (и да, я запускаю код как службу из окна Services)?
Возможно, проверьте, является ли родительский процесс C: \ Windows \ system32 \ services.exe.
Я изменил 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);
}
}
Пример выше не обрабатывает кавычки должным образом, проверьте это для лучшего решения stackoverflow.com/questions/4862580/…
Улучшена обработка кавычек вокруг пути.
Другой обходной путь ... так что можно запускать как 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);
}
Мне нравится это решение, похоже, именно для этого был разработан Environment.UserInteractive.
Интересно, что бы произошло, если бы вы отметили «Разрешить службе взаимодействовать с рабочим столом» для этой службы. Насколько я знаю, это позволит сервису иметь графический интерфейс. Разве тогда свойство UserInteractive не должно возвращать true? [MSDN: свойство UserInteractive сообщает false для процесса Windows или такой службы, как IIS, которая работает без пользовательского интерфейса.]
Я тестировал: когда вы устанавливаете флажок «Разрешить службе взаимодействовать с рабочим столом», то UserInteractive верен.
Я попытался запустить свой процесс из контейнера Windows Docker, и там UserInteractive также неверен ... Тем не менее, я определенно не работаю как служба.
Эта ветка действительно старая, но я подумал, что выкину свое решение там. Проще говоря, чтобы справиться с подобными ситуациями, я создал «служебную привязку», которая используется как в консоли, так и в служебных случаях 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() независимо от того, работаете ли вы в консольном режиме или в качестве службы.
Ну, есть очень старый код (лет 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);
}
Это не похоже на C#.
Но ведь его можно было бы перевести на C#, не так ли?
Не так-то просто C# -ified
В Си есть гораздо более простой способ сделать это. В какой-то момент вам нужно вызвать StartServiceCtrlDispatcher в вашем коде C. Если этот вызов завершился неудачно, И GetLastError вернет ERROR_FAILED_SERVICE_CONTROLLER_CONNECT, значит, вы знаете, что работаете не как служба. Интересно, есть ли этому аналог на C#?
@Ferruccio да, вы можете вызвать ServiceBase.Run и поймать исключение
ServiceBase.Run не обязательно вызывает исключение при вызове из консольного приложения. В моем тестовом коде с использованием .NET Core 2.2 if просто выводит следующую ошибку в окно консоли и продолжает работу без исключения: Невозможно запустить службу из командной строки или отладчика. A Windows Service must first be installed and then started with the ServerExplorer, Windows Services Administrative tool or the NET START command.
Вот перевод ответа 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;
Кажется, я немного опаздываю на вечеринку, но интересная разница при запуске в качестве службы заключается в том, что при запуске текущая папка указывает на системный каталог (по умолчанию C:\windows\system32). Едва ли маловероятно, что пользовательское приложение запустится из системной папки в любой реальной жизненной ситуации.
Итак, я использую следующий трюк (C#):
protected static bool IsRunAsService()
{
string CurDir = Directory.GetCurrentDirectory();
if (CurDir.Equals(Environment.SystemDirectory, StringComparison.CurrentCultureIgnoreCase))
{
return true;
}
return (false);
}
Для будущего расширения необходимо выполнить дополнительную проверку для System.Environment.UserInteractive == false (но я не знаю, как это соотносится с настройками службы «Разрешить службе взаимодействовать с рабочим столом»).
Вы также можете проверить сеанс окна с помощью System.Diagnostics.Process.GetCurrentProcess().SessionId == 0 (я не знаю, как он соотносится с настройками службы «Разрешить службе взаимодействовать с рабочим столом»).
Если вы пишете переносимый код (скажем, с помощью .NetCore), вы также можете проверить Environment.OSVersion.Platform, чтобы убедиться, что вы сначала находитесь в Windows.
Текущий каталог может быть system32 во множестве реальных случаев, таких как запуск приложения от имени администратора, запуск с повышенными привилегиями, запуск его из сценария или ручное копирование туда. Это НЕ способ сказать, что вы работаете как служба.
Спасибо за добавление вашего решения, должно быть полезным справочником.