Сетевые диски и UNC-пути: типичные ловушки ── как бизнес-приложению работать с файловым сервером (общей папкой)

· Обновлено: · · Сетевой диск, UNC-путь, SMB, Общий доступ к файлам, Служба Windows, Файловый сервер, C#, .NET, Сеть, Расследование сбоев, Разработка Windows, Техническая консультация

История изменений (1 обновлений, последнее 30 Aug 2026)

Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.

Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
Первая публикация
Цитирование статьи(DOI (зарегистрированный архив): 10.5281/zenodo.21620042)

Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.

Го Комура (2026). Сетевые диски и UNC-пути: типичные ловушки ── как бизнес-приложению работать с файловым сервером (общей папкой). KomuraSoft LLC. https://comcomponent.com/ru/blog/network-share-unc-path-pitfalls/

DOI (зарегистрированный архив)
10.5281/zenodo.21620042
DOI (последняя зарегистрированная версия)
10.5281/zenodo.21620043

«На машине разработчика всё работало, а у заказчика — „Z:\ не найден“». «Стоит поставить задачу в Планировщик заданий — и запись в общую папку начинает падать». «Перевели приложение в службу Windows — и файловый сервер пропал». В консультациях по бизнес-приложениям сбои вокруг файлового сервера (общей папки) — едва ли не самый частый класс проблем. Приём данных заказов, выгрузка отчётов и CSV, наблюдение за файлами, которые выдаёт оборудование: в локальных (on-premises) бизнес-системах общая папка до сих пор живая шина интеграции.

Сложность в том, что большинство таких сбоев — не «баг в коде», а следствие устройства Windows: как буква диска связана с сеансом входа, как учётная запись запуска службы проходит аутентификацию, как SMB управляет соединениями. Отладчик до причины не доводит, а фраза «на моём ПК не воспроизводится» съедает дни.

Статья рассчитана на разработчиков бизнес-приложений (WinForms/WPF/служба Windows), которым нужно писать, принимать и отслеживать файлы в общей папке. Разберём ловушки сетевых дисков и UNC-путей на уровне устройства системы и сведём рабочие правила в таблицу решений.

Предполагаемая среда

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

Что Предположение
ОС Windows 10 / 11, Windows Server (актуальные поддерживаемые версии)
Протокол общего доступа SMB (стандартный файловый обмен Windows)
Инфраструктура аутентификации В основном среда домена Active Directory. Если домена нет (рабочая группа) или NAS со своими учётными записями — каждый раз отдельно оговариваем: «в рабочей группе»
Как запускается приложение настольное приложение (WinForms / WPF), служба Windows, консольное приложение из Планировщика заданий
Примеры кода C# / .NET 6 и новее (используются File.WriteAllBytesAsync и сопоставление с образцом is ... or ...)

Самая важная развилка в этой статье — домен это или рабочая группа. Варианты учётной записи запуска в главе 3 (учётная запись компьютера и gMSA) имеют смысл только при наличии домена. Без домена вы смещаетесь к главе 4: передавать учётные данные явно. Сначала выясните, что у вас, и уже потом читайте дальше.

Сокращения в этой статье

Сокращение Чтение / полное имя Коротко
UNC-путь Universal Naming Convention (универсальное соглашение об именовании) Формат \\имя_сервера\имя_ресурса\.... Настоящий адрес общей папки
SMB Server Message Block Протокол, которым Windows обменивается файлами по сети
Сеанс входа Контекст выполнения, который ОС создаёт при входе пользователя или при запуске службы. Буквы дисков ведутся в этом контексте (глава 2)
Kerberos Стандартный протокол аутентификации в домене Active Directory. Стороны обмениваются билетами, чтобы подтвердить личность
SPN Service Principal Name (имя субъекта-службы) Имя, которым Kerberos однозначно указывает целевую службу. Клиент собирает SPN из имени узла и запрашивает билет (глава 4)
gMSA group Managed Service Account (групповая управляемая учётная запись службы) Доменная учётная запись службы, пароль которой ОС сама генерирует и обновляет (глава 3)1

1. Сначала выводы

  • Буква диска (Z: и подобные) принадлежит не всей системе, а сеансу входа. Каждому сеансу выделяется свой полный набор букв от A до Z, поэтому диск, который подключил пользователь, не виден процессу другого пользователя и службе в другом сеансе входа.2
  • Если для администратора включён UAC, создаются два сеанса входа — обычный и с повышением прав, — и подключения дисков (символические ссылки DosDevices) у каждого сеанса свои. Отсюда симптом «Z: не видно только из приложения, запущенного от имени администратора». Параметр реестра EnableLinkedConnections делает подключение общим для обоих сеансов, но Microsoft прямо пишет, что это неподдерживаемая настройка, которая «может снизить безопасность системы».34
  • Когда служба (или процесс в другом контексте безопасности) обращается к удалённому ресурсу, официальная рекомендация — UNC-путь (\\server\share\...). Подключать букву диска изнутри службы через net use или функции WNet не рекомендуется: есть риск утечки учётных данных и взаимного влияния служб.2
  • Кому выдавать права на стороне общего ресурса, определяет учётная запись запуска службы. LocalSystem и NetworkService в сети аутентифицируются как учётные данные компьютера, LocalService предъявляет анонимные учётные данные и для доступа к общим ресурсам не годится. Служба под локальной учётной записью пользователя к сетевым ресурсам обратиться не может. На практике основной выбор — доменная учётная запись или gMSA (group Managed Service Account, групповая управляемая учётная запись службы), у которой паролем управляет ОС. gMSA, однако, предполагает домен и подготовленный корневой ключ KDS (глава 3).567819
  • К одному серверу нельзя одновременно держать соединения под разными учётными данными. Второе соединение падает с ошибкой 1219 (ERROR_SESSION_CREDENTIAL_CONFLICT), и это поведение by design.1011
  • «Медленно, рвётся, иногда отсутствует» — штатное поведение общей папки, а не исключение. Простаивающее соединение сервер по умолчанию разрывает через 15 минут (при следующем обращении оно поднимается снова). File.Exists при нехватке прав или другой ошибке возвращает false без исключения, поэтому не отличает «файла нет» от «сервер недоступен».1213
  • FileSystemWatcher умеет наблюдать за сетевым диском и удалённым компьютером, но проектировать нужно с расчётом на потерю событий. Буфер может переполниться, и события пропадут; при наблюдении по сети верхний предел внутреннего буфера — 64 КБ. Совмещение с опросом (полным сканированием) — обычная практика.14

