Tag Archives: auth

Checking IIS Authentication Settings Before and After Changes

When maintaining Exchange Server or other IIS-based applications, configuration changes — such as cumulative updates, patches, or manual adjustments — can sometimes alter authentication settings without notice.
These changes may lead to unexpected login prompts, authentication failures, or inconsistent client access.

To help detect and document such changes, I use a simple but powerful PowerShell script that captures IIS authentication settings before and after any updates or modifications.

How the Script Works

The script queries IIS and exports the current authentication configuration to a CSV file.
After performing your updates (for example, applying an Exchange CU or modifying a virtual directory), you run the script again to generate a second CSV snapshot.

Each CSV contains detailed authentication settings for every relevant IIS site and virtual directory — including Windows, Basic, OAuth, and other authentication providers.

You can then use PowerShell’s built-in Compare-Object command to identify any differences between the two CSV files:

Compare-Object (Import-Csv .\Auth-Before.csv) (Import-Csv .\Auth-After.csv) -Property Site,Path,AuthType,Enabled

This comparison highlights exactly which authentication settings have changed, allowing administrators to verify that everything still aligns with best practices and organizational standards.

Why This Is Useful

Authentication misconfigurations can be subtle — and difficult to diagnose once users begin reporting access issues.
By documenting authentication settings before and after maintenance, you gain:

  • Full visibility into IIS authentication changes.
  • Quick detection of unexpected configuration drift.
  • Simplified troubleshooting after updates or patching.
  • A reliable audit trail for compliance and change management.

Practical Example

Before installing a new Exchange cumulative update, you run the script and save the output as Auth-Before.csv.
After the update completes, you run it again to create Auth-After.csv.
By comparing the two files, you instantly see whether any authentication methods were enabled, disabled, or modified — all without manually checking dozens of virtual directories.

n summary:
This PowerShell script provides a simple, reliable way to monitor and compare IIS authentication settings before and after system changes — helping administrators maintain consistent, secure configurations and avoid service disruptions.

<#
.SYNOPSIS
  Extended local snapshot of IIS Authentication and Extended Protection (EP) settings per site/app/vdir on an Exchange server.

.DESCRIPTION
  Scans all IIS sites, applications, and virtual directories, and collects:
    • Anonymous / Basic / Windows / Digest authentication status (enabled/disabled)
    • Windows authentication providers (NTLM / Negotiate order)
    • Basic authentication realm (if configured)
    • SSL flags (Require SSL / client certificate mode)
    • Extended Protection settings (tokenChecking, flags, SPNs)
    • (Optional) Exchange virtual directories (OWA, ECP, EWS, Autodiscover, MAPI, ActiveSync, OAB, Outlook Anywhere)

  Also performs a brief risk assessment regarding reverse proxy (e.g. NetScaler) compatibility with Extended Protection settings.

  Output is saved in C:\CNXA\IIS-Auth-Snapshots\<timestamp> as CSV, JSON, and SUMMARY.txt.

.PARAMETER OutputRoot
  Root output folder. Default: C:\CNXA\IIS-Auth-Snapshots

.PARAMETER IncludeExchangeVdirs
  Include Exchange virtual directories (OWA, ECP, EWS, Autodiscover, MAPI, ActiveSync, OAB, Outlook Anywhere) if corresponding cmdlets are available.
#>

[CmdletBinding()]
param(
  [string]$OutputRoot = 'C:\CNXA\IIS-Auth-Snapshots',
  [switch]$IncludeExchangeVdirs
)

