Hacer que la previsualización y la confirmación sean honestas

ShellBeginner
Practicar Ahora

Introducción

Un comando de cambio de sala debe ganarse la confianza del organizador: la previsualización no debe dejar trabajo, el rechazo no debe hacer nada y la aprobación debe aplicar exactamente todo el plan apto. Declarar compatibilidad con WhatIf no basta para proteger todos los métodos de escritura de archivos.

Este laboratorio independiente comienza con su propio evento y un script incompleto. Utiliza las habilidades de validación por ID, JSON, parámetros y gestión de errores del laboratorio guiado anterior; no se necesita ningún archivo de una VM anterior. PowerShell y los recursos sintéticos están preparados. Utilizarás el Terminal predeterminado, realizarás confirmaciones nativas reales y volverás a abrir el evento realmente guardado. La duración es una estimación del autor que incluye lectura y recuperación.

Poner cada escritura detrás de una sola decisión

En este paso, corregirás una implementación engañosa de WhatIf para que tanto la previsualización como el rechazo de la confirmación dejen el espacio de trabajo intacto.

Ver por qué el script inicial no es seguro

Abre Terminal, que inicialmente es un shell de Linux. Inicia una sesión limpia de PowerShell con UTF-8 limitado a la sesión de este rol. env transmite estos ajustes solo a este programa; -NoLogo oculta el mensaje inicial y -NoProfile omite los perfiles personales:

env LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 pwsh -NoLogo -NoProfile

Espera al indicador PS. Todos los comandos restantes son de PowerShell. Este laboratorio tiene su propio escenario preparado, independiente de las VM anteriores:

Set-Location /home/labex/project/room-changes

El evento de origen y las solicitudes utilizan el mismo formato que en el laboratorio anterior. Léelos antes de cambiar el script:

Get-Content ./requests.json -Raw
./Show-RoomSchedule.ps1 -Path ./event.json

El archivo inicial apply-room-changes.ps1 está incompleto deliberadamente. Declara compatibilidad con WhatIf, pero escribe mediante .NET sin pedir permiso y no ha implementado los cambios de sala completos. Pruébalo solo con esta nueva salida de demostración desechable:

pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./unsafe-preview.json -WhatIf

Imprime Plan applied.. Ese mensaje no demuestra que haya aplicado el plan correcto ni respetado la previsualización. Comprueba el archivo real:

Test-Path ./unsafe-preview.json
True

Declarar un parámetro común no protege automáticamente las escrituras .NET arbitrarias. El evento original sigue intacto, pero esta previsualización no debería haber creado salida alguna. Elimina únicamente este archivo desechable con Remove-Item -LiteralPath, sin tocar las entradas ni las notas:

Remove-Item -LiteralPath ./unsafe-preview.json

Conservar el evento completo

Las entradas event.json, requests.json, small-request.json, bad-request.json y el archivo ajeno notes.txt deben conservar sus bytes, modo de archivo, propietario y mtime. No modifiques el lector preparado. Puedes reemplazar tu apply-room-changes.ps1 y crear el nuevo resultado nombrado. El evento y las solicitudes se proporcionan como JSON UTF-8 bien formado. El evento contiene ID exactos únicos, asistentes y capacidades enteros no negativos y una lista de aprobaciones. Las solicitudes forman un array no vacío de ID únicos de sesiones e ID de salas de destino. Los nombres son etiquetas de presentación; los ID distinguen mayúsculas.

Los valores desconocidos pueden incluir objetos anidados, arrays ordenados, null, booleanos, cadenas y números JSON finitos acotados dentro de 20 niveles. Las claves de objeto exactamente duplicadas y los esquemas malformados arbitrarios quedan fuera del ámbito. Utiliza ConvertFrom-Json -AsHashtable -DateKind String para conservar claves desconocidas que difieren en mayúsculas y mantener las cadenas con aspecto de fecha con sus desplazamientos originales exactos. Estas opciones se introdujeron en el laboratorio anterior. El origen incluye tanto When como when; no los combines. Utiliza ConvertTo-Json -Depth 100 para el evento completo, de modo que sobrevivan sus objetos anidados desconocidos. La profundidad predeterminada de dos no basta. Se aceptan representaciones finitas iguales como 1 y 1.0; los booleanos deben seguir siendo booleanos, el orden de los arrays debe mantenerse y el orden de las propiedades de los objetos es cosmético.