На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 31, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle

2. Буква диска и UNC-путь ── Z: принадлежит «вашему сеансу входа»

Когда в Проводнике подключаешь \\fileserver\share к Z:, кажется, будто на всей машине появился «диск Z:». Это первое заблуждение.

Официальная документация формулирует это однозначно. Буква диска не глобальна для системы: каждому сеансу входа выделяется свой набор от A до Z. Перенаправленный диск (сетевой диск) нельзя разделить между процессами под разными учётными записями, а служба в другом сеансе входа не может обратиться к букве, установленной в чужом сеансе.2 Система ведёт подключения дисков по logon SID, который однозначно идентифицирует сеанс входа.2

Иными словами, Z: — это лишь «сокращение пути, по одному набору на сеанс входа»; за ним всегда стоит UNC-путь. На схеме это выглядит так.

Сеанс входа B ── служба / Планировщик заданийСеанс входа A ── интерактивный вход пользователядоступен как Z:Z: нет. По UNC-пути доступенСлужба Windowsзапланированный пакетный запускНабор букв дисков этого сеансаZ: не назначенаПроводникприложения, запущенные с рабочего столаНабор букв дисков этого сеансаZ: уже подключён к общему ресурсуРеальный ресурс общей папкиUNC-путь ── share на fileserver01

Рис. 1. Буквы дисков — по одному набору на сеанс входа, а реальный UNC-путь один.

Важно: это разделение не потому, что «пользователь другой». Даже если службу настроить на ту же учётную запись, что и сеанс A, система создаёт для службы новый сеанс входа, и подключение из сеанса A туда не переносится.2 Типичная жалоба «у службы тот же пользователь, что у меня, а Z: всё равно не находится» как раз из этой путаницы.

Отсюда цепочкой объясняются симптомы, которые на практике встречаются снова и снова.

Симптом Причина на уровне устройства
Запустили под другим пользователем — Z: нет Буква диска привязана к сеансу входа; чужое подключение не видно2
Запустили «от имени администратора» — Z: нет UAC создаёт два сеанса входа — обычный и с повышением прав, — и подключения между ними не общие3
Перенесли в Планировщик заданий / службу — Z: нет Выполнение идёт в другом сеансе входа. Даже если служба настроена на учётную запись пользователя, система всё равно создаёт для неё новый сеанс входа2

Историю с UAC стоит разобрать чуть подробнее. Когда пользователь из группы администраторов входит в систему, система создаёт два связанных сеанса входа: один с ограниченным токеном, другой — с полным токеном администратора. Само подключение диска — объект символической ссылки (DosDevices), который связывает букву с UNC-путём; объект принадлежит конкретному сеансу входа и между сеансами не разделяется.3 Сценарии входа выполняются на стороне обычных прав, поэтому из процесса с повышением прав это подключение просто не видно.

Если установить EnableLinkedConnections (значение DWORD в разделе HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System) в 1, символическая ссылка записывается в оба связанных сеанса, и симптом исчезает. Официальная документация при этом прямо предупреждает: «этот обходной путь может снизить безопасность системы. Microsoft его не поддерживает. Используйте на свой риск».4 Закладывать в окружение заказчика неподдерживаемую настройку реестра — плохое решение для бизнес-приложения. Правильный путь — хранить в приложении UNC-пути. В официальном разборе неполадок перенаправления папок тоже сказано: «всегда используйте UNC-путь, а не букву диска».4

В коде это просто: достаточно держать пути в конфигурации как UNC.

// appsettings.json ── храним UNC, а не букву диска
{
  "FileTransfer": {
    "IncomingDir": "\\\\fileserver01\\edi\\incoming",
    "ProcessedDir": "\\\\fileserver01\\edi\\processed"
  }
}

Если приложение позволяет выбрать в диалоге папку под Z:, нормализуйте путь в UNC при сохранении — тогда смена контекста выполнения позже ничего не сломает. Для преобразования пути с буквой диска в UNC есть API WNetGetUniversalName.15

3. Доступ к общей папке из службы Windows ── UNC обязателен, и «от чьего имени» идёт доступ

Как показано в предыдущей главе, подключённый диск из службы недоступен. Официальная документация требует, чтобы служба (и любой процесс в другом контексте безопасности) ходила к удалённым ресурсам по UNC-имени, и прямо не рекомендует реализацию, в которой служба во время работы подключает букву диска через net use или функции WNet. Названные причины: подключение становится видно другим службам в том же контексте; учётные данные, переданные в net use, могут утечь за границы службы; несколько служб, которые пытаются установить одно и то же подключение, мешают друг другу ошибкой «уже подключено».2

