web-dev-qa-db-fra.com

Utilisation de SQL LocalDB dans un service Windows

J'ai une très petite application de test dans laquelle j'essaie d'installer un service Windows et de créer une base de données LocalDB pendant le processus d'installation, puis de me connecter à cette base de données LocalDB lors de l'exécution du service Windows.

Je rencontre de gros problèmes de connexion à une instance LocalDB à partir de mon service Windows.

Mon processus d'installation est exactement comme ceci:

  1. Exécutez un fichier .msi du programme d'installation qui exécute le processus msiexec en tant que compte NT AUTHORITY\SYSTEM.

  2. Exécutez une action personnalisée pour exécuter SqlLocalDB.exe avec les commandes suivantes:

    • sqllocaldb.exe crée MYINSTANCE
    • sqllocaldb.exe partager MYINSTANCE MYINSTANCESHARE
    • sqllocaldb.exe démarre MYINSTANCE
  3. Exécutez une action C # personnalisée à l'aide de ADO.NET (System.Data.SqlConnection) pour effectuer les actions suivantes:

    • Connectez-vous à la chaîne de connexion suivante, Data Source=(localdb)\MYINSTANCE; Integrated Security=true
    • CREATE DATABASE TestDB
    • UTILISER TestDB
    • CRÉER LA TABLE ...
  4. Démarrez le service Windows avant la fin du programme d'installation.

  5. Le service Windows est installé sur le compte LocalSystem et s'exécute donc également sous le compte d'utilisateur NT AUTHORITY\SYSTEM.

  6. Le service tente de se connecter en utilisant la même chaîne de connexion que celle utilisée ci-dessus.

J'obtiens systématiquement l'erreur suivante lorsque j'essaie d'ouvrir la connexion à la chaîne de connexion ci-dessus à partir du service Windows:

System.Data.SqlClient.SqlException (0x80131904): une erreur liée au réseau ou spécifique à l'instance S'est produite lors de l'établissement d'une connexion à SQL Server. Le serveur est introuvable ou inaccessible. Vérifiez Que le nom de l'instance est correct et que SQL Server est configuré pour autoriser les connexions à distance. (fournisseur: interfaces réseau SQL, erreur: 50 - Une erreur d'exécution de la base de données locale s'est produite. L'instance LocalDB spécifiée n'existe pas.

Cela est frustrant car l’action personnalisée de l’installateur msi et le service Windows sont exécutés sous le même compte utilisateur Windows (j’ai vérifié, c’est tous les deux NT AUTHORITY\System). Alors, pourquoi le premier fonctionne et le second ne me dépasse pas.

J'ai essayé de modifier les chaînes de connexion utilisées dans l'action personnalisée et le service Windows pour qu'il utilise le nom de partage (localdb)\.\MYINSTANCESHARE et je reçois exactement la même erreur du service Windows.

J'ai essayé de modifier le compte d'utilisateur ouvert par le service Windows sur mon compte d'utilisateur Windows, qui fonctionne tant que j'exécute pour la première fois une commande pour l'ajouter aux informations de connexion au serveur SQL de cette instance.

J'ai également essayé d'exécuter une application console et de me connecter à la chaîne de connexion du nom de partage, ce qui fonctionne également.

J'ai également essayé de me connecter au nom de partage de SQL Server Management Studio et cela fonctionne également.

Cependant, aucune de ces méthodes ne résout vraiment mon problème. J'ai besoin d'un service Windows car il démarre dès que l'ordinateur démarre (même si aucun utilisateur ne se connecte) et démarre quel que soit le compte d'utilisateur connecté.

Comment un service Windows se connecte-t-il à une instance privée LocalDB?

J'utilise SQL Server 2014 Express LocalDB.

13
Trevor Elliott

J'ai pu résoudre un problème similaire dans notre programme d'installation WiX récemment. Nous avons un service Windows fonctionnant sous un compte SYSTEM et un programme d'installation, où le stockage basé sur LocalDB est l'une des options pour la configuration de la base de données. Pendant un certain temps (quelques années en fait), les mises à niveau et le service des produits ont fonctionné sans problème, sans aucun problème lié à LocalDB. Nous utilisons l'instance par défaut v11.0, créée dans le profil SYSTEM dans l'arborescence C:\Windows\System32\config, ainsi qu'une base de données spécifiée via AttachDbFileName, créée dans l'arborescence ALLUSERSPROFILE. Le fournisseur de base de données est configuré pour utiliser l'authentification Windows. Nous avons également une action personnalisée dans le programme d’installation, planifiée comme différée/non empruntée, qui exécute des mises à jour de schéma de base de données.

Tout cela a bien fonctionné jusqu'à récemment. Après une série de mises à jour de bases de données, notre nouvelle version a commencé à échouer après une mise à niveau - le service n'a pas pu démarrer, signalant ainsi une erreur notoire "Une erreur liée au réseau ou spécifique à une instance s'est produite lors de l'établissement d'une connexion à SQL Server" (erreur 50 ) faute.

