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
WebAdministrationy 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:
| Nivel | Condición |
|---|---|
| BAJO | Último acceso hace menos de 180 días |
| MEDIO | Último acceso entre 180 y 365 días |
| ALTO | Sin 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:
| Columna | Descripción |
|---|---|
Sitio | Nombre del sitio IIS (ej. Default Web Site) |
Aplicacion | Path de la aplicación detectada (ej. /MiApp) |
Hits | Total de peticiones exitosas acumuladas en todos los logs |
UltimaUrl | La última URL exacta registrada para esa aplicación |
UltimoAcceso | Fecha y hora del último acceso exitoso |
DiasSinUso | Días transcurridos desde el último acceso hasta hoy |
Tipo | Web o API, detectado por el patrón en el path |
Riesgo | BAJO, 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.