Vorschau und Bestätigung ehrlich umsetzen

ShellBeginner
Jetzt üben

Einführung

Ein Raumänderungsbefehl sollte das Vertrauen der Organisation verdienen: Eine Vorschau darf keine Arbeit hinterlassen, eine Ablehnung darf nichts tun, und eine Zustimmung muss genau den vollständigen zulässigen Plan anwenden. Die bloße Deklaration von WhatIf-Unterstützung schützt nicht jede Methode zum Schreiben von Dateien.

Dieses unabhängige Lab beginnt mit einem eigenen Ereignis und einem unvollständigen Skript. Nutze die ID-basierte Validierung sowie JSON-, Parameter- und Fehlerbehandlungskenntnisse aus dem vorherigen geführten Lab; Dateien aus einer früheren VM werden nicht benötigt. PowerShell und synthetische Ressourcen sind vorbereitet. Du verwendest das Standard-Terminal, führst echte native Bestätigungen aus und öffnest das tatsächlich gespeicherte Ereignis erneut. Die Dauer ist eine Autorenschätzung einschließlich Lesen und Fehlerbehebung.

Jeden Schreibzugriff hinter eine Entscheidung stellen

In diesem Schritt korrigierst du eine irreführende WhatIf-Implementierung, damit sowohl die Vorschau als auch eine abgelehnte Bestätigung den Arbeitsbereich unverändert lassen.

Erkennen, warum das Startskript unsicher ist

Öffne Terminal, zunächst eine Linux-Shell. Starte eine saubere PowerShell-Sitzung mit UTF-8-Einstellungen nur für diese Rollensitzung. env übergibt die Einstellungen nur diesem Programm; -NoLogo blendet das Banner aus und -NoProfile überspringt persönliche Profile:

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

Warte auf die PS-Eingabeaufforderung. Alle weiteren Befehle sind PowerShell-Befehle. Dieses Lab besitzt seine eigene vorbereitete Szene, unabhängig von früheren VMs:

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

Quellereignis und Anfragen verwenden dasselbe Format wie im vorherigen Lab. Lies sie vor der Skriptänderung:

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

Das anfängliche apply-room-changes.ps1 ist absichtlich unvollständig. Es deklariert WhatIf-Unterstützung, schreibt aber ohne Erlaubnis über .NET und hat die vollständigen Raumänderungen nicht implementiert. Probiere es nur mit dieser neuen, entbehrlichen Demonstrationsausgabe aus:

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

Es gibt Plan applied. aus. Diese Meldung beweist weder die Anwendung des richtigen Plans noch die Beachtung der Vorschau. Prüfe die tatsächliche Datei:

Test-Path ./unsafe-preview.json
True

Die Deklaration eines allgemeinen Parameters schützt nicht automatisch beliebige .NET-Schreibzugriffe. Das ursprüngliche Ereignis ist weiterhin intakt, aber diese Vorschau hätte keine Ausgabe anlegen dürfen. Entferne mit Remove-Item -LiteralPath nur diese entbehrliche Datei und lasse Eingaben und Notizen unberührt:

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

Das vollständige Ereignis erhalten

Die Eingaben event.json, requests.json, small-request.json, bad-request.json und die unabhängige notes.txt müssen ihre Bytes, ihren Dateimodus, Eigentümer und mtime behalten. Ändere den vorbereiteten Leser nicht. Du darfst dein apply-room-changes.ps1 ersetzen und das benannte neue Ergebnis erstellen. Ereignis und Anfragen liegen als wohlgeformtes UTF-8-JSON vor. Das Ereignis enthält eindeutige exakte IDs, nicht negative ganzzahlige Teilnahmezahlen und Kapazitäten sowie eine Genehmigungsliste. Anfragen sind ein nicht leeres Array eindeutiger Sitzungs-IDs und Zielraum-IDs. Namen dienen der Anzeige; IDs beachten die Groß-/Kleinschreibung.

