NetEng Command Reference

Windows · macOS · FortiGate · Kubernetes · GitLab — for the whole tech team

0 results found for ""

No commands found

Try a different keyword, command name, or description

🔀
Windows CMD → macOS Terminal Equivalents
Network Core
Windows CMD / PowerShell macOS Terminal Description & Usage
ipconfig ifconfig
ip addr
Show Network Interfaces & IPsLists all active network interfaces, their IP addresses, subnet masks, and link-local addresses. Use ip addr for more modern output on macOS/Linux. BASIC
ipconfig /all ifconfig -a
networksetup -listallhardwareports
Detailed NIC InformationShows full adapter details including MAC addresses, DHCP status, DNS servers, and lease info. The networksetup command is macOS-specific and shows hardware port names. BASIC
ipconfig /flushdns sudo dscacheutil -flushcache sudo killall -HUP mDNSResponder Flush DNS CacheClears the local DNS resolver cache. Essential after DNS changes, split-tunnel VPN issues, or when troubleshooting stale DNS entries. Both commands required on modern macOS. SUDO
ipconfig /release ipconfig /renew sudo ipconfig set en0 DHCP sudo ipconfig set en0 NONE Release / Renew DHCP LeaseForce a new DHCP lease. Replace en0 with your interface (en1 for Wi-Fi on some Macs). Useful when IP conflicts arise or after VLAN changes. SUDO
ping 8.8.8.8 ping -n 10 8.8.8.8 ping 8.8.8.8 ping -c 10 8.8.8.8 ICMP PingTests basic reachability and measures round-trip latency. Windows uses -n for count; macOS/Linux uses -c. On macOS, ping runs indefinitely without -c. BASIC
tracert 8.8.8.8 pathping 8.8.8.8 traceroute 8.8.8.8 mtr 8.8.8.8 Trace Network Pathtraceroute shows each hop to destination. mtr (install via Homebrew: brew install mtr) is superior — combines ping + traceroute with live packet loss stats. INSTALL mtr
route print route print -4 netstat -rn route -n get default View Routing TableShows all routing entries. route -n get default quickly shows the default gateway. Add a static route on macOS: sudo route add -net 10.0.0.0/8 192.168.1.1. ROUTING
nslookup google.com nslookup -type=MX domain.com nslookup google.com dig google.com dig MX domain.com DNS LookupResolves hostnames to IPs (and vice versa). dig is more powerful — shows full DNS response with TTL, authoritative server, and record types. Prefer dig for troubleshooting. PREFER dig
arp -a arp -d 192.168.1.1 arp -a sudo arp -d 192.168.1.1 ARP TableDisplays the ARP cache mapping IP → MAC addresses. Use to identify devices on the local segment or diagnose IP conflicts. Delete a stale entry with -d. LAYER 2
netstat -ano netstat -anob netstat -anv ss -tulnp lsof -i :443 Active Connections & Open PortsShows TCP/UDP connections, listening ports, and associated PIDs. lsof -i :PORT is the quickest way to check what's using a specific port on macOS. PORTS
hostname hostname scutil --get ComputerName Show HostnameReturns the machine's hostname. scutil --get ComputerName shows the user-friendly macOS name (may differ from DNS hostname). BASIC
getmac /v ipconfig /all ifconfig en0 | grep ether networksetup -getmacaddress en0 Get MAC AddressShows the hardware MAC address for an interface. Replace en0 with the target interface. Useful when whitelisting MACs on switches or wireless controllers. LAYER 2
telnet 10.0.0.1 443 Test-NetConnection 10.0.0.1 -Port 443 nc -vz 10.0.0.1 443 nc -vz -w 3 10.0.0.1 443 TCP Port TestChecks if a TCP port is reachable — essential for firewall rule verification. -w 3 sets a 3-second timeout. PowerShell's Test-NetConnection also gives latency. FIREWALL TEST
curl https://example.com curl -I https://example.com Invoke-WebRequest curl https://example.com curl -I https://example.com curl -v https://example.com HTTP Requests / curlFetches URLs, tests APIs, checks HTTP headers. -I for headers only, -v for verbose (shows TLS handshake). Pipe to jq for JSON APIs. Identical on both platforms. API TEST
ssh user@host ssh -p 2222 user@host ssh -i key.pem user@host ssh user@host ssh -p 2222 user@host ssh -i ~/.ssh/key.pem user@host SSH Remote LoginConnects to remote hosts securely. -p for custom port, -i for key-based auth. Create SSH config at ~/.ssh/config for shortcuts to common hosts. REMOTE
net use Z: \\server\share net use * /delete mount_smbfs //user@server/share /mnt/share open smb://server/share Map SMB Network ShareMounts Windows/SMB file shares. On macOS, Finder → Go → Connect to Server (⌘K) is often easier. open smb:// launches Finder connection dialog. FILE SHARE
tasklist Get-Process tasklist /fi "imagename eq nginx.exe" ps aux ps aux | grep nginx top / htop List Running ProcessesShows all running processes with PID and resource usage. Pipe through grep to filter. htop (brew install) is an interactive alternative to top. PROCESS
taskkill /PID 1234 /F taskkill /IM nginx.exe /F kill -9 1234 pkill nginx killall nginx Kill a ProcessTerminates a process by PID or name. kill -9 is SIGKILL (force). kill -15 is SIGTERM (graceful). Use pkill or killall to kill by name. FORCE KILL
systeminfo Get-ComputerInfo system_profiler SPHardwareDataType sysctl -n machdep.cpu.brand_string System Hardware InfoShows CPU model, RAM, serial number, OS version. sysctl hw.memsize returns total RAM in bytes. Useful for asset tracking and support tickets. SYSINFO
whoami whoami /groups whoami id groups Current User / IdentityShows current username. id shows UID, GID, and all group memberships — useful for permission troubleshooting. BASIC
scp file.txt user@host:/path/ pscp (PuTTY) scp file.txt user@host:/path/ rsync -avz ./dir user@host:/path/ Secure File Transferscp copies files over SSH. rsync is better for directory syncs — only transfers changed files. Use -avz for archive mode, verbose, compressed. USE rsync
openssl s_client -connect host:443 openssl s_client -connect host:443 echo | openssl s_client -connect host:443 2>/dev/null | openssl x509 -noout -dates SSL/TLS Certificate CheckInspects TLS certificates. The second command quickly shows cert expiry dates — useful for monitoring cert lifetimes on FortiGate, web servers, and load balancers. CERT CHECK
🍎
macOS-Specific Network Commands
Terminal Pro
CommandDescription & Usage
networksetup -listallhardwareports networksetup -getinfo "Wi-Fi" networksetup -setdnsservers Wi-Fi 1.1.1.1 8.8.8.8 Network Setup UtilitymacOS-native tool for managing network interfaces from CLI. Set DNS, toggle DHCP/static, list hardware ports. Scriptable for automation.
scutil --dns scutil --proxy scutil --get LocalHostName System Configuration UtilityQuery macOS system configuration. --dns shows all DNS resolvers (including VPN split-DNS). --proxy shows active proxy settings. Critical for VPN/FortiClient troubleshooting.
alias airport='/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport' airport -s airport -I Airport Wi-Fi DiagnosticsHidden macOS tool for Wi-Fi diagnostics. -s scans available networks (SSID, RSSI, security), -I shows current connection details including BSSID, channel, and noise. HIDDEN TOOL
sudo launchctl load /Library/LaunchDaemons/com.name.plist sudo launchctl unload ... launchctl list | grep ssh Service Management (launchctl)macOS equivalent of Windows Services / Linux systemctl. Load/unload background daemons. Use to manage VPN agents, monitoring tools, and SSH services.
sudo pfctl -s rules sudo pfctl -sr sudo pfctl -e / -d Packet Filter Firewall (pf)macOS's built-in firewall (BSD pf). Show current rules, enable/disable. Used when building routing/NATing workflows on Mac. Edit rules in /etc/pf.conf. SUDO
lsof -i lsof -i TCP:443 lsof -i -n -P | grep LISTEN List Open Files / SocketsShows all network connections and listening services. -n skips DNS resolution, -P shows port numbers. Combine with grep LISTEN to find what's bound to a port.
df -h du -sh /var/log/* diskutil list Disk / Storage Utilitiesdf -h shows filesystem usage. du -sh shows directory sizes. diskutil list shows all disks and partitions including external. Useful for NAS/storage troubleshooting.
brew install mtr nmap tcpdump netcat brew install ipcalc sipcalc brew install jq yq Homebrew — Essential Network ToolsInstall missing network tools via Homebrew. Key packages: mtr (better traceroute), nmap (port scanner), tcpdump, ipcalc (subnet calc), jq (JSON parsing for API work). MUST INSTALL
sudo tcpdump -i en0 sudo tcpdump -i any host 10.0.0.1 sudo tcpdump -i en0 port 443 -w capture.pcap Packet Capture (tcpdump)Captures live network packets. -w saves to .pcap for analysis in Wireshark. Filter by host, port, or protocol. Replace en0 with your interface. SUDO
sudo -i sudo -u otheruser command sudo visudo Sudo / Privilege Escalationsudo -i opens a root shell. sudo visudo safely edits sudoers file. On macOS, many network commands require sudo — unlike Windows which prompts UAC.
🌐
Advanced Network Diagnostics
Deep Dive
CommandDescription & Usage
nmap -sV 10.0.0.0/24 nmap -p 22,80,443 10.0.0.1 nmap -A -T4 10.0.0.1 nmap --script=ssl-cert 10.0.0.1 Nmap — Network ScannerDiscovers hosts, open ports, services, and OS fingerprints. -sV detects service versions, -A aggressive scan (OS + scripts), -T4 faster timing. Essential for network auditing and firewall rule validation. INSTALL: brew install nmap
dig google.com A dig google.com MX dig -x 8.8.8.8 dig @1.1.1.1 google.com dig +short google.com dig — DNS Query ToolThe go-to DNS troubleshooting tool. -x for reverse lookup, @server to query specific DNS server, +short for clean output. Query specific record types: A, AAAA, MX, TXT, CNAME, NS, SOA. DNS
whois google.com whois 8.8.8.8 WHOIS LookupShows domain registration details and IP block ownership. Useful for BGP troubleshooting — confirms ASN and netblock ownership of IPs you're peering with or receiving routes from.
ip route show ip route add 10.0.0.0/8 via 192.168.1.1 ip route del 10.0.0.0/8 ip route get 8.8.8.8 ip route — Linux/macOS Routingip route get is incredibly useful — shows exactly which route/interface will be used for a destination. Available on macOS via brew install iproute2mac. ROUTING
ss -tulnp ss -s ss -ta state established ss — Socket StatisticsModern replacement for netstat on Linux. -t TCP, -u UDP, -l listening, -n numeric, -p show process. Faster and more detailed than netstat. Available on macOS via Homebrew.
# Server side: iperf3 -s # Client side: iperf3 -c 10.0.0.1 iperf3 -c 10.0.0.1 -t 30 -P 4 iperf3 — Bandwidth TestingMeasures actual TCP/UDP throughput between two hosts. Run as server on one side, client on the other. -P 4 uses 4 parallel streams. Invaluable for testing DirectConnect, FastConnect, or MPLS circuit throughput. INSTALL: brew install iperf3
ipcalc 192.168.1.0/24 ipcalc 10.0.0.0/8 --split 100 ipcalc — Subnet CalculatorCalculates network ranges, broadcast addresses, usable hosts from CIDR notation. --split divides a block into subnets. Use when planning VLAN addressing or checking FortiGate firewall address objects. INSTALL: brew install ipcalc
snmpwalk -v2c -c public 10.0.0.1 snmpget -v2c -c public 10.0.0.1 sysDescr.0 snmpwalk -v3 -l authPriv -u user -a SHA -A pass -x AES -X pass host SNMP ToolsPoll SNMP-enabled devices (switches, FortiGate, routers). snmpwalk traverses MIB tree. snmpget fetches specific OID. Use for quick config verification or when monitoring agents fail. INSTALL: brew install net-snmp
ssh-keygen -t ed25519 -C "email@domain.com" ssh-keygen -t rsa -b 4096 ssh-copy-id user@host cat ~/.ssh/id_ed25519.pub SSH Key ManagementGenerate and deploy SSH key pairs. Prefer ed25519 over RSA (shorter, faster, more secure). ssh-copy-id installs your public key on a remote host. Store public keys in ~/.ssh/authorized_keys. AUTH
# Local port forward: ssh -L 8080:internal-host:80 user@bastion # Remote forward: ssh -R 9090:localhost:3000 user@server # SOCKS proxy: ssh -D 1080 user@server SSH Tunnelling & Port ForwardingCreate encrypted tunnels through SSH. Local forward: access internal services via bastion. SOCKS proxy (-D) routes browser traffic through the server — useful when FortiClient VPN is unavailable. TUNNEL
🔥
FortiGate CLI Commands
FortiOS 7.x
CommandDescription & Usage
get system status
GLOBAL
System Status OverviewShows firmware version, serial number, uptime, HA status, and build info. First command to run when connecting to any FortiGate for the first time or logging a support case. BASIC
diagnose debug flow filter addr 10.0.0.1 diagnose debug flow filter daddr 10.0.0.2 diagnose debug flow show function-name enable diagnose debug flow show iprope enable diagnose debug enable diagnose debug flow trace start 100 # Stop: diagnose debug flow trace stop diagnose debug disable
VDOM
Flow/Packet Debug TraceThe most powerful FortiGate troubleshooting tool. Traces how the firewall processes packets — shows policy lookup, NAT, routing, and session creation/denial. Filter by source/dest IP. Run trace start 100 to capture 100 packets. Always disable when done. PERF IMPACT
get router info routing-table all get router info routing-table database get router info routing-table bgp get router info routing-table connected
VDOM
Routing TableShows the active routing table (FIB). all includes all routes; database shows all learned routes before best-path selection; bgp filters to BGP routes only. Essential for BGP/Megaport troubleshooting. ROUTING
get router info bgp summary get router info bgp neighbors get router info bgp neighbors 169.254.x.x advertised-routes get router info bgp neighbors 169.254.x.x received-routes
VDOM
BGP Neighbor StatusShows BGP peer status, uptime, prefixes sent/received. advertised-routes and received-routes debug prefix exchange with Megaport MCR. Look for state Established and non-zero prefix counts. BGP
# Single host (any direction): diagnose sniffer packet any 'host 10.0.0.1' 4 100 # Host to host (traffic between two specific IPs): diagnose sniffer packet any 'host 10.0.0.1 and host 10.0.0.2' 4 100 # Specific interface, host-to-host: diagnose sniffer packet port1 'host 10.0.0.1 and host 10.0.0.2' 4 0 # Host-to-host on specific port (e.g. HTTPS): diagnose sniffer packet any 'host 10.0.0.1 and host 10.0.0.2 and port 443' 6 0 # One-way: traffic FROM src TO dst: diagnose sniffer packet any 'src 10.0.0.1 and dst 10.0.0.2' 4 100 # All traffic on a port across any interface: diagnose sniffer packet any 'port 443' 4 0 # Verbose with timestamps (l = line buffer): diagnose sniffer packet any 'host 10.0.0.1 and host 10.0.0.2' 6 0 l # Stop sniffer: # Press Ctrl+C
GLOBAL
Built-in Packet SnifferCaptures packets directly on FortiGate — tcpdump-style filters.

Verbosity levels:
1 — header only (src/dst IP, port)
4 — header + hex/ASCII data
6 — includes interface name (best for multi-interface troubleshooting)

Packet count: 0 = unlimited (Ctrl+C to stop). Set a number like 100 to auto-stop.

Host-to-host tip: Use and to combine filters — this is the most useful pattern when tracing traffic between two specific endpoints such as a site-to-site VPN peer, a server and its gateway, or two VLAN interfaces.

Add l at the end for timestamps — essential when correlating with FortiAnalyzer logs or comparing with the other side of a tunnel. PERF IMPACT CTRL+C TO STOP
diagnose sys session list diagnose sys session filter src 10.0.0.1 diagnose sys session filter dst 192.168.1.1 diagnose sys session filter dport 443 diagnose sys session stat diagnose sys session clear
VDOM
Session TableShows active firewall sessions (stateful connection tracking). Filter by source, destination, port, or protocol. stat shows session counts. clear flushes all sessions — use with caution in production. CLEAR = CAUTION
get system ha status diagnose sys ha status diagnose sys ha checksum show execute ha failover set 1 execute ha failover unset
GLOBAL
High Availability StatusCheck HA cluster health, sync status, and which unit is primary. checksum show compares config between HA members (mismatch = sync issue). failover set 1 forces a failover — test or use during maintenance. FAILOVER = OUTAGE
get vpn ipsec tunnel summary get vpn ipsec tunnel details diagnose vpn tunnel list diagnose vpn ike log-filter dst-addr4 10.0.0.1 diagnose debug app ike -1 diagnose debug enable
VDOM
IPsec VPN Tunnel Statustunnel summary shows all tunnels and SA state. ike log-filter + debug IKE is used to troubleshoot phase1/2 failures. Look for established status. Check selectors, PSK, and encryption mismatches when tunnels are down. VPN
get system interface physical get system interface transceiver diagnose netlink interface list diagnose hardware deviceinfo nic port1
GLOBAL
Interface / Hardware StatusShows physical interface states, link speed, duplex, and hardware stats. transceiver shows SFP optical power levels. deviceinfo nic gives detailed driver stats including errors and drops — essential for diagnosing CRC errors or flapping links.
get system performance status diagnose sys top diagnose sys top-mem diagnose hardware sysinfo memory diagnose sys process pidof ipsengine
GLOBAL
CPU & Memory Performanceperformance status gives a quick summary. sys top is like Linux top — shows per-process CPU. High ipsengine or scanunitd CPU often indicates deep inspection load. Check during slowness events. PERFORMANCE
diagnose firewall iprope lookup 10.0.0.1 10.0.0.2 6 1024 443
VDOM
Policy Lookup (iprope)Simulates a packet and shows which firewall policy would match. Arguments: src-ip dst-ip proto src-port dst-port. Proto 6=TCP, 17=UDP, 1=ICMP. Useful to verify policy ordering without generating real traffic. POLICY TEST
diagnose sys virtual-wan-link health-check diagnose sys virtual-wan-link member diagnose sys virtual-wan-link service get router info virtual-wan-link
VDOM
SD-WAN Status & DiagnosticsShows SD-WAN member status, health-check results (latency, jitter, packet loss), and which service rule is being used. member shows bandwidth and preference. Essential when traffic isn't taking expected SD-WAN path. SD-WAN
execute backup config ftp backup.conf 10.0.0.1 execute backup config tftp backup.conf 10.0.0.1 execute restore config ftp backup.conf 10.0.0.1
GLOBAL
Backup & Restore ConfigBack up config to FTP/TFTP server. Always backup before changes. Restore from saved config for rollback. Also available via GUI (System → Config → Backup). Keep versioned backups before major changes. BACKUP FIRST
# Enter VDOM context: config vdom edit root end # Switch to global: config global # Check current context: get vdom-list
GLOBAL/VDOM
VDOM Context SwitchingWhen VDOMs are enabled, most commands run in current VDOM context. Use config vdom → edit root to enter a specific VDOM. config global for global settings. Important — running commands in wrong VDOM gives empty results. VDOM
execute log filter category traffic execute log filter device memory execute log display execute log filter reset diagnose log test
VDOM
Local Log ViewerView logs stored in FortiGate memory. Filter by category (traffic, event, system). diagnose log test sends a test log entry. Most production environments send logs to FortiAnalyzer — use these when FAZ is unavailable. LOGS
diagnose test application dnsproxy 1 execute nslookup name google.com execute nslookup name google.com 8.8.8.8
VDOM
DNS Diagnosticsnslookup name performs a DNS lookup from the FortiGate itself — useful to verify DNS resolution from the firewall's perspective vs. a client. dnsproxy 1 shows DNS proxy stats and cache. DNS
🚨
Troubleshooting Step-by-Step Guides
Click to expand
🔐 IPsec VPN Tunnel Down
1
Check tunnel summary — is Phase1 up?
get vpn ipsec tunnel summary

Look for established in the IKE column. If missing, Phase1 is failing. Note the peer IP.

2
Check detailed tunnel state
get vpn ipsec tunnel details diagnose vpn tunnel list name <tunnel-name>

Confirms SA state, bytes in/out, selectors. Zero traffic on an "up" tunnel = selector mismatch.

3
Enable IKE debug to see Phase1/2 negotiation
diagnose vpn ike log-filter dst-addr4 <peer-ip> diagnose debug app ike -1 diagnose debug enable

Watch for: no proposal chosen (encryption mismatch), invalid id (PSK wrong), no policy found (selector mismatch). Always run diagnose debug disable when done.

4
Confirm routing to peer exists
get router info routing-table all | grep <peer-ip>

If no route to the peer IP, FortiGate can't send IKE packets — check your WAN/SD-WAN interface routing.

5
Sniff IKE packets to/from peer
diagnose sniffer packet any 'host <peer-ip> and port 500' 6 0 l diagnose sniffer packet any 'host <peer-ip> and port 4500' 6 0 l

Port 500 = IKE. Port 4500 = NAT-T. If you see outbound packets but no reply, the peer is not responding or there's a firewall blocking UDP 500/4500.

6
Bounce the tunnel manually
diagnose vpn tunnel flush <tunnel-name>

Forces renegotiation. If tunnel comes up then drops again, check DPD settings and peer keepalives.

7
Check firewall policy allows tunnel traffic
diagnose firewall iprope lookup <src-ip> <dst-ip> 6 1024 443

Even with tunnel up, traffic may be dropped if no policy exists for the interesting traffic subnets.

8
Common root causes checklist

Mismatched encryption/hash — IKEv2 both sides must match exactly
Wrong PSK — IKEv1 or PSK-based IKEv2
Selector mismatch — subnet ranges don't match peer's Phase2
NAT issue — enable NAT-T if either side is behind NAT
Dead peer detection — DPD timeout causing flapping
Routing asymmetry — HA or SD-WAN sending reply out wrong interface

📡 BGP Not Establishing / Missing Routes
1
Check BGP summary — peer state
get router info bgp summary

State should be Established. Other states: Active = trying to connect (TCP), Idle = not attempting, Connect = TCP SYN sent.

2
Check TCP reachability to BGP peer
diagnose sniffer packet any 'host <peer-ip> and port 179' 6 0 l

BGP runs over TCP 179. If no packets seen, a firewall rule or routing issue is blocking the TCP session before BGP even starts.

3
Check BGP neighbor detail
get router info bgp neighbors <peer-ip>

Look at: BGP state, Hold time, Connect Retry, Notification messages (these tell you why it dropped). Also confirms remote ASN.

4
Verify route exchange
get router info bgp neighbors <peer-ip> advertised-routes get router info bgp neighbors <peer-ip> received-routes

If established but no routes received: check peer's redistribute or network statements. If sending but peer not accepting: check peer's route-map/prefix-list filters.

5
Check routing table for BGP routes
get router info routing-table bgp get router info routing-table database

database shows all learned routes even if not installed. If a route is in database but not routing-table, a higher-priority route (static/connected) is winning.

6
Enable BGP debug
diagnose ip router bgp all enable diagnose ip router bgp level info diagnose debug enable # Wait for events, then: diagnose ip router bgp all disable diagnose debug disable

Shows OPEN, UPDATE, NOTIFICATION messages in real time. Look for NOTIFICATION codes — these are the BGP error codes explaining why the session dropped.

7
Common BGP issues checklist

Wrong remote ASN — must match exactly what peer is configured with
Wrong update-source interface — esp. for loopback-peered BGP
Route-map filtering everything — check inbound/outbound route-maps
Missing redistribute — static/connected routes not redistributed into BGP
eBGP multihop not set — if peer is not directly connected
Firewall blocking TCP 179 — check interface policies and SD-WAN rules

💻 FortiClient / SSL-VPN Issues
1
Check SSL-VPN daemon status on FortiGate
get vpn ssl monitor diagnose vpn ssl list

Shows active SSL-VPN sessions. If user's session appears but they're having issues, proceed to routing/policy checks.

2
Check SSL-VPN port is reachable from client
# On client Mac/Windows: nc -vz <fortigate-wan-ip> 443 curl -k https://<fortigate-wan-ip>:443

If port 443 is blocked, SSL-VPN can't establish. Check ISP, local firewall on client, and FortiGate WAN policy.

3
Enable SSL-VPN debug on FortiGate
diagnose debug app sslvpn -1 diagnose debug enable # Reproduce the issue, then: diagnose debug disable

Shows authentication attempts, certificate errors, and tunnel setup. Look for authentication failed, group not found, or split tunnel policy mismatches.

4
Check assigned IP and split-tunnel routes
diagnose vpn ssl list get vpn ssl settings

Confirms IP pool assignment and portal config. If user connects but can't reach internal resources, check split-tunnel routing config and firewall policies for SSL-VPN zone.

5
Check DNS resolution from VPN client
# On client: scutil --dns # macOS — check VPN resolver nslookup internal.domain.local

Split-DNS is a common issue — VPN may connect but internal DNS isn't pushed correctly. Verify DNS servers in SSL-VPN portal config.

6
FortiClient on macOS — common fixes
# Reset FortiClient network extension: sudo /Applications/FortiClient.app/Contents/MacOS/FortiClient vpn stop sudo /Applications/FortiClient.app/Contents/MacOS/FortiClient vpn start # Check VPN interface was created: ifconfig | grep utun

macOS requires a System Extension approval in Security & Privacy. If utun interface not created, extension is likely blocked — check System Preferences → Security.

7
Common FortiClient issues checklist

Certificate CN mismatch — FQDN in FortiClient must match cert's CN/SAN
MFA timeout — user didn't approve push in time
Wrong portal/group mapping — user in wrong group, wrong IP pool assigned
macOS System Extension blocked — needs manual approval
Split tunnel not routing LAN — missing route in portal config
FortiEMS compliance block — check endpoint compliance rules

🌐 SD-WAN Traffic Not Using Expected Path
1
Check SD-WAN member status and health
diagnose sys virtual-wan-link health-check diagnose sys virtual-wan-link member

Each member shows: status (alive/dead), latency, jitter, packet loss. A "dead" member won't be used regardless of rules. Check if health-check SLA thresholds are set too strictly.

2
Check which service rule traffic is matching
diagnose sys virtual-wan-link service

Shows SD-WAN service rules and which interface each is currently using. If a rule is matching but using wrong member, check SLA conditions and priority settings.

3
Trace the actual path for a specific flow
diagnose debug flow filter addr <src-ip> diagnose debug flow filter daddr <dst-ip> diagnose debug flow show function-name enable diagnose debug enable diagnose debug flow trace start 20

Flow trace shows which SD-WAN rule matched and which interface was chosen. Look for sdwan_select_route in the output.

4
Check SD-WAN performance SLA targets
get router info virtual-wan-link

Shows configured SLA targets. If latency thresholds are too tight, the preferred link may be excluded. Tune thresholds or change rule strategy (best-quality vs lowest-cost vs manual).

5
Sniff traffic on both SD-WAN members
# Check which interface traffic is actually leaving on: diagnose sniffer packet <wan1> 'host <dst-ip>' 4 20 diagnose sniffer packet <wan2> 'host <dst-ip>' 4 20

Run both in sequence or open two SSH sessions. Confirms at packet level which egress path is being used vs. what you expect.

6
Common SD-WAN issues checklist

Health check probe failing — wrong probe IP, ICMP blocked, wrong interval
SLA thresholds too aggressive — link marked dead due to minor jitter
Rule order wrong — implicit rule catching traffic before SD-WAN rule
Existing session not rerouted — SD-WAN only applies to new sessions; flush sessions to force reroute
Static route overriding SD-WAN — check for conflicting static routes with lower distance
BGP routes not re-advertised after failover — check BGP community/AS-path on backup link

FortiGate HA / Cluster Issues
1
Check HA status and sync state
get system ha status diagnose sys ha status

Shows which unit is primary, HA mode (A/P or A/A), sync status, heartbeat interface state, and override settings. Both units must show in sync.

2
Check config checksums match
diagnose sys ha checksum show diagnose sys ha checksum recalculate

Checksum mismatch between primary and secondary = config out of sync. recalculate forces a sync check. If persistent, look for locally-modified settings on secondary.

3
Check heartbeat interfaces
diagnose sys ha dump-by vcluster get system interface | grep -A5 heartbeat

HA heartbeat must be up on both units. Heartbeat loss triggers failover. Check the dedicated HA link or heartbeat VLAN for physical issues.

4
Force failover (planned maintenance)
execute ha failover set 1 # After maintenance, revert: execute ha failover unset

⚠ This causes a brief traffic interruption. Alert the team before running. Sessions are re-established on the new primary.

5
Connect directly to secondary unit CLI
execute ha manage <secondary-id> admin

Allows you to run CLI commands on the secondary from the primary's session. Use secondary ID from get system ha status output. Needed to check secondary-specific interface states.

6
Common HA issues checklist

Heartbeat link down — split-brain risk; both units may become primary
Config out of sync — changes made directly on secondary
Different firmware versions — units must run identical FortiOS version
HA override enabled — unit with higher priority may not be primary if override off
Session-sync failing — long-lived sessions may drop on failover
SNMP/syslog from wrong unit — check management IP binding after failover

🗺️ No Connectivity / Routing Issue
1
Ping from FortiGate itself vs. through it
# FortiGate pinging destination: execute ping <dst-ip> execute ping-options source <src-interface-ip> execute ping <dst-ip>

If FortiGate can ping but clients can't, the issue is policy or NAT. If FortiGate itself can't ping, it's routing or WAN. Always set source interface for accurate results.

2
Confirm route exists for destination
get router info routing-table all get router info routing-table details <dst-ip>

If no route, traffic is blackholed or hitting a default route unexpectedly. Check BGP, OSPF, or static routes for the subnet.

3
Run flow debug to trace policy hit
diagnose debug flow filter addr <src-ip> diagnose debug flow filter daddr <dst-ip> diagnose debug flow show function-name enable diagnose debug enable diagnose debug flow trace start 20

This is the definitive tool. Look for: Denied by forward policy check, no matching policy, reverse path check fail, or iprope_in_check() check failed.

4
Sniff both ingress and egress interfaces
# See if traffic arrives on ingress: diagnose sniffer packet <lan-int> 'host <src-ip> and host <dst-ip>' 4 20 # See if traffic leaves on egress: diagnose sniffer packet <wan-int> 'host <src-ip> and host <dst-ip>' 4 20

Traffic arriving on LAN but not leaving WAN = policy drop or routing issue. Traffic leaving WAN but no reply = upstream or NAT issue.

5
Check session table for existing state
diagnose sys session filter src <src-ip> diagnose sys session filter dst <dst-ip> diagnose sys session list

If a stale session exists with wrong routing, it may persist even after a route change. Clear with diagnose sys session clear — this affects all sessions. Use carefully in production.

6
Check NAT is translating correctly
diagnose firewall iprope lookup <src> <dst> 6 1024 443 get firewall policy | grep -A20 "policy-id <id>"

If traffic is NATted to wrong source IP, check VIP and SNAT settings. Confirm nat enable is set on the correct policy and NAT pool is right.

☁️ Megaport / Direct Connect / FastConnect BGP
1
Check BGP sessions to MCR peers
get router info bgp summary

Look for your two Megaport MCR peer IPs (typically 169.254.x.x link-locals or assigned /30s). Both should show Established for redundancy.

2
Check VLAN subinterfaces for MCR are up
get system interface | grep -A10 <vlan-interface> diagnose netlink interface list | grep <vlan-int>

If the VLAN interface is down, BGP can't form. Check VLAN tagging matches what Megaport provisioned. Verify the parent physical interface is up and linked.

3
Confirm prefixes being advertised to MCR
get router info bgp neighbors <mcr-peer-ip> advertised-routes

Your on-prem prefixes (e.g., your /29 public block) should appear here. If missing, check BGP network statements or redistribution config in FortiGate BGP settings.

4
Confirm cloud prefixes received from MCR
get router info bgp neighbors <mcr-peer-ip> received-routes get router info routing-table bgp

OCI VCN CIDRs and AWS VPC CIDRs should appear as BGP routes. If not received, check Megaport MCR VXC config and cloud side BGP settings (AWS DXGW, OCI FastConnect).

5
Test reachability to cloud instances
execute ping-options source <fortigate-lan-ip> execute ping <aws-instance-private-ip> execute ping <oci-instance-private-ip> # Trace path: execute traceroute <cloud-ip>

Ping from FortiGate first. If reachable from FortiGate but not from LAN clients, the issue is internal routing or firewall policy — not the cloud circuit.

6
Sniff traffic on MCR-facing VLAN interface
diagnose sniffer packet <vlan-int> 'host <cloud-ip>' 6 0 l

Confirms traffic is actually leaving on the correct interface toward Megaport. If packets seen outbound but no replies, check MCR routing or cloud-side security groups/NACLs.

7
Common Megaport/cloud BGP issues

AWS DXGW not associated to TGW — check Direct Connect Gateway → TGW association in AWS console
OCI route table missing DRG — ensure DRG attachment and route rules in OCI VCN
BGP AS mismatch — FortiGate AS (65000) must match what MCR expects
Missing allowed prefixes on cloud side — AWS VIF and OCI FastConnect have prefix allow-lists
MTU mismatch over FastConnect/DX — use 1500 or match cloud provider's MTU
Second MCR node not peered — check both MCR nodes have BGP sessions for redundancy

Kubernetes (kubectl)
K8s Commands
CommandDescription & Usage
kubectl get pods kubectl get pods -A kubectl get pods -n kube-system kubectl get pods -o wide kubectl get all -n my-namespace Get ResourcesLists Kubernetes objects. -A all namespaces, -n specific namespace, -o wide shows node assignment and IP. Use get all to see pods, services, deployments, and replicasets together. BASIC
kubectl describe pod my-pod -n default kubectl describe node worker-01 kubectl describe svc my-service kubectl describe deployment my-app Describe ResourcesShows detailed information about a K8s object including events, conditions, resource limits, and labels. The Events section at the bottom is most useful for troubleshooting — shows why a pod is pending, crashlooping, or failing. CHECK EVENTS
kubectl logs my-pod kubectl logs my-pod -f kubectl logs my-pod --tail=100 kubectl logs my-pod -c my-container kubectl logs my-pod --previous View Pod Logs-f follows live output (like tail -f), --tail limits output, -c selects container in multi-container pods, --previous shows logs from the previous (crashed) container. Critical for app debugging. LOGS
kubectl exec -it my-pod -- /bin/bash kubectl exec -it my-pod -c my-container -- sh kubectl exec my-pod -- env kubectl exec my-pod -- curl http://other-svc:8080 Exec Into a PodOpens an interactive shell inside a running pod — essential for debugging. -it for interactive TTY. Run any command directly. If no bash, try sh or /bin/ash. Useful to test DNS resolution, network connectivity, and env vars from within the cluster. SHELL IN
kubectl apply -f manifest.yaml kubectl apply -f ./manifests/ kubectl apply -k ./kustomize/ kubectl create deployment nginx --image=nginx kubectl create namespace my-ns Apply / Create Resourcesapply is declarative (creates or updates). create is imperative (fails if exists). Apply a directory to deploy all manifests at once. -k for Kustomize directories. Preferred: always use apply with YAML files for GitOps workflows. DEPLOY
kubectl delete pod my-pod kubectl delete -f manifest.yaml kubectl delete pod my-pod --force --grace-period=0 kubectl delete all --all -n my-namespace Delete ResourcesRemove K8s objects. --force --grace-period=0 immediately terminates a stuck/terminating pod. delete all --all removes all resources in a namespace (not namespaced resources like PVCs). DESTRUCTIVE
kubectl scale deployment my-app --replicas=3 kubectl scale deployment my-app --replicas=0 kubectl rollout restart deployment my-app Scale & Restart DeploymentsScale replicas up/down. Scale to 0 effectively pauses a deployment. rollout restart performs a rolling restart without downtime — useful to pick up updated ConfigMaps or Secrets, or to clear pod state. SCALE
kubectl rollout status deployment my-app kubectl rollout history deployment my-app kubectl rollout undo deployment my-app kubectl rollout undo deployment my-app --to-revision=2 Rollout ManagementMonitor and control deployment rollouts. status waits and reports progress. history shows revision history. undo instantly rolls back to the previous version. Specify --to-revision for a specific version. ROLLBACK
kubectl config get-contexts kubectl config use-context my-cluster kubectl config set-context --current --namespace=my-ns kubectl cluster-info kubectx # (requires kubectx plugin) Context / Cluster SwitchingManage multiple clusters via kubeconfig contexts. use-context switches cluster. set-context --current changes the default namespace. Install kubectx + kubens (brew install kubectx) for fast switching. INSTALL kubectx
kubectl port-forward pod/my-pod 8080:80 kubectl port-forward svc/my-service 8080:80 kubectl port-forward deployment/my-app 8080:80 -n my-ns Port ForwardingForwards a local port to a pod/service — access internal K8s services without exposing them externally. Access at localhost:8080. Essential for debugging apps behind ClusterIP services or during development. LOCAL ACCESS
kubectl top pods kubectl top pods -A kubectl top nodes kubectl top pods --sort-by=memory Resource Usage (top)Shows real-time CPU and memory usage for pods and nodes. Requires metrics-server installed in cluster. --sort-by ranks by resource consumption. Use to identify resource hogs and right-size requests/limits. METRICS
kubectl get events -n my-namespace kubectl get events --sort-by='.lastTimestamp' kubectl get events --field-selector reason=Failed Cluster EventsShows Kubernetes events — warnings, scheduling failures, image pull errors. Sort by timestamp to see the most recent events. Filter by reason to find specific failure types. Often faster than describe for cluster-wide issues. TROUBLESHOOT
kubectl get secrets kubectl get secret my-secret -o jsonpath='{.data.password}' | base64 -d kubectl get configmap my-cm -o yaml kubectl create secret generic my-secret --from-literal=key=value Secrets & ConfigMapsRetrieve and decode K8s secrets (base64 encoded, not encrypted). Use jsonpath to extract specific keys. ConfigMaps store non-sensitive config. Use kubectl create secret to create from literals or files. SENSITIVE
kubectl cordon node-01 kubectl drain node-01 --ignore-daemonsets --delete-emptydir-data kubectl uncordon node-01 kubectl taint nodes node-01 key=value:NoSchedule Node Maintenancecordon prevents new pods from scheduling on a node. drain evicts existing pods (for maintenance/upgrades). uncordon makes node schedulable again. taint reserves nodes for specific workloads. MAINTENANCE
kubectl get svc -A kubectl get ingress -A kubectl get networkpolicy -A kubectl describe svc my-service kubectl get endpoints my-service Networking & ServicesInspect services, ingress rules, and network policies. get endpoints shows actual pod IPs backing a service — if empty, selector may not match pods. Critical for diagnosing "service not reaching pods" issues. NETWORKING
🦊
GitLab & Git Commands
GitOps & CI/CD
CommandDescription & Usage
git clone git@gitlab.com:org/repo.git git clone --depth=1 git@gitlab.com:org/repo.git git clone -b main git@gitlab.com:org/repo.git Clone a RepositoryDownloads a repo. --depth=1 is a shallow clone (no history) — faster for CI/CD pipelines. -b clones a specific branch. Use SSH URL for key-based auth (no password prompts in CI). BASIC
git status git add . git add -p # interactive staging git commit -m "message" git commit --amend # edit last commit Stage & Commit ChangesCore workflow. git add -p lets you review and selectively stage chunks — good practice for clean commits. --amend rewrites the last commit (don't use on pushed commits). BASIC
git push origin main git push -u origin feature/my-branch git pull origin main git fetch --all git pull --rebase origin main Push & Pull-u sets upstream tracking. fetch downloads without merging. pull --rebase keeps a linear history by replaying your commits on top of remote — preferred in GitLab workflows. REMOTE
git branch # list local git branch -a # list all including remote git checkout -b feature/new # create + switch git switch main # modern way to switch git branch -d old-branch git push origin --delete old-branch Branch ManagementCreate, switch, list, and delete branches. git switch is the modern alternative to checkout for switching branches. Delete both local (-d) and remote (push --delete) when done with a feature branch. BRANCHING
git merge feature/my-branch git merge --no-ff feature/my-branch # always create merge commit git rebase main git rebase -i HEAD~3 # interactive rebase last 3 commits git cherry-pick abc1234 Merge & Rebase--no-ff preserves merge history. rebase -i lets you squash, reorder, or edit commits before merging — useful to clean up before raising a MR. cherry-pick applies a specific commit to another branch. CLEAN HISTORY
git log --oneline git log --oneline --graph --all git log --author="Wasim" --since="2 weeks ago" git log -p filename.txt # changes to a file git show abc1234 Log & History--graph --all shows a visual branch history. Filter by author or date. -p shows the actual diff for each commit on a file. git show displays a specific commit's changes. HISTORY
git diff # unstaged changes git diff --staged # staged vs last commit git diff main feature/new # branch comparison git diff HEAD~1 HEAD # last commit changes Diff — Compare ChangesView differences between working tree, staged, and committed code. Compare branches before merging. HEAD~1 refers to one commit before HEAD. Use in CI to detect what changed between pipeline runs. DIFF
git stash git stash save "work in progress" git stash list git stash pop git stash apply stash@{0} git stash drop stash@{0} StashTemporarily saves uncommitted changes so you can switch branches cleanly. pop applies the most recent stash and removes it. apply applies without removing. Stash often before pulling to avoid merge conflicts. CONTEXT SWITCH
git reset --soft HEAD~1 # undo commit, keep staged git reset --hard HEAD~1 # undo commit + discard changes git revert abc1234 # safe undo (creates new commit) git restore filename.txt # discard working changes Undo / Resetrevert is safe for shared branches — creates a new commit that undoes changes. reset --hard is destructive — only use on local unpushed commits. restore discards file changes. HARD = DESTRUCTIVE
git remote -v git remote add origin git@gitlab.com:org/repo.git git remote set-url origin git@gitlab.com:org/new-repo.git git remote remove origin Manage RemotesView and configure remote URLs. Use set-url to switch from HTTPS to SSH (avoids password prompts) or after a repo rename/migration. Check this first if push/pull fails with "remote not found". REMOTE
# .gitlab-ci.yml basics: stages: - build - test - deploy build-job: stage: build script: - echo "Building..." - docker build -t myapp . deploy-job: stage: deploy script: - kubectl apply -f k8s/ GitLab CI/CD PipelineGitLab CI is defined in .gitlab-ci.yml at repo root. Stages run sequentially; jobs in the same stage run in parallel. Each job runs on a GitLab Runner. Use rules: or only:/except: to control when jobs trigger. CI/CD
gitlab-runner register gitlab-runner start gitlab-runner stop gitlab-runner list gitlab-runner verify GitLab Runner ManagementRegister a new runner with your GitLab instance (provide URL + registration token from GitLab UI). Executors: docker (recommended), shell, kubernetes. verify checks runner connectivity. Run on your own infrastructure for private deployments. RUNNER
# List projects: curl -H "PRIVATE-TOKEN: your_token" \ https://gitlab.com/api/v4/projects # Trigger pipeline: curl -X POST \ -H "PRIVATE-TOKEN: your_token" \ -F "ref=main" \ https://gitlab.com/api/v4/projects/123/trigger/pipeline GitLab REST APIAutomate GitLab with the REST API. Generate a Personal Access Token in GitLab UI → User Settings → Access Tokens. Use for pipeline triggers, creating issues, querying project info, or building automation around deployments. AUTOMATION
git tag # list tags git tag v1.2.0 # lightweight tag git tag -a v1.2.0 -m "Release" # annotated tag git push origin v1.2.0 git push origin --tags # push all tags Tagging ReleasesTags mark specific commits as releases. Annotated tags (-a) include metadata — preferred for releases. GitLab uses tags to trigger release pipelines. Tag must be pushed separately from commits. RELEASE
git submodule add git@gitlab.com:org/lib.git lib/ git submodule update --init --recursive git submodule foreach git pull origin main Git SubmodulesEmbed another Git repo inside your repo. Common for shared libraries or config repos. --recursive initialises nested submodules. Run update --init after cloning a repo that has submodules. ADVANCED
🪟
Windows CMD & PowerShell (Network)
Windows Bonus
CommandDescription & Usage
Test-NetConnection google.com -Port 443 Test-NetConnection 10.0.0.1 -Port 22 -InformationLevel Detailed (Test-NetConnection 10.0.0.1).PingSucceeded Test-NetConnection (PowerShell)Far superior to telnet for port testing. Shows TCP success, latency, route, and interface used. -InformationLevel Detailed shows the route taken. Returns objects — scriptable in PowerShell automations. BETTER THAN TELNET
Get-NetAdapter Get-NetIPAddress Get-NetRoute Get-DnsClientServerAddress Resolve-DnsName google.com PowerShell Network CmdletsModern PowerShell replacements for cmd tools. Get-NetIPAddress replaces ipconfig, Get-NetRoute replaces route print. Resolve-DnsName is a rich DNS tool. All return objects for scripting. POWERSHELL
netsh interface ip reset netsh winsock reset netsh wlan show profiles netsh wlan show profile name="SSID" key=clear netsh advfirewall show allprofiles netsh — Network ShellPowerful Windows network config tool. wlan show profile key=clear reveals saved Wi-Fi passwords. ip reset resets TCP/IP stack (needs reboot). advfirewall manages Windows Firewall rules. ADMIN
Get-EventLog -LogName System -Newest 50 Get-WinEvent -LogName System -MaxEvents 20 Get-WinEvent -FilterHashtable @{LogName='System'; Level=2} Windows Event LogQueries the Event Log from PowerShell. Level 2 = Error, Level 3 = Warning. Filter by time, source, or event ID. Useful for diagnosing NIC driver errors, DHCP failures, or DNS client issues. LOGS
robocopy C:\Source \\server\dest /MIR /Z /LOG:log.txt robocopy /MIR /COPYALL /R:3 /W:5 Robocopy — Robust File CopyFar superior to xcopy for network file operations. /MIR mirrors directories, /Z restartable mode (survives network drops), /R:3 retries 3 times. Essential for large file transfers over SMB/WAN links. USE ROBOCOPY
⚙️
PowerShell — Entra ID & Microsoft 365
Sysadmin Scripts
Script / CommandDescription & Usage
# Install required modules (run as Admin once): Install-Module Microsoft.Graph -Scope CurrentUser -Force Install-Module ExchangeOnlineManagement -Scope CurrentUser -Force Install-Module MicrosoftTeams -Scope CurrentUser -Force # Connect to Microsoft Graph (Entra ID): Connect-MgGraph -Scopes "User.ReadWrite.All","Group.ReadWrite.All","Directory.ReadWrite.All" # Connect to Exchange Online: Connect-ExchangeOnline -UserPrincipalName admin@domain.com # Disconnect when done: Disconnect-MgGraph Disconnect-ExchangeOnline -Confirm:$false Module Setup & ConnectionInstall Microsoft Graph SDK (replaces deprecated AzureAD module). Use Connect-MgGraph for all Entra ID / user / group operations. Scopes must match the operations you intend to run — add scopes as needed. ADMIN RUN ONCE
# ── Bulk Create Security Groups from CSV ── # CSV format: DisplayName,Description,MailNickname # Example CSV row: IT-Network-Team,Network Engineers,IT-Network-Team $groups = Import-Csv "C:\groups.csv" foreach ($group in $groups) { $params = @{ DisplayName = $group.DisplayName Description = $group.Description MailNickname = $group.MailNickname SecurityEnabled = $true MailEnabled = $false GroupTypes = @() } $newGroup = New-MgGroup -BodyParameter $params Write-Host "Created: $($newGroup.DisplayName) — $($newGroup.Id)" -ForegroundColor Green } Write-Host "Done. $($groups.Count) groups created." -ForegroundColor Cyan Bulk Create Security Groups (CSV)Creates pure security groups in Entra ID from a CSV file. SecurityEnabled=$true + MailEnabled=$false = security group (not M365 group). GroupTypes=@() ensures it's not a dynamic group. Output logs each created group's Object ID for audit trail. GRAPH SCOPE: Group.ReadWrite.All
# Create a single security group: $params = @{ DisplayName = "VPN-Users" Description = "Users permitted FortiClient VPN access" MailNickname = "VPN-Users" SecurityEnabled = $true MailEnabled = $false GroupTypes = @() } $group = New-MgGroup -BodyParameter $params Write-Host "Created group ID: $($group.Id)" # Add an owner to the group: $ownerRef = @{ "@odata.id" = "https://graph.microsoft.com/v1.0/users/<user-object-id>" } New-MgGroupOwnerByRef -GroupId $group.Id -BodyParameter $ownerRef Create a Single Security GroupQuick one-off group creation. Common use: creating groups for Conditional Access policies, FortiGate RADIUS auth, or app assignments. Capture the returned Id — you'll need it to add members or assign apps. ENTRA
# Add a single user to a group: $groupId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" $userId = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy" New-MgGroupMember -GroupId $groupId -DirectoryObjectId $userId # ── Bulk add from CSV (UPN column) ── $groupId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" $members = Import-Csv "C:\members.csv" # column: UPN foreach ($m in $members) { $user = Get-MgUser -Filter "userPrincipalName eq '$($m.UPN)'" if ($user) { New-MgGroupMember -GroupId $groupId -DirectoryObjectId $user.Id Write-Host "Added: $($m.UPN)" -ForegroundColor Green } else { Write-Host "NOT FOUND: $($m.UPN)" -ForegroundColor Red } } Add Members to a Group (Bulk)Adds users by UPN from CSV. The -Filter lookup handles UPN→ObjectId resolution automatically. Red output flags users not found in Entra — useful for spotting typos before an audit. ENTRA
# ── Full User Offboarding Script ── param([string]$UPN = "leaver@domain.com") Connect-MgGraph -Scopes "User.ReadWrite.All","Group.ReadWrite.All","Directory.ReadWrite.All" Connect-ExchangeOnline -UserPrincipalName admin@domain.com $user = Get-MgUser -Filter "userPrincipalName eq '$UPN'" -Property Id,DisplayName,AssignedLicenses if (-not $user) { Write-Error "User not found: $UPN"; exit } Write-Host "=== Offboarding: $($user.DisplayName) ===" -ForegroundColor Yellow # 1. Block sign-in immediately Update-MgUser -UserId $user.Id -AccountEnabled $false Write-Host "[1] Sign-in blocked" -ForegroundColor Green # 2. Revoke all active sessions / tokens Revoke-MgUserSignInSession -UserId $user.Id Write-Host "[2] All sessions revoked" -ForegroundColor Green # 3. Reset password to random (prevent re-entry) $newPw = [System.Web.Security.Membership]::GeneratePassword(20,4) $pwProfile = @{ Password = $newPw; ForceChangePasswordNextSignIn = $false } Update-MgUser -UserId $user.Id -PasswordProfile $pwProfile Write-Host "[3] Password reset" -ForegroundColor Green # 4. Remove from all groups (except dynamic) $groups = Get-MgUserMemberOf -UserId $user.Id | Where-Object { $_.AdditionalProperties["@odata.type"] -eq "#microsoft.graph.group" } foreach ($g in $groups) { try { Remove-MgGroupMemberByRef -GroupId $g.Id -DirectoryObjectId $user.Id Write-Host " Removed from: $($g.AdditionalProperties['displayName'])" -ForegroundColor DarkGray } catch { Write-Host " Skipped (dynamic/role): $($g.AdditionalProperties['displayName'])" -ForegroundColor DarkYellow } } Write-Host "[4] Group memberships removed" -ForegroundColor Green # 5. Remove all licence assignments if ($user.AssignedLicenses) { $removeLicenses = $user.AssignedLicenses | Select-Object -ExpandProperty SkuId Set-MgUserLicense -UserId $user.Id -AddLicenses @() -RemoveLicenses $removeLicenses Write-Host "[5] Licences removed" -ForegroundColor Green } # 6. Convert mailbox to shared (preserves email, no licence needed) Set-Mailbox -Identity $UPN -Type Shared Write-Host "[6] Mailbox converted to Shared" -ForegroundColor Green # 7. Set Out of Office auto-reply Set-MailboxAutoReplyConfiguration -Identity $UPN ` -AutoReplyState Enabled ` -InternalMessage "$($user.DisplayName) has left the organisation. Please contact helpdesk@domain.com." ` -ExternalMessage "$($user.DisplayName) is no longer with the organisation. Please contact info@domain.com." Write-Host "[7] Out-of-office set" -ForegroundColor Green # 8. Hide from Global Address List Set-Mailbox -Identity $UPN -HiddenFromAddressListsEnabled $true Write-Host "[8] Hidden from GAL" -ForegroundColor Green # 9. Forward mail to manager (optional — uncomment and set manager UPN) # Set-Mailbox -Identity $UPN -ForwardingSmtpAddress "manager@domain.com" -DeliverToMailboxAndForward $false Write-Host "`n=== Offboarding complete for $UPN ===" -ForegroundColor Cyan Write-Host "MANUAL TODO: Reclaim hardware, revoke physical access, notify HR" -ForegroundColor Magenta Disconnect-MgGraph Disconnect-ExchangeOnline -Confirm:$false Full User Offboarding ScriptEnd-to-end leaver process in one script. Runs 8 steps:

1. Block sign-in (immediate)
2. Revoke all OAuth tokens & sessions
3. Reset password
4. Remove all group memberships
5. Remove M365 licences
6. Convert mailbox to Shared (no licence cost)
7. Set out-of-office
8. Hide from GAL

Run as: .\Offboard-User.ps1 -UPN "name@domain.com" IMMEDIATE EFFECT ADMIN REQUIRED
# ── New User Onboarding ── param( [string]$FirstName = "Jane", [string]$LastName = "Smith", [string]$Department = "IT", [string]$Manager = "manager@domain.com", [string]$LicenseSku = "reseller-account:ENTERPRISEPREMIUM" # M365 E3/E5 SKU ) $UPN = "$($FirstName.ToLower()).$($LastName.ToLower())@domain.com" $TempPw = "Welcome@$(Get-Random -Minimum 1000 -Maximum 9999)!" $FullName = "$FirstName $LastName" # Create user $params = @{ DisplayName = $FullName GivenName = $FirstName Surname = $LastName UserPrincipalName = $UPN MailNickname = "$($FirstName.ToLower()).$($LastName.ToLower())" Department = $Department AccountEnabled = $true PasswordProfile = @{ Password = $TempPw ForceChangePasswordNextSignIn = $true } } $newUser = New-MgUser -BodyParameter $params Write-Host "Created user: $UPN (ID: $($newUser.Id))" -ForegroundColor Green # Assign licence $licenseParams = @{ AddLicenses = @(@{ SkuId = (Get-MgSubscribedSku | Where-Object SkuPartNumber -eq "ENTERPRISEPREMIUM").SkuId }) RemoveLicenses = @() } Set-MgUserLicense -UserId $newUser.Id @licenseParams Write-Host "Licence assigned" -ForegroundColor Green # Set manager $managerUser = Get-MgUser -Filter "userPrincipalName eq '$Manager'" $managerRef = @{ "@odata.id" = "https://graph.microsoft.com/v1.0/users/$($managerUser.Id)" } Set-MgUserManagerByRef -UserId $newUser.Id -BodyParameter $managerRef Write-Host "Manager set: $Manager" -ForegroundColor Green # Add to standard groups $standardGroups = @("All-Staff", "M365-Licenced-Users", "IT-Department") foreach ($gName in $standardGroups) { $g = Get-MgGroup -Filter "displayName eq '$gName'" if ($g) { New-MgGroupMember -GroupId $g.Id -DirectoryObjectId $newUser.Id } } Write-Host "Added to standard groups" -ForegroundColor Green Write-Host "`nTemp password: $TempPw" -ForegroundColor Yellow Write-Host "User must change on first login." -ForegroundColor Yellow New User Onboarding ScriptCreates a new Entra ID user, assigns an M365 licence, sets manager, and adds to standard groups in one pass.

Customise $standardGroups for your environment and update the $LicenseSku SkuPartNumber. Run Get-MgSubscribedSku | Select SkuPartNumber,SkuId to find your SKU names. ADMIN REQUIRED
# Get full user details: Get-MgUser -UserId "user@domain.com" -Property * | Format-List # Check MFA status: Get-MgUserAuthenticationMethod -UserId "user@domain.com" # List all users with their sign-in status: Get-MgUser -All -Property DisplayName,UserPrincipalName,AccountEnabled,SignInActivity | Select DisplayName,UserPrincipalName,AccountEnabled, @{N="LastSignIn";E={$_.SignInActivity.LastSignInDateTime}} | Sort-Object LastSignIn -Descending | Format-Table # Find all disabled accounts: Get-MgUser -Filter "accountEnabled eq false" -Property DisplayName,UserPrincipalName | Select DisplayName,UserPrincipalName Get User Information & AuditRetrieve detailed user properties. SignInActivity requires AuditLog.Read.All scope. Useful for identifying inactive accounts, stale licences, or users who haven't enrolled MFA. AUDIT
# Reset a single user's password: $pw = @{ Password = "TempPassword@123" ForceChangePasswordNextSignIn = $true } Update-MgUser -UserId "user@domain.com" -PasswordProfile $pw # Force password reset for all users in a group: $groupId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" $members = Get-MgGroupMember -GroupId $groupId -All foreach ($m in $members) { $pw = @{ Password = "Welcome@$(Get-Random -Min 1000 -Max 9999)!" ForceChangePasswordNextSignIn = $true } Update-MgUser -UserId $m.Id -PasswordProfile $pw Write-Host "Reset: $($m.Id)" } Password Reset (Single & Bulk)Reset passwords and force change on next login. Bulk version iterates group members — useful after a credential breach or onboarding a cohort. Log the generated passwords securely and deliver via separate channel. SENSITIVE
# List all Conditional Access policies and their state: Get-MgIdentityConditionalAccessPolicy | Select DisplayName,State,@{N="CreatedDateTime";E={$_.CreatedDateTime}} | Sort-Object DisplayName | Format-Table # Export to CSV: Get-MgIdentityConditionalAccessPolicy | Select DisplayName,State,Description,CreatedDateTime,ModifiedDateTime | Export-Csv "C:\CA-Policies-$(Get-Date -f yyyyMMdd).csv" -NoTypeInformation Write-Host "Exported." -ForegroundColor Green Conditional Access Policy ReportExports all CA policies with their state (enabled/disabled/reportOnly). Run before any changes as a baseline. States: enabled, disabled, enabledForReportingButNotEnforced (report-only mode). EXPORT FIRST
# Show all licences in tenant and available count: Get-MgSubscribedSku | Select SkuPartNumber, @{N="Assigned";E={$_.ConsumedUnits}}, @{N="Total";E={$_.PrepaidUnits.Enabled}}, @{N="Available";E={$_.PrepaidUnits.Enabled - $_.ConsumedUnits}} | Format-Table # Assign a licence to a user: $skuId = (Get-MgSubscribedSku | Where-Object SkuPartNumber -eq "ENTERPRISEPREMIUM").SkuId Set-MgUserLicense -UserId "user@domain.com" ` -AddLicenses @(@{ SkuId = $skuId }) ` -RemoveLicenses @() # Remove a licence from a user: Set-MgUserLicense -UserId "user@domain.com" ` -AddLicenses @() ` -RemoveLicenses @($skuId) # Find all unlicensed users: Get-MgUser -All -Property DisplayName,UserPrincipalName,AssignedLicenses | Where-Object { $_.AssignedLicenses.Count -eq 0 } | Select DisplayName,UserPrincipalName Licence ManagementCheck tenant licence inventory, assign/remove licences, and find unlicensed users. Run the inventory check regularly — unused licences on disabled accounts are a common waste. ENTERPRISEPREMIUM = M365 E3. Check your SKU names with Get-MgSubscribedSku. LICENCES
# Get mailbox details: Get-Mailbox -Identity "user@domain.com" | Format-List # Grant Full Access (delegate): Add-MailboxPermission -Identity "target@domain.com" ` -User "delegate@domain.com" -AccessRights FullAccess -InheritanceType All # Grant Send As: Add-RecipientPermission -Identity "target@domain.com" ` -Trustee "delegate@domain.com" -AccessRights SendAs -Confirm:$false # Check mailbox size: Get-MailboxStatistics -Identity "user@domain.com" | Select DisplayName,TotalItemSize,ItemCount # List inbox rules: Get-InboxRule -Mailbox "user@domain.com" | Select Name,Enabled,Description # Check for mail forwarding (external forward audit): Get-Mailbox -ResultSize Unlimited | Where-Object { $_.ForwardingSmtpAddress -ne $null } | Select DisplayName,UserPrincipalName,ForwardingSmtpAddress | Format-Table Exchange Online — Mailbox ManagementDelegate access, check sizes, audit forwarding rules. The external forwarding audit is critical for security — identifies any mailboxes silently forwarding to external addresses (data exfiltration risk). Run monthly. AUDIT FORWARDING
# Find accounts with no sign-in for 90+ days: $cutoff = (Get-Date).AddDays(-90) Get-MgUser -All -Property DisplayName,UserPrincipalName,AccountEnabled,SignInActivity | Where-Object { $_.AccountEnabled -eq $true -and ($_.SignInActivity.LastSignInDateTime -lt $cutoff -or $_.SignInActivity.LastSignInDateTime -eq $null) } | Select DisplayName,UserPrincipalName, @{N="LastSignIn";E={$_.SignInActivity.LastSignInDateTime}} | Export-Csv "C:\Stale-Accounts-$(Get-Date -f yyyyMMdd).csv" -NoTypeInformation Write-Host "Report saved." -ForegroundColor Green # Find all guest (B2B) accounts: Get-MgUser -Filter "userType eq 'Guest'" -Property DisplayName,UserPrincipalName,SignInActivity | Select DisplayName,UserPrincipalName,@{N="LastSignIn";E={$_.SignInActivity.LastSignInDateTime}} | Format-Table Stale Account AuditFinds active accounts with no sign-in in 90 days — common compliance requirement. Also lists all Guest (B2B) accounts. Export CSV for manager review before bulk-disabling. Requires AuditLog.Read.All Graph scope. RUN MONTHLY
# Export all groups with member count: Get-MgGroup -All -Property DisplayName,GroupTypes,SecurityEnabled,MailEnabled,Description | Select DisplayName, @{N="Type";E={ if($_.GroupTypes -contains "Unified"){"M365"} elseif($_.SecurityEnabled){"Security"} else {"Distribution"} }}, Description, @{N="Members";E={(Get-MgGroupMember -GroupId $_.Id -All).Count}} | Export-Csv "C:\Groups-Report-$(Get-Date -f yyyyMMdd).csv" -NoTypeInformation # List all members of a specific group: $g = Get-MgGroup -Filter "displayName eq 'VPN-Users'" Get-MgGroupMember -GroupId $g.Id -All | ForEach-Object { Get-MgUser -UserId $_.Id -Property DisplayName,UserPrincipalName } | Select DisplayName,UserPrincipalName | Format-Table Groups & Membership ReportExports all groups with type classification and member count. Use to identify empty groups, oversized security groups, or before a Conditional Access policy change. The member-count loop can be slow on large tenants — run outside business hours. AUDIT
# Report on users and their registered auth methods: $users = Get-MgUser -All -Property DisplayName,UserPrincipalName,AccountEnabled | Where-Object AccountEnabled -eq $true $report = foreach ($u in $users) { $methods = Get-MgUserAuthenticationMethod -UserId $u.Id [PSCustomObject]@{ DisplayName = $u.DisplayName UPN = $u.UserPrincipalName MFAMethods = ($methods.AdditionalProperties["@odata.type"] -join ", ") HasMFA = ($methods.Count -gt 1) # Password alone = 1 method } } $report | Export-Csv "C:\MFA-Status-$(Get-Date -f yyyyMMdd).csv" -NoTypeInformation $report | Where-Object HasMFA -eq $false | Select DisplayName,UPN | Format-Table Write-Host "Users without MFA: $(($report | Where-Object HasMFA -eq $false).Count)" MFA Enrollment Status ReportLists all enabled users and their registered authentication methods. Exports to CSV and prints users with no MFA enrolled. Essential for Conditional Access planning — know your exposure before enforcing MFA. SCOPE: UserAuthenticationMethod.Read.All
# List all Teams: Connect-MicrosoftTeams Get-Team | Select DisplayName,Visibility,Archived | Format-Table # Get members of a Team: $team = Get-Team -DisplayName "IT Infrastructure" Get-TeamUser -GroupId $team.GroupId | Select User,Role | Format-Table # Add a member to a Team: Add-TeamUser -GroupId $team.GroupId -User "user@domain.com" -Role Member # Find Teams with external (guest) members: Get-Team | ForEach-Object { $guests = Get-TeamUser -GroupId $_.GroupId | Where-Object Role -eq Guest if ($guests) { [PSCustomObject]@{ Team=$_.DisplayName; Guests=($guests.User -join ", ") } } } | Format-Table Microsoft Teams ManagementList teams, manage membership, and audit guest access. The guest audit is important — teams with external members may expose internal files. Run quarterly and review with team owners. TEAMS
# List all Entra directory roles and their members: $roles = Get-MgDirectoryRole foreach ($role in $roles) { $members = Get-MgDirectoryRoleMember -DirectoryRoleId $role.Id if ($members) { Write-Host "`n=== $($role.DisplayName) ===" -ForegroundColor Cyan $members | ForEach-Object { $u = Get-MgUser -UserId $_.Id -ErrorAction SilentlyContinue if ($u) { Write-Host " $($u.DisplayName) — $($u.UserPrincipalName)" } } } } # Export Global Admins specifically: $gaRole = Get-MgDirectoryRole | Where-Object DisplayName -eq "Global Administrator" Get-MgDirectoryRoleMember -DirectoryRoleId $gaRole.Id | ForEach-Object { Get-MgUser -UserId $_.Id -Property DisplayName,UserPrincipalName } | Select DisplayName,UserPrincipalName | Export-Csv "C:\GlobalAdmins-$(Get-Date -f yyyyMMdd).csv" -NoTypeInformation Entra Directory Role AuditLists all privileged role assignments — who has Global Admin, User Admin, Exchange Admin, etc. Export Global Admins to CSV for compliance reviews. Best practice: Global Admin count should be 2–4 break-glass accounts maximum. PRIVILEGE AUDIT