Introdução
Um comando de mudança de sala deve conquistar a confiança do organizador: a visualização não deve deixar trabalho, a recusa não deve fazer nada e a aprovação deve aplicar exatamente o plano elegível inteiro. Apenas declarar suporte a WhatIf não basta para proteger todos os métodos de gravação de arquivos.
Este laboratório independente começa com seu próprio evento e um script incompleto. Use as habilidades de validação por IDs, JSON, parâmetros e tratamento de erros do laboratório guiado anterior; nenhum arquivo de uma VM anterior é necessário. PowerShell e recursos sintéticos estão preparados. Você usará o Terminal padrão, realizará confirmação nativa real e reabrirá o evento realmente salvo. A duração é uma estimativa do autor que inclui leitura e recuperação.
Colocar toda gravação atrás de uma única decisão
Nesta etapa, você corrigirá uma implementação enganosa de WhatIf para que tanto a visualização quanto a recusa da confirmação deixem o espaço de trabalho intacto.
Entender por que o script inicial é inseguro
Abra Terminal, inicialmente um shell Linux. Inicie uma sessão limpa do PowerShell com UTF-8 restrito à sessão desse papel. env transmite essas configurações somente a este programa; -NoLogo oculta a mensagem inicial e -NoProfile ignora perfis pessoais:
env LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 pwsh -NoLogo -NoProfile
Aguarde o prompt PS. Todos os comandos restantes são do PowerShell. Este laboratório tem seu próprio cenário preparado, independente das VMs anteriores:
Set-Location /home/labex/project/room-changes
O evento de origem e as solicitações usam o mesmo formato do laboratório anterior. Leia-os antes de alterar seu script:
Get-Content ./requests.json -Raw
./Show-RoomSchedule.ps1 -Path ./event.json
O apply-room-changes.ps1 inicial é propositalmente incompleto. Declara suporte a WhatIf, mas escreve por .NET sem pedir permissão e ainda não implementa as alterações completas de salas. Teste-o somente com esta nova saída de demonstração descartável:
pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./unsafe-preview.json -WhatIf
Ele imprime Plan applied.. Essa mensagem não prova que aplicou o plano correto ou respeitou a visualização. Verifique o arquivo real:
Test-Path ./unsafe-preview.json
True
Declarar um parâmetro comum não protege automaticamente gravações .NET arbitrárias. O evento original continua intacto, mas essa visualização não deveria ter criado arquivo de saída algum. Remova somente esse arquivo descartável com Remove-Item -LiteralPath, sem tocar nas entradas e notas:
Remove-Item -LiteralPath ./unsafe-preview.json
Preservar o evento completo
As entradas event.json, requests.json, small-request.json, bad-request.json e o arquivo não relacionado notes.txt devem manter bytes, modo de arquivo, proprietário e mtime. Não modifique o leitor preparado. Você pode substituir seu apply-room-changes.ps1 e criar o novo resultado nomeado. O evento e as solicitações são JSON UTF-8 bem formado. O evento contém IDs exatos únicos, participantes e capacidades inteiros não negativos e uma lista de aprovação. As solicitações formam um array não vazio de IDs únicos de sessões e IDs de salas de destino. Nomes são rótulos de apresentação; IDs distinguem maiúsculas e minúsculas.
Valores desconhecidos podem incluir objetos aninhados, arrays ordenados, null, booleanos, strings e números JSON finitos limitados dentro de 20 níveis. Chaves de objeto exatamente duplicadas e esquemas malformados arbitrários estão fora do escopo. Use ConvertFrom-Json -AsHashtable -DateKind String para preservar chaves desconhecidas com capitalizações distintas e manter strings com aparência de data com seus deslocamentos originais exatos. Essas opções foram introduzidas no laboratório anterior. A origem inclui tanto When quanto when; não os una. Use ConvertTo-Json -Depth 100 para o evento completo, preservando seus objetos aninhados desconhecidos. A profundidade padrão de dois é insuficiente. Representações finitas iguais, como 1 e 1.0, são aceitas; booleanos devem continuar booleanos, a ordem dos arrays deve ser mantida e a ordem das propriedades dos objetos é apenas estética.
A saída deve ser um novo arquivo dentro de um diretório pai real já existente, diferente de cada entrada. A cadeia de diretórios do curso é real; nenhum outro processo escreve simultaneamente nesses arquivos. Recuse arquivos, diretórios e links existentes, inclusive links sem destino, sem alterá-los. Um plano inválido deve recusar a invocação inteira antes de escrever: uma linha válida anterior não justifica um ID desconhecido posterior, uma sessão não aprovada, uma duplicata ou uma capacidade excedida. Igualdade com a capacidade é válida. As salas de destino já estão livres nesses horários.
Usar ShouldProcess nativo
Sua chamada reutilizável recebe EventPath, PlanPath e OutputPath, como o validador. Ela deve ser autônoma com esses argumentos e os comandos instalados, sem auxiliares vizinhos. Mantenha toda a validação antes da decisão de autorização e salve depois o evento completo alterando somente os campos RoomId solicitados.
Leia estas adições antes de salvar o código:
[CmdletBinding(SupportsShouldProcess)]adiciona os parâmetros comuns nativos-WhatIfe-Confirm. Não invente um parâmetro booleano que apenas imprime uma visualização.$PSCmdleté o objeto automático de comando do PowerShell.$PSCmdlet.ShouldProcess(target, action)retorna se a operação deve ser realizada. Com WhatIf, exibe a ação proposta e retorna false; com Confirm, pergunta e retorna true somente na aprovação.- Chame-o uma vez para o plano validado completo. O destino identifica a saída e a ação nomeia cada ID exato de sessão e os IDs de sala anterior e nova.
ForEach-Objectconstrói essa descrição;-join '; 'combina suas strings. Somente a ramificação true pode alterar objetos ou escrever arquivos. Uma recusa não deve criar diretórios de saída, arquivos ou trabalho temporário, nem alterar trabalho existente. - É uma confirmação geral, não um prompt por sessão. Aqui, o método .NET não gera um segundo prompt nativo. Se usar depois um cmdlet interno que admite confirmação, suprima seu prompt adicional somente dentro da ramificação já autorizada; nunca contorne a decisão externa.
- Depois da aprovação, cada
Sessionarmazenada referencia o objeto correspondente dentro do evento completo. Atualizar seuRoomIdmantém todos os outros campos. A chamada .NETWriteAllTexte a codificação UTF-8 explícita são o mesmo mecanismo apresentado no laboratório anterior, agora protegido por ShouldProcess.
Resolução de caminhos, análise do objeto original, arrays de correspondências, comparações sensíveis a maiúsculas, tratamento de erros e coleta de todas as alterações antes do uso seguem o laboratório anterior. Salve a substituição completa com a here-string literal; ela substitui apenas seu 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
}
'@
Visualizar e depois recusar a operação completa
O próximo comando deve emitir uma mensagem WhatIf nativa descrevendo tanto s-a: r-old -> r-equal quanto s-c: r-old -> r-large, com o novo caminho de saída como destino:
pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./event-updated.json -WhatIf
Leia imediatamente o status nativo do filho:
$LASTEXITCODE
0
Agora solicite confirmação nativa. Aguarde o prompt real, leia a ação completa, digite N e pressione Enter. Não cole N antes de o prompt aparecer:
pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./event-updated.json -Confirm
Você deverá ver Confirm, a ação completa e opções como [Y] Yes e [N] No. Há uma pergunta para as duas alterações. A opção No ignora a operação; isso não é um erro de validação, portanto o filho termina com sucesso sem fazer trabalho algum:
$LASTEXITCODE
0
Test-Path ./event-updated.json
False
Leia o original novamente, em vez de confiar em uma variável na memória:
Get-Content ./event.json -Raw
Todos os valores RoomId das sessões continuam r-old e as notas aninhadas permanecem. Nem WhatIf nem No devem deixar arquivo, diretório ou candidato temporário. A verificação funcional lê seus arquivos originais e testa o script exato em PTYs descartáveis do PowerShell, incluindo entradas N/Y reais. Essas sondagens testam o comportamento do programa; não criam nem alegam um histórico das suas confirmações no cenário real.
Se um caminho estiver errado, inspecione Get-Location e Get-ChildItem e volte com Set-Location /home/labex/project/room-changes. Um erro de digitação no script é corrigido salvando novamente a here-string completa corrigida. Se aprovar cedo por engano, preserve a origem e remova somente seu event-updated.json com Remove-Item -LiteralPath ./event-updated.json antes de praticar a recusa novamente. Nunca exclua nem edite o evento, as solicitações ou as notas originais para esconder um erro.
Aprovar e reabrir a programação real
Nesta etapa, você aprovará o plano completo validado, lerá o evento salvo e verificará a recusa de uma solicitação posterior inválida.
Comece na mesma sessão do PowerShell em Terminal, em /home/labex/project/room-changes. Seu script corrigido continua salvo da etapa anterior; a saída final ainda não existe. Se abriu outro Terminal, use o mesmo comando explícito de inicialização do PowerShell com UTF-8 e Set-Location daquela etapa. Este script não precisa de variáveis de sessão, pois cada caminho é um argumento.
Aplicar um plano aprovado completo
Execute novamente a chamada de confirmação. Aguarde o prompt nativo, leia os dois mapeamentos de IDs, digite Y e pressione Enter:
pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./event-updated.json -Confirm
Espere uma pergunta seguida de Saved: e do caminho de saída. Leia seu status nativo imediatamente:
$LASTEXITCODE
0
Isso confirma uma vez todo o plano validado. Não é uma garantia de transação para falhas do processo, queda de energia ou gravações concorrentes. A entrada é mantida como original; o resultado é um evento completo separado. Uma falha de escrita é um erro, não permissão para afirmar que a programação foi aplicada.
O leitor preparado lê exatamente o arquivo passado a -Path. Ele não regenera um resultado ausente nem recorre a event.json:
./Show-RoomSchedule.ps1 -Path ./event-updated.json
As linhas realmente salvas agora identificam s-a em r-equal, s-b ainda em r-old e s-c em r-large. Duas sessões ainda compartilham Shared maps e duas salas ainda compartilham Studio; os IDs as distinguem.
Reabra a saída inteira como dicionários, mantendo strings com aparência de timestamp como strings:
$saved = Get-Content ./event-updated.json -Raw | ConvertFrom-Json -AsHashtable -DateKind String
Um ponto percorre as propriedades aninhadas. Os metadados originais da primeira sessão têm uma string com aparência de data e uma chave com capitalização diferente. A consulta por colchetes usa cada chave exata do dicionário. Leia-as separadamente:
$saved.Sessions[0].Metadata.contact['When']
2026-11-07T09:30:00+05:30
$saved.Sessions[0].Metadata.contact['when']
Keep this distinct key
Leia outro valor desconhecido e a nota original não relacionada:
$saved.Unknown.offset
2027-01-02T12:34:56-07:00
Get-Content ./notes.txt
Venue manager: preserve this personal access note.
Essas observações acompanham a comparação semântica completa; dois campos preservados não bastariam para provar que todos os objetos desconhecidos sobreviveram. Não reconstrua o evento de uma projeção contendo apenas as colunas exibidas.
Impedir que uma solicitação posterior inválida publique uma alteração parcial
O bad-request.json preparado tem uma primeira solicitação válida e uma sala desconhecida na segunda. Use outra saída nova para manter o resultado concluído intacto. A próxima chamada falha intencionalmente antes de qualquer confirmação:
pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./bad-request.json -OutputPath ./rejected-event.json -Confirm
Espere Room change failed: e uma explicação sobre a sala desconhecida, sem pergunta de confirmação. Leia o resultado imediatamente:
$LASTEXITCODE
1
Test-Path ./rejected-event.json
False
A solicitação válida anterior não foi aplicada nem publicada isoladamente. Para trabalhos legítimos futuros, volte o argumento a requests.json; não edite o exemplo incorreto fornecido. O evento original e o event-updated.json concluído continuam disponíveis.
Um destino existente também é uma recusa intencional. Você pode observá-la com WhatIf, que ainda valida pré-condições antes de autorizar qualquer ação:
pwsh -NoLogo -NoProfile -File ./apply-room-changes.ps1 -EventPath ./event.json -PlanPath ./requests.json -OutputPath ./event-updated.json -WhatIf
$LASTEXITCODE
1
Espere Output already exists; choose a new path.. Visualizar não permite ignorar argumentos inválidos. Mantenha o resultado existente; uma nova invocação precisa de um novo caminho de saída. Se quiser recriar deliberadamente o resultado canônico deste laboratório depois de corrigir seu script, remova apenas sua saída e repita a confirmação válida. Os originais permanecem como referência de recuperação.
A verificação final lê o resultado completo real e a referência de preservação. Ela testa os bytes originais do seu script em eventos isolados com outros IDs, capacidade igual, registros com títulos iguais, dados aninhados e caminhos de falha. Loops e implementações de objetos equivalentes são aceitos. Ela compara valores e tipos JSON, não ordem de propriedades de objetos, escrita de comandos, editor escolhido ou mensagens de sucesso. Repetir a verificação não aplica, redefine nem repara seu evento real.
Resumo
Você corrigiu uma visualização enganosa, validou o plano completo antes de escrever e usou uma única decisão ShouldProcess nativa. WhatIf e No deixaram o trabalho intacto; Yes salvou um evento completo com os relacionamentos de IDs solicitados e todos os demais valores aninhados preservados. Você reabriu esse arquivo real e confirmou que uma solicitação posterior inválida ou uma saída existente é recusada em vez de publicada ou substituída silenciosamente.



