#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'){ if(-not $CaptureDirectory){$CaptureDirectory=Join-Path $stateRoot ('capture-'+[guid]::NewGuid().ToString('N'))} $CaptureDirectory=Resolve-WindCaptureDirectory -Path $CaptureDirectory -StateRoot $stateRoot $etl=Join-Path $CaptureDirectory 'capture.etl';if(Test-Path -LiteralPath $etl){throw 'Capture output already exists. Choose a new capture directory.'} if(-not $PSCmdlet.ShouldProcess($CaptureDirectory,'Start packet capture')){return} Assert-WindMutationAllowed [IO.Directory]::CreateDirectory($CaptureDirectory)|Out-Null Invoke-WindPacketMonitor -ArgumentList @('start','--capture','--comp','nics','--pkt-size','0','--file-name',$etl) [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=Resolve-WindCaptureDirectory -Path $CaptureDirectory -StateRoot $stateRoot if(-not $PSCmdlet.ShouldProcess($full,'Stop packet capture and convert to PCAPNG')){return} Assert-WindMutationAllowed $etl=Join-Path $full 'capture.etl';$pcap=Join-Path $full 'capture.pcapng' if(-not(Test-Path -LiteralPath $etl -PathType Leaf)){throw 'Capture ETL file is missing; no capture was stopped.'} if(Test-Path -LiteralPath $pcap){throw 'Converted capture already exists; preserve or move it before retrying conversion.'} Invoke-WindPacketMonitor -ArgumentList @('stop') Invoke-WindPacketMonitor -ArgumentList @('etl2pcap',$etl,'--out',$pcap) if(-not(Test-Path -LiteralPath $pcap -PathType Leaf)){throw 'Packet capture conversion returned success without creating the PCAPNG file.'} 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 $journalWriter=Get-Command Save-WindJournal -CommandType Function;$save={param($x)& $journalWriter -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}