Windows Failover Cluster 2022: cómo nombrar CSV de VHD Set en clúster invitado (Hyper‑V) con PowerShell

¿Usas VHD Set (.vhds) como discos compartidos en clústeres invitados de Hyper‑V y tus CSV aparecen como “Cluster Disk 1/2/3”? Aquí tienes una guía práctica para entender por qué ocurre, cómo renombrarlos de forma fiable y cómo mantener una trazabilidad clara CSV ↔ volumen ↔ archivo .vhds en Windows Server 2022 con PowerShell.

Índice

Resumen de la pregunta

En clústeres invitados (VMs que ejecutan Windows Failover Cluster sobre Hyper‑V) es habitual utilizar VHD Set (.vhds) como discos compartidos y convertirlos a Cluster Shared Volumes (CSV). El problema: cuando se agregan al clúster, PowerShell asigna nombres genéricos como “Cluster Disk 1/2/3” y no hay un cmdlet que proporcione, de forma simple, un mapeo de cada CSV al archivo .vhds subyacente para renombrar con algo significativo (por ejemplo, “Data1”). Además, si cambias manualmente los nombres de carpetas en C:\ClusterStorage o renombras CSV a mano, puedes perder la relación punto de montaje ↔ CSV ↔ .vhds. La GUI de Failover Cluster Manager deja entrever datos útiles, pero no se exponen de forma directa en PowerShell.

Qué está pasando y por qué no es trivial

  • PowerShell no expone un vínculo directo CSV → archivo .vhds para VHD Set. Cmdlets como Get‑VMHardDiskDrive pueden mostrar discos compartidos, pero no siempre de forma consistente en todos los entornos/Versiones de Hyper‑V.
  • La GUI sí “parece saberlo” porque combina información del clúster, del volumen y del registro del invitado. Sin embargo, automatizar esa correlación no es evidente.
  • Renombrar CSV es en línea y seguro en general, pero cambia la ruta de la carpeta bajo C:\ClusterStorage<NombreCSV>\ y cualquier script/servicio que use rutas absolutas puede romperse.

Objetivo

Disponer de una estrategia repetible y automatizable para:

  1. Asignar a cada CSV un nombre significativo y estable.
  2. Mantener la trazabilidad con el volumen y, cuando se necesite, con el archivo .vhds en el host de Hyper‑V.
  3. Evitar dependencias de APIs no documentadas o inconsistentes.

Estrategia recomendada: propagar el nombre desde la etiqueta del volumen

La forma más robusta y portable es usar la etiqueta del sistema de archivos (NTFS/ReFS) como fuente de verdad. Tú decides el nombre cuando das formato al volumen dentro del invitado, y luego lo usas para renombrar el CSV.

Flujo de trabajo

  1. En el invitado, al crear/formatear el volumen que se convertirá en CSV, asigna una etiqueta significativa (p. ej., “Data1”).
  2. Usa PowerShell para leer esa etiqueta y renombrar el CSV con Rename‑ClusterSharedVolume.

One‑liner mínimo

# Ejecutar en un nodo del clúster invitado (Windows Server 2022+)
Get-ClusterSharedVolume | ForEach-Object {
    $mp    = $_.SharedVolumeInfo.FriendlyVolumeName   # C:\ClusterStorage&lt;NombreActual&gt;\
    $label = (Get-Volume -Path $mp).FileSystemLabel    # p. ej., "Data1"
    if ($label -and $_.Name -ne $label) {
        Rename-ClusterSharedVolume -InputObject $_ -NewName $label
    }
}

Ventajas: no necesitas descubrir el archivo .vhds en el host; el nombre “nace” desde un metadato estable (la etiqueta del volumen) y queda alineado con la carpeta C:\ClusterStorage\.

Versión endurecida para producción

El siguiente script añade controles: sanitiza el nombre (caracteres inválidos), maneja duplicados, registra cambios y ofrece WhatIf.

