Skip to content

HealthChecker: Fix IIS URL rewrite rule display bugs#2548

Open
dpaulson45 wants to merge 3 commits into
mainfrom
dpaul-HcIISRewriteDisplayFixes
Open

HealthChecker: Fix IIS URL rewrite rule display bugs#2548
dpaulson45 wants to merge 3 commits into
mainfrom
dpaul-HcIISRewriteDisplayFixes

Conversation

@dpaulson45
Copy link
Copy Markdown
Member

Summary

Fixes three display-layer bugs in the HealthChecker IIS URL rewrite rule processing pipeline. These were discovered during server testing as follow-up to #2545 (outbound rewrite rule support).

Changes

1. Detailed display ignores <remove> directives (Fixes #2003)

The detailed inbound and outbound URL rewrite rule display loops did not honor <remove> entries from inherited configuration sections. Rules explicitly removed via <remove name='...' /> in a parent web.config still appeared in the detailed rule table and could be incorrectly flagged as misconfigured.

Fix: Collect exclude lists from each vDir's Remove.Name entries and skip matching rules in both inbound and outbound detailed display loops.

2. AppHost-only IIS locations missing from rewrite rule processing

Some IIS locations like Microsoft-Server-ActiveSync/Proxy exist only in applicationHost.config and are not returned by Get-WebApplication. Get-URLRewriteRule previously only iterated WebConfigContent keys, so these locations were skipped entirely — their inherited rewrite rules never appeared in the summary or detailed display.

Fix: Build a combined location list from WebConfigContent keys plus any appHost location paths not already present. The existing walk-up logic handles null web.config content gracefully.

3. Match property array bug when match has extra attributes (Fixes #2024)

When a rewrite rule <match> element has extra attributes like negate or ignoreCase, Get-Member returns multiple properties causing \ to become an array. This broke the match value resolution, URL Match Problem check, and display — showing negate url - instead of url - .*.

Fix: Filter the property list to known match targets (url, serverVariable) before resolving the match value.

Testing

  • 20 unit tests for Get-URLRewriteRule (3 new for appHost-only locations)
  • 34 scenario tests in Main.Tests.ps1 (updated assertions for remove filtering and match property)
  • All fixes verified via stash/unstash TDD: tests fail without fix, pass with fix
  • Server-tested on live Exchange 2019 environment

Closes #2003
Closes #2024

dpaulson45 and others added 3 commits June 5, 2026 13:38
The detailed inbound and outbound URL rewrite rule display loops
did not honor <remove> entries from inherited configuration sections.
Rules that were explicitly removed via <remove name='...' /> in a
parent web.config still appeared in the detailed rule table and could
be incorrectly flagged as misconfigured.

Collect exclude lists from each vDir's Remove.Name entries and skip
matching rules in both inbound and outbound detailed display loops.

Added mock data (AppHost Only Rule + remove directive) and test
assertion to verify removed rules do not appear in detailed output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Some IIS locations like Microsoft-Server-ActiveSync/Proxy exist only
in applicationHost.config and are not returned by Get-WebApplication.
Get-URLRewriteRule previously only iterated WebConfigContent keys,
so these appHost-only locations were skipped entirely and their
inherited rewrite rules never appeared in the display.

Build a combined location list from WebConfigContent keys plus any
appHost location paths not already present. The existing walk-up logic
handles null web.config content gracefully, so no loop body changes
are needed.

Added 3 unit tests using shared mock data covering appHost-only
location processing, parent rule inheritance, and key deduplication.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a rewrite rule <match> element has extra attributes like negate
or ignoreCase, Get-Member returns multiple properties causing
\ to become an array. This broke the match value
resolution, URL Match Problem check, and display — showing
'negate url - ' instead of 'url - .*'.

Filter the property list to known match targets (url, serverVariable)
before resolving the match value.

Added mock rule with negate='true' and test assertion for correct
MatchProperty display.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 5, 2026 20:05
@dpaulson45 dpaulson45 requested a review from a team as a code owner June 5, 2026 20:05
@dpaulson45
Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes display-layer issues in HealthChecker’s IIS URL rewrite rule reporting so results align with effective IIS configuration (inheritance + <remove> + <match> attribute parsing), and so appHost-only locations aren’t skipped.

Changes:

  • Add support for processing IIS locations that exist only in applicationHost.config (no Get-WebApplication/web.config entry).
  • Update inbound/outbound detailed display to exclude rules removed via <remove name="..."/>.
  • Fix match-property resolution when <match> includes additional attributes (e.g., negate, ignoreCase) so display and “URL Match Problem” checks are correct.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Diagnostics/HealthChecker/Analyzer/Get-URLRewriteRule.ps1 Builds a combined location list (web.config keys + appHost-only locations) so inheritance walk-up includes appHost-only vDirs.
Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 Updates detailed inbound/outbound URL rewrite rule display logic for <remove> filtering and robust match-property selection.
Diagnostics/HealthChecker/Analyzer/Tests/Get-URLRewriteRule.Tests.ps1 Adds unit tests covering appHost-only location processing/inheritance and key de-duplication.
Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 Updates scenario assertions for <remove> filtering and match-property display correctness.
Diagnostics/HealthChecker/Tests/DataCollection/E19/Exchange/IIS/* Extends mock IIS configs to include <remove> and <match negate="..."> scenarios for regression coverage.

Comment on lines +705 to +710
$excludeOutboundRules = @()
foreach ($section in $currentSection) {
if ($null -ne $section.Remove) {
$excludeOutboundRules += $section.Remove.Name
}
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid edge case. The concern is about the "remove-then-readd" pattern where a rule is removed and re-added with different settings in the same section:

<rules>
    <remove name="InheritedRule" />
    <!-- Override inherited version with different config -->
    <rule name="InheritedRule" stopProcessing="false">
        <match url="different-pattern" />
        <action type="Rewrite" url="new-destination" />
    </rule>
</rules>

IIS processes this as: remove the inherited version, then add the new local version. Our up-front exclude collection would incorrectly hide the re-added rule because the name matches the <remove> entry.

However, this pattern is not used in Exchange environments. The typical Exchange pattern is a simple <remove> to exclude an inherited rule, not remove-then-readd to override it. Adding ordered section processing would increase complexity without practical benefit for HealthChecker's target scenarios. If this pattern surfaces in the wild, we can revisit.

Comment on lines +615 to +619
# Collect <remove> entries so inherited rules that are removed at a lower level are excluded.
$excludeRules = @()
foreach ($section in $currentSection) {
if ($null -ne $section.Remove) {
$excludeRules += $section.Remove.Name
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same response as the inbound comment — this is the same pattern applied to outbound rules. Not a concern for Exchange environments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

URL Rewrite Rule wrong match property - Health Checker [Issue] - Health Checker Get-URLRewirteRule doesn't honor Remove

2 participants