Enterprise-grade, zero-dependency PowerShell module for automating workstation transitions between air-gapped staging VLANs and production networks with automatic stateful rollback.
- Overview
- Architecture
- Directory Structure
- Prerequisites
- Quick Start
- Environment Profiles
- Function Reference
- Transition Pipeline
- Auto-Toggle Launcher
- Safety Mechanisms
- Audit & Logging
- Configuration Schema
- Troubleshooting
State-Ops solves a common problem in air-gapped or segmented network environments: safely switching a workstation's network identity between staging and production VLANs without risking a stranded, half-configured state.
| Principle | Implementation |
|---|---|
| Zero dependencies | Pure PowerShell 5.1+ using only built-in NetAdapter, NetTCPIP, and DnsClient cmdlets |
| Stateful rollback | Full adapter state captured to memory AND disk before any change; automatic rollback on ANY failure |
| Idempotent | Detects if the adapter already matches the target config and skips redundant changes |
| Auditable | Every operation writes structured entries to the Windows Application Event Log |
| Concurrency-safe | Lock file prevents parallel transitions on the same adapter |
| Fail-fast | Pre-flight diagnostics catch physical-layer issues (cable, link, IP conflict) before touching config |
flowchart TD
A["0. Test-NetStatePreFlight\n(Adapter up? Cable? IP conflict?)"] -->|PASS| B["1. Acquire Transition Lock"]
A -->|FAIL| Z["Abort -- no changes made"]
B --> C["2. Get-NetState\n(Backup to memory + JSON on disk)"]
C --> D["3. Set-NetState\n(Validate input -> Strip config -> Apply -> Flush DNS)"]
D --> E["4. Test-NetStateValidation\n(ICMP + TCP + DNS with retry/backoff)"]
E -->|PASS| F["5. Success\n(Event Log + structured result)"]
E -->|FAIL| G["ROLLBACK\n(Set-NetState with backup object)"]
G --> H["Event Log error entry + re-throw"]
F --> I["Release Lock"]
H --> I
flowchart TD
A["Load PROD.json + STG.json"] --> B["Get-NetState on adapter"]
B --> C{"Current IP matches\na known profile?"}
C -->|"Matches PROD"| D["Auto-target: STG"]
C -->|"Matches STG"| E["Auto-target: PROD"]
C -->|"No match"| F["Interactive menu:\n1=PROD 2=STG Q=Quit"]
D --> G["Invoke-EnvironmentTransition"]
E --> G
F --> G
G -->|OK| H["Success banner + summary"]
G -->|FAIL| I["Rollback + error details"]
State-Ops Controller/
|-- Switch-Environment.ps1 # Auto-toggle launcher (run this)
|-- StateOps/
| |-- StateOps.psm1 # Core module (8 public + 5 private functions)
| |-- StateOps.psd1 # Module manifest v2.1.0
| |-- Environments/
| |-- PROD.json # Production network profile
| |-- STG.json # Staging VLAN profile
| Requirement | Details |
|---|---|
| OS | Windows 10 / Server 2016 or later |
| PowerShell | 5.1+ (ships with Windows) |
| Privileges | Must run as Administrator (elevated session) |
| Network | Static IP adapter (e.g., Ethernet); DHCP adapters will lose their lease |
| Dependencies | None -- uses only built-in Windows cmdlets |
Edit the JSON files in StateOps/Environments/ to match your actual network:
PROD.json -- Production network
{
"AdapterName": "Ethernet",
"IPAddress": "10.0.50.25",
"PrefixLength": 24,
"DefaultGateway": "10.0.50.1",
"DnsServers": ["10.0.50.10", "10.0.50.11"],
"VlanId": 50
}STG.json -- Staging VLAN
{
"AdapterName": "Ethernet",
"IPAddress": "10.0.20.25",
"PrefixLength": 24,
"DefaultGateway": "10.0.20.1",
"DnsServers": ["10.0.20.10", "10.0.20.11"],
"VlanId": 20
}Open an elevated PowerShell window and run:
.\Switch-Environment.ps1The script will:
- Detect your current environment (PROD or STG) by matching the adapter's IP
- Prompt to confirm the switch to the opposite environment
- Execute the full transition pipeline with pre-flight, backup, apply, validate, and rollback protection
Import-Module .\StateOps\StateOps.psd1
# Snapshot current state
$current = Get-NetState -AdapterName 'Ethernet'
# Load a profile and transition
$target = Import-EnvironmentProfile -Path '.\StateOps\Environments\PROD.json'
Invoke-EnvironmentTransition -TargetConfiguration $target `
-ValidationTarget '10.0.50.1' `
-ValidationTcpPort 389 `
-ForceProfiles are plain JSON files stored in StateOps/Environments/. They can be version-controlled, templated, and validated at load time.
| Field | Type | Description |
|---|---|---|
AdapterName |
string |
Windows adapter display name (e.g., Ethernet) |
IPAddress |
string |
Target IPv4 address (validated via IPAddress.TryParse) |
PrefixLength |
int |
Subnet prefix length (1-32) |
DnsServers |
string[] |
One or more DNS server IPv4 addresses |
| Field | Type | Description |
|---|---|---|
DefaultGateway |
string |
IPv4 gateway; omit for isolated/gateway-less VLANs |
VlanId |
int |
802.1Q VLAN tag; applied via adapter advanced properties |
_meta |
object |
Documentation metadata (ignored by the module) |
# Load and validate a profile
$config = Import-EnvironmentProfile -Path '.\StateOps\Environments\PROD.json'
# Export current adapter state as a new profile
Export-EnvironmentProfile -AdapterName 'Ethernet' -Path '.\StateOps\Environments\CurrentState.json'
# Export an existing config object
Export-EnvironmentProfile -Configuration $config -Path '.\backup.json'| Function | Purpose | Key Parameters |
|---|---|---|
Get-NetState |
Snapshots full adapter state (IP, DNS, GW, VLAN, MAC, link speed, status) | -AdapterName, -ExportPath |
Set-NetState |
Validates input, strips existing config, applies new static IP/DNS, flushes DNS cache | -Configuration, -Force |
Test-NetStateValidation |
ICMP ping + TCP port + DNS resolution with configurable retry/backoff | -TargetHost, -TcpPort, -DnsName, -RetryCount |
Invoke-EnvironmentTransition |
Full orchestrated pipeline with pre-flight, backup, apply, validate, and auto-rollback | -TargetConfiguration, -ValidationTarget, -Force |
| Function | Purpose | Key Parameters |
|---|---|---|
Test-NetStatePreFlight |
Checks adapter existence, operational status, cable, link speed, and IP conflicts via ARP | -AdapterName, -TargetIP |
| Function | Purpose | Key Parameters |
|---|---|---|
Import-EnvironmentProfile |
Loads and validates a JSON profile | -Path |
Export-EnvironmentProfile |
Serializes a config or live state to JSON | -Configuration or -AdapterName, -Path |
| Function | Purpose | Key Parameters |
|---|---|---|
Get-StateOpsLog |
Queries Windows Event Log for StateOps entries | -Hours, -EntryType, -MaxRecords |
Every configuration object is validated before use:
- IPv4 addresses parsed via
[System.Net.IPAddress]::TryParse() - Prefix length enforced to 1-32 range
- DNS array must contain at least one valid IPv4 address
- Required properties checked:
AdapterName,IPAddress,PrefixLength,DnsServers
Set-NetState compares the target config against the adapter's live state. If IP, prefix, and DNS already match, the operation is skipped with a [SKIP] message. Override with -Force.
Test-NetStatePreFlight runs before every transition (unless -SkipPreFlight):
- Adapter exists and is status
Up - Media connection state is
Connected(cable plugged in) - Link speed reported
- ARP probe for IP conflicts on the target address
A .lock file in $env:TEMP\StateOps\ prevents concurrent transitions on the same adapter. Stale locks (older than 300 seconds) are automatically cleared.
Before applying any changes, the current state is:
- Held in memory (for immediate rollback)
- Written to
$env:TEMP\StateOps\backup_<adapter>_<timestamp>.json(for manual recovery)
If Set-NetState or Test-NetStateValidation throws, the catch block in Invoke-EnvironmentTransition:
- Logs a Critical event to the Windows Event Log
- Calls
Set-NetStatewith the backup object to restore the original config - Re-throws the error to the caller
Clear-DnsClientCache is called automatically after every DNS server change to prevent stale resolution.
State-Ops registers itself as an Event Log source under Application/StateOps and writes entries at every pipeline boundary:
| Event ID | Category | Trigger |
|---|---|---|
1000 |
StateCapture | Get-NetState completes a snapshot |
2000 |
ConfigApply | Set-NetState applies or skips a config |
3000 |
Validation | Test-NetStateValidation reports pass/fail |
4000 |
Rollback | Automatic rollback executed |
5000 |
TransitionComplete | Full pipeline succeeds |
6000 |
PreFlight | Pre-flight diagnostics result |
7000 |
ProfileIO | Profile imported or exported |
9000 |
CriticalFailure | Fatal errors (apply fail, rollback fail) |
# Last 8 hours, all entry types
Get-StateOpsLog -Hours 8
# Only errors from the last 24 hours
Get-StateOpsLog -EntryType Error
# Formatted table
Get-StateOpsLog -Hours 1 | Format-Table TimeGenerated, EntryType, Message -AutoSizeAdapterName : Ethernet
InterfaceIndex : 5
IPAddress : 10.0.50.25
PrefixLength : 24
DefaultGateway : 10.0.50.1
DnsServers : {10.0.50.10, 10.0.50.11}
VlanId : 50
MacAddress : AA-BB-CC-DD-EE-FF
LinkSpeed : 1 Gbps
AdapterStatus : Up
MediaState : Connected
Timestamp : 2026-03-01T12:00:00.0000000+01:00
ComputerName : WKS-001
CapturedBy : admin
Success : True
IcmpSuccess : True
TcpSuccess : True
DnsSuccess : # null = not tested
LatencyMs : 1.23
RetryAttempts : 0
TargetHost : 10.0.50.1
Timestamp : 2026-03-01T12:00:05.0000000+01:00
Success : True
BackupState : [StateOps.NetState]
AppliedState : [PSCustomObject]
ValidationResult : [StateOps.ValidationResult]
BackupFile : C:\Users\admin\AppData\Local\Temp\StateOps\backup_Ethernet_20260301_120000.json
Duration : 00:00:07.3421234
Timestamp : 2026-03-01T12:00:07.0000000+01:00
| Symptom | Cause | Resolution |
|---|---|---|
State-Ops requires an elevated PowerShell session |
Not running as admin | Right-click PowerShell > Run as Administrator |
Adapter 'X' not found |
Wrong AdapterName in profile |
Run Get-NetAdapter to list actual adapter names |
A transition is already in progress |
Stale lock from crashed session | Delete $env:TEMP\StateOps\StateOps_*.lock manually |
Pre-flight FAILED: Media 'Disconnected' |
Network cable unplugged | Check physical connection |
Validation FAILED after N attempts |
Target unreachable on new VLAN | Verify VLAN trunking, firewall rules, and gateway reachability |
ROLLBACK FAILED |
Multiple cascading errors | Manually restore from backup JSON at $env:TEMP\StateOps\ |
| Module won't parse on PS 5.1 | Non-ASCII characters in file | Ensure file uses ASCII-only encoding |
See CONTRIBUTING.md for development guidelines, code standards, and PR workflow.
See CHANGELOG.md for version history and release notes.
Mohamed Senator -- Network Automation Engineer
This project is licensed under the MIT License.