begin {
  $ErrorActionPreference = 'Stop'
  Import-Module WebAdministration -ErrorAction Stop

  $timestamp = (Get-Date).ToString('yyyy-MM-ddTHH-mm-ss')
  $runRoot   = Join-Path $OutputRoot $timestamp
  New-Item -ItemType Directory -Path $runRoot -Force | Out-Null
  Write-Host ("Snapshot folder: {0}" -f $runRoot) -ForegroundColor Cyan

  function Get-AuthInfo([string]$Location, [string]$PathType) {
    $row = [ordered]@{
      Location  = $Location
      PathType  = $PathType   # Site / App / VDir
      anonymousAuthentication = $null
      basicAuthentication     = $null
      windowsAuthentication   = $null
      digestAuthentication    = $null
      windowsProviders        = $null
      basicRealm              = $null
      sslFlags                = $null
      ep_tokenChecking        = $null   # None/Allow/Require (IIS viser ofte: Off/Accept/Require)
      ep_flags                = $null   # None/Proxy/ProxyCohosting/NoServiceNameCheck
      ep_spns                 = $null   # Semi-kolon-separeret liste
      Risk_NetScaler_EP       = $null   # Heuristik: EP + sandsynlig proxy = risiko
      Error                   = $null
    }

    try {
      foreach($name in 'anonymousAuthentication','basicAuthentication','windowsAuthentication','digestAuthentication'){
        try {
          $state = (Get-WebConfigurationProperty -PSPath IIS:\ -Location $Location -Filter "system.webServer/security/authentication/$name" -Name enabled -ErrorAction Stop).Value
          $row[$name] = [bool]$state
        } catch { $row[$name] = $null }
      }
      # Providers for Windows auth
      try {
        $providers = (Get-WebConfiguration -PSPath IIS:\ -Location $Location -Filter 'system.webServer/security/authentication/windowsAuthentication/providers').Collection |
                     ForEach-Object { $_.Value }
        $row['windowsProviders'] = ($providers -join ',')
      } catch { $row['windowsProviders'] = $null }
      # Basic realm
      try {
        $realm = (Get-WebConfigurationProperty -PSPath IIS:\ -Location $Location -Filter 'system.webServer/security/authentication/basicAuthentication' -Name 'realm' -ErrorAction Stop).Value
        $row['basicRealm'] = $realm
      } catch { $row['basicRealm'] = $null }
      # SSL flags
      try {
        $flags = (Get-WebConfigurationProperty -PSPath IIS:\ -Location $Location -Filter 'system.webServer/security/access' -Name 'sslFlags' -ErrorAction Stop).Value
        $row['sslFlags'] = $flags
      } catch { $row['sslFlags'] = $null }

      # Extended Protection (under windowsAuthentication/extendedProtection)
      try {
        $epBase = 'system.webServer/security/authentication/windowsAuthentication/extendedProtection'
        $token  = (Get-WebConfigurationProperty -PSPath IIS:\ -Location $Location -Filter $epBase -Name 'tokenChecking' -ErrorAction Stop).Value
        $flags2 = (Get-WebConfigurationProperty -PSPath IIS:\ -Location $Location -Filter $epBase -Name 'flags' -ErrorAction Stop).Value
        $spnCol = (Get-WebConfiguration -PSPath IIS:\ -Location $Location -Filter ($epBase + '/spn')).Collection
        $spns   = @(); if($spnCol){ $spns = $spnCol | ForEach-Object { $_.Value } }
        $row['ep_tokenChecking'] = $token
        $row['ep_flags']         = $flags2
        $row['ep_spns']          = ($spns -join ';')
      } catch {
        # Hvis noderne ikke findes, lader vi felter være null
      }

      # Heuristik for risiko bag reverse proxy: Hvis WindowsAuth=On og EP tokenChecking er 'Require' eller flags indeholder 'Proxy', så marker risiko
      if($row.windowsAuthentication -and ($row.ep_tokenChecking -match 'Require' -or ($row.ep_flags -and $row.ep_flags -match 'Proxy'))){
        $row.Risk_NetScaler_EP = $true
      } else {
        $row.Risk_NetScaler_EP = $false
      }
    }
    catch { $row['Error'] = $_.Exception.Message }

    return [pscustomobject]$row
  }
}

