Work

Automation I've built, engagements I've led, and things I build on my own time.

Client and employer names are left out.

  • 12automation packages measured
  • 2,560runs
  • 2,151tickets
  • ~80%less handling time on SentinelOne triage

One three-month stretch, 2026-07-08 to 2026-10-06, measured with Pia Package Reporter.

Automation

Ticket-driven automation I built on Pia for a managed services provider: EDR, email security, vulnerability management, and Microsoft 365, wired into the ConnectWise PSA. Engineers run each package from the ticket chat. The pattern carries over: if a tool has an API, I can wire it in for triage, enrichment, and reporting.

Automation / 01 Live

SentinelOne threat triage, from alert to verdict

  • SentinelOne
  • ConnectWise
  • REST APIs
  • PowerShell

Situation

Analysts copied threat details out of the EDR console into tickets by hand. Customer notes varied from one analyst to the next, and verdicts and exclusions were separate steps.

What I built

Three chained packages:

  • Enrichment. Pulls the full threat bundle and posts an internal note. Sets the ticket's company, type, and subtype for the board, then finds the right agreement. Notes cover lateral movement and handle blank users and file paths with trailing empty parentheses.
  • Verdict. The engineer picks a verdict. The package posts a standardized customer note, sets the verdict in SentinelOne, and writes the ticket number back to SentinelOne as a note. On a false positive it unquarantines automatically and reminds the engineer about exclusions.
  • Exclusions. Adds hash or path exclusions scoped to a site or group.
SentinelOne triage flow An engineer runs a package from the ticket chat, or a new ticket or any ConnectWise ticket action triggers it. The automation packages (enrichment, verdict, exclusions) look up the right SentinelOne portal and token for the customer, then read from and write to SentinelOne over its REST API, and write notes, ticket type, and agreement to ConnectWise. Engineer or ConnectWise trigger ticket chat, a new ticket, or any ticket action Automation packages 1 Enrichment · 2 Verdict · 3 Exclusions per-customer portal and token lookup REST API REST API SentinelOne threat, verdict, exclusions ConnectWise PSA notes, type, agreement
Simplified flow. Products are named. The environment is not.

How it works

  • Runs two ways: an engineer starts it from the ticket chat, or a new ticket or any ConnectWise ticket action triggers it automatically.
  • Resolves the right SentinelOne portal and API token per customer, across several consoles.
  • Board-aware logic handles different markets and agreement types.
  • Hostname searches are scoped from the PSA company to the matching SentinelOne site, so lookups across portals stay clean.
  • Fixed an unquarantine bug by checking the vendor's Swagger spec for the exact action name.

Outcome

Analyst handling time dropped by roughly 80 percent. In three months the enrichment package ran 970 times and the verdict package 228 times, and volume depends on how heavily clients use SentinelOne. The library is now standard practice across four regional markets.

Automation / 02 Live

Avanan email security, from enrichment to allowlist

  • Avanan
  • ConnectWise
  • REST APIs
  • Request signing
  • SPF

Situation

Phishing triage meant manual entity lookups, hand-built notes, and a restore-or-decline call with no consistent customer message.

What I built

  • Enrichment. Builds a full entity note: defanged links, the sending IP with whois, and SPF failures flagged at the vendor layer. Maps the Avanan tenant to the PSA company, sets ticket type and subtype, assigns the agreement, and restores the original requester on CC after patching the ticket.
  • Restore decision. The engineer approves or declines through a form. The package posts a customer note and calls the real restore, decline, or quarantine action. On approval it offers to chain into the allowlist.
  • Allowlist. Phishing and spam lists, tenant-wide or recipient-scoped, including domain-only entries. Warns before submitting if SPF failed.
  • Email download. Uploads the message to the ticket as a document. Delete-after-upload is waiting on a permissions change.

How it works

  • Signs every request with SHA-256 over the request ID, app ID, date, endpoint, and secret. The allowlist endpoint needs the full path and query string in the signature.
  • Found an undocumented company-name field on the tenants endpoint and used it as a clean tenant-to-company mapping.
  • Ran two search endpoints side by side to check a vendor support claim against the vendor's own published docs.
  • Fixed customer notes that weren't emailing the contact list. Ticket-level automatic-email flags defaulted to false.
  • Built so the enrichment package can be copied to other boards and markets once the first is stable.