Как только путь стал UNC, следующий вопрос — аутентификация. От чьего имени служба обращается к файловому серверу? Это определяет учётная запись запуска, и она же определяет, кому выдавать права на стороне общего ресурса.

Учётная запись запуска Сетевая идентичность Права на стороне общего ресурса Оценка
LocalSystem Учётные данные компьютера5 В домене — разрешения общего доступа и NTFS учётной записи компьютера (DOMAIN\MACHINE$) Работает, но права избыточны. После замены машины права нужно выдавать заново
NetworkService Учётные данные компьютера6 То же Локальные права минимальны, сетевая идентичность та же, что у LocalSystem
LocalService Анонимные учётные данные7 Выдать некому Для доступа к общим ресурсам не годится
Локальный пользователь К сетевым ресурсам обратиться нельзя8
Доменный пользователь Эта учётная запись Права выдаются этой учётной записи Практический стандарт. Слабое место — смена пароля в эксплуатации
gMSA Эта учётная запись Права выдаются этой учётной записи Паролем управляет ОС (автоматическая смена каждые 30 дней). Основной вариант там, где среда это позволяет116

То, что LocalSystem и NetworkService выходят в сеть «от имени компьютера», официально задокументировано.56 В документации BITS из этого сразу следует практический вывод: если ACL исходного файла ограничивает доступ учётной записью пользователя, служба (которая аутентифицируется учётными данными компьютера) получит отказ в доступе; системным учётным записям не следует пользоваться подключёнными дисками.17

Отсюда три рабочих правила:

  • Первый шаг при «после перевода в службу доступ пропал» — проверить учётную запись запуска. На рабочем столе проверка шла под «вашими» правами, в службе — под идентичностью из таблицы выше. И разрешения общего доступа, и ACL NTFS нужно смотреть именно для этой идентичности.
  • Для долгой эксплуатации в домене учётной записью запуска должна быть доменная учётная запись или gMSA. У gMSA пароль — случайные 240 байт, ОС меняет его каждые 30 дней. Инциденты вроде «в понедельник утром всё встало из-за истёкшего пароля учётной записи службы» таким образом снимаются конструктивно.16
  • В рабочей группе (без домена) аутентификация учётными данными компьютера не складывается, поэтому в ход идут явные учётные данные из следующей главы.

У gMSA есть предпосылки. Выше сказано «основной вариант», но это не то, что включают в тот же день, когда идея пришла в голову. gMSA изначально расширяет возможности sMSA (standalone Managed Service Account, изолированной управляемой учётной записи службы) с одной машины на несколько серверов: это доменная учётная запись с автоматическим управлением паролем и упрощённым управлением SPN.1 Перед внедрением проверьте следующее.

Предпосылка Суть
Домен Active Directory gMSA — доменная учётная запись. В рабочей группе её нет. На Windows старше Windows Server 2012 она тоже не применяется1
Корневой ключ KDS Контроллерам домена для генерации пароля gMSA нужен корневой ключ Key Distribution Service (KDS). В лесу его создают один раз командой Add-KdsRootKey -EffectiveImmediately9
Ожидание после создания До 10 часов после создания корневого ключа gMSA создать нельзя: должна сойтись репликация AD на всех контроллерах домена. Это защита, чтобы пароль не сгенерировали, пока все DC ещё не готовы отвечать на запросы gMSA. «Сразу после создания пользоваться нельзя» — так задумано9
Управляющая станция Команды Windows PowerShell для управления gMSA требуют 64-разрядную архитектуру19
Типы шифрования gMSA зависит от поддерживаемых типов шифрования Kerberos. Если на узле отключён RC4 и нет атрибута msDS-SupportedEncryptionTypes, аутентификация всегда падает; для MSA рекомендуется всегда настраивать AES1

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

Как строить саму службу (учётная запись запуска, параметры восстановления, безопасная остановка) — в статье «Как создавать и эксплуатировать службу Windows». Как устроены сеансы и вход — в «Как понимать изоляцию сеансов Windows».

4. Учётные данные ── net use, Диспетчер учётных данных и ошибка 1219

Если самой учётной записи запуска нельзя выдать права на стороне общего ресурса (рабочая группа, NAS со своей системой учётных записей и т. п.), приходится подключаться, явно передавая учётные данные. Основных способов три.

  • net use \\server\share /user:... ── устанавливает соединение в данном сеансе входа. Удобно для интерактивной проверки, но, как уже сказано, запуск изнутри службы не рекомендуется.2
  • Диспетчер учётных данных (cmdkey) ── cmdkey /add:server /user:svc-file /pass:... сохраняет учётные данные, и дальше при аутентификации к этому серверу они подставляются сами.1819 Хранение привязано к профилю пользователя, поэтому для службы регистрировать их нужно в контексте учётной записи запуска службы — это типичная ловушка.
  • Подключение из программы (WNetAddConnection2) ── устанавливает соединение с сетевым ресурсом, явно указывая учётные данные клиента. Официальная документация называет это одной из стратегий доступа серверного процесса к сетевым ресурсам.20 Если сделать «подключение без устройства» (deviceless), не назначая букву диска, доступ остаётся по UNC-пути.

