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 技能连载 - REST API 设计与实现

适用于 PowerShell 7.0 及以上版本

PowerShell 不仅能调用 API,还能创建 API。PolarisPode 等模块让 PowerShell 可以快速搭建 Web 服务器,处理 HTTP 请求。虽然不建议用 PowerShell 替代专业的 Web 框架,但在内网工具、运维自动化接口、轻量级中间服务等场景中,PowerShell API 服务器非常实用——快速实现一个健康检查接口、暴露系统信息给监控平台、提供配置查询 API。

本文将讲解如何使用 PowerShell 构建轻量级 REST API 服务。

使用 Pode 搭建 API

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
# 安装 Pode 模块
Install-Module -Name Pode -Force -Scope CurrentUser

# 创建 API 服务器脚本
$startApi = {
Start-PodeServer {
Add-PodeEndpoint -Address localhost -Port 8080 -Protocol Http

# 日志中间件
Add-PodeMiddleware -Name 'Logger' -ScriptBlock {
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
$method = $WebEvent.Method
$path = $WebEvent.Path
Write-PodeHost "[$timestamp] $method $path"
}

# GET /api/health —— 健康检查
Add-PodeRoute -Method Get -Path '/api/health' -ScriptBlock {
$os = Get-CimInstance Win32_OperatingSystem
$cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
$uptime = [math]::Round(((Get-Date) - $os.LastBootUpTime).TotalDays, 1)

Write-PodeJsonResponse -Value @{
status = 'healthy'
timestamp = (Get-Date).ToString('yyyy-MM-ddTHH:mm:ss')
computer = $env:COMPUTERNAME
uptime = "$uptime 天"
cpuUsage = "$($cpu.LoadPercentage)%"
memoryUsage = "$([math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / $os.TotalVisibleMemorySize * 100, 1))%"
}
}

# GET /api/processes —— 进程列表
Add-PodeRoute -Method Get -Path '/api/processes' -ScriptBlock {
$top = if ($WebEvent.Query['top']) { [int]$WebEvent.Query['top'] } else { 10 }
$sortBy = if ($WebEvent.Query['sort']) { $WebEvent.Query['sort'] } else { 'Memory' }

$processes = Get-Process | Sort-Object WorkingSet64 -Descending |
Select-Object -First $top |
ForEach-Object {
@{
name = $_.Name
pid = $_.Id
memoryMB = [math]::Round($_.WorkingSet64 / 1MB, 1)
cpu = [math]::Round($_.CPU, 2)
}
}

Write-PodeJsonResponse -Value @{
count = $processes.Count
data = $processes
}
}

# GET /api/disks —— 磁盘信息
Add-PodeRoute -Method Get -Path '/api/disks' -ScriptBlock {
$disks = Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" |
ForEach-Object {
$totalGB = [math]::Round($_.Size / 1GB, 2)
$freeGB = [math]::Round($_.FreeSpace / 1GB, 2)
@{
drive = $_.DeviceID
totalGB = $totalGB
freeGB = $freeGB
usagePct = [math]::Round(($_.Size - $_.FreeSpace) / $_.Size * 100, 1)
}
}

Write-PodeJsonResponse -Value @{ data = $disks }
}
}
}

Write-Host "API 服务器启动中..." -ForegroundColor Cyan
Write-Host "端点:" -ForegroundColor Yellow
Write-Host " GET http://localhost:8080/api/health" -ForegroundColor Green
Write-Host " GET http://localhost:8080/api/processes?top=5" -ForegroundColor Green
Write-Host " GET http://localhost:8080/api/disks" -ForegroundColor Green

执行结果示例:

1
2
3
4
5
6
API 服务器启动中...
端点:
GET http://localhost:8080/api/health
GET http://localhost:8080/api/processes?top=5
GET http://localhost:8080/api/disks
[2025-07-23 08:30:15] GET /api/health