function Set-CsvNameFromLabel {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [int]$MaxLength = 60,
        [switch]$AppendUniqueSuffix, # Si hay colisión, añade -2, -3, ...
        [switch]$SkipEmptyLabels,    # Omite volúmenes sin etiqueta
        [switch]$VerboseLog
    )```
$existing = @{}
Get-ClusterSharedVolume | ForEach-Object { $existing[$_.Name] = $true }

$changed = @()

Get-ClusterSharedVolume | ForEach-Object {
    $csv = $_
    $mountPath = $csv.SharedVolumeInfo.FriendlyVolumeName
    try {
        $label = (Get-Volume -Path $mountPath -ErrorAction Stop).FileSystemLabel
    } catch {
        if ($VerboseLog) { Write-Warning "No se pudo leer la etiqueta de $mountPath ($($_.Exception.Message))." }
        return
    }

    if ([string]::IsNullOrWhiteSpace($label)) {
        if ($SkipEmptyLabels) { 
            if ($VerboseLog) { Write-Verbose "CSV '$($csv.Name)' sin etiqueta: omitido." }
            return 
        }
        else { $label = $csv.Name }
    }

    # Sanitizar para usar como nombre de carpeta CSV
    $safe = $label.Trim()
    $safe = $safe -replace '[\\/:*?""&lt;&gt;|]', '-' # caracteres inválidos
    $safe = $safe.TrimEnd('.', ' ')                 # evitar finales problemáticos
    if ($safe.Length -gt $MaxLength) { $safe = $safe.Substring(0, $MaxLength) }

    # Resolver colisiones de nombres
    $target = $safe
    $i = 2
    while ($existing.ContainsKey($target) -and $target -ne $csv.Name) {
        if (-not $AppendUniqueSuffix) {
            if ($VerboseLog) { Write-Warning "Colisión: ya existe un CSV llamado '$target'. Use -AppendUniqueSuffix para forzar." }
            return
        }
        $suffix = "-$i"
        $maxBase = [Math]::Max(1, $MaxLength - $suffix.Length)
        $base = $safe.Substring(0, [Math]::Min($safe.Length, $maxBase))
        $target = "$base$suffix"
        $i++
    }

    if ($target -ne $csv.Name) {
        if ($PSCmdlet.ShouldProcess("CSV '$($csv.Name)'", "Renombrar a '$target'")) {
            Rename-ClusterSharedVolume -InputObject $csv -NewName $target
            $existing.Remove($csv.Name) | Out-Null
            $existing[$target] = $true
            $changed += [pscustomobject]@{
                OldName    = $csv.Name
                NewName    = $target
                MountPoint = $mountPath
                Time       = (Get-Date)
            }
        }
    } elseif ($VerboseLog) {
        Write-Verbose "CSV '$($csv.Name)' ya coincide con la etiqueta."
    }
}

return $changed
```
}

Ejemplo de uso:

Set-CsvNameFromLabel -AppendUniqueSuffix -VerboseLog -WhatIf

Verificación rápida

Get-ClusterSharedVolume | Select-Object Name, @{n='MountPoint';e={$_.SharedVolumeInfo.FriendlyVolumeName}}, OwnerNode

Comprueba que cada Name coincide con lo que esperabas y que la ruta en C:\ClusterStorage\ refleja el cambio.

Guardar la relación en el momento de creación

Si ya gestionas la creación de .vhds desde el host Hyper‑V, crea tu fuente de verdad (JSON/CSV/Registro) con:

  • Ruta del archivo .vhds en el host/Cluster de Hyper‑V.
  • VMs a las que se adjunta y su posición SCSI (ControllerNumber/ControllerLocation).
  • Nombre deseado del CSV (p. ej., derivado del nombre del archivo).

Más tarde, cuando el invitado convierta el disco a CSV, un script leerá ese registro para aplicar el nombre.

Ejemplo para generar el registro en el host

# Ejecutar en hosts de Hyper-V (o desde un nodo del clúster de virtualización)
$map = Get-VM | ForEach-Object {
    $vm = $_
    Get-VMHardDiskDrive -VMName $vm.Name |
      Where-Object { $.SupportPersistentReservations -or $.ShareVirtualDisk } |
      Select-Object @{
          n='VM';e={$vm.Name}
      }, ControllerType, ControllerNumber, ControllerLocation, Path
}

\$map | ConvertTo-Json -Depth 5 | Out-File "C:\vhds-map.json" -Encoding UTF8 

Consumir el registro en el invitado

Imagina que guardaste además el nombre deseado para el CSV. El playbook sería:

  1. Leer C:\vhds-map.json (copiado/entregado de forma segura al invitado).
  2. Para cada entrada, localizar el volumen por posición SCSI o tamaño y establecer la etiqueta de volumen.
  3. Ejecutar Set-CsvNameFromLabel para renombrar.

Derivar la relación por GUID de disco/volumen (post‑producción)

Si el entorno ya está en marcha sin metadatos, puedes reconstruir la relación.

En el invitado: descubrir GUIDs desde el recurso de clúster

# Para un CSV concreto
Get-ClusterResource -Name "Cluster Disk 1" | Get-ClusterParameter | 
    Where-Object { $_.Name -in 'DiskIdGuid','VolumeName' }