И самая известная ловушка в этой области — ошибка 1219.

Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. (ERROR_SESSION_CREDENTIAL_CONFLICT, 1219)11

Из одного сеанса входа нельзя установить к одному серверу несколько соединений под разными именами пользователей. Если к одному файловому серверу попытаться подключиться двумя наборами учётных данных — скажем, к отделу продаж под своей учётной записью, а к ресурсу для интеграции систем под выделенной — второе соединение упадёт с 1219. Официальная документация прямо называет это «by design», а обходные пути — «подключаться по IP-адресу» и «создать отдельный DNS-псевдоним», то есть сделать вид, что это другой сервер.10

На практике первый выбор — вообще не строить схему с несколькими наборами учётных данных на один сервер: свести всё к одной учётной записи интеграции и выдать ей права на все нужные общие ресурсы. Только если это невозможно по обстоятельствам, разделяйте пути псевдонимом (alias).

Псевдоним — это не «добавить одну запись CNAME в DNS, и готово». В среде с Kerberos SMB-клиент пытается аутентифицироваться по SPN (имени субъекта-службы), соответствующему имени цели, поэтому если для псевдонима не зарегистрирован SPN, обращение через CNAME может упасть уже на этом шаге. Официальный разбор неполадок тоже называет отсутствие SPN одной из причин и рекомендует задавать псевдоним не через CNAME в DNS, а как альтернативное имя компьютера командой netdom computername <имя_сервера> /add:<псевдоним>.21 Прежде чем закладывать подключение через псевдоним в архитектуру как обход 1219, обязательно проверьте его в целевой среде.

Отдельный, более сложный случай: «служба должна ходить на файловый сервер с правами того клиентского пользователя, который к ней подключился». Это уже не повторное использование учётных данных, а олицетворение (impersonation). Официальная документация тоже рекомендует олицетворение вместо того, чтобы служба держала собственные учётные данные.2 Как правильно писать олицетворение и какие дополнительные оговорки появляются на удалённых ресурсах — в статье «Как правильно работать с токенами олицетворения Windows».

Диагностика своей среды ── какие команды бить первыми

Всё выше становится практикой только тогда, когда состояние можно посмотреть в среде, где симптом уже есть. Точка старта разбора: от чьего имени работает этот процесс и какие соединения у него есть. Следующие три команды выполняйте в том же контексте выполнения, в котором проявляется симптом.

Команда Что показывает На что смотреть
whoami От чьего имени вы сейчас работаете (формат домен\пользователь) Совпадает ли это с ожидаемой учётной записью запуска. Для службы — какая строка таблицы из главы 3
net use (без аргументов) Список сетевых соединений этого сеанса входа22 Есть ли в списке Z:, которого «не должно быть». Если нет — «в этом сеансе его нет» уже факт
net use \\fileserver01\share Состояние указанного соединения22 Само соединение не устанавливается или оно есть, но права уже режут доступ

Главное — выполнять в том же контексте. net use с вашего рабочего стола ничего не говорит о соединениях службы. Чтобы посмотреть от учётной записи запуска службы, временно зарегистрируйте в Планировщике заданий задачу с той же учётной записью и теми же правами, например cmd /c "whoami > C:\temp\who.txt & net use >> C:\temp\who.txt", и смотрите выходной файл.

Как воспроизвести ошибку 1219 у себя

1219 быстрее «садится» в голове, если один раз получить её самому, а не только прочитать описание. На машине в домене подключитесь к двум общим ресурсам одного файлового сервера под разными учётными данными.

:: 1) Подключаемся к первому ресурсу под своей учётной записью (пароль * — ввод с приглашения)
net use \\fileserver01\share1 * /user:CONTOSO\tanaka

:: 2) Пытаемся подключить другой ресурс того же сервера под другой учётной записью
net use \\fileserver01\share2 * /user:CONTOSO\svc-file
::    → здесь вернётся процитированная выше ошибка 1219, соединение не установится

:: Уборка: рвём все сетевые соединения этого сеанса
net use * /delete

Суть: даже если ресурсы разные, при одном сервере будет конфликт. Что первое — share1, а второе — share2, не важно. Ограничение действует на уровне сервера. Наоборот, если сначала разорвать первое соединение net use \\fileserver01\share1 /delete и потом установить второе, оно пройдёт — то есть в зависимости от порядка всё может «случайно сработать», поэтому на разработке симптом не ловится и всплывает уже в бою. Если обходите это IP-адресом или псевдонимом, не забудьте про SPN, о котором речь чуть выше.1021

5. Проектируем с расчётом на «медленно, рвётся, иногда отсутствует»

Код, написанный с тем же ощущением, что и для локального диска, на общей папке рано или поздно обязательно сломается. В предпосылки нужно закладывать три реальности.

Первое: разрыв соединения — это нормально. Простаивающее соединение по умолчанию отключается через 15 минут таймаута, чтобы не тратить ресурсы сервера. Это тот самый знакомый красный крестик на значке сетевого диска в Проводнике; при следующем обращении соединение быстро поднимается снова.12 То, что редко ходящее к папке бизнес-приложение каждый раз на мгновение подвисает, и то, что мониторинг орёт «отключено!», хотя вреда нет, — оба явления штатные. Судить нужно не по факту «соединение есть», а по тому, прошёл ли реальный ввод-вывод.

Этот таймаут можно изменить. Но таймеры стоят и на сервере, и на клиенте, и побеждает более короткий.12

