PowerShell 技能连载 - IIS 管理自动化

适用于 PowerShell 5.1 及以上版本(Windows),需要 WebAdministration 模块

IIS(Internet Information Services)是 Windows Server 上的 Web 服务器角色,承载着企业内部的 Web 应用、API 服务和外部网站。在多站点环境中,手动管理 IIS 配置既繁琐又容易出错——创建网站、配置绑定、管理应用池、设置 SSL 证书,每个操作都需要反复点击 IIS 管理器。PowerShell 的 WebAdministration 模块将所有这些操作变成了可脚本化的命令,让 IIS 管理实现真正的自动化。

本文将介绍 IIS 的常用管理操作和批量自动化方案。

站点与应用池管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
Import-Module WebAdministration

# 查询所有网站状态
Get-Website | Select-Object Name, State,
@{N='Bindings'; E={ ($_.Bindings.Collection | ForEach-Object { $_.Protocol + "://" + $_.BindingInformation }) -join ", " }},
@{N='AppPool'; E={ $_.ApplicationPool }},
PhysicalPath |
Format-Table -AutoSize

# 查询应用池状态
Get-IISAppPool | Select-Object Name, State,
@{N='Runtime'; E={ $_.ManagedRuntimeVersion }},
@{N='Pipeline'; E={ $_.ManagedPipelineMode }},
@{N='Identity'; E={ $_.ProcessModel.IdentityType }} |
Format-Table -AutoSize

