Análisis de uso de aplicaciones IIS con Log Parser y PowerShell antes de migrar

Hace algunos días me topé con la tarea de realizar una migración de las aplicaciones de un IIS a otro servidor. El proceso inició con un análisis de uso de las aplicaciones, para identificar cuáles tenían muy poco o nulo uso en el último año y descartarlas de la migración.

De esta primera etapa voy a hablar en esta publicación.

Herramientas utilizadas

Para esta tarea utilicé las siguientes herramientas:

  • PowerShell — para obtener el listado de aplicaciones del IIS mediante el módulo WebAdministration y orquestar la ejecución del análisis.
  • Log Parser 2.2 — herramienta de consola gratuita de Microsoft que permite hacer queries de SQL sobre los archivos de log del IIS en formato W3C. La puedes descargar directamente del sitio de Microsoft buscando “Log Parser 2.2”.

Se requirieron privilegios de administrador y el módulo WebAdministration instalado en PowerShell.

El script de análisis

Para realizar el análisis me apoyé de un script de PowerShell cuya tarea es obtener el listado de aplicaciones del IIS y consultar, mediante un query de Log Parser, el estadístico de uso de cada una, clasificándolas en 3 niveles de riesgo según los días transcurridos desde su último acceso exitoso.

La consulta que se ejecuta sobre los logs filtra únicamente peticiones exitosas (código HTTP menor a 400) y excluye recursos estáticos como CSS, JavaScript, imágenes y documentos, de modo que el resultado refleja el uso real de páginas y endpoints, no el tráfico de archivos.

Los niveles de riesgo que manejo son:

NivelCondición
BAJOÚltimo acceso hace menos de 180 días
MEDIOÚltimo acceso entre 180 y 365 días
ALTOSin acceso exitoso en más de 365 días

El umbral de 365 días es configurable en la variable $thresholdDays al inicio del script.

El script quedó de la siguiente forma:

# RevisionIISv2.ps1
# ============================================================
# CONFIG
$logParserPath = "C:\Program Files (x86)\Log Parser 2.2\LogParser.exe"
$logRoot       = "C:\inetpub\logs\LogFiles"
$outputCsv     = "C:\Revision IIS\IIS_Auditoria_V2.csv"
$thresholdDays = 365

Import-Module WebAdministration
$resultados = @{}

foreach ($site in Get-Website) {
    $siteId  = $site.ID
    $logPath = Join-Path $logRoot "W3SVC$siteId"
    if (!(Test-Path $logPath)) { continue }

    $query = @"
SELECT cs-uri-stem,
       COUNT(*) AS Hits,
       MAX(TO_TIMESTAMP(date, time)) AS UltimoAcceso
FROM $logPath\*.log
WHERE sc-status < 400
  AND cs-uri-stem NOT LIKE '%.css'
  AND cs-uri-stem NOT LIKE '%.js'
  AND cs-uri-stem NOT LIKE '%.png'
  AND cs-uri-stem NOT LIKE '%.jpg'
  AND cs-uri-stem NOT LIKE '%.gif'
  AND cs-uri-stem NOT LIKE '%.ico'
  AND cs-uri-stem NOT LIKE '%.pdf'
  AND cs-uri-stem NOT LIKE '%.doc%'
  AND cs-uri-stem NOT LIKE '%.xls%'
  AND cs-uri-stem NOT LIKE '%health%'
GROUP BY cs-uri-stem
"@

    $tmpFile = [System.IO.Path]::GetTempFileName()
    & $logParserPath $query -i:IISW3C -o:CSV > $tmpFile
    $csv = Import-Csv $tmpFile
    Remove-Item $tmpFile

    foreach ($row in $csv) {
        if ([string]::IsNullOrWhiteSpace($row.UltimoAcceso)) { continue }
        try   { $fecha = [datetime]$row.UltimoAcceso }
        catch { continue }

        $diasSinUso = (New-TimeSpan -Start $fecha -End (Get-Date)).Days
        $appPath    = "/"
        if ($row.'cs-uri-stem' -match "^/([^/]+)") {
            $appPath = "/" + $matches[1]
        }
        $key = "$($site.Name)|$appPath"

        if ($resultados.ContainsKey($key)) {
            if ($fecha -gt $resultados[$key].UltimoAcceso) {
                $resultados[$key].UltimoAcceso = $fecha
                $resultados[$key].DiasSinUso   = $diasSinUso
                $resultados[$key].UltimaUrl    = $row.'cs-uri-stem'
                $resultados[$key].Riesgo = if ($diasSinUso -gt $thresholdDays) { 'ALTO' }
                                           elseif ($diasSinUso -gt 180)        { 'MEDIO' }
                                           else                                { 'BAJO'  }
            }
            $resultados[$key].Hits += [int]$row.Hits
        } else {
            $tipo   = if ($appPath -match 'api|svc|service') { 'API' } else { 'Web' }
            $riesgo = if ($diasSinUso -gt $thresholdDays) { 'ALTO' }
                      elseif ($diasSinUso -gt 180)        { 'MEDIO' }
                      else                                { 'BAJO'  }
            $resultados[$key] = [PSCustomObject]@{
                Sitio        = $site.Name
                Aplicacion   = $appPath
                Hits         = [int]$row.Hits
                UltimaUrl    = $row.'cs-uri-stem'
                UltimoAcceso = $fecha
                DiasSinUso   = $diasSinUso
                Tipo         = $tipo
                Riesgo       = $riesgo
            }
        }
    }
}

# Exportar ordenado por días sin uso (mayor primero)
$resultados.Values |
    Sort-Object DiasSinUso -Descending |
    Export-Csv $outputCsv -NoTypeInformation -Encoding UTF8

Write-Host "Reporte generado en: $outputCsv"

Resultado del script

El resultado del script es un archivo CSV con la siguiente estructura:

ColumnaDescripción
SitioNombre del sitio IIS (ej. Default Web Site)
AplicacionPath de la aplicación detectada (ej. /MiApp)
HitsTotal de peticiones exitosas acumuladas en todos los logs
UltimaUrlLa última URL exacta registrada para esa aplicación
UltimoAccesoFecha y hora del último acceso exitoso
DiasSinUsoDías transcurridos desde el último acceso hasta hoy
TipoWeb o API, detectado por el patrón en el path
RiesgoBAJO, MEDIO o ALTO según los días sin uso

Con este CSV ya puedo validar y descartar las aplicaciones que ya no son relevantes o tienen poco o nulo uso. El proceso es manual: abro el archivo en Excel, ordeno por DiasSinUso de mayor a menor y voy decidiendo cuáles se migran. Las de Riesgo ALTO son las candidatas a descartar, aunque siempre hay que validar con el negocio, ya que puede haber sistemas con uso estacional que solo se acceden unos días al año pero son críticos.

Posterior a esto, el siguiente paso es exportar la configuración de cada aplicativo seleccionado e importarla en el nuevo servidor, pero de esa parte les hablaré en mi próxima publicación.

Referencias