前言

最近在进行 Windows 服务器迁移时,遇到了一个比较经典的问题。

将网站文件夹直接复制到新的服务器后,网站能够正常访问,但是很多功能全部失效,例如:

  • 文件上传失败

  • 文件下载失败

  • PDF、Excel、Zip 等文件无法生成

  • 日志无法写入

  • 图片无法保存

  • 临时文件创建失败

  • 删除文件时报 Access Denied

应用程序没有报编译错误,也没有出现 IIS 启动失败,而是在执行涉及文件操作时不断提示:

Access to the path is denied.

或者

UnauthorizedAccessException

又或者

Access is denied.

排查了很久之后,最终发现原因其实非常简单。


问题原因

Windows 文件夹不仅仅包含文件内容,还包含 NTFS 权限(ACL)

如果网站目录采用下面几种方式迁移:

  • Windows 复制

  • WinSCP

  • FTP

  • 压缩包

  • Robocopy(未复制 ACL)

那么原来的 IIS 权限通常都会丢失。

例如:

D:\Website\ProjectA

原服务器可能拥有:

IIS AppPool\ProjectAPool

的修改权限(Modify)。

但是迁移到新服务器以后,只剩下:

Administrators
SYSTEM
Users

而真正运行网站的 Application Pool 身份已经没有权限了。

因此:

  • 可以读取页面

  • 但是不能写文件

  • 不能删除文件

  • 不能创建目录

  • 不能上传文件

这就是为什么首页正常,但是上传下载全部失败。


为什么只影响上传下载?

因为 IIS 默认只需要读取网站文件即可。

例如:

wwwroot
    index.html
    css
    js

浏览网页只需要:

Read

权限即可。

但是上传文件时,需要:

Modify

权限。

例如:

wwwroot
    upload
        image.jpg

如果程序池账号没有 Modify 权限,就会直接失败。


手动修改权限的问题

当然,也可以手动修改。

例如:

右键文件夹

属性
    安全
        编辑
            添加

输入:

IIS AppPool\程序池名称

然后给予:

Modify

权限即可。

但是如果服务器上有:

  • 十几个网站

  • 几十个网站

  • 上百个站点

一个一个添加权限显然效率太低。

于是可以利用 PowerShell 一次性完成。


PowerShell 自动修复所有 IIS 站点权限

下面这个脚本会:

  1. 自动读取所有 IIS Site

  2. 获取对应的 Application Pool

  3. 获取站点物理目录

  4. 为对应程序池账号授予 Modify 权限

  5. 自动递归到所有子目录

Import-Module WebAdministration

# 获取所有 Application Pool 名称
$AppPools = Get-ChildItem IIS:\AppPools | Select-Object -ExpandProperty Name

# 获取所有 Site
$Sites = Get-Website

foreach ($Site in $Sites)
{
    $PhysicalPath = $Site.PhysicalPath

    if (-not (Test-Path $PhysicalPath))
    {
        Write-Host "Skip: $PhysicalPath"
        continue
    }

    Write-Host ""
    Write-Host "====================================="
    Write-Host "Site: $($Site.Name)"
    Write-Host "Path: $PhysicalPath"

    # 获取当前站点使用的程序池
    $Pool = $Site.applicationPool

    $Identity = "IIS AppPool\$Pool"

    Write-Host "Grant -> $Identity"

    icacls $PhysicalPath `
        /grant "$($Identity):(OI)(CI)M" `
        /T `
        /C `
        /Q

    Write-Host "Done."
}

Write-Host ""
Write-Host "All IIS Sites Permission Updated."

脚本原理解析

1、获取所有 IIS 站点

$Sites = Get-Website

等价于打开 IIS 管理器查看:

Sites

中的所有网站。


2、获取站点物理目录

$Site.PhysicalPath

例如:

D:\Website\CRM

D:\Website\API

D:\Website\Admin

3、获取程序池名称

$Pool = $Site.applicationPool

例如:

CRMPool

ApiPool

AdminPool

4、拼接程序池身份

$Identity = "IIS AppPool\$Pool"

最终得到:

IIS AppPool\CRMPool

这是 IIS 应用程序真正运行时使用的身份。


5、授予 Modify 权限

icacls $PhysicalPath `
    /grant "$($Identity):(OI)(CI)M"

其中:

参数

说明

OI

Object Inherit,文件继承

CI

Container Inherit,目录继承

M

Modify(修改权限)

因此:

(OI)(CI)M

表示:

当前目录及所有子目录、文件都授予 Modify 权限。


6、递归所有子目录

/T

表示:

Recursive

递归整个目录树。

否则只会修改根目录。


7、忽略错误继续执行

/C

即使某个目录权限修改失败,也不会中断整个脚本。

例如:

Site1 成功

Site2 成功

Site3 失败

Site4 继续执行

非常适合批量修复。


8、静默输出

/Q

关闭 icacls 的详细输出。

控制台只保留脚本中输出的日志,更清晰易读。


如何运行脚本

  1. 使用管理员身份启动 Windows PowerShell

  2. 将上述脚本保存为 Grant-IISPermissions.ps1

  3. 如系统限制脚本执行,可临时执行:

Set-ExecutionPolicy RemoteSigned -Scope Process
  1. 运行脚本:

.\Grant-IISPermissions.ps1

执行完成后,将看到类似输出:

=====================================
Site: CRM
Path: D:\Website\CRM
Grant -> IIS AppPool\CRMPool
Done.

=====================================
Site: API
Path: D:\Website\API
Grant -> IIS AppPool\ApiPool
Done.

All IIS Sites Permission Updated.

修复完成后建议验证

建议重点验证以下功能是否恢复正常:

  • 文件上传

  • 文件下载

  • 图片保存

  • PDF 导出

  • Excel 导出

  • ZIP 压缩生成

  • 日志写入

  • 临时文件创建

  • 删除或覆盖文件

如果上述功能均恢复正常,基本可以确认问题由目录权限缺失导致。


总结

Windows 服务器迁移站点后,网站能够访问但上传、下载、导出等涉及文件写入的功能异常,大多数情况下都是 IIS 应用程序池账号缺少目标目录的 NTFS Modify 权限所致。

相比逐个站点手动配置权限,本文提供的 PowerShell 脚本能够自动遍历所有 IIS 网站,为对应的应用程序池授予正确的目录权限,特别适用于多站点服务器迁移后的批量修复场景。

建议在每次完成网站迁移后,将此脚本作为标准运维检查项执行一次,可以有效避免因权限问题导致的各类线上故障。