# 创建新网站(含应用池)
function New-IISWebSite {
param(
[Parameter(Mandatory)]
[string]$SiteName,

[Parameter(Mandatory)]
[string]$PhysicalPath,

[int]$Port = 80,
[string]$HostHeader = "",
[string]$RuntimeVersion = "v4.0"
)

# 创建物理目录
if (-not (Test-Path $PhysicalPath)) {
New-Item $PhysicalPath -ItemType Directory -Force | Out-Null
}

# 创建应用池
$appPoolName = "${SiteName}_Pool"
if (-not (Get-IISAppPool -Name $appPoolName -ErrorAction SilentlyContinue)) {
New-WebAppPool -Name $appPoolName
Set-ItemProperty "IIS:\AppPools\$appPoolName" -Name managedRuntimeVersion -Value $RuntimeVersion
Write-Host "已创建应用池:$appPoolName" -ForegroundColor Green
}

# 创建网站
$bindingInfo = "*:${Port}:${HostHeader}"
if (-not (Get-Website -Name $SiteName -ErrorAction SilentlyContinue)) {
New-Website -Name $SiteName -PhysicalPath $PhysicalPath `
-ApplicationPool $appPoolName -Port $Port -HostHeader $HostHeader -Force
Write-Host "已创建网站:$SiteName(端口 $Port)" -ForegroundColor Green
}

# 设置默认文档
Add-WebConfiguration "system.webServer/defaultDocument/files" -Value "index.html" -PSPath "IIS:\Sites\$SiteName" -ErrorAction SilentlyContinue
}

New-IISWebSite -SiteName "MyApp" -PhysicalPath "C:\inetpub\MyApp" -Port 8080 -HostHeader "myapp.local"

# 启动/停止/回收
Start-Website -Name "MyApp"
# Stop-Website -Name "MyApp"
# Restart-WebAppPool -Name "MyApp_Pool"

执行结果示例:

1
2
3
4
5
6
7
Name        State Bindings                  AppPool     PhysicalPath
---- ----- -------- ------- ------------
Default Web Site Started http://*:80 DefaultAppPool C:\inetpub\wwwroot
MyApp Started http://*:8080:myapp.local MyApp_Pool C:\inetpub\MyApp

已创建应用池:MyApp_Pool
已创建网站:MyApp(端口 8080)

SSL 证书与绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# 为网站配置 HTTPS 绑定
function Add-IISHttpsBinding {
param(
[Parameter(Mandatory)]
[string]$SiteName,

[Parameter(Mandatory)]
[string]$Domain,

[int]$Port = 443,

[string]$CertStore = "WebHosting"
)

# 查找证书
$cert = Get-ChildItem "Cert:\LocalMachine\$CertStore" |
Where-Object { $_.Subject -match $Domain -and $_.NotAfter -gt (Get-Date) } |
Sort-Object NotAfter -Descending |
Select-Object -First 1

if (-not $cert) {
Write-Host "未找到 $Domain 的有效证书" -ForegroundColor Red
return
}

Write-Host "使用证书:$($cert.Subject)(过期:$($cert.NotAfter.ToString('yyyy-MM-dd')))" -ForegroundColor Cyan

# 添加 HTTPS 绑定
New-WebBinding -Name $SiteName -Protocol "https" -Port $Port -HostHeader $Domain
$binding = Get-WebBinding -Name $SiteName -Protocol "https" -Port $Port -HostHeader $Domain
$binding.AddSslCertificate($cert.Thumbprint, $certStore)

Write-Host "HTTPS 绑定已添加:$Domain`:$Port" -ForegroundColor Green
}

# 证书到期检查
function Get-IISCertExpiryReport {
$certs = Get-ChildItem "Cert:\LocalMachine\My" |
Where-Object { $_.HasPrivateKey -and $_.NotAfter -gt (Get-Date) }

$report = foreach ($cert in $certs) {
$daysLeft = ($cert.NotAfter - (Get-Date)).Days

[PSCustomObject]@{
Subject = $cert.Subject
Issuer = $cert.Issuer
Expires = $cert.NotAfter.ToString('yyyy-MM-dd')
DaysLeft = $daysLeft
Thumbprint = $cert.Thumbprint.Substring(0, 16) + "..."
Status = if ($daysLeft -le 30) { "CRITICAL" } elseif ($daysLeft -le 90) { "WARNING" } else { "OK" }
}
}

$report | Sort-Object DaysLeft | Format-Table -AutoSize

$critical = $report | Where-Object { $_.Status -eq "CRITICAL" }
if ($critical) {
Write-Host "$($critical.Count) 个证书即将过期!" -ForegroundColor Red
}
}

Get-IISCertExpiryReport

# HTTP 到 HTTPS 重定向
$configPath = "IIS:\Sites\MyApp"
Add-WebConfigurationProperty -PSPath $configPath -Filter "system.webServer/rewrite/rules" -Name "." -Value @{
name = "HTTP to HTTPS"
stopProcessing = $true
} -ErrorAction SilentlyContinue

执行结果示例:

1
2
3
4
5
6
7
8
使用证书:CN=myapp.local(过期:2026-03-15)
HTTPS 绑定已添加:myapp.local:443
Subject Issuer Expires DaysLeft Thumbprint Status
------- ----- ------- -------- ---------- ------
CN=myapp.local CN=MyCA 2026-03-15 192 a1b2c3d4e5f6... OK
CN=api.contoso.com CN=Let's Encrypt 2025-11-20 76 f1e2d3c4b5a6... WARNING
CN=old.contoso.com CN=MyCA 2025-10-01 6 b2c3d4e5f6a1... CRITICAL
1 个证书即将过期!

批量部署与监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# 批量部署网站
function Deploy-WebApplications {
param(
[string[]]$Servers = @("SRV01", "SRV02"),
[string]$ConfigPath = "C:\Config\webapps.json"
)

$apps = Get-Content $ConfigPath -Raw | ConvertFrom-Json

foreach ($server in $Servers) {
Write-Host "`n--- 部署到 $server ---" -ForegroundColor Cyan

Invoke-Command -ComputerName $server -ScriptBlock {
param($appList)

Import-Module WebAdministration

foreach ($app in $appList) {
$siteName = $app.SiteName
$appPool = "${siteName}_Pool"
$path = $app.PhysicalPath

# 确保目录存在
if (-not (Test-Path $path)) {
New-Item $path -ItemType Directory -Force | Out-Null
}

# 创建或更新应用池
if (-not (Get-IISAppPool -Name $appPool -ErrorAction SilentlyContinue)) {
New-WebAppPool -Name $appPool
}
Set-ItemProperty "IIS:\AppPools\$appPool" -Name managedRuntimeVersion -Value $app.Runtime

# 创建或更新网站
if (Get-Website -Name $siteName -ErrorAction SilentlyContinue) {
Set-ItemProperty "IIS:\Sites\$siteName" -Name physicalPath -Value $path
Write-Host " 已更新:$siteName" -ForegroundColor Yellow
} else {
New-Website -Name $siteName -PhysicalPath $path `
-ApplicationPool $appPool -Port $app.Port -Force
Write-Host " 已创建:$siteName" -ForegroundColor Green
}
}

} -ArgumentList (,$apps) -ErrorAction Stop
}
}

# IIS 健康检查
function Get-IISHealthReport {
$sites = Get-Website
$pools = Get-IISAppPool

$siteReport = foreach ($site in $sites) {
[PSCustomObject]@{
Site = $site.Name
State = $site.State
AppPool = $site.ApplicationPool
Requests = (Get-Counter "\Web Service($($site.Name))\Total Method Requests/sec" -ErrorAction SilentlyContinue).CounterSamples.CookedValue
}
}

$poolReport = foreach ($pool in $pools) {
$workerProcesses = Get-Process -Name "w3wp" -ErrorAction SilentlyContinue |
Where-Object { $_.MainWindowTitle -match $pool.Name }

[PSCustomObject]@{
AppPool = $pool.Name
State = $pool.State
Runtime = $pool.ManagedRuntimeVersion
MemoryMB = if ($workerProcesses) { [math]::Round(($workerProcesses | Measure-Object WorkingSet64 -Sum).Sum / 1MB, 2) } else { 0 }
}
}

Write-Host "=== 网站状态 ===" -ForegroundColor Cyan
$siteReport | Format-Table -AutoSize

Write-Host "=== 应用池状态 ===" -ForegroundColor Cyan
$poolReport | Format-Table -AutoSize

$stopped = $sites | Where-Object { $_.State -ne "Started" }
if ($stopped) {
Write-Host "警告:$($stopped.Count) 个网站未运行" -ForegroundColor Red
}
}

Get-IISHealthReport

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
=== 网站状态 ===
Site State AppPool Requests
---- ----- ------- --------
Default Web Site Started DefaultAppPool 2.5
MyApp Started MyApp_Pool 12.3

=== 应用池状态 ===
AppPool State Runtime MemoryMB
------- ----- ------- --------
DefaultAppPool Started v4.0 45.6
MyApp_Pool Started v4.0 128.3

注意事项

  1. 权限要求:IIS 管理操作需要管理员权限,远程管理还需启用 WinRM 和 IIS 远程管理服务
  2. 应用池隔离:不同应用使用独立的应用池,避免一个应用崩溃影响其他应用
  3. 配置备份:修改 IIS 配置前使用 %windir%\system32\inetsrv\appcmd.exe add backup 创建备份
  4. 日志管理:IIS 日志文件增长迅速,应配置日志滚动和定期清理策略
  5. 绑定冲突:同一端口和主机头的绑定只能有一个,部署前检查绑定冲突
  6. 回收策略:应用池默认 29 小时回收一次,可根据应用特点调整或禁用(配合健康检查)

PowerShell 技能连载 - IIS Web 服务器管理

适用于 Windows Server 2016 及以上版本,需安装 IIS 管理工具

Internet Information Services(IIS)是 Windows 上最流行的 Web 服务器,广泛用于托管 ASP.NET 应用、静态网站和反向代理。IIS 管理器(GUI)虽然直观,但在管理多台服务器或执行批量操作时效率极低。PowerShell 的 WebAdministration 模块提供了完整的 IIS 管理能力,可以实现网站创建、应用池管理、绑定配置和性能调优的全面自动化。

本文将讲解 IIS 的 PowerShell 管理技巧,涵盖日常运维的各个场景。

IIS 管理环境准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 安装 IIS 和管理工具(Windows Server)
Install-WindowsFeature -Name Web-Server, Web-Mgmt-Tools, Web-Scripting-Tools

# 导入 WebAdministration 模块
Import-Module WebAdministration

# 查看 IIS 站点列表
Get-Website | Select-Object Name, State, PhysicalPath,
@{N='绑定'; E={($_.bindings.Collection | ForEach-Object { $_.protocol + '://' + $_.bindingInformation }) -join ', '}} |
Format-Table -AutoSize

# 查看应用池
Get-IISAppPool | Select-Object Name, State,
@{N='.NET版本'; E={$_.managedRuntimeVersion}},
@{N='管线模式'; E={$_.managedPipelineMode}} |
Format-Table -AutoSize

执行结果示例:

1
2
3
4
5
6
7
8
9
Name       State   PhysicalPath               绑定
---- ----- ------------ ----
Default Started C:\inetpub\wwwroot http://*:80:
MyApp Started D:\Apps\MyApp http://*:8080:, https://*:443:

Name State .NET版本 管线模式
---- ----- -------- --------
DefaultAppPool Started v4.0 Integrated
MyAppPool Started v4.0 Integrated

网站与应用池管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 创建新的应用池
New-IISAppPool -Name "NewAppPool" -managedRuntimeVersion "v4.0"
Set-ItemProperty IIS:\AppPools\NewAppPool -Name processModel.identityType -Value ApplicationPoolIdentity
Set-ItemProperty IIS:\AppPools\NewAppPool -Name recycling.periodicRestart.time -Value "00:00:00"
Write-Host "应用池已创建:NewAppPool" -ForegroundColor Green

# 创建新网站
$siteParams = @{
Name = "MyNewSite"
PhysicalPath = "D:\Apps\MyNewSite"
Port = 8090
ApplicationPool = "NewAppPool"
}
New-IISSite @siteParams
Write-Host "网站已创建:MyNewSite" -ForegroundColor Green

# 配置 HTTPS 绑定
$cert = Get-ChildItem Cert:\LocalMachine\My |
Where-Object { $_.Subject -match 'myapp.contoso.com' } |
Select-Object -First 1

New-IISSiteBinding -Name "MyNewSite" -BindingInformation "*:443:myapp.contoso.com" `
-Protocol https -CertificateThumbPrint $cert.Thumbprint -CertStoreLocation "Cert:\LocalMachine\My"

# 启动/停止/重启网站
Start-Website -Name "MyNewSite"
Stop-Website -Name "MyNewSite"
Restart-WebAppPool -Name "NewAppPool"

# 删除网站和应用池
Remove-IISSite -Name "MyNewSite" -Confirm:$false
Remove-IISAppPool -Name "NewAppPool" -Confirm:$false

执行结果示例:

1
2
应用池已创建:NewAppPool
网站已创建:MyNewSite

应用池健康监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function Get-AppPoolHealthReport {
<#
.SYNOPSIS
生成 IIS 应用池健康报告
#>

$appPools = Get-IISAppPool
$report = foreach ($pool in $appPools) {
$workerProcesses = Get-Process -Name w3wp -ErrorAction SilentlyContinue |
Where-Object { $_.AppPoolName -eq $pool.Name }

$totalMemory = 0
$totalCPU = 0
$wpCount = 0

foreach ($wp in $workerProcesses) {
$totalMemory += $wp.WorkingSet64
$totalCPU += $wp.CPU
$wpCount++
}

[PSCustomObject]@{
名称 = $pool.Name
状态 = $pool.State
工作进程 = $wpCount
内存MB = [math]::Round($totalMemory / 1MB, 2)
运行时 = $pool.managedRuntimeVersion
身份 = $pool.processModel.identityType
上次回收 = (Get-ItemProperty "IIS:\AppPools\$($pool.Name)" -Name recycling.logEventOnRecycle -ErrorAction SilentlyContinue).Value
}
}

$report | Format-Table -AutoSize
}

Get-AppPoolHealthReport

执行结果示例:

1
2
3
4
名称            状态   工作进程 内存MB   运行时 身份
---- ---- -------- ------ ------ ----
DefaultAppPool Started 1 85.32 v4.0 ApplicationPoolIdentity
MyAppPool Started 2 345.67 v4.0 NetworkService

IIS 配置导出与迁移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 导出 IIS 配置
function Export-IISConfig {
param([string]$OutputPath = "C:\Config\IIS-Backup")

New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null

# 导出应用池配置
Get-IISAppPool | Export-Clixml -Path "$OutputPath\AppPools.xml"

# 导出网站配置
Get-Website | Export-Clixml -Path "$OutputPath\Websites.xml"

# 导出 IIS 配置文件
Copy-Item "$env:windir\System32\inetsrv\config\applicationHost.config" `
"$OutputPath\applicationHost.config.bak"

Write-Host "IIS 配置已导出到:$OutputPath" -ForegroundColor Green
}

# 查看 IIS 日志
function Get-IISLogSummary {
param(
[string]$SiteName = "Default Web Site",
[int]$TopUrls = 10
)

$logPath = (Get-Website -Name $SiteName).logfile.directory
$logPath = $logPath -replace '%SystemDrive%', $env:SystemDrive

$latestLog = Get-ChildItem $logPath -Filter "*.log" -Recurse |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1

Write-Host "分析日志:$($latestLog.FullName)" -ForegroundColor Cyan

# 解析 W3C 格式日志
$header = Get-Content $latestLog.FullName |
Where-Object { $_ -match '^#Fields:' } |
Select-Object -First 1

$fields = ($header -replace '^#Fields:\s*', '') -split '\s+'

$entries = Get-Content $latestLog.FullName -Tail 10000 |
Where-Object { $_ -notmatch '^#' -and $_.Trim() } |
ConvertFrom-Csv -Delimiter ' ' -Header $fields

$entries | Group-Object 'cs-uri-stem' |
Sort-Object Count -Descending |
Select-Object -First $TopUrls Count, Name |
Format-Table -AutoSize
}

Get-IISLogSummary -SiteName "MyApp" -TopUrls 10

执行结果示例:

1
2
3
4
5
6
7
8
9
10
IIS 配置已导出到:C:\Config\IIS-Backup

分析日志:C:\inetpub\logs\LogFiles\W3SVC2\ex250611.log

Count Name
----- ----
2345 /api/users
1876 /api/products
987 /static/css/main.css
654 /api/orders

注意事项

  1. 应用池回收:配置合理的回收间隔。默认 29 小时回收一次可能导致请求中断,建议在低峰期回收
  2. 权限配置:应用池的 Identity 账户需要对网站目录有读取权限
  3. HTTPS 证书:使用 Let’s Encrypt 或组织内部 CA 证书时,确保证书自动续期机制正常
  4. 日志管理:IIS 日志增长很快,配置日志轮转和定期归档清理
  5. Web.config 继承:子目录的 web.config 会继承父目录的配置,可能导致冲突。使用 <location> 标签控制继承范围
  6. 32位应用:如果应用需要 32 位模式,在应用池中设置 enable32BitAppOnWin64true