process {
  $rows = @()

  # Sites
  $sites = Get-Website | Sort-Object Name
  foreach($s in $sites){
    $rows += Get-AuthInfo -Location $s.Name -PathType 'Site'

    # Apps
    $apps = Get-WebApplication -Site $s.Name -ErrorAction SilentlyContinue
    foreach($a in $apps){
      $loc = "$($s.Name)$($a.Path)"
      $rows += Get-AuthInfo -Location $loc -PathType 'App'

      # VDirs under app
      $vdirs = Get-WebVirtualDirectory -Site $s.Name -Application $a.Path -ErrorAction SilentlyContinue
      foreach($v in $vdirs){
        $loc2 = "$($s.Name)$($v.Path)"
        $rows += Get-AuthInfo -Location $loc2 -PathType 'VDir'
      }
    }

    # Root VDirs (hvis ingen apps)
    $rootVdirs = Get-WebVirtualDirectory -Site $s.Name -Application '/' -ErrorAction SilentlyContinue
    foreach($rv in $rootVdirs){
      $loc3 = "$($s.Name)$($rv.Path)"
      $rows += Get-AuthInfo -Location $loc3 -PathType 'VDir'
    }
  }

  # Gem IIS-auth + EP
  $csvPath  = Join-Path $runRoot 'iis-auth.csv'
  $jsonPath = Join-Path $runRoot 'iis-auth.json'
  $rows | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $csvPath
  $rows | ConvertTo-Json -Depth 5 | Set-Content -Path $jsonPath -Encoding UTF8
  Write-Host ("IIS auth skrevet: {0}" -f $csvPath) -ForegroundColor Green

  # Lidt opsummering og fokus på EP + velkendte Exchange-kataloger
  $summary = New-Object System.Text.StringBuilder
  [void]$summary.AppendLine("IIS Auth / Extended Protection Snapshot - " + $timestamp)
  [void]$summary.AppendLine("")

  $hot = $rows | Where-Object { $_.Risk_NetScaler_EP -eq $true }
  if($hot.Count -gt 0){
    [void]$summary.AppendLine("!! Potentielle problemer bag reverse proxy (EP Require/Proxy + WindowsAuth):")
    $hot | Select-Object Location,PathType,windowsAuthentication,ep_tokenChecking,ep_flags,sslFlags | Format-Table | Out-String | ForEach-Object { [void]$summary.Append($_) }
  } else {
    [void]$summary.AppendLine("Ingen åbenlyse EP/Proxy-risici fundet baseret på heuristik.")
  }

  $summaryPath = Join-Path $runRoot 'SUMMARY.txt'
  $summary.ToString() | Set-Content -Path $summaryPath -Encoding UTF8

  if($IncludeExchangeVdirs){
    if(Get-Command Get-OwaVirtualDirectory -ErrorAction SilentlyContinue){
      $ex = [ordered]@{}
      $ex.OWA  = Get-OwaVirtualDirectory              | Select-Object Server,Name,InternalUrl,ExternalUrl,FormsAuthentication,BasicAuthentication,WindowsAuthentication,OAuthAuthentication,RequireSSL
      $ex.ECP  = Get-EcpVirtualDirectory              | Select-Object Server,Name,InternalUrl,ExternalUrl,FormsAuthentication,BasicAuthentication,WindowsAuthentication,OAuthAuthentication,RequireSSL
      $ex.EWS  = Get-WebServicesVirtualDirectory      | Select-Object Server,Name,InternalUrl,ExternalUrl,BasicAuthentication,WindowsAuthentication,OAuthAuthentication,RequireSSL
      $ex.Auto = Get-AutodiscoverVirtualDirectory     | Select-Object Server,Name,InternalUrl,ExternalUrl,WindowsAuthentication,OAuthAuthentication,RequireSSL
      $ex.MAPI = Get-MapiVirtualDirectory             | Select-Object Server,Name,InternalUrl,ExternalUrl,IISAuthenticationMethods,RequireSSL
      $ex.AS   = Get-ActiveSyncVirtualDirectory       | Select-Object Server,Name,InternalUrl,ExternalUrl,BasicAuthentication,WindowsAuthentication,ClientCertAuth,ExternalAuthenticationMethods,RequireSSL
      $ex.OAB  = Get-OabVirtualDirectory              | Select-Object Server,Name,InternalUrl,ExternalUrl,RequireSSL
      $ex.OA   = Get-OutlookAnywhere                  | Select-Object Server,Name,ExternalHostname,InternalHostname,SSLOffloading,ExternalClientAuthenticationMethod,InternalClientAuthenticationMethod,IISAuthenticationMethods

      # Flatten for CSV
      $exRows = @()
      foreach($k in $ex.Keys){ if($null -ne $ex[$k]){ $exRows += $ex[$k] | Select-Object * } }
      if($exRows.Count -gt 0){
        $exCsv  = Join-Path $runRoot 'exchange-vdirs.csv'
        $exJson = Join-Path $runRoot 'exchange-vdirs.json'
        $exRows | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $exCsv
        $ex     | ConvertTo-Json -Depth 6 | Set-Content -Path $exJson -Encoding UTF8
        Write-Host ("Exchange VDirs skrevet: {0}" -f $exCsv) -ForegroundColor Green
      } else {
        Set-Content -Path (Join-Path $runRoot 'exchange-vdirs.txt') -Value 'No Exchange VDirs returned.'
      }
    } else {
      Set-Content -Path (Join-Path $runRoot 'exchange-vdirs.txt') -Value 'Exchange cmdlets not found; skipping.'
    }
  }
}

