No commands found
Try a different keyword, command name, or description
| Windows CMD / PowerShell | macOS Terminal | Description & Usage |
|---|---|---|
ipconfig |
ifconfigip 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 -anetworksetup -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 |
| Command | Description & 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. |
| Command | Description & 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 |
| Command | Description & 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 data6 — 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 |
get vpn ipsec tunnel summary
Look for established in the IKE column. If missing, Phase1 is failing. Note the peer IP.
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.
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.
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.
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.
diagnose vpn tunnel flush <tunnel-name>
Forces renegotiation. If tunnel comes up then drops again, check DPD settings and peer keepalives.
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.
❌ 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
get router info bgp summary
State should be Established. Other states: Active = trying to connect (TCP), Idle = not attempting, Connect = TCP SYN sent.
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.
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.
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.
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.
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.
❌ 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
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.
# 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.
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.
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.
# 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.
# 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.
❌ 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
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.
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.
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.
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).
# 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.
❌ 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
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.
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.
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.
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.
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.
❌ 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
# 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.
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.
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.
# 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.
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.
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.
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.
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.
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.
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).
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.
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.
❌ 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
| Command | Description & 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 |
| Command | Description & 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 |
| Command | Description & 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 |
| Script / Command | Description & 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 |