网络驱动器与 UNC 路径的陷阱 ── 业务应用中处理文件服务器(共享文件夹)的实务
· 更新日期: · Go Komura · 网络驱动器, UNC 路径, SMB, 文件共享, Windows 服务, 文件服务器, C#, .NET, 网络, 故障排查, Windows 开发, 技术咨询
「明明在开发机上能正常运行,客户环境却报『找不到 Z:\』」「一放到任务计划程序上,向共享文件夹输出就开始失败」「改造成 Windows 服务之后,文件服务器就看不见了」── 在业务应用的咨询中,与文件服务器(共享文件夹)相关的故障堪称经典中的经典。订单数据的导入、报表或 CSV 的输出、对设备生成文件的监视 —— 在本地部署的业务系统中,共享文件夹至今仍是现役的对接基础设施。
麻烦之处在于,这类故障大多并非「代码中的 bug」,而是根植于驱动器号与登录会话的关系、服务执行账户与身份验证、SMB 的连接管理这类 Windows 底层机制。即便用调试器一路追踪也找不到原因,时间就在「自己的电脑上重现不出来」中悄悄流逝。
本文面向正在实现向共享文件夹输出、导入、监视文件的业务应用开发者(WinForms/WPF/Windows 服务),从底层机制出发梳理网络驱动器与 UNC 路径的陷阱,并将实务定式归纳成一张判断表。
1. 先说结论
- 驱动器号(如 Z: 等)并非系统全局资源,而是按登录会话分配的。 每个登录会话都会获得一整套从 A 到 Z 的驱动器号,因此用户映射的驱动器,对于以不同用户运行的进程、或运行在不同登录会话中的服务都是不可见的。1
- 当 UAC 处于启用状态时,管理员会拥有两个登录会话——普通权限与提升权限——驱动器映射(DosDevices 符号链接)在每个会话中是相互独立的。 这就是为什么会出现「只有在提升权限的应用中才看不到 Z:」的现象。虽然存在通过
EnableLinkedConnections注册表值让两个会话共享映射的规避方法,但微软明确将其记载为「可能会降低系统安全性、不受支持」的非受支持设置。23 - 当服务(或运行在不同安全上下文中的进程)需要访问远程资源时,官方方针是使用 UNC 路径(
\\server\share\...)。 在服务内部通过net use或 WNet 系列 API 来映射驱动器号的做法,出于凭据泄露与服务间相互干扰的考虑,是不被推荐的。1 - 共享一侧应该授权给谁,取决于服务的执行账户。 LocalSystem 和 NetworkService 在网络上会以「计算机的凭据」进行身份验证,而 LocalService 则使用匿名凭据,因此不适合用于共享访问。以本地用户账户运行的服务无法访问网络资源。实务中的首选是域账户,或者能把密码管理完全交给操作系统的 gMSA。45678
- 无法用多个凭据同时连接同一台服务器。 第二个连接会因错误 1219(
ERROR_SESSION_CREDENTIAL_CONFLICT)而失败,这是官方规定的行为(by design)。910 - 「变慢、断开、有时甚至不存在」是共享文件夹的正常状态。 空闲连接默认会在 15 分钟后被服务器端断开(下次访问时会自动重新连接)。
File.Exists在权限不足或出错时也不会抛出异常,而是返回false,因此无法区分「文件不存在」与「无法连接到服务器」。1112 - FileSystemWatcher 支持对网络驱动器和远程计算机进行监视,但设计时应以「会漏掉事件」为前提。 缓冲区溢出会导致事件丢失,而在通过网络进行监视时,内部缓冲区的上限会被限制为 64KB。并行使用轮询(完全扫描)是实务定式。13
2. 驱动器号与 UNC 路径的关系 ── Z: 属于「你的登录会话」
在资源管理器中把 \\fileserver\share 分配为 Z: 之后,看上去就像整台机器上凭空多出了一个「Z: 驱动器」。这正是最初的误解所在。
官方文档的表述十分明确。驱动器号并非系统全局的资源,而是按登录会话分配一整套从 A 到 Z 的字母。 重定向的驱动器(网络驱动器)无法在以不同用户账户运行的进程之间共享,而运行在不同登录会话中的服务,也无法访问在其他会话中建立的驱动器号。1 系统是根据能够唯一标识登录会话的登录 SID 来管理驱动器映射的。1
也就是说,Z: 不过是「每个登录会话各自拥有一套路径的缩写符号」,其实体始终是 UNC 路径。由此出发,现场频繁出现的各种症状便可以顺藤摸瓜地得到解释。
| 症状 | 机制上的原因 |
|---|---|
| 以其他用户身份运行后 Z: 消失 | 驱动器号是按登录会话分配的。看不到其他人的映射1 |
| 「以管理员身份运行」后 Z: 消失 | UAC 会创建普通权限与提升权限两个登录会话,映射不在会话间共享2 |
| 放到任务计划程序/服务上后 Z: 消失 | 因为运行在不同的登录会话中。即使服务配置为以用户账户运行,系统也会为服务创建新的登录会话1 |
UAC 这件事值得再深入了解一些。当属于管理员组的用户登录时,系统会创建两个相互关联的登录会话:一个持有受限权限令牌,另一个持有完整的管理员令牌。驱动器映射的实体是把驱动器号与 UNC 路径对应起来的符号链接对象(DosDevices),它是特定于某个登录会话的,不会在会话之间共享。2 登录脚本运行在普通权限一侧的会话中,因此提升权限的进程自然看不到那份映射——道理就是这么简单。
将 EnableLinkedConnections(HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System 下的 DWORD 值)设置为 1,就会把符号链接同时写入两个相互关联的会话,从而消除这一症状。但官方文档明确写道:「此规避方法可能会降低系统安全性。Microsoft 不支持此规避方法,使用需自行承担风险」。3 在客户环境的注册表中植入一个不受支持的设置,作为业务应用的解决方案来说并不明智。在应用一侧始终使用 UNC 路径来编写代码才是正道。 官方针对文件夹重定向的故障排查文档中也指出:「始终建议使用 UNC 路径,而不是驱动器号」。3
实现起来很简单,只需要在配置文件中以 UNC 形式保存路径即可。
// appsettings.json ── 以 UNC 形式保存,而非驱动器号
{
"FileTransfer": {
"IncomingDir": "\\\\fileserver01\\edi\\incoming",
"ProcessedDir": "\\\\fileserver01\\edi\\processed"
}
}
如果应用允许用户在文件夹选择对话框中选到 Z: 下的路径,那么在保存时就把它规范化为 UNC 形式,这样即使日后执行上下文发生变化也不会出问题。将驱动器号路径转换为 UNC 形式,有一个专门提供的 API,叫做 WNetGetUniversalName。14
3. 从 Windows 服务访问共享文件夹 ── UNC 是必须的,而且要弄清楚「以谁的身份」访问
正如上一章所述,映射驱动器在服务中是无法使用的。官方文档明确指出,服务(以及运行在不同安全上下文中的进程)应该使用 UNC 名称来访问远程资源,并明确不推荐在服务内部通过 net use 或 WNet 系列 API 在运行时映射驱动器号的做法。文档给出的理由包括:映射会被运行在同一上下文中的其他服务看到、传给 net use 的凭据可能泄露到服务边界之外、多个服务尝试建立同一个映射时会因「已经连接」的错误而相互干扰。1
一旦改用 UNC 路径,接下来的问题就是身份验证了。服务究竟是「以谁的身份」访问文件服务器? 这由执行账户决定,同时也决定了共享一侧应该把权限授予给谁。
| 执行账户 | 网络上的身份 | 共享一侧的授权对象 | 判断 |
|---|---|---|---|
| LocalSystem | 计算机的凭据4 | 域环境下,向计算机账户(DOMAIN\MACHINE$)授予共享/NTFS 权限 | 能用,但权限过大。更换机器需要重新设置权限 |
| NetworkService | 计算机的凭据5 | 同上 | 本地权限最小,但在网络上与 LocalSystem 是相同身份 |
| LocalService | 匿名凭据6 | 无从授予 | 不适合用于共享访问 |
| 本地用户 | ─ | ─ | 无法访问网络资源7 |
| 域用户 | 该账户本身 | 授予该账户 | 实务标准做法。密码变更的运维是难点 |
| gMSA | 该账户本身 | 授予该账户 | 密码由操作系统自动管理(每 30 天自动更换)。支持的环境下的首选815 |
LocalSystem 与 NetworkService 会「以计算机的身份」出现在网络上,这是官方明确记载的规范行为。45 BITS 的文档中,作为这一行为的直接推论,写有以下实务性的提醒:「如果源文件的 ACL 把访问权限限制给了某个用户账户,那么(以计算机凭据进行身份验证的)服务就会遭到访问拒绝」「系统账户不应该使用映射驱动器」。16
由此可以得出以下三条实务指导方针。
- 对于「改成服务之后就访问被拒」的问题,第一步排查应该是确认执行账户。 在桌面上运行时,访问检查用的是「你」的权限;而在服务中,访问检查用的是上表中的身份。需要针对该身份,同时确认共享的访问权限和 NTFS 的 ACL 这两方面。
- 如果要在域环境中长期运行,应把执行账户设置为域账户或 gMSA。 gMSA 拥有一个 240 字节的随机密码,由操作系统每 30 天自动更新一次,因此可以从结构上消除「因服务账户密码过期,周一早上全线停摆」这类事故。15
- 在工作组环境(没有域)中,无法成立基于计算机凭据的身份验证,此时就需要用到下一章介绍的显式凭据。
关于服务本身的构建方法(执行账户的设置、恢复选项、安全停止),我们在「Windows 服务的创建与运维」中做了整理;关于会话与登录的机制,则整理在「如何理解 Windows 的会话隔离」中。
4. 凭据的处理方式 ── net use、凭据管理器与错误 1219
如果无法直接为执行账户本身授予共享一侧的权限(例如工作组环境,或 NAS 采用自有账户体系的情况),就需要显式传递凭据来建立连接。主要有三种手段。
net use \\server\share /user:...── 在该登录会话中建立连接。用于交互式确认时很方便,但正如上一章所述,不推荐在服务内部执行。1- 凭据管理器(
cmdkey) ── 用cmdkey /add:server /user:svc-file /pass:...保存凭据后,之后对该服务器进行身份验证时会自动使用保存的凭据。1718 由于保存是按用户配置文件划分的,如果要在服务中使用,就必须在「服务执行账户的上下文中」进行注册,这是一个容易踩的坑。 - 通过程序建立连接(
WNetAddConnection2) ── 可以指定客户端凭据来建立到网络资源的连接。官方文档也将其列为服务器进程访问网络资源的策略之一。19 如果采用不分配驱动器号的「无设备连接(deviceless connection)」,就可以继续以 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)10
不能从同一个登录会话,用不同的用户名对同一台服务器建立多个连接。 如果打算「销售共享用自己的账户,系统对接用的共享用专用账户」,尝试用两种不同凭据连接同一台文件服务器,第二个连接就会因 1219 而失败。官方文档明确将其定性为「by design(设计使然)」,给出的规避方法是「用 IP 地址连接」或「新建一个 DNS 别名后连接」── 也就是让它看起来像是另一台服务器的做法。9
在实务中,首选做法是从一开始就避免在同一台服务器上区分使用多种凭据的架构:把用于对接的账户统一为一个,并为该账户授予所有所需共享的权限。只有在确实无法这样做的情况下,才通过别名(alias)来分开连接路径。
不过,别名并不是「在 DNS 里加一行 CNAME 就完事」这么简单的事情。在 Kerberos 身份验证的环境中,SMB 客户端会尝试使用与连接目标名称对应的 SPN(服务主体名称)来进行身份验证,因此如果没有为该别名注册 SPN,经由 CNAME 的访问本身就可能失败。官方的故障排查文档也把 SPN 缺失列为原因之一,并建议不要使用 DNS 的 CNAME,而是通过 netdom computername <服务器名> /add:<别名> 把别名配置为计算机名别名。20 在把经由别名的连接作为 1219 的对策纳入设计之前,务必先在目标环境中进行实际验证。
另外,「希望服务以连接进来的客户端用户的权限访问文件服务器」这类高级需求,属于模拟(impersonation)的范畴,而不是靠反复使用凭据来解决。官方文档也建议与其让服务自行保管凭据,不如使用模拟。1 关于模拟的正确写法,以及针对远程资源需要额外考虑的地方,请参阅「正确处理 Windows 的模拟令牌」。
5. 以「变慢、断开、有时甚至不存在」为前提进行设计
用和本地磁盘一样的思维写出来的代码,在面对共享文件夹时必然会在某个环节出事。应当作为前提接受的现实有三点。
第一,连接会断开是正常现象。 处于空闲状态的连接,为了避免浪费服务器资源,默认会在 15 分钟超时后被断开——这正是资源管理器中网络驱动器上那个熟悉的红叉现象,下次访问时会迅速重新连接。11 也就是说,「偶尔才访问一次的业务应用,每次访问都会被卡顿一瞬间」以及「监视工具大喊『断开了!』但实际上并无损害」,这两者都是按照规范运行的表现。不应该去监视连接是否存在,而应根据实际的 I/O 是否成功来判断。
第二,报错方式很不友好。 无论是路径不合法、权限不足,还是磁盘故障,File.Exists 都不会抛出异常,而是统一返回 false。12 在本地磁盘上,「false 就代表不存在」几乎不会造成什么困扰;但面对共享资源时,「文件不存在」与「无法连接到服务器/没有权限」全都被压缩成同一个 false,于是依据 Exists 的结果做业务判断分支的代码,会在网络故障时被当作『目标不存在』而正常结束——这是一种相当令人不快的失效方式。跳过存在性检查、直接尝试打开文件、再通过异常类型来区分情况,对面对共享资源的场景来说是更易于诊断的设计。
第三,对方有时还不存在。 机器刚启动时,服务的启动有时会先于网络就绪完成,文件服务器那一端也可能正处于重启过程中。持久连接(persistent connection)是在用户登录时才会被恢复的机制,21 而在不经过登录的服务世界里,「一启动共享就能看到」这件事并没有任何保证。正确的做法不是在启动时只做一次连通性确认、失败就直接退出,而是一边重试一边等待。
把这三点都考虑进去之后,写入端的骨架大致如下。
// 临时性的网络错误重试,业务错误立即失败
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 会以同一种类型抛出,无论是「网络断开」「目标位置已存在同名文件」还是「磁盘已满」。
// 用 HResult 的低 16 位(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: 超时(所谓的信号量超时)
}
不要粗糙地写成「只要是 IOException 就重试」,这一点也很重要。目标位置已存在同名文件、路径过长、磁盘已满——这类无论重试多少次结果都不会改变的失败,也会以同样的 IOException 抛出,因此如果只靠异常类型来判断,就会把永久性错误重试 5 次,白白等待退避时间。请像上面的例子那样,通过 Win32 错误代码(53/59/64/121 等与共享访问的断开、超时相关的代码22)来筛选出「重试真正有意义的失败」。在实际环境的日志中不断观察并逐步补充代码,是比较现实的运维方式。
关键并不在于「加上重试」这件事本身,而在于把它与即使重试也安全的交接协议(temp -> rename、幂等性)配套使用。如果在写入过程中被断开,就会留下一个写到一半的文件。只要约定 final 文件名「只会在关闭句柄之后通过 rename 出现」,就能防止接收方读到未完成文件的事故。另外,进程崩溃或断电时,代码内部的清理逻辑本身不会执行,因此最好在交接文件夹中再准备一项定期清扫——「删除 ~*.tmp 中一段时间未更新的文件」——这样就不会因为垃圾堆积而堵塞。这套交接设计的整体思路,我们整理在「文件集成互斥控制基础知识」中。
还有一点是面对网络对象时特有的注意事项。「失败」这个结果,并不能保证真的失败了。 如果服务器端刚完成 rename 之后连接就断开,客户端会收到异常,但 final 文件名对应的文件其实已经被公开出去了——这种状态是可能发生的。如果不假思索地就此重试,要么会卡在「目标位置已存在同名文件」的错误上,要么——如果接收方已经把第一份文件摄入了——就会把同一份数据重复公开出去。对策是,在重试 rename 步骤的失败之前,先确认目标位置的状态并进行核对。只要在 final 文件名中包含处理 ID(单据号或执行 GUID),就可以判断出「同一 ID 的 final 文件已经存在 = 上一次的 rename 其实已经成功了」,从而按成功处理直接退出;接收方也可以通过 ID 重复来拦截二次摄入。
6. SMB 上的互斥控制与 FileSystemWatcher 的可靠性
6.1. 切勿过度信任锁
共享文件夹会被多个客户端同时触碰。用 FileShare.None 打开文件,在打开期间的互斥即使跨越 SMB 也能生效,但如果设计上过度依赖「拿到锁就安全」,一旦因断连而丢失句柄、或者混入了不获取锁的一方(人工复制、其他系统),这种设计就会崩溃。互斥的主体不应该放在操作系统的锁上,而应该放在 temp -> rename、原子式 claim(只有把文件从 incoming 重命名到 processing 成功的一方才去处理)、幂等性这些交接协议一侧。具体思路的详细内容请参阅上文提到的互斥控制文章。
6.2. 在远程共享中使用 FileSystemWatcher 的现实情况
官方文档明确记载,FileSystemWatcher 不仅支持本地文件监视,也支持对网络驱动器和远程计算机上的文件进行监视。13 使用它本身是正当的。不过,有两个关于可靠性的前提需要了解。
- 通知是经由缓冲区传递的,一旦溢出就会丢失。 如果变更在短时间内集中发生,超出缓冲区大小的那部分事件就会丢失。13
- 在通过网络进行监视时,
InternalBufferSize的上限会被限制为 64KB。 官方文档明确写明了这一「无法像本地那样调大」的限制。13
此外,当共享的另一端发生服务器重启或断连时,监视会悄无声息地死掉、完全没有通知传来——这种症状我在故障排查的现场反复见过。把远程共享上的 FileSystemWatcher 定位为「用来尽早察觉变化的提示」,并把启动时、出错时、以及定期执行的完全扫描(轮询)当作真正的事实来源,这是实务上得出的结论。包括对事件重复、顺序错乱的处理在内的设计模式,详见「FileSystemWatcher 实务指南:应对遗漏通知与重复通知」。
7. 实务定式(判断表)
把到目前为止的内容,按照设计时容易犹豫不决的论点,整理成一张判断表。
| 论点 | 选项 | 判断依据 |
|---|---|---|
| 路径的保存方式 | 驱动器号 / UNC 路径 | 应用处理的路径应始终为 UNC。把驱动器号仅仅当作用户界面上的便利符号1 |
| 向共享的写入方式 | 直接写入 / temp -> rename | 直接写入无法保证「写到一半的文件不会被读到」。以 final 文件名 = 已完成 作为基本约定 |
| 新文件的检测方式 | 单独使用 FileSystemWatcher / 轮询 / 两者并用 | 远程共享要以会漏掉事件为前提。如果几分钟的间隔可以接受,只用轮询会更简单、更稳健;需要即时性时并用两者13 |
| 服务的执行账户 | LocalSystem / 域账户 / gMSA | 只要涉及共享访问,就用域账户或 gMSA。在支持 gMSA 的环境中,甚至可以连密码运维一并省去8 |
| 需要单独凭据时 | 在代码里调用 net use / WNetAddConnection2 / 预先注册 cmdkey | 不推荐在服务内部使用 net use。对同一服务器使用多个凭据会卡在 1219 上,因此应从设计阶段起就通过账户集中或 DNS 别名来规避19 |
| 大量客户端直接访问 | 各终端直接访问 UNC / 经由中间服务(API) | 一旦终端数量 × 凭据数量 × 并发访问变得难以管理,就把接触共享的工作收拢到一个服务中,让客户端改为通过 API 通信 |
最后一行值得补充说明。共享文件夹对接虽然简便,但访问源越多,「谁拥有什么权限」「谁和谁会发生冲突」的管理难度就会呈指数级上升。超过一定规模之后,应把接触文件服务器的进程收拢为一个 Windows 服务,各客户端改用 HTTP 或 gRPC 与之通信。把共享文件夹从「系统之间的接口」降格为「该服务的内部实现细节」,从长期来看是最易于维护的形态。
8. 总结
- 驱动器号不过是按登录会话分配的符号而已。对不同用户、提升权限的进程、服务不可见,是规范行为,应用应始终以 UNC 路径来编写。
EnableLinkedConnections是一种不受支持的规避方法。 - 从服务访问共享必须使用 UNC。以谁的身份进行身份验证由执行账户决定:LocalSystem/NetworkService 使用计算机凭据,LocalService 使用匿名凭据。实务中应把共享一侧的权限授予域账户或 gMSA。
- 对同一台服务器使用多个凭据同时连接,会因错误 1219 而失败(这是规范行为)。应从设计阶段起就通过账户集中或 DNS 别名加以规避。
- 共享文件夹「变慢、断开、有时甚至不存在」是正常状态。空闲断连(默认 15 分钟)并非异常,
File.Exists返回的 false 也无法与网络故障区分开来。应把重试与 temp -> rename、幂等性配套引入。 - FileSystemWatcher 支持远程监视,但存在因缓冲区溢出导致的漏事件问题以及 64KB 的上限,因此并用完全扫描是实务定式。
- 拿不定主意时,请参考第 7 章的判断表。规模扩大之后,也请考虑把接触共享的进程收拢到中间服务中的架构。
相关文章
- 文件集成互斥控制基础知识 - 文件锁与原子 claim 的最佳实践
- FileSystemWatcher 实务指南:应对遗漏通知与重复通知
- Windows 服务的创建与运维 ── 从任务计划程序的取舍到 BackgroundService 服务化
- 如何理解 Windows 的会话隔离 ── Session 0・RDP・多用户同时运行
- 正确处理 Windows 的模拟令牌 ── 线程级权限借用与安全的还原方式
相关咨询领域
合同会社小村软件(合同会社小村ソフト)承接经由共享文件夹的文件对接系统的设计与实现、伴随 Windows 服务化而来的访问权限与身份验证方面的架构探讨,以及「改成服务后共享就看不见了」「只在特定环境下失败」这类故障的排查工作。
参考链接
-
Microsoft Learn, Services and Redirected Drives。关于驱动器号并非系统全局资源、而是按登录会话分配的说明;服务无法访问其他会话的驱动器号、应使用 UNC 名称的说明;在服务内部通过 net use、WNet 函数进行驱动器映射不被推荐的原因(凭据泄露、服务间相互干扰等);即使服务配置为以用户账户运行,系统也会为其创建新登录会话的说明;以及相较于让服务自行保管凭据,官方推荐使用客户端模拟的说明。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11
-
Microsoft Learn, Mapped drives are not available from an elevated prompt when UAC is configured to Prompt for credentials。关于 UAC 启用时会创建两个相互关联的登录会话的说明;驱动器映射的实体是特定于登录会话的符号链接对象(DosDevices)、不会在会话间共享的说明;以及 EnableLinkedConnections 会强制将符号链接写入两个会话的说明。 ↩ ↩2 ↩3
-
Microsoft Learn, Folder Redirection fails to apply when redirected to mapped drive letter, instead of UNC path。关于管理员登录时 LSA 会创建两个访问令牌、驱动器会在标准令牌下被映射的说明;EnableLinkedConnections 注册表值的设置步骤,以及「可能会降低系统安全性、Microsoft 不予支持」这一警告;以及始终建议使用 UNC 路径而非驱动器号的说明。 ↩ ↩2 ↩3
-
Microsoft Learn, LocalSystem Account。关于 LocalSystem 在本地拥有广泛特权、在网络上会向远程服务器出示计算机凭据的说明。 ↩ ↩2 ↩3
-
Microsoft Learn, NetworkService Account。关于 NetworkService 在本地仅拥有最小特权、在网络上会向远程服务器出示计算机凭据的说明。 ↩ ↩2 ↩3
-
Microsoft Learn, LocalService Account。关于 LocalService 在网络上会出示匿名凭据的说明。 ↩ ↩2
-
Microsoft Learn, About Service Logon Accounts。关于服务的登录账户决定其运行时安全上下文、以本地用户账户安全上下文运行的服务无法访问网络资源的说明。 ↩ ↩2
-
Microsoft Learn, Group Managed Service Accounts overview。关于 gMSA 是一种提供自动密码管理与简化 SPN 管理的域账户、可以把密码管理交给 Windows 操作系统的说明。 ↩ ↩2 ↩3
-
Microsoft Learn, The network folder specified is currently mapped using a different user name and password error。关于尝试用不同凭据对同一台服务器建立多个连接时出现的错误属于「by design(设计使然)」的说明,以及规避方法为通过 IP 地址连接或新建 DNS 别名的说明。 ↩ ↩2 ↩3
-
Microsoft Learn, System Error Codes (1000-1299)。关于错误 1219(ERROR_SESSION_CREDENTIAL_CONFLICT)的定义与消息文本。 ↩ ↩2
-
Microsoft Learn, Mapped drive connection to network share may be lost。关于空闲连接默认会在 15 分钟超时后断开(autodisconnect)、资源管理器的驱动器图标会显示红叉、但访问后会迅速重新连接的说明。 ↩ ↩2
-
Microsoft Learn, File.Exists(String) Method。关于在没有读取权限时不会抛出异常而是返回 false、在存在性判断过程中发生任何错误(路径无效、磁盘故障、权限不足等)时同样会返回 false 的说明。 ↩ ↩2
-
Microsoft Learn, FileSystemWatcher Class。关于支持对本地计算机、网络驱动器、远程计算机上的文件进行监视、缓冲区大小溢出时可能丢失事件、以及在通过网络进行监视时 InternalBufferSize 的最大值为 64KB 的说明。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, WNet Functions。关于 WNetGetUniversalName 是用于从基于驱动器的路径获取通用(UNC)格式名称的函数的说明。 ↩
-
Microsoft Learn, Secure group managed service accounts。关于 gMSA 的密码是随机生成的 240 字节、由操作系统每 30 天自动更换一次,因而无需管理员规划密码变更或服务停机的说明。 ↩ ↩2
-
Microsoft Learn, Service Accounts and BITS。关于 LocalSystem/NetworkService 在网络上以计算机凭据进行身份验证、LocalService 以匿名凭据进行身份验证的说明;当 ACL 仅限于某个用户账户时会导致访问拒绝的说明;以及系统账户不应使用映射驱动器的说明。 ↩
-
Microsoft Learn, cmdkey。关于 cmdkey 命令可以创建、列出、删除已保存的用户名与密码(凭据)的说明。 ↩
-
Microsoft Learn, Credentials processes in Windows authentication。关于凭据管理器会把凭据保存在 Windows 凭据容器中、并在后续身份验证时自动出示的说明。 ↩
-
Microsoft Learn, Client Access to Network Resources。关于将通过 WNetAddConnection2 指定客户端凭据来建立连接列为服务器进程访问网络资源的策略之一的说明。 ↩
-
Microsoft Learn, SMB file server share access is unsuccessful through DNS CNAME alias。关于经由 CNAME 的 SMB 访问失败的原因之一是别名缺少 SPN 注册的说明,以及建议使用 netdom computername 命令而非 DNS CNAME 来定义别名的说明。 ↩
-
Microsoft Learn, Windows Networking Operations。关于持久连接(persistent connection)是系统在用户登录时自动恢复的网络连接的说明。 ↩
-
Microsoft Learn, System Error Codes (0-499)。关于 ERROR_BAD_NETPATH(53)、ERROR_UNEXP_NET_ERR(59)、ERROR_NETNAME_DELETED(64)、ERROR_SEM_TIMEOUT(121)各自的定义。 ↩
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
睡眠、休眠、Modern Standby 与长时间运行应用 ── 用设计防止「夜里意外停止」
本文从 S3 睡眠、休眠、Modern Standby 的区别入手,整理长时间运行的 Windows 应用为何会出现「早上一看已经停止」的原因,并讲解睡眠期间计时器与 TCP 连接的行为,以及如何用 SetThreadExecutionState 进行抑制。
Arm 版 Windows 上业务应用能运行吗 ── x64 仿真(Prism)与原生 DLL・COM 的现实
面向开发者与信息系统部门,回答「Arm 版 Windows 上业务应用能运行吗」这一问题。梳理 x64 仿真(Prism)的原理、驱动程序等无法运行的层面、.NET 中 AnyCPU 与 P/Invoke 的组合问题,以及 Arm 适配自查清单。
Windows 应用的任务栏托盘常驻与 Toast 通知 —— NotifyIcon 的坑与 AppNotification 的选型
本文整理了将业务 Windows 应用常驻在任务栏托盘(通知区域)并通过 Toast 通知告知用户的实现要点。内容涵盖 NotifyIcon 的正确用法与「关闭后驻留托盘」的设计、资源管理器重启后的重新注册、三种 Toast API(Windows App SDK AppN...
VB6 应用能用到什么时候 ── 运行时支持现状与务实的 .NET 迁移做法
VB6 应用程序到底能用到什么时候?本文整理 VB6 运行时的支持政策(Windows 11 也在支持范围内)与 IDE 支持早已终止这一不对称现状,并以实务指南的形式说明全面重写、自动转换、分阶段迁移的判断表、迁移前的资产盘点、VB6 与 .NET 的不兼容之处,以及 C...
业务应用程序的日本年号・法定节假日・结算日处理 —— 抗改元设计与 JapaneseCalendar・营业日计算实务
报表要显示「令和8年」、营业日计算要排除法定节假日、20日结算次月末付款——日本业务应用程序特有的日期处理背后,隐藏着「日后会变动的规格」:改元、法定节假日的法规修订、月末的进位。本文整理了用 JapaneseCalendar 显示年号、抗改元的设计方式,法定节假日不可写死...
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
故障调查 & 长期运行故障
整理间歇性故障、通信诊断、长期运行崩溃、失败路径测试基础的主题页面。
与本主题相关的服务
本文与以下服务页面相关联,欢迎从最接近的入口查看。
Windows 应用程序开发
支持包含常驻处理、设备联动、运行日志与可维护结构的 Windows 桌面应用程序。
技术咨询 & 设计评审
协助梳理设计方向、架构边界、生命周期责任,以及既有 Windows 资产的处理方式。
常见问题
汇总了咨询这一主题时常见的问题。
- 为什么 Windows 服务无法访问网络驱动器(Z:)?
- 因为驱动器号不是系统全局的资源,而是按登录会话分配的。用户在资源管理器中映射的 Z: 只是属于该用户登录会话的一个符号,对运行在不同登录会话中的服务是不可见的。即使服务以用户账户运行,系统也会为该服务创建一个新的登录会话,因此即便是同一个账户,桌面端的映射也不会被继承下来。从服务中访问共享资源的正确做法是使用 \\server\share 这样的 UNC 路径。
- 以 LocalSystem 身份运行的服务应该如何访问共享文件夹?
- LocalSystem 在网络上会以计算机自身的凭据进行身份验证。在域环境中,只要在共享一侧(共享的访问权限与 NTFS 的 ACL)为该机器的计算机账户(DOMAIN\MACHINE$)授予权限,就可以实现访问。不过,一旦更换机器就需要重新设置权限,运维上并不方便,因此在实务中我们建议将服务的执行账户设置为域账户或 gMSA(组托管服务账户),并为该账户授予共享一侧的权限。
- 可以用 FileSystemWatcher 监视共享文件夹中的文件吗?
- 可以使用,但不要单独依赖它。FileSystemWatcher 支持监视网络驱动器和远程计算机上的内容,但变更通知是经由缓冲区传递的,一旦通知集中到来,缓冲区就会溢出并丢失事件。而且在通过网络进行监视时,缓冲区上限会被限制为 64KB。应将通知仅仅当作「触发重新扫描的契机」,并务必在启动时、出错时、以及定期时机都并行执行完全扫描(轮询),这才是实务定式。
- 如何设计出能够抵御网络断开的文件对接方案?
- 应把「共享会变慢、会断开、有时甚至不存在」当作正常情况来设计。具体来说:写入时先使用临时文件名,关闭句柄后再重命名以对外公开(temp -> rename);用重试机制包裹读写操作;并将临时性的网络错误与业务错误区分处理。需要注意的是,File.Exists 在无法访问时也会返回 false,因此无法区分「文件不存在」与「无法连接到服务器」这两种情况;将处理设计成即使重复执行也不会破坏数据的幂等(idempotency)处理,是最后一道防线。
作者简介
本文作者的个人简介页面。
Go Komura
小村软件有限公司 代表
以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。