Category: Weekly Blog

Weekly blog series featuring real-world IT solutions, cloud security strategies, automation projects, and development tutorials to help professionals build resilient, scalable environments.

  • When Inbox Rules Go Rogue: A 10-Command Playbook to Stop Impersonation

    Macro ant on a leaf—small bug, big damage. Quiet inbox rules (forward/delete/hide) are how real-account impersonation starts. This post shows 10 PowerShell fixes to stop it fast.

    Excerpt
    Most “email impersonation” losses start quietly—rules that forward, delete, or hide mail. This playbook backs up evidence, stops the bleed, removes risky rules, clears forwarding, and verifies. Calm hands, clear steps.

    Intro
    Most “email impersonation” (BEC) starts in two ways:

    1. Real-account misuse—someone phishes a password/token and quietly adds inbox rules (forward, delete, hide) or enables mailbox forwarding.
    2. No-account spoofing—look-alike domains and weak SPF/DKIM/DMARC let crooks send as if they’re us.

    This post fixes bucket #1 fast. You don’t need Compliance Center/Purview to clean a single mailbox: run these in Windows PowerShell 5.1 to back up → stop all rules → remove risky patterns → clear forwarding → verify. The examples below target [email protected]. After cleanup, keep the door shut by disabling SMTP AUTH/legacy protocols and blocking external auto-forwarding. (For bucket #2, tighten SPF/DKIM/DMARC—that’s outside this quick fix.)

    Perspective
    There are no super heroes in IT—no capes, no instant rescues. When rules go rogue, heroics make noise; runbooks make progress. The job is to protect people’s work with boring, proven steps.

    Practice (today, not someday)

    • Connect (read-only) — open a secure session to Exchange Online for the mailbox you’re fixing. Import-Module ExchangeOnlineManagement -RequiredVersion 3.9.0 -Force Connect-ExchangeOnline -UserPrincipalName [email protected] -ShowBanner:$false $mbx = "[email protected]"
    • Backup rules to CSV (read-only) — take a snapshot so you have evidence and an easy rollback reference. $ts = (Get-Date).ToString('yyyyMMdd-HHmm') Get-InboxRule -Mailbox $mbx | Select Name,Enabled,Priority,From,SentTo,SubjectContainsWords,MoveToFolder,ForwardTo,RedirectTo,DeleteMessage,StopProcessingRules | Sort Priority | Export-Csv "$env:USERPROFILE\Desktop\$($mbx)-InboxRules-$ts.csv" -NoTypeInformation -Encoding UTF8
    • Disable all rules (change) — safe stop; nothing runs while you fix things. Get-InboxRule -Mailbox $mbx | Disable-InboxRule -Confirm:$false
    • Remove delete rules (change) — get rid of any rule that silently deletes messages. Get-InboxRule -Mailbox $mbx | Where-Object {$_.DeleteMessage} | ForEach-Object { Remove-InboxRule -Mailbox $mbx -Identity $_.Name -Confirm:$false }
    • Remove hide/stop rules (change) — remove rules that hide mail (Junk/Archive/RSS/Conversation History) or halt further processing. Get-InboxRule -Mailbox $mbx | Where-Object { $_.StopProcessingRules -or ($_.MoveToFolder -match 'Junk|Archive|RSS|Conversation History') } | ForEach-Object { Remove-InboxRule -Mailbox $mbx -Identity $_.Name -Confirm:$false }
    • Remove forward/redirect rules, focusing on external (change) — strip any rule that forwards or redirects mail, especially off-tenant. $internal = @('jetmariano.us') # add internal domains if needed $rules = Get-InboxRule -Mailbox $mbx foreach($r in $rules){ $targets=@() foreach($t in @($r.ForwardTo)+@($r.RedirectTo)){ if($t -is [string]){$targets+=$t} elseif($t.PrimarySmtpAddress){$targets+=$t.PrimarySmtpAddress.ToString()} elseif($t.Address){$targets+=$t.Address.ToString()} elseif($t){$targets+=$t.ToString()} } $external = $false foreach($addr in $targets){ if($addr -match '@'){ $domain = ($addr -split '@')[-1].ToLower() if(-not ($internal -contains $domain)){ $external = $true } } } if($external -or $targets.Count -gt 0){ Remove-InboxRule -Mailbox $mbx -Identity $r.Name -Confirm:$false } }
    • Clear mailbox-level forwarding (change) — turn off any top-level forwarding set on the mailbox. Set-Mailbox -Identity $mbx -DeliverToMailboxAndForward:$false -ForwardingSmtpAddress $null -ForwardingAddress $null
    • Verify list and count (read-only) — prove you’re clean; zero is ideal. Get-InboxRule -Mailbox $mbx | Sort Priority | Format-Table Name,Enabled,ForwardTo,RedirectTo,MoveToFolder,DeleteMessage -Auto (Get-InboxRule -Mailbox $mbx | Measure-Object).Count
    • Re-enable only safe movers (optional change) — if you truly want routine filing, turn on only simple move-to-folder rules. Get-InboxRule -Mailbox $mbx | Where-Object { $_.MoveToFolder -and -not $_.ForwardTo -and -not $_.RedirectTo -and -not $_.DeleteMessage -and -not $_.StopProcessingRules } | ForEach-Object { Enable-InboxRule -Mailbox $mbx -Identity $_.Name -Confirm:$false }
    • Disconnect (read-only) — close your session cleanly. Disconnect-ExchangeOnline -Confirm:$false

    Final Reflection
    The work narrowed down to steady steps. Not a clever hack—just patience, order, and protection of someone’s inbox.

    Pocket I’m Keeping
    Runbooks over heroics.

    What I Hear Now
    Be steady. Protect the work. I’ll show you the next step.

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Exchange Online: Mailbox Used/Quota/Available (Redacted)

    Excerpt

    Exchange Online sometimes reports mailbox sizes with “Unlimited” wrappers that break simple math. Today I built a one-liner-friendly PowerShell snippet that returns Used GB, Quota GB, and Available GB—even when EXO wraps values in Unlimited<T>.

    Intro

    I needed the available mailbox size for [mailbox]@[domain] without exposing tenant internals. The usual TotalItemSize parsing failed because EXO returned Unlimited<ByteQuantifiedSize>. Here’s the redacted approach that works reliably and falls back cleanly.

    Notes from {Speaker}

    • Context: Exchange Online + PowerShell; target was [mailbox]@[domain].
    • Constraint: TotalItemSize and ProhibitSendQuota show Unlimited wrappers or localized strings.
    • Goal: Get UsedGB / QuotaGB / AvailableGB with no tenant secrets.

    Perspective (direct quotes)

    “If it’s Unlimited<T>, ask for .Value—and always guard with IsUnlimited.”
    “When objects don’t expose bytes, regex the (123,456 bytes) pattern as a fallback.”

    Practice (today, not someday)

    Use this redacted snippet. It works with Get-EXO* and falls back to classic cmdlets:

    # EXO connection (redacted UPN)
    Connect-ExchangeOnline -UserPrincipalName [me] -ShowBanner:$false
    
    $upn = '[mailbox]@[domain]'   # e.g., [email protected]
    
    try {
      $stat = Get-EXOMailboxStatistics -Identity $upn -ErrorAction Stop
      $mbx  = Get-EXOMailbox           -Identity $upn -PropertySets Quota -ErrorAction Stop
    
      $usedBytes = if ($stat.TotalItemSize.PSObject.Properties.Name -contains 'Value') {
        [int64]$stat.TotalItemSize.Value.ToBytes()
      } else {
        [int64](([regex]::Match($stat.TotalItemSize.ToString(), '\(([\d,]+)\sbytes\)')).Groups[1].Value -replace ',','')
      }
    
      $quotaBytes = if ($mbx.ProhibitSendQuota -and `
                        ($mbx.ProhibitSendQuota.PSObject.Properties.Name -contains 'IsUnlimited') -and `
                        -not $mbx.ProhibitSendQuota.IsUnlimited) {
        [int64]$mbx.ProhibitSendQuota.Value.ToBytes()
      } elseif ($mbx.ProhibitSendQuota.ToString() -notmatch 'Unlimited') {
        [int64](([regex]::Match($mbx.ProhibitSendQuota.ToString(), '\(([\d,]+)\sbytes\)')).Groups[1].Value -replace ',','')
      } else { $null }
    }
    catch {
      $stat = Get-MailboxStatistics -Identity $upn
      $mbx  = Get-Mailbox           -Identity $upn
      $usedBytes  = [int64](([regex]::Match($stat.TotalItemSize.ToString(), '\(([\d,]+)\sbytes\)')).Groups[1].Value -replace ',','')
      $quotaBytes = if ($mbx.ProhibitSendQuota.ToString() -match 'Unlimited') { $null }
                    else { [int64](([regex]::Match($mbx.ProhibitSendQuota.ToString(), '\(([\d,]+)\sbytes\)')).Groups[1].Value -replace ',','') }
    }
    
    $usedGB  = [math]::Round($usedBytes/1GB, 2)
    $quotaGB = if ($quotaBytes) { [math]::Round($quotaBytes/1GB, 2) } else { $null }
    $availGB = if ($quotaBytes) { [math]::Round(($quotaBytes-$usedBytes)/1GB, 2) } else { $null }
    
    [pscustomobject]@{
      Mailbox            = $upn
      UsedGB             = $usedGB
      QuotaGB            = $quotaGB
      AvailableGB        = $availGB
      StorageLimitStatus = $stat.StorageLimitStatus
    }
    

    Final Reflection

    EXO’s objects are powerful but quirky. Guarding for IsUnlimited, using .Value.ToBytes(), and keeping a regex fallback turns a flaky one-off into a repeatable tool.

    Pocket I’m Keeping

    Parse what’s there, not what you expect.” When APIs return wrapped or localized strings, a small fallback (regex for (#### bytes)) saves the day.

    What I Hear Now (direct quotes)

    “Measure in bytes, report in GB.”
    “Handle Unlimited first, then do math.”
    “One clean object out—every time.”

    Link to the Script

    Microsoft Exchange Online PowerShell (Get-EXOMailbox, Get-Mailbox) — official docs

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Restoring Delivery Safely: SCL-1 + Tenant Allow/Block List

    Message trace + quarantine check (redacted): verified deliveries and deployed a Priority-0 SCL-1 allow rule for [partner-domain]

    Excerpt

    When a trusted partner’s emails started landing in spam (or nowhere at all), I traced the path, set a top-priority allow rule, and used Defender’s Allow/Block list to force clean delivery—without exposing internal details.

    Intro

    Today’s note is for admins who wear the on-call hat. Messages from [partner-domain] weren’t reaching our users. Some were quarantined as spam; others never showed. I documented the exact, reversible steps I used to restore delivery while keeping our environment safe and auditable. All vendor names, mailboxes, IPs, and hostnames are redacted.

    Notes from {Speaker}

    • Symptom: Mail from [partner-domain] was flagged/filtered; end users reported “not delivered” or “went to Junk.”
    • Tools used: Exchange Admin Center, Microsoft 365 Defender, Exchange Online PowerShell.
    • Constraints: Multiple legacy transport rules; need a change that’s surgical, auditable, and easy to roll back.
    • Guardrails: Prefer allowlisting at the domain + spoof layer (not blanket bypass for everything). Keep headers/SPF/DKIM intact for future tuning.

    Perspective (direct quotes)

    “No decision is a decision—so we’ll choose the safe, reversible one.”
    “Top-priority SCL-1 with Stop processing beats noisy downstream rules.”
    “If spoof intelligence is tripping, meet it where it lives: Tenant Allow/Block.”

    Practice (today, not someday)

    1) Triage — see what actually happened

    Connect-ExchangeOnline -UserPrincipalName [me] -ShowBanner:$false
    Get-MessageTrace -StartDate (Get-Date).AddDays(-2) -EndDate (Get-Date) `
      -SenderAddress *@[partner-domain] |
      Select Received,SenderAddress,RecipientAddress,Status
    
    # If quarantined:
    Connect-IPPSSession
    Get-QuarantineMessage -StartReceivedDate (Get-Date).AddDays(-2) `
      -EndReceivedDate (Get-Date) -SenderAddress *@[partner-domain]
    

    2) Create a top-priority transport rule to force deliver

    • Condition: Sender domain is [partner-domain]
    • Action: Set SCL to -1
    • Option: Stop processing more rules
    • Priority: 0 (top)

    PowerShell (redacted names):

    New-TransportRule -Name "Allow [partner-domain] (SCL-1)" `
      -SenderDomainIs "[partner-domain]" -SetSCL -1 -StopRuleProcessing $true -Priority 0
    

    3) Add an org-wide Allow (Defender)

    • Defender → Policies & rules → Tenant Allow/Block List
    • Domains & addresses → Add → Allow → [partner-domain]
    • If Message Trace shows Spoof/High Confidence Phish, also allow under Spoofing for the exact sending pair.

    4) (Optional) Anti-spam policy allow list

    • Anti-spam inbound policy → Allowed domains → add [partner-domain].

    5) Verify

    Get-TransportRule "Allow [partner-domain] (SCL-1)" |
      fl Name,Priority,State,SenderDomainIs,SetSCL,StopRuleProcessing
    
    # Send a new test; re-run Get-MessageTrace to confirm Status = Delivered
    

    6) Longer-term hygiene (ask the sender)

    • Ensure SPF passes (include correct outbound hosts).
    • Turn on DKIM for their domain.
    • Align DMARC policy gradually (none → quarantine → reject) once stable.

    Final Reflection

    Most “email is broken” moments aren’t outages—they’re safety systems doing their job a bit too eagerly. The win is balancing protection with precision so real mail flows and risky mail doesn’t.

    Pocket I’m Keeping

    Put the SCL-1 allow rule with Stop Processing at the top. It’s decisive, reversible, and neutralizes noisy legacy rules downstream.

    What I Hear Now (direct quotes)

    “Measure twice, move to Priority 0 once.”
    “Don’t fight spoof intelligence—configure it.”
    “Document the rollback the same minute you deploy the fix.”

    • Microsoft 365 Defender — Tenant Allow/Block List (TABL)
    • Exchange Online — Transport rules (mail flow rules)
    • Message trace in the modern EAC
      (references listed for context; links omitted for privacy—add your preferred docs when publishing)

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Beat the 99% Wall: Upgrade Windows 10 → 11 the Easy Offline Way (Do This Before Oct 5, 2025)

    When the upgrade sits at 99%… don’t panic. Go offline and run the ISO upgrade the smart way

    Windows 10 reaches end of life on Oct 5, 2025. After that, it won’t get security updates. If you stay on Win10, your machine is a sitting duck for malware and attackers. Don’t procrastinate.

    If Windows Update keeps failing—or hangs forever at 99%—use this clean, offline upgrade that skips the flaky “checking for updates” step.


    1) First, confirm your PC is Windows 11–ready

    Use Microsoft’s official checker:
    https://support.microsoft.com/en-us/windows/check-if-a-device-meets-windows-11-system-requirements-after-changing-device-hardware-f3bc0aeb-6884-41a1-ab57-88258df6812b

    Important: The most common blockers are the CPU and motherboard (TPM 2.0, UEFI/Secure Boot). If your device doesn’t meet Windows 11 requirements, it’s unsupported after Oct 5, 2025. Treat that Windows 10 PC as unsafe for internet use—either upgrade/replace the hardware, reassign it to offline tasks, or retire it.


    2) Prep (5–10 minutes)

    • Unplug non-essential USB devices (drives, printers, docks).
    • Ensure ≥30 GB free on C:.
    • Suspend BitLocker (if enabled): Control Panel → BitLocker → Suspend.
    • Temporarily disable third-party AV/VPN.
    • Clean Boot: msconfigServicesHide Microsoft servicesDisable all; Startup → disable everything.

    3) Reset Windows Update & appraiser caches (PowerShell)

    Open PowerShell as Administrator and run:

    net stop wuauserv
    net stop bits
    net stop cryptsvc
    
    ren "C:\Windows\SoftwareDistribution" SoftwareDistribution.old
    ren "C:\Windows\System32\catroot2" catroot2.old
    
    rd /s /q "C:\$WINDOWS.~BT"
    rd /s /q "C:\$WINDOWS.~WS"
    rd /s /q "C:\Windows\Panther"
    md "C:\Windows\Panther"
    
    net start cryptsvc
    net start bits
    net start wuauserv
    

    Then heal the image:

    DISM /Online /Cleanup-Image /RestoreHealth
    sfc /scannow
    

    4) Run the upgrade offline from ISO (no update checks)

    1. Download the official Windows 11 ISO (same edition/language/arch) from Microsoft.
    2. Right-click the ISO → Mount → note the drive letter (e.g., E:).
    3. Disconnect the network (unplug Ethernet / disable Wi-Fi).
    4. In elevated PowerShell, launch Setup with Dynamic Update disabled:
    Start-Process -FilePath 'E:\setup.exe' -ArgumentList '/auto upgrade /dynamicupdate disable /copylogs C:\$UpgradeLogs' -Verb RunAs -Wait
    

    (Optional quick blocker scan without upgrading):

    Start-Process -FilePath 'E:\setup.exe' -ArgumentList '/compat scanonly /dynamicupdate disable' -Verb RunAs -Wait
    

    5) If it still stalls

    • Drivers (most common):
      • Storage: Device Manager → Storage controllers → switch to Microsoft Standard controller (Update driver → Let me pick).
      • Display: use Microsoft Basic Display Adapter temporarily.
      • Remove extra language packs, old VPN clients, and heavy OEM utilities.
    • BIOS/Chipset: update from your PC maker’s support page.
    • Rerun the offline setup command.

    6) Pinpoint the exact blocker (2 minutes)

    If it fails again, run Microsoft SetupDiag and read the summary:

    mkdir C:\SetupDiag; cd C:\SetupDiag
    .\SetupDiag.exe /Output:C:\SetupDiag\SetupDiagResults.log
    
    • Codes like 0xC1900101-0x… usually name a driver (oem*.inf)—remove/roll it back and retry.
    • Dynamic-Update/Appraiser errors → repeat Step 3 and ensure you’re truly offline with /dynamicupdate disable.

    7) After success

    • Re-enable BitLocker, AV/VPN, and normal startup apps.
    • Reconnect the network and run Windows Update to pull fresh drivers and features.

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Marked in Time — Sep 24, 2025 Email Offboarding: Forward for 14 Days → Then Retire the Mailbox (No Shared Mailboxes)

    Clean handoff: 14-day forward then retired the mailbox. using powershell

    Excerpt

    A simple, low-risk offboarding pattern: enable a 14-day forward to the supervisor with an auto-reply, keep copies in the mailbox, then remove forwarding and retire the account. No shared mailboxes, no drama.

    Photo suggestion

    Something neutral and professional: a close-up of a keyboard lock icon, or a soft sunset over a temple (if you want to keep the page’s visual theme).
    Caption idea: “Quiet handoffs, clean closures.”


    Context (redacted)

    Policy: No shared mailbox conversions. For leavers, enable a 2-week mail forward to the supervisor, show a clear auto-reply, then delete the user so the mailbox soft-deletes and later hard-deletes. Team files live in SharePoint/Teams; local working data is archived to encrypted USB for short-term retention.


    Before (T0) — Enable 14-Day Forward + Auto-Reply

    Goal: Forward new messages for two weeks and keep a copy in the mailbox for audit/review; clearly inform senders.

    Replace with your addresses before running:
    $User = "[email protected]"
    $Supervisor = "[email protected]"

    # Admin sign-in
    Import-Module ExchangeOnlineManagement -ErrorAction SilentlyContinue
    Connect-ExchangeOnline
    
    # Vars
    $User       = "[email protected]"
    $Supervisor = "[email protected]"
    $Days       = 14
    $Now        = Get-Date
    $End        = $Now.AddDays($Days)
    
    # Enable mailbox-level forwarding (keep a copy)
    Set-Mailbox -Identity $User `
      -ForwardingSmtpAddress $Supervisor `
      -DeliverToMailboxAndForward $true
    
    # Schedule auto-replies for the same window
    $InternalMsg = @"
    This mailbox is no longer monitored.
    For assistance, please contact $Supervisor or call the main line.
    "@
    
    $ExternalMsg = @"
    Thanks for your message. This mailbox is no longer monitored.
    Please email $Supervisor for assistance.
    "@
    
    Set-MailboxAutoReplyConfiguration -Identity $User `
      -AutoReplyState Scheduled `
      -StartTime $Now `
      -EndTime $End `
      -InternalMessage $InternalMsg `
      -ExternalMessage $ExternalMsg `
      -ExternalAudience All
    

    Parallel housekeeping (same day):

    • Reset the user’s password, revoke sign-in sessions, and (optionally) block sign-in during the transition.
    • Transfer/confirm ownership of OneDrive/SharePoint/Teams files needed by the team.
    • Archive any local workstation data to an encrypted USB (BitLocker To Go) if policy allows.

    After (T+14) — Remove Forwarding → Retire Account

    Goal: Stop forwarding, disable auto-reply, and delete the user (soft-delete mailbox). Optionally hard-delete the mailbox once soft-delete is visible.

    Import-Module ExchangeOnlineManagement -ErrorAction SilentlyContinue
    Connect-ExchangeOnline
    
    $User = "[email protected]"
    
    # Remove mailbox-level forwarding & auto-reply
    Set-Mailbox -Identity $User -ForwardingSmtpAddress $null -DeliverToMailboxAndForward $false
    Set-MailboxAutoReplyConfiguration -Identity $User -AutoReplyState Disabled
    
    # Delete the user in Entra ID (do this in the portal or via Graph)
    # Entra admin center → Users → select user → Delete
    # After directory sync, the mailbox will be in "soft-deleted" state (up to 30 days)
    
    # Optional: Permanently delete the mailbox once soft-deleted
    $Soft = Get-Mailbox -SoftDeletedMailbox -ErrorAction SilentlyContinue |
            Where-Object {$_.PrimarySmtpAddress -eq $User}
    if ($Soft) {
      Remove-Mailbox -PermanentlyDelete -Identity $Soft.ExchangeGuid -Confirm:$false
    }
    

    Lessons Learned

    • Clarity beats complexity. Forward + auto-reply for a defined window avoids confusing senders and helps the team capture anything urgent.
    • Keep a copy while forwarding. It preserves context during the transition.
    • No shared mailbox needed. If policy prohibits it, you can still do a clean, auditable handoff.
    • Document the timestamps. Password reset, token revocation, forward on/off, user deletion, and any permanent mailbox purge.

    Pocket I’m Keeping

    • Short window, clear message, clean cutover.
    • Files belong in SharePoint/Teams; email is a temporary bridge.
    • Quiet, consistent process reduces friction and drama.

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Cloning a VM with PowerShell and VMware PowerCLI


    Intro

    When you need to quickly spin up a test or lab machine, cloning an existing VM can save hours compared to building from scratch. VMware PowerCLI brings the full power of vSphere management into PowerShell. Here’s a simple walkthrough.


    Step 1 — Install VMware PowerCLI

    Open PowerShell as administrator and run:

    Install-Module -Name VMware.PowerCLI -Scope CurrentUser
    Import-Module VMware.PowerCLI
    

    This installs the official VMware module and loads it into your session.


    Step 2 — Connect to vCenter

    You’ll need credentials for your vCenter server.

    Connect-VIServer -Server <vcenter-server.domain> -User <username> -Password '<password>'
    

    Step 3 — Clone an Existing VM

    Pick the source VM, target VM names, host, and datastore. Example:

    # Define source VM
    $sourceVM = "Base-Win10-VM"
    
    # Clone to new VM
    New-VM -Name "Test-VM01" -VM $sourceVM `
           -VMHost (Get-VMHost -Name <target-host>) `
           -Datastore (Get-Datastore -Name <datastore-name>) `
           -Location (Get-Folder -Name "VMs")
    
    • -VM points to the existing machine you’re cloning.
    • -VMHost pins the new VM to a specific ESXi host.
    • -Datastore chooses where to store the VM’s disks.
    • -Location defines the vCenter folder for organization.

    Step 4 — Power On the New VM

    Start-VM -VM "Test-VM01"
    

    Final Reflection

    PowerCLI makes cloning fast, repeatable, and scriptable. Instead of clicking through vSphere UI screens, you can prepare test VMs with a single command.


    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Marked in Time — Fixing a “Sender not allowed” DL (redacted)

    Excerpt
    Our all-hands list rejected internal senders after we allowed two external addresses. Here’s what happened, how to fix it cleanly in Exchange Online, and a PowerShell snippet you can reuse.


    Intro
    Two days ago, I could email everyone@[redacted].com just fine. Today, my message bounced: “this group only accepts messages from people in its organization or on its allowed senders list.” We’d recently added two partner addresses (s@[partner].com, j@[partner].com) so they could email the DL. That change flipped the DL into strict allow-list mode—blocking even internal senders who weren’t explicitly listed. Here’s the minimal, durable fix.


    Straight line (what happened)
    • Symptom: NDR when sending to everyone@[redacted].com from an internal account.
    • State check showed:
    – RequireSenderAuthenticationEnabled: False
    – AcceptMessagesOnlyFromSendersOrMembers: {} (and earlier, it contained only the two partner GUIDs).
    • Root cause: Delivery management was saved in “only these senders” mode; membership/ownership doesn’t matter in that state.
    • Goal: Let all internal, authenticated users send; allow only specific externals; block the rest.


    Fix (clean model)

    1. Let internal, authenticated users send to the DL (no hard allow-list on the group).
    2. Enforce external restrictions with a transport rule that allows only the partner exceptions.

    Commands (PowerShell — Exchange Online)

    Connect

    Connect-ExchangeOnline -ShowBanner:$false
    

    Allow internal, authenticated senders (clear hard allow-list)

    Set-DistributionGroup everyone@[redacted].com `
      -AcceptMessagesOnlyFromSendersOrMembers $null `
      -RequireSenderAuthenticationEnabled:$true
    

    Create the external block rule with an allow-list

    # remove if an older copy exists (safe if none)
    Get-TransportRule "Block external to Everyone (except allow-list)" -ErrorAction SilentlyContinue |
      Remove-TransportRule -Confirm:$false
    
    New-TransportRule "Block external to Everyone (except allow-list)" `
      -FromScope NotInOrganization `
      -AnyOfToHeader "everyone@[redacted].com" `
      -ExceptIfFrom "s@[partner].com","j@[partner].com" `
      -RejectMessageReasonText "External senders are not allowed for this list."
    

    Verify

    Get-DistributionGroup everyone@[redacted].com |
      fl PrimarySmtpAddress,RequireSenderAuthenticationEnabled,AcceptMessagesOnlyFromSendersOrMembers
    
    Get-TransportRule "Block external to Everyone (except allow-list)" |
      fl Name,State,FromScope,AnyOfToHeader,ExceptIfFrom
    

    Update the allow-list later

    # add another partner
    Set-TransportRule "Block external to Everyone (except allow-list)" `
      -ExceptIfFrom @{Add="newuser@[partner].com"}
    
    # remove a partner
    Set-TransportRule "Block external to Everyone (except allow-list)" `
      -ExceptIfFrom @{Remove="j@[partner].com"}
    

    Smoke tests
    • Internal sender → everyone@[redacted].com: delivers.
    • External sender (not on list): NDR with “External senders are not allowed…”
    • Allowed partner (s@[partner].com or j@[partner].com): delivers.


    Why not leave the DL in allow-list mode?
    Because it’s brittle. Every internal sender must be explicitly added, which guarantees future bounces and admin toil. Using RequireSenderAuthenticationEnabled for internal mail + a transport rule for externals gives you clarity and control.


    Final reflection
    Small toggles can have outsized effects. DL delivery settings look simple, but one checkbox can silently change who’s “allowed.” The durable pattern is: authenticate inside, whitelist outside, and verify with a quick trace.


    Pocket I’m keeping
    • Always snapshot DL settings before/after a change.
    • Prefer transport rules for external policy; don’t hard-gate internals via allow-lists.
    • Add a ready-to-run “add/remove external exception” snippet to the runbook.


    What I hear now
    Clarity beats cleverness. Make the rule obvious enough that the next admin can read it and know exactly who can send and why.

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Ops Note — Picking the best vSAN host with one PowerCLI check

    Excerpt
    Quick, repeatable way to see CPU/RAM/vSAN headroom across hosts and choose where to place the next VM. Today it pointed us to vsan2.


    Intro
    Before cloning a new Windows VM, I ran a fast PowerCLI sweep across three vSAN hosts to compare free CPU, free memory, and vSAN free space. All three had identical vSAN capacity; vsan2 had the most free RAM, so that’s the landing spot.


    Straight line (what I did)
    • Pulled CPU and memory usage per host (MHz/MB) and calculated free.
    • Queried each host’s vSAN datastore(s) and summed free/total GB.
    • Printed a compact table to compare vsan1/2/3 at a glance.
    • Chose the host with the highest Mem_Free_GB (tie-break on vSAN free).


    Command (copy/paste)

    # Hosts to check (redacted)
    $hosts = 'vsan1.example.local','vsan2.example.local','vsan3.example.local'
    
    $report = foreach ($h in $hosts) {
      try {
        $vmh    = Get-VMHost -Name $h -ErrorAction Stop
        $cpuTot = $vmh.CpuTotalMhz;  $cpuUse = $vmh.CpuUsageMhz
        $memTot = $vmh.MemoryTotalMB; $memUse = $vmh.MemoryUsageMB
    
        $vsan      = $vmh | Get-Datastore | Where-Object { $_.Type -eq 'vsan' }
        $dsCapGB   = ($vsan | Measure-Object CapacityGB  -Sum).Sum
        $dsFreeGB  = ($vsan | Measure-Object FreeSpaceGB -Sum).Sum
        $dsFreePct = if ($dsCapGB) { [math]::Round(100*($dsFreeGB/$dsCapGB),2) } else { 0 }
    
        [pscustomobject]@{
          Host          = $vmh.Name
          CPU_Free_GHz  = [math]::Round(($cpuTot-$cpuUse)/1000,2)
          CPU_Total_GHz = [math]::Round($cpuTot/1000,2)
          CPU_Free_pct  = if ($cpuTot) { [math]::Round(100*(($cpuTot-$cpuUse)/$cpuTot),2) } else { 0 }
          Mem_Free_GB   = [math]::Round(($memTot-$memUse)/1024,2)
          Mem_Total_GB  = [math]::Round($memTot/1024,2)
          Mem_Free_pct  = if ($memTot) { [math]::Round(100*(($memTot-$memUse)/$memTot),2) } else { 0 }
          vSAN_Free_GB  = [math]::Round($dsFreeGB,2)
          vSAN_Total_GB = [math]::Round($dsCapGB,2)
          vSAN_Free_pct = $dsFreePct
        }
      } catch {
        [pscustomobject]@{ Host=$h; CPU_Free_GHz='n/a'; CPU_Total_GHz='n/a'; CPU_Free_pct='n/a';
          Mem_Free_GB='n/a'; Mem_Total_GB='n/a'; Mem_Free_pct='n/a';
          vSAN_Free_GB='n/a'; vSAN_Total_GB='n/a'; vSAN_Free_pct='n/a' }
      }
    }
    
    $report | Format-Table -AutoSize
    
    # Optional: pick best host by RAM, then vSAN GB
    $best = $report | Where-Object { $_.Mem_Free_GB -is [double] } |
            Sort-Object Mem_Free_GB, vSAN_Free_GB -Descending | Select-Object -First 1
    "Suggested placement: $($best.Host) (Mem free: $($best.Mem_Free_GB) GB, vSAN free: $($best.vSAN_Free_GB) GB)"
    

    Result today
    • vsan2 showed the most free RAM, with CPU headroom similar across all three and identical vSAN free space.
    • Suggested placement: vsan2.


    Pocket I’m keeping
    • Check host headroom before every clone—30 seconds now saves hours later.
    • Prefer RAM headroom for Windows VDI/worker VMs; CPU is usually similar across nodes.
    • Keep a one-liner that prints the table and the suggested host.


    What I hear now
    Clone to vsan2, power up, then let DRS/vMotion rebalance after the build window. Repeat this check whenever adding workloads or after maintenance.

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Marked in Time — “If Thou Endure Well” (Neal A. Maxwell)

    Saratoga Springs Utah Temple with a rising waxing gibbous moon.

    Excerpt
    None of us is immune from trial. Elder Neal A. Maxwell teaches that if we endure well, today’s struggles are shaped into tomorrow’s blessings. Here’s my mark-in-time takeaway and how I’m applying it.


    Intro
    I listened again to Elder Neal A. Maxwell’s devotional “If Thou Endure Well.” The sentence that stayed with me: None of us can or will be immune from the trials of life. However, if we learn to endure our struggles well, they will be turned into blessings in eternity. That’s both bracing and kind—God doesn’t waste pain when we place it in His hands.


    Straight line (what he’s saying)
    • Mortality guarantees opposition; surprise is optional.
    • Enduring well ≠ grim hanging-on; it’s faithful submission, patience, and continuing to choose light.
    • Timing is part of God’s tutoring—deliverance sometimes tarries so discipleship can deepen.
    • Gratitude and meekness change how trials shape us. They don’t shorten the storm, but they change the sailor.
    • The Lord consecrates affliction to our gain when we refuse cynicism and keep covenant routines (scripture, prayer, sacrament, service).


    Final reflection
    Enduring well is a decision repeated—quietly—over and over. It’s choosing not to narrate my trial as abandonment, but as apprenticeship. It’s trusting that God is doing more with my life than I can see from the shoreline.


    Pocket I’m keeping
    • Expect opposition; practice patience on purpose.
    • Pair prayers with small, durable acts (keep the next covenant, serve the next person, take the next right step).
    • Measure “progress” by faithfulness, not by ease.


    What I hear now
    Tonight’s images—reflections, a quiet bench, a waxing gibbous over the spire—feel like a lesson in waiting. I can’t rush the moon to its mark, but I can keep framing, steady my hands, and choose light again. If I endure well, God will finish the alignment.


    Link to the talk
    Full devotional: “If Thou Endure Well” — Neal A. Maxwell (BYU Speeches).

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Fixing “Sender not allowed” to an internal group (Exchange Online) — a quick forensic + runbook


    POST BODY

    When a partner emailed our all-hands list, they got an NDR:
    “the group only accepts messages from people in its organization or on its allowed senders list… sender not allowed.”

    We’d solved this once before and didn’t capture the steps. This time we did.


    Forensic summary (redacted)

    • group: all@[corp-redacted].com
    • external sender: firstname.lastname@[partner-redacted].com
    • symptom: NDR “sender not allowed”
    • root causes:
      1. the group required authenticated (internal) senders only, and
      2. the external wasn’t on the group’s allowed-senders list
    • gotcha we hit: New-MailContact failed with ProxyAddressExists — an existing MailUser already owned the external SMTP, so we reused it instead of creating a new contact

    Straight line (what fixed it)

    1. identify group by SMTP and check whether it’s a DL or a Microsoft 365 Group
    2. locate the external as an existing MailContact/MailUser (include soft-deleted objects)
    3. add that object to the group’s AcceptMessagesOnlyFromSendersOrMembers list
    4. allow the group to accept external senders (keeps the allow-list in effect)
    5. test and confirm with Message trace

    Reusable runbook (PowerShell, redacted)

    # 0) Connect
    Connect-ExchangeOnline
    
    # 1) Variables (edit these)
    $GroupSmtp = "all@[corp-redacted].com"
    $ExternalAddresses = @("firstname.lastname@[partner-redacted].com")
    
    # 2) Resolve the group (works for DL or M365 Group)
    $grp = Get-EXORecipient -Filter "PrimarySmtpAddress -eq '$GroupSmtp'"
    $grp | fl Name,RecipientTypeDetails,PrimarySmtpAddress,Identity,ExternalDirectoryObjectId
    
    # 3) Ensure each external exists as a recipient we can allow (MailContact/MailUser).
    #    If already present (or soft-deleted), reuse it.
    $recips = @()
    foreach ($addr in $ExternalAddresses) {
      $r = Get-EXORecipient -ResultSize Unlimited -IncludeSoftDeletedRecipients `
           -Filter "PrimarySmtpAddress -eq '$addr'"
      if (-not $r) {
        try { New-MailContact -Name $addr -ExternalEmailAddress $addr | Out-Null
              $r = Get-EXORecipient -Filter "PrimarySmtpAddress -eq '$addr'" }
        catch { Write-Host "Contact already exists somewhere: $addr" }
      }
      $recips += $r
    }
    $recips | ft Name,RecipientTypeDetails,PrimarySmtpAddress -AutoSize
    
    # 4) Add externals to allow-list AND allow external senders
    if ($grp.RecipientTypeDetails -eq "GroupMailbox") {
      # Microsoft 365 Group (Unified Group)
      foreach ($r in $recips) {
        Set-UnifiedGroup -Identity $grp.ExternalDirectoryObjectId `
          -AcceptMessagesOnlyFromSendersOrMembers @{Add=$r.Identity}
      }
      Set-UnifiedGroup -Identity $grp.ExternalDirectoryObjectId -AllowExternalSenders:$true
      Get-UnifiedGroup -Identity $grp.ExternalDirectoryObjectId |
        fl DisplayName,PrimarySmtpAddress,AllowExternalSenders,AcceptMessagesOnlyFromSendersOrMembers
    } else {
      # Distribution Group / Mail-enabled Security Group
      foreach ($r in $recips) {
        Set-DistributionGroup -Identity $grp.Identity `
          -AcceptMessagesOnlyFromSendersOrMembers @{Add=$r.Identity}
      }
      Set-DistributionGroup -Identity $grp.Identity -RequireSenderAuthenticationEnabled:$false
      Get-DistributionGroup -Identity $grp.Identity |
        fl DisplayName,PrimarySmtpAddress,RequireSenderAuthenticationEnabled,AcceptMessagesOnlyFromSendersOrMembers
    }
    
    # 5) Message trace (adjust window)
    Get-MessageTrace -SenderAddress $ExternalAddresses[0] -RecipientAddress $GroupSmtp `
      -StartDate (Get-Date).AddHours(-2) -EndDate (Get-Date) |
      ft Received,Status,RecipientAddress,MessageId
    

    Common pitfalls we saw (and how we handled them)

    • ProxyAddressExists on New-MailContact → an existing MailUser/Contact already holds that SMTP; reuse it (or permanently remove the soft-deleted recipient first).
    • group can’t be found by display name → target by SMTP with Get-EXORecipient -Filter "PrimarySmtpAddress -eq '...'".
    • delivery still blocked after allow-list → the DL still required authenticated senders; set RequireSenderAuthenticationEnabled:$false (DL) or AllowExternalSenders:$true (M365 Group).

    Click-path (EAC, if you don’t want PowerShell)

    • Recipients → Contacts → add/find the partner’s contact
    • Recipients → Groups → open the group → Delivery management → “Accept messages from” → add the contact
    • For DLs: turn off “Require sender authentication”
    • For M365 Groups: enable “Allow external senders”

    Prevention / hygiene

    • keep an “Authorized External Senders — all” mail-enabled security group; allow that group on the DL/M365 Group, then just add/remove partner contacts over time
    • document the NDR verbatim and the message trace ID when you close an incident

    Redaction note

    All addresses and names are redacted. Replace with your real SMTPs when running the script.

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Outlook Won’t Send, Can’t Search, or Stuck on “Updating”? A One-Page Fix (for Everyone)

    Outbox (1) and a red error banner—typical signs Outlook can’t send because the local data file (OST/PST) hit the size limit or the client is Working Offline.

    Intro

    When mail matters, guessing hurts. This is the quick way I fix the three big Outlook problems—won’t send, can’t search, won’t connect—with steps for employees and deeper checks for admins.

    The straight line

    Rule #1: Prove if it’s your Outlook, your profile, or the service—then act. Don’t change ten things; follow the flow.


    For employees (5 fixes you can do safely)

    1. Compare with Outlook on the web
      • Open your browser → sign in to outlook.office.com.
      • If web mail works, your account is fine; the issue is this device/Outlook app.
    2. Check the basics
      • Make sure Work Offline isn’t turned on.
      • Restart Outlook (fully exit from the tray), then restart the computer.
      • Trim the Outbox: very large attachments (>20–25 MB) can block the queue.
    3. Search not finding results?
      • Windows: Outlook → File → Options → Search → Indexing OptionsRebuild. Give it time.
      • Mac: System Settings → Siri & Spotlight → ensure Mail & Messages are allowed. If needed, add then remove your Outlook profile folder from Spotlight Privacy to force a re-index.
    4. Disable add-ins (quick test)
      • Windows: File → Options → Add-insCOM Add-ins → Go… → uncheck all (especially meeting/CRM add-ins).
      • Mac (New Outlook): Get Add-insMy add-ins → disable. Re-test.
    5. Free up mailbox space
      • Empty Deleted Items and Junk, clear Sync Issues folders, and archive old Sent Items. Low free space = slow Outlook.

    If mail works on the web but not in the app after these steps, it’s a profile or device issue—hand off to IT or continue with the admin flow below.


    For IT pros (targeted triage)

    1) Scope & signal

    • Service or client? If OWA works and multiple users in the site are fine, it’s local.
    • Status bar messages matter: “Trying to connect…”, “Updating this folder…”, “Need password”, “Limited connectivity”—write them down.

    2) Profile & connectivity

    • New profile (Windows): Control Panel → Mail (Microsoft Outlook)Show Profiles…Add → set Prompt for a profile and test.
    • Connection Status (Windows): Ctrl + right-click the Outlook tray icon → Connection Status; confirm Auth/Protocol and server round-trip.
    • Cached Exchange setting: File → Account Settings → Account → Change… → move the mail to keep offline slider down (e.g., 6–12 months) and retest.

    3) Search

    • Windows Search service running? Rebuild from Indexing Options and ensure Outlook is in the index list.
    • OST health: If search is corrupt or folders are out of sync, close Outlook, rename the OST, reopen to rebuild.

    4) Add-ins & startup

    • Safe mode test (Windows): Start Outlook while holding Ctrl (you’ll be asked to start in safe mode). If that works, remove add-ins (Teams/Zoom/CRM are usual suspects).
    • Reset the navigation pane (Windows): Run command box and reset the nav pane if views are corrupted (as an IT step).

    5) Credentials & auth

    • Windows Credential Manager: remove stale Office/Outlook creds; relaunch and re-auth.
    • Modern Auth prompts stuck? Close all Office apps; kill background “Office” processes; try again.

    6) Calendar & send issues

    • Delegate/Shared mailbox problems:** verify Full Access/Send As and re-map the mailbox.
    • Rules causing loops: export, disable all, re-test send/receive.
    • Stuck meetings: clear Outbox, switch to Online Mode briefly, send, switch back to Cached.

    7) Tools that save time

    • Microsoft Support and Recovery Assistant (SaRA): excellent for profile, activation, and connection repairs.
    • Message Trace (Exchange/Defender portals): confirm delivery path before blaming the client.

    8) When to rebuild or repair

    • New profile fixed it? Keep it and retire the old one.
    • Office repair (Quick Repair, then Online Repair) if multiple Office apps are unstable.

    60-second decision tree

    1. OWA works?
      • No → service/network issue; escalate.
      • Yes → client/device issue → continue.
    2. Safe mode works?
      • Yes → disable add-ins until stable.
      • No → new profile.
    3. Still failing after new profile?
      • Check Credentials, Cached slider, OST rebuild.
      • If send only fails for shared/delegate mailbox → permissions or transport rules.
    4. Search still blank?
      • Rebuild index (Windows), verify Spotlight (Mac), rebuild OST.

    Prevent the repeat (settings that help)

    • Mailbox hygiene: retention/archiving for Sent & large attachments.
    • Keep add-ins lean: only what the team truly uses.
    • Known-good profile image: for kiosk/reimaging scenarios.
    • Network indicators: if Wi-Fi is flaky, Outlook shows it first—fix the Wi-Fi.
    • One place for help: a short “How to open OWA + report exact error text + timestamp” guide pinned for staff.

    Final reflection — why this approach won’t go away

    • Clarity beats tinkering. OWA tells you if it’s the account or the app.
    • Profiles are perishable. Rebuilding is faster than endless registry spelunking.
    • Add-ins are the usual villains. Test in safe mode first.
    • Search takes time. Reindex once, then let it finish; don’t keep poking.
    • Document the path. The same steps teach juniors and calm frustrated users.

    For employees — Data file full? (PST/OST ~50 GB default)

    Symptoms: messages stuck in Outbox, sync never finishes, warnings about “data file reached maximum size.”

    Fix (Windows Outlook):

    1. Outlook → File → Info → Tools → Mailbox Cleanup
      • Empty Deleted Items / Junk.
      • View Mailbox Size → delete/archive biggest folders (Sent Items is usually #1).
    2. Search for big attachments: in the search bar choose Size → Huge (> 1 MB) or Very Large (> 5 MB) and delete/move.
    3. Data file compact: File → Account Settings → Account Settings → Data Files (tab) → select your account’s Outlook Data FileSettings → Compact Now.
    4. If you use Exchange/Business account: File → Account Settings → Account Settings → Change → slide “Mail to keep offline” down to 6–12 months, then restart Outlook (older mail stays available in OWA).

    If OWA sends fine but the app still can’t after this, hand it to IT (profile rebuild or archive needed).


    For IT pros — PST/OST limits & remediation

    • Default limit: modern Outlook uses ~50 GB per PST/OST (configurable via policy). Near the cap (there’s a warn threshold), send/receive fails and users see “data file has reached maximum size.”
    • Triage: confirm the user’s Data Files size (File → Account Settings → Account Settings → Data Files), and whether the profile caches shared mailboxes (common OST bloat).
    • Remediation options (prefer in this order):
      1. Mailbox hygiene / archiving: enable Online Archive (Exchange Online) and apply retention to move old items automatically.
      2. Reduce cache depth: set Mail to keep offline to 3–12 months; leave older mail online.
      3. Shared mailbox strategy: uncheck Download shared folders (Account Settings → More Settings → Advanced) for very large shared mailboxes, or add them as additional mailboxes without caching.
      4. Compact / rebuild OST: after cleanup, compact; if corruption suspected, close Outlook, rename the OST, relaunch to rebuild.
      5. Policy keys: you can raise the max size via policy/registry (also set the warn threshold) but Microsoft guidance is to favor Online Archive over very large OST/PST files.

    Tell-tale errors/messages: send stuck in Outbox, “Data file reached maximum size,” frequent sync loops; OWA sends normally.


    What I hear now

    • Start with service vs. client (OWA).
    • Safe mode, then add-ins.
    • If in doubt, new profile.
    • Index once, wait.
    • Be kind: Outlook issues feel personal to users—steady process helps them breathe.

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • When a “Microsoft” alert hijacks your screen after a spoofed Facebook call

    Tech-support scam pop-up mimicking Microsoft Defender with a bogus support line 877-415-4519DO NOT CALL.

    Intro

    Tonight’s “video call” looked like it came from a friend. The moment you tapped Accept, your browser flipped full-screen: “Microsoft has shut down your internet. Do not turn off your computer. Call now.” That’s a classic tech-support scam—built to scare, not to help.

    ─────────────────────────────────────────

    What’s really happening

    • It’s only a web page (often opened by the call link) that abuses pop-ups, full-screen mode, and fake Windows/Defender art.
    • Microsoft/Apple/your ISP never lock your device through a browser or post a phone number to call.
    • If you call, they’ll try to remote in, install “fixers,” and charge you—or steal data.

    ─────────────────────────────────────────

    Do this immediately (quick exit)

    1. Do not call. Do not click.
    2. Kill the browser.
      • Windows: Ctrl+W (close tab). If stuck, Alt+F4 or open Task Manager (Ctrl+Shift+Esc) and End task on the browser.
      • Mac: +W (close tab). If stuck, Force Quit with ++Esc.
      • iPhone/iPad/Android: swipe up and force-close the browser app.
    3. Reopen safely (prevents the bad tab from restoring):
      • Windows/Mac: hold Shift while launching the browser to block session restore.
      • iPhone Safari: Settings ▸ Safari ▸ Clear History and Website Data.
      • Chrome mobile: Chrome ▸ ⋮ ▸ History ▸ Clear browsing data (Time range: All time).

    ─────────────────────────────────────────

    Clean up (2–5 minutes)

    • Run a scan. Windows: Windows Security ▸ Virus & threat protection ▸ Quick scan (then a Full scan later). Mac/mobile: update OS; run your trusted AV if installed.
    • Remove permission junk.
      • Browser Notifications/Permissions: Settings ▸ Privacy & security ▸ Site settings ▸ Notifications ▸ remove unknown sites.
      • Extensions/Add-ons: remove anything you don’t recognize.
    • Messenger/Facebook safety.
      • Tell your friend their account may be compromised.
      • Facebook ▸ Settings ▸ Password & security ▸ Where you’re logged in ▸ Log out of unknown sessions; Turn on two-factor.
    • If you entered info / installed software / called them:
      • Disconnect from the internet.
      • Uninstall any remote tools they had you add (AnyDesk, TeamViewer, Quick Assist sessions).
      • From a clean device, change passwords (email first).
      • Run Microsoft Defender Offline scan (Windows Security ▸ Scan options).
      • Contact your bank if you paid or shared card info.

    ─────────────────────────────────────────

    Prevent the next one

    • Treat surprise video calls as suspect. Decline and message the friend to confirm.
    • Lock calling down in Messenger: Settings ▸ Privacy ▸ Message delivery / Who can call you ▸ restrict to Friends.
    • Keep autosaving tabs off if you don’t need it.
    • Update OS and browsers; updates close the tricks these pages use.
    • Never let strangers remote into your device. Real companies don’t cold-call you for support.

    ─────────────────────────────────────────

    Final Reflection

    Scams run on panic. Breathe, quit the tab, then clean up. A browser page can’t “brick” your computer—but fear can make us hand over the keys.

    ─────────────────────────────────────────

    What I hear now

    • Close first, investigate second.
    • Call no numbers that pop up on a web page.
    • Verify with the friend; secure your accounts; turn on 2FA.
    • Slow is smooth, and smooth is fast.

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • 5-Minute Fix: Why Your Windows PC Feels Slow (and what to try before calling IT)

    Top memory consumers at a glance—captured with PowerShell to diagnose a sluggish system.

    TL;DR: Check Task Manager → close the hog → restart apps/PC → free space → trim startup apps → update → quick scan. If it’s still slow, capture a screenshot and call IT.


    1) Is it one app or everything?

    • Press Ctrl+Shift+EscTask ManagerProcesses.
    • If CPU / Memory / Disk sits >90% for a minute, note the top app.
    • Right-click → End task (only on apps you opened). If speed returns, you found the culprit.

    2) Quick reset (fastest real fix)

    • Save work → Restart the PC (not Shut down). Restarts clear memory leaks and stuck updates.

    3) Free up space

    • Open File Explorer → This PC. If your C: drive has <10 GB free, Windows will crawl.
    • Settings → System → Storage → Storage Sense → Run cleanup now.
    • Empty Downloads and Recycle Bin if safe.

    4) Trim startup apps (the slow-boot killers)

    • Ctrl+Shift+Esc → Startup apps.
    • Set non-essentials to Disabled (music updaters, PDF helpers, “helper” launchers, etc.). Leave security/backup tools enabled.

    5) Browser bloat check

    • Close tabs you don’t need.
    • Disable heavy extensions (Edge/Chrome → … → Extensions).
    • Consider “Continue running background apps” Off (Chrome → System).

    6) Updates (do it once, then restart)

    • Settings → Windows Update → Check for updates.
    • Install → Restart outside your busiest hour.

    7) Quick malware scan

    • Windows Security → Virus & threat protection → Quick scan.

    8) Network ≠ computer

    • If only web/video is slow, run a quick speed test. If speed is normal but the PC lags, it’s local; if speed is bad on all devices, it’s the network.

    Optional: Simple PowerShell checks (for confident users)

    Open PowerShell as your normal user.

    Top memory users

    Get-Process | Sort-Object -Descending WorkingSet |
     Select-Object -First 10 Name,Id,@{n='RAM(MB)';e={[math]::Round($_.WorkingSet/1MB)}}
    

    Disk space by drive

    Get-PSDrive -PSProvider FileSystem |
     Select Name,@{n='Free(GB)';e={[math]::Round($_.Free/1GB,1)}},
            @{n='Used(GB)';e={[math]::Round(($_.Used)/1GB,1)}}
    

    List startup items (view only)

    Get-CimInstance Win32_StartupCommand | Select Name,Command,Location
    

    Tip: Disable startup apps in Task Manager, not via the registry.


    When to call IT (and what to send)

    If it’s still slow after these steps, send:

    • A screenshot of Task Manager → Processes (sorted by CPU and then Memory),
    • Your free disk space (C: drive),
    • What you were doing when it slowed down.

    That info turns a 30-minute back-and-forth into a 5-minute fix.


    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Secure Azure setup with Entra ID, Bastion, and private VM


    Scope

    Stand up a fresh Azure landing zone with a minimal but secure baseline: Entra ID (Azure AD) hardening, management structure, logging, networking, a Windows/Linux VM without public exposure, and safe access (Bastion + Entra sign-in).

    Placeholders to replace:
    TENANT_NAME · MG_ROOT · SUB_NAME · RG_CORE · RG_NET · RG_VM · LOCATION · VNET_NAME · SUBNET_APP · BASTION_SUBNET · VM_NAME · VM_SIZE · ADMIN_GROUP_OBJECTID


    0) Prereqs

    • Azure tenant & subscription created (via portal/Commerce).
    • Azure CLI logged in: az login az account set --subscription "SUB_NAME"
    • Optional SKUs: Entra ID P1/P2 for Conditional Access, PIM, Identity Protection.

    1) Entra ID (Tenant) Baseline

    • Create two break-glass cloud-only Global Admin accounts; long passwords; exclude from CA; store offline.
    • Turn on Security Defaultsor implement baseline Conditional Access:
      • Require MFA for admins.
      • Disable legacy/basic auth.
      • Require MFA for all users or at least privileged roles.
    • Enable SSPR, passwordless Authenticator (and FIDO2 keys if available).
    • Use PIM for role activation (P2).
    • Create AAD groups for RBAC (e.g., Azure-VM-Admins).

    (Portal-driven; no commands included to keep this redacted.)


    2) Management Structure & Tags

    • Create management group root and place the subscription under it.
    • Standardize tags (Owner, CostCenter, Env, DataClass).
    az account management-group create -n MG_ROOT
    az account management-group subscription add --name MG_ROOT --subscription "SUB_NAME"
    

    3) Core Resource Groups & Logging

    az group create -n RG_CORE -l LOCATION
    az group create -n RG_NET  -l LOCATION
    az group create -n RG_VM   -l LOCATION
    
    # Log Analytics workspace
    az monitor log-analytics workspace create -g RG_CORE -n LAW-CORE -l LOCATION
    LAW_ID=$(az monitor log-analytics workspace show -g RG_CORE -n LAW-CORE --query id -o tsv)
    
    # Send Activity Logs to LAW
    az monitor diagnostic-settings create \
      --name "activity-to-law" \
      --resource "/subscriptions/$(az account show --query id -o tsv)" \
      --workspace $LAW_ID \
      --logs '[{"categoryGroup":"allLogs","enabled":true}]'
    

    4) Guardrails with Azure Policy (minimal starter)

    # Require tags
    az policy assignment create -g RG_CORE -n require-tags \
      --policy "Require a tag and its value on resources" \
      --params '{"tagName":{"value":"Owner"},"tagValue":{"value":"REDACTED"}}'
    
    # Allowed locations
    az policy assignment create -g RG_CORE -n allowed-locations \
      --policy "Allowed locations" \
      --params '{"listOfAllowedLocations":{"value":["LOCATION"]}}'
    

    Enable Microsoft Defender for Cloud and auto-provision agents (portal) to get JIT VM access recommendations and secure score.


    5) Networking (no public RDP/SSH)

    # VNet + subnets
    az network vnet create -g RG_NET -n VNET_NAME -l LOCATION \
      --address-prefixes 10.10.0.0/16 \
      --subnet-name SUBNET_APP --subnet-prefix 10.10.10.0/24
    
    # Dedicated Bastion subnet (must be exactly AzureBastionSubnet)
    az network vnet subnet create -g RG_NET --vnet-name VNET_NAME \
      -n AzureBastionSubnet --address-prefixes 10.10.254.0/27
    
    # NSG and rules (deny inbound by default; allow vnet)
    az network nsg create -g RG_NET -n NSG-APP
    az network nsg rule create -g RG_NET --nsg-name NSG-APP -n Allow-VNet \
      --priority 100 --access Allow --direction Inbound --protocol '*' \
      --source-address-prefixes VirtualNetwork --source-port-ranges '*' \
      --destination-address-prefixes VirtualNetwork --destination-port-ranges '*'
    
    # Associate NSG to the app subnet
    az network vnet subnet update -g RG_NET --vnet-name VNET_NAME -n SUBNET_APP \
      --network-security-group NSG-APP
    

    6) Bastion (safe console access)

    # Public IP for Bastion
    az network public-ip create -g RG_NET -n pip-bastion -l LOCATION --sku Standard --zone 1 2 3
    
    # Bastion host
    az network bastion create -g RG_NET -n bas-VNET_NAME -l LOCATION \
      --public-ip-address pip-bastion --vnet-name VNET_NAME
    

    7) VM (managed identity, no public IP, Entra login)

    Windows example:

    # NIC (no public IP)
    az network nic create -g RG_VM -n nic-VM_NAME \
      --vnet-name VNET_NAME --subnet SUBNET_APP
    
    # VM
    az vm create -g RG_VM -n VM_NAME \
      --image Win2022Datacenter --size VM_SIZE \
      --nics nic-VM_NAME --assign-identity \
      --admin-username "localadmin" --admin-password "GENERATE-STRONG-PASSWORD" \
      --enable-agent true --os-disk-size-gb 128
    
    # Enable AAD login extension (Windows)
    az vm extension set -g RG_VM -n AADLoginForWindows --publisher Microsoft.Azure.ActiveDirectory \
      --vm-name VM_NAME
    
    # Grant Entra groups the VM login roles
    VM_ID=$(az vm show -g RG_VM -n VM_NAME --query id -o tsv)
    az role assignment create --assignee-object-id ADMIN_GROUP_OBJECTID \
      --role "Virtual Machine Administrator Login" --scope $VM_ID
    

    Linux example (SSH keys + AAD login):

    az vm create -g RG_VM -n VM_NAME \
      --image Ubuntu2204 --size VM_SIZE \
      --nics nic-VM_NAME --assign-identity \
      --authentication-type ssh --ssh-key-values ~/.ssh/id_rsa.pub
    
    # Enable AAD SSH login (Linux)
    az vm extension set -g RG_VM -n AADSSHLoginForLinux --publisher Microsoft.Azure.ActiveDirectory \
      --vm-name VM_NAME
    
    # RBAC for login
    az role assignment create --assignee-object-id ADMIN_GROUP_OBJECTID \
      --role "Virtual Machine Administrator Login" --scope $VM_ID
    

    Accessing the VM (no public IP):

    • Portal → Resource → ConnectBastion → Open session (RDP for Windows, SSH for Linux).
    • Optionally enable Just-In-Time in Defender for Cloud; keep NSG closed otherwise.

    8) Backup, Patching, and Keys

    # Recovery Services vault + VM backup
    az backup vault create -g RG_CORE -n rsv-core -l LOCATION
    az backup protection enable-for-vm -g RG_CORE -v rsv-core --vm VM_NAME --policy-name "DefaultPolicy"
    
    # VM guest patching (Update Manager) – enable in portal for the RG/VM
    
    • Store secrets/keys in Azure Key Vault; use managed identity from the VM to fetch secrets.
    • Use Server-side encryption (SSE) with platform-managed keys (default) or customer-managed keys (CMK) via Key Vault if required.

    9) Monitoring (Guest + Platform)

    # Enable VM Insights / Diagnostics to LAW
    az monitor diagnostic-settings create \
      --name "vm-to-law" \
      --resource $VM_ID --workspace $LAW_ID \
      --metrics '[{"category":"AllMetrics","enabled":true}]' \
      --logs '[{"categoryGroup":"allLogs","enabled":true}]'
    

    10) Cost Guardrails

    • Create a Budget in Cost Management with email alerts at 50/80/100%.
    • Consider Reservations and Auto-shutdown on dev/test VMs.

    11) Access Patterns to Prefer

    • Bastion or Private endpoints; avoid public RDP/SSH.
    • Entra sign-in to VMs with RBAC (Virtual Machine User/Administrator Login).
    • PIM + MFA for privileged roles.
    • JIT for any temporary inbound need.

    Minimal Tear-down (lab)

    # Danger: deletes resources
    az group delete -n RG_VM  -y
    az group delete -n RG_NET -y
    az group delete -n RG_CORE -y
    

    Notes & Deviations

    • For domain-join scenarios, use Entra ID DS (managed domain) or a full AD DS in Azure; keep DCs on a separate subnet with restricted NSG.
    • For Intune/MDM of servers, consider Azure Arc + Defender for Endpoint.
    • Replace all placeholders and remove screenshots/IDs before publishing externally.

    For more info:
    Microsoft Entra ID overview/service description. Microsoft Learn
    • Connect to a VM using Azure Bastion (private IP). Microsoft Learn
    • Private Endpoint / Private Link overview & quickstart. Microsoft Learn+1


    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Hot-cloning a Running Windows 11 VM in vSphere (Forensic, Redacted Runbook)

    This guide covers hot cloning a Windows 11 VM in vSphere with PowerCLI

    Goal. Create a new Windows 11 jump VM (WIN11-Jumpbox-6) by cloning a running source (WIN11-Jumpbox-2) in vCenter—without interrupting the source—and bring the clone up with a fresh identity (Sysprep), correct name, and domain join.

    Applies to. vCenter/vSphere with vSAN (or any datastore), Windows 11 guest, PowerCLI.

    Redaction note: All names below are placeholders. Replace the ALL_CAPS parts with local values.
    vCenter: VCENTER.FQDN
    Source VM: WIN11-Jumpbox-2
    New VM: WIN11-Jumpbox-6
    Target ESXi host: esxi-03.example.local
    Datastore: vsanDatastore
    Domain (optional): corp.local
    Join account: corp.local\joinaccount


    Constraints & safety

    • No source outage. Clone while the source is powered on (vCenter snapshots and clones from it).
    • Fresh identity. Use guest customization (Sysprep) so the clone receives a new SID and hostname.
    • Parameter sets. When cloning with -VM, avoid -NetworkName/-NumCPU/-MemoryGB in the same New-VM call; set those after the clone boots.
    • VMware Tools must be running in the guest for customization to apply.

    Pre-flight checks (30–60 seconds)

    # Connect
    Connect-VIServer VCENTER.FQDN
    
    # Capacity snapshot (optional)
    Get-VMHost | Select Name,
     @{N="CPU MHz Used";E={$_.CpuUsageMhz}},
     @{N="CPU MHz Total";E={$_.CpuTotalMhz}},
     @{N="Mem GB Used";E={[math]::Round($_.MemoryUsageGB,2)}},
     @{N="Mem GB Total";E={[math]::Round($_.MemoryTotalGB,2)}}
    
    Get-Datastore -Name "vsanDatastore" | Select Name,Type,State,
     @{N="CapacityGB";E={[math]::Round($_.CapacityGB,2)}},
     @{N="FreeGB";E={[math]::Round($_.FreeSpaceGB,2)}},
     @{N="Free%";E={[math]::Round(($_.FreeSpaceGB/$_.CapacityGB)*100,2)}}
    

    Rule of thumb: keep vSAN Free% ≥ 20–25% to avoid slack-space pressure during resync/rebuild.


    Method A — Clone with one-time guest customization (recommended)

    This path Syspreps the clone, renames it, and (optionally) joins the domain. It also avoids the PowerShell reserved variable $host (use $targetHost).

    # -------- Vars --------
    $srcName        = "WIN11-Jumpbox-2"
    $newName        = "WIN11-Jumpbox-6"
    $targetHostName = "esxi-03.example.local"
    $dsName         = "vsanDatastore"
    $domainFqdn     = "corp.local"                 # leave blank if no domain join
    $joinUser       = "corp.local\joinaccount"     # account allowed to join computers
    
    # -------- Objects --------
    $src        = Get-VM -Name $srcName -ErrorAction Stop
    $targetHost = Get-VMHost -Name $targetHostName -ErrorAction Stop
    $ds         = Get-Datastore -Name $dsName -ErrorAction Stop
    $pg         = ($src | Get-NetworkAdapter | Select-Object -First 1).NetworkName
    
    # -------- One-time Windows customization spec (NonPersistent) --------
    $specName = "TMP-Join-Redacted"
    $existing = Get-OSCustomizationSpec -Name $specName -ErrorAction SilentlyContinue
    if ($existing) { Remove-OSCustomizationSpec -OSCustomizationSpec $existing -Confirm:$false }
    
    # If domain join is desired
    $spec = if ($domainFqdn) {
      $joinCred = Get-Credential -UserName $joinUser -Message "Password for $joinUser"
      New-OSCustomizationSpec -Name $specName -Type NonPersistent `
        -OSType Windows -NamingScheme VMName -FullName "IT" -OrgName "Redacted" `
        -Domain $domainFqdn -DomainCredentials $joinCred
    }
    else {
      New-OSCustomizationSpec -Name $specName -Type NonPersistent `
        -OSType Windows -NamingScheme VMName -FullName "IT" -OrgName "Redacted"
    }
    
    # NIC(s) -> DHCP (switch to static if needed)
    Get-OSCustomizationNicMapping -OSCustomizationSpec $spec |
      ForEach-Object { Set-OSCustomizationNicMapping -OSCustomizationNicMapping $_ -IpMode UseDhcp | Out-Null }
    
    # -------- Clone (do NOT pass -NetworkName/-NumCPU/-MemoryGB here) --------
    $newVM = New-VM -Name $newName -VM $src -VMHost $targetHost -Datastore $ds -OSCustomizationSpec $spec
    
    Start-VM $newVM
    $newVM | Wait-Tools -TimeoutSeconds 900
    
    # -------- Post-boot tuning --------
    Set-VM -VM $newVM -NumCPU 4 -MemoryGB 8 -Confirm:$false
    Get-NetworkAdapter -VM $newVM | Set-NetworkAdapter -NetworkName $pg -Connected:$true -Confirm:$false
    

    Why this works (and common pitfalls)

    • Reserved variable. Cannot overwrite variable Host… appears when assigning to $host (PowerShell reserved). Use $targetHost.
    • Missing spec. Get-OSCustomizationSpec … ObjectNotFound indicates the named spec didn’t exist. The runbook creates a NonPersistent spec on the fly.
    • Ambiguous parameter set. New-VM : Parameter set cannot be resolved… occurs when mixing clone parameter -VM with -NetworkName/-NumCPU/-MemoryGB. Clone first, then adjust CPU/RAM/NIC after boot.

    Method B — Fallback: clone now, join inside the guest

    If guest customization is blocked (e.g., Tools not running, limited join rights), clone without customization, then rename/join inside the guest.

    # Clone without customization
    $src        = Get-VM -Name "WIN11-Jumpbox-2"
    $targetHost = Get-VMHost -Name "esxi-03.example.local"
    $ds         = Get-Datastore -Name "vsanDatastore"
    $newName    = "WIN11-Jumpbox-6"
    
    $newVM = New-VM -Name $newName -VM $src -VMHost $targetHost -Datastore $ds
    Start-VM $newVM
    $newVM | Wait-Tools -TimeoutSeconds 900
    
    # Rename to match VM name (inside guest)
    $localAdminCred = Get-Credential -Message "Local Administrator on the cloned VM"
    Invoke-VMScript -VM $newVM -GuestCredential $localAdminCred -ScriptType Powershell -ScriptText `
     'Rename-Computer -NewName "WIN11-Jumpbox-6" -Force; Restart-Computer -Force'
    
    $newVM | Wait-Tools -TimeoutSeconds 900
    
    # Optional domain join (inside guest)
    $joinCred = Get-Credential -UserName "corp.local\joinaccount"
    Invoke-VMScript -VM $newVM -GuestCredential $localAdminCred -ScriptType Powershell -ScriptText `
     'Add-Computer -DomainName "corp.local" -Credential (New-Object System.Management.Automation.PSCredential("corp.local\joinaccount",(Read-Host -AsSecureString))) -Force -Restart'
    

    Verification (quick, non-invasive)

    # Where did it land? (host, datastore, portgroup)
    Get-VM -Name "WIN11-Jumpbox-6" | Select Name,PowerState,
     @{N="Host";E={$_.VMHost.Name}},
     @{N="Datastore(s)";E={($_ | Get-Datastore).Name -join ", "}},
     @{N="PortGroup";E={(Get-NetworkAdapter -VM $_ | Select -First 1).NetworkName}}
    
    # Optional: ensure VM files are on the intended datastore
    Get-VM -Name "WIN11-Jumpbox-6" | Get-HardDisk | Select Parent,Name,FileName
    

    Post-build hygiene

    • RDP enabled; restricted to an AD group.
    • Endpoint agents (AV/EDR/RMM) register as a new device (fresh identity).
    • Patching applied; baseline GPO/Intune policies targeted; backup/monitoring added.

    Forensic addendum: errors & remediation

    • Cannot overwrite variable Host…
      Cause: attempted $host = Get-VMHost … (PowerShell reserved).
      Fix: rename the variable to $targetHost.
    • Get-OSCustomizationSpec … ObjectNotFound
      Cause: referenced a non-existent customization spec.
      Fix: create a NonPersistent spec in-line.
    • New-VM … Parameter set cannot be resolved…
      Cause: mixed -VM (clone) with create-new switches.
      Fix: keep New-VM to the clone parameter set; tune CPU/RAM/NIC after boot.

    Security & privacy guardrails

    • No real hostnames, domains, IPs, or identifying screenshots in public artifacts.
    • Least-privilege join accounts or pre-staged computer objects in AD.
    • When publishing logs, hash or redact VM names and datastore paths.

    Summary

    Hot-cloning a Windows 11 VM in vSphere is reliable for a jump host when the process (1) allows vCenter to snapshot and clone a powered-on source, (2) applies Sysprep guest customization for a clean identity, and (3) keeps New-VM to a single parameter set. The runbook above is deterministic, quiet, and free of sensitive fingerprints.

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • 🌥️ The Cloud Above Us

    PIMCO (Newport Beach HQ, CA) 🌍 — Global financial services supporting regions in NA, EMEA, APAC.
    Church (Riverton Office Building, UT) ⛪ — Worldwide infrastructure with 200k employees and over 80k missionaries.
    Monster Energy (Corona HQ, CA) ⚡ — Global enterprise IT operations across NA, EMEA, APAC.
    City National Bank (Downtown LA, CA) 🏙️ — U.S. banking systems at scale.

    A journey across scales: national (CNB), global (PIMCO & Monster Energy), and worldwide (The Church).


    Every IT career tells a story, and mine has moved through three different scales of impact:

    Company-Level Foundations → At PayForward, I migrated an entire OnPrem environment into AWS. That meant setting up VPCs, building HA Exchange clusters with load balancers, and proving the power of cloud for a fast-moving startup.

    Regional / Global Scale → At Monster Energy and PIMCO, the work stretched across North America, EMEA, and APAC. The systems never slept. VMware clusters and M365 tenants had to function as one, even though users were scattered across time zones and continents.

    Worldwide Reach → At the Church, the scale expanded beyond regions. Over 200,000 employees and over 80,000 missionaries, connected by systems that had to reach every corner of the globe, demanded both technical precision and spiritual responsibility.

    This journey shows that the “cloud above us” isn’t just AWS, Azure, or GCP — it’s the ability to design, secure, and sustain systems at every possible scale.

    A colleague once told me: “Automate, or eliminate.” In IT, that isn’t just a clever saying — it’s survival. At the scale of hundreds or even thousands of VMs, EC2 instances, or mailboxes, doing things manually is not just unrealistic — it’s risky. What automation can finish in under 10 minutes might take days or weeks by hand, and even then would be prone to errors.

    That’s why Python, PowerShell, Bash, and automation frameworks became part of my daily toolkit. Not to flaunt, but because without automation, no single engineer could handle the demands of environments as large as PIMCO, Monster Energy, or the Church.


    Snippet 1: AWS (My PayForward Days)

    import boto3
    
    # Connect to AWS S3
    s3 = boto3.client('s3')
    
    # List buckets
    buckets = s3.list_buckets()
    print("Your AWS buckets:")
    for bucket in buckets['Buckets']:
        print(f"  {bucket['Name']}")
    

    From racks of servers to a few lines of Python—that’s the power of AWS.

    Snippet 2: PowerShell + Azure (My Church Years, CNB)

    Connect-AzAccount
    Get-AzResourceGroup | Select ResourceGroupName, Location
    

    One line, and you can see every Azure resource group spread across the world. A task that once required data center visits and clipboards is now just a command away.

    Snippet 3: PHP + GCP (Expanding Horizons)

    use Google\Cloud\Storage\StorageClient;
    
    $storage = new StorageClient([
        'keyFilePath' => 'my-service-account.json'
    ]);
    
    $buckets = $storage->buckets();
    
    foreach ($buckets as $bucket) {
        echo $bucket->name() . PHP_EOL;
    }
    

    Snippet 4: VMware + M365 (Monster Energy, PIMCO, and Beyond)

    # Connect to vCenter and list VMs across data centers
    Connect-VIServer -Server vcenter.global.company.com -User admin -Password pass
    Get-VM | Select Name, PowerState, VMHost, Folder
    
    # Quick check of licensed users in M365 (global tenants)
    Connect-MgGraph -Scopes "User.Read.All"
    Get-MgUser -All -Property DisplayName, UserPrincipalName, UsageLocation |
        Group-Object UsageLocation |
        Select Name, Count
    

    One script, and suddenly you’re seeing footprints of users spread across the globe — NA, EMEA, APAC, or even worldwide. That’s the reality of modern IT infrastructure.


    The “cloud above us” is both a literal technology — AWS, Azure, and GCP that I’ve worked across — and a metaphor. It represents resilience, scalability, and unseen support. Just as automation carries workloads we could never handle by hand, life has storms we cannot carry alone.

    From startups making their first move to the cloud, to global financial institutions, to worldwide organizations with hundreds of thousands of users, the lesson is the same: we are not meant to fight every battle manually.

    We are given tools, teammates, and even unseen strength from above to keep moving forward. The same way a script can manage thousands of servers or accounts without error, trust and preparation help us navigate the storms of life with less fear.

    ☁️ Above every storm, there’s always a cloud carrying potential. And above that cloud, always light waiting to break through.

    Before my cloud journey, I also spent nine years in forensic IT supporting law enforcement — a grounding reminder that technology isn’t only about systems and scale, but about accountability and truth.

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Secure Automation with PowerShell SecretManagement: Simplifying Credential Management for IT Pros

    Introduction:
    In enterprise environments, automation is only as secure as the credentials it uses. Hardcoding passwords into scripts is a security disaster waiting to happen. Enter PowerShell SecretManagement — a cross-platform module that allows IT professionals to store, retrieve, and manage credentials securely while keeping scripts clean, compliant, and automation-ready.

    Description & Guide:

    1. What is SecretManagement?
      The SecretManagement module provides a unified way to work with secrets across different vaults like Windows Credential Manager, Azure Key Vault, KeePass, or HashiCorp Vault — without locking you into a single storage provider.
    2. Installing the Modules
    Install-Module Microsoft.PowerShell.SecretManagement
    Install-Module Microsoft.PowerShell.SecretStore
    

    3. Registering a Vault
    For a local secure store:

    Register-SecretVault -Name LocalVault -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault
    

    4. Adding a Secret

    Set-Secret -Name MySQLAdmin -Secret (Get-Credential)
    

    5. Retrieving a Secret in Scripts

    $cred = Get-Secret -Name MySQLAdmin -AsCredential
    Invoke-Sqlcmd -ServerInstance "SQL01" -Username $cred.UserName -Password $cred.GetNetworkCredential().Password
    

    6. Why This Matters

    • Eliminates plaintext passwords in scripts
    • Centralizes secret management for easier updates
    • Works seamlessly with CI/CD pipelines and scheduled tasks

    Conclusion:
    Security and automation don’t have to be enemies. With PowerShell SecretManagement, you can protect sensitive credentials without sacrificing automation speed or flexibility. For IT pros managing hybrid environments, this module is a must-have in your PowerShell toolbox.

    If you’d like to go beyond this post and see what Microsoft officially recommends, here are my go-to resources:

    Microsoft Docs – SecretManagement Overview

    Microsoft Docs – SecretStore vault extension

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Migrating Azure AD Scripts to Microsoft Graph PowerShell: A Practical Guide for IT Administrators

    Introduction
    The AzureAD PowerShell module has served IT administrators for years, but it’s now officially deprecated in favor of the Microsoft Graph PowerShell SDK. While the change may feel like another “cloud shuffle,” migrating your scripts is not just a compliance move — it’s your ticket to a more powerful, secure, and future-proof automation toolkit. In this post, I’ll walk you through the essentials of converting your Azure AD scripts to Microsoft Graph, with clear side-by-side examples.

    Why Migrate?

    • Future Support: Microsoft Graph is actively developed; AzureAD is on life support.
    • Unified Endpoint: Graph covers Azure AD, Intune, Exchange Online, Teams, and more in one API.
    • Security: Better authentication methods, including secure app registrations and least-privilege scopes.

    Step 1 – Install Microsoft Graph PowerShell

    # Install the module
    Install-Module Microsoft.Graph -Scope CurrentUser
    
    # Update if already installed
    Update-Module Microsoft.Graph
    
    # Connect with interactive sign-in
    Connect-MgGraph -Scopes "User.Read.All", "Group.ReadWrite.All"
    
    # Confirm connection
    Get-MgContext
    

    Step 2 – Side-by-Side Script Conversion

    Example: Get all Azure AD users
    AzureAD Module:

    Connect-AzureAD
    Get-AzureADUser -All $true
    

    Microsoft Graph:

    Connect-MgGraph -Scopes "User.Read.All"
    Get-MgUser -All
    

    Example: Get members of a group
    AzureAD Module:

    $groupId = (Get-AzureADGroup -SearchString "Sales Team").ObjectId
    Get-AzureADGroupMember -ObjectId $groupId
    

    Microsoft Graph:

    $groupId = (Get-MgGroup -Filter "displayName eq 'Sales Team'").Id
    Get-MgGroupMember -GroupId $groupId
    

    Example: Create a new group
    AzureAD Module:

    New-AzureADGroup -DisplayName "Project A Team" -MailEnabled $false -SecurityEnabled $true -MailNickname "ProjectATeam"
    

    Microsoft Graph:

    New-MgGroup -DisplayName "Project A Team" `
        -MailEnabled:$false `
        -SecurityEnabled `
        -MailNickname "ProjectATeam"
    

    Step 3 – Updating Authentication
    With Microsoft Graph, you can fine-tune permissions at sign-in instead of granting broad directory access:

    Connect-MgGraph -Scopes "User.ReadWrite.All", "Group.ReadWrite.All"
    

    Only request the scopes you actually need — this aligns with least privilege best practices.

    Step 4 – Testing and Verification
    Before replacing scripts in production, run them in a test tenant or a non-production environment. Compare outputs from AzureAD and Graph to ensure parity.

    Conclusion
    Migrating from AzureAD to Microsoft Graph PowerShell is more than just a rewrite — it’s a forward-looking investment. Once you adapt, you’ll unlock richer APIs, cross-service automation, and security benefits that AzureAD simply can’t match. My advice? Start small: pick one script, convert it, and test until you’re confident. Once you see the gains, the rest will follow naturally.

    For official guidance and best practices from Microsoft, check out these resources:

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

  • Before the boarding call

    Gate A21, Salt Lake City Airport — Just before takeoff to my destination, one last act of service: a restored VM and an unlocked account.

    Right before boarding at Gate A21 for a flight to the Big Apple, I found myself once again doing what I do best—helping quietly behind the scenes.
    With minutes to spare, I had just unlocked a user account and ensured a critical VM was restored.
    Even in transit, purpose doesn’t clock out. Some of the best service happens when no one sees it.

    Before Takeoff

    Poem by Jet Mariano
    A final ping, a task complete,
    Between the rows of outbound seats.
    Not all flights take off with wings—
    Some soar when hearts do faithful things.

    A gate, a call, the engine’s song,
    But even then, I can’t be gone.
    For hands that serve and souls that stay,
    Are never truly far away.

    Some journeys begin long before wheels lift from the ground. On that late July afternoon, it wasn’t just about reaching a destination—it was about leaving no soul behind. Service, even from Gate A21, has a way of grounding us in purpose.

    © 2012–2025 Jet Mariano. All rights reserved.
    For usage terms, please see the Legal Disclaimer.

error: Content is protected !!