Outcome

Every phishing ticket gets the same complete note and the same customer message. In three months the two Avanan enrichment packages ran 220 times.

Automation / 03 Live

Cross-platform device decommission

  • SentinelOne
  • Tenable
  • NinjaOne
  • ConnectWise

Situation

Retiring a device meant working through several consoles by hand, each with its own search quirks.

What I built

One package that starts from a hostname. It finds the device in SentinelOne and uninstalls the agent, unlinks and deletes it in Tenable, and finds it in NinjaOne. If a hostname matches more than one organization, it lists every match for manual review.

How it works

  • Scopes each search from the PSA company to the right site, so it never touches the wrong customer.
  • Fixed a skip cascade, so one platform with no match no longer stops the rest of the run.
  • Tenable matching returned nothing at first. The asset field is a NetBIOS name, not an FQDN, so the filter changed. Companies now match on Tenable's custom name, exactly equal to the PSA company name, after aligning all child accounts.
  • Tested live against both online and offline devices.

Outcome

One hostname in, three consoles cleaned up, with nothing touched outside the right customer.

Automation / 04 Live

International travel access for any Microsoft 365 client

  • Microsoft Graph
  • Entra ID
  • PowerShell

Situation

Travelers had to be added to a travel-allowed group on their leave date and removed on their return date. The first version was hardcoded to one customer.

What I built

The engineer picks the user and the group from dropdowns built live from the client's tenant. The package adds the user at the start of the leave date and removes them at the end of the return date, with internal notes at setup, on add, and on removal. A same-day trip adds the user immediately and confirms in chat.

How it works

  • Dynamic forms through the platform's form_def property. Users are enabled, non-guest accounts from Graph. Groups are travel groups Graph can add members to, excluding synced, dynamic, and mail-enabled security groups.
  • Validates dates and stops if leave and return are the same day, out of order, or already past.
  • Fixed a time zone bug. The date picker returns midnight UTC, which scheduled removals for the evening before the return date. It now adds at 12:00 AM and removes at 11:59 PM in a fixed local time zone, converted to UTC for the scheduler.

Outcome

Works for any client tenant, replacing a version hardcoded to one customer. Travelers get access on the day they leave and lose it the night they return, with nobody setting a reminder.

Write-up: scheduling travel access in Pia

Automation / 05 Live

Exchange Online archive check and enable

  • Exchange Online
  • Microsoft Graph
  • PowerShell

Situation

Mailbox storage questions meant signing in to the admin center for each client.

What I built

The engineer picks a user or shared mailbox from a live list that shows archive on or off next to each name. The package reports primary storage against quota, item count, deleted items, and archive status, size, and auto-expanding state. It then asks whether to enable the archive and logs everything to the ticket.

How it works

  • Client-scoped Graph and Exchange Online tokens, with the tenant's onmicrosoft domain resolved automatically.
  • Exchange cmdlets run through the same admin API invoke pattern as the platform's built-in activities.
  • Fixed a missing-data bug. Get-Mailbox by identity returns a trimmed object without archive fields, and a helper wrapped its return value in an array. A filtered lookup and a corrected return fixed both.

Outcome

Storage questions get answered from the ticket, with the numbers logged, and the archive enabled in the same run if it's needed.

Write-up: checking and enabling Exchange Online archives

Toolbox

  • Pax8 license management. A skill-gated package that checks reclaimable licenses first, shows a numbered subscription list, and prepares a quantity increase.
  • Microsoft 365 onboarding and offboarding. Onboarding creates the user, sets usage location with a Graph PATCH, assigns a license, waits for the mailbox, and adds groups by mirroring a template user plus standard and SharePoint groups. Reminds the engineer about the steps that can't be automated. Offboarding runs as its own package.
  • PSA agreement setter. Sets agreement, type, and subtype by board and market.
  • Tenable vulnerability export. Simplified to use the custom company name instead of fragile regex parsing.
  • Child-ticket company sync. Applies a parent ticket's company to its child tickets.
  • Panorama license monitoring. PowerShell against the Panorama API.