Где Настройка Что делает
Сервер (команда) net config server /autodisconnect:<минуты> Минуты до отключения по простою. Максимум 65 535. 0 функцию не выключает — наоборот, отключение начнётся уже через несколько секунд простоя
Сервер (команда) net config server /autodisconnect:-1 Выключает сам механизм autodisconnect
Сервер (реестр) autodisconnect (REG_DWORD) в HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters Меняет только таймаут. Этим способом функцию выключить нельзя
Клиент (реестр) KeepConn (REG_DWORD, 1–65535 секунд) в HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanworkstation\parameters Время удержания простоя на клиенте. По умолчанию 600 секунд (10 минут)

Легко пропустить последнюю строку. Клиентское значение по умолчанию — 10 минут, короче серверных 15, поэтому если удлинить только сервер, поведение разрыва не изменится. Сеанс рвётся по более короткому из autodisconnect и KeepConn.12

И всё же менять эти значения — не первый выбор. Разрыв — штатное поведение, при следующем обращении соединение поднимается, обычно вреда нет. Крутить значения имеет смысл, только если на руках старое приложение, которое падает на каждом разрыве и его нельзя трогать, — и то по согласованию с администратором сервера, потому что настройка задевает и другие системы. Со стороны приложения правильный путь — повторять операции, исходя из того, что соединение рвётся.

Второе: ошибки сообщаются неинформативно. File.Exists возвращает false без исключения и при неверном пути, и при нехватке прав, и при сбое диска.13 Локально «false значит файла нет» почти никогда не мешает, но на общей папке в одно и то же false схлопываются «файла нет» и «сервер недоступен / нет прав», поэтому код, который принимает бизнес-решение по Exists, при сетевом сбое тихо завершается успешно как «объекта нет» — неприятный вид поломки. На общей папке диагностичнее открывать файл сразу, без предварительной проверки существования, и различать случаи по исключению.

Третье: с той стороны ресурс может ещё отсутствовать. Сразу после загрузки машины запуск службы иногда обгоняет готовность сети, да и сам файловый сервер может быть в процессе перезапуска. Постоянное подключение (persistent connection) система восстанавливает при входе пользователя,23 поэтому в мире службы, которая через вход не проходит, гарантии «после запуска общий ресурс сразу виден» нет. Правильное поведение — не проверить связь один раз при старте и завершиться при неудаче, а повторять попытки и ждать готовности.

Если заложить все три момента, каркас стороны записи выглядит так:

// Временные сетевые ошибки повторяем, бизнес-ошибки сразу считаем окончательным сбоем
private static async Task WriteToShareAsync(string finalPath, byte[] content, CancellationToken ct)
{
    var dir = Path.GetDirectoryName(finalPath)!;
    var tempPath = Path.Combine(dir, $"~{Guid.NewGuid():N}.tmp");

    try
    {
        for (var attempt = 1; ; attempt++)
        {
            try
            {
                await File.WriteAllBytesAsync(tempPath, content, ct);
                File.Move(tempPath, finalPath); // rename в том же каталоге публикует файл как «готовый»
                return;
            }
            catch (IOException ex) when (attempt < 5 && IsRetryable(ex))
            {
                // Повторяем только временные сетевые сбои с экспоненциальной задержкой (с потолком попыток)
                _logger.LogWarning(ex, "Не удалось записать в общий ресурс (попытка {Attempt}). Повторяем", attempt);
                await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)), ct);
            }
        }
    }
    catch
    {
        // Если сдаёмся и пробрасываем исключение, по возможности убираем временный файл
        try { File.Delete(tempPath); } catch { /* Ошибку очистки игнорируем */ }
        throw;
    }
}

// IOException прилетает одним типом и при «разрыве сети», и при «файле с тем же
// именем в месте назначения», и при «переполненном диске».
// По младшим 16 битам HResult (код ошибки Win32) оставляем только те временные
// сбои, для которых повтор имеет смысл
private static bool IsRetryable(IOException ex)
{
    var win32 = ex.HResult & 0xFFFF;
    return win32 is 53   // ERROR_BAD_NETPATH: сетевой путь не найден
              or 59   // ERROR_UNEXP_NET_ERR: непредвиденная сетевая ошибка
              or 64   // ERROR_NETNAME_DELETED: сетевое имя больше недоступно
              or 121; // ERROR_SEM_TIMEOUT: истёк тайм-аут (так называемый semaphore timeout)
}

Важно и не писать небрежно «раз это IOException — повторяем». Ошибки, результат которых не изменится сколько ни повторяй — файл с тем же именем уже есть в месте назначения, путь слишком длинный, диск переполнен, — прилетают тем же типом IOException, поэтому судить только по типу значит пять раз повторять постоянную ошибку и впустую ждать экспоненциальную задержку. Как в примере выше, по коду ошибки Win32 (53/59/64/121 и подобные коды разрыва и таймаута при доступе к общему ресурсу24) отбирайте только те сбои, «для которых повтор действительно имеет смысл». Реалистичный подход — постепенно дополнять список кодами, которые реально видны в логах боевой среды.

Суть не столько в самих повторах, сколько в том, чтобы сочетать их с протоколом передачи, безопасным при повторном выполнении (temp -> rename, идемпотентность). Если запись оборвётся на середине, останется недописанный файл. Если договориться, что конечное имя появляется только через rename после закрытия, принимающая сторона не прочитает «сырой» файл. Учтите также: при аварийном завершении процесса или отключении питания код очистки внутри функции просто не выполнится, поэтому в папке обмена стоит отдельно чистить «файлы ~*.tmp, которые давно не обновлялись» — иначе мусор накопится и забьёт каталог. Полная картина этой схемы передачи — в статье «Основы взаимного исключения при обмене файлами».

