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-WhatIfy-Confirm. No inventes un parámetro booleano que solo imprima una previsualización.$PSCmdletes 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-Objectconstruye 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
Sessionalmacenada hace referencia al objeto correspondiente dentro del evento completo. Actualizar suRoomIdconserva todos los demás campos. La llamada .NETWriteAllTexty 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.