La salida debe ser un archivo nuevo dentro de un directorio padre real ya existente, distinto de todas las entradas. La cadena de directorios del curso es real; ningún otro escritor modifica estos archivos simultáneamente. Rechaza los archivos, directorios y enlaces existentes, incluidos los rotos, sin cambiarlos. Un plan no válido debe rechazar toda la invocación antes de escribir: una fila válida anterior no justifica un ID desconocido posterior, una sesión no aprobada, un duplicado o una capacidad excedida. La igualdad con la capacidad es válida. Las salas de destino ya están libres a esas horas.

Utilizar ShouldProcess nativo

Tu llamada reutilizable acepta EventPath, PlanPath y OutputPath, como el validador. Debe ser autónoma con estos argumentos y los comandos instalados, sin auxiliares vecinos. Mantén toda la validación antes de la decisión de autorización y guarda después el evento completo cambiando únicamente los campos RoomId solicitados.

Lee estas incorporaciones antes de guardar el código:

  • [CmdletBinding(SupportsShouldProcess)] añade los parámetros comunes nativos -WhatIf y -Confirm. No inventes un parámetro booleano que solo imprima una previsualización.
  • $PSCmdlet es el objeto automático de comando de PowerShell. $PSCmdlet.ShouldProcess(target, action) devuelve si debe realizarse la operación. Con WhatIf muestra la acción propuesta y devuelve false; con Confirm pregunta y solo devuelve true ante la aprobación.
  • Llámalo una vez para todo el plan validado. El destino identifica la salida y la acción nombra cada ID exacto de sesión y los ID de sala anterior y nueva. ForEach-Object construye la descripción; -join '; ' combina sus cadenas. Solo la rama true puede modificar objetos o escribir archivos. Un rechazo no debe crear directorios de salida, archivos ni trabajo temporal, ni cambiar trabajo existente.
  • Es una confirmación global, no una pregunta por sesión. Aquí el método .NET no genera una segunda pregunta nativa. Si más adelante utilizas un cmdlet interno que admite confirmación, suprime su pregunta adicional únicamente dentro de la rama ya autorizada; nunca eludas la decisión exterior.
  • Tras la aprobación, cada Session almacenada hace referencia al objeto correspondiente dentro del evento completo. Actualizar su RoomId conserva todos los demás campos. La llamada .NET WriteAllText y la codificación UTF-8 explícita son el mismo mecanismo introducido en el laboratorio anterior, ahora protegido por ShouldProcess.

La resolución de rutas, el análisis del objeto original, los arrays de coincidencias, las comparaciones sensibles a mayúsculas, la gestión de errores y la recopilación de todos los cambios antes de usarlos siguen el laboratorio anterior. Guarda el reemplazo completo mediante el here-string literal; solo reemplaza tu script:

