Skip to content

Commit 5392ba3

Browse files
authored
Merge pull request #2545 from microsoft/dpaul-HcPesterIIS
HealthChecker: Add outbound IIS URL rewrite rule detection
2 parents 1c0563f + b83bd3b commit 5392ba3

8 files changed

Lines changed: 500 additions & 30 deletions

File tree

Diagnostics/HealthChecker/Analyzer/Get-URLRewriteRule.ps1

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33

44
<#
55
.SYNOPSIS
6-
Pulls out URL Rewrite Rules from the web.config and applicationHost.config file to return a Hashtable of those settings.
6+
Pulls out URL Rewrite Rules from the web.config and applicationHost.config file to return inbound and outbound rules.
77
.DESCRIPTION
88
This is a function that is designed to pull out the URL Rewrite Rules that are set on a location of IIS.
9+
It extracts both inbound rules (from rewrite/rules) and outbound rules (from rewrite/outboundRules).
910
Because you can set it on an individual web.config file or the parent site(s), or the ApplicationHostConfig file for the location
1011
We need to check all locations to properly determine what is all set.
1112
The ApplicationHostConfig file must be able to be converted to Xml, but the web.config file doesn't.
@@ -14,11 +15,12 @@
1415
2. ApplicationHost.config file for the same location
1516
3. Then move up one level (Default Web Site/mapi -> Default Web Site) and repeat 1 and 2 till no more locations.
1617
a. If the 'clear' flag was set at any point, we stop at that location in the process.
18+
b. Inbound and outbound rules track the 'clear' flag independently.
1719
4. Then there is a global setting in the ApplicationHost.config file.
1820
#>
1921
function Get-URLRewriteRule {
2022
[CmdletBinding()]
21-
[OutputType([hashtable])]
23+
[OutputType([PSCustomObject])]
2224
param(
2325
[Parameter(Mandatory = $true)]
2426
[System.Xml.XmlNode]$ApplicationHostConfig,
@@ -31,51 +33,82 @@ function Get-URLRewriteRule {
3133
begin {
3234
Write-Verbose "Calling: $($MyInvocation.MyCommand)"
3335
$urlRewriteRules = @{}
36+
$urlOutboundRewriteRules = @{}
3437
$appHostConfigLocations = $ApplicationHostConfig.configuration.Location.path
3538
}
3639
process {
3740
foreach ($key in $WebConfigContent.Keys) {
3841
Write-Verbose "Working on key: $key"
3942
$continue = $true
40-
$clear = $false
43+
$clearInbound = $false
44+
$clearOutbound = $false
4145
$currentKey = $key
4246
$urlRewriteRules.Add($key, (New-Object System.Collections.Generic.List[object]))
47+
$urlOutboundRewriteRules.Add($key, (New-Object System.Collections.Generic.List[object]))
4348

4449
do {
4550
Write-Verbose "Working on currentKey: $currentKey"
4651
try {
4752
# the Web.config is looked at first
4853
[xml]$content = $WebConfigContent[$currentKey]
49-
$rules = $content.configuration.'system.webServer'.rewrite.rules
5054

51-
if ($null -ne $rules) {
52-
$clear = $null -ne $rules.clear
53-
$urlRewriteRules[$key].Add($rules)
54-
} else {
55-
Write-Verbose "No rewrite rules in the config file"
55+
if (-not $clearInbound) {
56+
$rules = $content.configuration.'system.webServer'.rewrite.rules
57+
58+
if ($null -ne $rules) {
59+
$clearInbound = $null -ne $rules.clear
60+
$urlRewriteRules[$key].Add($rules)
61+
} else {
62+
Write-Verbose "No inbound rewrite rules in the config file"
63+
}
64+
}
65+
66+
if (-not $clearOutbound) {
67+
$outboundRules = $content.configuration.'system.webServer'.rewrite.outboundRules
68+
69+
if ($null -ne $outboundRules) {
70+
$clearOutbound = $null -ne $outboundRules.clear
71+
$urlOutboundRewriteRules[$key].Add($outboundRules)
72+
} else {
73+
Write-Verbose "No outbound rewrite rules in the config file"
74+
}
5675
}
5776
} catch {
5877
Write-Verbose "Failed to convert to xml"
5978
Invoke-CatchActions
6079
}
6180

62-
if (-not $clear) {
81+
if (-not $clearInbound -or -not $clearOutbound) {
6382
# Now need to look at the applicationHost.config file to determine what is set at that location.
6483
# need to do this because of the case sensitive query to get the xmlNode
65-
Write-Verbose "clear not set on config. Looking at the applicationHost.config file"
84+
Write-Verbose "Looking at the applicationHost.config file"
6685
$appKey = $appHostConfigLocations | Where-Object { $_ -eq $currentKey }
6786

6887
if ($appKey.Count -eq 1) {
6988
$location = $ApplicationHostConfig.SelectNodes("/configuration/location[@path = '$appKey']")
7089

7190
if ($null -ne $location) {
72-
$rules = $location.'system.webServer'.rewrite.rules
7391

74-
if ($null -ne $rules) {
75-
$clear = $null -ne $rules.clear
76-
$urlRewriteRules[$key].Add($rules)
77-
} else {
78-
Write-Verbose 'No rewrite rules in the applicationHost.config file'
92+
if (-not $clearInbound) {
93+
$rules = $location.'system.webServer'.rewrite.rules
94+
95+
if ($null -ne $rules) {
96+
$clearInbound = $null -ne $rules.clear
97+
$urlRewriteRules[$key].Add($rules)
98+
} else {
99+
Write-Verbose "No inbound rewrite rules in the applicationHost.config file"
100+
}
101+
}
102+
103+
if (-not $clearOutbound) {
104+
$outboundRules = $location.'system.webServer'.rewrite.outboundRules
105+
106+
if ($null -ne $outboundRules) {
107+
$clearOutbound = $null -ne $outboundRules.clear
108+
$urlOutboundRewriteRules[$key].Add($outboundRules)
109+
} else {
110+
Write-Verbose "No outbound rewrite rules in the applicationHost.config file"
111+
}
79112
}
80113
} else {
81114
Write-Verbose "We didn't find the location for '$appKey' in the applicationHostConfig. This shouldn't occur."
@@ -85,21 +118,34 @@ function Get-URLRewriteRule {
85118
}
86119
}
87120

88-
if ($clear) {
89-
Write-Verbose "Clear was set, don't need to know what else was set."
121+
if ($clearInbound -and $clearOutbound) {
122+
Write-Verbose "Clear was set for both inbound and outbound, don't need to know what else was set."
90123
$continue = $false
91124
} else {
92125
$index = $currentKey.LastIndexOf("/")
93126

94127
if ($index -eq -1) {
95128
$continue = $false
96-
# look at the global configuration of the applicationHost.config file
97-
$rules = $ApplicationHostConfig.configuration.'system.webServer'.rewrite.rules
98129

99-
if ($null -ne $rules) {
100-
$urlRewriteRules[$key].Add($rules)
101-
} else {
102-
Write-Verbose "No global configuration for rewrite rules."
130+
if (-not $clearInbound) {
131+
# look at the global configuration of the applicationHost.config file
132+
$rules = $ApplicationHostConfig.configuration.'system.webServer'.rewrite.rules
133+
134+
if ($null -ne $rules) {
135+
$urlRewriteRules[$key].Add($rules)
136+
} else {
137+
Write-Verbose "No global configuration for inbound rewrite rules."
138+
}
139+
}
140+
141+
if (-not $clearOutbound) {
142+
$outboundRules = $ApplicationHostConfig.configuration.'system.webServer'.rewrite.outboundRules
143+
144+
if ($null -ne $outboundRules) {
145+
$urlOutboundRewriteRules[$key].Add($outboundRules)
146+
} else {
147+
Write-Verbose "No global configuration for outbound rewrite rules."
148+
}
103149
}
104150
} else {
105151
$currentKey = $currentKey.Substring(0, $index)
@@ -111,6 +157,9 @@ function Get-URLRewriteRule {
111157
}
112158
}
113159
end {
114-
return $urlRewriteRules
160+
return [PSCustomObject]@{
161+
Inbound = $urlRewriteRules
162+
Outbound = $urlOutboundRewriteRules
163+
}
115164
}
116165
}

Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -416,10 +416,12 @@ function Invoke-AnalyzerIISInformation {
416416
WebConfigContent = $iisWebConfigContent
417417
}
418418

419-
$urlRewriteRules = $null
419+
$urlRewriteResult = $null
420420
$ipFilterSettings = $null
421421
$authTypeSettings = $null
422-
Get-URLRewriteRule @ruleParams | Invoke-RemotePipelineHandler -Result ([ref]$urlRewriteRules)
422+
Get-URLRewriteRule @ruleParams | Invoke-RemotePipelineHandler -Result ([ref]$urlRewriteResult)
423+
$urlRewriteRules = $urlRewriteResult.Inbound
424+
$urlOutboundRewriteRules = $urlRewriteResult.Outbound
423425
Get-IPFilterSetting -ApplicationHostConfig ([xml]$applicationHostConfig) | Invoke-RemotePipelineHandler -Result ([ref]$ipFilterSettings)
424426
Get-IISAuthenticationType -ApplicationHostConfig ([xml]$applicationHostConfig) | Invoke-RemotePipelineHandler -Result ([ref]$authTypeSettings)
425427
$failedLocationsForAuth = @()
@@ -440,6 +442,7 @@ function Invoke-AnalyzerIISInformation {
440442
$epValue = "None"
441443
$ep = $extendedProtectionConfiguration | Where-Object { $_.VirtualDirectoryName -eq $location.Path }
442444
$currentRewriteRules = $urlRewriteRules[$location.Path]
445+
$currentOutboundRewriteRules = $urlOutboundRewriteRules[$location.Path]
443446
$authentication = $authTypeSettings[$location.Path]
444447

445448
if ($currentRewriteRules.Count -ne 0) {
@@ -455,7 +458,23 @@ function Invoke-AnalyzerIISInformation {
455458
}
456459

457460
$displayRewriteRules = ($currentRewriteRules.rule | Where-Object { $_.enabled -ne "false" }).name |
458-
Where-Object { $_ -notcontains $excludeRules }
461+
Where-Object { $_ -notin $excludeRules }
462+
}
463+
464+
$displayOutboundRewriteRules = [string]::Empty
465+
466+
if ($currentOutboundRewriteRules.Count -ne 0) {
467+
$excludeOutboundRules = @()
468+
foreach ($rule in $currentOutboundRewriteRules) {
469+
$remove = $rule.Remove
470+
471+
if ($null -ne $remove) {
472+
$excludeOutboundRules += $remove.Name
473+
}
474+
}
475+
476+
$displayOutboundRewriteRules = ($currentOutboundRewriteRules.rule | Where-Object { $_.enabled -ne "false" }).name |
477+
Where-Object { $_ -notin $excludeOutboundRules }
459478
}
460479

461480
if ($null -ne $ep) {
@@ -484,7 +503,8 @@ function Invoke-AnalyzerIISInformation {
484503
ExtendedProtection = $epValue
485504
SslFlags = $sslFlag
486505
IPFilteringEnabled = $ipFilterEnabled
487-
URLRewrite = $displayRewriteRules
506+
InURLRewrite = $displayRewriteRules
507+
OutURLRewrite = $displayOutboundRewriteRules
488508
Authentication = $authentication
489509
})
490510
}
@@ -635,6 +655,7 @@ function Invoke-AnalyzerIISInformation {
635655
IndentSpaces = 8
636656
})
637657
AddHtmlDetailRow = $false
658+
TestingName = "Inbound URL Rewrite Rules"
638659
}
639660
Add-AnalyzedResultInformation @params
640661

@@ -651,6 +672,68 @@ function Invoke-AnalyzerIISInformation {
651672
}
652673
}
653674

675+
# Display Outbound URL Rewrite Rules.
676+
# Same deduplication pattern as inbound - don't display rules on multiple vDirs by same name.
677+
$alreadyDisplayedOutboundRules = @{}
678+
$outboundDisplayKey = "DisplayKey"
679+
$alreadyDisplayedOutboundRules.Add($outboundDisplayKey, (New-Object System.Collections.Generic.List[object]))
680+
681+
foreach ($key in $urlOutboundRewriteRules.Keys) {
682+
$currentSection = $urlOutboundRewriteRules[$key]
683+
684+
if ($currentSection.Count -ne 0) {
685+
foreach ($rule in $currentSection.rule) {
686+
687+
if ($null -eq $rule) {
688+
Write-Verbose "Outbound rule is NULL skipping."
689+
continue
690+
} elseif ($rule.enabled -eq "false") {
691+
Write-Verbose "skipping over disabled outbound rule: $($rule.Name) for vDir '$key'"
692+
continue
693+
}
694+
695+
$displayObject = [PSCustomObject]@{
696+
RewriteRuleName = $rule.name
697+
ServerVariable = $rule.match.serverVariable
698+
MatchPattern = $rule.match.pattern
699+
PreCondition = $rule.preCondition
700+
ActionType = $rule.action.type
701+
}
702+
703+
if (-not ($alreadyDisplayedOutboundRules.ContainsKey((($displayObject.RewriteRuleName))))) {
704+
$alreadyDisplayedOutboundRules.Add($displayObject.RewriteRuleName, $displayObject)
705+
$alreadyDisplayedOutboundRules[$outboundDisplayKey].Add($displayObject)
706+
}
707+
}
708+
}
709+
}
710+
711+
if ($alreadyDisplayedOutboundRules[$outboundDisplayKey].Count -gt 0) {
712+
$params = $baseParams + @{
713+
OutColumns = ([PSCustomObject]@{
714+
DisplayObject = $alreadyDisplayedOutboundRules[$outboundDisplayKey]
715+
IndentSpaces = 8
716+
})
717+
AddHtmlDetailRow = $false
718+
TestingName = "Outbound URL Rewrite Rules"
719+
}
720+
Add-AnalyzedResultInformation @params
721+
}
722+
723+
$globalRules = ([xml]$applicationHostConfig).configuration.'system.webServer'.rewrite.globalRules
724+
725+
if ($null -ne $globalRules -and
726+
$null -ne $globalRules.rule) {
727+
$params = $baseParams + @{
728+
Name = "Global IIS Rewrite Rules Detected"
729+
Details = "Global URL Rewrite rules are defined in applicationHost.config and apply to all sites on this server." +
730+
"`r`n`t`tReview these rules to ensure they are expected and not interfering with Exchange traffic."
731+
DisplayWriteType = "Yellow"
732+
TestingName = "Global IIS Rewrite Rules"
733+
}
734+
Add-AnalyzedResultInformation @params
735+
}
736+
654737
foreach ($webApp in $iisWebApplications) {
655738
if ($correctLocations.ContainsKey($webApp.FriendlyName)) {
656739
if ($webApp.PhysicalPath -notlike "*$($correctLocations[$webApp.FriendlyName])") {

0 commit comments

Comments
 (0)