Unbekannte Werte dürfen verschachtelte Objekte, geordnete Arrays, null, boolesche Werte, Zeichenfolgen und begrenzte endliche JSON-Zahlen innerhalb von 20 Ebenen enthalten. Exakt doppelte Objektschlüssel und beliebige fehlerhafte Schemas liegen außerhalb des Umfangs. Verwende ConvertFrom-Json -AsHashtable -DateKind String, um unbekannte Schlüssel mit verschiedener Groß-/Kleinschreibung und datumsähnliche Zeichenfolgen mit ihren exakten ursprünglichen Zeitversätzen zu erhalten. Diese Optionen wurden im vorherigen Lab eingeführt. Die Quelle enthält sowohl When als auch when; fasse sie nicht zusammen. Verwende für das vollständige Ereignis ConvertTo-Json -Depth 100, damit unbekannte verschachtelte Objekte erhalten bleiben. Die Standardtiefe zwei reicht nicht. Gleiche endliche Zahlenschreibweisen wie 1 und 1.0 sind zulässig; boolesche Werte müssen boolesch bleiben, die Arrayreihenfolge muss gleich bleiben, und die Reihenfolge der Objekteigenschaften ist kosmetisch.

Die Ausgabe muss eine neue Datei unter einem bereits vorhandenen echten Elternverzeichnis sein und sich von jeder Eingabe unterscheiden. Die Verzeichniskette des Kurses ist echt; kein anderer Prozess schreibt gleichzeitig in diese Dateien. Lehne bestehende Dateien, Verzeichnisse und Links einschließlich ins Leere zeigender Links unverändert ab. Ein ungültiger Plan muss den gesamten Aufruf vor dem Schreiben ablehnen: Eine frühere gültige Zeile rechtfertigt keine spätere unbekannte ID, nicht genehmigte Sitzung, doppelte Anfrage oder Kapazitätsüberschreitung. Kapazitätsgleichheit ist gültig. Die Zielräume sind zu den betreffenden Zeiten bereits frei.

Natives ShouldProcess verwenden

Dein wiederverwendbarer Aufruf erhält wie der Validator EventPath, PlanPath und OutputPath. Er muss mit diesen Argumenten und installierten Befehlen eigenständig funktionieren, ohne benachbarte Hilfsdateien. Lasse die vollständige Prüfung vor der Autorisierungsentscheidung und speichere anschließend das vollständige Ereignis mit ausschließlich den angeforderten RoomId-Änderungen.

Lies vor dem Speichern des Codes diese Ergänzungen:

  • [CmdletBinding(SupportsShouldProcess)] ergänzt die nativen allgemeinen Parameter -WhatIf und -Confirm. Erfinde keinen booleschen Parameter, der nur eine Vorschau ausgibt.
  • $PSCmdlet ist PowerShells automatisches Befehlsobjekt. $PSCmdlet.ShouldProcess(target, action) gibt zurück, ob die Operation ausgeführt werden soll. Mit WhatIf zeigt es die vorgeschlagene Aktion und liefert false; mit Confirm fragt es nach und liefert nur bei Zustimmung true.
  • Rufe es einmal für den gesamten geprüften Plan auf. Das Ziel kennzeichnet die Ausgabe, und die Aktion nennt jede exakte Sitzungs-ID sowie alte und neue Raum-ID. ForEach-Object erstellt die Beschreibung; -join '; ' verbindet die Zeichenfolgen. Nur der true-Zweig darf Objekte ändern oder Dateien schreiben. Eine Ablehnung darf keine Ausgabeverzeichnisse, Dateien oder temporären Arbeiten erzeugen und keine bestehende Arbeit ändern.
  • Dies ist eine Gesamtbestätigung, keine Rückfrage je Sitzung. Eine .NET-Methode erzeugt hier keine zweite native Rückfrage. Falls du später ein inneres Cmdlet mit Bestätigungsunterstützung verwendest, unterdrücke dessen zusätzliche Rückfrage nur im bereits autorisierten Zweig; umgehe niemals die äußere Entscheidung.
  • Nach der Zustimmung verweist jede gespeicherte Session auf das passende Objekt im vollständigen Ereignis. Die Änderung ihrer RoomId erhält alle anderen Felder. Der .NET-Aufruf WriteAllText und die explizite UTF-8-Kodierung entsprechen dem im vorherigen Lab eingeführten Mechanismus, jetzt durch ShouldProcess geschützt.

