Использование SQL LocalDB в службе Windows

У меня очень небольшое тестовое приложение, в котором я пытаюсь установить службу Windows и создать базу данных LocalDB во время процесса установки, а затем подключиться к этой базе данных LocalDB при запуске службы Windows.

У меня возникают огромные проблемы с подключением к экземпляру LocalDB из моей службы Windows.

Мой процесс установки выглядит примерно так:

  • Выполните файл .msi установщика, который запускает процесс msiexec в качестве учетной записи NT AUTHORITY\SYSTEM.

  • Запустите специальное действие для выполнения SqlLocalDB.exe со следующими командами:

    • sqllocaldb.exe create MYINSTANCE
    • sqllocaldb.exe доля MYINSTANCE MYINSTANCESHARE
    • sqllocaldb.exe start MYINSTANCE
  • Запустите пользовательское действие С# с помощью ADO.NET(System.Data.SqlConnection), чтобы выполнить следующие действия:

    • Подключитесь к следующей строке подключения, Data Source=(localdb)\MYINSTANCE; Integrated Security=true
    • CREATE DATABASE TestDB
    • ИСПОЛЬЗОВАНИЕ TestDB
    • CREATE TABLE...
  • Запустите службу Windows до завершения установки.

  • Служба Windows установлена ​​на учетную запись LocalSystem, а также работает как пользовательская учетная запись NT AUTHORITY\SYSTEM.

  • Служба пытается подключиться с использованием той же строки соединения, которая была использована выше.

Я постоянно получаю следующую ошибку при попытке открыть соединение с указанной выше строкой подключения из службы Windows:

