I've always been more interested in how things work than in the job title.
I'm an IT Systems Engineer with about a decade of experience. My background is in Network and Telecommunications Management, and I've spent most of my career at a mid-size distribution company building and running the IT stack end to end.
What started as a standard sysadmin job turned into a lot more over time. When the company needed an ERP system, I ended up running the whole implementation myself: design, configuration, data migration, user training, all of it. When the network became a liability, I redesigned it. When manual processes were eating people's time, I automated them. That pattern has repeated a lot.
Day-to-day I'm managing a full stack: Microsoft 365, Entra ID, Active Directory, Hyper-V, SAP Business One, network infrastructure, backup, and endpoint security. There's a lot to it, but most of it runs itself at this point, which is kind of the point.
I went to Illinois State University for Network and Telecom Management. Having a broad foundation has been useful in practice. Enterprise environments don't stay in one lane, and being able to jump between infrastructure, cloud, security, and application layers has mattered more than I expected early on.
Lately I've been spending time with AI tools and agentic workflows, both for my own productivity and to understand where they actually fit in an enterprise context. I use AI daily to speed up scripting and problem-solving, and I'm experimenting with more autonomous workflows to figure out what's genuinely worth building.
A breakdown of the technologies and platforms I work with on a regular basis.
Real projects from a live enterprise environment. What the problem was, what I built, and what actually changed.
The Challenge: The company needed an ERP system to manage finance, inventory, and operations. The default path was expensive consultants for implementation and ongoing dependency on them for anything that needed changing.
My Approach: I led the SAP Business One implementation end to end: system design, configuration, data migration, user training, custom reporting in Crystal Reports, and building automation to connect the ERP with our other business systems.
The Challenge: We were moving more into the cloud but identity was still entirely on-prem. Access was inconsistent, security enforcement was manual, and there was no unified way to manage cloud and on-premises resources together.
My Approach: Set up Azure AD Connect with Entra ID, configured hybrid join and password hash sync, and built out conditional access policies. Brought Exchange Online, Teams Voice, SharePoint, and Intune into a consistent admin experience.
The Challenge: A lot of the daily work was repetitive and manual. Finance reports compiled by hand, user provisioning done inconsistently, operational tasks that ran on people's time instead of running themselves.
My Approach: Built automation across three layers: PowerShell for admin and system tasks, SQL Agent jobs for database workflows, and Power Automate for business processes that cross system boundaries. Reports now run on a schedule and land in inboxes without anyone pulling them.
The Challenge: The network was flat. No segmentation, aging firewall rules, and a setup where a problem in one area could spread everywhere. It worked until it didn't.
My Approach: Redesigned the whole thing. VLAN segmentation across traffic types, SD-WAN to connect two sites and share on-premises resources reliably, rebuilt VPN for remote access, and rewrote the firewall policy from scratch with explicit deny rules and proper logging.
The Challenge: We needed a real backup and recovery strategy. Not just scheduled jobs running in the background, but defined recovery targets, documented procedures, and actual confidence that a restore would work when it mattered.
My Approach: Built a layered setup using Azure Backup for cloud workloads and MABS for on-prem systems. Documented RTO/RPO targets, wrote recovery runbooks, and set up a recurring test cadence to verify that restores actually work before there's a reason to need them.
PowerShell handles most of my automation work. The scripts below are representative of what I build and maintain day-to-day.
#Requires -Modules ActiveDirectory, Microsoft.Graph.Users
<#
.SYNOPSIS
New employee onboarding: AD account, Entra ID sync, M365 license.
Logs to file and rolls back the AD account on failure.
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$FirstName,
[Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$LastName,
[Parameter(Mandatory)]
[ValidateSet('Finance','Operations','Sales','IT','Warehouse')]
[string]$Department,
[string]$Manager,
[string]$Title,
[ValidateSet('E3','E5')] [string]$LicenseTier = 'E3'
)
$ErrorActionPreference = 'Stop'
$LogFile = "C:\Logs\Onboarding\$($LastName)_$(Get-Date -f 'yyyyMMdd_HHmmss').log"
function Write-Log {
param([string]$Message, [ValidateSet('INFO','WARN','ERROR')] [string]$Level = 'INFO')
$Entry = "$(Get-Date -f 'yyyy-MM-dd HH:mm:ss') [$Level] $Message"
$Entry | Tee-Object -FilePath $LogFile -Append | Write-Host -ForegroundColor $(
switch ($Level) { 'WARN'{'Yellow'} 'ERROR'{'Red'} default{'Cyan'} }
)
}
function Wait-EntraSync {
param([string]$UPN, [int]$TimeoutSec = 180)
Write-Log "Triggering delta sync and waiting for Entra ID propagation..."
Start-ADSyncSyncCycle -PolicyType Delta | Out-Null
$Elapsed = 0
do {
Start-Sleep -Seconds 15; $Elapsed += 15
if (Get-MgUser -Filter "userPrincipalName eq '$UPN'" -ErrorAction SilentlyContinue) {
Write-Log "Entra ID sync confirmed after ${Elapsed}s"; return
}
} while ($Elapsed -lt $TimeoutSec)
throw "Entra sync timed out after ${TimeoutSec}s for $UPN"
}
try {
$Username = "$($FirstName.Substring(0,1).ToLower())$($LastName.ToLower())"
$UPN = "$Username@company.com"
$TempPass = ConvertTo-SecureString "Temp$(Get-Random -Min 1000 -Max 9999)!Kx" -AsPlainText -Force
if (Get-ADUser -Filter "SamAccountName -eq '$Username'" -ErrorAction SilentlyContinue) {
throw "AD account '$Username' already exists. Verify with HR before proceeding."
}
Write-Log "Creating AD account: $Username ($Department)"
New-ADUser -Name "$FirstName $LastName" `
-GivenName $FirstName -Surname $LastName `
-SamAccountName $Username -UserPrincipalName $UPN `
-Department $Department -Title $Title -Manager $Manager `
-AccountPassword $TempPass -Enabled $true -ChangePasswordAtLogon $true `
-Path "OU=$Department,OU=Users,DC=company,DC=local"
Write-Log "AD account created: $Username"
Connect-MgGraph -Scopes 'User.ReadWrite.All','Organization.Read.All' -NoWelcome
Wait-EntraSync -UPN $UPN
$SkuMap = @{ E3 = 'ENTERPRISEPACK'; E5 = 'ENTERPRISEPREMIUM' }
$License = Get-MgSubscribedSku | Where-Object {
$_.SkuPartNumber -eq $SkuMap[$LicenseTier] -and
$_.ConsumedUnits -lt $_.PrepaidUnits.Enabled
}
if (-not $License) { throw "No available $LicenseTier licenses in tenant." }
Set-MgUserLicense -UserId $UPN `
-AddLicenses @{ SkuId = $License.SkuId } `
-RemoveLicenses @()
Write-Log "M365 $LicenseTier license assigned to $UPN"
Write-Log "Onboarding complete. Temp password requires reset at first login."
}
catch {
Write-Log "FAILED: $_" -Level ERROR
if (Get-ADUser -Filter "SamAccountName -eq '$Username'" -ErrorAction SilentlyContinue) {
Remove-ADUser -Identity $Username -Confirm:$false
Write-Log "Rolled back AD account '$Username'" -Level WARN
}
exit 1
}
<#
.SYNOPSIS
Nightly SQL Server health check. Covers backup age, index fragmentation,
and long-running queries. Emails a formatted report with status flags.
#>
$ErrorActionPreference = 'Stop'
$Config = @{
SQLServer = 'SQL-SERVER-01'
Databases = @('CompanyDB', 'SAPB1DB', 'ReportingDB')
Recipients = @('it@company.com', 'manager@company.com')
SmtpServer = 'smtp.company.com'
FragThreshPct = 30 # flag indexes above this fragmentation %
LongQuerySec = 60 # flag queries running longer than this
BackupMaxSec = 86400 # 24 hours
}
$Results = [System.Collections.Generic.List[PSCustomObject]]::new()
$Errors = [System.Collections.Generic.List[string]]::new()
foreach ($DB in $Config.Databases) {
try {
$SizeBackup = Invoke-Sqlcmd -ServerInstance $Config.SQLServer -Database $DB `
-ErrorAction Stop -Query @"
SELECT
DB_NAME() AS DatabaseName,
CAST(SUM(size * 8.0 / 1024) AS INT) AS SizeMB,
(SELECT TOP 1 backup_finish_date
FROM msdb.dbo.backupset
WHERE database_name = DB_NAME() AND type = 'D'
ORDER BY backup_finish_date DESC) AS LastFullBackup
FROM sys.database_files
WHERE type = 0
"@
# Pull index data and filter in PowerShell to avoid SQL comparison operators
$IndexData = Invoke-Sqlcmd -ServerInstance $Config.SQLServer -Database $DB -Query @"
SELECT index_id, avg_fragmentation_in_percent
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED')
WHERE index_type_desc != 'HEAP'
"@
$FragCount = ($IndexData |
Where-Object { $_.avg_fragmentation_in_percent -gt $Config.FragThreshPct }).Count
$AgeSec = if ($SizeBackup.LastFullBackup) {
[int]((Get-Date) - $SizeBackup.LastFullBackup).TotalSeconds
} else { [int]::MaxValue }
$Results.Add([PSCustomObject]@{
Database = $DB
SizeMB = $SizeBackup.SizeMB
LastFullBackup = if ($SizeBackup.LastFullBackup) {
$SizeBackup.LastFullBackup.ToString('yyyy-MM-dd HH:mm')
} else { 'NEVER' }
BackupAgeHours = [Math]::Round($AgeSec / 3600, 1)
BackupStatus = if ($AgeSec -le $Config.BackupMaxSec) { 'OK' } else { 'STALE' }
FragIdxCount = $FragCount
})
}
catch { $Errors.Add("${DB}: $_") }
}
# Long-running query check (fetch all active requests, filter in PowerShell)
try {
$AllActive = Invoke-Sqlcmd -ServerInstance $Config.SQLServer -Database master -Query @"
SELECT r.session_id, DB_NAME(r.database_id) AS DatabaseName,
r.status, r.command,
r.total_elapsed_time / 1000 AS ElapsedSec,
SUBSTRING(t.text, 1, 100) AS QuerySnippet
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.session_id != @@SPID
"@
$LongRunning = $AllActive | Where-Object {
$_.session_id -gt 50 -and $_.ElapsedSec -gt $Config.LongQuerySec
} | Select-Object -First 5
} catch { $LongRunning = @() }
$HasIssues = ($Results | Where-Object { $_.BackupStatus -eq 'STALE' }) -or $Errors -or $LongRunning
$Subject = "SQL Health $(if ($HasIssues) {'[ACTION REQUIRED]'} else {'[OK]'}) - $(Get-Date -f 'yyyy-MM-dd')"
$Body = "SQL Server Health Report - $(Get-Date -f 'yyyy-MM-dd HH:mm')`n"
$Body += ("=" * 60) + "`n`n"
$Body += ($Results | Format-Table Database, SizeMB, LastFullBackup, BackupAgeHours, BackupStatus, FragIdxCount -AutoSize | Out-String)
if ($LongRunning) { $Body += "`nLONG-RUNNING QUERIES:`n$($LongRunning | Format-Table -AutoSize | Out-String)" }
if ($Errors) { $Body += "`nERRORS:`n$($Errors -join "`n")" }
Send-MailMessage -To $Config.Recipients -From 'sqlmonitor@company.com' `
-Subject $Subject -Body $Body -SmtpServer $Config.SmtpServer
Write-Host "Report sent to $($Config.Recipients -join ', '). Issues detected: $([int][bool]$HasIssues)"
#Requires -Modules Az.RecoveryServices, Az.Accounts
<#
.SYNOPSIS
Azure Backup job audit with Teams alerting and retry logic.
Checks the past 48 hours across a Recovery Services Vault.
Runs nightly via Task Scheduler using managed identity auth.
#>
$ErrorActionPreference = 'Stop'
$Config = @{
VaultName = 'CompanyBackupVault'
ResourceGroup = 'Company-RG-Backup'
LookbackHours = 48
MaxAuthRetries = 3
TeamsWebhook = $env:TEAMS_BACKUP_WEBHOOK # stored in system env, never hardcoded
AlertEmail = 'it@company.com'
SmtpServer = 'smtp.company.com'
}
function Send-TeamsAlert {
param([string]$Title, [string]$Details, [string]$Color = 'attention')
if (-not $Config.TeamsWebhook) { return }
try {
$Card = @{
type = 'message'
attachments = @(@{
contentType = 'application/vnd.microsoft.card.adaptive'
content = @{
'$schema' = 'http://adaptivecards.io/schemas/adaptive-card.json'
type = 'AdaptiveCard'
version = '1.2'
body = @(
@{ type = 'TextBlock'; text = $Title; weight = 'Bolder'; color = $Color },
@{ type = 'TextBlock'; text = $Details; wrap = $true }
)
}
})
} | ConvertTo-Json -Depth 10
Invoke-RestMethod -Uri $Config.TeamsWebhook -Method Post -Body $Card `
-ContentType 'application/json' -ErrorAction SilentlyContinue | Out-Null
}
catch { Write-Warning "Teams alert failed: $_" }
}
# Connect via managed identity with retry
$AuthAttempt = 0
do {
try { Connect-AzAccount -Identity -ErrorAction Stop; break }
catch {
$AuthAttempt++
if ($AuthAttempt -ge $Config.MaxAuthRetries) {
throw "Azure authentication failed after $AuthAttempt attempts: $_"
}
Write-Warning "Auth attempt $AuthAttempt failed. Retrying in 30s..."
Start-Sleep -Seconds 30
}
} while ($AuthAttempt -lt $Config.MaxAuthRetries)
$Vault = Get-AzRecoveryServicesVault -Name $Config.VaultName -ResourceGroupName $Config.ResourceGroup
Set-AzRecoveryServicesVaultContext -Vault $Vault
$Jobs = Get-AzRecoveryServicesBackupJob -From (Get-Date).AddHours(-$Config.LookbackHours) -VaultId $Vault.ID
$Summary = [PSCustomObject]@{
Total = $Jobs.Count
Success = ($Jobs | Where-Object Status -eq 'Completed').Count
Warnings = ($Jobs | Where-Object Status -eq 'CompletedWithWarnings').Count
Failed = ($Jobs | Where-Object Status -eq 'Failed').Count
Running = ($Jobs | Where-Object Status -eq 'InProgress').Count
}
Write-Host ($Summary | Format-List | Out-String)
$FailedJobs = $Jobs | Where-Object Status -eq 'Failed'
$WarningJobs = $Jobs | Where-Object Status -eq 'CompletedWithWarnings'
if ($FailedJobs) {
$Details = $FailedJobs | ForEach-Object {
$Duration = if ($_.StartTime -and $_.EndTime) {
"$([Math]::Round(($_.EndTime - $_.StartTime).TotalMinutes, 1))m"
} else { 'N/A' }
"$($_.WorkloadName) | Duration: $Duration | $($_.ErrorDetails.ErrorMessage -replace '\s+', ' ')"
}
$AlertText = "$($Summary.Failed) backup job(s) failed in vault '$($Config.VaultName)':`n$($Details -join "`n")"
Send-TeamsAlert -Title "Azure Backup Failure ($($Summary.Failed) jobs)" `
-Details ($Details -join ' | ') -Color 'attention'
Send-MailMessage -To $Config.AlertEmail -From 'backup-monitor@company.com' `
-Subject "ALERT: Azure Backup Failure - $(Get-Date -f 'yyyy-MM-dd')" `
-Body $AlertText -SmtpServer $Config.SmtpServer
Write-Warning $AlertText
}
if ($WarningJobs) {
$WarnDetails = $WarningJobs | ForEach-Object { "$($_.WorkloadName): $($_.ErrorDetails.ErrorMessage)" }
Write-Warning "$($Summary.Warnings) job(s) completed with warnings:`n$($WarnDetails -join "`n")"
Send-TeamsAlert -Title "Backup Warnings ($($Summary.Warnings) jobs)" `
-Details ($WarnDetails -join '; ') -Color 'warning'
}
if (-not $FailedJobs -and -not $WarningJobs) {
Write-Host "All $($Summary.Success) backup jobs completed successfully." -ForegroundColor Green
}
LinkedIn is the best place to reach me. Most of my code is private, but GitHub is there too.