commit cce5204c254c0eeec5c9092759f13042149d865a Author: engelgardt Date: Sat May 16 11:36:45 2026 +0300 Initial public release: netswitch v1.0.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e515396 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Build output / staging +*.exe +portable-v*/ + +# Local backup of release archives +releases/ + +# Editor / OS junk +.vscode/ +.idea/ +.DS_Store +Thumbs.db diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bcb05b0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 engelgardt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..aee3f82 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# netswitch + +[![Latest release](https://img.shields.io/github/v/release/Engelgardt23/netswitch)](https://github.com/Engelgardt23/netswitch/releases/latest) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) + +A tiny portable tool to flip a Windows network adapter between a **static IP** and **DHCP** with a few keystrokes. + +Built for the recurring engineer chore of "give my laptop NIC 10.10.10.1 so I can talk to a server's BMC" and "now put it back on DHCP so I can have internet again." + +> **Made by engelgardt.** + +--- + +## Download + +Grab the latest release: [**releases page**](https://github.com/Engelgardt23/netswitch/releases/latest). +The asset is `netswitch-portable-vX.Y.Z.zip` (~30 KB). + +## Run + +1. Unzip anywhere. +2. Double-click `netswitch.exe`. +3. Accept the UAC prompt (admin is needed for `netsh interface ipv4 set address`). +4. Pick the network adapter from the list. +5. Choose mode: + - **Static**: enter IP (default `10.10.10.1`), mask (default `255.255.255.0`), gateway (optional). + - **DHCP**: just confirms — the NIC reverts to DHCP for both IP and DNS. + +## What it filters + +Only real, wired physical adapters appear in the picker. Wireless, VPN, virtual, Hyper-V, VMware, VirtualBox, TAP/TUN, WireGuard, OpenVPN, Tailscale, ZeroTier, Bluetooth, Loopback, WAN Miniport — all skipped. + +## Update check + +On every launch the tool calls GitHub's `/releases/latest` (3-second timeout). If a newer version is available, it prints a yellow notice and offers to open the download page in your browser. If you're offline, it stays silent. + +## Build from source + +The script is a single `netswitch.ps1`. To rebuild the bundled `.exe`: + +``` +Install-Module ps2exe -Scope CurrentUser +Invoke-ps2exe -inputFile netswitch.ps1 -outputFile netswitch.exe -requireAdmin -title "netswitch" -version 1.0.0.0 +``` + +## License + +MIT — see [LICENSE](LICENSE). diff --git a/netswitch.ps1 b/netswitch.ps1 new file mode 100644 index 0000000..c80c733 --- /dev/null +++ b/netswitch.ps1 @@ -0,0 +1,143 @@ +# netswitch v1.0.0 - quick NIC IP / DHCP toggle +# made by engelgardt + +$NetswitchVersion = '1.0.0' +$GithubRepo = 'Engelgardt23/netswitch' + +$ErrorActionPreference = 'Stop' + +# --- Self-elevate if not admin (no-op when launched from the ps2exe build which already requests admin) --- +$me = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() +if (-not $me.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + if ($PSCommandPath) { + Start-Process -FilePath 'powershell.exe' ` + -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File',"`"$PSCommandPath`"") ` + -Verb RunAs + } else { + Start-Process -FilePath ([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName) -Verb RunAs + } + exit +} + +# --- Banner --- +Write-Host "" +Write-Host "==============================================" -ForegroundColor Cyan +Write-Host " netswitch v$NetswitchVersion - NIC IP/DHCP toggle" -ForegroundColor Cyan +Write-Host " made by engelgardt" -ForegroundColor DarkCyan +Write-Host "==============================================" -ForegroundColor Cyan +Write-Host "" + +# --- Update check --- +function Test-NetswitchUpdate { + try { + $url = "https://api.github.com/repos/$GithubRepo/releases/latest" + $r = Invoke-RestMethod -Uri $url -TimeoutSec 3 -Headers @{ 'User-Agent' = "netswitch/$NetswitchVersion" } + $latest = ($r.tag_name -as [string]) -replace '^v','' + if (-not $latest) { return } + $toTuple = { param($s) + $parts = ($s -split '\.') | ForEach-Object { + $n = 0; [void][int]::TryParse($_, [ref]$n); $n + } + while ($parts.Count -lt 3) { $parts += 0 } + ,$parts[0..2] + } + $L = & $toTuple $latest + $C = & $toTuple $NetswitchVersion + $isNewer = $false + for ($i = 0; $i -lt 3; $i++) { + if ($L[$i] -gt $C[$i]) { $isNewer = $true; break } + if ($L[$i] -lt $C[$i]) { break } + } + if ($isNewer) { + Write-Host "Update available: v$NetswitchVersion -> $($r.tag_name)" -ForegroundColor Yellow + $ans = Read-Host "Open the download page in your browser? [Y/n]" + if ($ans -notmatch '^(n|N|no|NO)$') { + Start-Process $r.html_url + } + Write-Host "" + } + } catch { + # silent on offline / API errors + } +} +Test-NetswitchUpdate + +# --- Pick adapter (physical wired only) --- +$skipDescriptionPattern = 'VPN|Virtual|AnyConnect|TAP-|TUN-|Bluetooth|Loopback|WAN Miniport|Hyper-V|VMware|VirtualBox|WireGuard|OpenVPN|Tailscale|ZeroTier' +$skipMediaTypes = @('Native 802.11', 'Wireless WAN') + +$adapters = @(Get-NetAdapter | Where-Object { + $_.Status -notin @('Disabled','Not Present') -and + -not $_.Virtual -and + $_.MediaType -notin $skipMediaTypes -and + $_.InterfaceDescription -notmatch $skipDescriptionPattern -and + $_.Name -notmatch $skipDescriptionPattern +} | Sort-Object ifIndex) + +if ($adapters.Count -eq 0) { + Write-Host "No physical wired adapters found." -ForegroundColor Red + Read-Host "Press Enter to exit"; exit 1 +} + +Write-Host "Available adapters:" +for ($i = 0; $i -lt $adapters.Count; $i++) { + $a = $adapters[$i] + $ips = (Get-NetIPAddress -InterfaceIndex $a.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue | + Where-Object { $_.PrefixOrigin -ne 'WellKnown' }).IPAddress -join ', ' + Write-Host (" {0}) [{1,-12}] {2} ({3}) {4}" -f ($i + 1), $a.Status, $a.Name, $a.InterfaceDescription, $ips) +} + +do { + $sel = (Read-Host "Select adapter number").Trim() + $valid = ($sel -match '^\d+$') -and ([int]$sel -ge 1) -and ([int]$sel -le $adapters.Count) + if (-not $valid) { Write-Host "Invalid selection." -ForegroundColor Red } +} while (-not $valid) +$nic = $adapters[[int]$sel - 1] +Write-Host "" +Write-Host "Selected: $($nic.Name)" -ForegroundColor Green + +# --- Mode --- +Write-Host "" +Write-Host "Mode:" +Write-Host " 1) Static IP" +Write-Host " 2) DHCP" +$modeChoice = Read-Host "Choice [1]" +if ([string]::IsNullOrWhiteSpace($modeChoice)) { $modeChoice = '1' } + +if ($modeChoice.Trim() -eq '2') { + # --- DHCP --- + Write-Host "" + Write-Host "Setting $($nic.Name) to DHCP..." -ForegroundColor Yellow + & netsh interface ipv4 set address name="$($nic.Name)" source=dhcp + & netsh interface ipv4 set dnsservers name="$($nic.Name)" source=dhcp + Write-Host "Done." -ForegroundColor Green +} +else { + # --- Static --- + $ip = Read-Host "IP address [10.10.10.1]" + if ([string]::IsNullOrWhiteSpace($ip)) { $ip = '10.10.10.1' } + + $mask = Read-Host "Subnet mask [255.255.255.0]" + if ([string]::IsNullOrWhiteSpace($mask)) { $mask = '255.255.255.0' } + + $gw = Read-Host "Gateway (Enter to skip)" + + Write-Host "" + Write-Host "Setting $($nic.Name) -> $ip / $mask$( if ($gw) { " via $gw" })" -ForegroundColor Yellow + + if ([string]::IsNullOrWhiteSpace($gw)) { + & netsh interface ipv4 set address name="$($nic.Name)" static $ip $mask + } else { + & netsh interface ipv4 set address name="$($nic.Name)" static $ip $mask $gw + } + Write-Host "Done." -ForegroundColor Green +} + +# --- Show current state --- +Write-Host "" +Write-Host "Current IPv4 config:" -ForegroundColor Cyan +Get-NetIPAddress -InterfaceIndex $nic.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue | + Where-Object { $_.PrefixOrigin -ne 'WellKnown' } | + Format-Table IPAddress, PrefixLength, PrefixOrigin -AutoSize + +Read-Host "Press Enter to exit"