Set-Content -Path ./apply-room-changes.ps1 -Encoding utf8 -Value @'
[CmdletBinding(SupportsShouldProcess)]
param(
    [Parameter(Mandatory)][string]$EventPath,
    [Parameter(Mandatory)][string]$PlanPath,
    [Parameter(Mandatory)][string]$OutputPath
)
try {
    $ErrorActionPreference = 'Stop'
    $fullOutput = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath)
    if (Get-Item -LiteralPath $fullOutput -Force -ErrorAction SilentlyContinue) {
        throw 'Output already exists; choose a new path.'
    }
    $parent = Split-Path -LiteralPath $fullOutput
    if (-not (Test-Path -LiteralPath $parent -PathType Container)) {
        throw 'Output parent must already exist.'
    }
    $event = Get-Content -LiteralPath $EventPath -Raw | ConvertFrom-Json -AsHashtable -DateKind String
    $requests = Get-Content -LiteralPath $PlanPath -Raw | ConvertFrom-Json -AsHashtable -DateKind String -NoEnumerate
    if ($requests -isnot [array] -or $requests.Count -eq 0) { throw 'Plan must be a nonempty array.' }
    $changes = @()
    $seen = @()
    foreach ($request in $requests) {
        if ($seen -ccontains $request.SessionId) { throw "Duplicate session ID: $($request.SessionId)" }
        $seen += $request.SessionId
        $sessions = @($event.Sessions | Where-Object { $_.SessionId -ceq $request.SessionId })
        $rooms = @($event.Rooms | Where-Object { $_.RoomId -ceq $request.RoomId })
        if ($sessions.Count -ne 1) { throw "Unknown or ambiguous session ID: $($request.SessionId)" }
        if ($rooms.Count -ne 1) { throw "Unknown or ambiguous room ID: $($request.RoomId)" }
        if ($event.ApprovedSessionIds -cnotcontains $request.SessionId) { throw "Unapproved session ID: $($request.SessionId)" }
        if ($sessions[0].Attendees -gt $rooms[0].Capacity) { throw "Capacity exceeded: $($request.SessionId) -> $($request.RoomId)" }
        $changes += [pscustomobject]@{
            Session = $sessions[0]
            SessionId = $request.SessionId
            OldRoomId = $sessions[0].RoomId
            NewRoomId = $request.RoomId
        }
    }
    $description = ($changes | ForEach-Object { "$($_.SessionId): $($_.OldRoomId) -> $($_.NewRoomId)" }) -join '; '
    if ($PSCmdlet.ShouldProcess($fullOutput, "Apply complete room plan [$description]")) {
        foreach ($change in $changes) { $change.Session.RoomId = $change.NewRoomId }
        $json = ConvertTo-Json -InputObject $event -Depth 100
        [System.IO.File]::WriteAllText($fullOutput, $json + [Environment]::NewLine, [System.Text.UTF8Encoding]::new($false))
        Write-Output "Saved: $fullOutput"
    }
}
catch {
    Write-Error ("Room change failed: {0}" -f $_.Exception.Message) -ErrorAction Continue
    exit 1
}
'@

Previsualizar y después rechazar la operación completa

El siguiente comando debe emitir un mensaje WhatIf nativo que describa tanto s-a: r-old -> r-equal como s-c: r-old -> r-large, con la nueva ruta de salida como destino:

pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./event-updated.json -WhatIf

Lee inmediatamente el estado nativo del hijo:

$LASTEXITCODE
0

Ahora solicita confirmación nativa. Espera a que aparezca la pregunta real, lee su acción completa, introduce N y pulsa Enter. No pegues N antes de que aparezca la pregunta:

pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./event-updated.json -Confirm

Debes ver Confirm, la acción completa y opciones como [Y] Yes y [N] No. Solo hay una pregunta para ambos cambios. Al elegir No, la operación se omite; no es un error de validación, por lo que el hijo termina correctamente sin hacer nada:

$LASTEXITCODE
0
Test-Path ./event-updated.json
False

Lee de nuevo el original en lugar de fiarte de una variable en memoria:

Get-Content ./event.json -Raw

Todos los valores RoomId de las sesiones siguen siendo r-old y las notas anidadas continúan presentes. Ni WhatIf ni No deben dejar ningún archivo, directorio ni candidato temporal. La comprobación funcional lee tus archivos originales y prueba el script exacto en PTY desechables de PowerShell, incluida la entrada real N/Y. Estas pruebas comprueban el comportamiento del programa; no crean ni afirman un historial de tus confirmaciones en el escenario real.

Si una ruta es incorrecta, inspecciona Get-Location y Get-ChildItem y vuelve con Set-Location /home/labex/project/room-changes. Un error de escritura del script se corrige guardando otra vez el here-string completo corregido. Si apruebas antes de tiempo por accidente, conserva el origen y elimina únicamente tu event-updated.json con Remove-Item -LiteralPath ./event-updated.json antes de practicar el rechazo de nuevo. Nunca borres ni edites el evento, las solicitudes o las notas originales para ocultar un error.

Aprobar y volver a abrir la programación real

En este paso, aprobarás el plan completo validado, utilizarás el evento guardado y comprobarás el rechazo de una solicitud posterior no válida.

Comienza en la misma sesión de PowerShell de Terminal, en /home/labex/project/room-changes. Tu script corregido sigue guardado desde el paso anterior; la salida final todavía no existe. Si abriste otro Terminal, utiliza el mismo comando explícito de inicio de PowerShell con UTF-8 y Set-Location del paso anterior. Este script no necesita variables de sesión, porque todas las rutas son argumentos.

Aplicar un plan aprobado completo

Ejecuta de nuevo la llamada de confirmación. Espera a la pregunta nativa, lee ambas asignaciones de ID, introduce Y y pulsa Enter:

pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./event-updated.json -Confirm