Para todos los CSV

Get-ClusterResource | Where-Object ResourceType -eq 'Physical Disk' |
ForEach-Object { $\_ | Get-ClusterParameter |
Where-Object { $\_.Name -in 'DiskIdGuid','VolumeName' } } 

Con DiskIdGuid/VolumeName podrás localizar el disco/volumen:

$guid = '{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'
$disk = Get-Disk | Where-Object { $_.UniqueId -match $guid }
$parts = Get-Partition -DiskNumber $disk.Number
$vol   = $parts | Get-Volume
$vol | Select-Object DriveLetter, FileSystem, FileSystemLabel, UniqueId

En el host: enumerar discos compartidos adjuntos a las VMs

Get-VM | ForEach-Object {
    $vm = $_
    Get-VMHardDiskDrive -VMName $vm.Name |
      Where-Object { $.SupportPersistentReservations -or $.ShareVirtualDisk } |
      Select-Object @{n='VM';e={$vm.Name}},
                    ControllerNumber, ControllerLocation, Path
} | Sort-Object VM, ControllerNumber, ControllerLocation

Luego, une la información por posición SCSI, tamaño y orden de creación. No es perfecto, pero funciona en la mayoría de casos. Una vez deducido el nombre “correcto”, aplica la estrategia de etiquetas y renombrado.

Buenas prácticas operativas al renombrar CSV

  • Impacto en rutas: renombrar cambia C:\ClusterStorage<Nombre>\. Revisa scripts, tareas, servicios y aplicaciones que dependan de rutas absolutas.
  • Auditoría: registra quién, qué y cuándo renombra (por ejemplo, volcando un JSON con el antes/después).
  • Idempotencia: diseña tus scripts para que, si los vuelves a ejecutar, no rompan nada (no renombrar si ya coincide la etiqueta).
  • Etiquetas como fuente de verdad: si cambias un nombre, cambia primero la FileSystemLabel y luego sincroniza el CSV.

Playbook rápido reutilizable

  1. Etiquetar el volumen: Set-Volume -Path C:\ClusterStorage\ClusterDisk1\ -NewFileSystemLabel "Data1"
  2. Renombrar CSV desde la etiqueta: Set-CsvNameFromLabel -AppendUniqueSuffix
  3. Verificar: Get-ClusterSharedVolume y confirmar rutas y consistencia en todos los nodos.
  4. Registrar: guarda el resultado devuelto por la función para auditoría.

Tabla comparativa de estrategias

EstrategiaAutomatizableEsfuerzo inicialRobustezCuándo usar
Propagar desde etiqueta del volumenAltaBajoAltaLa opción por defecto para la mayoría
Registro al crear .vhds (fuente de verdad)AltaMedioMuy altaEntornos gobernados / con CMDB
Reconstrucción por GUID/posición SCSIMediaMedio‑altoMediaInstalaciones existentes sin metadatos

Marcadores de nombre dentro del volumen

Si no quieres depender de etiquetas (o quieres validación extra), crea un “marcador” en la raíz del CSV, por ejemplo un archivo oculto .csv-name con el nombre deseado. El flujo:

  1. Leer el marcador.
  2. Aplicar esa cadena como etiqueta del volumen (Set-Volume).
  3. Ejecutar el renombrado del CSV.
Get-ClusterSharedVolume | ForEach-Object {
    $mp = $_.SharedVolumeInfo.FriendlyVolumeName
    $marker = Join-Path $mp '.csv-name'
    if (Test-Path $marker) {
        $desired = (Get-Content $marker -ErrorAction SilentlyContinue | Select-Object -First 1).Trim()
        if ($desired) {
            Set-Volume -Path $mp -NewFileSystemLabel $desired
        }
    }
}
Set-CsvNameFromLabel -AppendUniqueSuffix

Operación segura y reversibilidad

  • Volver atrás: mantén un backup del listado “antes/después”. Renombrar a su estado anterior es inmediato si conservas ese registro.
  • Permisos: ejecuta en un Cluster‑Aware PowerShell con privilegios de Administrador de clúster.
  • Ventanas de cambio: aunque renombrar es en línea, hazlo en una ventana controlada y valida que ningún job use rutas antiguas.

Preguntas frecuentes

¿Puedo renombrar CSV desde cualquier nodo? Sí, pero asegúrate de que el clúster está saludable. El cambio se replica.

¿Cambiar la etiqueta del volumen interrumpe E/S? No, es una operación de metadatos. Aun así, aplica prácticas de cambio seguras.