System.Data.SqlClient.SqlException(0x80131904): связанный с сетью или произошла ошибка конкретного экземпляра при установлении соединения с SQL Server. Сервер не найден или не был доступен. проверить что имя экземпляра верное и что SQL Server настроен на разрешить удаленные подключения. (поставщик: сетевые интерфейсы SQL, ошибка: 50 - Произошла ошибка локальной базы данных. Указанный экземпляр LocalDB не существует.

Это разочаровывает то, что как пользовательское действие установщика msi, так и служба Windows работают под одной учетной записью пользователя Windows (я проверил, они оба NT AUTHORITY\System). Итак, почему первые работы, а вторая - не вне меня.

Я попытался изменить строки подключения, используемые в пользовательском действии, и службу Windows для использования имени общего ресурса (localdb)\.\MYINSTANCESHARE, и я получаю ту же самую ошибку из службы Windows.

Я попытался изменить учетную запись пользователя, которую служба Windows входит в учетную запись пользователя Windows, которая работает до тех пор, пока я впервые запустил команду, чтобы добавить ее в логины SQL Server для этого экземпляра.

Я также попытался запустить консольное приложение и подключиться к строке подключения имени общего ресурса, и это также работает.

Я также попытался подключиться к имени общего ресурса из SQL Server Management Studio, и это тоже работает.

Однако ни один из этих методов не решит мою проблему. Мне нужна служба Windows, потому что она запускается, как только компьютер запускается (даже если пользователь не входит в систему) и запускается независимо от того, какая учетная запись пользователя зарегистрирована.

Как служба Windows подключается к локальному экземпляру LocalDB?

Я использую SQL Server 2014 Express LocalDB.

Ответ 1

Недавно я смог решить подобную проблему в нашей установщике WiX. У нас есть служба Windows, работающая под учетной записью SYSTEM, и установщик, где хранилище на базе LocalDB является одним из параметров конфигурации базы данных. В течение некоторого времени (на пару лет на самом деле) обновления продуктов и обслуживание работали неплохо, без проблем, связанных с LocalDB. Мы используем экземпляр по умолчанию v11.0, который создается в профиле SYSTEM в дереве C:\Windows\System32\config и базе данных, указанной через AttachDbFileName, созданной в дереве ALLUSERSPROFILE. Поставщик базы данных настроен на использование проверки подлинности Windows. У нас также есть настраиваемые действия в установщике, запланированные как отложенные/не-олицетворяющие, которые выполняют обновления схемы базы данных.

Все это работало до недавнего времени. После очередной группы обновлений БД наш новый выпуск начал сбой после обновления поверх прежнего - служба не смогла запуститься, сообщив о непонятном "Связанная с сетью или конкретная ошибка экземпляра при установлении соединения с SQL Server" (ошибка 50).

При исследовании этой проблемы стало очевидно, что проблема в том, что WiX запускает пользовательские действия. Хотя незарегистрированные CA-ы работают под учетной записью SYSTEM, профиль и среда реестра остаются прежними пользователями (я подозреваю, что WiX загружает эти данные при подключении к сеансу пользователя). Это приводит к тому, что неверный путь расширяется из переменной LOCALAPPDATA - служба получает профиль SYSTEM один, но CA для обновления схемы работает с пользовательским.

Итак, вот два возможных решения. Первый простой, но слишком навязчивый для пользовательской системы - с cmd.exe, запущенным через psexec, воссоздайте сломанный экземпляр под учетной записью SYSTEM. Это не было возможным для нас, так как у пользователя могут быть другие базы данных, созданные в экземпляре v11.0, который является общедоступным. Второй вариант предполагал много рефакторинга, но ничего не повредил бы. Вот что нужно сделать, чтобы правильно выполнять обновления схемы базы данных с помощью LocalDB в WiX CA:

  • Настройте свой ЦС как отложенный/не-олицетворяющий (должен работать под учетной записью 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);
    
  • Загрузите профиль учетной записи SYSTEM. Я использовал собственные методы API LogonUser/LoadUserProfile, как показано ниже:

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

    Оберните это в класс IDisposable и используйте с инструкцией using для построения контекста.

  • Самое главное - реорганизовать свой код для выполнения необходимых обновлений БД в дочернем процессе. Это может быть простой exe-wrapper над вашей DLL-установщиком или автономной утилитой, если у вас уже есть.

P.S. Все эти трудности можно было бы избежать, если только Microsoft разрешает использовать выбор, где создавать экземпляры LocalDB, с помощью опции командной строки. Например, как и утилиты initdb/pg_ctl Postgres,

Ответ 2

Поднимаясь из комментариев по этому вопросу, рассмотрим некоторые области. Некоторые из них уже получили ответы на эти комментарии, но я документирую здесь для других, если информация может быть полезной.


  • Какая версия .Net используется? Здесь это 4.5.1 (хорошо), но более ранние версии не могли обрабатывать предпочтительную строку соединения (т.е. @"(localdb)\InstanceName"). Следующая цитата взята из ссылки, указанной выше:

    Если ваше приложение использует версию .NET до 4.0.2, вы должны напрямую подключиться к именованному каналу LocalDB.

    И согласно странице MSDN для SqlConnection.ConnectionString:

    Начиная с .NET Framework 4.5, вы также можете подключиться к базе данных LocalDB следующим образом:

    server=(localdb)\\myInstance

  • Дорожки:

    • Экземпляры: C:\Users {Windows Login}\AppData\Local\Microsoft\Microsoft SQL Server Локальная БД\Экземпляры

    • Базы данных:

      • Создано через SSMS или прямое соединение: C:\Users {Windows Login}\Documents или C:\Users {Windows Login}
      • Создано через Visual Studio: C:\Users {Windows Login}\AppData\Local\Microsoft\VisualStudio\SSDT


  • Начальная задача
    • Симптомы:
      • Файлы базы данных (.mdf и .ldf), созданные в ожидаемом месте:
        C:\Windows\System32\Config\systemprofile
      • Файлы экземпляров, созданные в непредвиденном месте:
        C:\Users\{текущий пользователь}\AppData\Local\Microsoft\Microsoft SQL Server Локальная БД\Экземпляры
    • Причина (примечание, взятое из страницы MSDN "SqlLocalDB Utility", которая связана выше, мой удар):

      Операции, отличные от запуска, могут выполняться только в экземпляре, принадлежащем зарегистрированному пользователю.


  • Что нужно попробовать:

    • Строка подключения, которая указывает базу данных (хотя, возможно, длинный снимок, если ошибка связана с невозможностью подключения к экземпляру):
    • "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"

    • Работает ли служба? Выполните следующее из командной строки:
      TASKLIST /FI "IMAGENAME eq sqlservr.exe"
      Вероятно, он должен быть указан в разделе "Консоль" для столбца "Имя сеанса"

    • Выполните следующее из командной строки:
      sqllocaldb.exe info MYINSTANCE
      И проверьте правильность значения для "Владелец". Является ли значение для "общего имени" каким оно должно быть? Если нет, в документации указано:

      Только администратор на компьютере может создать общий экземпляр LocalDB

    • В рамках настройки добавьте учетную запись NT AUTHORITY\System в качестве входа в систему, которая требуется, если эта учетная запись не отображается как "Владелец" экземпляра:
      CREATE LOGIN [NT AUTHORITY\System] FROM WINDOWS; ALTER SERVER ROLE [sysadmin] ADD MEMBER [NT AUTHORITY\System];

    • Проверьте следующие файлы/подсказки:
      C:\Users {Windows Login}\AppData\Local\Microsoft\Microsoft SQL Server Локальная БД\Экземпляры\MYINSTANCE\error.log

    • В конце вам может понадобиться создать реальную учетную запись для создания и владения экземпляром и базой данных, а также для запуска вашей службы. LocalDB действительно предназначен для пользовательского режима, и есть ли недостаток в том, что у вашего сервиса есть свой логин? И вам, вероятно, не нужно будет делиться экземпляром в этот момент.

      И на самом деле, как заметил Microsoft на SQL Server YYYY Express LocalDB, введите MSDN:

      Экземпляр LocalDB, принадлежащий встроенным учетным записям, таким как NT AUTHORITY\SYSTEM, может иметь проблемы с управлением из-за перенаправления файловой системы Windows; Вместо этого используйте обычную учетную запись Windows в качестве владельца.

ОБНОВЛЕНИЕ (2015-08-21)

На основе отзывов от OP, что использование обычной учетной записи пользователя может быть проблематичным в определенных средах, и имея в виду исходную проблему экземпляра LocalDB, созданного в папке %LOCALAPPDATA% для пользователя, запускающего установщик (и не в папке %LOCALAPPDATA% для NT AUTHORITY\System), я нашел решение, которое, как представляется, поддерживает намерение простой установки (без создания пользователя) и не требует необходимости в дополнительном коде для загрузки SYSTEM профиль.

Попробуйте использовать одну из двух встроенных учетных записей, которая не является учетной записью LocalSystem (которая не поддерживает свою собственную информацию о реестре.

Оба имеют свои папки в папке: C:\Windows\ServiceProfiles

В то время как я не смог проверить через установщик, я проверил ведение журнала службы как NT AUTHORITY\NetworkService, установив экземпляр SQL Server Express 2014 для входа в систему как эту учетную запись и перезапустил службу SQL Server. Затем я запустил следующее:

EXEC xp_cmdshell 'sqllocaldb c MyTestInstance -s';

и он создал экземпляр в: C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances

Затем я выполнил следующее:

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

и он создал базу данных в: C:\Windows\ServiceProfiles\NetworkService

Ответ 3

Я предлагаю использовать другую учетную запись пользователя, а не использовать системную учетную запись, выполнив следующие действия: -

  • создать новую учетную запись на компьютере и установить ее как учетную запись под которым работает служба Windows. Не рекомендуется использовать системную учетную запись просто для запуска приложения, во всяком случае, как разрешения чрезмерны.
  • убедитесь, что разрешения для файлов LocalDB установлены, чтобы позволить указанной учетной записи пользователя получать доступ к базе данных (и, таким образом, продолжать используйте встроенную защиту)
  • убедитесь, что он работает, пытаясь подключиться к БД (после установки) под той же учетной записью пользователя, запустив sqlcmd или Management Studio в контексте указанного пользователя, затем соединение с интегрированным Безопасность для обеспечения ее работоспособности.

Некоторые другие вещи, которые следует попробовать/рассмотреть:

  • Вы проверили журнал событий Windows для любых событий, которые могут быть полезны для диагностики?
  • Убедитесь, что если у вас есть какие-либо другие версии SQL Server (особенно до 2012 года), для инструментов командной строки,% PATH% не настроен сначала на поиск старой версии. Старые инструменты не поддерживают LocalDB.
  • Возможно также (в качестве альтернативы) настроить LocalDB для совместного использования с другими пользователями. Это предполагает совместное использование экземпляра, а затем предоставление доступа другим пользователям. См. Раздел "Проблемы с общим доступом" в этой статье: Устранение неполадок SQL Server 2012 Express LocalDB.

Там также другая статья SO, которая может содержать более полезную информацию там в ссылках внутри (изменение языка в URL с польского на английский путем изменения pl-pl en-us). В его работе используются учетные записи SQL Server, которые могут быть не совсем в вашем случае.

Это также может быть полезно, поскольку это связано с отказом разрешений безопасности и возможными разрешениями: https://dba.stackexchange.com/questions/30383/cannot-start-sqllocaldb-instance-with-my-windows-account

Ответ 4

Тревор, проблема у вас есть с пользовательскими действиями MSI. Вы должны настроить их с помощью "Impersonate = false", иначе пользовательские действия будут выполняться в контексте текущего пользователя.

Кстати, какой инструмент вы используете для создания установщика? В зависимости от используемого вами инструмента, можете ли вы предоставить скриншоты или фрагменты кода вашей настраиваемой конфигурации действий?

Принятый ответ из этого сообщения предоставит вам дополнительную информацию о различных альтернативах выполнения пользовательских действий: Запустить ExeCommand в customAction как режим администратора в установщике Wix

Здесь вы найдете дополнительную информацию о олицетворении: http://blogs.msdn.com/b/rflaming/archive/2006/09/23/768248.aspx

Ответ 5

Я бы не создал базу данных под экземпляром localdb системы. Я бы создал его под текущим пользователем, устанавливающим продукт. Это облегчит жизнь, если вам нужно удалить или управлять базой данных. Они могут сделать это через студию управления sql. В противном случае вам нужно будет использовать psexc или что-то еще, чтобы запустить процесс под учетной записью SYSTEM, чтобы управлять им.

После создания db используйте опцию share, которую вы упомянули. После этого учетная запись SYSTEM может получить доступ к базе данных через имя общего ресурса.

sqllocaldb share MSSqlLocalDb LOCAL_DB

При совместном использовании я заметил, что вам придется перезапустить локальный экземпляр db, чтобы фактически получить доступ к db через имя общего ресурса:

sqllocaldb stop MSSQLLocalDB

sqllocaldb запустить MSSQLLocalDB

Кроме того, вам может потребоваться добавить учетную запись SYSTEM в качестве устройства чтения и записи db в базу данных...

EXEC sp_addrolemember db_datareader, 'NT AUTHORITY\SYSTEM'