#Requires -Version 5.1 [CmdletBinding(SupportsShouldProcess)] param( [ValidateSet('Plan','Apply','Restore','Confirm','Validate','CaptureStart','CaptureStop')][string]$Mode='Plan', [ValidateSet('Observe','BlockOptionalTelemetry','Isolate')][string]$Profile='Observe', [int]$RollbackMinutes=10,[string]$Journal,[string]$CaptureDirectory,[switch]$FromRecoveryTask,[switch]$Json ) Set-StrictMode -Version Latest;$ErrorActionPreference='Stop' Import-Module (Join-Path $PSScriptRoot 'src/Wind.Core.psm1') Import-Module (Join-Path $PSScriptRoot 'src/Wind.Network.psm1') $stateRoot=Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'MsPrivate\network' if($Mode -eq 'Plan'){$x=Get-WindNetworkPlan -Profile $Profile -RollbackMinutes $RollbackMinutes;if($Json){$x|ConvertTo-Json -Depth 8}else{$x.Items|Format-Table -Wrap};return} if($Mode -eq 'Validate'){$x=Get-WindNetworkValidation;if($Json){$x|ConvertTo-Json -Depth 8}else{$x|Format-List};return} if($Mode -eq 'CaptureStart'){ Assert-WindMutationAllowed;if(-not (Get-Command pktmon.exe -ErrorAction SilentlyContinue)){throw 'pktmon.exe is unavailable.'} if(-not $CaptureDirectory){$CaptureDirectory=Join-Path $stateRoot ('capture-'+(Get-Date -Format 'yyyyMMdd-HHmmss'))} [IO.Directory]::CreateDirectory($CaptureDirectory)|Out-Null $etl=Join-Path $CaptureDirectory 'capture.etl';& pktmon.exe start --capture --comp nics --pkt-size 0 --file-name $etl|Out-Host [pscustomobject]@{Status='Running';Directory=$CaptureDirectory;Warning='Capture can contain private addresses and DNS names. Stop and delete or export it explicitly.'}|ConvertTo-Json return } if($Mode -eq 'CaptureStop'){ if(-not $CaptureDirectory){throw 'CaptureStop requires -CaptureDirectory.'};$full=[IO.Path]::GetFullPath($CaptureDirectory) if(-not $full.StartsWith([IO.Path]::GetFullPath($stateRoot)+'\',[StringComparison]::OrdinalIgnoreCase)){throw 'Capture directory must be inside the ms-private local state directory.'} & pktmon.exe stop|Out-Host;$etl=Join-Path $full 'capture.etl';$pcap=Join-Path $full 'capture.pcapng';if(Test-Path $etl){& pktmon.exe etl2pcap $etl --out $pcap|Out-Host} Save-WindJournal -Journal (Get-WindNetworkValidation) -Path (Join-Path $full 'connections.json') Write-Output "Capture stopped: $full";return } Assert-WindMutationAllowed if($Mode -eq 'Apply'){ if($Profile -eq 'Observe'){throw 'Observe has no firewall changes to apply.'};if(-not $PSCmdlet.ShouldProcess('Windows Firewall',$Profile)){return} [IO.Directory]::CreateDirectory($stateRoot)|Out-Null if(-not $Journal){$Journal=Join-Path $stateRoot ((Get-Date -Format 'yyyyMMdd-HHmmss')+'-'+[guid]::NewGuid().ToString('N')+'.json')} $runtime=Join-Path $stateRoot 'recovery-runtime';[IO.Directory]::CreateDirectory((Join-Path $runtime 'src'))|Out-Null;[IO.Directory]::CreateDirectory((Join-Path $runtime 'config'))|Out-Null Copy-Item -LiteralPath $PSCommandPath -Destination (Join-Path $runtime 'Network.ps1') -Force foreach($name in @('Wind.Core.psm1','Wind.Network.psm1','MsPrivate.IO.psm1')){Copy-Item -LiteralPath (Join-Path $PSScriptRoot ('src\'+$name)) -Destination (Join-Path $runtime ('src\'+$name)) -Force} Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'config\network.json') -Destination (Join-Path $runtime 'config\network.json') -Force $result=Invoke-WindNetworkApply -Profile $Profile -RollbackMinutes $RollbackMinutes -JournalPath $Journal -RecoveryScript (Join-Path $runtime 'Network.ps1') -Confirm:$false Write-Output "Network journal: $Journal";$result|Format-List;return } if(-not $Journal){throw "$Mode requires -Journal."} $saved=Get-Content -LiteralPath $Journal -Raw|ConvertFrom-Json $save={param($x) Save-WindJournal -Journal $x -Path $Journal}.GetNewClosure() if($Mode-eq'Confirm'){Confirm-WindNetwork -Journal $saved -Save $save|Format-List}else{Restore-WindNetwork -Journal $saved -Save $save|Format-List}