Pfadauflösung, Einlesen des Originalobjekts, Trefferarrays, Vergleiche mit Groß-/Kleinschreibung, Fehlerbehandlung und das Sammeln aller Änderungen vor der Nutzung folgen dem vorherigen Lab. Speichere den vollständigen Ersatz mit dem wörtlichen Here-String; er ersetzt nur dein Skript:

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
}
'@

Vorschau anzeigen und die gesamte Operation ablehnen

Der nächste Befehl muss eine native WhatIf-Meldung ausgeben, die sowohl s-a: r-old -> r-equal als auch s-c: r-old -> r-large beschreibt und den neuen Ausgabepfad als Ziel nennt:

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

Lies den nativen Status des Kindprozesses sofort:

$LASTEXITCODE
0

Fordere nun eine native Bestätigung an. Warte auf die tatsächliche Rückfrage, lies die vollständige Aktion, gib N ein und drücke Enter. Füge N nicht schon vor Erscheinen der Rückfrage ein:

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

Du solltest Confirm, die vollständige Aktion und Optionen wie [Y] Yes und [N] No sehen. Für beide Änderungen gibt es eine Frage. Mit No wird die Operation übersprungen; dies ist kein Validierungsfehler, daher beendet sich der Kindprozess erfolgreich, ohne Arbeit auszuführen:

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

Lies das Original erneut, statt einer Speichervariablen zu vertrauen:

Get-Content ./event.json -Raw

Alle RoomId-Werte der Sitzungen bleiben r-old, und die verschachtelten Notizen bleiben vorhanden. Weder WhatIf noch No dürfen eine Datei, ein Verzeichnis oder einen temporären Kandidaten hinterlassen. Die Funktionsprüfung liest deine Originaldateien und testet das exakte Skript in entbehrlichen PowerShell-PTYs, einschließlich echter N/Y-Eingaben. Diese Proben testen das Programmverhalten; sie erzeugen oder behaupten keine Historie deiner Bestätigungen in der echten Szene.

Prüfe bei einem falschen Pfad Get-Location und Get-ChildItem und kehre mit Set-Location /home/labex/project/room-changes zurück. Einen Skript-Tippfehler behebst du durch erneutes Speichern des vollständigen korrigierten Here-Strings. Falls du versehentlich zu früh zustimmst, erhalte die Quelle und entferne vor dem erneuten Üben der Ablehnung nur dein eigenes event-updated.json mit Remove-Item -LiteralPath ./event-updated.json. Lösche oder bearbeite niemals das ursprüngliche Ereignis, die Anfragen oder Notizen, um einen Fehler zu verbergen.

Zustimmen und den tatsächlichen Zeitplan erneut öffnen

In diesem Schritt genehmigst du den vollständigen geprüften Plan, liest das gespeicherte Ereignis und prüfst die Ablehnung einer späteren ungültigen Anfrage.

Beginne in derselben PowerShell-Sitzung von Terminal unter /home/labex/project/room-changes. Dein korrigiertes Skript ist aus dem vorherigen Schritt gespeichert; die endgültige Ausgabe fehlt weiterhin. Wenn du ein neues Terminal geöffnet hast, verwende denselben expliziten UTF-8-PowerShell-Aufruf und Set-Location aus jenem Schritt. Dieses Skript benötigt keine Sitzungsvariablen, da jeder Pfad ein Argument ist.

Einen vollständigen genehmigten Plan anwenden

Führe den Bestätigungsaufruf erneut aus. Warte auf die native Rückfrage, lies beide ID-Zuordnungen, gib Y ein und drücke Enter:

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

Erwarte eine Frage, danach Saved: und den Ausgabepfad. Lies sofort den nativen Status:

$LASTEXITCODE
0

Damit bestätigst du den gesamten geprüften Plan einmal. Dies garantiert keine Transaktion bei Absturz, Stromausfall oder gleichzeitigen Schreibzugriffen. Die Eingabe bleibt das Original; das Ergebnis ist ein separates vollständiges Ereignis. Ein Schreibfehler ist ein Fehler und keine Erlaubnis, die Anwendung des Zeitplans zu behaupten.

