<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[</InpuTech>]]></title><description><![CDATA[</InpuTech>]]></description><link>https://blog.marconotarrigo.tech</link><generator>RSS for Node</generator><lastBuildDate>Wed, 08 Apr 2026 14:34:47 GMT</lastBuildDate><atom:link href="https://blog.marconotarrigo.tech/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Automating Microsoft Entra ID PIM Role Activation with PowerShell and Microsoft Graph]]></title><description><![CDATA[Managing Microsoft Entra ID roles through Privileged Identity Management (PIM) can be repetitive and time-consuming, especially for administrators who frequently activate roles. To streamline this process while maintaining robust security, I develope...]]></description><link>https://blog.marconotarrigo.tech/automating-microsoft-entra-id-pim-role-activation-with-powershell-and-microsoft-graph</link><guid isPermaLink="true">https://blog.marconotarrigo.tech/automating-microsoft-entra-id-pim-role-activation-with-powershell-and-microsoft-graph</guid><dc:creator><![CDATA[Marco Notarrigo]]></dc:creator><pubDate>Sat, 07 Jun 2025 11:05:46 GMT</pubDate><content:encoded><![CDATA[<p>Managing Microsoft Entra ID roles through Privileged Identity Management (PIM) can be repetitive and time-consuming, especially for administrators who frequently activate roles. To streamline this process while maintaining robust security, I developed a PowerShell script that automates role activation using Microsoft Graph with full Multi-Factor Authentication (MFA) support.</p>
<p><strong>Version 2.0 Update</strong>: After receiving feedback and conducting a thorough security review, I've significantly enhanced the script with security hardening, input validation, audit logging, and adherence to the principle of least privilege.</p>
<hr />
<h2 id="heading-key-features">🔧 Key Features</h2>
<h3 id="heading-core-functionality">Core Functionality</h3>
<ul>
<li><strong>🔐 Secure Connection</strong>: Establishes connection to Microsoft Graph with minimal required permissions (only 3 scopes instead of 5)</li>
<li><strong>👤 User Context Retrieval</strong>: Automatically fetches the current user context to identify eligible roles</li>
<li><strong>⚡ Automated Activation</strong>: Streamlines the role activation process with intelligent prompts</li>
<li><strong>📋 Interactive Selection</strong>: Clear, numbered menu when multiple roles are available</li>
<li><strong>⏱️ Configurable Duration</strong>: Set activation duration from 1-8 hours (default: 4 hours)</li>
<li><strong>✅ Explicit Confirmation</strong>: Type "ACTIVATE" to confirm and prevent accidental activations</li>
</ul>
<h3 id="heading-security-enhancements-new-in-v20">Security Enhancements (New in v2.0)</h3>
<ul>
<li><strong>🛡️ Least Privilege</strong>: Reduced Microsoft Graph scopes by 40% - uses only what's essential</li>
<li><strong>✅ Input Validation</strong>: Comprehensive validation with 20-500 character requirement for justifications</li>
<li><strong>🧹 Input Sanitization</strong>: Automatic cleaning of potentially dangerous characters</li>
<li><strong>📝 Audit Logging</strong>: Persistent JSON logs for compliance and security monitoring</li>
<li><strong>🔒 Data Protection</strong>: Sensitive information (like Request IDs) never displayed to console</li>
<li><strong>🎯 Error Handling</strong>: Sanitized error messages with helpful troubleshooting guidance</li>
</ul>
<hr />
<h2 id="heading-how-it-works">💻 How It Works</h2>
<p>The script follows a secure, step-by-step process:</p>
<ol>
<li><strong>Clean State</strong>: Disconnects any existing Microsoft Graph sessions to ensure a fresh start</li>
<li><strong>Secure Authentication</strong>: Connects with only 3 essential scopes (reduced from 5):<ul>
<li><code>RoleAssignmentSchedule.ReadWrite.Directory</code> - For self-activation</li>
<li><code>Directory.Read.All</code> - For reading role information  </li>
<li><code>User.Read</code> - For user profile access</li>
</ul>
</li>
<li><strong>User Verification</strong>: Validates the current user and retrieves their context</li>
<li><strong>Role Discovery</strong>: Fetches all eligible PIM roles for the authenticated user</li>
<li><strong>Interactive Selection</strong>: If multiple roles exist, displays a clear numbered menu</li>
<li><strong>Duration Configuration</strong>: Prompts for activation duration (1-8 hours, default 4)</li>
<li><strong>Justification Input</strong>: Requires meaningful justification (minimum 20 characters) with automatic sanitization</li>
<li><strong>Confirmation Summary</strong>: Shows all details and requires typing "ACTIVATE" to proceed</li>
<li><strong>Secure Activation</strong>: Submits the activation request with MFA support</li>
<li><strong>Verification</strong>: Confirms the role is active and displays expiration time</li>
<li><strong>Audit Trail</strong>: Logs the entire transaction to local audit file for compliance</li>
</ol>
<p>All actions are logged to <code>%LOCALAPPDATA%\PIMActivation\audit.log</code> in JSON format for easy parsing and compliance reporting.</p>
<hr />
<h2 id="heading-security-improvements-what-changed">🔒 Security Improvements - What Changed</h2>
<h3 id="heading-before-v10">Before (v1.0)</h3>
<p>❌ 5 Microsoft Graph scopes (over-privileged)<br />❌ No input validation<br />❌ No audit logging<br />❌ Sensitive data exposed in console<br />❌ Generic justifications accepted<br />❌ No explicit confirmation</p>
<h3 id="heading-after-v20">After (v2.0)</h3>
<p>✅ 3 Microsoft Graph scopes (least privilege)<br />✅ Comprehensive input validation<br />✅ Full audit logging with JSON format<br />✅ Sensitive data protected<br />✅ Enforced 20-character minimum justifications<br />✅ Explicit "ACTIVATE" confirmation required</p>
<hr />
<h2 id="heading-real-world-example">📊 Real-World Example</h2>
<p>Here's what the improved experience looks like:</p>
<pre><code class="lang-powershell">===================================================================
  PIM Role Activation Script - Secured Version <span class="hljs-number">2.0</span>