Engagements

Client infrastructure and security projects, with names and identifying details removed.

Engagements / 01

Firewalls, switching, and wireless: new builds and migrations

  • Palo Alto
  • Panorama
  • Fortinet
  • Cisco
  • Meraki
  • WatchGuard
  • UniFi
  • SD-WAN
  • Azure

New builds

  • New firewall deployments across Palo Alto, Fortinet, Cisco, Meraki, WatchGuard, UniFi, and others.
  • Palo Alto HA pairs from the PA-200 to PA-1400 range, onboarded to Panorama.
  • More than one Panorama built from scratch, both on-premises and hosted in Azure.
  • Wireless deployments on Meraki and UniFi.
  • SD-WAN across branch and multi-hub topologies, and TLS decryption.

Migrations

  • Cross-vendor firewall migrations, moving clients from one platform to another.
  • Palo Alto to Palo Alto hardware migrations, including multi-site rollouts.
  • Switch migrations and refreshes.
  • Office network relocations and segmentation with new VLANs and DMZs.

Approach

Every site gets its own cutover plan with rollback steps, and every build is documented and handed off to the team running it.

Engagements / 02

Migrations: Azure, Microsoft 365, and Entra ID

  • Azure
  • AWS
  • Microsoft 365
  • Exchange Online
  • Gmail
  • Entra ID
  • Intune
  • Conditional Access

Situation

Recurring work across many clients. Workloads moving to Azure from AWS and on-premises servers. Organizations moving into Microsoft 365 from Gmail and other tenants. Machines moving from local accounts or on-prem AD to Entra ID join or hybrid join.

Approach

Inventory first: workloads and their dependencies, users and mailboxes, DNS, devices and the GPOs they rely on. Then a cutover plan with a rollback path. For mail, domain and MX changes are sequenced so mail keeps flowing, with SPF, DKIM, and DMARC set up for the new platform. For devices, identity moves first, GPOs become Intune configuration profiles, and Conditional Access is enforced once the fleet is compliant.

What I built

  • Azure IaaS environments, and lift-and-shift migrations into them.
  • Target tenants, mailbox and data migrations, and domain cutovers.
  • Entra ID and hybrid join, Intune MDM and MAM on every platform, and SSO for cloud apps with Entra ID as the identity provider.

Outcome

Each migration documented and handed off to the team running it.

More client projects

  • Assessments. Environment assessments covering infrastructure, identity, cloud posture, and security readiness. Firewall health assessments, and Palo Alto security profile hardening against best practice.
  • Identity and remote access. Duo and Okta MFA rollouts, SAML and SSO integrations, VPNs, and Zero Trust remote access with Prisma Access and Cloudflare Zero Trust.
  • Backup and DR. Veeam and Cohesity, and a Nutanix AHV conversion with Nutanix Cloud Clusters on Azure as the DR target.
  • Client onboarding and endpoint. Onboarding of networking, endpoint, server, and host-based security services, and SentinelOne and Tenable agent rollouts at scale.
  • Incident response. SOC alert triage from SentinelOne and Arctic Wolf, and investigations into OAuth device-code phishing and ransomware detections on Linux hosts.
  • Microsoft 365 tenant separation. Moved part of an organization out of a shared tenant into its own. A domain can only be verified in one tenant at a time, so the domain moves set the shape of the cutover.
  • Citrix to DaaS and NetScaler. Moved on-premises Citrix, including Citrix ADC, to Citrix DaaS, with VDA deployments built from scratch.
  • .NET end-of-life assessment. Inventoried .NET runtimes and SDKs across 424 systems for an audit, narrowed the real exposure to 2 dependencies, scripted the remediation, and validated findings cleared in Tenable.
  • DNS record monitoring for client domains.

Side projects

Adventure: a travel tracker

  • Next.js
  • TypeScript
  • Tailwind
  • Supabase
  • Postgres
  • MapLibre
  • Geoapify
  • Cloudflare Workers

A digital corkboard map of everywhere I've been, lived, and want to go. Search any city, park, or landmark, drop a pin, and the map fills in.