¿Por qué no usar solo la GUI? La GUI sirve para inspección puntual. Para coherencia, auditable y CI/CD, apuesta por scripts idempotentes.

Errores frecuentes y cómo evitarlos

ErrorConsecuenciaPrevención
Renombrar manualmente carpetas en C:\ClusterStorageDesalineación nombre ↔ CSV, confusión y scripts rotosUsa Rename-ClusterSharedVolume; nunca toques la carpeta a mano
Asumir que Get-VMHardDiskDrive siempre lista todos los .vhdsTrazabilidad incompletaCombina con etiquetas, GUIDs y/o registro propio
Permitir etiquetas vacías o duplicadasCSV sin nombre significativo o colisionesSanitiza y fuerza sufijo único con scripts
No registrar cambiosDificultad para auditar o revertirGuardar JSON con antes/después cada vez

Alternativa tecnológica: Storage Spaces Direct en clúster invitado

Si el mapeo CSV ↔ .vhds se convierte en un lastre operativo, valora Storage Spaces Direct (S2D) dentro del clúster invitado. Ya no hay archivos compartidos por VM y el almacenamiento se gestiona internamente por el propio S2D.

VentajasInconvenientes
Simplifica la capa de almacenamiento; elimina la necesidad de mapear a .vhds; resiliencia y escalabilidad integradas.Mayor complejidad de diseño; más discos por VM/nodo; curva de aprendizaje y supervisión específicas.

Conclusión y receta TL;DR

  • No hay un cmdlet que diga “Cluster Disk X → …\data1.vhds”.
  • La vía práctica y robusta es usar la etiqueta del volumen como fuente de verdad y sincronizar el nombre del CSV con Rename‑ClusterSharedVolume.
  • Si necesitas trazabilidad completa host↔invitado, crea un registro al aprovisionar los VHD Set (VM, posición SCSI, ruta .vhds, nombre CSV).
  • En instalaciones existentes, reconstruye por GUID/posición y estandariza con etiquetas.
  • Si el modelo VHD Set es una carga operacional, evalúa S2D en los clústeres invitados.

Apéndice: utilidades adicionales

Listar CSV con su etiqueta y tamaño

Get-ClusterSharedVolume | ForEach-Object {
    $mp = $_.SharedVolumeInfo.FriendlyVolumeName
    $v  = Get-Volume -Path $mp
    [pscustomobject]@{
        CsvName   = $_.Name
        Label     = $v.FileSystemLabel
        FileSystem= $v.FileSystem
        SizeGB    = [math]::Round(($v.Size/1GB),2)
        FreeGB    = [math]::Round(($v.SizeRemaining/1GB),2)
        Mount     = $mp
        OwnerNode = $_.OwnerNode
    }
} | Sort-Object CsvName | Format-Table -AutoSize

Renombrar etiqueta del volumen de forma segura

# Cambia solo la etiqueta (no el CSV); útil para preparar el renombrado
$mp = (Get-ClusterSharedVolume -Name 'Cluster Disk 1').SharedVolumeInfo.FriendlyVolumeName
Set-Volume -Path $mp -NewFileSystemLabel 'Data1'

Auditoría: exportar antes/después

$before = Get-ClusterSharedVolume | Select-Object Name, @{n='Mount';e={$_.SharedVolumeInfo.FriendlyVolumeName}}
$changes = Set-CsvNameFromLabel -AppendUniqueSuffix
$after  = Get-ClusterSharedVolume | Select-Object Name, @{n='Mount';e={$_.SharedVolumeInfo.FriendlyVolumeName}}

\[pscustomobject]@{
Time    = Get-Date
Before  = \$before
Changes = \$changes
After   = \$after
} | ConvertTo-Json -Depth 6 | Out-File "C:\logs\csv-rename-\$(Get-Date -f yyyyMMdd-HHmmss).json" 

Tips de resolución de problemas

  • Si Get-Volume -Path falla, comprueba que el CSV esté en línea y que la ruta termine con \.
  • Si la etiqueta no aparece, monta temporalmente el volumen con una letra y revisa permisos del servicio de clúster.
  • Si un CSV no puede renombrarse por colisión, usa -AppendUniqueSuffix o cambia ligeramente la etiqueta.

Con esta guía y scripts, puedes convertir un “Cluster Disk 3” anónimo en un “Data1” significativo, mantener alineada la carpeta de C:\ClusterStorage, y conservar la trazabilidad necesaria para operar y auditar tu clúster invitado en Windows Server 2022.

Índice