POST 接口与请求处理

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
# 在同一 Pode 服务器中添加 POST 接口
$apiWithPost = {
Start-PodeServer {
Add-PodeEndpoint -Address localhost -Port 8081 -Protocol Http

# POST /api/execute —— 执行命令(需要认证)
Add-PodeRoute -Method Post -Path '/api/execute' -ScriptBlock {
$body = $WebEvent.Data

if (-not $body.command) {
Set-PodeResponseStatus -Code 400
Write-PodeJsonResponse -Value @{ error = '缺少 command 参数' }
return
}

# 安全限制:只允许特定命令
$allowed = @('Get-Process', 'Get-Service', 'Get-EventLog', 'Get-CimInstance')
$cmdName = ($body.command -split '\s+')[0]
if ($cmdName -notin $allowed) {
Set-PodeResponseStatus -Code 403
Write-PodeJsonResponse -Value @{ error = "命令不允许:$cmdName" }
return
}

try {
$result = Invoke-Expression $body.command -ErrorAction Stop
$jsonResult = $result | ConvertTo-Json -Depth 3 -Compress

Write-PodeJsonResponse -Value @{
success = $true
command = $body.command
result = $result
}
} catch {
Set-PodeResponseStatus -Code 500
Write-PodeJsonResponse -Value @{
success = $false
error = $_.Exception.Message
}
}
}

# POST /api/alert —— 接收告警
Add-PodeRoute -Method Post -Path '/api/alert' -ScriptBlock {
$body = $WebEvent.Data

$alertInfo = @{
timestamp = (Get-Date).ToString('yyyy-MM-ddTHH:mm:ss')
source = $body.source
level = $body.level
message = $body.message
hostname = $body.hostname
receivedAt = Get-Date -Format 'HH:mm:ss'
}

# 记录到文件
$alertInfo | ConvertTo-Json | Add-Content "C:\Logs\alerts.json" -Encoding UTF8

Write-PodeJsonResponse -Value @{
received = $true
id = [guid]::NewGuid().ToString()
}
}

# GET /api/alerts —— 查询告警
Add-PodeRoute -Method Get -Path '/api/alerts' -ScriptBlock {
$count = if ($WebEvent.Query['count']) { [int]$WebEvent.Query['count'] } else { 10 }
$level = $WebEvent.Query['level']

if (Test-Path "C:\Logs\alerts.json") {
$alerts = Get-Content "C:\Logs\alerts.json" -Tail $count |
ConvertFrom-Json |
Where-Object { if ($level) { $_.level -eq $level } else { $true } }

Write-PodeJsonResponse -Value @{
count = $alerts.Count
data = $alerts
}
} else {
Write-PodeJsonResponse -Value @{ count = 0; data = @() }
}
}
}
}

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
# 调用 POST 接口
$body = @{ command = "Get-Process | Select-Object -First 3 Name,Id" } | ConvertTo-Json
Invoke-RestMethod -Uri "http://localhost:8081/api/execute" -Method Post -Body $body -ContentType "application/json"

# 发送告警
$alert = @{
source = "monitor"
level = "ERROR"
message = "磁盘空间不足"
hostname = "SRV01"
} | ConvertTo-Json
Invoke-RestMethod -Uri "http://localhost:8081/api/alert" -Method Post -Body $alert -ContentType "application/json"

API 客户端封装

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
# 封装为可复用的 API 客户端函数
function Get-ServerHealth {
param([string]$ServerUrl = "http://localhost:8080")

try {
$response = Invoke-RestMethod -Uri "$ServerUrl/api/health" -TimeoutSec 5
return $response
} catch {
Write-Host "健康检查失败:$($_.Exception.Message)" -ForegroundColor Red
return $null
}
}

function Get-ServerProcesses {
param(
[string]$ServerUrl = "http://localhost:8080",
[int]$Top = 10
)

return Invoke-RestMethod -Uri "$ServerUrl/api/processes?top=$Top" -TimeoutSec 10
}

# 批量检查多台服务器
$servers = @(
"http://web-01:8080",
"http://web-02:8080",
"http://db-01:8080"
)

Write-Host "集群健康检查:" -ForegroundColor Cyan
foreach ($server in $servers) {
$health = Get-ServerHealth -ServerUrl $server
if ($health) {
$status = $health.status
$color = if ($status -eq "healthy") { "Green" } else { "Red" }
Write-Host " $server : $status (CPU: $($health.cpuUsage), MEM: $($health.memoryUsage))" -ForegroundColor $color
} else {
Write-Host " $server : 不可达" -ForegroundColor Red
}
}

执行结果示例:

1
2
3
4
集群健康检查:
http://web-01:8080 : healthy (CPU: 35%, MEM: 72.4%)
http://web-02:8080 : healthy (CPU: 28%, MEM: 65.1%)
http://db-01:8080 : healthy (CPU: 42%, MEM: 85.3%)

注意事项

  1. 安全风险:暴露命令执行接口极度危险,生产环境必须加认证、限白名单、审计日志
  2. 性能限制:PowerShell HTTP 服务器性能有限,不适合高并发场景(建议使用反向代理)
  3. 认证:生产 API 必须添加认证(API Key、JWT、Basic Auth),Pode 内置认证中间件
  4. HTTPS:生产环境使用 HTTPS,Pode 支持自签名证书和 Let’s Encrypt
  5. 服务化:长期运行的 API 应注册为 Windows 服务,使用 nssmRegister-PodeService
  6. 错误处理:API 接口必须有完善的错误处理和统一的错误响应格式

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