Der vorbereitete Leser liest genau die an -Path übergebene Datei. Er erzeugt kein fehlendes Ergebnis neu und greift nicht ersatzweise auf event.json zurück:

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

Die tatsächlich gespeicherten Zeilen zeigen nun s-a in r-equal, s-b weiterhin in r-old und s-c in r-large. Zwei Sitzungen heißen weiterhin Shared maps und zwei Räume weiterhin Studio; die IDs unterscheiden sie.

Öffne die vollständige Ausgabe erneut als Wörterbücher und behalte zeitstempelähnliche Texte als Zeichenfolgen:

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

Ein Punkt führt durch verschachtelte Eigenschaften. Die ursprünglichen Metadaten der ersten Sitzung enthalten einen datumsähnlichen Text und einen anders geschriebenen Schlüssel. Der Zugriff in eckigen Klammern verwendet den jeweiligen exakten Wörterbuchschlüssel. Lies beide getrennt:

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

Lies einen weiteren unbekannten Wert und die unabhängige Originalnotiz:

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

Diese Beobachtungen ergänzen den vollständigen semantischen Vergleich; zwei erhaltene Felder allein beweisen nicht, dass alle unbekannten Objekte überlebt haben. Baue das Ereignis nicht aus einer Projektion auf, die nur angezeigte Spalten enthält.

Verhindern, dass eine spätere ungültige Anfrage eine Teiländerung veröffentlicht

Die vorbereitete bad-request.json enthält eine gültige erste Anfrage und einen unbekannten Raum in der zweiten. Verwende eine andere neue Ausgabe, damit das fertige Ergebnis erhalten bleibt. Der nächste Aufruf schlägt absichtlich vor jeder Bestätigung fehl:

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

Erwarte Room change failed: und eine Erklärung zum unbekannten Raum, ohne Bestätigungsfrage. Lies sofort das Ergebnis:

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

Die frühere gültige Anfrage wurde nicht allein angewendet oder veröffentlicht. Ändere für spätere legitime Arbeit das Argument zurück zu requests.json; bearbeite das bereitgestellte fehlerhafte Beispiel nicht. Das ursprüngliche Ereignis und das fertige event-updated.json bleiben verfügbar.

Auch ein vorhandenes Ziel führt zu einer beabsichtigten Ablehnung. Du kannst dies mit WhatIf beobachten, das weiterhin vor jeder Autorisierung die Voraussetzungen prüft:

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

Erwarte Output already exists; choose a new path.. Eine Vorschau erlaubt nicht, ungültige Argumente zu ignorieren. Behalte das bestehende Ergebnis; ein neuer Aufruf benötigt einen neuen Ausgabepfad. Wenn du nach einer Skriptkorrektur absichtlich das kanonische Ergebnis dieses Labs neu erstellen willst, entferne nur deine eigene Ausgabe und wiederhole die gültige Bestätigung. Die Originale bleiben die Wiederherstellungsreferenz.

Die Abschlussprüfung liest das tatsächliche vollständige Ergebnis und die Erhaltungsbaseline. Sie testet deine originalen Skriptbytes in isolierten Ereignissen mit anderen IDs, Kapazitätsgleichheit, gleichnamigen Datensätzen, verschachtelten Daten und Fehlerpfaden. Gleichwertige Schleifen- und Objektimplementierungen sind willkommen. Verglichen werden JSON-Werte und -Typen, nicht Objekteigenschaftsreihenfolge, Befehlsschreibweise, Editorwahl oder Erfolgsmeldungen. Eine wiederholte Prüfung wendet dein echtes Ereignis nicht an, setzt es nicht zurück und repariert es nicht.

Zusammenfassung

Du hast eine irreführende Vorschau korrigiert, den gesamten Plan vor dem Schreiben geprüft und eine einzige native ShouldProcess-Entscheidung verwendet. WhatIf und No ließen die Arbeit unverändert; Yes speicherte ein vollständiges Ereignis mit den angeforderten ID-Beziehungen und allen übrigen verschachtelten Werten. Du hast diese echte Datei erneut geöffnet und bestätigt, dass eine spätere ungültige Anfrage oder eine bestehende Ausgabe abgelehnt werden, statt stillschweigend veröffentlicht oder ersetzt zu werden.