===================================================================

Connecting to Microsoft Graph...
✓ Successfully connected to Microsoft Graph
✓ Connected as: admin@contoso.com
✓ Found <span class="hljs-number">3</span> eligible role(s)

===================================================================
  Available Roles <span class="hljs-keyword">for</span> Activation
===================================================================
<span class="hljs-number">1</span>. Global Reader
<span class="hljs-number">2</span>. Security Administrator
<span class="hljs-number">3</span>. User Administrator

Enter the number of the role to activate (<span class="hljs-number">1</span><span class="hljs-literal">-3</span>): <span class="hljs-number">1</span>

Selected role: Global Reader

-------------------------------------------------------------------
Duration Configuration
-------------------------------------------------------------------
Enter activation duration <span class="hljs-keyword">in</span> hours (<span class="hljs-number">1</span><span class="hljs-literal">-8</span>, default: <span class="hljs-number">4</span>): <span class="hljs-number">4</span>
✓ Duration <span class="hljs-built_in">set</span> to: <span class="hljs-number">4</span> hour(s)

-------------------------------------------------------------------
Justification (minimum <span class="hljs-number">20</span> characters)
-------------------------------------------------------------------
Enter justification <span class="hljs-keyword">for</span> role activation: Monthly security review and compliance audit
✓ Justification validated

===================================================================
  Activation Summary
===================================================================
Role:          Global Reader
Duration:      <span class="hljs-number">4</span> hour(s)
Justification: Monthly security review and compliance audit...
User:          admin@contoso.com
===================================================================

⚠️  This will activate privileged access. Ensure you have authorization.

<span class="hljs-built_in">Type</span> <span class="hljs-string">'ACTIVATE'</span> to confirm (case<span class="hljs-literal">-sensitive</span>): ACTIVATE

✅ Role activation successful!

Currently Active Roles:
  ✓ Global Reader (Expires: <span class="hljs-number">2025</span><span class="hljs-literal">-11</span><span class="hljs-literal">-02</span> <span class="hljs-number">18</span>:<span class="hljs-number">30</span>:<span class="hljs-number">00</span> UTC)

✓ Script completed successfully
===================================================================
  Audit log: C:\Users\Admin\AppData\Local\PIMActivation\audit.log