Есть ещё одна особенность именно сети. Результат «неудача» не гарантирует, что неудача действительно произошла. Если соединение рвётся сразу после того, как на сервере завершился rename, клиент может получить исключение, хотя файл под конечным именем уже опубликован. Если в этом состоянии бездумно повторить попытку, можно упереться в ошибку «файл с таким именем уже есть в месте назначения» либо — если принимающая сторона уже забрала первый файл — опубликовать те же данные второй раз. Решение: перед повтором неудавшегося шага rename проверить состояние места назначения и сверить его. Если в конечное имя включить идентификатор обработки (номер накладной или GUID выполнения), ситуацию «конечный файл с этим ID уже есть» можно считать признаком того, что предыдущий rename на самом деле прошёл, и выйти, засчитав успех; принимающая сторона по тому же ID отбросит повторный приём.

6. Взаимное исключение через SMB и надёжность FileSystemWatcher

6.1. На блокировки слишком полагаться нельзя

К общей папке одновременно обращается несколько клиентов. Открытие с FileShare.None действительно даёт исключительный доступ, пока дескриптор открыт, даже через SMB, но проект, который опирается на «если блокировка получена — значит безопасно», рушится, когда дескриптор теряется из-за разрыва или когда вмешивается участник, который блокировку не берёт (ручное копирование, другая система). Настоящую основу взаимного исключения стоит класть не на блокировку ОС, а на сам протокол передачи: temp -> rename, атомарный claim (обрабатывает только тот, кто выиграл rename из incoming в processing) и идемпотентность. Подробности — в статье о взаимном исключении, упомянутой выше.

6.2. Реальность FileSystemWatcher на удалённом общем ресурсе

FileSystemWatcher официально документирован как поддерживающий наблюдение за файлами не только локально, но и на сетевых дисках и удалённых компьютерах.14 Само по себе его использование оправдано. Однако у надёжности две оговорки.

  • Уведомления идут через буфер, и переполнение приводит к потере. Если изменения сыплются кучно за короткое время, события сверх размера буфера теряются.14
  • При наблюдении по сети верхний предел InternalBufferSize — 64 КБ. Это ограничение «раздуть сильнее, чем локально, нельзя» зафиксировано явно.14

К тому же в разборе сбоев я неоднократно видел ситуацию, когда при перезапуске сервера или разрыве соединения наблюдатель на той стороне просто молча умирает и уведомления больше нет. Практический вывод: FileSystemWatcher на удалённом общем ресурсе — «подсказка, чтобы среагировать быстрее», а источником истины считать полное сканирование (опрос) при запуске, при ошибке и по расписанию. Схемы проектирования, включая дубли событий и нарушенный порядок, подробно разобраны в статье «Практическое руководство по FileSystemWatcher».

7. Рабочие правила (таблица решений)

Сведём изложенное в таблицу решений по спорным вопросам проектирования.

Вопрос Варианты Ориентир
Как хранить пути Буква диска / UNC-путь Путь, с которым работает приложение, всегда UNC. Букву диска считайте удобством на экране пользователя2
Запись в общий ресурс Прямая запись / temp -> rename Прямая запись не даёт гарантии «незавершённый файл никто не прочитает». Основа — договорённость, что конечное имя означает «готово»
Обнаружение новых файлов Только FileSystemWatcher / опрос / оба На удалённом общем ресурсе считайте, что события теряются. Если задержка в несколько минут приемлема — один опрос проще и крепче; если нужна мгновенность — сочетайте оба способа14
Учётная запись запуска службы LocalSystem / доменная учётная запись / gMSA Если есть доступ к общему ресурсу — доменная учётная запись или gMSA. Там, где gMSA доступна, эксплуатацию пароля можно убрать целиком1
Когда нужны отдельные учётные данные Вызов net use из кода / WNetAddConnection2 / предварительная регистрация через cmdkey net use изнутри службы не рекомендуется. Несколько наборов учётных данных к одному серверу упираются в 1219, поэтому обходите это ещё на этапе проектирования — через одну учётную запись или DNS-псевдоним210
Прямой доступ множества клиентов Каждое устройство напрямую по UNC / через промежуточную службу (API) Когда произведение числа устройств, наборов учётных данных и одновременных обращений становится неуправляемым, сведите обращения к общему ресурсу в одну службу, а клиентам говорите с ней через API

Последняя строка требует пояснения. Интеграция через общую папку удобна, но чем больше точек доступа, тем экспоненциально сложнее управлять тем, «у кого какие права» и «кто с кем конфликтует». После определённого масштаба процесс, который трогает файловый сервер, стоит свести в одну службу Windows, а каждому клиенту общаться с ней по HTTP или gRPC. Понижение общей папки с «интерфейса между системами» до «внутренней реализации этой одной службы» в долгую оказывается самой удобной в сопровождении формой.