end {
  # no-op
}

Test SMTP TLS with OpenSSL with Auth

On a linux box type these two commands

echo -ne '[email protected]' | base64
echo -ne 'password' | base64

The command will return with the username and password in base64 format.
Example:

root@Linux-homeserver:~# echo -ne '[email protected]' | base64
dXNlcm5hbWVAZG9tYWluLnByZWZpeA==
root@Linux-homeserver:~# echo -ne 'password' | base64
cGFzc3dvcmQ=

So now you need to telnet with OpenSSL to the mailserver:

openssl s_client -debug -starttls smtp -crlf -connect mailserver.kennethdalbjerg.dk:465

After you have connection, you need to present yourself
Example:

ehlo host.kennethdalbjerg.dk

Some mailserver, do check if the ehlo is you corrert PTR record, for the IP address you are coming from, but most do not.

The mail server will return with:
250 mailserver.kennethdalbjerg.dk Hello dell1 [10.10.10.10]

You will now try to authenticated you self, if you don’t need to AUTH, please go to last code example in this blog.

AUTH LOGIN

And the mailserver will return with:
VXNlcm5hbWU6

Here you type you username in base64 format

dXNlcm5hbWVAZG9tYWluLnByZWZpeA==

And the mailserver will return with:
UGFzc3dvcmQ6

Please now type you password in base64 format

cGFzc3dvcmQ=

The server should now return with:
235 2.7.0 Authentication successful

After this you type normal email request to send an email
Example: (You should not type the line starting with a >, it are what the mailserver return to you.

mail from: [email protected]
> 250 2.1.0 Sender OK
rcpt to: [email protected]
> 250 2.1.5 Recipient OK
data
> 354 Start mail input; end with <CRLF>.<CRLF>
test
.
> 250 2.6.0 <604863cb-3dcc-4cee-9c1a-77fb6d951d43@mailserver.kennethdalbjerg.dk> [InternalId=9753870729240, Hostname=mailserver.kennethdalbjerg.dk] 1203 bytes in 1.581, 0,743 KB/sec Queued mail for delivery
quit
>221 2.0.0 Service closing transmission channel