适用于 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 WebAdministrationGet-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"
执行结果示例:
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 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 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 $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.. . OKCN =api.contoso.com CN =Let's Encrypt 2025-11-20 76 f1e2d3c4b5a6.. . WARNING CN =old.contoso.com CN =MyCA 2025-10-01 6 b2c3d4e5f6a1.. . CRITICAL1 个证书即将过期!
批量部署与监控 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 } } 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 / 1 MB, 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
注意事项
权限要求 :IIS 管理操作需要管理员权限,远程管理还需启用 WinRM 和 IIS 远程管理服务
应用池隔离 :不同应用使用独立的应用池,避免一个应用崩溃影响其他应用
配置备份 :修改 IIS 配置前使用 %windir%\system32\inetsrv\appcmd.exe add backup 创建备份
日志管理 :IIS 日志文件增长迅速,应配置日志滚动和定期清理策略
绑定冲突 :同一端口和主机头的绑定只能有一个,部署前检查绑定冲突
回收策略 :应用池默认 29 小时回收一次,可根据应用特点调整或禁用(配合健康检查)