computer name変更は、domain membershipとsecure channelを含むidentity transactionとして扱います。変更前にold name・PartOfDomain・domain/workgroupを新規checkpointへ保存し、domain joined端末ではhealthy secure channelを開始条件にします。rollback完了も旧nameだけでなく全fieldの一致を必須にします。
変更前domain identityを保存する
新nameを検証し、現在のWin32_ComputerSystemを一件取得します。domain joined端末でTest-ComputerSecureChannelがfalseまたはerrorならrenameしません。checkpointはGUID名をCreateNewで作り、既存stateを上書きしません。
$ErrorActionPreference='Stop'
$newName='APP-SRV-02'
if ($newName -notmatch '^[A-Za-z0-9](?:[A-Za-z0-9-]{0,13}[A-Za-z0-9])?$') { throw 'computer name形式が不正です。' }
$cs=Get-CimInstance Win32_ComputerSystem -ErrorAction Stop
$oldName=[string]$cs.Name
if ($oldName -ieq $newName) { throw '既に同じcomputer nameです。' }
$domainHealthy=$null
if ([bool]$cs.PartOfDomain) {
$domainHealthy=Test-ComputerSecureChannel -ErrorAction Stop
if (-not $domainHealthy) { throw '変更前secure channelが不健全です。先に修復してください。' }
}
$operationId=[guid]::NewGuid().ToString('D')
$stateDir='C:\ProgramData\ITtrip\ComputerNameChange'
$null=New-Item -ItemType Directory -Path $stateDir -Force -ErrorAction Stop
$checkpointPath=Join-Path $stateDir "$operationId.json"
$state=[ordered]@{OperationId=$operationId;Stage='Planned';OldName=$oldName;NewName=$newName;PartOfDomain=[bool]$cs.PartOfDomain;Domain=[string]$cs.Domain;BaselineSecureChannel=$domainHealthy;CreatedUtc=(Get-Date).ToUniversalTime().ToString('o')}
$json=$state|ConvertTo-Json -Depth 5
$bytes=[Text.Encoding]::UTF8.GetBytes($json)
$stream=[IO.File]::Open($checkpointPath,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None)
try{$stream.Write($bytes,0,$bytes.Length);$stream.Flush($true)}finally{$stream.Dispose()}
[pscustomobject]@{Checkpoint=$checkpointPath;State=$state}
preview・再bind・実rename・別承認restart
WhatIfの後にold/new/domainを含むtokenを要求し、承認後にname・membership・domain・secure channelを再確認します。実Rename-Computerの成功を確認し、restartは保存と退避を確認した別tokenでのみ実行します。
Rename-Computer -NewName $newName -WhatIf
$token="RENAME-COMPUTER OP=$operationId FROM=$oldName TO=$newName DOMAIN=$($state.Domain)"
if((Read-Host "実変更を承認する場合だけ $token を入力") -ne $token){throw '中止しました。'}
$rebound=Get-CimInstance Win32_ComputerSystem -ErrorAction Stop
if([string]$rebound.Name -cne $oldName -or [bool]$rebound.PartOfDomain -ne $state.PartOfDomain -or [string]$rebound.Domain -cne $state.Domain){throw '承認後identityが変わりました。'}
if($state.PartOfDomain -and -not (Test-ComputerSecureChannel -ErrorAction Stop)){throw '承認後secure channelが不健全です。'}
$result=Rename-Computer -NewName $newName -PassThru -Force -ErrorAction Stop
if(-not $result.HasSucceeded){throw 'Rename-Computerが成功を返しませんでした。'}
$state.Stage='RenameIssued';$state.RenameIssuedUtc=(Get-Date).ToUniversalTime().ToString('o')
$state|ConvertTo-Json -Depth 5|Set-Content -LiteralPath $checkpointPath -Encoding UTF8 -ErrorAction Stop
$restartToken="RESTART-AFTER-RENAME OP=$operationId"
if((Read-Host "保存と退避を確認し、再起動する場合だけ $restartToken を入力") -ne $restartToken){throw 'renameは発行済みです。checkpointを保存し、承認済み時間に再起動してください。'}
$state.Stage='RenameRestartApproved';$state|ConvertTo-Json -Depth 5|Set-Content -LiteralPath $checkpointPath -Encoding UTF8 -ErrorAction Stop
Restart-Computer -Force -ErrorAction Stop
fresh sessionでapplyを検証しrollbackを分離する
再login後はcheckpointだけからnew name・PartOfDomain・domainとsecure channelを検証します。rollbackは別token、別Rename-Computer、別restart承認で行い、自動的に戻しません。
$checkpointPath='PASTE_CHECKPOINT_PATH'
$state=Get-Content -LiteralPath $checkpointPath -Raw -ErrorAction Stop|ConvertFrom-Json
if($state.Stage -ne 'RenameRestartApproved'){throw 'apply verify対象stageではありません。'}
$current=Get-CimInstance Win32_ComputerSystem -ErrorAction Stop
if([string]$current.Name -cne [string]$state.NewName -or [bool]$current.PartOfDomain -ne [bool]$state.PartOfDomain -or [string]$current.Domain -cne [string]$state.Domain){throw '新computer name/domain identityが一致しません。'}
if($state.PartOfDomain -and -not (Test-ComputerSecureChannel -ErrorAction Stop)){throw '変更後secure channelが不健全です。'}
[pscustomobject]@{Stage='AppliedVerified';Name=$current.Name;PartOfDomain=$current.PartOfDomain;Domain=$current.Domain;SecureChannel=if($state.PartOfDomain){$true}else{$null}}
$rollbackToken="ROLLBACK-COMPUTER-NAME OP=$($state.OperationId) FROM=$($state.NewName) TO=$($state.OldName)"
if((Read-Host "rollbackを開始する場合だけ $rollbackToken を入力。不要ならEnter") -eq $rollbackToken){
$again=Get-CimInstance Win32_ComputerSystem -ErrorAction Stop
if([string]$again.Name -cne [string]$state.NewName -or [bool]$again.PartOfDomain -ne [bool]$state.PartOfDomain -or [string]$again.Domain -cne [string]$state.Domain){throw 'rollback直前identityが一致しません。'}
if($state.PartOfDomain -and -not(Test-ComputerSecureChannel -ErrorAction Stop)){throw 'rollback直前secure channelが不健全です。'}
$rr=Rename-Computer -NewName ([string]$state.OldName) -PassThru -Force -ErrorAction Stop
if(-not $rr.HasSucceeded){throw 'rollback Rename-Computer失敗。'}
$state.Stage='RollbackIssued';$state|ConvertTo-Json -Depth 5|Set-Content -LiteralPath $checkpointPath -Encoding UTF8 -ErrorAction Stop
$rt="RESTART-AFTER-ROLLBACK OP=$($state.OperationId)"
if((Read-Host "rollback再起動を承認する場合だけ $rt を入力") -ne $rt){throw 'rollback renameは発行済みです。checkpointを保存してください。'}
$state.Stage='RollbackRestartApproved';$state|ConvertTo-Json -Depth 5|Set-Content -LiteralPath $checkpointPath -Encoding UTF8 -ErrorAction Stop
Restart-Computer -Force -ErrorAction Stop
}
fresh session VERIFY_ROLLBACK
旧name、PartOfDomain、domain/workgroup、domain joined時のsecure channelをすべてexact verifyします。一つでも読取失敗・不一致・falseなら成功表示せず、追加renameを止めてdomain管理者へescalateします。
$checkpointPath='PASTE_CHECKPOINT_PATH'
$state=Get-Content -LiteralPath $checkpointPath -Raw -ErrorAction Stop|ConvertFrom-Json
if($state.Stage -ne 'RollbackRestartApproved'){throw 'VERIFY_ROLLBACK対象stageではありません。'}
$current=Get-CimInstance Win32_ComputerSystem -ErrorAction Stop
$failures=[Collections.Generic.List[string]]::new()
if([string]$current.Name -cne [string]$state.OldName){$failures.Add('OldName mismatch')}
if([bool]$current.PartOfDomain -ne [bool]$state.PartOfDomain){$failures.Add('PartOfDomain mismatch')}
if([string]$current.Domain -cne [string]$state.Domain){$failures.Add('Domain/workgroup mismatch')}
$secure=$null
if($state.PartOfDomain){try{$secure=Test-ComputerSecureChannel -ErrorAction Stop;if(-not $secure){$failures.Add('secure channel unhealthy')}}catch{$failures.Add("secure channel read failed: $($_.Exception.Message)")}}
if($failures.Count){[pscustomobject]@{Stage='RollbackFailed';Failures=@($failures);Escalate=$true};throw 'rollback verification failed; 追加renameせずdomain管理者へescalateします。'}
[pscustomobject]@{Stage='RollbackVerified';Name=$current.Name;PartOfDomain=$current.PartOfDomain;Domain=$current.Domain;SecureChannel=$secure;Exact=$true}
受入条件
broken secure channel fixtureでは変更前に停止します。clean applyではnew nameとdomain identity、clean rollbackではold name・membership・domain・secure channelが一致します。旧nameだけ一致するfixtureはRollbackFailedです。
運用上の停止条件
cluster node、certificateやlicenseがhostnameに依存するserver、代替管理経路がないremote hostでは実施しません。DNS、SPN、監視、backup、endpoint管理のrename影響を事前に確認してください。

コメント