<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Technical Library on Alfred van Ster</title><link>https://avanster.tech/categories/technical-library/</link><description>Recent content in Technical Library on Alfred van Ster</description><generator>Hugo -- 0.161.1</generator><language>en-us</language><lastBuildDate>Sun, 03 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://avanster.tech/categories/technical-library/index.xml" rel="self" type="application/rss+xml"/><item><title>Cloud Identity: Entra ID Stale Guest Reaper</title><link>https://avanster.tech/library/script-entra-guest-reaper/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://avanster.tech/library/script-entra-guest-reaper/</guid><description>&lt;ol&gt;
&lt;li&gt;The Workflow&lt;/li&gt;
&lt;li&gt;The Implementation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;External guest accounts are frequently provisioned for vendors but rarely decommissioned, creating a lingering attack surface. This script uses the Microsoft Graph API to identify and remove guest accounts that have been inactive for over 90 days.&lt;/p&gt;
&lt;h3 id="1-the-workflow"&gt;1. The Workflow&lt;/h3&gt;
&lt;p&gt;The script performs the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Authentication:&lt;/strong&gt; Uses App Registration credentials to silently authenticate to MS Graph.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filtering:&lt;/strong&gt; Queries all users where &lt;code&gt;userType&lt;/code&gt; equals &amp;ldquo;Guest&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evaluation:&lt;/strong&gt; Checks the &lt;code&gt;signInActivity&lt;/code&gt; property against a 90-day threshold.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Purge:&lt;/strong&gt; Identifies stale guest accounts to close the security loop.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-the-implementation"&gt;2. The Implementation&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ThresholdDate = (Get-Date).AddDays(&lt;span style="color:#ae81ff"&gt;-90&lt;/span&gt;).ToString(&lt;span style="color:#e6db74"&gt;&amp;#34;yyyy-MM-ddTHH:mm:ssZ&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$Url = &lt;span style="color:#e6db74"&gt;&amp;#34;[https://graph.microsoft.com/beta/users](https://graph.microsoft.com/beta/users)?&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;`$&lt;/span&gt;&lt;span style="color:#e6db74"&gt;filter=userType eq &amp;#39;Guest&amp;#39;&amp;amp;&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;`$&lt;/span&gt;&lt;span style="color:#e6db74"&gt;select=displayName,userPrincipalName,signInActivity&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$Guests = Invoke-RestMethod -Uri $Url -Method Get -Headers $Headers
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; ($Guest &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; $Guests.value) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; ([&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;]::IsNullOrEmpty($Guest.signInActivity.lastSignInDateTime) &lt;span style="color:#f92672"&gt;-or&lt;/span&gt; ($Guest.signInActivity.lastSignInDateTime &lt;span style="color:#f92672"&gt;-lt&lt;/span&gt; $ThresholdDate)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Write-Host &lt;span style="color:#e6db74"&gt;&amp;#34;Stale Guest Found: &lt;/span&gt;$($Guest.displayName)&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Endpoint Security: BitLocker Key Escrow to IT Glue</title><link>https://avanster.tech/library/script-bitlocker-itglue/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://avanster.tech/library/script-bitlocker-itglue/</guid><description>&lt;ol&gt;
&lt;li&gt;The Workflow&lt;/li&gt;
&lt;li&gt;The Implementation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Relying on manual documentation for BitLocker recovery keys often results in locked data when an endpoint fails. This script automates the escrow process, pulling the active numeric password from the local disk and pushing it directly into an IT Glue configuration record via their REST API.&lt;/p&gt;
&lt;h3 id="1-the-workflow"&gt;1. The Workflow&lt;/h3&gt;
&lt;p&gt;The script performs the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Extraction:&lt;/strong&gt; Queries the WMI namespace for the active, 48-digit BitLocker Numeric Password on the OS drive.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authentication:&lt;/strong&gt; Connects to the IT Glue API using a secure organizational API key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Payload Delivery:&lt;/strong&gt; Matches the local Hostname to the IT Glue Configuration ID and PATCHes the &amp;ldquo;BitLocker Key&amp;rdquo; custom field with the extracted key.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-the-implementation"&gt;2. The Implementation&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# IT Glue API Configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ITGKey = &lt;span style="color:#e6db74"&gt;&amp;#34;YOUR_ITGLUE_API_KEY&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ITGBaseUrl = &lt;span style="color:#e6db74"&gt;&amp;#34;[https://api.itglue.com](https://api.itglue.com)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$Header = @{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;x-api-key&amp;#34;&lt;/span&gt; = $ITGKey
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;application/vnd.api+json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$BitLocker = Get-BitLockerVolume -MountPoint $env:SystemDrive
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$RecoveryKey = ($BitLocker.KeyProtector | Where-Object { $_.KeyProtectorType &lt;span style="color:#f92672"&gt;-eq&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;RecoveryPassword&amp;#39;&lt;/span&gt; }).RecoveryPassword
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$SearchUri = &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$ITGBaseUrl&lt;span style="color:#e6db74"&gt;/configurations?filter[name]=&lt;/span&gt;$env:COMPUTERNAME&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ConfigRecord = Invoke-RestMethod -Uri $SearchUri -Method Get -Headers $Header
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; ($ConfigRecord.data) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $Payload = @{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data = @{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type = &lt;span style="color:#e6db74"&gt;&amp;#34;configurations&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; attributes = @{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;custom-fields&amp;#34;&lt;/span&gt; = @{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;bitlocker-recovery-key&amp;#34;&lt;/span&gt; = $RecoveryKey
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } | ConvertTo-Json -Depth &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Invoke-RestMethod -Uri &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$ITGBaseUrl&lt;span style="color:#e6db74"&gt;/configurations/&lt;/span&gt;$($ConfigRecord.data[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;].id)&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; -Method Patch -Headers $Header -Body $Payload
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Infrastructure: Hyper-V Orphaned Snapshot Monitor</title><link>https://avanster.tech/library/script-hyperv-snapshot-monitor/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://avanster.tech/library/script-hyperv-snapshot-monitor/</guid><description>&lt;ol&gt;
&lt;li&gt;The Workflow&lt;/li&gt;
&lt;li&gt;The Implementation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A common L3 infrastructure issue is technicians taking a VM checkpoint before an update and forgetting to delete it. This script scans for stale snapshots and fires an alert to a Microsoft Teams or Slack webhook.&lt;/p&gt;
&lt;h3 id="1-the-workflow"&gt;1. The Workflow&lt;/h3&gt;
&lt;p&gt;The script performs the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Discovery:&lt;/strong&gt; Scans the local Hyper-V host for all active checkpoints.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evaluation:&lt;/strong&gt; Compares the creation date of the snapshot against a 7-day threshold.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alerting:&lt;/strong&gt; If stale snapshots exist, it constructs a JSON payload and POSTs it to a designated webhook URL.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-the-implementation"&gt;2. The Implementation&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ThresholdDate = (Get-Date).AddDays(&lt;span style="color:#ae81ff"&gt;-7&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$StaleSnapshots = Get-VM | Get-VMSnapshot | Where-Object { $_.CreationTime &lt;span style="color:#f92672"&gt;-lt&lt;/span&gt; $ThresholdDate }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; ($StaleSnapshots) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $Payload = @{ text = &lt;span style="color:#e6db74"&gt;&amp;#34;⚠️ Orphaned Snapshots Detected on &lt;/span&gt;$env:COMPUTERNAME&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; } | ConvertTo-Json
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Invoke-RestMethod -Uri &lt;span style="color:#e6db74"&gt;&amp;#34;[https://your-webhook.url](https://your-webhook.url)&amp;#34;&lt;/span&gt; -Method Post -Body $Payload -ContentType &lt;span style="color:#e6db74"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Infrastructure: Windows Server DNS Stale Record Scavenger</title><link>https://avanster.tech/library/script-dns-stale-scavenger/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://avanster.tech/library/script-dns-stale-scavenger/</guid><description>&lt;ol&gt;
&lt;li&gt;The Workflow&lt;/li&gt;
&lt;li&gt;The Implementation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In dynamic DHCP environments, DNS zones become polluted with stale A-records. This script provides a surgical, auditable way to identify and purge stale DNS records older than a defined threshold.&lt;/p&gt;
&lt;h3 id="1-the-workflow"&gt;1. The Workflow&lt;/h3&gt;
&lt;p&gt;The script performs the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Targeting:&lt;/strong&gt; Selects a specific internal DNS zone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evaluation:&lt;/strong&gt; Pulls all A records and compares the Timestamp against a 14-day threshold.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution:&lt;/strong&gt; Exports a CSV log of the stale records before actively removing them from the server.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-the-implementation"&gt;2. The Implementation&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ZoneName = &lt;span style="color:#e6db74"&gt;&amp;#34;internal.avanster.tech&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ThresholdDate = (Get-Date).AddDays(&lt;span style="color:#ae81ff"&gt;-14&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$Records = Get-DnsServerResourceRecord -ZoneName $ZoneName -RRType &lt;span style="color:#e6db74"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; ($Record &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; $Records) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; ($Record.Timestamp &lt;span style="color:#f92672"&gt;-ne&lt;/span&gt; $null &lt;span style="color:#f92672"&gt;-and&lt;/span&gt; $Record.Timestamp &lt;span style="color:#f92672"&gt;-lt&lt;/span&gt; $ThresholdDate) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Remove-DnsServerResourceRecord -ZoneName $ZoneName -InputObject $Record -Force
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Write-Host &lt;span style="color:#e6db74"&gt;&amp;#34;[-] Removed: &lt;/span&gt;$($Record.HostName)&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>M365 Security: Unauthorized Mailbox Forwarding Auditor</title><link>https://avanster.tech/library/script-exchange-forwarding-auditor/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://avanster.tech/library/script-exchange-forwarding-auditor/</guid><description>&lt;ol&gt;
&lt;li&gt;The Workflow&lt;/li&gt;
&lt;li&gt;The Implementation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A classic BEC tactic involves configuring an inbox rule to forward emails to an external address. This script audits an entire Exchange Online tenant for any mailboxes with active forwarding rules to external domains.&lt;/p&gt;
&lt;h3 id="1-the-workflow"&gt;1. The Workflow&lt;/h3&gt;
&lt;p&gt;The script performs the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Connection:&lt;/strong&gt; Authenticates to Exchange Online via module parameters.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditing:&lt;/strong&gt; Iterates through all user mailboxes checking forwarding properties.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evaluation:&lt;/strong&gt; Compares the forwarding destination against the tenant&amp;rsquo;s accepted domains.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alerting:&lt;/strong&gt; Outputs a high-priority warning if external exfiltration is detected.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-the-implementation"&gt;2. The Implementation&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connect-ExchangeOnline -ShowBanner:$false
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$AcceptedDomains = (Get-AcceptedDomain).Name
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; ($Mailbox &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; (Get-Mailbox -ResultSize Unlimited)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; ($Mailbox.ForwardingSmtpAddress) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $ForwardDestination = $Mailbox.ForwardingSmtpAddress.Replace(&lt;span style="color:#e6db74"&gt;&amp;#34;smtp:&amp;#34;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; ($ForwardDestination &lt;span style="color:#f92672"&gt;-notmatch&lt;/span&gt; ($AcceptedDomains -join &lt;span style="color:#e6db74"&gt;&amp;#34;|&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Write-Host &lt;span style="color:#e6db74"&gt;&amp;#34;⚠️ EXTERNAL FORWARD: &lt;/span&gt;$($Mailbox.UserPrincipalName)&lt;span style="color:#e6db74"&gt; -&amp;gt; &lt;/span&gt;$ForwardDestination&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; -ForegroundColor Red
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Networking: Cisco Meraki Automated Configuration Backup</title><link>https://avanster.tech/library/script-meraki-config-backup/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://avanster.tech/library/script-meraki-config-backup/</guid><description>&lt;ol&gt;
&lt;li&gt;The Workflow&lt;/li&gt;
&lt;li&gt;The Implementation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Meraki dashboards are convenient, but if an admin accidentally modifies a critical firewall rule, rolling back is a nightmare. This Python script uses the Meraki Dashboard API to serialize your network configurations into a secure JSON format.&lt;/p&gt;
&lt;h3 id="1-the-workflow"&gt;1. The Workflow&lt;/h3&gt;
&lt;p&gt;The script performs the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Authentication:&lt;/strong&gt; Initializes the Meraki SDK using a read-only API key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Iteration:&lt;/strong&gt; Loops through the Organization to find all active Networks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extraction:&lt;/strong&gt; Pulls VLAN subnets, SSID configurations, and MX L3 Firewall rules.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serialization:&lt;/strong&gt; Dumps the state into a structured JSON file.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-the-implementation"&gt;2. The Implementation&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; meraki
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; json
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; datetime &lt;span style="color:#f92672"&gt;import&lt;/span&gt; datetime
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;API_KEY &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;YOUR_MERAKI_API_KEY&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ORG_ID &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;YOUR_ORG_ID&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dashboard &lt;span style="color:#f92672"&gt;=&lt;/span&gt; meraki&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DashboardAPI(API_KEY, suppress_logging&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;backup_network_config&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; networks &lt;span style="color:#f92672"&gt;=&lt;/span&gt; dashboard&lt;span style="color:#f92672"&gt;.&lt;/span&gt;organizations&lt;span style="color:#f92672"&gt;.&lt;/span&gt;getOrganizationNetworks(ORG_ID)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; backup_data &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; net &lt;span style="color:#f92672"&gt;in&lt;/span&gt; networks:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; net_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; net[&lt;span style="color:#e6db74"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; net_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; net[&lt;span style="color:#e6db74"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; backup_data[net_name] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {&lt;span style="color:#e6db74"&gt;&amp;#39;vlans&amp;#39;&lt;/span&gt;: dashboard&lt;span style="color:#f92672"&gt;.&lt;/span&gt;appliance&lt;span style="color:#f92672"&gt;.&lt;/span&gt;getNetworkApplianceVlans(net_id)}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; filename &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;meraki_backup_&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;datetime&lt;span style="color:#f92672"&gt;.&lt;/span&gt;now()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;strftime(&lt;span style="color:#e6db74"&gt;&amp;#39;%Y%m&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%d&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt;)&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;.json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; open(filename, &lt;span style="color:#e6db74"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; f:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; json&lt;span style="color:#f92672"&gt;.&lt;/span&gt;dump(backup_data, f, indent&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item></channel></rss>