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:
Exécutez un fichier .msi du programme d'installation qui exécute le processus msiexec en tant que compte NT AUTHORITY\SYSTEM.
Exécutez une action personnalisée pour exécuter SqlLocalDB.exe avec les commandes suivantes:
Exécutez une action C # personnalisée à l'aide de ADO.NET (System.Data.SqlConnection) pour effectuer les actions suivantes:
Data Source=(localdb)\MYINSTANCE; Integrated Security=true
Démarrez le service Windows avant la fin du programme d'installation.
Le service Windows est installé sur le compte LocalSystem et s'exécute donc également sous le compte d'utilisateur NT AUTHORITY\SYSTEM.
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.
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:
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);
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.
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.
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.
Vérifiez ici une excellente source d’informations sur SQL Server Express LocalDB:
LocalDB peut être lancé à partir d'un service, tant que le profil est chargé pour le compte de service.
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:
.mdf
et .ldf
) créés à l'emplacement prévu:Les opérations autres que le démarrage ne peuvent être effectuées que sur une instance appartenant à l'utilisateur actuellement connecté.
Choses à essayer:
"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.
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
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
Je suggère d'utiliser un compte d'utilisateur différent, et non d'utiliser le compte système, en procédant comme suit: -
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:
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
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'