Введение
Команда смены комнат должна заслуживать доверия организатора: предпросмотр не должен оставлять работу, отказ не должен ничего делать, а одобрение должно применять в точности весь допустимый план. Одного объявления поддержки WhatIf недостаточно для защиты всех способов записи файлов.
Эта независимая работа начинается со своего мероприятия и незавершённого сценария. Используйте навыки проверки по ID, JSON, параметров и обработки ошибок из предыдущей пошаговой работы; файлы прежней VM не нужны. PowerShell и синтетические ресурсы подготовлены. Вы будете работать в Terminal по умолчанию, выполнять настоящие встроенные подтверждения и снова открывать действительно сохранённое мероприятие. Длительность оценена автором с учётом чтения и восстановления.
Поместите каждую запись за единым решением
На этом шаге вы исправите вводящую в заблуждение реализацию WhatIf, чтобы предпросмотр и отказ в подтверждении оставляли рабочую область без изменений.
Разберитесь, почему начальный сценарий небезопасен
Откройте Terminal, где изначально работает оболочка Linux. Запустите чистый сеанс PowerShell с UTF-8 только для сеанса этой роли. env передаёт настройки лишь этой программе; -NoLogo скрывает баннер, а -NoProfile пропускает личные профили:
env LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 pwsh -NoLogo -NoProfile
Дождитесь приглашения PS. Далее выполняются только команды PowerShell. В этой лабораторной работе свой подготовленный сценарий, независимый от предыдущих VM:
Set-Location /home/labex/project/room-changes
Исходное мероприятие и запросы имеют тот же формат, что и в предыдущей работе. Прочитайте их перед изменением сценария:
Get-Content ./requests.json -Raw
./Show-RoomSchedule.ps1 -Path ./event.json
Начальный apply-room-changes.ps1 намеренно не завершён. Он объявляет поддержку WhatIf, но пишет через .NET, не запрашивая разрешения, и не реализует полную смену комнат. Испытайте его только с этим новым одноразовым демонстрационным результатом:
pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./unsafe-preview.json -WhatIf
Он выводит Plan applied.. Это сообщение не доказывает ни применение правильного плана, ни соблюдение предпросмотра. Проверьте настоящий файл:
Test-Path ./unsafe-preview.json
True
Объявление общего параметра не защищает автоматически произвольную запись через .NET. Исходное мероприятие осталось целым, но предпросмотр не должен был создавать выходной файл. Удалите только этот одноразовый файл через Remove-Item -LiteralPath, не трогая входные данные и заметки:
Remove-Item -LiteralPath ./unsafe-preview.json
Сохраните мероприятие целиком
Входные event.json, requests.json, small-request.json, bad-request.json и не относящийся к задаче notes.txt должны сохранить байты, режим файла, владельца и mtime. Не меняйте подготовленный инструмент чтения. Вы можете заменить свой apply-room-changes.ps1 и создать указанный новый результат. Мероприятие и запросы предоставлены в корректном UTF-8 JSON. В мероприятии есть уникальные точные ID, неотрицательные целые числа участников и вместимости и список одобрений. Запросы — непустой массив уникальных ID сессий и ID целевых комнат. Названия служат отображаемыми метками; ID учитывают регистр.
Неизвестные значения могут включать вложенные объекты, упорядоченные массивы, null, логические значения, строки и ограниченные конечные числа JSON в пределах 20 уровней. Полностью повторяющиеся ключи объектов и произвольные некорректные схемы вне рамок задачи. Используйте ConvertFrom-Json -AsHashtable -DateKind String, чтобы сохранять неизвестные ключи с разным регистром и строки, похожие на даты, с их точными исходными смещениями. Эти параметры введены в предыдущей работе. В источнике есть и When, и when; не объединяйте их. Для полного мероприятия используйте ConvertTo-Json -Depth 100, чтобы сохранить неизвестные вложенные объекты. Глубины по умолчанию, равной двум, недостаточно. Равные конечные числовые записи вроде 1 и 1.0 допустимы; логические значения должны остаться логическими, порядок массивов должен сохраниться, а порядок свойств объекта влияет лишь на оформление.
Результат должен быть новым файлом в уже существующем настоящем родительском каталоге и отличаться от всех входных файлов. Цепочка каталогов курса настоящая; другие процессы одновременно не пишут в эти файлы. Отклоняйте существующие файлы, каталоги и ссылки, включая ссылки с отсутствующей целью, не меняя их. Недопустимый план должен отклонять весь вызов до записи: допустимая ранняя строка не оправдывает последующий неизвестный ID, неодобренную сессию, дубликат или превышение вместимости. Равенство вместимости допустимо. Целевые комнаты уже свободны в нужное время.
Используйте встроенный ShouldProcess
Повторно используемый вызов принимает EventPath, PlanPath и OutputPath, как валидатор. Он должен быть самостоятельным, использовать эти аргументы и установленные команды без соседних помощников. Полная проверка должна предшествовать решению об авторизации; затем сохраняется всё мероприятие с изменением только запрошенных полей RoomId.
Прочитайте дополнения перед сохранением кода:
[CmdletBinding(SupportsShouldProcess)]добавляет встроенные общие параметры-WhatIfи-Confirm. Не создавайте собственный логический параметр, который лишь печатает предпросмотр.$PSCmdlet— автоматический объект команды PowerShell.$PSCmdlet.ShouldProcess(target, action)возвращает решение о выполнении операции. С WhatIf он показывает предполагаемое действие и возвращает false; с Confirm задаёт вопрос и возвращает true только при одобрении.- Вызывайте его один раз для полного проверенного плана. Цель обозначает выходной файл, а действие называет каждый точный ID сессии и ID прежней и новой комнаты.
ForEach-Objectсоздаёт описание;-join '; 'объединяет строки. Только ветвь true может менять объекты или писать файлы. Отказ не должен создавать выходные каталоги, файлы или временную работу и не должен менять существующую работу. - Это единое подтверждение, а не вопрос для каждой сессии. Метод .NET здесь не создаёт второго встроенного запроса. Если позднее вы используете внутренний командлет с подтверждением, подавляйте его дополнительный вопрос только внутри уже авторизованной ветви; никогда не обходите внешнее решение.
- После одобрения каждый сохранённый
Sessionссылается на соответствующий объект внутри полного мероприятия. Обновление егоRoomIdсохраняет все прочие поля. Вызов .NETWriteAllTextи явная UTF-8-кодировка — тот же механизм, что в предыдущей работе, теперь защищённый ShouldProcess.
Разрешение путей, чтение исходного объекта, массивы совпадений, сравнение с учётом регистра, обработка ошибок и сбор всех изменений до использования соответствуют предыдущей работе. Сохраните полную замену через литеральную here-string; она заменит только ваш сценарий:
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
}
'@
Выполните предпросмотр, затем отклоните всю операцию
Следующая команда должна выдать встроенное сообщение WhatIf, описывающее и s-a: r-old -> r-equal, и s-c: r-old -> r-large, с новым выходным путём в качестве цели:
pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./event-updated.json -WhatIf
Немедленно прочитайте собственный код завершения дочернего процесса:
$LASTEXITCODE
0
Теперь запросите встроенное подтверждение. Дождитесь настоящего вопроса, прочитайте действие целиком, введите N и нажмите Enter. Не вставляйте N до появления вопроса:
pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./event-updated.json -Confirm
Вы должны увидеть Confirm, полное действие и варианты, включая [Y] Yes и [N] No. Для обоих изменений задаётся один вопрос. Выбор No пропускает операцию; это не ошибка валидации, поэтому дочерний процесс успешно завершается без выполнения работы:
$LASTEXITCODE
0
Test-Path ./event-updated.json
False
Снова прочитайте оригинал вместо доверия переменной в памяти:
Get-Content ./event.json -Raw
Все значения RoomId сессий остались r-old, вложенные заметки на месте. Ни WhatIf, ни No не должны оставлять файл, каталог или временный кандидат. Функциональная проверка читает ваши оригиналы и испытывает точный сценарий в одноразовых PowerShell PTY, включая настоящий ввод N/Y. Эти испытания проверяют поведение программы, а не создают и не заявляют историю ваших подтверждений в реальном сценарии.
При неверном пути изучите Get-Location и Get-ChildItem, затем вернитесь через Set-Location /home/labex/project/room-changes. Опечатка в сценарии исправляется повторным сохранением полной корректной here-string. Если вы случайно одобрили слишком рано, сохраните источник и удалите только свой event-updated.json через Remove-Item -LiteralPath ./event-updated.json, прежде чем снова упражняться в отказе. Никогда не удаляйте и не редактируйте исходное мероприятие, запросы или заметки для сокрытия ошибки.
Одобрите и снова откройте настоящее расписание
На этом шаге вы одобрите полный проверенный план, прочитаете сохранённое мероприятие и проверите отказ при последующем недопустимом запросе.
Начните в том же сеансе PowerShell в Terminal, в /home/labex/project/room-changes. Исправленный сценарий сохранён на предыдущем шаге; итогового файла ещё нет. Если вы открыли новый Terminal, используйте ту же явную команду запуска PowerShell с UTF-8 и Set-Location из предыдущего шага. Переменные сеанса сценарию не нужны: каждый путь передаётся аргументом.
Примените один полный одобренный план
Снова выполните вызов с подтверждением. Дождитесь встроенного вопроса, прочитайте обе связи ID, введите Y и нажмите Enter:
pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./event-updated.json -Confirm
Ожидается один вопрос, после него Saved: и выходной путь. Немедленно прочитайте собственный код завершения:
$LASTEXITCODE
0
Так вы один раз подтверждаете полный проверенный план. Это не гарантия транзакции при сбоях, отключении питания или одновременной записи. Вход остаётся оригиналом; результат — отдельное полное мероприятие. Сбой записи является ошибкой, а не разрешением заявить о применении расписания.
Подготовленный инструмент чтения читает ровно файл, переданный в -Path. Он не создаёт отсутствующий результат заново и не подставляет event.json:
./Show-RoomSchedule.ps1 -Path ./event-updated.json
В действительно сохранённых строках s-a находится в r-equal, s-b всё ещё в r-old, а s-c в r-large. Две сессии по-прежнему называются Shared maps, а две комнаты — Studio; различать их позволяют ID.
Снова откройте весь результат как словари, сохранив похожие на временные метки строки строками:
$saved = Get-Content ./event-updated.json -Raw | ConvertFrom-Json -AsHashtable -DateKind String
Точка переходит по вложенным свойствам. В исходных метаданных первой сессии есть строка, похожая на дату, и ключ с другим регистром. Доступ через квадратные скобки использует каждый точный ключ словаря. Прочитайте их отдельно:
$saved.Sessions[0].Metadata.contact['When']
2026-11-07T09:30:00+05:30
$saved.Sessions[0].Metadata.contact['when']
Keep this distinct key
Прочитайте другое неизвестное значение и не относящуюся к задаче исходную заметку:
$saved.Unknown.offset
2027-01-02T12:34:56-07:00
Get-Content ./notes.txt
Venue manager: preserve this personal access note.
Эти наблюдения дополняют полное семантическое сравнение; двух сохранённых полей недостаточно, чтобы доказать сохранность всех неизвестных объектов. Не пересобирайте мероприятие из проекции, содержащей только отображаемые столбцы.
Не допустите публикации частичного изменения из-за позднего неверного запроса
В подготовленном bad-request.json первая заявка допустима, а во второй указана неизвестная комната. Используйте другой новый выходной файл, сохранив готовый результат. Следующий вызов намеренно завершается ошибкой до любого подтверждения:
pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./bad-request.json -OutputPath ./rejected-event.json -Confirm
Ожидается Room change failed: и объяснение неизвестной комнаты без вопроса о подтверждении. Немедленно прочитайте результат:
$LASTEXITCODE
1
Test-Path ./rejected-event.json
False
Ранняя допустимая заявка не была применена или опубликована отдельно. Для будущей допустимой работы верните аргумент на requests.json; не редактируйте предоставленный неверный пример. Исходное мероприятие и готовый event-updated.json остаются доступными.
Существующее назначение также намеренно отклоняется. Это можно увидеть с WhatIf, который всё равно проверяет предварительные условия до разрешения любых действий:
pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./event-updated.json -WhatIf
$LASTEXITCODE
1
Ожидается Output already exists; choose a new path.. Предпросмотр не разрешает игнорировать неверные аргументы. Сохраните существующий результат; новому вызову нужен новый выходной путь. Если после исправления сценария вы намеренно хотите пересоздать канонический результат работы, удалите только собственный выходной файл и повторите допустимое подтверждение. Оригиналы остаются эталоном восстановления.
Итоговая проверка читает настоящий полный результат и эталон сохранности. Она испытывает исходные байты вашего сценария на изолированных мероприятиях с другими ID, равенством вместимости, одинаковыми названиями, вложенными данными и путями отказа. Допускаются эквивалентные циклы и реализации объектов. Сравниваются значения и типы JSON, а не порядок свойств объекта, написание команд, выбор редактора или сообщения об успехе. Повторная проверка не применяет, не сбрасывает и не исправляет ваше реальное мероприятие.
Резюме
Вы исправили вводящий в заблуждение предпросмотр, проверили весь план до записи и использовали единое встроенное решение ShouldProcess. WhatIf и No оставили работу неизменной; Yes сохранил полное мероприятие с запрошенными связями ID и всеми прочими вложенными значениями. Вы снова открыли настоящий файл и подтвердили, что поздний неверный запрос или существующий результат отклоняются, а не публикуются и не заменяются молча.



