web-dev-qa-db-fra.com

Utilisation de Process.Start () pour démarrer un processus en tant qu'utilisateur différent à partir d'un service Windows

J'aimerais exécuter périodiquement un fichier .NET arbitraire sous un compte d'utilisateur spécifié à partir d'un service Windows. 

Jusqu'à présent, mon service Windows est exécuté avec une logique qui détermine le processus cible et le moment de son exécution. Le processus cible est démarré de la manière suivante:

  1. Le service Windows est démarré à l'aide des informations d'identification "administrateur".
  2. Le moment venu, un processus .NET intermédiaire est exécuté avec des arguments détaillant le processus à démarrer (nom de fichier, nom d'utilisateur, domaine, mot de passe).
  3. Ce processus crée un nouveau System.Diagnostics.Process, associe un objet ProcessStartInfo rempli avec les arguments qui lui sont transmis, puis appelle Start () sur l'objet de processus.

La première fois cela se produit, le processus cible s'exécute correctement puis se ferme normalement . Cependant, chaque fois que le processus cible est démarré, l'erreur "Échec de l'initialisation de l'application correctement (0xc0000142)" a été effectuée à chaque fois. Le redémarrage du service Windows permettra au processus de s'exécuter à nouveau avec succès (pour la première exécution).

Naturellement, l'objectif est que le processus cible soit exécuté avec succès à chaque fois.

Concernant l'étape 2 ci-dessus: Pour exécuter un processus en tant qu'utilisateur différent, .NET appelle la fonction win32 CreateProcessWithLogonW. Cette fonction nécessite un handle de fenêtre pour connecter l'utilisateur spécifié. Comme le service Windows ne s'exécute pas en mode interactif, il n'a pas de handle de fenêtre. Ce processus intermédiaire résout le problème car il dispose d'un handle de fenêtre qui peut être transmis au processus cible.

S'il vous plaît, aucune suggestion d'utilisation de psexec ou du planificateur de tâches Windows. J'ai accepté mon sort dans la vie, et cela inclut la résolution du problème de la manière indiquée ci-dessus.

34
Matt Jacobsen

Je semble avoir une implémentation fonctionnelle (Works On My Machine (TM)) pour les scénarios suivants:

Fichier de commandes, Assemblage de la console .NET, Application .NET Windows Forms.

Voici comment:

J'ai un service Windows qui s'exécute en tant qu'utilisateur administrateur . J'ajoute les stratégies suivantes à l'utilisateur administrateur:

  • Se connecter en tant que service 
  • Agir en tant que partie du système d'exploitation
  • Ajuster les quotas de mémoire pour un processus 
  • Remplacer un jeton de niveau processus

Ces stratégies peuvent être ajoutées en ouvrant le Panneau de configuration/Outils d'administration/Stratégie de sécurité locale/Attribution des droits de l'utilisateur. Une fois qu'elles sont définies, les stratégies ne prennent effet qu'à la prochaine connexion. Vous pouvez utiliser un autre utilisateur à la place de l'administrateur, ce qui pourrait rendre les choses un peu plus sûres :)

À présent, mon service Windows dispose des autorisations nécessaires pour démarrer des travaux en tant qu'autres utilisateurs. Lorsqu'un travail doit être démarré, le service exécute un assemblage séparé (assemblage "Starter" .NET) qui lance le processus pour moi.

Le code suivant, situé dans le service Windows, exécute mon assemblée de console "Starter":

Process proc = null;
System.Diagnostics.ProcessStartInfo info;
string domain = string.IsNullOrEmpty(row.Domain) ? "." : row.Domain;
info = new ProcessStartInfo("Starter.exe");
info.Arguments = cmd + " " + domain + " " + username + " " + password + " " + args;
info.WorkingDirectory = Path.GetDirectoryName(cmd);
info.UseShellExecute = false;
info.RedirectStandardError = true;
info.RedirectStandardOutput = true;
proc = System.Diagnostics.Process.Start(info);

La console Assembly démarre ensuite le processus cible via des appels interop:

class Program
{
    #region Interop

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID
    {
        public UInt32 LowPart;
        public Int32 HighPart;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID_AND_ATTRIBUTES
    {
        public LUID Luid;
        public UInt32 Attributes;
    }

    public struct TOKEN_PRIVILEGES
    {
        public UInt32 PrivilegeCount;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public LUID_AND_ATTRIBUTES[] Privileges;
    }

    enum TOKEN_INFORMATION_CLASS
    {
        TokenUser = 1,
        TokenGroups,
        TokenPrivileges,
        TokenOwner,
        TokenPrimaryGroup,
        TokenDefaultDacl,
        TokenSource,
        TokenType,
        TokenImpersonationLevel,
        TokenStatistics,
        TokenRestrictedSids,
        TokenSessionId,
        TokenGroupsAndPrivileges,
        TokenSessionReference,
        TokenSandBoxInert,
        TokenAuditPolicy,
        TokenOrigin,
        TokenElevationType,
        TokenLinkedToken,
        TokenElevation,
        TokenHasRestrictions,
        TokenAccessInformation,
        TokenVirtualizationAllowed,
        TokenVirtualizationEnabled,
        TokenIntegrityLevel,
        TokenUIAccess,
        TokenMandatoryPolicy,
        TokenLogonSid,
        MaxTokenInfoClass
    }

    [Flags]
    enum CreationFlags : uint
    {
        CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
        CREATE_DEFAULT_ERROR_MODE = 0x04000000,
        CREATE_NEW_CONSOLE = 0x00000010,
        CREATE_NEW_PROCESS_GROUP = 0x00000200,
        CREATE_NO_WINDOW = 0x08000000,
        CREATE_PROTECTED_PROCESS = 0x00040000,
        CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
        CREATE_SEPARATE_WOW_VDM = 0x00001000,
        CREATE_SUSPENDED = 0x00000004,
        CREATE_UNICODE_ENVIRONMENT = 0x00000400,
        DEBUG_ONLY_THIS_PROCESS = 0x00000002,
        DEBUG_PROCESS = 0x00000001,
        DETACHED_PROCESS = 0x00000008,
        EXTENDED_STARTUPINFO_PRESENT = 0x00080000
    }

    public enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation
    }

    public enum SECURITY_IMPERSONATION_LEVEL
    {
        SecurityAnonymous,
        SecurityIdentification,
        SecurityImpersonation,
        SecurityDelegation
    }

    [Flags]
    enum LogonFlags
    {
        LOGON_NETCREDENTIALS_ONLY = 2,
        LOGON_WITH_PROFILE = 1
    }

    enum LOGON_TYPE
    {
        LOGON32_LOGON_INTERACTIVE = 2,
        LOGON32_LOGON_NETWORK,
        LOGON32_LOGON_BATCH,
        LOGON32_LOGON_SERVICE,
        LOGON32_LOGON_UNLOCK = 7,
        LOGON32_LOGON_NETWORK_CLEARTEXT,
        LOGON32_LOGON_NEW_CREDENTIALS
    }

    enum LOGON_PROVIDER
    {
        LOGON32_PROVIDER_DEFAULT,
        LOGON32_PROVIDER_WINNT35,
        LOGON32_PROVIDER_WINNT40,
        LOGON32_PROVIDER_WINNT50
    }

    #region _SECURITY_ATTRIBUTES
    //typedef struct _SECURITY_ATTRIBUTES {  
    //    DWORD nLength;  
    //    LPVOID lpSecurityDescriptor;  
    //    BOOL bInheritHandle;
    //} SECURITY_ATTRIBUTES,  *PSECURITY_ATTRIBUTES,  *LPSECURITY_ATTRIBUTES;
    #endregion
    struct SECURITY_ATTRIBUTES
    {
        public uint Length;
        public IntPtr SecurityDescriptor;
        public bool InheritHandle;
    }

    [Flags] enum SECURITY_INFORMATION : uint
    {
        OWNER_SECURITY_INFORMATION        = 0x00000001,
        GROUP_SECURITY_INFORMATION        = 0x00000002,
        DACL_SECURITY_INFORMATION         = 0x00000004,
        SACL_SECURITY_INFORMATION         = 0x00000008,
        UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000,
        UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000,
        PROTECTED_SACL_SECURITY_INFORMATION   = 0x40000000,
        PROTECTED_DACL_SECURITY_INFORMATION   = 0x80000000
    }

    #region _SECURITY_DESCRIPTOR
    //typedef struct _SECURITY_DESCRIPTOR {
    //  UCHAR  Revision;
    //  UCHAR  Sbz1;
    //  SECURITY_DESCRIPTOR_CONTROL  Control;
    //  PSID  Owner;
    //  PSID  Group;
    //  PACL  Sacl;
    //  PACL  Dacl;
    //} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
    #endregion
    [StructLayoutAttribute(LayoutKind.Sequential)]
    struct SECURITY_DESCRIPTOR
    {
        public byte revision;
        public byte size;
        public short control; // public SECURITY_DESCRIPTOR_CONTROL control;
        public IntPtr owner;
        public IntPtr group;
        public IntPtr sacl;
        public IntPtr dacl;
    }

    #region _STARTUPINFO
    //typedef struct _STARTUPINFO {  
    //    DWORD cb;  
    //    LPTSTR lpReserved;  
    //    LPTSTR lpDesktop;  
    //    LPTSTR lpTitle;  
    //    DWORD dwX;  
    //    DWORD dwY;  
    //    DWORD dwXSize;  
    //    DWORD dwYSize;  
    //    DWORD dwXCountChars;  
    //    DWORD dwYCountChars;  
    //    DWORD dwFillAttribute;  
    //    DWORD dwFlags;  
    //    Word wShowWindow;  
    //    Word cbReserved2;  
    //    LPBYTE lpReserved2;  
    //    HANDLE hStdInput;  
    //    HANDLE hStdOutput;  
    //    HANDLE hStdError; 
    //} STARTUPINFO,  *LPSTARTUPINFO;
    #endregion
    struct STARTUPINFO
    {
        public uint cb;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string Reserved;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string Desktop;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string Title;
        public uint X;
        public uint Y;
        public uint XSize;
        public uint YSize;
        public uint XCountChars;
        public uint YCountChars;
        public uint FillAttribute;
        public uint Flags;
        public ushort ShowWindow;
        public ushort Reserverd2;
        public byte bReserverd2;
        public IntPtr StdInput;
        public IntPtr StdOutput;
        public IntPtr StdError;
    }

    #region _PROCESS_INFORMATION
    //typedef struct _PROCESS_INFORMATION {  
    //  HANDLE hProcess;  
    //  HANDLE hThread;  
    //  DWORD dwProcessId;  
    //  DWORD dwThreadId; } 
    //  PROCESS_INFORMATION,  *LPPROCESS_INFORMATION;
    #endregion
    [StructLayout(LayoutKind.Sequential)]
    struct PROCESS_INFORMATION
    {
        public IntPtr Process;
        public IntPtr Thread;
        public uint ProcessId;
        public uint ThreadId;
    }

    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool InitializeSecurityDescriptor(IntPtr pSecurityDescriptor, uint dwRevision);
    const uint SECURITY_DESCRIPTOR_REVISION = 1;

    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool SetSecurityDescriptorDacl(ref SECURITY_DESCRIPTOR sd, bool daclPresent, IntPtr dacl, bool daclDefaulted);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    extern static bool DuplicateTokenEx(
        IntPtr hExistingToken,
        uint dwDesiredAccess,
        ref SECURITY_ATTRIBUTES lpTokenAttributes,
        SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
        TOKEN_TYPE TokenType,
        out IntPtr phNewToken);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool LogonUser(
        string lpszUsername,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        out IntPtr phToken
        );

    #region GetTokenInformation
    //BOOL WINAPI GetTokenInformation(
    //  __in       HANDLE TokenHandle,
    //  __in       TOKEN_INFORMATION_CLASS TokenInformationClass,
    //  __out_opt  LPVOID TokenInformation,
    //  __in       DWORD TokenInformationLength,
    //  __out      PDWORD ReturnLength
    //);
    #endregion
    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool GetTokenInformation(
        IntPtr TokenHandle,
        TOKEN_INFORMATION_CLASS TokenInformationClass,
        IntPtr TokenInformation,
        int TokenInformationLength,
        out int ReturnLength
        );


    #region CreateProcessAsUser
    //        BOOL WINAPI CreateProcessAsUser(
    //  __in_opt     HANDLE hToken,
    //  __in_opt     LPCTSTR lpApplicationName,
    //  __inout_opt  LPTSTR lpCommandLine,
    //  __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
    //  __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
    //  __in         BOOL bInheritHandles,
    //  __in         DWORD dwCreationFlags,
    //  __in_opt     LPVOID lpEnvironment,
    //  __in_opt     LPCTSTR lpCurrentDirectory,
    //  __in         LPSTARTUPINFO lpStartupInfo,
    //  __out        LPPROCESS_INFORMATION lpProcessInformation);
    #endregion
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool CreateProcessAsUser(
        IntPtr Token, 
        [MarshalAs(UnmanagedType.LPTStr)] string ApplicationName,
        [MarshalAs(UnmanagedType.LPTStr)] string CommandLine,
        ref SECURITY_ATTRIBUTES ProcessAttributes, 
        ref SECURITY_ATTRIBUTES ThreadAttributes, 
        bool InheritHandles,
        uint CreationFlags, 
        IntPtr Environment, 
        [MarshalAs(UnmanagedType.LPTStr)] string CurrentDirectory, 
        ref STARTUPINFO StartupInfo, 
        out PROCESS_INFORMATION ProcessInformation);

    #region CloseHandle
    //BOOL WINAPI CloseHandle(
    //      __in          HANDLE hObject
    //        );
    #endregion
    [DllImport("Kernel32.dll")]
    extern static int CloseHandle(IntPtr handle);

    [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
    internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);

    [DllImport("advapi32.dll", SetLastError = true)]
    internal static extern bool LookupPrivilegeValue(string Host, string name, ref long pluid);

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    internal struct TokPriv1Luid
    {
        public int Count;
        public long Luid;
        public int Attr;
    }

    //static internal const int TOKEN_QUERY = 0x00000008;
    internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
    //static internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;

    internal const int TOKEN_QUERY = 0x00000008;
    internal const int TOKEN_DUPLICATE = 0x0002;
    internal const int TOKEN_ASSIGN_PRIMARY = 0x0001;

    #endregion

    [STAThread]
    static void Main(string[] args)
    {
        string username, domain, password, applicationName;
        username = args[2];
        domain = args[1];
        password = args[3];
        applicationName = @args[0];

        IntPtr token = IntPtr.Zero;
        IntPtr primaryToken = IntPtr.Zero;
        try
        {
            bool result = false;

            result = LogonUser(username, domain, password, (int)LOGON_TYPE.LOGON32_LOGON_NETWORK, (int)LOGON_PROVIDER.LOGON32_PROVIDER_DEFAULT, out token);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
            }

            string commandLine = null;

            #region security attributes
            SECURITY_ATTRIBUTES processAttributes = new SECURITY_ATTRIBUTES();

            SECURITY_DESCRIPTOR sd = new SECURITY_DESCRIPTOR();
            IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(sd));
            Marshal.StructureToPtr(sd, ptr, false);
            InitializeSecurityDescriptor(ptr, SECURITY_DESCRIPTOR_REVISION);
            sd = (SECURITY_DESCRIPTOR)Marshal.PtrToStructure(ptr, typeof(SECURITY_DESCRIPTOR));

            result = SetSecurityDescriptorDacl(ref sd, true, IntPtr.Zero, false);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
            }

            primaryToken = new IntPtr();
            result = DuplicateTokenEx(token, 0, ref processAttributes, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
            }
            processAttributes.SecurityDescriptor = ptr;
            processAttributes.Length = (uint)Marshal.SizeOf(sd);
            processAttributes.InheritHandle = true;
            #endregion

            SECURITY_ATTRIBUTES threadAttributes = new SECURITY_ATTRIBUTES();
            threadAttributes.SecurityDescriptor = IntPtr.Zero;
            threadAttributes.Length = 0;
            threadAttributes.InheritHandle = false;

            bool inheritHandles = true;
            //CreationFlags creationFlags = CreationFlags.CREATE_DEFAULT_ERROR_MODE;
            IntPtr environment = IntPtr.Zero;
            string currentDirectory = currdir;

            STARTUPINFO startupInfo = new STARTUPINFO();
            startupInfo.Desktop = "";

            PROCESS_INFORMATION processInformation;

            result = CreateProcessAsUser(primaryToken, applicationName, commandLine, ref processAttributes, ref threadAttributes, inheritHandles, 16, environment, currentDirectory, ref startupInfo, out processInformation);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
                File.AppendAllText(logfile, DateTime.Now.ToLongTimeString() + " " + winError + Environment.NewLine);
            }
        }
        catch
        {
            int winError = Marshal.GetLastWin32Error();
            File.AppendAllText(logfile, DateTime.Now.ToLongTimeString() + " " + winError + Environment.NewLine);
        }
        finally
        {
            if (token != IntPtr.Zero)
            {
                int x = CloseHandle(token);
                if (x == 0)
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                x = CloseHandle(primaryToken);
                if (x == 0)
                    throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
    }

La procédure de base est la suivante:

  1. Connectez l'utilisateur sur
  2. convertir le jeton donné en un jeton principal
  3. En utilisant ce jeton, exécutez le processus
  4. Fermez la poignée lorsque vous avez terminé.

C’est un code de développement fraîchement sorti de ma machine et nullement prêt à être utilisé dans des environnements de production. Le code ici est toujours bogué - Pour commencer: je ne suis pas sûr que les descripteurs soient fermés au bon moment, et quelques fonctions d'interopérabilité définies ci-dessus ne sont pas obligatoires. Le processus de démarrage séparé m'agace aussi beaucoup. Idéalement, j'aimerais que tous ces éléments de Job soient regroupés dans un assemblage et puissent être utilisés à partir de notre API ainsi que de ce service. Si quelqu'un a des suggestions ici, elles seraient appréciées.

23
Matt Jacobsen

Juste une supposition - utilisez-vous LoadUserProfile = true avec les informations de démarrage? CreateProcessWithLogonW ne charge pas le registre d'utilisateurs Hive par défaut, sauf indication contraire de votre part.

1
liggett78

La raison pour laquelle l'appel échoue après la première fois est très probablement due au fait qu'il utilise un descripteur de sécurité "par défaut" (quel qu'il soit).

à partir de msdn :

lpProcessAttributes [in, facultatif]

Un pointeur sur un SECURITY_ATTRIBUTES structure qui spécifie une sécurité descripteur pour le nouvel objet processus et détermine si les processus enfants peut hériter du descripteur renvoyé au fichier processus. Si lpProcessAttributes est NULL ou lpSecurityDescriptor est NULL, le processus obtient une sécurité par défaut descripteur et le descripteur ne peuvent pas être hérité. La sécurité par défaut Le descripteur est celui de l'utilisateur référencé dans le paramètre hToken . Ce descripteur de sécurité peut ne pas permettre accès pour l'appelant, auquel cas le processus ne peut pas être ouvert à nouveau après il est exécuté.Le descripteur de processus Est valide et continuera d'avoir les droits d'accès complets de }

J'imagine que CreateProcessWithLogonW crée ce descripteur de sécurité par défaut (dans tous les cas, je n'en spécifie pas). 