8. Итог

  • Буква диска — лишь обозначение в рамках сеанса входа. То, что она не видна другому пользователю, процессу с повышением прав или службе, так задумано; правильный путь — писать приложение на UNC-путях. EnableLinkedConnections — неподдерживаемый обходной путь.
  • Доступ к общему ресурсу из службы требует UNC. От чьего имени идёт аутентификация, определяет учётная запись запуска: LocalSystem/NetworkService используют учётные данные компьютера, LocalService — анонимные. На практике права на стороне общего ресурса выдают доменной учётной записи или gMSA.
  • Одновременные соединения с одним сервером под разными учётными данными падают с ошибкой 1219 (так задумано). Обходите это ещё на этапе проектирования — через одну учётную запись или DNS-псевдоним.
  • «Медленно, рвётся, иногда отсутствует» — штатное поведение общей папки. Отключение по простою (по умолчанию 15 минут) — не аномалия, а false от File.Exists неотличим от сетевого сбоя. Закладывайте повторы, temp -> rename и идемпотентность вместе.
  • FileSystemWatcher умеет удалённое наблюдение, но из-за переполнения буфера и предела 64 КБ события теряются, поэтому сочетание с полным сканированием — обычная практика.
  • Если сомневаетесь — к таблице решений из главы 7. Когда масштаб вырастет, рассмотрите вариант свести доступ к общему ресурсу в промежуточную службу.

Похожие статьи

Смежные направления консультаций

В KomuraSoft LLC мы проектируем и реализуем системы обмена файлами через общие папки, прорабатываем права доступа и аутентификацию при переводе приложения в службу Windows, а также разбираем сбои вроде «после перевода в службу общий ресурс перестал быть виден» или «падает только в конкретном окружении».

Справочные ссылки

  1. Microsoft Learn, Group Managed Service Accounts overview. О том, что gMSA — доменная учётная запись с автоматическим управлением паролем и упрощённым управлением SPN, что позволяет переложить управление паролем на ОС Windows.  2 3 4 5 6 7 8

  2. Microsoft Learn, Services and Redirected Drives. О том, что буквы дисков назначаются не глобально для системы, а по сеансу входа; что службы не могут обращаться к буквам дисков другого сеанса и должны использовать UNC-имена; почему не рекомендуется подключать диски изнутри службы через net use или функции WNet (утечка учётных данных, взаимное влияние служб и т. п.); что для службы, настроенной на учётную запись пользователя, всё равно создаётся новый сеанс входа; и что вместо хранения собственных учётных данных службой рекомендуется олицетворение клиента.  2 3 4 5 6 7 8 9 10 11 12

  3. Microsoft Learn, Mapped drives are not available from an elevated prompt when UAC is configured to Prompt for credentials. О двух связанных сеансах входа при включённом UAC; о том, что подключение диска физически представляет собой объект символической ссылки (DosDevices), привязанный к конкретному сеансу входа и не разделяемый между сеансами; и о том, что EnableLinkedConnections принудительно записывает символическую ссылку в оба сеанса.  2 3

  4. Microsoft Learn, Folder Redirection fails to apply when redirected to mapped drive letter, instead of UNC path. О том, что при входе администратора LSA создаёт два токена доступа, а диск подключается под стандартным токеном; о порядке настройки параметра реестра EnableLinkedConnections и предупреждении «этот обходной путь может снизить безопасность системы, Microsoft его не поддерживает»; и о том, что всегда рекомендуется использовать UNC-пути вместо буквы диска.  2 3

  5. Microsoft Learn, LocalSystem Account. О том, что LocalSystem обладает широкими локальными привилегиями, а в сети предъявляет удалённым серверам учётные данные компьютера.  2 3

  6. Microsoft Learn, NetworkService Account. О том, что NetworkService обладает минимальными локальными привилегиями, а в сети предъявляет удалённым серверам учётные данные компьютера.  2 3

  7. Microsoft Learn, LocalService Account. О том, что LocalService предъявляет в сети анонимные учётные данные.  2

  8. Microsoft Learn, About Service Logon Accounts. О том, что учётная запись входа службы определяет её контекст безопасности во время выполнения, и о том, что служба в контексте безопасности локальной учётной записи пользователя не может обращаться к сетевым ресурсам.  2

  9. Microsoft Learn, Create a Key Distribution Service (KDS) Root Key. О том, что контроллерам домена для генерации пароля gMSA нужен корневой ключ KDS; что его создают командой Add-KdsRootKey -EffectiveImmediately; что до 10 часов после создания gMSA создать нельзя, пока не сойдётся репликация AD на всех контроллерах домена (защита от генерации пароля, пока все DC ещё не готовы отвечать на запросы gMSA); и что команды управления gMSA требуют 64-разрядную архитектуру.  2 3 4

  10. Microsoft Learn, The network folder specified is currently mapped using a different user name and password error. О том, что ошибка при попытке установить несколько соединений с одним сервером под разными учётными данными — by design, и что обходные пути — подключение по IP-адресу или отдельный DNS-псевдоним.  2 3 4

  11. Microsoft Learn, System Error Codes (1000-1299). Определение и текст сообщения ошибки 1219 (ERROR_SESSION_CREDENTIAL_CONFLICT).  2

  12. Microsoft Learn, Mapped drive connection to network share may be lost. О том, что простаивающее соединение по умолчанию отключается после 15-минутного таймаута (autodisconnect); что значок диска в Проводнике при этом отмечается красным крестиком, но при обращении соединение быстро поднимается; что таймаут меняется командой net config server /autodisconnect:<минуты> с максимумом 65 535; что 0 функцию не выключает и приводит к отключению через несколько секунд простоя; что -1 выключает функцию; что серверный параметр реестра autodisconnect в HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters меняет только таймаут и не выключает функцию; что на клиенте есть KeepConn (REG_DWORD, 1–65535 секунд, по умолчанию 600) в HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanworkstation\parameters; и что сеанс рвётся по более короткому из autodisconnect и KeepConn.  2 3 4

  13. Microsoft Learn, File.Exists(String) Method. О том, что метод возвращает false без исключения при отсутствии прав на чтение, а также возвращает false при любой ошибке во время проверки существования (неверный путь, сбой диска, нехватка прав и т. п.).  2

  14. Microsoft Learn, FileSystemWatcher Class. О поддержке наблюдения за файлами на локальном компьютере, сетевом диске и удалённом компьютере, о возможной потере событий при превышении размера буфера и о том, что при наблюдении по сети максимальное значение InternalBufferSize составляет 64 КБ.  2 3 4 5

  15. Microsoft Learn, WNet Functions. О том, что WNetGetUniversalName получает универсальное (UNC) имя из пути с буквой диска. 

  16. Microsoft Learn, Secure group managed service accounts. О том, что пароль gMSA — случайно сгенерированные 240 байт, автоматически меняемые ОС каждые 30 дней, поэтому администраторам не нужно планировать смену пароля и простой службы.  2

  17. Microsoft Learn, Service Accounts and BITS. О том, что сетевая аутентификация LocalSystem/NetworkService идёт по учётным данным компьютера, а LocalService — по анонимным; что при ограничении ACL учётной записью пользователя доступ будет отказан; и что системным учётным записям не следует пользоваться подключёнными дисками. 

  18. Microsoft Learn, cmdkey. О том, что команда cmdkey создаёт, перечисляет и удаляет сохранённые имена пользователей и пароли (учётные данные). 

  19. Microsoft Learn, Credentials processes in Windows authentication. О том, что Диспетчер учётных данных сохраняет учётные данные в контейнере учётных данных Windows и автоматически предъявляет их при последующей аутентификации. 

  20. Microsoft Learn, Client Access to Network Resources. О том, что установление соединения через WNetAddConnection2 с указанием учётных данных клиента названо одной из стратегий доступа серверного процесса к сетевым ресурсам. 

  21. Microsoft Learn, SMB file server share access is unsuccessful through DNS CNAME alias. О том, что одна из причин сбоя SMB через CNAME — отсутствие регистрации SPN для псевдонима, и о рекомендации задавать псевдоним командой netdom computername, а не через CNAME в DNS.  2

  22. Microsoft Learn, Net use. О том, что без параметров команда показывает список сетевых соединений; что net use <имя_устройства> выводит сведения о конкретном соединении; что пароль * запрашивается с приглашения; что /user задаёт другое имя пользователя; и что /delete с * разрывает все сетевые соединения.  2

  23. Microsoft Learn, Windows Networking Operations. О том, что постоянное подключение (persistent connection) — сетевое соединение, которое система автоматически восстанавливает при входе пользователя. 

  24. Microsoft Learn, System Error Codes (0-499). Определения ERROR_BAD_NETPATH (53), ERROR_UNEXP_NET_ERR (59), ERROR_NETNAME_DELETED (64) и ERROR_SEM_TIMEOUT (121). 

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Как создать и эксплуатировать службу Windows — от выбора между Планировщиком заданий и службой до превращения BackgroundService в службу