Espera una pregunta seguida de Saved: y la ruta de salida. Lee inmediatamente su estado nativo:

$LASTEXITCODE
0

Esto confirma una vez el plan validado completo. No garantiza una transacción frente a caídas, cortes de alimentación o escritores concurrentes. La entrada se conserva como original; el resultado es un evento completo separado. Un fallo de escritura es un error, no un permiso para afirmar que la programación se aplicó.

El lector preparado lee exactamente el archivo pasado a -Path. No regenera un resultado ausente ni recurre a event.json:

./Show-RoomSchedule.ps1 -Path ./event-updated.json

Las filas realmente guardadas identifican ahora s-a en r-equal, s-b todavía en r-old y s-c en r-large. Dos sesiones siguen compartiendo Shared maps y dos salas siguen compartiendo Studio; los ID las distinguen.

Vuelve a abrir toda la salida como diccionarios y conserva como cadenas los textos con aspecto de marca temporal:

$saved = Get-Content ./event-updated.json -Raw | ConvertFrom-Json -AsHashtable -DateKind String

Un punto desciende por las propiedades anidadas. Los metadatos originales de la primera sesión contienen una cadena con aspecto de fecha y una clave con otra combinación de mayúsculas. La búsqueda entre corchetes utiliza cada clave exacta del diccionario. Léelas por separado:

$saved.Sessions[0].Metadata.contact['When']
2026-11-07T09:30:00+05:30
$saved.Sessions[0].Metadata.contact['when']
Keep this distinct key

Lee otro valor desconocido y la nota original ajena a la tarea:

$saved.Unknown.offset
2027-01-02T12:34:56-07:00
Get-Content ./notes.txt
Venue manager: preserve this personal access note.

Estas observaciones acompañan a la comparación semántica completa; dos campos conservados no bastarían para demostrar que todos los objetos desconocidos sobrevivieron. No reconstruyas el evento a partir de una proyección que solo contenga las columnas mostradas.

Evitar que una solicitud posterior no válida publique un cambio parcial

El archivo preparado bad-request.json contiene una primera solicitud válida y una sala desconocida en la segunda. Utiliza otra salida nueva para conservar intacto el resultado terminado. La siguiente llamada falla intencionadamente antes de cualquier confirmación:

pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./bad-request.json -OutputPath ./rejected-event.json -Confirm

Espera Room change failed: y una explicación sobre la sala desconocida, sin pregunta de confirmación. Lee el resultado inmediatamente:

$LASTEXITCODE
1
Test-Path ./rejected-event.json
False

La solicitud válida anterior no se aplicó ni se publicó por separado. Para futuros trabajos legítimos, cambia el argumento de nuevo a requests.json; no edites el ejemplo incorrecto proporcionado. El evento original y el event-updated.json completado siguen disponibles.

Un destino existente también se rechaza deliberadamente. Puedes observarlo con WhatIf, que sigue validando las condiciones previas antes de autorizar nada:

pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./event-updated.json -WhatIf
$LASTEXITCODE
1

Espera Output already exists; choose a new path.. Previsualizar no permite ignorar argumentos no válidos. Conserva el resultado existente; una nueva invocación necesita una nueva ruta de salida. Si quieres reconstruir intencionadamente el resultado canónico del laboratorio después de corregir el script, elimina solo tu salida y repite la confirmación válida. Los originales siguen siendo la referencia para recuperarte.

La comprobación final lee el resultado completo real y la referencia de conservación. Prueba los bytes originales de tu script en eventos aislados con otros ID, igualdad de capacidad, registros con el mismo título, datos anidados y rutas de fallo. Se aceptan bucles e implementaciones de objetos equivalentes. Compara valores y tipos JSON, no el orden de propiedades de objetos, la escritura de comandos, el editor elegido ni los mensajes de éxito. Repetir la comprobación no aplica, reinicia ni repara tu evento real.

Resumen

Corregiste una previsualización engañosa, validaste todo el plan antes de escribir y utilizaste una sola decisión ShouldProcess nativa. WhatIf y No dejaron el trabajo intacto; Yes guardó un evento completo con las relaciones de ID solicitadas y todos los demás valores anidados conservados. Volviste a abrir ese archivo real y confirmaste que una solicitud posterior no válida o una salida existente se rechazan en lugar de publicarse o sustituirse en silencio.