===================================================================
</code></pre>
<hr />
<h2 id="heading-access-the-script">📂 Access the Script</h2>
<p>You can find the updated script on my GitHub repository: <a target="_blank" href="https://github.com/bitnash/m365-powershell-scripts/tree/main/entra-pim-select/">Activate-EntraIDPIMRole.ps1</a></p>
<p><strong>What's Included:</strong></p>
<ul>
<li>✅ Complete PowerShell script (v2.0)</li>
<li>✅ Comprehensive README with security details</li>
<li>✅ Usage examples and troubleshooting guide</li>
<li>✅ Audit log format documentation</li>
</ul>
<p>Feel free to clone the repository, test the script in your environment, and customize it to fit your organization's security policies and compliance requirements.</p>
<hr />
<h2 id="heading-security-considerations">🛡️ Security Considerations</h2>
<p>When using this script in production:</p>
<ol>
<li><strong>Review Audit Logs Regularly</strong>: Check <code>%LOCALAPPDATA%\PIMActivation\audit.log</code> for unusual patterns</li>
<li><strong>Use Minimum Duration</strong>: Only activate roles for as long as needed</li>
<li><strong>Provide Clear Justifications</strong>: Always document why you're activating privileged access</li>
<li><strong>Test First</strong>: Run in a non-production environment before deploying</li>
<li><strong>Follow Policies</strong>: Ensure compliance with your organization's security policies</li>
<li><strong>Monitor Failed Attempts</strong>: Investigate repeated activation failures</li>
</ol>
<hr />
<h2 id="heading-feedback-and-contributions">📣 Feedback and Contributions</h2>
<p>The security improvements in v2.0 came directly from community feedback and security reviews. I'm always open to more suggestions!</p>
<p><strong>How to Contribute:</strong></p>
<ul>
<li>🐛 Found a bug? <a target="_blank" href="https://github.com/bitnash/m365-powershell-scripts/issues">Open an issue</a></li>
<li>💡 Have an enhancement idea? Submit a pull request</li>
<li>🔒 Identified a security concern? Please report it responsibly</li>
<li>📖 Want to improve documentation? Contributions welcome!</li>
</ul>
<hr />
<h2 id="heading-whats-next">🎯 What's Next?</h2>
<p>I'm considering these enhancements for future versions:</p>
<ul>
<li>🔔 <strong>Notification System</strong>: Alerts when roles are about to expire</li>
<li>📊 <strong>Reporting Dashboard</strong>: Visual analytics from audit logs</li>
<li>🤖 <strong>Scheduled Activations</strong>: Pre-approved activation windows</li>
<li>🔗 <strong>Integration with Ticketing Systems</strong>: Link to ITSM tickets</li>
</ul>
<p>Let me know in the comments which features would be most valuable for your environment!</p>
<hr />
<h2 id="heading-related-resources">📚 Related Resources</h2>
<ul>
<li><a target="_blank" href="https://learn.microsoft.com/entra/id-governance/privileged-identity-management/">Microsoft Entra PIM Documentation</a></li>
<li><a target="_blank" href="https://learn.microsoft.com/powershell/microsoftgraph/">Microsoft Graph PowerShell SDK</a></li>
<li><a target="_blank" href="https://learn.microsoft.com/entra/id-governance/privileged-identity-management/pim-deployment-plan">PIM Best Practices Guide</a></li>
</ul>
<hr />
<p><strong>Update History:</strong></p>
<ul>
<li><strong>v2.0</strong> (Nov 2025): Security hardening, audit logging, input validation</li>
<li><strong>v1.0</strong> (Initial): Basic PIM role activation automation</li>
</ul>
<hr />
<p><em>Have you implemented PIM automation in your environment? What security considerations were most important to you? Share your experience in the comments below!</em></p>
]]></content:encoded></item><item><title><![CDATA[How I Ended Up Building a Chatbot to Automate Azure — And Why It Blew My Mind 🤯]]></title><description><![CDATA[I didn’t plan to build this.Honestly, it started as an experiment. Something small.I was just playing around with chatbot frameworks, not expecting anything serious to come out of it.
But… something clicked. ⚡
Now I'm working on a system where a chat...]]></description><link>https://blog.marconotarrigo.tech/how-i-ended-up-building-a-chatbot-to-automate-azure-and-why-it-blew-my-mind</link><guid isPermaLink="true">https://blog.marconotarrigo.tech/how-i-ended-up-building-a-chatbot-to-automate-azure-and-why-it-blew-my-mind</guid><category><![CDATA[Azure]]></category><category><![CDATA[chatbot]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[AI]]></category><category><![CDATA[automation]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Cloud]]></category><dc:creator><![CDATA[Marco Notarrigo]]></dc:creator><pubDate>Sat, 05 Apr 2025 08:46:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743842460650/03581f34-6e5f-4e5d-b075-473a7fd98d80.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>I didn’t plan to build this.<br />Honestly, it started as an experiment. Something small.<br />I was just playing around with chatbot frameworks, not expecting anything serious to come out of it.</p>
<p>But… something clicked. ⚡</p>
<p>Now I'm working on a system where a chatbot can provision <strong>Azure resources</strong> on demand — and it's honestly blowing my mind how powerful and <strong>natural</strong> this can become.</p>
<hr />
<h2 id="heading-the-idea-talking-to-the-cloud">🤖 The Idea: Talking to the Cloud</h2>
<p>Imagine messaging a bot and saying:</p>
<blockquote>
<p>“Hey, I need an Ubuntu VM in West Europe. Small size.”</p>
</blockquote>
<p>And it replies:</p>
<blockquote>
<p>✅ <em>You're about to create a VM with these specs... Do you confirm?</em></p>
</blockquote>
<p>If you say “Yes,” it <strong>triggers a full DevOps pipeline</strong>, builds the infrastructure via <strong>Pulumi</strong>, and comes back with provisioning information.</p>
<p>We're not just scripting anymore.<br />We're <em>interacting</em> with infrastructure.</p>
<hr />
<h2 id="heading-the-stack-so-far">🔧 The Stack (So Far)</h2>
<p>Here’s what powers this project:</p>
<ul>
<li><p>🧠 <strong>Bot Framework + Python</strong> — handles the backend logic</p>
</li>
<li><p>🤖 <strong>OpenAI GPT</strong> — parses natural language into structured intents</p>
</li>
<li><p>📦 <strong>Pulumi</strong> — my tool of choice for Infrastructure-as-Code</p>
</li>
<li><p>🔁 <strong>Azure DevOps Pipelines</strong> — automate deployments via bot</p>
</li>
<li><p>🗂 <strong>JSON-based infra knowledge base</strong> — soon evolving into FAISS + RAG</p>
</li>
<li><p>🌐 (Optional) React-based Admin UI — for managing infrastructure state</p>
</li>
</ul>
<p>And yes, it’s fully modular — adding support for new resource types or prompts is super straightforward.</p>
<hr />
<h2 id="heading-why-i-think-this-matters">💡 Why I Think This Matters</h2>
<p>This isn't <em>just</em> about automation.<br />It’s about <strong>changing the interaction model</strong> in cloud infrastructure.</p>
<p>We're so used to clicking dashboards or writing code…<br />But what if you could just say:</p>
<blockquote>
<p>“Spin up a staging environment like last week's one, but in West US.”</p>
</blockquote>
<p>That’s the kind of experience we can aim for.</p>
<p>And even if it started as a curiosity, this project <strong>opened up new ways of thinking</strong> for me ; about DevOps, user experience, and cloud accessibility.</p>
<hr />
<h2 id="heading-what-im-working-on-next">🧪 What I’m Working On Next</h2>
<ul>
<li><p>✅ Parsing OpenAI GPT results into real infra actions</p>
</li>
<li><p>✅ Running DevOps pipelines with parameter injection</p>
</li>
<li><p>✅ Returning real-time output</p>
</li>
<li><p>🔜 Integrating FAISS to understand current cloud layout</p>
</li>
<li><p>🔜 Building a knowledge base: SKUs, pricing, compliance, etc</p>
</li>
</ul>
<p>Yes, it’s a lot. But it’s <em>fun</em>.</p>
<hr />
<h2 id="heading-final-thoughts">🌍 Final Thoughts</h2>
<p>I didn't expect this to work as well as it does.</p>
<p>This project made me realize that AI + DevOps + conversational UX is not just a cool demo — it's a serious enabler for productivity and cloud automation.</p>
<p>And the best part? I’m just getting started. 😄</p>
<hr />
<p>If you're curious, want to collaborate, or just want to see how it evolves, hit me up on <a target="_blank" href="https://www.linkedin.com/in/marco-notarrigo/">Linkedin</a> or drop a comment below! 👇</p>
]]></content:encoded></item><item><title><![CDATA[🔐 Proactive Azure App Security: How to Automate Secret Expiry Alerts with PowerShell]]></title><description><![CDATA[If you're managing Azure App Registrations across multiple environments or teams, you've probably dealt with expiring client secrets at the worst possible time; usually right before something breaks. 😅
Let’s fix that.
In this post, I’ll walk you thr...]]></description><link>https://blog.marconotarrigo.tech/proactive-azure-app-security-how-to-automate-secret-expiry-alerts-with-powershell</link><guid isPermaLink="true">https://blog.marconotarrigo.tech/proactive-azure-app-security-how-to-automate-secret-expiry-alerts-with-powershell</guid><category><![CDATA[#Azure #PowerShell #MicrosoftGraph #DevOps #CloudSecurity #Automation #AppRegistration #AzureAD #ScriptAutomation #SecOps]]></category><dc:creator><![CDATA[Marco Notarrigo]]></dc:creator><pubDate>Sun, 30 Mar 2025 15:57:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743350073252/04d0a9c5-5902-47ce-9eef-4abd10a19b0b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you're managing Azure App Registrations across multiple environments or teams, you've probably dealt with <strong>expiring client secrets</strong> at the worst possible time; usually right before something breaks. 😅</p>
<p>Let’s fix that.</p>
<p>In this post, I’ll walk you through a PowerShell solution I built to <strong>automatically audit Azure App secrets</strong> and <strong>notify you before they expire</strong>; with a clean HTML report and optional email alerts.</p>
<hr />
<h2 id="heading-why-you-need-this">🚨 Why You Need This</h2>
<p>App secrets don’t last forever (nor should they). They typically expire in 6–12 months, but there’s no built-in alerting system from Azure unless you build one yourself.</p>
<p>Miss a secret rotation? Suddenly, your production app can’t authenticate, and you’re scrambling to debug authentication failures during a deploy.</p>
<p>This script automates:</p>
<p>✅ Checking all Azure App Registrations<br />✅ Identifying secrets expiring in the next <em>N</em> days<br />✅ Generating a sortable HTML report (When running script with <em>UseLocalParameters</em>)<br />✅ Sending it via email</p>
<hr />
<h2 id="heading-how-it-works">🛠️ How It Works</h2>
<p>The script uses the <strong>Microsoft Graph API</strong> to authenticate using a service principal (client ID/secret) and fetch all application registrations. It then;</p>
<ol>
<li><p>Filters secrets that expire within a configurable number of days (default: 30)</p>
</li>
<li><p>Generates a styled HTML report (saves it locally)</p>
</li>
<li><p>Sends an email report using Microsoft Graph's <code>sendMail</code> endpoint</p>
</li>
</ol>
<p><strong>Bonus</strong>: If you include a line like <code>NotifyEmail = team@yourdomain.com</code> in the app’s Notes field, it’ll extract that automatically and show it in the report.</p>
<hr />
<h2 id="heading-getting-started">🚀 Getting Started</h2>
<h3 id="heading-prerequisites">🧾 Prerequisites</h3>
<h4 id="heading-1-app-registration-in-entra-id">1. App Registration in Entra ID</h4>
<ul>
<li><p>Create an app in <strong>Entra ID &gt; App Registrations</strong></p>
</li>
<li><p>Assign <strong>API Permissions</strong>:</p>
<ul>
<li><p><code>Application.Read.All</code></p>
</li>
<li><p><code>Directory.Read.All</code></p>
</li>
<li><p><code>Mail.Send</code></p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-2-licensed-sender-mailbox">2. Licensed Sender Mailbox</h4>
<p>Ensure your app is authorized to send mail <strong>on behalf of a licensed user</strong>, such as <code>noreply@yourdomain.com</code>.</p>
<h4 id="heading-3-installed-modules">3. Installed modules;</h4>
<p>* <code>Microsoft.Graph.Applications</code></p>
<p>* <code>Microsoft.Graph.Users</code></p>
<p>* <code>Microsoft.Graph.Authentication</code></p>
<h4 id="heading-4-optional-azure-automation-setup">4. Optional: Azure Automation Setup</h4>
<p>Store sensitive variables as <strong>Automation Variables</strong>:</p>
<ul>
<li><p><code>ClientID</code></p>
</li>
<li><p><code>ClientSecret</code></p>
</li>
<li><p><code>TenantID</code></p>
</li>
<li><p><code>WarningDays</code></p>
</li>
<li><p><code>SenderEmail</code></p>
</li>
<li><p><code>ToEmail</code></p>
</li>
</ul>
<hr />
<h2 id="heading-local-usage">💻 Local Usage</h2>
<p>Here’s how to run it locally with manual parameters:</p>
<pre><code class="lang-plaintext">powershellCopyEdit.\Notify-AppSecretExpire.ps1 `
    -ClientId "&lt;your-client-id&gt;" `
    -ClientSecret "&lt;your-client-secret&gt;" `
    -TenantId "&lt;your-tenant-id&gt;" `
    -SenderEmail "noreply@yourdomain.com" `
    -ToEmail "admin@yourdomain.com" `
    -OutputPath "C:\Reports\AppSecretsExpirationReport.html" `
    -UseLocalParameters
</code></pre>
<hr />
<h2 id="heading-azure-automation-usage">☁️ Azure Automation Usage</h2>
<p>If you're running this in an Azure Automation Account, you don’t need to pass parameters. Instead, define <strong>Automation Variables</strong> like:</p>
<ul>
<li><p><code>ClientId</code></p>
</li>
<li><p><code>ClientSecret</code></p>
</li>
<li><p><code>TenantId</code></p>
</li>
<li><p><code>SenderEmail</code></p>
</li>
<li><p><code>ToEmail</code></p>
</li>
</ul>
<p>The script auto-detects that it's running in Automation and pulls these values in.</p>
<hr />
<h2 id="heading-what-the-report-looks-like">📄 What the Report Looks Like</h2>
<p>The script outputs a responsive, clean HTML report with:</p>
<ul>
<li><p>App name and ID</p>
</li>
<li><p>Secret name (linked to the Azure portal)</p>
</li>
<li><p>Expiration date and days remaining</p>
</li>
<li><p>Optional NotifyEmail extracted from notes</p>
</li>
<li><p>Clickable column headers for sorting</p>
</li>
</ul>
<p>Here’s a preview of the UI;</p>
<blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743347626960/ec5702a0-7409-4101-9dcc-1a1ac03be32f.png" alt /></p>
</blockquote>
<p><em>(This is an example of the generated HTML report)</em></p>
<hr />
<h2 id="heading-github-repository">🔗 GitHub Repository</h2>
<p>All the code is available here:<br />👉 <a target="_blank" href="https://github.com/bitnash/m365-powershell-scripts/tree/main/secret-expiry-alert">GitHub – secret-expiry-alert</a></p>
<hr />
<h3 id="heading-new-feature-notify-application-owners-via-notifyemail">🔔 New Feature: Notify Application Owners via <code>NotifyEmail</code></h3>
<p>In the latest version of the script (last edited June 20, 2025), there’s now support for automatically notifying the responsible team for each Azure AD App Registration using a custom tag in the app’s <strong>branding properties</strong>.</p>
<h4 id="heading-how-it-works-1">✅ How It Works</h4>
<ul>
<li><p>You can specify a <strong>NotifyEmail</strong> tag in the Notes field under <strong>Branding</strong> <strong>&amp; properties</strong> of an App Registration:</p>
<pre><code class="lang-plaintext">  NotifyEmail=team-azureops@yourdomain.com
</code></pre>
</li>
<li><p>When the script runs, it searches for this tag in <a target="_blank" href="http://Application.Info"><code>Internal Notes</code></a> and extracts the email address.</p>
</li>
<li><p>If credentials (client secrets or certificates) are expiring within the configured warning window (e.g. 30 days), the script:</p>
<ul>
<li><p>Sends a <strong>HTML report to the global ToEmail</strong>.</p>
</li>
<li><p>Sends a <strong>separate, targeted email to each unique NotifyEmail recipient</strong> with only the credentials related to their apps.</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-example-usage-in-branding-properties">✏️ Example Usage in Branding properties:</h4>
<ul>
<li><p>Go to <strong>Azure Portal → Entra ID → App Registrations → [Your App] → Branding &amp; Properties</strong></p>
</li>
<li><p>In the <em>Internal Notes</em> add:</p>
<pre><code class="lang-plaintext">  NotifyEmail=team-azureops@yourdomain.com
</code></pre>
</li>
</ul>
<h4 id="heading-script-changes-summary">📄 Script Changes Summary</h4>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><strong>NotifyEmail</strong> parsing</td><td>Reads from <a target="_blank" href="http://Application.Info"><code>Internal Notes</code></a> for <code>NotifyEmail=&lt;email&gt;</code></td></tr>
<tr>
<td><strong>Per‑team alerts</strong></td><td>Sends a separate email to each unique NotifyEmail address with only relevant expiring credentials</td></tr>
<tr>
<td><strong>Fallback behavior</strong></td><td>If no <code>NotifyEmail</code> is defined, a credential expiry still appears in the global report, but no separate email is sent</td></tr>
</tbody>
</table>
</div><h4 id="heading-benefits-of-using-notifyemail">💡 Benefits of Using <code>NotifyEmail</code></h4>
<ul>
<li><p>Eliminates alert noise by notifying only those who need to act.</p>
</li>
<li><p>Ensures accountability by routing alerts to the correct team.</p>
</li>
<li><p>Simplifies onboarding of new apps—just define your tag once and the automation handles the rest.</p>
</li>
</ul>
<hr />
<h2 id="heading-contributions-welcome">🤝 Contributions Welcome</h2>
<p>Have ideas? Found a bug? Want to add secret expiration to KeyVault secrets too?<br />Open an issue or send a PR; I’d love to collaborate.</p>
<hr />
<h2 id="heading-final-thoughts">🙌 Final Thoughts</h2>
<p>Security is proactive; not reactive.<br />Automating app secret monitoring is one small investment that saves a lot of future pain.</p>
<p>Let me know if you try this in your environment or have feedback!</p>
]]></content:encoded></item><item><title><![CDATA[Automatically Notify Microsoft 365 Users of Upcoming Password Expiration Using PowerShell & Azure Automation]]></title><description><![CDATA[Ensuring your Microsoft 365 users are aware of password expiration is critical for both security and productivity. Without proper notification, users can get locked out unexpectedly, causing support overhead and frustration. While Microsoft 365 doesn...]]></description><link>https://blog.marconotarrigo.tech/automatically-notify-microsoft-365-users-of-upcoming-password-expiration-using-powershell-and-azure-automation</link><guid isPermaLink="true">https://blog.marconotarrigo.tech/automatically-notify-microsoft-365-users-of-upcoming-password-expiration-using-powershell-and-azure-automation</guid><category><![CDATA[msgraphapi]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[Microsoft365]]></category><category><![CDATA[automation]]></category><category><![CDATA[azure automation]]></category><category><![CDATA[scripts]]></category><dc:creator><![CDATA[Marco Notarrigo]]></dc:creator><pubDate>Sat, 22 Mar 2025 21:28:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742671951169/d29b80a5-37e6-4f90-9d53-9c3fe86690b1.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ensuring your Microsoft 365 users are aware of password expiration is critical for both security and productivity. Without proper notification, users can get locked out unexpectedly, causing support overhead and frustration. While Microsoft 365 doesn't send these notifications by default, we can fix that.</p>
<p>In this guide, you'll learn how to automate password expiration checks and send timely email alerts using:</p>
<ul>
<li><p><strong>PowerShell</strong></p>
</li>
<li><p><strong>Microsoft Graph API</strong></p>
</li>
<li><p><strong>Azure Automation (or local execution)</strong></p>
</li>
</ul>
<h2 id="heading-what-this-script-does">📊 What This Script Does</h2>
<ul>
<li><p>Connects to Microsoft Graph using <strong>App Registration</strong></p>
</li>
<li><p>Retrieves users from a <strong>dynamic Microsoft 365 group</strong></p>
</li>
<li><p>Checks their <strong>last password change date</strong></p>
</li>
<li><p>Calculates <strong>days until expiration</strong> (based on org policy)</p>
</li>
<li><p>Sends <strong>custom email notifications</strong> for users whose passwords will expire soon (e.g., within 7 days)</p>
</li>
</ul>
<blockquote>
<p>💡 <strong>Note</strong>: The script assumes all users in the dynamic group have passwords that <strong>do expire</strong> (e.g., users logging in via Microsoft Entra ID Join). Therefore, it does not need to filter out accounts with <code>PasswordNeverExpires</code>, since the group logic already excludes them.</p>
</blockquote>
<h2 id="heading-pre-requisites">📄 Pre-Requisites</h2>
<h3 id="heading-1-app-registration-in-entra-id">1. App Registration in Entra ID</h3>
<ul>
<li><p>Create an app in <strong>Entra ID &gt; App Registrations</strong></p>
</li>
<li><p>Assign <strong>API Permissions</strong>:</p>
<ul>
<li><p><code>User.Read.All</code></p>
</li>
<li><p><code>Group.Read.All</code></p>
</li>
<li><p><code>Mail.Send</code></p>
</li>
</ul>
</li>
<li><p><strong>Grant Admin Consent</strong></p>
</li>
<li><p>Generate a <strong>client secret</strong> and note the <strong>Application (Client) ID</strong> and <strong>Directory (Tenant) ID</strong></p>
</li>
</ul>
<h3 id="heading-2-licensed-sender-mailbox">2. Licensed Sender Mailbox</h3>
<p>Ensure your app is authorized to send mail <strong>on behalf of a licensed user</strong>, such as <code>noreply@yourdomain.com</code>.</p>
<h3 id="heading-3-optional-azure-automation-setup">3. Optional: Azure Automation Setup</h3>
<p>Store sensitive variables as <strong>Automation Variables</strong>:</p>
<ul>
<li><p><code>ClientID</code></p>
</li>
<li><p><code>ClientSecret</code></p>
</li>
<li><p><code>TenantID</code></p>
</li>
<li><p><code>GroupId</code></p>
</li>
<li><p><code>SenderEmail</code></p>
</li>
<li><p><code>PasswordExpirationDays</code></p>
</li>
</ul>
<hr />
<h2 id="heading-features-of-the-script">💡 Features of the Script</h2>
<ul>
<li><p><strong>Can run locally</strong> or inside <strong>Azure Automation</strong></p>
</li>
<li><p>Smart logging via <code>Write-Log</code> (supports verbosity and silent automation)</p>
</li>
<li><p>UPN masking for privacy in logs</p>
</li>
<li><p>Error handling and graceful exits</p>
</li>
</ul>
<hr />
<h2 id="heading-masked-logging-example">👺 Masked Logging Example</h2>
<pre><code class="lang-plaintext">User: jo*****@domain.com | Days remaining: 5
Sending alert...
Email sent to jo*****@domain.com
</code></pre>
<hr />
<h2 id="heading-script-usage-locally-or-in-azure">✎ Script Usage (Locally or in Azure)</h2>
<h3 id="heading-local-test-example">⚡ Local Test Example:</h3>
<pre><code class="lang-plaintext">.\Notify-M365PasswordExpiry.ps1 `
    -ClientId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" `
    -ClientSecret "your-secret" `
    -TenantId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" `
    -GroupId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" `
    -PasswordExpirationDays 45 `
    -SenderEmail "noreply@yourdomain.com" `
    -UseLocalParameters