The main design choice is splitting a place from an experience. Each place appears once on the map, with as many visits attached as you like: Tokyo in 2019, again in 2022, "go back" in 2026. Each visit keeps its own dates, notes, and ratings. Search fills in the coordinates, so you never type them.

Supabase handles Postgres and auth, with row-level security. Maps are MapLibre, place search is Geoapify, and it runs on Cloudflare Workers.

DealDesk

In development

  • Next.js
  • TypeScript
  • Tailwind
  • Supabase
  • Zod
  • Claude API

Back office for creators who do brand deals. Paste the deal and it tells you what you owe, what they owe, when it's due, and what to clarify before you agree.

The core is structured extraction: a Zod schema and a prompt, checked by a standalone eval harness against a fixture corpus after every prompt or schema change. Supabase handles the data with row-level security, and all AI calls stay server-side. It flags "terms to clarify" and stops short of legal advice.

Waypoint

In development

  • TypeScript
  • Node.js
  • Next.js
  • Tailwind CSS
  • PostgreSQL
  • Supabase
  • Envoy
  • Docker Compose
  • GitHub Actions

A strategic IT planning tool for MSPs. RMM shows what exists and PSA shows what work is happening. Waypoint answers what a client should do next, why, what blocks it, what it costs, and when.

The engine turns device data into findings, technology debt, initiatives, dependencies, a sequenced roadmap, and a budget forecast. It's pure TypeScript with no runtime dependencies. A validation suite runs mock datasets and fails if expected findings go missing, if sequencing breaks (a refresh jumping ahead of the cleanup that unblocks it), or if any output loses its explanation.

A Next.js web app over PostgreSQL 17 with row-level security on self-hosted Supabase, behind Envoy. Data comes in through an HMAC-signed ingest API, a collector pulls from GitHub today, and it all runs on Docker Compose with no cloud hosting needed. CI runs type checks, lint, pgTAP database tests, image scanning, and a hardening check.

Pia Package Reporter

  • JavaScript
  • Chrome extension (Manifest V3)

A browser extension that reports on Pia automation runs over a date range: totals for success, errors, warnings, cancellations, and unique tickets, a per-package breakdown with average run time, and every run with its ticket number. Exports to CSV.

I built it on my own to measure my own automation. The run counts in the Automation section come from it.

It uses the signed-in browser session, so there are no API keys to manage, and it sends nothing anywhere except the user's own Pia instance. Reports keep running in the background when the popup closes, and it backs off when Pia rate-limits it.

Useful-Skillet

  • Palo Alto
  • Panorama
  • Prisma Access
  • Prisma SD-WAN
  • FortiGate
  • Meraki

My Palo Alto reference library: architecture guides (NGFW sizing and HA, NGFW in Azure, Prisma Access, Prisma SD-WAN), hardening baselines, a BPA review checklist, PAN-OS upgrade runbooks for standalone and HA pairs, and a list of quirks that aren't obvious until they bite.

Sysadmin scripts

  • PowerShell
  • Bash
  • Microsoft Graph
  • Exchange Online
  • NinjaOne

PowerShell and Bash for Windows, Microsoft 365, and endpoint work, many written to run unattended under NinjaOne as SYSTEM. Includes agent deployment for SentinelOne and Tenable, mailbox forwarding and delegation reports, MFA status reports, forced Intune enrollment, .NET vulnerability cleanup, and macOS security posture checks.

DiskScope

In development

  • Swift
  • macOS

A Mac app that maps disk usage as a navigable directory tree.

IPScanner

In development

  • Python
  • Tkinter
  • Nmap

A desktop network scanner. Scans IP ranges, reports hosts and ports, and exports results to CSV.

Earlier

  1. University networking and cybersecurity lab, lab manager

    Led a team of 7 lab assistants, with a focus on automation. Deployed Splunk to monitor equipment usage across more than 10 network devices, 10 servers, and 100 virtual machines. Brought in over 15 log source types from 30 systems and built 25 detections.

  2. Collegiate cyber defense club, lead threat hunter

    Threat hunting with Splunk and Elasticsearch. First place in the state and Midwest qualifiers and at the Iowa CDC in 2022. Competed at the Global CCDC in 2021.