Il est temps de commencer Interopping ...

1
Matt Jacobsen

Je ne suggérerai ni psexec ni le planificateur de tâches. Mais avez-vous regardé Sudowin ?

Il fait presque exactement ce que vous souhaitez, à l'exception qu'il demande un mot de passe avant d'exécuter le processus. 

En outre, étant source ouverte et tout, vous pouvez voir comment il exécute et exécute des processus à partir du service associé.

1
Vinko Vrsalovic

Je viens de lire ce commentaire sur msdn ( http://msdn.Microsoft.com/en-us/library/ms682431(VS.85).aspx ):

N'appelez pas les applications utilisateur avec cela une fonction! ChristianWimmer |
Modifier | Afficher l'historique s'il vous plaît attendez Si vous appelez le mode utilisateur applications qui offrent le document édition et des choses similaires (comme Word), toutes les données non sauvegardées seront perdues. C'est parce que la séquence d'arrêt habituelle ne s'applique pas aux processus lancés avec CreateProcessWithLogonW. Dans ce façon dont les applications lancées ne le font pas Obtenez WM_QUERYENDSESSION, WM_ENDSESSION et le plus important WM_QUIT message. Donc, ils ne demandent pas de sauver les données ou nettoyer leurs trucs. Ils va juste quitter sans avertissement. Ce la fonction n'est pas conviviale et doit être utilisé avec prudence.

C'est juste une "mauvaise expérience utilisateur" . Personne ne s'y attend.

Cela pourrait expliquer ce que j'ai observé: Fonctionne la première fois. Échoue à chaque fois. Cela renforce ma conviction que quelque chose n'est pas nettoyé correctement en interne

0
Matt Jacobsen

Vous n'avez pas besoin d'un handle de fenêtre pour utiliser CreateProcessWithLogonW, je ne sais pas d'où proviennent vos informations.

L'application n'a pas réussi à initialiser l'erreur a plusieurs causes, mais elle est presque toujours liée à la sécurité ou aux ressources utilisateur épuisées. Il est extrêmement difficile de diagnostiquer cela sans beaucoup d’informations sur ce que vous exécutez et sur le contexte dans lequel vous vous exécutez. Mais voici les points à examiner: l’utilisateur fourni at-il les autorisations appropriées pour accéder au répertoire du fichier exécutable? l'utilisateur est autorisé à accéder à la station Windows et au bureau sur lequel il est lancé, dispose-t-il des autorisations adéquates sur tous les fichiers dll qu'il doit charger à l'initialisation, etc.

0
Stephen Martin

J'ai eu des problèmes similaires lorsque j'ai essayé de démarrer le binaire PhantomJS avec le "runas" -verb dans un service Windows. J'ai maintenant résolu le problème en utilisant la procédure suivante:

  • Emprunter l'identité de l'utilisateur
  • Processus de démarrage (sans interface utilisateur)
  • Imiter le dos

Vous pouvez utiliser Impersonator-Class pour l’emprunt d’emprunt d’identité. Il est également important de définir les propriétés suivantes dans ProcessStartInfo afin que l'application n'essaie pas d'accéder à l'interface utilisateur Windows:

var processStartInfo = new ProcessStartInfo()
{
    FileName = $@"{assemblyFolder}\PhantomJS\phantomjs.exe",
    Arguments = $"--webdriver={port}",
    RedirectStandardOutput = true,
    RedirectStandardError = true,
    RedirectStandardInput = true,
    UseShellExecute = false,
    CreateNoWindow = true,
    ErrorDialog = false,
    WindowStyle = ProcessWindowStyle.Hidden
};
0
Harry Berry

Vous dites que "le service Windows est démarré à l'aide des informations d'identification" administrateur "

Voulez-vous dire le compte 'Administrateur' ou un utilisateur du groupe 'Administrateurs'? Démarrer le service en tant qu'administrateur a résolu ce problème pour moi.

0
Fiona