Lors de l'examen de ce problème, il est devenu évident qu'il s'agissait d'une manière dont WiX exécute des actions personnalisées. Bien que les autorités de certification non empruntées s'exécutent sous un compte système, le profil de registre et l'environnement restent ceux de l'utilisateur actuel (je suppose que WiX les charge de manière volontaire lors de la connexion à la session de l'utilisateur). Cela entraîne l’extension du chemin incorrect à partir de la variable LOCALAPPDATA: le service reçoit le profil SYSTEM, mais l’autorité de certification de mise à jour du schéma fonctionne avec celui de l’utilisateur.

Donc, voici deux solutions possibles. Le premier est simple, mais trop intrusif pour le système de l'utilisateur - avec cmd.exe démarré via psexec, recréez une instance interrompue sous le compte SYSTEM. Ce n'était pas une option pour nous car l'utilisateur peut avoir d'autres bases de données créées dans l'instance v11.0, qui est publique. La deuxième option supposait beaucoup de refactoring, mais ne ferait pas de mal. Voici comment procéder pour exécuter correctement les mises à jour de schéma de base de données avec LocalDB dans WiX CA:

  1. Configurez votre autorité de certification comme différée/non imitée (elle doit s'exécuter sous un compte SYSTEM);
  2. Corrigez l'environnement pour qu'il pointe vers les chemins de profil SYSTEM:

    var systemRoot = Environment.GetEnvironmentVariable("SystemRoot");
    Environment.SetEnvironmentVariable("USERPROFILE", String.Format(@"{0}\System32\config\systemprofile", systemRoot));
    Environment.SetEnvironmentVariable("APPDATA", String.Format(@"{0}\System32\config\systemprofile\AppData\Roaming", systemRoot));
    Environment.SetEnvironmentVariable("LOCALAPPDATA", String.Format(@"{0}\System32\config\systemprofile\AppData\Local", systemRoot));
    Environment.SetEnvironmentVariable("HOMEPATH", String.Empty);
    Environment.SetEnvironmentVariable("USERNAME", Environment.UserName);
    
  3. Charger le profil de compte SYSTEM. J'ai utilisé les méthodes LogonUser/LoadUserProfile de l'API native, comme suit:

    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool LogonUser(
        string lpszUserName,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        ref IntPtr phToken);
    
    [StructLayout(LayoutKind.Sequential)]
    struct PROFILEINFO
    {
        public int dwSize; 
        public int dwFlags;
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpUserName; 
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpProfilePath; 
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpDefaultPath; 
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpServerName; 
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpPolicyPath; 
        public IntPtr hProfile; 
    }
    
    [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo);
    
    var hToken = IntPtr.Zero;
    var hProfile = IntPtr.Zero;
    bool result = LogonUser("SYSTEM", "NT AUTHORITY", String.Empty, 3 /* LOGON32_LOGON_SERVICE */, 0 /* LOGON32_PROVIDER_DEFAULT */, ref token);
    if (result)
    {
        var profileInfo = new PROFILEINFO();
        profileInfo.dwSize = Marshal.SizeOf(profileInfo);
        profileInfo.lpUserName = @"NT AUTHORITY\SYSTEM";
        if (LoadUserProfile(token, ref profileInfo))
            hProfile = profileInfo.hProfile;
    }
    

    Emballez ceci dans une classe IDisposable et utilisez-le avec une instruction using pour créer un contexte.

  4. Le plus important - refactoriser votre code pour effectuer les mises à jour de base de données nécessaires dans un processus enfant. Cela pourrait être un simple wrapper exe sur votre DLL d'installation ou un utilitaire autonome, si vous en avez déjà un.

P.S. Toutes ces difficultés pourraient être évitées si Microsoft laissait les utilisateurs choisir le lieu de création des instances LocalDB, via l’option de ligne de commande. Comme les utilitaires initdb/pg_ctl de Postgres, par exemple.

3
denis.gz

En reprenant les commentaires sur la question, voici quelques domaines à examiner. On a déjà répondu à certaines de ces questions dans ces commentaires, mais je documente ici pour d'autres au cas où les informations pourraient être utiles.


  • Quelle version de .Net est utilisée? Ici, il s’agit de la version 4.5.1 (bonne), mais les versions antérieures ne pouvaient pas gérer la chaîne de connexion préférée (c'est-à-dire @"(localdb)\InstanceName"). La citation suivante est tirée du lien indiqué ci-dessus:

    Si votre application utilise une version de .NET antérieure à la version 4.0.2, vous devez vous connecter directement au canal nommé de LocalDB.

    Et selon la page MSDN pour SqlConnection.ConnectionString :

    À partir de .NET Framework 4.5, vous pouvez également vous connecter à une base de données LocalDB comme suit:

    server=(localdb)\\myInstance

  • Chemins:

    • Instances: C:\Utilisateurs {Connexion Windows}\AppData\Local\Microsoft\Base de données locale SQL Server Microsoft\Instances

    • Bases de données:

      • Créé via SSMS ou connexion directe: C:\Utilisateurs {Connexion Windows}\Documents ou C:\Utilisateurs {Connexion Windows}
      • Créé via Visual Studio: C:\Utilisateurs {Connexion Windows}\AppData\Local\Microsoft\VisualStudio\SSDT


  • Problème initial
    • Symptômes:
      • Fichiers de base de données (.mdf et .ldf) créés à l'emplacement prévu:
        C:\Windows\System32\config\systemprofile
      • Fichiers d'instance créés dans un emplacement inattendu:
        C:\Utilisateurs\{utilisateur actuel}\AppData\Local\Microsoft\Microsoft Base de données locale SQL Server \]
    • Cause (remarque tirée de la page MSDN «SqlLocalDB Utility» (liée ci-dessus); italiques, mien):

      Les opérations autres que le démarrage ne peuvent être effectuées que sur une instance appartenant à l'utilisateur actuellement connecté.


  • Choses à essayer:

    • Chaîne de connexion qui spécifie la base de données (mais peut-être longue si l'erreur concerne l'impossibilité de se connecter à l'instance):
    • "Server=(LocalDB)\MYINSTANCE; Integrated Security=true ;AttachDbFileName=C:\Windows\System32\config\systemprofile\TestDB.mdf"
    • "Server=(LocalDB)\.\MYINSTANCESHARE; Integrated Security=true ;AttachDbFileName=C:\Windows\System32\config\systemprofile\TestDB.mdf"

    • Le service est-il actif? Exécutez ce qui suit à partir d'une invite de commande:
      TASKLIST /FI "IMAGENAME eq sqlservr.exe"
      Il devrait probablement figurer sous "Console" pour la colonne "Nom de session"

    • Exécutez ce qui suit à partir d'une invite de commande:
      sqllocaldb.exe info MYINSTANCE
      Et vérifiez que la valeur de "Propriétaire" est correcte. La valeur pour "Nom partagé" est-elle ce qu'elle devrait être? Sinon, la documentation indique: 

      Seul un administrateur sur l'ordinateur peut créer une instance partagée de LocalDB.

    • Dans le cadre de la configuration, ajoutez le compte NT AUTHORITY\System en tant que connexion au système, ce qui est requis si ce compte ne s'affiche pas en tant que "propriétaire" de l'instance:
      CREATE LOGIN [NT AUTHORITY\System] FROM WINDOWS; ALTER SERVER ROLE [sysadmin] ADD MEMBER [NT AUTHORITY\System];

    • Vérifiez le fichier suivant pour des indices/détails:
      C:\Utilisateurs {Connexion Windows}\AppData\Local\Microsoft\Microsoft Base de données locale SQL Server Microsoft\Instances\MYINSTANCE\error.log

    • En fin de compte, vous devrez peut-être créer un compte réel pour créer et posséder l'instance et la base de données, ainsi que pour exécuter votre service. LocalDB est vraiment destiné à être en mode utilisateur, et y at-il des inconvénients à ce que votre service ait son propre identifiant? Et vous n'auriez probablement pas besoin de partager l'instance à ce stade.

      Et en fait, comme l'a noté Microsoft sur la page SQL Server YYYY Express LocalDB MSDN:

      Une instance de LocalDB appartenant aux comptes intégrés tels que NT AUTHORITY\SYSTEM peut poser des problèmes de gestion en raison de la redirection du système de fichiers Windows; Utilisez plutôt un compte Windows normal en tant que propriétaire.

MISE À JOUR (2015-08-21)

D'après les informations communiquées par l'OP selon lesquelles l'utilisation d'un compte utilisateur normal peut poser problème dans certains environnements, ET en gardant à l'esprit le problème d'origine de l'instance LocalDB créée dans le dossier %LOCALAPPDATA% pour l'utilisateur exécutant le programme d'installation (et non le dossier %LOCALAPPDATA% pour NT AUTHORITY\System), j'ai trouvé une solution qui semble conserver son objectif d'installation facile (aucun utilisateur à créer) et qui ne devrait pas nécessiter de code supplémentaire pour charger le profil SYSTEM.

Essayez d’utiliser l’un des deux comptes intégrés qui est non le LocalSystem account (qui ne conserve pas ses propres informations de registre. Utilisez soit:

Les deux dossiers ont leur dossier de profil dans: C:\Windows\ServiceProfiles

Bien que je n'ai pas pu tester via un programme d'installation, j'ai testé un service se connectant sous le nom NT AUTHORITY\NetworkService en configurant mon instance SQL Server Express 2014 pour qu'elle se connecte en tant que compte et redémarré le service SQL Server. J'ai ensuite couru ce qui suit:

EXEC xp_cmdshell 'sqllocaldb c MyTestInstance -s';

et il a créé l'instance dans: C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Microsoft\Microsoft Base de données locale du serveur SQL\Microsoft

J'ai ensuite couru ce qui suit:

EXEC xp_cmdshell N'SQLCMD -S (localdb)\MyTestInstance -E -Q "CREATE DATABASE [MyTestDB];"';

et il avait créé la base de données dans: C:\Windows\ServiceProfiles\NetworkService

13
Solomon Rutzky

Trevor, le problème que vous avez est lié aux actions personnalisées MSI. Vous devez les configurer avec "Impersonate = false", sinon les actions personnalisées seront exécutées dans le contexte utilisateur actuel.

BTW quel outil utilisez-vous pour créer le programme d'installation? Selon l’outil utilisé, pourriez-vous fournir des captures d’écran ou des extraits de code de votre configuration d’actions personnalisées? 

La réponse acceptée de cet article vous donnera des informations supplémentaires sur les différentes alternatives d’exécution d’actions personnalisées: Exécuter ExeCommand dans customAction en mode Administrateur dans Wix Installer

Vous trouverez des informations supplémentaires sur l'emprunt d'identité ici: http://blogs.msdn.com/b/rflaming/archive/2006/09/23/768248.aspx

0
Rolo

Je suggère d'utiliser un compte d'utilisateur différent, et non d'utiliser le compte système, en procédant comme suit: -

  • créez un nouveau compte sur la machine et définissez-le comme le compte sous lequel le service Windows s'exécute. Ce n'est pas une bonne pratique d'utiliser Le compte système uniquement pour exécuter une application, de toute façon, car les autorisations Sont excessives.
  • assurez-vous que les autorisations sur les fichiers LocalDB sont définies pour permettre audit compte d'utilisateur d'accéder à la base de données (et ainsi continuer à utiliser la sécurité intégrée)
  • assurez-vous que cela fonctionne en essayant de vous connecter à la base de données (une fois installée) sous le même compte utilisateur en exécutant sqlcmd ou Management Studio dans le contexte dudit utilisateur, puis en vous connectant à Integrated Security à assurez-vous que cela fonctionne.

Quelques autres choses à essayer/à considérer:

  • avez-vous vérifié dans le journal des événements Windows des événements pouvant être utiles à des fins de diagnostic?
  • Assurez-vous que, si vous avez d'autres versions de SQL Server (en particulier avant 2012), pour les outils de ligne de commande,% PATH% n'est pas configuré pour rechercher une version d'outils plus ancienne en premier. Les outils plus anciens ne supportent pas LocalDB.
  • Il est également possible (au lieu de cela) de configurer LocalDB pour le partager avec d'autres utilisateurs. Cela implique de partager l'instance, puis d'accorder l'accès à d'autres utilisateurs. Consultez la section "Problèmes de partage" de cet article: Résoudre les problèmes liés à SQL Server 2012 Express LocalDB .

Il y a aussi un autre SO article qui peut contenir des informations plus utiles dans les liens qu'il contient (changez la langue de l'URL du polonais en anglais en remplaçant pl-pl en en-us). Sa solution consiste à utiliser des comptes SQL Server, ce qui peut ne pas être correct dans votre cas. 

Cela peut également être utile car il concerne le refus d'autorisations de sécurité et les solutions possibles: https://dba.stackexchange.com/questions/30383/cannot-start-sqllocaldb-instance-with-my-windows-account

0
CJBS

Je ne créerais pas la base de données sous l'instance localdb du système. Je le créerais sous l'utilisateur actuel installant le produit. Cela facilitera grandement la vie si vous devez supprimer ou gérer la base de données. Ils peuvent le faire via un studio de gestion SQL. Sinon, vous devrez utiliser psexc ou autre chose pour lancer un processus sous le compte SYSTEM afin de le gérer.

Une fois la base de données créée, utilisez l'option de partage que vous avez mentionnée. Le compte SYSTEM peut ensuite accéder à la base de données via le nom de partage.

partage sqllocaldb MSSqlLocalDb LOCAL_DB

Lors du partage, j'ai remarqué que vous deviez redémarrer l'instance de la base de données locale pour pouvoir accéder à la base de données via le nom de partage:

sqllocaldb stop MSSQLLocalDB

sqllocaldb démarre MSSQLLocalDB

En outre, vous devrez peut-être ajouter le compte SYSTEM en tant que lecteur et rédacteur de base de données à la base de données ...

EXEC sp_addrolemember db_datareader, 'NT AUTHORITY\SYSTEM'

0
Lars Fiedler