</code></pre>
<h3 id="heading-scheduled-in-azure-automation">🚀 Scheduled in Azure Automation:</h3>
<ul>
<li><p>Import the script as a <strong>Runbook</strong></p>
</li>
<li><p>Set up a <strong>daily schedule</strong></p>
</li>
<li><p>Securely store secrets using <strong>Automation Variables</strong></p>
</li>
</ul>
<hr />
<h2 id="heading-under-the-hood-logic-breakdown">🔧 Under the Hood (Logic Breakdown)</h2>
<ol>
<li><p>Get access token using <code>client_credentials</code></p>
</li>
<li><p>Connect to Microsoft Graph</p>
</li>
<li><p>Fetch users from the target group</p>
</li>
<li><p>For each user:</p>
<ul>
<li><p>Get <code>LastPasswordChangeDateTime</code></p>
</li>
<li><p>Add org-defined expiration window (e.g., 45 days)</p>
</li>
<li><p>Calculate remaining days</p>
</li>
<li><p>If within 7 days, send email</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-key-functions-explained">🧩 Key Functions Explained</h3>
<h4 id="heading-get-graphaccesstoken">🔐 <code>Get-GraphAccessToken</code></h4>
<p>Authenticates using client credentials and retrieves a Microsoft Graph API access token.</p>
<h4 id="heading-connect-tograph">🌐 <code>Connect-ToGraph</code></h4>
<p>Uses the Microsoft Graph PowerShell SDK to establish a session for additional operations like retrieving user data.</p>
<h4 id="heading-get-groupusers">👥 <code>Get-GroupUsers</code></h4>
<p>Fetches members from the specified dynamic group by Group ID.</p>
<h4 id="heading-get-userdetails">🔍 <code>Get-UserDetails</code></h4>
<p>Retrieves <code>DisplayName</code>, <code>UserPrincipalName</code>, and <code>LastPasswordChangeDateTime</code> for each user.</p>
<h4 id="heading-send-emailnotification">✉ <code>Send-EmailNotification</code></h4>
<p>Sends a formatted HTML email to users using Microsoft Graph's <code>sendMail</code> endpoint. The message content is fully customizable based on your organization’s communication style.</p>
<hr />
<h2 id="heading-github-repository">🔗 GitHub Repository</h2>
<p>You can find the full script and future updates here: <a target="_blank" href="https://github.com/bitnash/m365-powershell-scripts/tree/main/password-expiry-alert"><strong>🔗 GitHub – Notify-M365PasswordExpiry.ps1</strong></a></p>
<hr />
<h2 id="heading-benefits">📈 Benefits</h2>
<ul>
<li><p>Reduces help desk tickets</p>
</li>
<li><p>Keeps users informed and proactive</p>
</li>
<li><p>Flexible: can run in the cloud or on-prem</p>
</li>
<li><p>Secure: supports masked logging and stored secrets</p>
</li>
</ul>
<hr />
<h2 id="heading-what-you-can-improve-next">🌐 What You Can Improve Next</h2>
<ul>
<li><p>Integrate Microsoft Teams alerts</p>
</li>
<li><p>Write logs to Azure Storage or Log Analytics</p>
</li>
<li><p>Localize email messages based on user locale <em>(optional – currently defaulted to English)</em></p>
</li>
</ul>
<hr />
<h2 id="heading-final-thoughts">🚀 Final Thoughts</h2>
<p>This script gives you full control over Microsoft 365 password expiration reminders, with enterprise-grade flexibility. Whether you run it in Azure Automation or locally, it's ready to scale and secure.</p>
<p>Have questions or want enhancements? Let's build on this together! ✨</p>
]]></content:encoded></item><item><title><![CDATA[A New Year, a New Beginning]]></title><description><![CDATA[The new year is a time of hope and inspiration. It's the perfect time to start fresh, set goals, and make intentions for what you want. That's why I decided to start a technical blog. While there are already many technical blogs out there, I hope tha...]]></description><link>https://blog.marconotarrigo.tech/a-new-year-a-new-beginning</link><guid isPermaLink="true">https://blog.marconotarrigo.tech/a-new-year-a-new-beginning</guid><category><![CDATA[first post]]></category><category><![CDATA[First Blog]]></category><category><![CDATA[New year]]></category><category><![CDATA[Technical writing ]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Marco Notarrigo]]></dc:creator><pubDate>Sat, 31 Dec 2022 23:13:34 GMT</pubDate><content:encoded><![CDATA[<p>The new year is a time of hope and inspiration. It's the perfect time to start fresh, set goals, and make intentions for what you want. That's why I decided to start a technical blog. While there are already many technical blogs out there, I hope that mine will be a valuable resource and a bookmark for those interested in the subject.</p>
<p>I plan to cover a wide range of technical subjects in this blog, including <em>DevOps</em>, <em>Agile</em>, <em>security</em>, <em>cloud computing</em>, <em>infrastructure automatio</em>n and <em>open source</em>, to name a few.</p>
<p>I am excited to start this new chapter in my journey as a technical blogger. I hope that through my posts, I can share valuable information and insights with my readers. I look forward to connecting with others who share my passion for technology and learning together. Thank you for joining me on this journey and I can't wait to see what the future holds.</p>
]]></content:encoded></item></channel></rss>