Стоит ли держать постоянно работающую обработку как службу Windows или хватит Планировщика заданий. Практический разбор: таблица выбора, ...

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Почему служба Windows не может обратиться к сетевому диску (Z:)?
Потому что буква диска принадлежит не всей системе, а конкретному сеансу входа (logon session). Диск Z:, который пользователь подключил в Проводнике, — это лишь обозначение внутри его сеанса входа; службе в другом сеансе он не виден. Даже если служба запущена под той же учётной записью пользователя, система создаёт для службы новый сеанс входа, поэтому подключение с рабочего стола туда не переносится. Службе нужно ходить по UNC-пути \\server\share.
Как службе под LocalSystem получить доступ к общей папке?
В сети LocalSystem аутентифицируется учётными данными компьютера. В домене можно выдать учётной записи компьютера (DOMAIN\MACHINE$) права на стороне общего ресурса — и в разрешениях общего доступа, и в ACL NTFS. На практике это неудобно: после замены машины права придётся настраивать заново. Поэтому обычно службу запускают под доменной учётной записью или gMSA (групповой управляемой учётной записью службы) и выдают права именно ей.
Можно ли следить за файлами в общей папке через FileSystemWatcher?
Можно, но одному ему доверять нельзя. FileSystemWatcher умеет наблюдать за сетевыми дисками и удалёнными компьютерами, однако уведомления об изменениях идут через буфер: при всплеске изменений буфер переполняется и события теряются. К тому же при наблюдении по сети верхний предел буфера ограничен 64 КБ. Уведомление стоит считать лишь поводом пересканировать каталог и обязательно сочетать его с полным сканированием (опросом) при запуске, при ошибке и по расписанию.
Как сделать обмен файлами устойчивым к разрывам сети?
Проектируйте так, будто «общий ресурс медленный, соединение рвётся, а сервер иногда отсутствует» — это штатное поведение, а не исключение. Конкретно: писать во временное имя и публиковать конечное имя только после закрытия файла (temp -> rename), чтение и запись оборачивать в повторы, временные сетевые ошибки отделять от бизнес-ошибок. File.Exists возвращает false и когда файл недоступен, поэтому не отличает «файла нет» от «сервер не отвечает». Последняя страховка — идемпотентная обработка: повтор не портит данные.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог