<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>PuttyQ</title>
	<atom:link href="https://www.puttyq.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.puttyq.com</link>
	<description></description>
	<lastBuildDate>Mon, 30 Mar 2026 18:38:28 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://www.puttyq.com/wp-content/uploads/2018/08/PuttyQ.com-Favicon.png</url>
	<title>PuttyQ</title>
	<link>https://www.puttyq.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">69152745</site>	<item>
		<title>Rewriting the MIM Pending Export Script for 2026</title>
		<link>https://www.puttyq.com/rewriting-the-mim-pending-export-script-for-2026/</link>
		
		<dc:creator><![CDATA[Almero]]></dc:creator>
		<pubDate>Mon, 30 Mar 2026 18:30:54 +0000</pubDate>
				<category><![CDATA[General]]></category>
		<guid isPermaLink="false">https://www.puttyq.com/?p=1156</guid>

					<description><![CDATA[There are tools you use once… and forget. And then there are tools that quietly become part of how you survive complex systems. For me, one of those tools has always been the Pending Export script originally written by Carol Wapshere. If you’ve worked in Microsoft Identity Manager long enough, you know the moment when [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>There are tools you use once… and forget. And then there are tools that quietly become part of how you survive complex systems. For me, one of those tools has always been the <strong><a href="https://github.com/missmiis">Pending Export script originally written by Carol Wapshere</a></strong>. If you’ve worked in Microsoft Identity Manager long enough, you know the moment when you are ready to run a big export. You <em>think</em> you know what’s going to happen, but there is so much data it is hard to validate in the console.</p>



<p>And in identity systems, “not really knowing” is where things have gone wrong for me in the past.</p>



<h2 class="wp-block-heading" id="The-Problem-MIM-Never-Really-Solved">The Problem MIM Never Really Solved</h2>



<p>MIM gives you power, but it does not give your stakeholders visibility. Before an export, they are often left asking:</p>



<ul class="wp-block-list">
<li>What exactly is going to change?</li>



<li>Which attributes are being updated?</li>



<li>What values are being added, removed, or overwritten?</li>



<li>How many objects are actually affected?</li>
</ul>



<p>Yes, you can inspect connector space objects and do previews &#8211; but at scale, this becomes impractical. That’s where Carol’s script came in. It took the output of <code>csexport.exe</code>, turned it into something human-readable, and gave you a pre-flight view of identity changes. This allowed you to send data to business for validation before running big exports (and to effectively follow change processes).</p>



<h2 class="wp-block-heading" id="Why-I-Had-to-Touch-It">Why I Had to Touch It</h2>



<p>I recently had to dust this tool off again for a project. In this project I have a sizable dataset which I wanted to validate with the customer. I got the script and ran it; this is where things broke.</p>



<p>The original script was brilliant for its time — but it struggled under time and modern scale:</p>



<ul class="wp-block-list">
<li>Connector spaces with <strong>95,000+ objects</strong></li>



<li>Large XML exports</li>



<li>Real-world attribute complexity</li>
</ul>



<p>Two issues became obvious:</p>



<ul class="wp-block-list">
<li>Memory: The script split XML into thousands of files and loaded each into memory. That works… until it doesn’t.</li>



<li>Performance: Each object was parsed twice and at scale, this becomes painfully slow.</li>
</ul>



<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex"></div>



<h2 class="wp-block-heading" id="This-Was-Not-a-Patch">This Was Not a Patch</h2>



<p>I started with the intention of fixing a few things but ended rewriting. And I want to be very clear here; the rewrite is not intended to minimize Carol’s original work. This version is simply an attempt to make that idea survive modern environments.</p>



<h2 class="wp-block-heading" id="The-Core-Change-—-From-DOM-to-Streaming">The Core Change — From DOM to Streaming</h2>



<p>The biggest shift was architectural.</p>



<p>Instead of:</p>



<ul class="wp-block-list">
<li>Splitting XML into per-object files</li>



<li>Loading full XML DOMs into memory</li>



<li>Re-parsing strings repeatedly</li>
</ul>



<p>The script now uses:</p>



<ul class="wp-block-list">
<li>A single-pass streaming XML reader <strong>(</strong><code>XmlReader</code><strong>)</strong></li>



<li>Forward-only processing</li>



<li>Depth-aware navigation</li>
</ul>



<p>What this means in practice:</p>



<ul class="wp-block-list">
<li>No temporary files</li>



<li>No full XML loads</li>



<li>Memory usage scales with file size, not object count</li>



<li>Processing time becomes predictable</li>
</ul>



<p>This one change fundamentally altered how the tool behaves under load but also exposed a lot of data hanlding issues.</p>



<h2 class="wp-block-heading" id="What-You-Get-Out-of-It">What You Get Out of It</h2>



<p>The goal of the tool has not changed, clarity before execution. But the output is now more usable at scale and has a few more features and validation built in.</p>



<h3 class="wp-block-heading" id="Reports-generated">Reports generated</h3>



<ul class="wp-block-list">
<li>Single-value attribute changes (per object)</li>



<li>Multi-value attribute changes (adds/removes)</li>



<li>Change validation (counts)</li>



<li>Optional per-attribute breakdowns: Something I always used to do with the original was extract and then start filtering changes per attribute, so this time I just added a flag to output straight to “per-attribute.csv” files.</li>



<li>HTML summary with totals and timing</li>
</ul>



<p>You can take these outputs and:</p>



<ul class="wp-block-list">
<li>Validate changes before export</li>



<li>Get stakeholder approval</li>



<li>Audit what is about to happen</li>



<li>Troubleshoot unexpected behaviour</li>
</ul>



<h2 class="wp-block-heading" id="The-Hidden-Problem-—-Data-Quality">The Hidden Problem — Data Quality</h2>



<p>The rewrite exposed something else that is not performance rated; data quality. Some target system data is “dirty” with means things like spaces, new lines and more creates issues for the script. When you start processing large datasets, you begin to see:</p>



<ul class="wp-block-list">
<li>Broken values</li>



<li>Unexpected formatting</li>



<li>Embedded line breaks</li>



<li>Edge-case attribute behaviour</li>
</ul>



<p>Some of the hardest bugs I hit were not performance-related but they were data integrity issues caused by real-world directory data. In the end with all the changes I made in the processing I was second guessing data all the time. This is why I introduced a second script:</p>



<h3 class="wp-block-heading" id="Test-PendingExports.ps1"><code>Test-PendingExports.ps1</code></h3>



<p>A validation tool that:</p>



<ul class="wp-block-list">
<li>Re-reads the original XML</li>



<li>Compares it to the generated CSVs</li>



<li>Verifies counts, values, and summaries</li>
</ul>



<p>Because if you are going to trust a report… You need to be able to verify the report.</p>



<h3 class="wp-block-heading" id="Sidenote:-The-CSV-Problem">Sidenote: The CSV Problem</h3>



<p>One of the more interesting issues was sometimes the CSV format itself. Embedded newlines inside attribute values caused:</p>



<ul class="wp-block-list">
<li>Broken rows</li>



<li>Phantom records</li>



<li>Silent data corruption in Excel pipelines</li>
</ul>



<p>Ultimately the only to fix this in the reporting was (altering trying simple quoting) was to sanitise the data at export time to prevent downstream parsing failures.</p>



<p></p>



<h2 class="wp-block-heading" id="Now,-and-Next-Steps">Now, and Next Steps</h2>



<p>For now, thank you <a href="https://github.com/missmiis">Carol </a>&#8211; hope you do not mind.</p>



<p>As for the project &#8211; it is not finished. I’ve tested heavily on a large Active Directory user exports (100k+ objects)</p>



<p>But I still want to validate:</p>



<ul class="wp-block-list">
<li>Moves</li>



<li>Deletes</li>



<li>Different Management Agents</li>



<li>More edge cases</li>
</ul>



<p></p>



<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="https://github.com/puttyq/mim-pending-exports" target="_blank" rel="noreferrer noopener">Download from Github</a></div>
</div>



<p></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1156</post-id>	</item>
		<item>
		<title>PSMA &#8211; Tracking M365 User Activity in MIM with the Graph Reports API</title>
		<link>https://www.puttyq.com/tracking-m365-user-activity-in-mim-with-the-graph-reports-api/</link>
		
		<dc:creator><![CDATA[Almero]]></dc:creator>
		<pubDate>Wed, 25 Mar 2026 11:54:48 +0000</pubDate>
				<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[PSMA]]></category>
		<guid isPermaLink="false">https://www.puttyq.com/?p=1145</guid>

					<description><![CDATA[If you have been working in identity governance for any length of time, you have almost certainly encountered the question:&#160;&#8220;Is this account actually being used?&#8221;&#160;It sounds simple. It is not. In a mature Microsoft 365 environment, answering it properly requires pulling together sign-in data from Entra ID, mailbox activity from Exchange Online, and enough freshness [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>If you have been working in identity governance for any length of time, you have almost certainly encountered the question:&nbsp;<em>&#8220;Is this account actually being used?&#8221;</em>&nbsp;It sounds simple. It is not. In a mature Microsoft 365 environment, answering it properly requires pulling together sign-in data from Entra ID, mailbox activity from Exchange Online, and enough freshness in the data to make a governance decision you can defend.</p>



<p>This post introduces a new Granfeldt PSMA —&nbsp;<a href="https://github.com/puttyq/mim-m365-activity" target="_blank" rel="noreferrer noopener">mim-m365-activity</a>&nbsp;— that does exactly that, and specifically digs into why the&nbsp;<a href="https://learn.microsoft.com/en-us/graph/api/reportroot-getemailactivityuserdetail?view=graph-rest-1.0" target="_blank" rel="noreferrer noopener">Microsoft Graph&nbsp;<code>getEmailActivityUserDetail</code></a>&nbsp;endpoint is the right tool for the Exchange side of this problem.</p>



<h2 class="wp-block-heading">Why people want this data</h2>



<p>The classic driver is licence cost (or security compliance). An Exchange Online Plan 2 licence sitting on an account that last received an email two years ago represents a straightforward saving once you can prove the inactivity. But the value does not stop at procurement.</p>



<ul class="wp-block-list">
<li><strong>Access certification:</strong>&nbsp;Reviewers need real activity signals, not just &#8220;account exists.&#8221; A last-activity date from Exchange and a last sign-in date from Entra together give a much more honest picture of risk than either alone.</li>



<li><strong>Orphan and stale account detection:</strong>&nbsp;AD accounts that have been off-boarded from HR but never disabled often show no Entra sign-in activity but may still have mail flow, forwarding rules, or delegation — the mailbox side tells the full story.</li>



<li><strong>Joiner–mover–leaver automation:</strong>&nbsp;Being able to suppress or extend provisioning actions based on whether an account has been genuinely active makes lifecycle rules far more defensible.</li>



<li><strong>Audit and compliance:</strong>&nbsp;Many regulatory frameworks require evidence that privileged and service accounts are monitored. Having this data flowing into MIM&#8217;s metaverse means it can be reported on, acted on, and exported to whatever governance tooling is downstream.</li>
</ul>



<p>The frustrating part, historically, has been getting this data into MIM in a clean, reliable, and maintainable way. The Graph Reports API solves the Exchange half of this cleanly.</p>



<h2 class="wp-block-heading">The&nbsp;<code>getEmailActivityUserDetail</code>&nbsp;endpoint</h2>



<p>The Microsoft Graph&nbsp;<a href="https://learn.microsoft.com/en-us/graph/api/reportroot-getemailactivityuserdetail?view=graph-rest-1.0" target="_blank" rel="noreferrer noopener"><code>reportRoot: getEmailActivityUserDetail</code></a>&nbsp;endpoint returns a per-user breakdown of Exchange Online mail activity over a specified reporting window (D7, D30, D90, or D180). What makes it genuinely useful for governance work rather than just reporting dashboards is the combination of fields it returns:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">Field</th><th class="has-text-align-left" data-align="left">Type</th><th class="has-text-align-left" data-align="left">Governance value</th></tr></thead><tbody><tr><td><code>User Principal Name</code></td><td>string</td><td>Join key to Entra / MIM objects</td></tr><tr><td><code>Last Activity Date</code></td><td>date</td><td>Core inactivity signal for mailbox</td></tr><tr><td><code>Send Count</code></td><td>int</td><td>Distinguish read-only from active senders</td></tr><tr><td><code>Receive Count</code></td><td>int</td><td>Detect mail flow without human interaction</td></tr><tr><td><code>Read Count</code></td><td>int</td><td>Confirm genuine user engagement</td></tr><tr><td><code>Is Deleted</code></td><td>bool</td><td>Soft-delete detection</td></tr><tr><td><code>Deleted Date</code></td><td>date</td><td>Retention / recovery window tracking</td></tr><tr><td><code>Assigned Products</code></td><td>string</td><td>Licence reconciliation</td></tr></tbody></table></figure>



<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<p></p>



<p><strong>Note on privacy settings:</strong>&nbsp;By default, Microsoft anonymises user-identifying data in usage reports at tenant level. You need to turn off the &#8220;Show concealed user, group, and site names in all reports&#8221; setting in the Microsoft 365 admin centre to see real UPNs. The PSMA README covers this.</p>
</div>
</div>



<p>The endpoint is available on both the&nbsp;<code>/v1.0</code>&nbsp;and&nbsp;<code>/beta</code>&nbsp;tracks. The beta variant also supports JSON output via&nbsp;<code>?$format=application/json</code>&nbsp;which simplifies parsing, though the v1.0 CSV response is stable and well-suited to the PowerShell pipeline used here.</p>



<p>The call itself is minimal — a single authenticated GET against the reports endpoint with a period parameter<a href="https://gist.github.com/puttyq/764ba84cd37a689a7ca612df7ef3d502#file-getemailactivityuserdetail-ps1">.</a></p>



<pre class="wp-block-code"><code># Get email activity for the last 30 days
$uri = "https://graph.microsoft.com/v1.0/reports/getEmailActivityUserDetail(period='D30')"</code></pre>



<p>The required Graph permission is&nbsp;<code>Reports.Read.All</code>&nbsp;(application permission). No delegated permission is needed for the PSMA use case — the management agent authenticates as an app registration with a client secret or certificate.</p>



<h2 class="wp-block-heading">How the PSMA works</h2>



<p>The management agent combines two data sources per user object: the Graph Reports API (Exchange activity) and the Graph&nbsp;<code>/users/{id}/signInActivity</code>&nbsp;endpoint (Entra sign-in data). These are merged on UPN and presented as a single flat object in the MIM connector space.</p>



<h3 class="wp-block-heading">Repository structure</h3>



<p>The repo follows the standard Granfeldt PSMA layout — three scripts and a schema file:</p>



<p><code><strong>PSSchema.ps1</strong></code> — defines the object class and attribute schema presented to MIM Sync</p>



<figure class="wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler"><div class="wp-block-embed__wrapper">
<style>.gist table { margin-bottom: 0; }</style><div style="tab-size: 8" id="gist146889692" class="gist">
    <div class="gist-file" translate="no" data-color-mode="light" data-light-theme="light">
      <div class="gist-data">
        
<div class="js-gist-file-update-container js-task-list-container">
      <div id="file-mim-m365-activity-psschema-ps1" class="file my-2">
    
    <div itemprop="text"
      class="Box-body p-0 blob-wrapper data type-powershell  "
      style="overflow: auto" tabindex="0" role="region"
      aria-label="mim-m365-activity-psschema.ps1 content, created by puttyq on 11:42AM on March 25."
    >

        
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">

  <template class="js-file-alert-template">
  <div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
  <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
    <span>
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      <a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
    </span>


  <div data-view-component="true" class="flash-action">        <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn">    Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
  <span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
    <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>

  <table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="4" data-paste-markdown-skip data-tagsearch-path="mim-m365-activity-psschema.ps1">
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC1" class="blob-code blob-code-inner js-file-line">&lt;# </td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC2" class="blob-code blob-code-inner js-file-line">	.SYNOPSIS  </td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC3" class="blob-code blob-code-inner js-file-line">	PSSchema.ps1 &#8211; M365 Management Agent Schema Definition</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC4" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC5" class="blob-code blob-code-inner js-file-line">	.DESCRIPTION  </td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC6" class="blob-code blob-code-inner js-file-line">	Schema for Entra and Exchange Online last sign-in activity</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC7" class="blob-code blob-code-inner js-file-line">	Source System:</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC8" class="blob-code blob-code-inner js-file-line">	&#8211; Entra ID (User and Audit Logs)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC9" class="blob-code blob-code-inner js-file-line">    &#8211; Exchange Online (Optional)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC10" class="blob-code blob-code-inner js-file-line">	&#8211; Exchange Usage Reports (Optional)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC11" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC12" class="blob-code blob-code-inner js-file-line">	.NOTES  </td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC13" class="blob-code blob-code-inner js-file-line">	Version History</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC14" class="blob-code blob-code-inner js-file-line">	&#8211; v1.0 &#8211; Initial schema definition for M365 activity data</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC15" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC16" class="blob-code blob-code-inner js-file-line">	.LINK</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L17" class="blob-num js-line-number js-blob-rnum" data-line-number="17"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC17" class="blob-code blob-code-inner js-file-line">	Author &#8211; Almero Steyn &#8211; https://www.puttyq.com</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L18" class="blob-num js-line-number js-blob-rnum" data-line-number="18"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC18" class="blob-code blob-code-inner js-file-line">	Integralis Website &#8211; http://www.integralis.co.za  </td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L19" class="blob-num js-line-number js-blob-rnum" data-line-number="19"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC19" class="blob-code blob-code-inner js-file-line">	Integralis Support &#8211; help@integralis.co.za  </td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L20" class="blob-num js-line-number js-blob-rnum" data-line-number="20"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC20" class="blob-code blob-code-inner js-file-line">	Granfeldt PowerShell Management Agent &#8211; https://github.com/sorengranfeldt/psma</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L21" class="blob-num js-line-number js-blob-rnum" data-line-number="21"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC21" class="blob-code blob-code-inner js-file-line">#&gt;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L22" class="blob-num js-line-number js-blob-rnum" data-line-number="22"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC22" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L23" class="blob-num js-line-number js-blob-rnum" data-line-number="23"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC23" class="blob-code blob-code-inner js-file-line">param</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L24" class="blob-num js-line-number js-blob-rnum" data-line-number="24"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC24" class="blob-code blob-code-inner js-file-line">(</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L25" class="blob-num js-line-number js-blob-rnum" data-line-number="25"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC25" class="blob-code blob-code-inner js-file-line">	$Username,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L26" class="blob-num js-line-number js-blob-rnum" data-line-number="26"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC26" class="blob-code blob-code-inner js-file-line">	$Password,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L27" class="blob-num js-line-number js-blob-rnum" data-line-number="27"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC27" class="blob-code blob-code-inner js-file-line">	$Credentials,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L28" class="blob-num js-line-number js-blob-rnum" data-line-number="28"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC28" class="blob-code blob-code-inner js-file-line">	$AuxUsername,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L29" class="blob-num js-line-number js-blob-rnum" data-line-number="29"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC29" class="blob-code blob-code-inner js-file-line">	$AuxPassword,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L30" class="blob-num js-line-number js-blob-rnum" data-line-number="30"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC30" class="blob-code blob-code-inner js-file-line">	$AuxCredentials,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L31" class="blob-num js-line-number js-blob-rnum" data-line-number="31"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC31" class="blob-code blob-code-inner js-file-line">	$ConfigurationParameter</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L32" class="blob-num js-line-number js-blob-rnum" data-line-number="32"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC32" class="blob-code blob-code-inner js-file-line">)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L33" class="blob-num js-line-number js-blob-rnum" data-line-number="33"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC33" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L34" class="blob-num js-line-number js-blob-rnum" data-line-number="34"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC34" class="blob-code blob-code-inner js-file-line">$obj = New-Object -Type PSCustomObject</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L35" class="blob-num js-line-number js-blob-rnum" data-line-number="35"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC35" class="blob-code blob-code-inner js-file-line">$obj | Add-Member -Type NoteProperty -Name &quot;Anchor-UserPrincipalName|String&quot; -Value &quot;user@domain.com&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L36" class="blob-num js-line-number js-blob-rnum" data-line-number="36"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC36" class="blob-code blob-code-inner js-file-line">$obj | Add-Member -Type NoteProperty -Name &quot;objectClass|String&quot; -Value &quot;user&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L37" class="blob-num js-line-number js-blob-rnum" data-line-number="37"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC37" class="blob-code blob-code-inner js-file-line">$obj | Add-Member -Type NoteProperty -Name &quot;LastInteractiveSignIn|String&quot; -Value &quot;2023-01-01T12:00:00Z&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L38" class="blob-num js-line-number js-blob-rnum" data-line-number="38"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC38" class="blob-code blob-code-inner js-file-line">$obj | Add-Member -Type NoteProperty -Name &quot;LastInteractiveAgeDays|String&quot; -Value &quot;345&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L39" class="blob-num js-line-number js-blob-rnum" data-line-number="39"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC39" class="blob-code blob-code-inner js-file-line">$obj | Add-Member -Type NoteProperty -Name &quot;LastNonInteractiveSignIn|String&quot; -Value &quot;2023-01-15T12:00:00Z&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L40" class="blob-num js-line-number js-blob-rnum" data-line-number="40"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC40" class="blob-code blob-code-inner js-file-line">$obj | Add-Member -Type NoteProperty -Name &quot;LastNonInteractiveAgeDays|String&quot; -Value &quot;345&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L41" class="blob-num js-line-number js-blob-rnum" data-line-number="41"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC41" class="blob-code blob-code-inner js-file-line">$obj | Add-Member -Type NoteProperty -Name &quot;LastSentMailDate|String&quot; -Value &quot;2023-01-20T12:00:00Z&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L42" class="blob-num js-line-number js-blob-rnum" data-line-number="42"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC42" class="blob-code blob-code-inner js-file-line">$obj | Add-Member -Type NoteProperty -Name &quot;LastSentMailAgeDays|String&quot; -Value &quot;345&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L43" class="blob-num js-line-number js-blob-rnum" data-line-number="43"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC43" class="blob-code blob-code-inner js-file-line">$obj | Add-Member -Type NoteProperty -Name &quot;ReportReadCount|String&quot; -Value &quot;100&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L44" class="blob-num js-line-number js-blob-rnum" data-line-number="44"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC44" class="blob-code blob-code-inner js-file-line">$obj | Add-Member -Type NoteProperty -Name &quot;ReportReceiveCount|String&quot; -Value &quot;100&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L45" class="blob-num js-line-number js-blob-rnum" data-line-number="45"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC45" class="blob-code blob-code-inner js-file-line">$obj | Add-Member -Type NoteProperty -Name &quot;ReportSendCount|String&quot; -Value &quot;100&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L46" class="blob-num js-line-number js-blob-rnum" data-line-number="46"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC46" class="blob-code blob-code-inner js-file-line">$obj | Add-Member -Type NoteProperty -Name &quot;ReportLastActivityDate|String&quot; -Value &quot;2023-01-20T12:00:00Z&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L47" class="blob-num js-line-number js-blob-rnum" data-line-number="47"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC47" class="blob-code blob-code-inner js-file-line">$obj | Add-Member -Type NoteProperty -Name &quot;ReportPeriod|String&quot; -Value &quot;D180&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L48" class="blob-num js-line-number js-blob-rnum" data-line-number="48"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC48" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psschema-ps1-L49" class="blob-num js-line-number js-blob-rnum" data-line-number="49"></td>
          <td id="file-mim-m365-activity-psschema-ps1-LC49" class="blob-code blob-code-inner js-file-line">$obj </td>
        </tr>
  </table>
</div>


    </div>

  </div>

</div>

      </div>
      <div class="gist-meta">
        <a href="https://gist.github.com/puttyq/764ba84cd37a689a7ca612df7ef3d502/raw/c85e4d11b1f14d44f9822e207cad90cf04fc8f5f/mim-m365-activity-psschema.ps1" style="float:right" class="Link--inTextBlock">view raw</a>
        <a href="https://gist.github.com/puttyq/764ba84cd37a689a7ca612df7ef3d502#file-mim-m365-activity-psschema-ps1" class="Link--inTextBlock">
          mim-m365-activity-psschema.ps1
        </a>
        hosted with &#10084; by <a class="Link--inTextBlock" href="https://github.com">GitHub</a>
      </div>
    </div>
</div>

</div></figure>



<p></p>



<p><code><strong>PSImport.ps1</strong></code> — full import script; calls both Graph endpoints and merges results</p>



<figure class="wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler"><div class="wp-block-embed__wrapper">
<style>.gist table { margin-bottom: 0; }</style><div style="tab-size: 8" id="gist146889692" class="gist">
    <div class="gist-file" translate="no" data-color-mode="light" data-light-theme="light">
      <div class="gist-data">
        
<div class="js-gist-file-update-container js-task-list-container">
      <div id="file-mim-m365-activity-psimport-ps1" class="file my-2">
    
    <div itemprop="text"
      class="Box-body p-0 blob-wrapper data type-powershell  "
      style="overflow: auto" tabindex="0" role="region"
      aria-label="mim-m365-activity-psimport.ps1 content, created by puttyq on 11:42AM on March 25."
    >

        
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">

  <template class="js-file-alert-template">
  <div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
  <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
    <span>
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      <a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
    </span>


  <div data-view-component="true" class="flash-action">        <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn">    Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
  <span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
    <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>

  <table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="4" data-paste-markdown-skip data-tagsearch-path="mim-m365-activity-psimport.ps1">
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC1" class="blob-code blob-code-inner js-file-line">&lt;#</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC2" class="blob-code blob-code-inner js-file-line">    .SYNOPSIS</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC3" class="blob-code blob-code-inner js-file-line">    PSImport.ps1 &#8211; M365 Management Agent Import Script</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC4" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC5" class="blob-code blob-code-inner js-file-line">    .DESCRIPTION</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC6" class="blob-code blob-code-inner js-file-line">    A PSMA to import M365 activity (directory and Exchange Online) from M365.</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC7" class="blob-code blob-code-inner js-file-line">    This import script reads Entra user, audit logs, and Exchange Online activity from</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC8" class="blob-code blob-code-inner js-file-line">    Microsoft 365 and exposes it to Microsoft Identity Manager / ECMA Host through the Granfeldt PowerShell Management Agent.</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC9" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC10" class="blob-code blob-code-inner js-file-line">    Required Entra ID (Azure AD) permissions (Application Registration):</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC11" class="blob-code blob-code-inner js-file-line">    &#8211; AuditLog.Read.All (sign-in activity)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC12" class="blob-code blob-code-inner js-file-line">    &#8211; Directory.Read.All (user enumeration)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC13" class="blob-code blob-code-inner js-file-line">    &#8211; Report.Read.All (getEmailActivityUserDetail report)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC14" class="blob-code blob-code-inner js-file-line">    &#8211; User.Read.All (user enumeration)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC15" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC16" class="blob-code blob-code-inner js-file-line">    OPTIONAL &#8211; Required Exchange Online permissions (Application Registration):</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L17" class="blob-num js-line-number js-blob-rnum" data-line-number="17"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC17" class="blob-code blob-code-inner js-file-line">    &#8211; Mail.Read (mailbox activity)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L18" class="blob-num js-line-number js-blob-rnum" data-line-number="18"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC18" class="blob-code blob-code-inner js-file-line">    &#8211; Exchange Admin or delegated mailbox access (for Get-Mailbox and Get-MailboxStatistics)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L19" class="blob-num js-line-number js-blob-rnum" data-line-number="19"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC19" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L20" class="blob-num js-line-number js-blob-rnum" data-line-number="20"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC20" class="blob-code blob-code-inner js-file-line">    .NOTES</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L21" class="blob-num js-line-number js-blob-rnum" data-line-number="21"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC21" class="blob-code blob-code-inner js-file-line">    Version History</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L22" class="blob-num js-line-number js-blob-rnum" data-line-number="22"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC22" class="blob-code blob-code-inner js-file-line">    v1.0 &#8211; Initial M365 PSImport script with sign-in activity and optional mailbox activity</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L23" class="blob-num js-line-number js-blob-rnum" data-line-number="23"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC23" class="blob-code blob-code-inner js-file-line">    v1.1 &#8211; Added feature flag to enable/disable Exchange mailbox check, and error handling for non-mailbox users</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L24" class="blob-num js-line-number js-blob-rnum" data-line-number="24"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC24" class="blob-code blob-code-inner js-file-line">    v1.2 &#8211; Added getEmailActivityUserDetail report import (with configurable period)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L25" class="blob-num js-line-number js-blob-rnum" data-line-number="25"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC25" class="blob-code blob-code-inner js-file-line">    v1.3 &#8211; Added structured logging via PowerShell Logging module</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L26" class="blob-num js-line-number js-blob-rnum" data-line-number="26"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC26" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L27" class="blob-num js-line-number js-blob-rnum" data-line-number="27"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC27" class="blob-code blob-code-inner js-file-line">    .LINK</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L28" class="blob-num js-line-number js-blob-rnum" data-line-number="28"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC28" class="blob-code blob-code-inner js-file-line">    Author &#8211; Almero Steyn &#8211; https://www.puttyq.com</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L29" class="blob-num js-line-number js-blob-rnum" data-line-number="29"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC29" class="blob-code blob-code-inner js-file-line">    Integralis Website &#8211; http://www.integralis.co.za</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L30" class="blob-num js-line-number js-blob-rnum" data-line-number="30"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC30" class="blob-code blob-code-inner js-file-line">    Integralis Support &#8211; help@integralis.co.za</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L31" class="blob-num js-line-number js-blob-rnum" data-line-number="31"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC31" class="blob-code blob-code-inner js-file-line">    Granfeldt PowerShell Management Agent &#8211; https://github.com/sorengranfeldt/psma</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L32" class="blob-num js-line-number js-blob-rnum" data-line-number="32"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC32" class="blob-code blob-code-inner js-file-line">#&gt;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L33" class="blob-num js-line-number js-blob-rnum" data-line-number="33"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC33" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L34" class="blob-num js-line-number js-blob-rnum" data-line-number="34"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC34" class="blob-code blob-code-inner js-file-line">PARAM</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L35" class="blob-num js-line-number js-blob-rnum" data-line-number="35"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC35" class="blob-code blob-code-inner js-file-line">(</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L36" class="blob-num js-line-number js-blob-rnum" data-line-number="36"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC36" class="blob-code blob-code-inner js-file-line">  $Username,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L37" class="blob-num js-line-number js-blob-rnum" data-line-number="37"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC37" class="blob-code blob-code-inner js-file-line">  $Password,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L38" class="blob-num js-line-number js-blob-rnum" data-line-number="38"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC38" class="blob-code blob-code-inner js-file-line">  $Credentials,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L39" class="blob-num js-line-number js-blob-rnum" data-line-number="39"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC39" class="blob-code blob-code-inner js-file-line">  $AuxUsername,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L40" class="blob-num js-line-number js-blob-rnum" data-line-number="40"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC40" class="blob-code blob-code-inner js-file-line">  $AuxPassword,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L41" class="blob-num js-line-number js-blob-rnum" data-line-number="41"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC41" class="blob-code blob-code-inner js-file-line">  $AuxCredentials,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L42" class="blob-num js-line-number js-blob-rnum" data-line-number="42"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC42" class="blob-code blob-code-inner js-file-line">  $OperationType,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L43" class="blob-num js-line-number js-blob-rnum" data-line-number="43"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC43" class="blob-code blob-code-inner js-file-line">  $UsePagedImport,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L44" class="blob-num js-line-number js-blob-rnum" data-line-number="44"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC44" class="blob-code blob-code-inner js-file-line">  $PageSize,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L45" class="blob-num js-line-number js-blob-rnum" data-line-number="45"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC45" class="blob-code blob-code-inner js-file-line">  $ImportPageNumber,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L46" class="blob-num js-line-number js-blob-rnum" data-line-number="46"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC46" class="blob-code blob-code-inner js-file-line">  $ConfigurationParameter,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L47" class="blob-num js-line-number js-blob-rnum" data-line-number="47"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC47" class="blob-code blob-code-inner js-file-line">  $Schema</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L48" class="blob-num js-line-number js-blob-rnum" data-line-number="48"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC48" class="blob-code blob-code-inner js-file-line">)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L49" class="blob-num js-line-number js-blob-rnum" data-line-number="49"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC49" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L50" class="blob-num js-line-number js-blob-rnum" data-line-number="50"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC50" class="blob-code blob-code-inner js-file-line">BEGIN</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L51" class="blob-num js-line-number js-blob-rnum" data-line-number="51"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC51" class="blob-code blob-code-inner js-file-line">{</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L52" class="blob-num js-line-number js-blob-rnum" data-line-number="52"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC52" class="blob-code blob-code-inner js-file-line">    # Setup structured logging using PowerShell Logging module</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L53" class="blob-num js-line-number js-blob-rnum" data-line-number="53"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC53" class="blob-code blob-code-inner js-file-line">    Import-Module Logging</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L54" class="blob-num js-line-number js-blob-rnum" data-line-number="54"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC54" class="blob-code blob-code-inner js-file-line">    </td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L55" class="blob-num js-line-number js-blob-rnum" data-line-number="55"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC55" class="blob-code blob-code-inner js-file-line">    # Logging &#8211; Configure targets</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L56" class="blob-num js-line-number js-blob-rnum" data-line-number="56"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC56" class="blob-code blob-code-inner js-file-line">    $LogPath = if ($ConfigurationParameter.LogPath) {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L57" class="blob-num js-line-number js-blob-rnum" data-line-number="57"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC57" class="blob-code blob-code-inner js-file-line">        $ConfigurationParameter.LogPath</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L58" class="blob-num js-line-number js-blob-rnum" data-line-number="58"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC58" class="blob-code blob-code-inner js-file-line">    } else {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L59" class="blob-num js-line-number js-blob-rnum" data-line-number="59"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC59" class="blob-code blob-code-inner js-file-line">        &quot;.\Debug&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L60" class="blob-num js-line-number js-blob-rnum" data-line-number="60"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC60" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L61" class="blob-num js-line-number js-blob-rnum" data-line-number="61"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC61" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L62" class="blob-num js-line-number js-blob-rnum" data-line-number="62"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC62" class="blob-code blob-code-inner js-file-line">    Add-LoggingTarget -Name File -Configuration @{</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L63" class="blob-num js-line-number js-blob-rnum" data-line-number="63"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC63" class="blob-code blob-code-inner js-file-line">        Path              = &quot;$LogPath\Import-%{+%Y%m%d}.log&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L64" class="blob-num js-line-number js-blob-rnum" data-line-number="64"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC64" class="blob-code blob-code-inner js-file-line">        Append            = $true</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L65" class="blob-num js-line-number js-blob-rnum" data-line-number="65"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC65" class="blob-code blob-code-inner js-file-line">        Encoding          = &#39;ascii&#39;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L66" class="blob-num js-line-number js-blob-rnum" data-line-number="66"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC66" class="blob-code blob-code-inner js-file-line">        RotateAfterAmount = 10</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L67" class="blob-num js-line-number js-blob-rnum" data-line-number="67"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC67" class="blob-code blob-code-inner js-file-line">        RotateAmount      = 1</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L68" class="blob-num js-line-number js-blob-rnum" data-line-number="68"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC68" class="blob-code blob-code-inner js-file-line">        RotateAfterSize   = 1048576</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L69" class="blob-num js-line-number js-blob-rnum" data-line-number="69"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC69" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L70" class="blob-num js-line-number js-blob-rnum" data-line-number="70"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC70" class="blob-code blob-code-inner js-file-line">    Add-LoggingTarget -Name Console -Configuration @{</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L71" class="blob-num js-line-number js-blob-rnum" data-line-number="71"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC71" class="blob-code blob-code-inner js-file-line">        PrintException = $true</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L72" class="blob-num js-line-number js-blob-rnum" data-line-number="72"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC72" class="blob-code blob-code-inner js-file-line">        ColorMapping   = @{</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L73" class="blob-num js-line-number js-blob-rnum" data-line-number="73"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC73" class="blob-code blob-code-inner js-file-line">            &#39;DEBUG&#39;   = &#39;Blue&#39;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L74" class="blob-num js-line-number js-blob-rnum" data-line-number="74"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC74" class="blob-code blob-code-inner js-file-line">            &#39;INFO&#39;    = &#39;Green&#39;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L75" class="blob-num js-line-number js-blob-rnum" data-line-number="75"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC75" class="blob-code blob-code-inner js-file-line">            &#39;WARNING&#39; = &#39;Yellow&#39;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L76" class="blob-num js-line-number js-blob-rnum" data-line-number="76"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC76" class="blob-code blob-code-inner js-file-line">            &#39;ERROR&#39;   = &#39;Red&#39;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L77" class="blob-num js-line-number js-blob-rnum" data-line-number="77"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC77" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L78" class="blob-num js-line-number js-blob-rnum" data-line-number="78"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC78" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L79" class="blob-num js-line-number js-blob-rnum" data-line-number="79"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC79" class="blob-code blob-code-inner js-file-line">    Set-LoggingDefaultLevel -Level &#39;DEBUG&#39;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L80" class="blob-num js-line-number js-blob-rnum" data-line-number="80"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC80" class="blob-code blob-code-inner js-file-line">    Set-LoggingDefaultFormat -Format &#39;[%{timestamp:+yyyy/MM/dd HH:mm:ss.fff}] [%{level:-7}] &#8211; %{message}&#39;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L81" class="blob-num js-line-number js-blob-rnum" data-line-number="81"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC81" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L82" class="blob-num js-line-number js-blob-rnum" data-line-number="82"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC82" class="blob-code blob-code-inner js-file-line">    Write-Log -Level &#39;INFO&#39; -Message &#39;M365 Activity Import starting&#39;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L83" class="blob-num js-line-number js-blob-rnum" data-line-number="83"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC83" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L84" class="blob-num js-line-number js-blob-rnum" data-line-number="84"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC84" class="blob-code blob-code-inner js-file-line">    # Function to get last sent mail date for a user, with error handling for non-mailbox users and feature flag to enable/disable Exchange check</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L85" class="blob-num js-line-number js-blob-rnum" data-line-number="85"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC85" class="blob-code blob-code-inner js-file-line">    function Get-LastSentMailDate {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L86" class="blob-num js-line-number js-blob-rnum" data-line-number="86"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC86" class="blob-code blob-code-inner js-file-line">        param(</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L87" class="blob-num js-line-number js-blob-rnum" data-line-number="87"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC87" class="blob-code blob-code-inner js-file-line">            [string]$UserPrincipalName,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L88" class="blob-num js-line-number js-blob-rnum" data-line-number="88"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC88" class="blob-code blob-code-inner js-file-line">            [hashtable]$Headers,</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L89" class="blob-num js-line-number js-blob-rnum" data-line-number="89"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC89" class="blob-code blob-code-inner js-file-line">            [bool]$EnableExchangeCheck</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L90" class="blob-num js-line-number js-blob-rnum" data-line-number="90"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC90" class="blob-code blob-code-inner js-file-line">        )</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L91" class="blob-num js-line-number js-blob-rnum" data-line-number="91"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC91" class="blob-code blob-code-inner js-file-line">        $lastSentMailDate = $null</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L92" class="blob-num js-line-number js-blob-rnum" data-line-number="92"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC92" class="blob-code blob-code-inner js-file-line">        if ($EnableExchangeCheck) {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L93" class="blob-num js-line-number js-blob-rnum" data-line-number="93"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC93" class="blob-code blob-code-inner js-file-line">            $MailUri = &quot;https://graph.microsoft.com/v1.0/users/$UserPrincipalName/mailFolders(&#39;sentitems&#39;)/messages?`$top=1&amp;`$orderby=sentDateTime desc&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L94" class="blob-num js-line-number js-blob-rnum" data-line-number="94"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC94" class="blob-code blob-code-inner js-file-line">            try {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L95" class="blob-num js-line-number js-blob-rnum" data-line-number="95"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC95" class="blob-code blob-code-inner js-file-line">                $MailResponse = Invoke-RestMethod -Uri $MailUri -Headers $Headers</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L96" class="blob-num js-line-number js-blob-rnum" data-line-number="96"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC96" class="blob-code blob-code-inner js-file-line">                if ($MailResponse.value -and $MailResponse.value.Count -gt 0) {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L97" class="blob-num js-line-number js-blob-rnum" data-line-number="97"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC97" class="blob-code blob-code-inner js-file-line">                    $lastSentMailDate = $MailResponse.value[0].sentDateTime</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L98" class="blob-num js-line-number js-blob-rnum" data-line-number="98"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC98" class="blob-code blob-code-inner js-file-line">                    Write-Log -Level &#39;DEBUG&#39; -Message &quot;  Last sent mail date: $lastSentMailDate&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L99" class="blob-num js-line-number js-blob-rnum" data-line-number="99"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC99" class="blob-code blob-code-inner js-file-line">                }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L100" class="blob-num js-line-number js-blob-rnum" data-line-number="100"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC100" class="blob-code blob-code-inner js-file-line">                else {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L101" class="blob-num js-line-number js-blob-rnum" data-line-number="101"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC101" class="blob-code blob-code-inner js-file-line">                    $lastSentMailDate = $null</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L102" class="blob-num js-line-number js-blob-rnum" data-line-number="102"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC102" class="blob-code blob-code-inner js-file-line">                }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L103" class="blob-num js-line-number js-blob-rnum" data-line-number="103"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC103" class="blob-code blob-code-inner js-file-line">            }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L104" class="blob-num js-line-number js-blob-rnum" data-line-number="104"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC104" class="blob-code blob-code-inner js-file-line">            catch {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L105" class="blob-num js-line-number js-blob-rnum" data-line-number="105"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC105" class="blob-code blob-code-inner js-file-line">                $lastSentMailDate = $null</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L106" class="blob-num js-line-number js-blob-rnum" data-line-number="106"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC106" class="blob-code blob-code-inner js-file-line">                Write-Log -Level &#39;WARNING&#39; -Message &quot;  Mailbox not enabled or error: $_&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L107" class="blob-num js-line-number js-blob-rnum" data-line-number="107"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC107" class="blob-code blob-code-inner js-file-line">            }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L108" class="blob-num js-line-number js-blob-rnum" data-line-number="108"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC108" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L109" class="blob-num js-line-number js-blob-rnum" data-line-number="109"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC109" class="blob-code blob-code-inner js-file-line">        else {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L110" class="blob-num js-line-number js-blob-rnum" data-line-number="110"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC110" class="blob-code blob-code-inner js-file-line">            $lastSentMailDate = $null</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L111" class="blob-num js-line-number js-blob-rnum" data-line-number="111"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC111" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L112" class="blob-num js-line-number js-blob-rnum" data-line-number="112"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC112" class="blob-code blob-code-inner js-file-line">        return $lastSentMailDate</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L113" class="blob-num js-line-number js-blob-rnum" data-line-number="113"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC113" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L114" class="blob-num js-line-number js-blob-rnum" data-line-number="114"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC114" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L115" class="blob-num js-line-number js-blob-rnum" data-line-number="115"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC115" class="blob-code blob-code-inner js-file-line">    function isValuePresent($passedValue)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L116" class="blob-num js-line-number js-blob-rnum" data-line-number="116"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC116" class="blob-code blob-code-inner js-file-line">    {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L117" class="blob-num js-line-number js-blob-rnum" data-line-number="117"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC117" class="blob-code blob-code-inner js-file-line">        if($null -ne $passedValue -and $passedValue -ne &#39;&#39;)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L118" class="blob-num js-line-number js-blob-rnum" data-line-number="118"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC118" class="blob-code blob-code-inner js-file-line">        {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L119" class="blob-num js-line-number js-blob-rnum" data-line-number="119"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC119" class="blob-code blob-code-inner js-file-line">            return $true</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L120" class="blob-num js-line-number js-blob-rnum" data-line-number="120"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC120" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L121" class="blob-num js-line-number js-blob-rnum" data-line-number="121"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC121" class="blob-code blob-code-inner js-file-line">        return $false</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L122" class="blob-num js-line-number js-blob-rnum" data-line-number="122"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC122" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L123" class="blob-num js-line-number js-blob-rnum" data-line-number="123"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC123" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L124" class="blob-num js-line-number js-blob-rnum" data-line-number="124"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC124" class="blob-code blob-code-inner js-file-line">    # Variables for Entra App Registration</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L125" class="blob-num js-line-number js-blob-rnum" data-line-number="125"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC125" class="blob-code blob-code-inner js-file-line">    $ClientId = $Username</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L126" class="blob-num js-line-number js-blob-rnum" data-line-number="126"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC126" class="blob-code blob-code-inner js-file-line">    $TenantId = $Password</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L127" class="blob-num js-line-number js-blob-rnum" data-line-number="127"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC127" class="blob-code blob-code-inner js-file-line">    $ClientSecret = $AuxPassword</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L128" class="blob-num js-line-number js-blob-rnum" data-line-number="128"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC128" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L129" class="blob-num js-line-number js-blob-rnum" data-line-number="129"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC129" class="blob-code blob-code-inner js-file-line">    # Check Exchange mailbox check feature flag</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L130" class="blob-num js-line-number js-blob-rnum" data-line-number="130"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC130" class="blob-code blob-code-inner js-file-line">    $EnableExchangeCheck = ($ConfigurationParameter.EnableExchangeCheck -eq &quot;true&quot;)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L131" class="blob-num js-line-number js-blob-rnum" data-line-number="131"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC131" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L132" class="blob-num js-line-number js-blob-rnum" data-line-number="132"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC132" class="blob-code blob-code-inner js-file-line">    # Check getEmailActivityUserDetail via Graph API</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L133" class="blob-num js-line-number js-blob-rnum" data-line-number="133"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC133" class="blob-code blob-code-inner js-file-line">    if ($ConfigurationParameter.EmailActivityUserDetail -in @(&#39;D180&#39;,&#39;D90&#39;,&#39;D30&#39;,&#39;D7&#39;)) {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L134" class="blob-num js-line-number js-blob-rnum" data-line-number="134"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC134" class="blob-code blob-code-inner js-file-line">        $EmailActivityUserDetail = $true</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L135" class="blob-num js-line-number js-blob-rnum" data-line-number="135"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC135" class="blob-code blob-code-inner js-file-line">        $EmailActivityUserDetailDays = $ConfigurationParameter.EmailActivityUserDetail</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L136" class="blob-num js-line-number js-blob-rnum" data-line-number="136"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC136" class="blob-code blob-code-inner js-file-line">    } else {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L137" class="blob-num js-line-number js-blob-rnum" data-line-number="137"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC137" class="blob-code blob-code-inner js-file-line">        $EmailActivityUserDetail = $false</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L138" class="blob-num js-line-number js-blob-rnum" data-line-number="138"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC138" class="blob-code blob-code-inner js-file-line">        $EmailActivityUserDetailDays = &#39;D0&#39;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L139" class="blob-num js-line-number js-blob-rnum" data-line-number="139"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC139" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L140" class="blob-num js-line-number js-blob-rnum" data-line-number="140"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC140" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L141" class="blob-num js-line-number js-blob-rnum" data-line-number="141"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC141" class="blob-code blob-code-inner js-file-line">    # Uncomment to override with local test config (see TestConfig.local.ps1.example)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L142" class="blob-num js-line-number js-blob-rnum" data-line-number="142"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC142" class="blob-code blob-code-inner js-file-line">    # . &quot;$PSScriptRoot\TestConfig.local.ps1&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L143" class="blob-num js-line-number js-blob-rnum" data-line-number="143"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC143" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L144" class="blob-num js-line-number js-blob-rnum" data-line-number="144"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC144" class="blob-code blob-code-inner js-file-line">    # Authenticate to Microsoft Graph (non-interactive)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L145" class="blob-num js-line-number js-blob-rnum" data-line-number="145"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC145" class="blob-code blob-code-inner js-file-line">    $TokenEndpoint = &quot;https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L146" class="blob-num js-line-number js-blob-rnum" data-line-number="146"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC146" class="blob-code blob-code-inner js-file-line">    $Body = @{</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L147" class="blob-num js-line-number js-blob-rnum" data-line-number="147"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC147" class="blob-code blob-code-inner js-file-line">        client_id     = $ClientId</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L148" class="blob-num js-line-number js-blob-rnum" data-line-number="148"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC148" class="blob-code blob-code-inner js-file-line">        scope         = &quot;https://graph.microsoft.com/.default&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L149" class="blob-num js-line-number js-blob-rnum" data-line-number="149"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC149" class="blob-code blob-code-inner js-file-line">        client_secret = $ClientSecret</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L150" class="blob-num js-line-number js-blob-rnum" data-line-number="150"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC150" class="blob-code blob-code-inner js-file-line">        grant_type    = &quot;client_credentials&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L151" class="blob-num js-line-number js-blob-rnum" data-line-number="151"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC151" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L152" class="blob-num js-line-number js-blob-rnum" data-line-number="152"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC152" class="blob-code blob-code-inner js-file-line">    try {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L153" class="blob-num js-line-number js-blob-rnum" data-line-number="153"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC153" class="blob-code blob-code-inner js-file-line">        $TokenResponse = Invoke-RestMethod -Method Post -Uri $TokenEndpoint -Body $Body</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L154" class="blob-num js-line-number js-blob-rnum" data-line-number="154"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC154" class="blob-code blob-code-inner js-file-line">        $AccessToken = $TokenResponse.access_token</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L155" class="blob-num js-line-number js-blob-rnum" data-line-number="155"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC155" class="blob-code blob-code-inner js-file-line">        Write-Log -Level &#39;INFO&#39; -Message &quot;Successfully authenticated to Microsoft Graph&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L156" class="blob-num js-line-number js-blob-rnum" data-line-number="156"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC156" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L157" class="blob-num js-line-number js-blob-rnum" data-line-number="157"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC157" class="blob-code blob-code-inner js-file-line">    catch {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L158" class="blob-num js-line-number js-blob-rnum" data-line-number="158"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC158" class="blob-code blob-code-inner js-file-line">        Write-Log -Level &#39;ERROR&#39; -Message &quot;Failed to authenticate to Microsoft Graph: $_&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L159" class="blob-num js-line-number js-blob-rnum" data-line-number="159"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC159" class="blob-code blob-code-inner js-file-line">        throw</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L160" class="blob-num js-line-number js-blob-rnum" data-line-number="160"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC160" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L161" class="blob-num js-line-number js-blob-rnum" data-line-number="161"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC161" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L162" class="blob-num js-line-number js-blob-rnum" data-line-number="162"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC162" class="blob-code blob-code-inner js-file-line">    Write-Log -Level &#39;INFO&#39; -Message &quot;EnableExchangeCheck: $EnableExchangeCheck&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L163" class="blob-num js-line-number js-blob-rnum" data-line-number="163"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC163" class="blob-code blob-code-inner js-file-line">    Write-Log -Level &#39;INFO&#39; -Message &quot;EmailActivityUserDetail: $EmailActivityUserDetail (period: $EmailActivityUserDetailDays)&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L164" class="blob-num js-line-number js-blob-rnum" data-line-number="164"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC164" class="blob-code blob-code-inner js-file-line">}</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L165" class="blob-num js-line-number js-blob-rnum" data-line-number="165"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC165" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L166" class="blob-num js-line-number js-blob-rnum" data-line-number="166"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC166" class="blob-code blob-code-inner js-file-line">PROCESS</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L167" class="blob-num js-line-number js-blob-rnum" data-line-number="167"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC167" class="blob-code blob-code-inner js-file-line">{</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L168" class="blob-num js-line-number js-blob-rnum" data-line-number="168"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC168" class="blob-code blob-code-inner js-file-line">    # Get all users from Entra ID in batches, including signInActivity</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L169" class="blob-num js-line-number js-blob-rnum" data-line-number="169"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC169" class="blob-code blob-code-inner js-file-line">    $Headers = @{ Authorization = &quot;Bearer $AccessToken&quot; }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L170" class="blob-num js-line-number js-blob-rnum" data-line-number="170"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC170" class="blob-code blob-code-inner js-file-line">    $users = [System.Collections.Generic.List[object]]::new()</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L171" class="blob-num js-line-number js-blob-rnum" data-line-number="171"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC171" class="blob-code blob-code-inner js-file-line">    $batchSize = 500</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L172" class="blob-num js-line-number js-blob-rnum" data-line-number="172"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC172" class="blob-code blob-code-inner js-file-line">    $filter = &quot;userType eq &#39;Member&#39;&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L173" class="blob-num js-line-number js-blob-rnum" data-line-number="173"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC173" class="blob-code blob-code-inner js-file-line">    $select = &quot;id,userPrincipalName,mail,mailNickname,signInActivity&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L174" class="blob-num js-line-number js-blob-rnum" data-line-number="174"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC174" class="blob-code blob-code-inner js-file-line">    $nextLink = &quot;https://graph.microsoft.com/v1.0/users?`$filter=$filter&amp;`$select=$select&amp;`$top=$batchSize&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L175" class="blob-num js-line-number js-blob-rnum" data-line-number="175"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC175" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L176" class="blob-num js-line-number js-blob-rnum" data-line-number="176"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC176" class="blob-code blob-code-inner js-file-line">    while ($nextLink) {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L177" class="blob-num js-line-number js-blob-rnum" data-line-number="177"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC177" class="blob-code blob-code-inner js-file-line">        Write-Log -Level &#39;DEBUG&#39; -Message &quot;Fetching batch: $nextLink&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L178" class="blob-num js-line-number js-blob-rnum" data-line-number="178"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC178" class="blob-code blob-code-inner js-file-line">        try {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L179" class="blob-num js-line-number js-blob-rnum" data-line-number="179"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC179" class="blob-code blob-code-inner js-file-line">            $UsersResponse = Invoke-RestMethod -Uri $nextLink -Headers $Headers</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L180" class="blob-num js-line-number js-blob-rnum" data-line-number="180"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC180" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L181" class="blob-num js-line-number js-blob-rnum" data-line-number="181"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC181" class="blob-code blob-code-inner js-file-line">        catch {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L182" class="blob-num js-line-number js-blob-rnum" data-line-number="182"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC182" class="blob-code blob-code-inner js-file-line">            Write-Log -Level &#39;ERROR&#39; -Message &quot;Failed to fetch users from Microsoft Graph: $nextLink &#8211; $_&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L183" class="blob-num js-line-number js-blob-rnum" data-line-number="183"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC183" class="blob-code blob-code-inner js-file-line">            throw</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L184" class="blob-num js-line-number js-blob-rnum" data-line-number="184"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC184" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L185" class="blob-num js-line-number js-blob-rnum" data-line-number="185"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC185" class="blob-code blob-code-inner js-file-line">        $UsersResponse.value | ForEach-Object { $users.Add($_) }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L186" class="blob-num js-line-number js-blob-rnum" data-line-number="186"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC186" class="blob-code blob-code-inner js-file-line">        if ($UsersResponse.&#39;@odata.nextLink&#39;) {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L187" class="blob-num js-line-number js-blob-rnum" data-line-number="187"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC187" class="blob-code blob-code-inner js-file-line">            $nextLink = $UsersResponse.&#39;@odata.nextLink&#39;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L188" class="blob-num js-line-number js-blob-rnum" data-line-number="188"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC188" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L189" class="blob-num js-line-number js-blob-rnum" data-line-number="189"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC189" class="blob-code blob-code-inner js-file-line">        else {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L190" class="blob-num js-line-number js-blob-rnum" data-line-number="190"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC190" class="blob-code blob-code-inner js-file-line">            $nextLink = $null</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L191" class="blob-num js-line-number js-blob-rnum" data-line-number="191"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC191" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L192" class="blob-num js-line-number js-blob-rnum" data-line-number="192"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC192" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L193" class="blob-num js-line-number js-blob-rnum" data-line-number="193"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC193" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L194" class="blob-num js-line-number js-blob-rnum" data-line-number="194"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC194" class="blob-code blob-code-inner js-file-line">    Write-Log -Level &#39;INFO&#39; -Message &quot;Fetched $($users.Count) users from Entra ID&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L195" class="blob-num js-line-number js-blob-rnum" data-line-number="195"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC195" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L196" class="blob-num js-line-number js-blob-rnum" data-line-number="196"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC196" class="blob-code blob-code-inner js-file-line">    # Get EmailActivityUserDetail report details (if enabled)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L197" class="blob-num js-line-number js-blob-rnum" data-line-number="197"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC197" class="blob-code blob-code-inner js-file-line">    if ($EmailActivityUserDetail) {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L198" class="blob-num js-line-number js-blob-rnum" data-line-number="198"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC198" class="blob-code blob-code-inner js-file-line">        Write-Log -Level &#39;INFO&#39; -Message &quot;Fetching EmailActivityUserDetail report (period: $EmailActivityUserDetailDays)&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L199" class="blob-num js-line-number js-blob-rnum" data-line-number="199"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC199" class="blob-code blob-code-inner js-file-line">        try {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L200" class="blob-num js-line-number js-blob-rnum" data-line-number="200"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC200" class="blob-code blob-code-inner js-file-line">            $EmailActivityUserDetailReportResults = Invoke-RestMethod -Method Get -Uri &quot;https://graph.microsoft.com/v1.0/reports/getEmailActivityUserDetail(period=&#39;$EmailActivityUserDetailDays&#39;)&quot; -Headers $Headers</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L201" class="blob-num js-line-number js-blob-rnum" data-line-number="201"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC201" class="blob-code blob-code-inner js-file-line">            $EmailActivityUserDetailData = $EmailActivityUserDetailReportResults | ConvertFrom-csv</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L202" class="blob-num js-line-number js-blob-rnum" data-line-number="202"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC202" class="blob-code blob-code-inner js-file-line">            $EmailActivityLookup = @{}</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L203" class="blob-num js-line-number js-blob-rnum" data-line-number="203"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC203" class="blob-code blob-code-inner js-file-line">            foreach ($record in $EmailActivityUserDetailData) {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L204" class="blob-num js-line-number js-blob-rnum" data-line-number="204"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC204" class="blob-code blob-code-inner js-file-line">                $EmailActivityLookup[$record.&quot;User Principal Name&quot;] = $record</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L205" class="blob-num js-line-number js-blob-rnum" data-line-number="205"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC205" class="blob-code blob-code-inner js-file-line">            }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L206" class="blob-num js-line-number js-blob-rnum" data-line-number="206"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC206" class="blob-code blob-code-inner js-file-line">            Write-Log -Level &#39;INFO&#39; -Message &quot;EmailActivityUserDetail report loaded &#8211; $($EmailActivityUserDetailData.Count) records&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L207" class="blob-num js-line-number js-blob-rnum" data-line-number="207"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC207" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L208" class="blob-num js-line-number js-blob-rnum" data-line-number="208"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC208" class="blob-code blob-code-inner js-file-line">        catch {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L209" class="blob-num js-line-number js-blob-rnum" data-line-number="209"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC209" class="blob-code blob-code-inner js-file-line">            Write-Log -Level &#39;ERROR&#39; -Message &quot;Failed to fetch EmailActivityUserDetail report: $_&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L210" class="blob-num js-line-number js-blob-rnum" data-line-number="210"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC210" class="blob-code blob-code-inner js-file-line">            $EmailActivityUserDetail = $false</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L211" class="blob-num js-line-number js-blob-rnum" data-line-number="211"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC211" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L212" class="blob-num js-line-number js-blob-rnum" data-line-number="212"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC212" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L213" class="blob-num js-line-number js-blob-rnum" data-line-number="213"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC213" class="blob-code blob-code-inner js-file-line">    </td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L214" class="blob-num js-line-number js-blob-rnum" data-line-number="214"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC214" class="blob-code blob-code-inner js-file-line">    # Loop through users and print sign-in dates, with optional Exchange mailbox check</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L215" class="blob-num js-line-number js-blob-rnum" data-line-number="215"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC215" class="blob-code blob-code-inner js-file-line">    for ($i = 0; $i -lt $users.Count; $i++) {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L216" class="blob-num js-line-number js-blob-rnum" data-line-number="216"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC216" class="blob-code blob-code-inner js-file-line">        $user = $users[$i]</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L217" class="blob-num js-line-number js-blob-rnum" data-line-number="217"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC217" class="blob-code blob-code-inner js-file-line">        $userPrincipalName = $user.userPrincipalName</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L218" class="blob-num js-line-number js-blob-rnum" data-line-number="218"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC218" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L219" class="blob-num js-line-number js-blob-rnum" data-line-number="219"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC219" class="blob-code blob-code-inner js-file-line">        Write-Log -Level &#39;INFO&#39; -Message &quot;Processing user $($i + 1)/$($users.Count): $userPrincipalName&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L220" class="blob-num js-line-number js-blob-rnum" data-line-number="220"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC220" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L221" class="blob-num js-line-number js-blob-rnum" data-line-number="221"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC221" class="blob-code blob-code-inner js-file-line">        # signInActivity is now included in $user</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L222" class="blob-num js-line-number js-blob-rnum" data-line-number="222"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC222" class="blob-code blob-code-inner js-file-line">        $lastInteractiveDate = $null</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L223" class="blob-num js-line-number js-blob-rnum" data-line-number="223"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC223" class="blob-code blob-code-inner js-file-line">        $lastNonInteractiveDate = $null</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L224" class="blob-num js-line-number js-blob-rnum" data-line-number="224"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC224" class="blob-code blob-code-inner js-file-line">        if ($user.signInActivity) {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L225" class="blob-num js-line-number js-blob-rnum" data-line-number="225"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC225" class="blob-code blob-code-inner js-file-line">            $lastInteractiveDate = $user.signInActivity.lastSignInDateTime</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L226" class="blob-num js-line-number js-blob-rnum" data-line-number="226"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC226" class="blob-code blob-code-inner js-file-line">            $lastNonInteractiveDate = $user.signInActivity.lastNonInteractiveSignInDateTime</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L227" class="blob-num js-line-number js-blob-rnum" data-line-number="227"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC227" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L228" class="blob-num js-line-number js-blob-rnum" data-line-number="228"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC228" class="blob-code blob-code-inner js-file-line">        if ($user.mail -and $user.mailNickname) {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L229" class="blob-num js-line-number js-blob-rnum" data-line-number="229"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC229" class="blob-code blob-code-inner js-file-line">            $lastSentMailDate = Get-LastSentMailDate -UserPrincipalName $userPrincipalName -Headers $Headers -EnableExchangeCheck $EnableExchangeCheck</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L230" class="blob-num js-line-number js-blob-rnum" data-line-number="230"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC230" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L231" class="blob-num js-line-number js-blob-rnum" data-line-number="231"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC231" class="blob-code blob-code-inner js-file-line">        else {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L232" class="blob-num js-line-number js-blob-rnum" data-line-number="232"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC232" class="blob-code blob-code-inner js-file-line">            $lastSentMailDate = $null</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L233" class="blob-num js-line-number js-blob-rnum" data-line-number="233"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC233" class="blob-code blob-code-inner js-file-line">            Write-Log -Level &#39;DEBUG&#39; -Message &quot;  Skipping Exchange mailbox check: no mail or mailNickname.&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L234" class="blob-num js-line-number js-blob-rnum" data-line-number="234"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC234" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L235" class="blob-num js-line-number js-blob-rnum" data-line-number="235"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC235" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L236" class="blob-num js-line-number js-blob-rnum" data-line-number="236"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC236" class="blob-code blob-code-inner js-file-line">        # Calculate age in days for each activity type</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L237" class="blob-num js-line-number js-blob-rnum" data-line-number="237"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC237" class="blob-code blob-code-inner js-file-line">        $now = Get-Date</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L238" class="blob-num js-line-number js-blob-rnum" data-line-number="238"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC238" class="blob-code blob-code-inner js-file-line">        $interactiveAgeDays = if ($lastInteractiveDate) { [math]::Ceiling(($now &#8211; [datetime]$lastInteractiveDate).TotalDays) } else { $null }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L239" class="blob-num js-line-number js-blob-rnum" data-line-number="239"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC239" class="blob-code blob-code-inner js-file-line">        $nonInteractiveAgeDays = if ($lastNonInteractiveDate) { [math]::Ceiling(($now &#8211; [datetime]$lastNonInteractiveDate).TotalDays) } else { $null }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L240" class="blob-num js-line-number js-blob-rnum" data-line-number="240"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC240" class="blob-code blob-code-inner js-file-line">        $sentMailAgeDays = if ($lastSentMailDate) { [math]::Ceiling(($now &#8211; [datetime]$lastSentMailDate).TotalDays) } else { $null }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L241" class="blob-num js-line-number js-blob-rnum" data-line-number="241"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC241" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L242" class="blob-num js-line-number js-blob-rnum" data-line-number="242"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC242" class="blob-code blob-code-inner js-file-line">        # Emit import object to PSMA pipeline</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L243" class="blob-num js-line-number js-blob-rnum" data-line-number="243"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC243" class="blob-code blob-code-inner js-file-line">        $obj = @{}</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L244" class="blob-num js-line-number js-blob-rnum" data-line-number="244"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC244" class="blob-code blob-code-inner js-file-line">        $obj.Add(&quot;objectClass&quot;, &quot;user&quot;)</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L245" class="blob-num js-line-number js-blob-rnum" data-line-number="245"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC245" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L246" class="blob-num js-line-number js-blob-rnum" data-line-number="246"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC246" class="blob-code blob-code-inner js-file-line">        if (isValuePresent $userPrincipalName) { $obj.Add(&quot;UserPrincipalName&quot;, $userPrincipalName.ToString()) }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L247" class="blob-num js-line-number js-blob-rnum" data-line-number="247"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC247" class="blob-code blob-code-inner js-file-line">        if (isValuePresent $lastInteractiveDate) { $obj.Add(&quot;LastInteractiveSignIn&quot;, $lastInteractiveDate.ToString()) }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L248" class="blob-num js-line-number js-blob-rnum" data-line-number="248"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC248" class="blob-code blob-code-inner js-file-line">        if (isValuePresent $interactiveAgeDays) { $obj.Add(&quot;LastInteractiveAgeDays&quot;, $interactiveAgeDays.ToString()) }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L249" class="blob-num js-line-number js-blob-rnum" data-line-number="249"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC249" class="blob-code blob-code-inner js-file-line">        if (isValuePresent $lastNonInteractiveDate) { $obj.Add(&quot;LastNonInteractiveSignIn&quot;, $lastNonInteractiveDate.ToString()) }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L250" class="blob-num js-line-number js-blob-rnum" data-line-number="250"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC250" class="blob-code blob-code-inner js-file-line">        if (isValuePresent $nonInteractiveAgeDays) { $obj.Add(&quot;LastNonInteractiveAgeDays&quot;, $nonInteractiveAgeDays.ToString()) }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L251" class="blob-num js-line-number js-blob-rnum" data-line-number="251"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC251" class="blob-code blob-code-inner js-file-line">        if (isValuePresent $lastSentMailDate) { $obj.Add(&quot;LastSentMailDate&quot;, $lastSentMailDate.ToString()) }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L252" class="blob-num js-line-number js-blob-rnum" data-line-number="252"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC252" class="blob-code blob-code-inner js-file-line">        if (isValuePresent $sentMailAgeDays) { $obj.Add(&quot;LastSentMailAgeDays&quot;, $sentMailAgeDays.ToString()) }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L253" class="blob-num js-line-number js-blob-rnum" data-line-number="253"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC253" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L254" class="blob-num js-line-number js-blob-rnum" data-line-number="254"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC254" class="blob-code blob-code-inner js-file-line">        if ($EmailActivityUserDetail) {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L255" class="blob-num js-line-number js-blob-rnum" data-line-number="255"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC255" class="blob-code blob-code-inner js-file-line">            $ActivityDetails = $EmailActivityLookup[$userPrincipalName]</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L256" class="blob-num js-line-number js-blob-rnum" data-line-number="256"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC256" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L257" class="blob-num js-line-number js-blob-rnum" data-line-number="257"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC257" class="blob-code blob-code-inner js-file-line">            if ($ActivityDetails) {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L258" class="blob-num js-line-number js-blob-rnum" data-line-number="258"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC258" class="blob-code blob-code-inner js-file-line">                Write-Log -Level &#39;DEBUG&#39; -Message &quot;  Email activity data found for $userPrincipalName&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L259" class="blob-num js-line-number js-blob-rnum" data-line-number="259"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC259" class="blob-code blob-code-inner js-file-line">                if (isValuePresent $ActivityDetails.&quot;Read Count&quot;) { $obj.Add(&quot;ReportReadCount&quot;, ($ActivityDetails.&quot;Read Count&quot;).ToString()) }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L260" class="blob-num js-line-number js-blob-rnum" data-line-number="260"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC260" class="blob-code blob-code-inner js-file-line">                if (isValuePresent $ActivityDetails.&quot;Receive Count&quot;) { $obj.Add(&quot;ReportReceiveCount&quot;, ($ActivityDetails.&quot;Receive Count&quot;).ToString()) }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L261" class="blob-num js-line-number js-blob-rnum" data-line-number="261"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC261" class="blob-code blob-code-inner js-file-line">                if (isValuePresent $ActivityDetails.&quot;Send Count&quot;) { $obj.Add(&quot;ReportSendCount&quot;, ($ActivityDetails.&quot;Send Count&quot;).ToString()) }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L262" class="blob-num js-line-number js-blob-rnum" data-line-number="262"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC262" class="blob-code blob-code-inner js-file-line">                if (isValuePresent $ActivityDetails.&quot;Last Activity Date&quot;) { $obj.Add(&quot;ReportLastActivityDate&quot;, ($ActivityDetails.&quot;Last Activity Date&quot;).ToString()) }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L263" class="blob-num js-line-number js-blob-rnum" data-line-number="263"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC263" class="blob-code blob-code-inner js-file-line">                if (isValuePresent $ActivityDetails.&quot;Report Period&quot;) { $obj.Add(&quot;ReportPeriod&quot;, ($ActivityDetails.&quot;Report Period&quot;).ToString()) }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L264" class="blob-num js-line-number js-blob-rnum" data-line-number="264"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC264" class="blob-code blob-code-inner js-file-line">            }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L265" class="blob-num js-line-number js-blob-rnum" data-line-number="265"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC265" class="blob-code blob-code-inner js-file-line">            else {</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L266" class="blob-num js-line-number js-blob-rnum" data-line-number="266"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC266" class="blob-code blob-code-inner js-file-line">                Write-Log -Level &#39;WARNING&#39; -Message &quot;  No email activity report data found for $userPrincipalName&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L267" class="blob-num js-line-number js-blob-rnum" data-line-number="267"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC267" class="blob-code blob-code-inner js-file-line">            }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L268" class="blob-num js-line-number js-blob-rnum" data-line-number="268"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC268" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L269" class="blob-num js-line-number js-blob-rnum" data-line-number="269"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC269" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L270" class="blob-num js-line-number js-blob-rnum" data-line-number="270"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC270" class="blob-code blob-code-inner js-file-line">        $obj</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L271" class="blob-num js-line-number js-blob-rnum" data-line-number="271"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC271" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L272" class="blob-num js-line-number js-blob-rnum" data-line-number="272"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC272" class="blob-code blob-code-inner js-file-line">}</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L273" class="blob-num js-line-number js-blob-rnum" data-line-number="273"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC273" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L274" class="blob-num js-line-number js-blob-rnum" data-line-number="274"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC274" class="blob-code blob-code-inner js-file-line">END</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L275" class="blob-num js-line-number js-blob-rnum" data-line-number="275"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC275" class="blob-code blob-code-inner js-file-line">{</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L276" class="blob-num js-line-number js-blob-rnum" data-line-number="276"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC276" class="blob-code blob-code-inner js-file-line">    Write-Log -Level &#39;INFO&#39; -Message &quot;M365 Activity Import complete &#8211; processed $($users.Count) users&quot;</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L277" class="blob-num js-line-number js-blob-rnum" data-line-number="277"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC277" class="blob-code blob-code-inner js-file-line">    Wait-Logging</td>
        </tr>
        <tr>
          <td id="file-mim-m365-activity-psimport-ps1-L278" class="blob-num js-line-number js-blob-rnum" data-line-number="278"></td>
          <td id="file-mim-m365-activity-psimport-ps1-LC278" class="blob-code blob-code-inner js-file-line">}</td>
        </tr>
  </table>
</div>


    </div>

  </div>

</div>

      </div>
      <div class="gist-meta">
        <a href="https://gist.github.com/puttyq/764ba84cd37a689a7ca612df7ef3d502/raw/c85e4d11b1f14d44f9822e207cad90cf04fc8f5f/mim-m365-activity-psimport.ps1" style="float:right" class="Link--inTextBlock">view raw</a>
        <a href="https://gist.github.com/puttyq/764ba84cd37a689a7ca612df7ef3d502#file-mim-m365-activity-psimport-ps1" class="Link--inTextBlock">
          mim-m365-activity-psimport.ps1
        </a>
        hosted with &#10084; by <a class="Link--inTextBlock" href="https://github.com">GitHub</a>
      </div>
    </div>
</div>

</div></figure>



<h3 class="wp-block-heading">App registration requirements</h3>



<p>Create a dedicated app registration in Entra ID. The MA needs three Graph application permissions:</p>



<ul class="wp-block-list">
<li><code>Reports.Read.All</code>&nbsp;— for the email activity report</li>



<li><code>AuditLog.Read.All</code>&nbsp;— for the sign-in activity data</li>



<li>User.Read.All &#8211; for all users data</li>
</ul>



<p>All require admin consent. A certificate credential is recommended over a client secret for production deployments.</p>



<h3 class="wp-block-heading">Authentication</h3>



<figure class="wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler"><div class="wp-block-embed__wrapper">
<style>.gist table { margin-bottom: 0; }</style><div style="tab-size: 8" id="gist146889692" class="gist">
    <div class="gist-file" translate="no" data-color-mode="light" data-light-theme="light">
      <div class="gist-data">
        
<div class="js-gist-file-update-container js-task-list-container">
      <div id="file-authentication-ps1" class="file my-2">
    
    <div itemprop="text"
      class="Box-body p-0 blob-wrapper data type-powershell  "
      style="overflow: auto" tabindex="0" role="region"
      aria-label="authentication.ps1 content, created by puttyq on 11:42AM on March 25."
    >

        
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">

  <template class="js-file-alert-template">
  <div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
  <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
    <span>
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      <a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
    </span>


  <div data-view-component="true" class="flash-action">        <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn">    Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
  <span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
    <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>

  <table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="4" data-paste-markdown-skip data-tagsearch-path="authentication.ps1">
        <tr>
          <td id="file-authentication-ps1-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
          <td id="file-authentication-ps1-LC1" class="blob-code blob-code-inner js-file-line"># Token acquisition in PSImport.ps1 (client credentials flow)</td>
        </tr>
        <tr>
          <td id="file-authentication-ps1-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
          <td id="file-authentication-ps1-LC2" class="blob-code blob-code-inner js-file-line">$tokenBody = @{</td>
        </tr>
        <tr>
          <td id="file-authentication-ps1-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
          <td id="file-authentication-ps1-LC3" class="blob-code blob-code-inner js-file-line">    grant_type    = &quot;client_credentials&quot;</td>
        </tr>
        <tr>
          <td id="file-authentication-ps1-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
          <td id="file-authentication-ps1-LC4" class="blob-code blob-code-inner js-file-line">    client_id     = $settings.ClientId</td>
        </tr>
        <tr>
          <td id="file-authentication-ps1-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
          <td id="file-authentication-ps1-LC5" class="blob-code blob-code-inner js-file-line">    client_secret = $settings.ClientSecret</td>
        </tr>
        <tr>
          <td id="file-authentication-ps1-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
          <td id="file-authentication-ps1-LC6" class="blob-code blob-code-inner js-file-line">    scope         = &quot;https://graph.microsoft.com/.default&quot;</td>
        </tr>
        <tr>
          <td id="file-authentication-ps1-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
          <td id="file-authentication-ps1-LC7" class="blob-code blob-code-inner js-file-line">}</td>
        </tr>
        <tr>
          <td id="file-authentication-ps1-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
          <td id="file-authentication-ps1-LC8" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-authentication-ps1-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
          <td id="file-authentication-ps1-LC9" class="blob-code blob-code-inner js-file-line">$tokenResponse = Invoke-RestMethod \`</td>
        </tr>
        <tr>
          <td id="file-authentication-ps1-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
          <td id="file-authentication-ps1-LC10" class="blob-code blob-code-inner js-file-line">    -Uri &quot;https://login.microsoftonline.com/$($settings.TenantId)/oauth2/v2.0/token&quot; \`</td>
        </tr>
        <tr>
          <td id="file-authentication-ps1-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
          <td id="file-authentication-ps1-LC11" class="blob-code blob-code-inner js-file-line">    -Method Post \`</td>
        </tr>
        <tr>
          <td id="file-authentication-ps1-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
          <td id="file-authentication-ps1-LC12" class="blob-code blob-code-inner js-file-line">    -Body $tokenBody</td>
        </tr>
        <tr>
          <td id="file-authentication-ps1-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
          <td id="file-authentication-ps1-LC13" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-authentication-ps1-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
          <td id="file-authentication-ps1-LC14" class="blob-code blob-code-inner js-file-line">$accessToken = $tokenResponse.access_token</td>
        </tr>
  </table>
</div>


    </div>

  </div>

</div>

      </div>
      <div class="gist-meta">
        <a href="https://gist.github.com/puttyq/764ba84cd37a689a7ca612df7ef3d502/raw/c85e4d11b1f14d44f9822e207cad90cf04fc8f5f/authentication.ps1" style="float:right" class="Link--inTextBlock">view raw</a>
        <a href="https://gist.github.com/puttyq/764ba84cd37a689a7ca612df7ef3d502#file-authentication-ps1" class="Link--inTextBlock">
          authentication.ps1
        </a>
        hosted with &#10084; by <a class="Link--inTextBlock" href="https://github.com">GitHub</a>
      </div>
    </div>
</div>

</div></figure>



<p></p>



<h3 class="wp-block-heading">Merging the two data sources</h3>



<figure class="wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler"><div class="wp-block-embed__wrapper">
<style>.gist table { margin-bottom: 0; }</style><div style="tab-size: 8" id="gist146889692" class="gist">
    <div class="gist-file" translate="no" data-color-mode="light" data-light-theme="light">
      <div class="gist-data">
        
<div class="js-gist-file-update-container js-task-list-container">
      <div id="file-merge-data-ps1" class="file my-2">
    
    <div itemprop="text"
      class="Box-body p-0 blob-wrapper data type-powershell  "
      style="overflow: auto" tabindex="0" role="region"
      aria-label="merge-data.ps1 content, created by puttyq on 11:42AM on March 25."
    >

        
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">

  <template class="js-file-alert-template">
  <div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
  <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
    <span>
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      <a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
    </span>


  <div data-view-component="true" class="flash-action">        <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn">    Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
  <span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
    <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>

  <table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="4" data-paste-markdown-skip data-tagsearch-path="merge-data.ps1">
        <tr>
          <td id="file-merge-data-ps1-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
          <td id="file-merge-data-ps1-LC1" class="blob-code blob-code-inner js-file-line"># 1. Pull email activity report (returns CSV, convert to objects)</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
          <td id="file-merge-data-ps1-LC2" class="blob-code blob-code-inner js-file-line">$emailActivity = Invoke-RestMethod \`</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
          <td id="file-merge-data-ps1-LC3" class="blob-code blob-code-inner js-file-line">    -Uri &quot;https://graph.microsoft.com/v1.0/reports/getEmailActivityUserDetail(period=&#39;D30&#39;)&quot; \`</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
          <td id="file-merge-data-ps1-LC4" class="blob-code blob-code-inner js-file-line">    -Headers @{Authorization=&quot;Bearer $accessToken&quot;} | ConvertFrom-Csv</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
          <td id="file-merge-data-ps1-LC5" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
          <td id="file-merge-data-ps1-LC6" class="blob-code blob-code-inner js-file-line"># Build a lookup hashtable keyed on UPN for fast joins</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
          <td id="file-merge-data-ps1-LC7" class="blob-code blob-code-inner js-file-line">$emailIndex = @{}</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
          <td id="file-merge-data-ps1-LC8" class="blob-code blob-code-inner js-file-line">foreach ($row in $emailActivity) {</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
          <td id="file-merge-data-ps1-LC9" class="blob-code blob-code-inner js-file-line">    $emailIndex[$row.&#39;User Principal Name&#39;] = $row</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
          <td id="file-merge-data-ps1-LC10" class="blob-code blob-code-inner js-file-line">}</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
          <td id="file-merge-data-ps1-LC11" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
          <td id="file-merge-data-ps1-LC12" class="blob-code blob-code-inner js-file-line"># 2. Enumerate users with signInActivity (paged)</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
          <td id="file-merge-data-ps1-LC13" class="blob-code blob-code-inner js-file-line">$usersUri = &quot;https://graph.microsoft.com/v1.0/users?`$select=id,userPrincipalName,displayName,signInActivity&amp;`$top=999&quot;</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
          <td id="file-merge-data-ps1-LC14" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
          <td id="file-merge-data-ps1-LC15" class="blob-code blob-code-inner js-file-line">do {</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
          <td id="file-merge-data-ps1-LC16" class="blob-code blob-code-inner js-file-line">    $page = Invoke-RestMethod -Uri $usersUri -Headers @{Authorization=&quot;Bearer $accessToken&quot;}</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L17" class="blob-num js-line-number js-blob-rnum" data-line-number="17"></td>
          <td id="file-merge-data-ps1-LC17" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L18" class="blob-num js-line-number js-blob-rnum" data-line-number="18"></td>
          <td id="file-merge-data-ps1-LC18" class="blob-code blob-code-inner js-file-line">    foreach ($user in $page.value) {</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L19" class="blob-num js-line-number js-blob-rnum" data-line-number="19"></td>
          <td id="file-merge-data-ps1-LC19" class="blob-code blob-code-inner js-file-line">        $mail = $emailIndex[$user.userPrincipalName]</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L20" class="blob-num js-line-number js-blob-rnum" data-line-number="20"></td>
          <td id="file-merge-data-ps1-LC20" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L21" class="blob-num js-line-number js-blob-rnum" data-line-number="21"></td>
          <td id="file-merge-data-ps1-LC21" class="blob-code blob-code-inner js-file-line">        # Emit combined object to MIM connector space</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L22" class="blob-num js-line-number js-blob-rnum" data-line-number="22"></td>
          <td id="file-merge-data-ps1-LC22" class="blob-code blob-code-inner js-file-line">        $obj = New-Object -Type PSCustomObject</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L23" class="blob-num js-line-number js-blob-rnum" data-line-number="23"></td>
          <td id="file-merge-data-ps1-LC23" class="blob-code blob-code-inner js-file-line">        $obj | Add-Member -NotePropertyName &quot;Anchor-UPN|String&quot;      -NotePropertyValue $user.userPrincipalName</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L24" class="blob-num js-line-number js-blob-rnum" data-line-number="24"></td>
          <td id="file-merge-data-ps1-LC24" class="blob-code blob-code-inner js-file-line">        $obj | Add-Member -NotePropertyName &quot;displayName|String&quot;       -NotePropertyValue $user.displayName</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L25" class="blob-num js-line-number js-blob-rnum" data-line-number="25"></td>
          <td id="file-merge-data-ps1-LC25" class="blob-code blob-code-inner js-file-line">        $obj | Add-Member -NotePropertyName &quot;lastSignInDateTime|String&quot; -NotePropertyValue $user.signInActivity.lastSignInDateTime</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L26" class="blob-num js-line-number js-blob-rnum" data-line-number="26"></td>
          <td id="file-merge-data-ps1-LC26" class="blob-code blob-code-inner js-file-line">        $obj | Add-Member -NotePropertyName &quot;mailLastActivityDate|String&quot; -NotePropertyValue $mail?.&#39;Last Activity Date&#39;</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L27" class="blob-num js-line-number js-blob-rnum" data-line-number="27"></td>
          <td id="file-merge-data-ps1-LC27" class="blob-code blob-code-inner js-file-line">        $obj | Add-Member -NotePropertyName &quot;mailSendCount|String&quot;       -NotePropertyValue $mail?.&#39;Send Count&#39;</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L28" class="blob-num js-line-number js-blob-rnum" data-line-number="28"></td>
          <td id="file-merge-data-ps1-LC28" class="blob-code blob-code-inner js-file-line">        $obj | Add-Member -NotePropertyName &quot;mailReceiveCount|String&quot;    -NotePropertyValue $mail?.&#39;Receive Count&#39;</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L29" class="blob-num js-line-number js-blob-rnum" data-line-number="29"></td>
          <td id="file-merge-data-ps1-LC29" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L30" class="blob-num js-line-number js-blob-rnum" data-line-number="30"></td>
          <td id="file-merge-data-ps1-LC30" class="blob-code blob-code-inner js-file-line">        $obj  # yield to MIM</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L31" class="blob-num js-line-number js-blob-rnum" data-line-number="31"></td>
          <td id="file-merge-data-ps1-LC31" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L32" class="blob-num js-line-number js-blob-rnum" data-line-number="32"></td>
          <td id="file-merge-data-ps1-LC32" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L33" class="blob-num js-line-number js-blob-rnum" data-line-number="33"></td>
          <td id="file-merge-data-ps1-LC33" class="blob-code blob-code-inner js-file-line">    $usersUri = $page.&#39;@odata.nextLink&#39;</td>
        </tr>
        <tr>
          <td id="file-merge-data-ps1-L34" class="blob-num js-line-number js-blob-rnum" data-line-number="34"></td>
          <td id="file-merge-data-ps1-LC34" class="blob-code blob-code-inner js-file-line">} while ($usersUri)</td>
        </tr>
  </table>
</div>


    </div>

  </div>

</div>

      </div>
      <div class="gist-meta">
        <a href="https://gist.github.com/puttyq/764ba84cd37a689a7ca612df7ef3d502/raw/c85e4d11b1f14d44f9822e207cad90cf04fc8f5f/merge-data.ps1" style="float:right" class="Link--inTextBlock">view raw</a>
        <a href="https://gist.github.com/puttyq/764ba84cd37a689a7ca612df7ef3d502#file-merge-data-ps1" class="Link--inTextBlock">
          merge-data.ps1
        </a>
        hosted with &#10084; by <a class="Link--inTextBlock" href="https://github.com">GitHub</a>
      </div>
    </div>
</div>

</div></figure>



<p></p>



<h3 class="wp-block-heading">MIM Sync configuration</h3>



<p>Once the connector space is populated, the activity attributes flow into the MIM metaverse via standard inbound synchronisation rules. From there they are available to:</p>



<ul class="wp-block-list">
<li>Outbound sync rules that set a stale-account flag on the AD user object</li>



<li>MIM Service workflows triggered by a calculated &#8220;days since last activity&#8221; attribute</li>



<li>Reporting exports via MIM&#8217;s built-in reporting or a downstream BI connector</li>
</ul>



<h2 class="wp-block-heading">Getting started</h2>



<p>The full implementation — including the schema script, import script, settings template, and a detailed README covering app registration, privacy settings, and MIM configuration — is available on GitHub.</p>



<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="https://github.com/puttyq/mim-m365-activity">mim-m365-activity github Repo</a></div>
</div>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p>As always, if you hit something interesting in your own tenant — an edge case with guest accounts in the reports data, or a pattern worth sharing — contributions and issues on the repo are welcome.</p>



<p></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1145</post-id>	</item>
		<item>
		<title>Most AI Models Will Confidently Answer a Question That Makes No Sense</title>
		<link>https://www.puttyq.com/most-ai-models-will-confidently-answer-a-question-that-makes-no-sense/</link>
		
		<dc:creator><![CDATA[Almero]]></dc:creator>
		<pubDate>Wed, 25 Mar 2026 10:32:53 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">https://www.puttyq.com/?p=1136</guid>

					<description><![CDATA[On the 12th of March 2026, Peter Gostev released version 2 of Bullshit-Benchmark, and I think it is worth your time. The premise is simple. Feed AI models questions that are fundamentally nonsensical — built on fake frameworks, invented metrics, and fabricated authority — and see what they do. Will they push back or do [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>On the 12th of March 2026, <a href="https://www.linkedin.com/in/peter-gostev/">Peter Gostev</a> released version 2 of <a href="https://petergpt.github.io/bullshit-benchmark/viewer/index.v2.html">Bullshit-Benchmark,</a> and I think it is worth your time. The premise is simple. Feed AI models questions that are fundamentally nonsensical — built on fake frameworks, invented metrics, and fabricated authority — and see what they do. Will they push back or do they answer anyway?</p>



<p>Most models answer. Confidently and in detail. That should make you pause.</p>



<h2 class="wp-block-heading" id="What-This-Benchmark-Is-Actually-Measuring">What This Benchmark Is Actually Measuring</h2>



<p>This is not a benchmark about accuracy; it is about something more important.</p>



<p>Epistemic (mental) integrity. Will the model say: “<em>this does not make sense” </em>or will it accept the premise and build a convincing answer on top of it? In the data, 9,400 questions and posed 94 models; and the outcome is uncomfortable.</p>



<p>Most models accept the premise, no matter how preposterous.</p>



<h2 class="wp-block-heading" id="What-You-See-When-You-Go-Through-the-Viewer">What You See When You Go Through the Viewer</h2>



<p>If you spend time in the viewer — especially in domains like finance — a pattern becomes very clear. Wrong answers do not look wrong. They are well structured, logical and written in clean, confident language. The only place you see that something is wrong is in the provided labels, and if you try to make sense of the original questions (some are absolutely amazing) <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f60a.png" alt="😊" class="wp-smiley" style="height: 1em; max-height: 1em;" />.</p>



<p>Nothing in the answer itself tells you and that is a big issue. These systems do not fail loudly. They fail <em>convincingly</em>.</p>



<h2 class="wp-block-heading" id="Better-Models-Don’t-Remove-the-Problem-—-They-Refine-It">Better Models Don’t Remove the Problem — They Refine It</h2>



<p>When you compare models side by side, something interesting happens. Smaller models tend to be simpler and more obviously wrong; while stronger models produce longer answers, use domain language and build structured reasoning.</p>



<p>But when they are wrong, they are still wrong. Stronger models are just harder to detect.</p>



<p>The improvement is not just capability. It is the quality of the hallucination.</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="700" height="447" data-attachment-id="1139" data-permalink="https://www.puttyq.com/most-ai-models-will-confidently-answer-a-question-that-makes-no-sense/image-2/" data-orig-file="https://www.puttyq.com/wp-content/uploads/2026/03/image-1.png" data-orig-size="1456,930" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://www.puttyq.com/wp-content/uploads/2026/03/image-1-300x192.png" data-large-file="https://www.puttyq.com/wp-content/uploads/2026/03/image-1-700x447.png" src="https://www.puttyq.com/wp-content/uploads/2026/03/image-1-700x447.png" alt="" class="wp-image-1139" srcset="https://www.puttyq.com/wp-content/uploads/2026/03/image-1-700x447.png 700w, https://www.puttyq.com/wp-content/uploads/2026/03/image-1-300x192.png 300w, https://www.puttyq.com/wp-content/uploads/2026/03/image-1-768x491.png 768w, https://www.puttyq.com/wp-content/uploads/2026/03/image-1.png 1456w" sizes="(max-width: 700px) 100vw, 700px" /></figure>



<h2 class="wp-block-heading" id="Reasoning-Makes-It-More-Persuasive-—-Not-More-Correct">Reasoning Makes It More Persuasive — Not More Correct</h2>



<p>One of the more subtle patterns is how models “reason.” They build step-by-step explanations, reference plausible concepts and construct a clean narrative. The goal is to create better answers, but the scary trend is when they go of the rails, reasoning just makes the hallucination more convincing.</p>



<p>Even when the conclusion is incorrect, there are no obvious breaks or contradictions. From the outside, it looks like thinking. But what you are seeing is smart language generation, not verified reasoning.</p>



<p>And we (humans) trust structured reasoning far more than they should or would like to believe.</p>



<h2 class="wp-block-heading" id="The-Default-Behavior-Is-to-Agree">The Default Behavior Is to Agree</h2>



<p>This is not new, we have all “felt” this, but the biggest takeaway for me was the models are not just wrong sometimes; they are overly agreeable by design.</p>



<p>They accept the premise of the question and move forward. Even when the framework does not exist, the numbers are meaningless or the logic is fundamentally broken.</p>



<p>They do not challenge by default, they comply. And this become most difficult when the questions are nuanced.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="700" height="160" data-attachment-id="1142" data-permalink="https://www.puttyq.com/most-ai-models-will-confidently-answer-a-question-that-makes-no-sense/image-4/" data-orig-file="https://www.puttyq.com/wp-content/uploads/2026/03/image-3.png" data-orig-size="1336,306" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://www.puttyq.com/wp-content/uploads/2026/03/image-3-300x69.png" data-large-file="https://www.puttyq.com/wp-content/uploads/2026/03/image-3-700x160.png" src="https://www.puttyq.com/wp-content/uploads/2026/03/image-3-700x160.png" alt="" class="wp-image-1142" srcset="https://www.puttyq.com/wp-content/uploads/2026/03/image-3-700x160.png 700w, https://www.puttyq.com/wp-content/uploads/2026/03/image-3-300x69.png 300w, https://www.puttyq.com/wp-content/uploads/2026/03/image-3-768x176.png 768w, https://www.puttyq.com/wp-content/uploads/2026/03/image-3.png 1336w" sizes="(max-width: 700px) 100vw, 700px" /></figure>



<h3 class="wp-block-heading" id="Why-This-Matters-More-Than-It-Seems">Why This Matters More Than It Seems</h3>



<p>In the short term, this is a systems problem. Garbage in → confident output.</p>



<p>In finance, legal, governance, or security workflows, that is a real risk. But the longer-term effect may be worse. If you interact with a system that validates your thinking, reinforces your arguments and tells you repeatedly that you are right. You do not become better. You become more certain.</p>



<p>And certainty without correction is dangerous.</p>



<p>We are not as smart as we think we are. We need friction and challenge by design. We need to be told when something does not make sense.</p>



<h2 class="wp-block-heading" id="Anthropic-Is-Doing-Something-Different">Anthropic Is Doing Something Different</h2>



<p>This is where the benchmark becomes more interesting. The leaderboard is not close. I know these “benchmarks” will shift drastically over time, but at least for now the top positions are dominated by Claude models. And more importantly, the improvement over time is consistent. It looks like a deliberate design choice.</p>



<p>From the data and behaviour, it seems clear:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Anthropic is optimizing for models that push back.</p>
</blockquote>



<p>Not just models that answer well.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="700" height="336" data-attachment-id="1141" data-permalink="https://www.puttyq.com/most-ai-models-will-confidently-answer-a-question-that-makes-no-sense/image-3/" data-orig-file="https://www.puttyq.com/wp-content/uploads/2026/03/image-2.png" data-orig-size="1458,700" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://www.puttyq.com/wp-content/uploads/2026/03/image-2-300x144.png" data-large-file="https://www.puttyq.com/wp-content/uploads/2026/03/image-2-700x336.png" src="https://www.puttyq.com/wp-content/uploads/2026/03/image-2-700x336.png" alt="" class="wp-image-1141" srcset="https://www.puttyq.com/wp-content/uploads/2026/03/image-2-700x336.png 700w, https://www.puttyq.com/wp-content/uploads/2026/03/image-2-300x144.png 300w, https://www.puttyq.com/wp-content/uploads/2026/03/image-2-768x369.png 768w, https://www.puttyq.com/wp-content/uploads/2026/03/image-2.png 1458w" sizes="(max-width: 700px) 100vw, 700px" /></figure>



<h3 class="wp-block-heading" id="Why-That-Matters">Why That Matters</h3>



<p>When you go through the same questions in the viewer, you start to see it. Claude models are more likely to question the premise, resist nonsensical framing and avoid confidently elaborating on bad inputs.</p>



<p>That is not just a capability difference. This appears to be a philosophy.</p>



<p>Helpful vs honest. And those are not always the same thing.</p>



<h2 class="wp-block-heading" id="The-Real-Divide-Is-Not-Capability-—-It-Is-Behaviour">The Real Divide Is Not Capability — It Is Behaviour</h2>



<p>We often talk about models in terms of size, speed, cost or benchmarks; but this benchmark highlights something more fundamental.</p>



<p>There are two paths emerging.</p>



<p>One optimizes for responsiveness, agreeableness and user satisfaction. The other optimizes for correctness of premise, willingness to challenge and epistemic discipline.</p>



<p>And those paths lead to very different outcomes.</p>



<h2 class="wp-block-heading" id="Why-This-Connects-to-AI-Governance">Why This Connects to AI Governance</h2>



<p>I have written recently about the challenge of managing AI risk. This benchmark makes that problem more concrete. If a model cannot identify that a question does not make sense in a controlled environment, what happens in production?</p>



<p>What happens when it is reviewing contracts, analysing financial models, embedded in agent workflows or operating without human oversight.</p>



<p>The risk is not just wrong answers, but confidently wrong answers that look correct.</p>



<p>And in regulated environments, the hardest nonsense to detect looks exactly like normal work that includes precise numbers, formal language and named frameworks. That is everyday enterprise content.</p>



<h2 class="wp-block-heading" id="What-I-Take-From-This">What I Take From This</h2>



<p>The takeaway for me is simple. The biggest risk is not that models hallucinate. It is that they agree. They agree with bad inputs. They agree with flawed assumptions. They agree with nonsense. And they do it in a way that feels intelligent.</p>



<h2 class="wp-block-heading" id="Where-I-Am-Leaning">Where I Am Leaning</h2>



<p>We grow through honesty, not flattery. A system that challenges you — even if it slows you down — is more valuable than one that always agrees.</p>



<p>From what I am seeing right now, Anthropic is leaning into that. Their models are not just more capable in this benchmark. They are more willing to say: <em>this does not make sense.</em> That is a trait I value, and it is why I am leaning towards into Claude in its current iterations (even though I am not a fan of vendor lock-in).</p>



<h3 class="wp-block-heading" id="The-Broader-Concern">The Broader Concern</h3>



<p>We are getting used to answers that sound right. Well-articulated and confident.</p>



<p>I can feel myself getting lazy, that is a big reason why I asked to write down my thoughts again. And we are starting to treat these answers as truth (because they are so well articulated and quickly presented).</p>



<p>We are not slowing down to question or testing assumptions. We are not asking whether something actually makes sense. Instead, we are quietly outsourcing that responsibility to systems that, in many cases, are designed to please. That is a dangerous combination.</p>



<p>Human beings already tend toward reinforcing our own views. We gravitate toward confirmation, toward being right, toward narratives that make us feel certain. Now we are pairing that with machines that agree quickly, validate confidently, and rarely challenge the premise.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><em>What emerges is not intelligence, but a feedback loop of affirmation.</em></p>
</blockquote>



<p>And if we are not careful, we do not just automate work — we automate self-deception.</p>



<p>The more articulate these systems become, the less obvious that risk is. The answers sound better. The reasoning looks cleaner. The confidence feels justified. But underneath that, the same problem remains: the absence of friction, the absence of challenge, the absence of a simple but critical response — <em>this does not make sense.</em></p>



<p>The real risk is not that AI replaces thinking. It is that it quietly removes the resistance that makes thinking honest. And that is something worth paying attention to.</p>



<p></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1136</post-id>	</item>
		<item>
		<title>Self-Sovereign Identity: The Idea That Refuses to Die</title>
		<link>https://www.puttyq.com/self-sovereign-identity-the-idea-that-refuses-to-die/</link>
		
		<dc:creator><![CDATA[Almero]]></dc:creator>
		<pubDate>Wed, 18 Mar 2026 09:26:15 +0000</pubDate>
				<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[Security]]></category>
		<guid isPermaLink="false">https://www.puttyq.com/?p=1132</guid>

					<description><![CDATA[Self-Sovereign Identity (SSI) is the idea that individuals should own and control their digital identity, rather than relying on centralized platforms or governments to manage it. In most online systems today, identity is fragmented. Every service maintains its own account database. Your identity is duplicated across hundreds of systems — each storing passwords, personal information, [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Self-Sovereign Identity (SSI) is the idea that individuals should own and control their digital identity, rather than relying on centralized platforms or governments to manage it. In most online systems today, identity is fragmented. Every service maintains its own account database. Your identity is duplicated across hundreds of systems — each storing passwords, personal information, and credentials.</p>



<p>SSI proposes a different model.</p>



<p>Instead of services storing your identity, trusted organizations issue verifiable credentials that you hold in a digital wallet. When a service needs proof of something — your age, your qualification, your employment — you present the credential. The service verifies the issuer’s signature rather than storing your personal data.</p>



<p>The goal is simple: identity that belongs to the individual, not the platform.</p>



<p>Technologies such as Decentralized Identifiers (DIDs) and Verifiable Credentials (VCs) make this possible. At least in theory.</p>



<h2 class="wp-block-heading" id="Why-SSI-Matters">Why SSI Matters</h2>



<p>The appeal of SSI becomes clearer when you consider the current trajectory of digital identity. Identity today is increasingly controlled by three powerful actors:</p>



<ul class="wp-block-list">
<li>Governments issuing national digital identities</li>



<li>Technology platforms controlling login ecosystems</li>



<li>Financial systems enforcing regulatory identity verification</li>
</ul>



<p>At the same time, the infrastructure around identity is expanding rapidly:</p>



<ul class="wp-block-list">
<li>smartphones as identity devices</li>



<li>biometric authentication</li>



<li>AI-driven verification</li>



<li>social platforms</li>



<li>digital wallets</li>
</ul>



<p>Together, these ingredients could create the largest surveillance infrastructure in human history if implemented without strong privacy safeguards. SSI emerged partly as a response to this risk.</p>



<p>The core idea was to design an identity architecture where</p>



<ul class="wp-block-list">
<li>individuals control their credentials</li>



<li>services only receive the minimum information required</li>



<li>identity verification does not require centralized databases</li>



<li>correlation of identity transactions becomes difficult</li>
</ul>



<p>In other words, SSI attempts to build privacy into the architecture of digital identity itself.</p>



<h2 class="wp-block-heading" id="Why-SSI-Is-Hard">Why SSI Is Hard</h2>



<p>Despite its elegance, SSI has struggled to gain real-world adoption. The technology is no longer the main barrier. The standards now exist:</p>



<ul class="wp-block-list">
<li>W3C Decentralized Identifiers</li>



<li>W3C Verifiable Credentials</li>



<li>Selective disclosure cryptography</li>



<li>Digital identity wallets</li>
</ul>



<p>The challenge lies elsewhere. Identity is not just a technical system. It is a legal, economic, and governance system. SSI disrupts several entrenched incentives:</p>



<ul class="wp-block-list">
<li>Platforms benefit from controlling identity because identity drives data ownership and user lock-in.</li>



<li>Governments benefit from centralized identity systems because they support regulation, taxation, and law enforcement.</li>



<li>Organizations prefer centralized identity providers because they simplify risk management and compliance.</li>
</ul>



<p>SSI asks all of these actors to relinquish some control. That is a difficult ask. And it askes that all of these and other platforms agree on standards of how this will be implemented, managed and secured.</p>



<h2 class="wp-block-heading" id="When-the-Vision-Faltered:-The-Sovrin-Case">When the Vision Faltered: The Sovrin Case</h2>



<p>The most ambitious attempt to build a true SSI ecosystem was the Sovrin Network. Launched in the mid-2010s, Sovrin aimed to create a global public utility for decentralized identity. It was supported by a non-profit foundation and designed around principles of self-sovereignty, privacy, and open governance. Many early identity professionals — including investors and technologists — saw it as a foundational layer for the next generation of the internet. But the project struggled.</p>



<p>Several factors contributed to its decline:</p>



<ul class="wp-block-list">
<li>unclear economic sustainability</li>



<li>limited enterprise adoption</li>



<li>complex governance structures</li>



<li>competition from government digital identity initiatives</li>
</ul>



<p>Eventually the Sovrin Foundation dissolved and the project was effectively abandoned. It was not a failure of the underlying idea. It was a reminder that identity systems succeed only when technical, economic, and institutional incentives align.</p>



<h2 class="wp-block-heading" id="The-Hybrid-Model-Emerging-Today">The Hybrid Model Emerging Today</h2>



<p>Interestingly, the ideas behind SSI did not disappear. Instead, they were absorbed into emerging government digital identity programs. (Ironic)</p>



<p>Many countries are now experimenting with identity architectures that combine elements of both models. These systems often include:</p>



<ul class="wp-block-list">
<li>government-issued digital identity wallets</li>



<li>verifiable credentials for documents</li>



<li>selective disclosure mechanisms</li>



<li>cryptographic verification</li>
</ul>



<p>The European Union’s Digital Identity Wallet under eIDAS 2.0 is the most prominent example. In this model, citizens hold credentials in a wallet and can share them with services when required. But the trust framework remains anchored in the state. This is not pure self-sovereign identity. It is better described as state-anchored decentralized identity — a compromise between privacy ideals and regulatory realities.</p>



<h2 class="wp-block-heading" id="Real-Working-Examples">Real Working Examples</h2>



<p>While full SSI ecosystems remain rare, several real-world deployments show elements of the model working. British Columbia in Canada has issued verifiable digital credentials for businesses and government services. The Netherlands has developed privacy-preserving attribute sharing through the IRMA system. The European Union is preparing large-scale deployment of national digital identity wallets.</p>



<p>These initiatives demonstrate that the technology works. What remains unresolved is how much sovereignty individuals will actually retain.</p>



<h2 class="wp-block-heading" id="A-Personal-Reflection">A Personal Reflection</h2>



<p>I invested in the Sovrin project in 2017, before its public launch. At the time, the vision was compelling. A decentralized identity layer for the internet. Privacy by design and user control as a foundational principle. I had hope for SSI (and commercial gain of course).</p>



<p>Today the conversation is different.</p>



<p>Digital identity is accelerating — but largely under the control of governments and major technology platforms. The irony is that many of the technologies originally designed to protect individuals from centralized identity power are now being deployed inside systems that may strengthen it.</p>



<p>We are starting to see this shift in very practical ways.</p>



<p>Governments around the world are introducing national age verification requirements to regulate access to online services. Social platforms are being pushed to implement age verification and identity checks before allowing access to certain content or communities. In California, proposals around operating system level age verification suggest that identity controls could move even deeper into the technology stack — into the devices themselves. And, at the same time, a new challenge is emerging: AI agents acting on behalf of people and organizations.</p>



<p>As autonomous agents interact with services, signing transactions, or making decisions, the identity layer becomes even more important. Someone — or something — must be accountable. The chain of responsibility must be clear.</p>



<ul class="wp-block-list">
<li>Who issued the agent’s credentials?</li>



<li>Who authorized its actions?</li>



<li>Who is responsible when it makes a mistake?</li>
</ul>



<p>These questions push us further toward stronger identity infrastructure. And once identity becomes the control mechanism for access, compliance, safety, and automation, it becomes extremely powerful.</p>



<p>This is why the principles behind Self-Sovereign Identity still matter.</p>



<p>SSI was never just about convenience. It was about architectural safeguards — designing identity systems that minimize surveillance, reduce centralized control, and give individuals meaningful ownership over their credentials.</p>



<p>Do I think we will get a pure form of SSI?</p>



<p>Probably not. The legal, economic, and political incentives all point toward state-anchored or platform-controlled identity ecosystems.</p>



<p>But I still hope that the principles behind SSI survive inside those systems — selective disclosure, minimal data sharing, portable credentials, and user agency. Because once identity becomes fully centralized — embedded into governments, platforms, devices, and AI systems — reversing that architecture will be extraordinarily difficult.</p>



<p>This is why the conversation around SSI still matters.</p>



<p>Do I think we will get it?</p>



<p>No.</p>



<p>Do I hope?</p>



<p>Always.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1132</post-id>	</item>
		<item>
		<title>The AI Supply Chain Nobody Has Mapped Yet</title>
		<link>https://www.puttyq.com/the-ai-supply-chain-nobody-has-mapped-yet/</link>
		
		<dc:creator><![CDATA[Almero]]></dc:creator>
		<pubDate>Thu, 12 Mar 2026 14:07:54 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://www.puttyq.com/?p=1124</guid>

					<description><![CDATA[Over the past few months (and last week) some recurring developments caught my attention. Firstly, the European Union’s acceleration of efforts to reduce dependency on U.S. technology platforms, including cloud infrastructure and payment networks such as Visa and Mastercard. And as of last week, the U.S. Department of Defense reportedly ordered agencies to phase out [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Over the past few months (and last week) some recurring developments caught my attention. Firstly, the European Union’s acceleration of efforts to reduce dependency on U.S. technology platforms, including cloud infrastructure and payment networks such as Visa and Mastercard. And as of last week, the U.S. Department of Defense reportedly ordered agencies to phase out Anthropic models within a defined window. Although these two events are not directly linked the do reveal something very interesting. Modern organizations no longer fully understand their technology dependencies. And in the age of AI, that problem is becoming more serious.</p>



<h2 class="wp-block-heading">The Old Model of Vendor Risk</h2>



<p>For most of the last twenty years vendor risk was relatively straightforward. If your company used a vendor, you signed a contract. Procurement reviewed it, security assessed it and legal negotiated it. You performed a vendor risk review and maybe you looked at SOC2 or ISO certifications. Your dependency list was essentially the list of vendors you paid. Not 100% perfect, but manageable. That model worked reasonably well in the enterprise software era, but has now started to break down in the AI era.</p>



<h2 class="wp-block-heading">AI Is Not a Product</h2>



<p>Many organizations still think about AI vendors the same way they think about software vendors. But AI systems are rarely standalone products. They are compositional systems, and this is even more so once you introduce agentic work.</p>



<p>A single workflow might look something like this:</p>



<p>Application<br>→ SaaS platform<br>→ analytics engine<br>→ AI inference service<br>→ model provider<br>→ cloud infrastructure</p>



<p>Your organization may never sign a contract with the model provider. But your workflow depends on it. Your vendor’s vendor’s vendor may ultimately be the one actually running the model. Very few organizations have mapped (or even tried to understand) that chain.</p>



<h2 class="wp-block-heading">The Illusion of Approval</h2>



<p>Enterprises often believe they have approved their AI vendors because we trial, POC, vet and review them. What they have actually approved is an technical interface &#8211; a very smart and intelligent one at that &#8211; that itself could consist of:</p>



<p>→ multiple models<br>→ different providers<br>→ dynamic routing<br>→ fallback systems<br>→ region-specific infrastructure<br>→ who know what storage and data collection<br>Even the vendor itself may change models over time for various reasons (sentiment, commercials, rivalry, etc.) That means the underlying dependency can shift without the customer (you) even realizing it. Security teams believe they know what is running. In many cases they don’t (they can’t).</p>



<h2 class="wp-block-heading">Why AI Dependencies Are Hard to See</h2>



<p>When SaaS adoption exploded, we saw the rise of shadow IT. But shadow IT left a trace. You could track a new SaaS product by things like, a new login, a new identity integration, new network traffic or a new invoice.</p>



<p>Eventually security teams caught up using a combination of CASBs, network monitoring, SSO enforcement, spending analysis, etc.</p>



<p>But, once again, AI is different. AI usage can, and often is, be embedded inside other platforms. It can be invoked dynamically through APIs. It can sit inside analytics features, copilots, or agent workflows. Traditional logs may not even tell you which model was used. The dependency is now invisible.</p>



<h2 class="wp-block-heading">A Real-World Stress Test</h2>



<p>The Anthropic phase-out scenario inside the U.S. government is essentially a stress test that we can watch in near real-time. Imagine discovering that one of your AI dependencies must disappear within months. Not just replaced, but removed. That sounds manageable until you remember something important.</p>



<p>Models are not interchangeable.</p>



<p>Switching model providers changes things like:</p>



<p>→ output structure<br>→ latency characteristics<br>→ safety filtering<br>→ fallback systems<br>→ reasoning behavior<br>→ hallucination profiles</p>



<p>Applications built around one model may behave very differently with another. There is a very good likelihood that agent workflows may break and security assumptions may change.</p>



<p>Rotating an API key is easy, untangling hidden dependencies is not and re-testing everything that was built is not.</p>



<h2 class="wp-block-heading">The European Perspective</h2>



<p>Across the Atlantic, Europe is confronting a similar dependency problem from a different angle. European policymakers have become increasingly concerned about structural dependence on foreign digital infrastructure. Cloud platforms are a major example.</p>



<p><a href="https://finance.yahoo.com/news/eu-pushes-tech-sovereignty-264-195504771.html">Today much of Europe’s digital economy runs on infrastructure operated</a> by a small number of U.S. hyperscale providers — primarily Amazon Web Services, Microsoft Azure, and Google Cloud. <a href="https://www.europarl.europa.eu/RegData/etudes/STUD/2025/778576/ECTI_STU(2025)778576_EN.pdf">European Parliament research</a> has highlighted several concerns around this concentration.</p>



<p>First, market concentration/dominance. A handful of hyper-scalers control a large share of global cloud infrastructure capacity and investment.</p>



<p>Second, strategic dependency. Cloud platforms now underpin critical sectors such as banking, government services, healthcare, research, industrial supply chains and now AI development environments. When those systems depend on foreign-owned infrastructure, policymakers increasingly see it as a strategic risk.</p>



<p>Third, jurisdictional exposure. It is not secret that European and US lawmakers sometimes have veery difference views on policy. Even when European data is stored inside the EU, providers headquartered elsewhere may still be subject to foreign legal frameworks. This creates tension with European data protection frameworks such as GDPR.</p>



<p>Finally, there is the economic dimension. A large portion of European cloud spending ultimately flows to companies outside Europe. As cloud adoption increases — particularly with AI workloads — that financial flow continues to grow.</p>



<p>For policymakers, this raises concerns about long-term technological competitiveness and economic sovereignty.</p>



<h2 class="wp-block-heading">Payments Tell the Same Story</h2>



<p>The same structural dependency appears in payments infrastructure. A large share of European card payments still flows through Visa and Mastercard, both U.S. networks. This has triggered discussions about building alternative European payment systems. Again, the goal is not necessarily to remove these providers, but to ensure that critical infrastructure does not depend entirely on systems controlled elsewhere. Ok, maybe it also has to do with the 61% of payment processing handled by US companies when considering the <a href="https://www.euronews.com/my-europe/2026/03/03/how-close-is-the-eu-to-break-free-from-visa-and-mastercards-grip">4.7 trillion USD spent in 2023.</a></p>



<h2 class="wp-block-heading">AI Will Deepen These Dependencies</h2>



<p>AI adds complete new layer to this picture. Training and running modern models requires enormous computing infrastructure, specialized chips, Large-scale data centers and distributed inference platforms. The companies that operate these environments today are largely the same hyperscale providers already dominating cloud infrastructure. That means the AI boom may reinforce existing infrastructure dependencies rather than diversify them.</p>



<p>Which brings us back to your enterprise.</p>



<p>Traditional vendor lock-in was mostly technical. You depended on a database, a storage system, an operating system. Migrating away was painful, but predictable.</p>



<p>AI lock-in is different. It is behavioral lock-in. Not impossible, just must must harder.</p>



<p>Applications built around a particular model assume certain patterns of how the model responds, how it structures output and how it interprets prompts. Changing the model <em>can</em> change the behavior of the entire system. That is a much deeper form of dependency.</p>



<h2 class="wp-block-heading">So What Can Organizations Actually Do</h2>



<p>None of this means companies should stop adopting AI. But it does mean organizations should start thinking about AI dependencies more deliberately.</p>



<ul class="wp-block-list">
<li><strong>Push for Transparency: </strong>The first step is pushing vendors for transparency, or just pushing for some sanity. Sidenote &#8211; I am hopeful with statements like <a href="https://ico.org.uk/media2/fb1br3d4/20260223-iewg-joint-statement-on-ai-generated-imagery.pdf">these </a>(but that is for another time). Organizations should <s>ask</s> <em>demand </em>vendors disclose which models they rely on, where those models run, and whether fallback providers are used if a model becomes unavailable. Many vendors struggle to answer these questions clearly today, which already tells you something about the visibility you actually have into the systems you depend on.</li>



<li><strong>Own the Control Points: </strong>The second step is identifying the control points you actually own. While companies may not control the AI provider itself, they can control the surrounding systems. This includes the data entering models, the orchestration layers that call them, agent frameworks or automation pipelines, and the systems that consume the outputs. If the only place where governance exists is at the vendor contract level, then operational visibility is likely very limited.</li>



<li><strong>Do a Kill Test: </strong>The third step is running dependency kill tests. This simply means simulating the removal of an important AI dependency. Disable the API key or block the endpoint in a controlled environment and observe what happens. Some systems will break immediately, while others may silently degrade. In many cases organizations discover dependencies they did not even realize existed.</li>



<li><strong>Understand Execution: </strong>Organizations should map how AI is actually used inside their environment rather than simply listing vendors. (Can anyone see a new governance role here &#8211; CAIO ). The important questions are which systems are calling models, which endpoints are involved, and what data is being sent.</li>



<li><strong>Data Control:</strong> Finally, companies need to understand what happens to their data once it enters an AI system. Where is it stored? How long is it retained? Is it used for training or evaluation? Can it be removed if needed? Data often passes through several layers such as inference services, safety filters, and monitoring systems before a response is returned, which means copies of that data may exist in more places than expected. If an organization does not understand how its data can be removed from a system, then it does not fully understand the dependency it has on that platform.</li>
</ul>



<h2 class="wp-block-heading">The 10-Second Takeaway</h2>



<p>The AI revolution is not just changing how software works. It is changing how technology dependencies are structured. Enterprises once depended on software vendors and now they depend on entire technology supply chains. Vendors, their vendors, model providers, cloud inference platforms, training ecosystems and more.</p>



<p>Most organizations still believe they understand their dependencies. But many only understand the surface layer. And the next forced migration may not come with a six-month warning.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1124</post-id>	</item>
		<item>
		<title>Code Is Cheap. Thinking Is Expensive.</title>
		<link>https://www.puttyq.com/code-is-cheap-thinking-is-expensive/</link>
		
		<dc:creator><![CDATA[Almero]]></dc:creator>
		<pubDate>Wed, 04 Mar 2026 14:47:12 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://www.puttyq.com/?p=1118</guid>

					<description><![CDATA[For years, the software industry behaved as if writing code was the hardest part of building technology. Today, with AI generating thousands of lines of code in seconds, we are finally forced to confront a reality many experienced engineers already knew: Code was never the real bottleneck. The difficult parts of building software have always [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>For years, the software industry behaved as if writing code was the hardest part of building technology. Today, with AI generating thousands of lines of code in seconds, we are finally forced to confront a reality many experienced engineers already knew: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Code was never the real bottleneck.</p>
</blockquote>



<p>The difficult parts of building software have always lived somewhere else. And AI is starting to making that clearer than ever.</p>



<h1 class="wp-block-heading">The Illusion: Software = Code</h1>



<p>When people think about software development, they imagine developers typing furiously, solving complex problems through clever code. But modern development has not worked like that for a long time. Frameworks like Rails, Laravel, Spring, and .NET already automated huge portions of the development process. Cloud platforms removed infrastructure complexity. Low-code platforms made it possible to build applications without writing code at all. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Even before AI, <strong>writing code had become one of the easiest part of building software.</strong> Yet many still measured progress in <strong>lines of code produced</strong>. AI has accelerated that illusion.</p>
</blockquote>



<h1 class="wp-block-heading">Code Is Cheap Now</h1>



<p>With modern AI tools:</p>



<ul class="wp-block-list">
<li>Entire APIs can be generated in minutes</li>



<li>UI prototypes appear in seconds</li>



<li>Boilerplate disappears almost completely</li>



<li>Refactoring can happen automatically</li>
</ul>



<p>The marginal cost of producing code is approaching zero. This continues to impress the heck out me as a technologist and to most of us at first glance appears as pure upside. But removing friction changes how people behave.</p>



<h1 class="wp-block-heading">Friction Forces Good Decisions</h1>



<p>When implementing a a product feature takes weeks, teams have to ask hard questions:</p>



<ul class="wp-block-list">
<li>Do we actually need this feature?</li>



<li>Is the design correct?</li>



<li>What will the maintenance cost be?</li>



<li>Will this introduce security or operational risk?</li>
</ul>



<p>Because implementation is expensive, bad ideas (have to) die early. Constraints force discipline. They force clarity and prioritization. </p>



<p>When the cost of building something drops to almost nothing, that discipline disappears. Why debate a feature when AI can generate 4 different versions in ten minutes?</p>



<p>And that is where the real problem begins.</p>



<h1 class="wp-block-heading">The Explosion of Software Complexity</h1>



<p>Lower friction does not reduce complexity. In fact it multiplies it. You have felt it, you are five prompt deep into a project and you brain switches off and you end up in a death prompting cycle of &#8220;make secure, built with no error, check you work, blah blah&#8221;.</p>



<p>Economists call this <strong>Jevons Paradox</strong>: when something becomes more efficient to produce, we do not use less of it — we find more ways to use it. </p>



<p>The same will happen with software. More teams will build internal tools, more departments will automate workflows and more experimental products will be launched. And many of them will never be properly designed, governed, or maintained.</p>



<p>Side note here: Think about the explosion of low-code platforms like Power Platform just a few years ago where thousands of little apps were developed with little thought about the entire lifecycle of software development (and this was just on a single banking customer we worked with).</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>We will not get less software. We will get more software, faster — and often worse.</p>
</blockquote>



<h1 class="wp-block-heading">The Hard Parts Were Never Code</h1>



<p>The difficult parts of building software have always been:</p>



<ul class="wp-block-list">
<li>Understanding the real problem</li>



<li>Defining the right system boundaries</li>



<li>Designing data models</li>



<li>Thinking through failure scenarios</li>
</ul>



<p>Then during implementation:</p>



<ul class="wp-block-list">
<li>Architecture design</li>



<li>Distributed systems thinking</li>



<li>Identity, security, and permission models</li>
</ul>



<p>And finally designing systems that survive change.</p>



<p>These require deep thinking, not code generation and structured process.  AI can help write functions — and increasingly even generate impressive applications &#8211; but it cannot replace judgment.</p>



<h1 class="wp-block-heading">The Hidden Risk: The Loss of Learning</h1>



<p>There is another subtle danger. When developers write code themselves, they simulate the system in their heads:</p>



<ul class="wp-block-list">
<li>What happens if this fails?</li>



<li>What assumptions does this function make?</li>



<li>How does this data move through the system?</li>
</ul>



<p>That mental simulation is the real training. If developers rely entirely on generated code, they risk skipping the thinking step. The result is a small number of experienced engineers end up responsible for understanding and fixing the systems everyone else generates. The same small team of people who always carried the complexity will now carry even more of it.</p>



<h1 class="wp-block-heading">The Real Bottlenecks of Software</h1>



<p><em>(And we haven&#8217;t even reached the business layer yet)</em></p>



<p>Even if AI makes code generation nearly free, the real constraints remain:</p>



<ul class="wp-block-list">
<li>Product vision</li>



<li>Architecture</li>



<li>Security</li>



<li>Governance</li>



<li>User trust</li>



<li>Distribution</li>



<li>Adoption</li>



<li>Change Management</li>
</ul>



<p>The Apple App Store and Google Play Store together contain more than four million applications. Yet industry analyses consistently show that the majority of those apps receive fewer than a thousand downloads. A tiny fraction of products capture most user attention while millions quietly sit unused. AI will not solve discovery, it will simply make it easier to create more unused software.</p>



<h1 class="wp-block-heading">The Future of Engineers</h1>



<p>This brings us to question &#8211; what happens to the software development job? Why all the layoffs? Ironically, if this all plays out it most likely means more engineer will be needed in the long term, not less. </p>



<p>But crucially roles will shift.</p>



<p>The most valuable engineers will not be those who produce the most code. They will be the ones who can:</p>



<ul class="wp-block-list">
<li>Understand complex systems</li>



<li>Design resilient architectures</li>



<li>Make strong product decisions</li>



<li>Govern technology responsibly</li>
</ul>



<p>In a world where code is cheap, thinking becomes the scarce resource.</p>



<h1 class="wp-block-heading">A Paradox Emerging in the Industry</h1>



<p>Interestingly, some early patterns in the industry reinforce this idea. Across the technology sector we see a surprising paradox:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Narrative</th><th>Reality</th></tr></thead><tbody><tr><td>AI replaces developers</td><td>Companies hire more engineers</td></tr><tr><td>Coding becomes automated</td><td>Architecture becomes more important</td></tr><tr><td>Productivity skyrockets</td><td>Complexity explodes</td></tr><tr><td>Junior roles disappear</td><td>Companies realize they must rebuild the talent pipeline</td></tr></tbody></table></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>For example, <a href="https://fortune.com/2026/02/13/tech-giant-ibm-tripling-gen-z-entry-level-hiring-according-to-chro-rewriting-jobs-ai-era/">IBM recently announced plans to triple its entry-level hiring</a>, including <a href="https://www.cio.com/article/4134276/ibm-looks-beyond-short-term-ai-gains-tripling-entry-level-hiring.html">software engineers</a>. Their leadership warned that companies that stop hiring junior engineers today risk creating future shortages of experienced technical leaders.</p>



<p>At the same time, Anthropic — the company behind Claude Code, one of the most advanced AI coding systems — <a href="https://www.anthropic.com/careers/jobs">is aggressively hiring engineers</a>, with reports suggesting more than 100 open roles. Despite building tools designed to automate coding, they still need engineers to design systems, evaluate AI output, and maintain architecture.</p>



<p>Another interesting signal is compensation. AI companies are offering some of the highest engineering salaries in history, with reports of median compensation around $570k and some packages approaching $700k.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="626" height="348" data-attachment-id="1119" data-permalink="https://www.puttyq.com/code-is-cheap-thinking-is-expensive/image/" data-orig-file="https://www.puttyq.com/wp-content/uploads/2026/03/image.png" data-orig-size="626,348" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://www.puttyq.com/wp-content/uploads/2026/03/image-300x167.png" data-large-file="https://www.puttyq.com/wp-content/uploads/2026/03/image.png" src="https://www.puttyq.com/wp-content/uploads/2026/03/image.png" alt="" class="wp-image-1119" srcset="https://www.puttyq.com/wp-content/uploads/2026/03/image.png 626w, https://www.puttyq.com/wp-content/uploads/2026/03/image-300x167.png 300w" sizes="(max-width: 626px) 100vw, 626px" /></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>We are also seeing companies announce layoffs while still hiring technical talent. For example, Block reduced its workforce but continues hiring AI engineers to expand its capabilities. (Interestingly this might also be because of other pressure, but the AI efficiency narrative is a much better public announcement.)</p>



<p>The pattern is becoming clear: AI may automate parts of coding, but the need for engineers who can design systems, govern complexity, and make sound technical decisions is only increasing.</p>



<h2 class="wp-block-heading">Final Thoughts</h2>



<p>In the end, there remains a noticeable disconnect between the public narrative and what is actually happening inside the industry. Headlines often suggest a future where AI replaces developers and software builds itself. Yet at the same time, companies are hiring engineers, investing in junior talent, and paying record salaries for technical expertise.</p>



<p>Reality rarely behaves like a perfectly predictable system — but neither are we living inside the chaos of the three-body problem where outcomes are impossible to understand. The signals are visible if we look carefully.</p>



<p>Time will ultimately tell how far AI will reshape software development. But for now, one thing is clear: the story being told in public and the decisions being made inside organizations do not entirely match. And that gap may be one of the most interesting signals of all.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1118</post-id>	</item>
		<item>
		<title>Agentic AI Is Scaling Faster Than Governance (2025 AI Agent Index)</title>
		<link>https://www.puttyq.com/agentic-ai-is-scaling-faster-than-governance-2025-ai-agent-index/</link>
		
		<dc:creator><![CDATA[Almero]]></dc:creator>
		<pubDate>Sat, 28 Feb 2026 17:00:56 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://www.puttyq.com/?p=1054</guid>

					<description><![CDATA[On 19 Feb 2026, the updated AI Agent Index (maintained by researchers from MIT, Cornell University and more) was published with new data tracking the evolution of agentic AI systems. While reading up on my previous post Agents of Chaos: The Unquantifiable Risk at the Heart of the AI Revolution, I came across the paper [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>On 19 Feb 2026, the updated AI Agent Index (maintained by researchers from MIT, Cornell University and more) was published with new data tracking the evolution of agentic AI systems. While reading up on my previous post <a href="https://www.puttyq.com/agents-of-chaos-the-unquantifiable-risk-at-the-heart-of-the-ai-revolution/">Agents of Chaos: The Unquantifiable Risk at the Heart of the AI Revolution</a>, I came across the paper and associated site.</p>



<p>If you have not looked at it yet, it is worth your time. It is one of the clearest snapshots we have of the current state of autonomous AI and where it is going.</p>



<p>Here are some useful links:</p>



<ul class="wp-block-list">
<li><a href="https://aiagentindex.mit.edu/">AI Agent Index</a> (Website)</li>



<li><a href="https://arxiv.org/abs/2602.17753">The 2025 AI Agent Index: Documenting Technical and Safety Features of Deployed Agentic AI Systems (Paper)</a></li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>The summary is simple.</p>



<ul class="wp-block-list">
<li>Autonomy is accelerating.</li>



<li>Governance is not keeping pace.</li>



<li>And that gap is going to matter.</li>
</ul>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">The Acceleration of Autonomy</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="700" height="310" data-attachment-id="1056" data-permalink="https://www.puttyq.com/agentic-ai-is-scaling-faster-than-governance-2025-ai-agent-index/fig_combined_timeline/" data-orig-file="https://www.puttyq.com/wp-content/uploads/2026/02/fig_combined_timeline.png" data-orig-size="1763,782" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="fig_combined_timeline" data-image-description="" data-image-caption="" data-medium-file="https://www.puttyq.com/wp-content/uploads/2026/02/fig_combined_timeline-300x133.png" data-large-file="https://www.puttyq.com/wp-content/uploads/2026/02/fig_combined_timeline-700x310.png" src="https://www.puttyq.com/wp-content/uploads/2026/02/fig_combined_timeline-700x310.png" alt="" class="wp-image-1056" srcset="https://www.puttyq.com/wp-content/uploads/2026/02/fig_combined_timeline-700x310.png 700w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_combined_timeline-300x133.png 300w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_combined_timeline-768x341.png 768w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_combined_timeline-1536x681.png 1536w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_combined_timeline.png 1763w" sizes="(max-width: 700px) 100vw, 700px" /></figure>



<div style="height:10px" aria-hidden="true" class="wp-block-spacer"></div>



<p>In the last two years alone, 24 of the 30 tracked agents were either released or received major updates. That is not incremental innovation, that is acceleration (a gold rush).</p>



<ul class="wp-block-list">
<li>Browser agents are now operating at L4–L5 autonomy. That means they do not just assist. They execute. They plan. They take multi-step actions with limited intervention once started.</li>
</ul>



<ul class="wp-block-list">
<li>Enterprise agents follow a similar path. They may be designed conservatively, but once deployed into workflows, they begin to operate at far higher levels of independence.</li>
</ul>



<div style="height:10px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="700" height="433" data-attachment-id="1060" data-permalink="https://www.puttyq.com/agentic-ai-is-scaling-faster-than-governance-2025-ai-agent-index/fig_distribution_autonomy_levels/" data-orig-file="https://www.puttyq.com/wp-content/uploads/2026/02/fig_distribution_autonomy_levels.png" data-orig-size="1751,1082" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="fig_distribution_autonomy_levels" data-image-description="" data-image-caption="" data-medium-file="https://www.puttyq.com/wp-content/uploads/2026/02/fig_distribution_autonomy_levels-300x185.png" data-large-file="https://www.puttyq.com/wp-content/uploads/2026/02/fig_distribution_autonomy_levels-700x433.png" src="https://www.puttyq.com/wp-content/uploads/2026/02/fig_distribution_autonomy_levels-700x433.png" alt="" class="wp-image-1060" srcset="https://www.puttyq.com/wp-content/uploads/2026/02/fig_distribution_autonomy_levels-700x433.png 700w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_distribution_autonomy_levels-300x185.png 300w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_distribution_autonomy_levels-768x475.png 768w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_distribution_autonomy_levels-1536x949.png 1536w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_distribution_autonomy_levels.png 1751w" sizes="(max-width: 700px) 100vw, 700px" /></figure>



<div style="height:10px" aria-hidden="true" class="wp-block-spacer"></div>



<p>We are no longer dealing with tools that help you think (like simple chat bots), we are deploying systems that act on your behalf. This changes everything.</p>



<p>With the acceleration and the increase and autonomy &#8211; they key question become how we maintain security, governance and visibility (not to mention control).</p>



<h2 class="wp-block-heading">The Safety (Governance) Disclosure Gap</h2>



<p>Of the 13 agents classified at frontier autonomy levels, only four disclose any meaningful agentic safety evaluations:</p>



<ul class="wp-block-list">
<li>ChatGPT Agent</li>



<li>OpenAI Codex</li>



<li>Claude Code</li>



<li>Gemini 2.5 Computer Use</li>
</ul>



<div style="height:10px" aria-hidden="true" class="wp-block-spacer"></div>



<p>The broader numbers are more revealing.</p>



<ul class="wp-block-list">
<li>25 of 30 agents disclose no internal safety results.</li>



<li>23 of 30 provide no third-party testing information.</li>
</ul>



<div style="height:10px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Developers are very transparent about what <strong>their agents can do</strong>, and they are far less transparent about <strong>how those agents were tested</strong>.</p>



<p>For enterprises operating under ISO 27001, ISO 42001, SOC 2, NIST, or sector regulation, this is not academic. It is a procurement risk. It is a board-level question waiting to be asked.</p>



<p>Just have a look at the &#8220;Safety&#8221; section of the disclosure map.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="700" height="259" data-attachment-id="1058" data-permalink="https://www.puttyq.com/agentic-ai-is-scaling-faster-than-governance-2025-ai-agent-index/fig_bubble_matrix/" data-orig-file="https://www.puttyq.com/wp-content/uploads/2026/02/fig_bubble_matrix-scaled.png" data-orig-size="2560,948" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="fig_bubble_matrix" data-image-description="" data-image-caption="" data-medium-file="https://www.puttyq.com/wp-content/uploads/2026/02/fig_bubble_matrix-300x111.png" data-large-file="https://www.puttyq.com/wp-content/uploads/2026/02/fig_bubble_matrix-700x259.png" src="https://www.puttyq.com/wp-content/uploads/2026/02/fig_bubble_matrix-700x259.png" alt="" class="wp-image-1058" srcset="https://www.puttyq.com/wp-content/uploads/2026/02/fig_bubble_matrix-700x259.png 700w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_bubble_matrix-300x111.png 300w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_bubble_matrix-768x284.png 768w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_bubble_matrix-1536x569.png 1536w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_bubble_matrix-2048x758.png 2048w" sizes="(max-width: 700px) 100vw, 700px" /></figure>



<h2 class="wp-block-heading">The Fragmented Responsibility Chain and Concentrated Model Providers</h2>



<p>Most agents are not vertically integrated systems, they sit on top of foundation models provided primarily be three main players, OpenAI, Anthropic and Google.</p>



<p>The agent builder adds orchestration and scaffolding. The enterprise adds configuration and deployment context. When something goes wrong, responsibility is distributed across layers. Who is to blame, the model, the agent framework, the tools integrated or some configuration operator?<br>This reminds me very much of the early days cloud adoption when abstraction created acceleration but could not solve bad architecture, design or configuration. In the case of cloud, shared responsibility models took years to mature in clarity. Contracts evolved. Audit rights became standard. Logging expectations stabilised. Data processing and storage became clear.</p>



<p>We are now at the early stage of that same cycle — but with systems that can act autonomously. <strong>No single entity clearly owns end-to-end accountability</strong>, and the is different from cloud today.</p>



<p>That is a yet again a governance problem, not a technical one.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="700" height="407" data-attachment-id="1059" data-permalink="https://www.puttyq.com/agentic-ai-is-scaling-faster-than-governance-2025-ai-agent-index/fig_model_mcp_by_category/" data-orig-file="https://www.puttyq.com/wp-content/uploads/2026/02/fig_model_mcp_by_category.png" data-orig-size="1712,996" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="fig_model_mcp_by_category" data-image-description="" data-image-caption="" data-medium-file="https://www.puttyq.com/wp-content/uploads/2026/02/fig_model_mcp_by_category-300x175.png" data-large-file="https://www.puttyq.com/wp-content/uploads/2026/02/fig_model_mcp_by_category-700x407.png" src="https://www.puttyq.com/wp-content/uploads/2026/02/fig_model_mcp_by_category-700x407.png" alt="" class="wp-image-1059" srcset="https://www.puttyq.com/wp-content/uploads/2026/02/fig_model_mcp_by_category-700x407.png 700w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_model_mcp_by_category-300x175.png 300w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_model_mcp_by_category-768x447.png 768w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_model_mcp_by_category-1536x894.png 1536w, https://www.puttyq.com/wp-content/uploads/2026/02/fig_model_mcp_by_category.png 1712w" sizes="(max-width: 700px) 100vw, 700px" /></figure>



<h2 class="wp-block-heading">The Web Identity Problem</h2>



<p>Browser-based agents introduce another layer of complexity. They simply act “on behalf of a user.” In practice, that often means bypassing traditional bot controls and ignoring conventional web signaling mechanisms like robots.txt. That also creates a huge question of identity and accountability for actions (but that is a whole other discussion). It is therefor shocking that only one agent in the index uses cryptographic request signing &#8211; ChatGPT Agent. That is crazy to consider.</p>



<p>We do not yet have a mature identity model for autonomous agents operating across the web. If an agent logs into a system, accesses content, or triggers transactions, what is its identity? How is that identity verified? How is it attested? How is it audited?</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="700" height="248" data-attachment-id="1061" data-permalink="https://www.puttyq.com/agentic-ai-is-scaling-faster-than-governance-2025-ai-agent-index/agent_ecosystem_control/" data-orig-file="https://www.puttyq.com/wp-content/uploads/2026/02/agent_ecosystem_control.png" data-orig-size="2220,786" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="agent_ecosystem_control" data-image-description="" data-image-caption="" data-medium-file="https://www.puttyq.com/wp-content/uploads/2026/02/agent_ecosystem_control-300x106.png" data-large-file="https://www.puttyq.com/wp-content/uploads/2026/02/agent_ecosystem_control-700x248.png" src="https://www.puttyq.com/wp-content/uploads/2026/02/agent_ecosystem_control-700x248.png" alt="" class="wp-image-1061" srcset="https://www.puttyq.com/wp-content/uploads/2026/02/agent_ecosystem_control-700x248.png 700w, https://www.puttyq.com/wp-content/uploads/2026/02/agent_ecosystem_control-300x106.png 300w, https://www.puttyq.com/wp-content/uploads/2026/02/agent_ecosystem_control-768x272.png 768w, https://www.puttyq.com/wp-content/uploads/2026/02/agent_ecosystem_control-1536x544.png 1536w, https://www.puttyq.com/wp-content/uploads/2026/02/agent_ecosystem_control-2048x725.png 2048w" sizes="(max-width: 700px) 100vw, 700px" /></figure>



<div style="height:11px" aria-hidden="true" class="wp-block-spacer"></div>



<p>In identity governance terms, we are allowing non-human actors to execute high-impact workflows without fully established control frameworks. We would never allow that for a human privileged account.</p>



<p>Yet we are beginning to allow it for AI.</p>



<h2 class="wp-block-heading">The Governance Opportunity</h2>



<p>This is not only a risk story. It is an opportunity story. As autonomy increases, the differentiator will not be raw power. It will be governability.</p>



<p>Enterprises, our businesses and our customers will need to start asking harder questions:</p>



<ul class="wp-block-list">
<li>What autonomy level does this agent operate at?</li>



<li>What red-teaming and safety testing was performed?</li>



<li>Is there third-party validation?</li>



<li>Are outbound actions signed and traceable?</li>



<li>Is there an auditable trail of decisions?</li>



<li>Is there a clear kill-switch?</li>
</ul>



<div style="height:11px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Vendors that can answer those questions confidently will win in regulated markets. Traditional IGA and PAM processes and platforms will need to evolve to keep up with demand for accountability (while allowing for speed of execution).</p>



<p>We are entering a new domain, not mere human and non-human, but a merging of:</p>



<ul class="wp-block-list">
<li>Agent identity.</li>



<li>Agent permissions.</li>



<li>Agent attestation.</li>



<li>Agent auditability.</li>
</ul>



<div style="height:10px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Those who work in governance, security, identity, and compliance should not see this as a threat. It is the next frontier of structured control and visibility.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1054</post-id>	</item>
		<item>
		<title>Agents of Chaos: The Unquantifiable Risk at the Heart of the AI Revolution</title>
		<link>https://www.puttyq.com/agents-of-chaos-the-unquantifiable-risk-at-the-heart-of-the-ai-revolution/</link>
		
		<dc:creator><![CDATA[Almero]]></dc:creator>
		<pubDate>Fri, 27 Feb 2026 15:45:45 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://www.puttyq.com/?p=309</guid>

					<description><![CDATA[We are living through a technological revolution. Not the kind we can neatly benchmark. Not the kind we can fully model. And certainly not the kind we yet understand. “AI” &#8211; particularly LLMs and increasingly all kinds of “agentic” systems are already delivering measurable gains in productivity, efficiency, and scale. Entire workflows are being compressed. [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>We are living through a technological revolution. Not the kind we can neatly benchmark. Not the kind we can fully model. And certainly not the kind we yet understand.</p>



<p>“AI” &#8211; particularly LLMs and increasingly all kinds of “agentic” systems are already delivering measurable gains in productivity, efficiency, and scale. Entire workflows are being compressed. Teams are being reimagined. Decision-making is being augmented—and in some cases, quietly replaced. And yet, beneath this acceleration, there is something else:</p>



<p>A growing, systemic unease.</p>



<h3 class="wp-block-heading"><strong>From Tools to Actors</strong></h3>



<p>For decades, we integrated software as “tools” &#8211; deterministic systems operating within clearly defined rules. That paradigm is under pressure and in many ways potentially over.</p>



<p>With agentic AI, we no longer deploy tools. We are introducing “actors” into our systems.</p>



<p>These actors have agency and they have access. They can consume and use other tools (email, file systems, APIs, shells), communicate with humans and other agents, persist vast amount of memory across interactions and now make decisions that have real-world consequences.</p>



<p>And critically—they can act outside of our immediate visibility.<br>We see this as an game changing benefit and the possibilities are considerable, but the pace is exposing the fundamentals of the technology and our ability to effectively use it. At least for now.</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots"/>



<h1 class="wp-block-heading"><strong>What Research Currently Actually Shows</strong></h1>



<p>A recent preprint, <em><a href="https://arxiv.org/abs/2602.20021" data-type="link" data-id="https://arxiv.org/abs/2602.20021">“Agents of Chaos”</a></em> (published 23 Feb &#8211; Cornell University), provides one of the clearest early looks at what happens when you combine LLMs with autonomy, tools, and real environments. For a two-week period, this wasn’t theoretical. A team of researchers deployed autonomous agents in a live environment with access to email accounts, Discord, file systems, shell execution and persistent memory.</p>



<p>Over the two-week red-teaming exercise, they observed 11 case studies concerning failure modes. Real behaviors emerging from real work setups.</p>



<h3 class="wp-block-heading">1. <strong>Unauthorized Agent Compliance</strong></h3>



<p>Agents followed instructions from individuals who were not their owners. This breaks one of the most fundamental assumptions in security: that authority and identity are tightly coupled. In practice, it means:</p>



<ul class="wp-block-list">
<li>An attacker doesn’t need system access</li>



<li>They just need to convincingly <em>ask</em></li>
</ul>



<h3 class="wp-block-heading">2. <strong>Sensitive Data Leakage</strong></h3>



<p>Agents disclosed sensitive information when prompted in the right way. Not because they were explicitly told but because context was misinterpreted, boundaries were poorly defined or objectives overrode constraints. This creates a new kind of data exfiltration vector: conversational leakage through delegated agents/actors/intelligence.</p>



<h3 class="wp-block-heading">3. <strong>Destructive System Actions</strong></h3>



<p>In some cases, agents executed system-level commands that caused damage. Not maliciously, but simply as a side effect of misaligned goals, over-broad permissions or an incomplete understanding of the consequence.</p>



<p>This is the equivalent of giving a junior engineer root access—and no supervision.</p>



<h3 class="wp-block-heading">4. <strong>Denial of Service &amp; Resource Exhaustion</strong></h3>



<p>Agents unintentionally entered loops or executed tasks that led to runaway compute usage, system slowdowns and self-inflicted denial-of-service conditions. Traditional systems fail predictably while agentic systems can fail dynamically.</p>



<h3 class="wp-block-heading">5. <strong>Identity Spoofing</strong></h3>



<p>Agents were susceptible to impersonation of identities and could then be convinced that a malicious actor was a legitimate user or another agent was authoritative. This undermines the very concept of trust in distributed systems.</p>



<h3 class="wp-block-heading">6. <strong>Cross-Agent Contamination</strong></h3>



<p>Perhaps one of the most concerning findings from the research was the agents could “propagate” unsafe behaviors to other agents. Bad practices didn’t stay isolated in the two-week study, they spread. This introduced a new category of risk that researchers called “Emergent systemic failure through agent interaction”.</p>



<h3 class="wp-block-heading">7. <strong>False Reporting of Task Completion</strong></h3>



<p>In multiple cases, agents reported that tasks were successfully completed, while the underlying system contradicted those claims. This is not just a hallucination problem. It is a verification, observability and governance failure. If you cannot trust system reporting,<br>you cannot trust system outcomes.</p>



<h1 class="wp-block-heading"><strong>This Is Not Hypothetical Risk</strong></h1>



<p>The paper is explicit: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>These behaviors establish “security-, privacy-, and governance-relevant vulnerabilities in realistic deployment settings.” And they emerge <strong>only when you combine</strong>: LLMs + autonomy + tools + communication.</p>
</blockquote>



<p>Ironically, this is exactly what the industry is racing toward and big tech promising.</p>



<p>We are facing a real and material yet currently unbounded risk that cannot be quantified—not because it is negligible, but because it is emergent, breaking traditional models based on known failure modes, historical data, and predictable behavior, and instead arising from non-deterministic decisions, context-dependent actions, and complex multi-agent interactions.<br><br>For now it seems like “Agents of Chaos” is not just a provocative title &#8211; it is an accurate description of where we are.</p>



<p></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">309</post-id>	</item>
		<item>
		<title>Frustrated trying to Maintain PCNS in a Multi-Domain Environment</title>
		<link>https://www.puttyq.com/frustrated-trying-to-maintain-pcns-in-a-multi-domain-environment/</link>
		
		<dc:creator><![CDATA[Almero]]></dc:creator>
		<pubDate>Thu, 18 Nov 2021 07:31:22 +0000</pubDate>
				<category><![CDATA[Identity Management]]></category>
		<guid isPermaLink="false">https://www.puttyq.com/?p=280</guid>

					<description><![CDATA[For those of you who have deployed Microsoft Password Change Notification Service (PCNS) services in a large domain (or as in this case a complete forest with many domains), you will know the mission of ensuring that PCNS is installed everywhere. Sure you can use GPO or ConfigMgr to ensure the service is installed on [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>For those of you who have deployed Microsoft Password Change Notification Service (PCNS) services in a large domain (or as in this case a complete forest with many domains), you will know the mission of ensuring that PCNS is installed everywhere. </p>



<p>Sure you can use GPO or ConfigMgr to ensure the service is installed on all DC, but sometimes one (or many) is missed or fails. This results in password changes being &#8220;lost&#8221;.</p>



<p>I have found the simplest way to fix this frustration is to use a simple PowerShell script to enumerate the forest, domains and domain controllers to test if the service is installed and active.</p>



<p>The following script is a simple example of how this can be achieved. At Integralis we have turned this into a monitor that reports into our monitoring platforms to automate the process (for large environments that does not have our agents on all domain controllers).</p>



<p>The approach is generic and will work for any service.</p>



<figure class="wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler"><div class="wp-block-embed__wrapper">
<style>.gist table { margin-bottom: 0; }</style><div style="tab-size: 8" id="gist113106240" class="gist">
    <div class="gist-file" translate="no" data-color-mode="light" data-light-theme="light">
      <div class="gist-data">
        
<div class="js-gist-file-update-container js-task-list-container">
      <div id="file-tester-service-status-ps1" class="file my-2">
    
    <div itemprop="text"
      class="Box-body p-0 blob-wrapper data type-powershell  "
      style="overflow: auto" tabindex="0" role="region"
      aria-label="Tester - Service Status.ps1 content, created by puttyq on 06:07AM on November 18, 2021."
    >

        
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">

  <template class="js-file-alert-template">
  <div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
  <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
    <span>
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      <a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
    </span>


  <div data-view-component="true" class="flash-action">        <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn">    Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
  <span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
    <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>

  <table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="4" data-paste-markdown-skip data-tagsearch-path="Tester - Service Status.ps1">
        <tr>
          <td id="file-tester-service-status-ps1-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
          <td id="file-tester-service-status-ps1-LC1" class="blob-code blob-code-inner js-file-line">&lt;#</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
          <td id="file-tester-service-status-ps1-LC2" class="blob-code blob-code-inner js-file-line"> .SYNOPSIS</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
          <td id="file-tester-service-status-ps1-LC3" class="blob-code blob-code-inner js-file-line"> Tester &#8211; Service Status.ps1 &#8211; Tester to check the status of all PCNSSVC services on all current domain context DCs. </td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
          <td id="file-tester-service-status-ps1-LC4" class="blob-code blob-code-inner js-file-line"> .DESCRIPTION</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
          <td id="file-tester-service-status-ps1-LC5" class="blob-code blob-code-inner js-file-line"> The scripts will look for all current domain context DC and query the status of the PCNSSVC service.</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
          <td id="file-tester-service-status-ps1-LC6" class="blob-code blob-code-inner js-file-line"> Possible status messages include:</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
          <td id="file-tester-service-status-ps1-LC7" class="blob-code blob-code-inner js-file-line"> &#8211; running</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
          <td id="file-tester-service-status-ps1-LC8" class="blob-code blob-code-inner js-file-line"> &#8211; stopped</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
          <td id="file-tester-service-status-ps1-LC9" class="blob-code blob-code-inner js-file-line"> &#8211; starting</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
          <td id="file-tester-service-status-ps1-LC10" class="blob-code blob-code-inner js-file-line"> &#8211; unreachable (the server is not avaiable on the network)</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
          <td id="file-tester-service-status-ps1-LC11" class="blob-code blob-code-inner js-file-line"> .LINK</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
          <td id="file-tester-service-status-ps1-LC12" class="blob-code blob-code-inner js-file-line"> Version 1.0 &#8211; Base features</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
          <td id="file-tester-service-status-ps1-LC13" class="blob-code blob-code-inner js-file-line"> Version 1.3 &#8211; Added nice(r) message for unreachable</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
          <td id="file-tester-service-status-ps1-LC14" class="blob-code blob-code-inner js-file-line">#&gt;</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
          <td id="file-tester-service-status-ps1-LC15" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
          <td id="file-tester-service-status-ps1-LC16" class="blob-code blob-code-inner js-file-line">Import-Module ActiveDirectory</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L17" class="blob-num js-line-number js-blob-rnum" data-line-number="17"></td>
          <td id="file-tester-service-status-ps1-LC17" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L18" class="blob-num js-line-number js-blob-rnum" data-line-number="18"></td>
          <td id="file-tester-service-status-ps1-LC18" class="blob-code blob-code-inner js-file-line">$ADForest = Get-ADForest</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L19" class="blob-num js-line-number js-blob-rnum" data-line-number="19"></td>
          <td id="file-tester-service-status-ps1-LC19" class="blob-code blob-code-inner js-file-line">$ADForestDomainNamingMaster = $ADForest.DomainNamingMaster</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L20" class="blob-num js-line-number js-blob-rnum" data-line-number="20"></td>
          <td id="file-tester-service-status-ps1-LC20" class="blob-code blob-code-inner js-file-line">$ADForestDomains = $ADForest.Domains</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L21" class="blob-num js-line-number js-blob-rnum" data-line-number="21"></td>
          <td id="file-tester-service-status-ps1-LC21" class="blob-code blob-code-inner js-file-line">$ADForestSchemaMaster = $ADForest.SchemaMaster</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L22" class="blob-num js-line-number js-blob-rnum" data-line-number="22"></td>
          <td id="file-tester-service-status-ps1-LC22" class="blob-code blob-code-inner js-file-line">$ADInfo = Get-ADDomain</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L23" class="blob-num js-line-number js-blob-rnum" data-line-number="23"></td>
          <td id="file-tester-service-status-ps1-LC23" class="blob-code blob-code-inner js-file-line">$ADDomainDNSRoot = $ADInfo.DNSRoot</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L24" class="blob-num js-line-number js-blob-rnum" data-line-number="24"></td>
          <td id="file-tester-service-status-ps1-LC24" class="blob-code blob-code-inner js-file-line">$ADDomainInfrastructureMaster = $ADInfo.InfrastructureMaster</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L25" class="blob-num js-line-number js-blob-rnum" data-line-number="25"></td>
          <td id="file-tester-service-status-ps1-LC25" class="blob-code blob-code-inner js-file-line">$ADDomainPDCEmulator = $ADInfo.PDCEmulator</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L26" class="blob-num js-line-number js-blob-rnum" data-line-number="26"></td>
          <td id="file-tester-service-status-ps1-LC26" class="blob-code blob-code-inner js-file-line">$ADDomainReadOnlyReplicaDirectoryServers = $ADInfo.ReadOnlyReplicaDirectoryServers</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L27" class="blob-num js-line-number js-blob-rnum" data-line-number="27"></td>
          <td id="file-tester-service-status-ps1-LC27" class="blob-code blob-code-inner js-file-line">$ADDomainReplicaDirectoryServers = $ADInfo.ReplicaDirectoryServers</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L28" class="blob-num js-line-number js-blob-rnum" data-line-number="28"></td>
          <td id="file-tester-service-status-ps1-LC28" class="blob-code blob-code-inner js-file-line">$ADDomainRIDMaster = $ADInfo.RIDMaster</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L29" class="blob-num js-line-number js-blob-rnum" data-line-number="29"></td>
          <td id="file-tester-service-status-ps1-LC29" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L30" class="blob-num js-line-number js-blob-rnum" data-line-number="30"></td>
          <td id="file-tester-service-status-ps1-LC30" class="blob-code blob-code-inner js-file-line">Write-Verbose “Discovering Domain Controllers in the AD Forest $ADForestName `r “</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L31" class="blob-num js-line-number js-blob-rnum" data-line-number="31"></td>
          <td id="file-tester-service-status-ps1-LC31" class="blob-code blob-code-inner js-file-line">ForEach ($Domain in $ADForestDomains)</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L32" class="blob-num js-line-number js-blob-rnum" data-line-number="32"></td>
          <td id="file-tester-service-status-ps1-LC32" class="blob-code blob-code-inner js-file-line"> { ## OPEN ForEach Domain in ADForestDomains</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L33" class="blob-num js-line-number js-blob-rnum" data-line-number="33"></td>
          <td id="file-tester-service-status-ps1-LC33" class="blob-code blob-code-inner js-file-line">   $DomainDCs = Get-ADDomainController -filter * -server $Domain</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L34" class="blob-num js-line-number js-blob-rnum" data-line-number="34"></td>
          <td id="file-tester-service-status-ps1-LC34" class="blob-code blob-code-inner js-file-line">   ForEach ($DC in $DomainDCs)</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L35" class="blob-num js-line-number js-blob-rnum" data-line-number="35"></td>
          <td id="file-tester-service-status-ps1-LC35" class="blob-code blob-code-inner js-file-line">    { ## OPEN ForEach DC in DomainDCs</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L36" class="blob-num js-line-number js-blob-rnum" data-line-number="36"></td>
          <td id="file-tester-service-status-ps1-LC36" class="blob-code blob-code-inner js-file-line">      $DCName = $DC.HostName</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L37" class="blob-num js-line-number js-blob-rnum" data-line-number="37"></td>
          <td id="file-tester-service-status-ps1-LC37" class="blob-code blob-code-inner js-file-line">      Write-Verbose “Adding $DCName to ForestDC list `r “</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L38" class="blob-num js-line-number js-blob-rnum" data-line-number="38"></td>
          <td id="file-tester-service-status-ps1-LC38" class="blob-code blob-code-inner js-file-line">      [array] $ForestDCs += $DC.HostName</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L39" class="blob-num js-line-number js-blob-rnum" data-line-number="39"></td>
          <td id="file-tester-service-status-ps1-LC39" class="blob-code blob-code-inner js-file-line">     } ## CLOSE ForEach DC in DomainDCs</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L40" class="blob-num js-line-number js-blob-rnum" data-line-number="40"></td>
          <td id="file-tester-service-status-ps1-LC40" class="blob-code blob-code-inner js-file-line">  } ## CLOSE ForEach Domain in ADForestDomains</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L41" class="blob-num js-line-number js-blob-rnum" data-line-number="41"></td>
          <td id="file-tester-service-status-ps1-LC41" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L42" class="blob-num js-line-number js-blob-rnum" data-line-number="42"></td>
          <td id="file-tester-service-status-ps1-LC42" class="blob-code blob-code-inner js-file-line">$ForestDCsCount = $ForestDCs.count</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L43" class="blob-num js-line-number js-blob-rnum" data-line-number="43"></td>
          <td id="file-tester-service-status-ps1-LC43" class="blob-code blob-code-inner js-file-line">Write-Verbose “Initial discovery found $ForestDCsCount DCs `r “</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L44" class="blob-num js-line-number js-blob-rnum" data-line-number="44"></td>
          <td id="file-tester-service-status-ps1-LC44" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L45" class="blob-num js-line-number js-blob-rnum" data-line-number="45"></td>
          <td id="file-tester-service-status-ps1-LC45" class="blob-code blob-code-inner js-file-line"># Add all DC lists into $DomainControllers</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L46" class="blob-num js-line-number js-blob-rnum" data-line-number="46"></td>
          <td id="file-tester-service-status-ps1-LC46" class="blob-code blob-code-inner js-file-line">$DomainControllers = $ForestDCs + $ADDomainReadOnlyReplicaDirectoryServers + $ADDomainReplicaDirectoryServers + $ADForestGlobalCatalogs</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L47" class="blob-num js-line-number js-blob-rnum" data-line-number="47"></td>
          <td id="file-tester-service-status-ps1-LC47" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L48" class="blob-num js-line-number js-blob-rnum" data-line-number="48"></td>
          <td id="file-tester-service-status-ps1-LC48" class="blob-code blob-code-inner js-file-line"># Remove duplicate DCs from $DomainControllers</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L49" class="blob-num js-line-number js-blob-rnum" data-line-number="49"></td>
          <td id="file-tester-service-status-ps1-LC49" class="blob-code blob-code-inner js-file-line">$DomainControllers = $DomainControllers | Select-Object -Unique</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L50" class="blob-num js-line-number js-blob-rnum" data-line-number="50"></td>
          <td id="file-tester-service-status-ps1-LC50" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L51" class="blob-num js-line-number js-blob-rnum" data-line-number="51"></td>
          <td id="file-tester-service-status-ps1-LC51" class="blob-code blob-code-inner js-file-line"># Sort the $DomainControllers DC list</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L52" class="blob-num js-line-number js-blob-rnum" data-line-number="52"></td>
          <td id="file-tester-service-status-ps1-LC52" class="blob-code blob-code-inner js-file-line">$DomainControllers = $DomainControllers | Sort-Object</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L53" class="blob-num js-line-number js-blob-rnum" data-line-number="53"></td>
          <td id="file-tester-service-status-ps1-LC53" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L54" class="blob-num js-line-number js-blob-rnum" data-line-number="54"></td>
          <td id="file-tester-service-status-ps1-LC54" class="blob-code blob-code-inner js-file-line">function Get-MrService {</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L55" class="blob-num js-line-number js-blob-rnum" data-line-number="55"></td>
          <td id="file-tester-service-status-ps1-LC55" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L56" class="blob-num js-line-number js-blob-rnum" data-line-number="56"></td>
          <td id="file-tester-service-status-ps1-LC56" class="blob-code blob-code-inner js-file-line">    [CmdletBinding()]</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L57" class="blob-num js-line-number js-blob-rnum" data-line-number="57"></td>
          <td id="file-tester-service-status-ps1-LC57" class="blob-code blob-code-inner js-file-line">    param (</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L58" class="blob-num js-line-number js-blob-rnum" data-line-number="58"></td>
          <td id="file-tester-service-status-ps1-LC58" class="blob-code blob-code-inner js-file-line">        [ValidateNotNullOrEmpty()]</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L59" class="blob-num js-line-number js-blob-rnum" data-line-number="59"></td>
          <td id="file-tester-service-status-ps1-LC59" class="blob-code blob-code-inner js-file-line">        [string[]]$ComputerName,</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L60" class="blob-num js-line-number js-blob-rnum" data-line-number="60"></td>
          <td id="file-tester-service-status-ps1-LC60" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L61" class="blob-num js-line-number js-blob-rnum" data-line-number="61"></td>
          <td id="file-tester-service-status-ps1-LC61" class="blob-code blob-code-inner js-file-line">        [ValidateNotNullOrEmpty()]</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L62" class="blob-num js-line-number js-blob-rnum" data-line-number="62"></td>
          <td id="file-tester-service-status-ps1-LC62" class="blob-code blob-code-inner js-file-line">        [string[]]$ServiceName = &#39;pcnssvc&#39;</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L63" class="blob-num js-line-number js-blob-rnum" data-line-number="63"></td>
          <td id="file-tester-service-status-ps1-LC63" class="blob-code blob-code-inner js-file-line">    )</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L64" class="blob-num js-line-number js-blob-rnum" data-line-number="64"></td>
          <td id="file-tester-service-status-ps1-LC64" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L65" class="blob-num js-line-number js-blob-rnum" data-line-number="65"></td>
          <td id="file-tester-service-status-ps1-LC65" class="blob-code blob-code-inner js-file-line">    foreach ($Computer in $ComputerName) {</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L66" class="blob-num js-line-number js-blob-rnum" data-line-number="66"></td>
          <td id="file-tester-service-status-ps1-LC66" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L67" class="blob-num js-line-number js-blob-rnum" data-line-number="67"></td>
          <td id="file-tester-service-status-ps1-LC67" class="blob-code blob-code-inner js-file-line">        foreach ($Service in $ServiceName) {</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L68" class="blob-num js-line-number js-blob-rnum" data-line-number="68"></td>
          <td id="file-tester-service-status-ps1-LC68" class="blob-code blob-code-inner js-file-line">            </td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L69" class="blob-num js-line-number js-blob-rnum" data-line-number="69"></td>
          <td id="file-tester-service-status-ps1-LC69" class="blob-code blob-code-inner js-file-line">            $ServiceInfo = Get-Service -Name $Service -ComputerName $Computer -ErrorAction SilentlyContinue</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L70" class="blob-num js-line-number js-blob-rnum" data-line-number="70"></td>
          <td id="file-tester-service-status-ps1-LC70" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L71" class="blob-num js-line-number js-blob-rnum" data-line-number="71"></td>
          <td id="file-tester-service-status-ps1-LC71" class="blob-code blob-code-inner js-file-line">            if (-not($ServiceInfo)) {</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L72" class="blob-num js-line-number js-blob-rnum" data-line-number="72"></td>
          <td id="file-tester-service-status-ps1-LC72" class="blob-code blob-code-inner js-file-line">                $ServiceInfo = @{</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L73" class="blob-num js-line-number js-blob-rnum" data-line-number="73"></td>
          <td id="file-tester-service-status-ps1-LC73" class="blob-code blob-code-inner js-file-line">                    MachineName = $Computer</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L74" class="blob-num js-line-number js-blob-rnum" data-line-number="74"></td>
          <td id="file-tester-service-status-ps1-LC74" class="blob-code blob-code-inner js-file-line">                    Name = $Service</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L75" class="blob-num js-line-number js-blob-rnum" data-line-number="75"></td>
          <td id="file-tester-service-status-ps1-LC75" class="blob-code blob-code-inner js-file-line">                    Status = &#39;Unreachable&#39;</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L76" class="blob-num js-line-number js-blob-rnum" data-line-number="76"></td>
          <td id="file-tester-service-status-ps1-LC76" class="blob-code blob-code-inner js-file-line">                }</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L77" class="blob-num js-line-number js-blob-rnum" data-line-number="77"></td>
          <td id="file-tester-service-status-ps1-LC77" class="blob-code blob-code-inner js-file-line">            }</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L78" class="blob-num js-line-number js-blob-rnum" data-line-number="78"></td>
          <td id="file-tester-service-status-ps1-LC78" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L79" class="blob-num js-line-number js-blob-rnum" data-line-number="79"></td>
          <td id="file-tester-service-status-ps1-LC79" class="blob-code blob-code-inner js-file-line">            [PSCustomObject]@{    </td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L80" class="blob-num js-line-number js-blob-rnum" data-line-number="80"></td>
          <td id="file-tester-service-status-ps1-LC80" class="blob-code blob-code-inner js-file-line">                ComputerName = $ServiceInfo.MachineName</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L81" class="blob-num js-line-number js-blob-rnum" data-line-number="81"></td>
          <td id="file-tester-service-status-ps1-LC81" class="blob-code blob-code-inner js-file-line">                Name = $ServiceInfo.Name</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L82" class="blob-num js-line-number js-blob-rnum" data-line-number="82"></td>
          <td id="file-tester-service-status-ps1-LC82" class="blob-code blob-code-inner js-file-line">                Status = $ServiceInfo.Status</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L83" class="blob-num js-line-number js-blob-rnum" data-line-number="83"></td>
          <td id="file-tester-service-status-ps1-LC83" class="blob-code blob-code-inner js-file-line">            }</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L84" class="blob-num js-line-number js-blob-rnum" data-line-number="84"></td>
          <td id="file-tester-service-status-ps1-LC84" class="blob-code blob-code-inner js-file-line">        }</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L85" class="blob-num js-line-number js-blob-rnum" data-line-number="85"></td>
          <td id="file-tester-service-status-ps1-LC85" class="blob-code blob-code-inner js-file-line">    }</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L86" class="blob-num js-line-number js-blob-rnum" data-line-number="86"></td>
          <td id="file-tester-service-status-ps1-LC86" class="blob-code blob-code-inner js-file-line">}</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L87" class="blob-num js-line-number js-blob-rnum" data-line-number="87"></td>
          <td id="file-tester-service-status-ps1-LC87" class="blob-code blob-code-inner js-file-line">
</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L88" class="blob-num js-line-number js-blob-rnum" data-line-number="88"></td>
          <td id="file-tester-service-status-ps1-LC88" class="blob-code blob-code-inner js-file-line">foreach ($server in $DomainControllers) {</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L89" class="blob-num js-line-number js-blob-rnum" data-line-number="89"></td>
          <td id="file-tester-service-status-ps1-LC89" class="blob-code blob-code-inner js-file-line">    #Get-Service -ComputerName $server -Name PCNSSVC | select MachineName, Name, Status</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L90" class="blob-num js-line-number js-blob-rnum" data-line-number="90"></td>
          <td id="file-tester-service-status-ps1-LC90" class="blob-code blob-code-inner js-file-line">    Get-MrService -ComputerName $server</td>
        </tr>
        <tr>
          <td id="file-tester-service-status-ps1-L91" class="blob-num js-line-number js-blob-rnum" data-line-number="91"></td>
          <td id="file-tester-service-status-ps1-LC91" class="blob-code blob-code-inner js-file-line">}</td>
        </tr>
  </table>
</div>


    </div>

  </div>

</div>

      </div>
      <div class="gist-meta">
        <a href="https://gist.github.com/puttyq/33a04a689971402c0560f59fc17045ca/raw/046016a4e5f35e1248c2d36a7d40016a7dedf108/Tester%20-%20Service%20Status.ps1" style="float:right" class="Link--inTextBlock">view raw</a>
        <a href="https://gist.github.com/puttyq/33a04a689971402c0560f59fc17045ca#file-tester-service-status-ps1" class="Link--inTextBlock">
          Tester &#8211; Service Status.ps1
        </a>
        hosted with &#10084; by <a class="Link--inTextBlock" href="https://github.com">GitHub</a>
      </div>
    </div>
</div>

</div></figure>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">280</post-id>	</item>
		<item>
		<title>Granfeldt PSMA for Microsoft Azure B2B Management</title>
		<link>https://www.puttyq.com/granfeldt-psma-for-microsoft-azure-b2b-management/</link>
		
		<dc:creator><![CDATA[Almero]]></dc:creator>
		<pubDate>Tue, 28 Aug 2018 10:58:30 +0000</pubDate>
				<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[AzureAD]]></category>
		<guid isPermaLink="false">https://www.puttyq.com/?p=134</guid>

					<description><![CDATA[Microsoft Azure B2B (Business to Business) collaboration provides companies with many collaboration capabilities which enables any organization using Azure AD to work safely and securely with users from other organizations using Azure AD. For more information about Microsoft Azure B2B, reference the &#8220;What is Azure Active Directory B2B collaboration&#8221; page. When it comes to inviting [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Microsoft Azure B2B (Business to Business) collaboration provides companies with many collaboration capabilities which enables any organization using Azure AD to work safely and securely with users from other organizations using Azure AD. For more information about Microsoft Azure B2B, reference the &#8220;<a href="https://docs.microsoft.com/en-us/azure/active-directory/b2b/what-is-b2b">What is Azure Active Directory B2B collaboration</a>&#8221; page.</p>



<p>When it comes to inviting users from different tenants to join an Azure AD, there are various options. Each of these options have specific benefits and some limitations. Options include:</p>



<ul class="wp-block-list">
<li><a href="https://docs.microsoft.com/en-us/powershell/module/azuread/?view=azureadps-2.0#users">AzureAD PowerShell Module</a>
<ul class="wp-block-list">
<li>New-AzureADMSinvitation</li>
</ul>
</li>



<li><a href="https://docs.microsoft.com/en-us/microsoft-identity-manager/microsoft-identity-manager-2016-connector-graph">Microsoft Graph API Management Agent for MIM</a></li>



<li><a href="https://docs.microsoft.com/en-us/azure/active-directory/b2b/add-users-administrator">Microsoft Azure Portal</a></li>
</ul>



<p>If a customer decides to use MIM 2016 as a synchronization method, there are two options</p>



<ul class="wp-block-list">
<li>using the Microsoft Graph API Management Agent</li>



<li>or a custom PowerShell Management Agent.</li>
</ul>



<p>The choice between to two will be largely driven between the usage scenarios. If a customer wants to simply invite users into the tenant to share resources, the Microsoft Graph API Management Agent is a great options since it offer the ability to do delta operations. The Graph API MA however has specific limitations regarding the attribute set it can manipulated (e.g. proxyAddresses cannot be written since this is a Exchange Online attribute, even though it can be imported via the Graph API).</p>



<p>I recently worked on a project where one of the core business scenarios was to enable a cross-tenant Global Address List (GAL) based off the B2B guest users in each of the Azure AD tenant. In order to manage all the different proxyAddresses (and other attributes) the had to integrated into Azure AD and Microsoft Exchange Online. As such the choice was made to sacrifice delta functionality (since the PowerShell module does not support delta data) and to use a custom PowerShell management agent to integrate both the Azure AD and Microsoft Exchange Online endpoints.</p>



<h2 class="wp-block-heading">Granfeldt PowerShell MA for Azure B2B</h2>



<p>The complete PowerShell MA is available on <a href="https://github.com/puttyq/mim.psma.azureb2b">Github</a> as is based off the Granfeldt PowerShell MA framework (available <a href="https://github.com/sorengranfeldt/psma">here</a>). The management agent supports various configuration options which are controlled by on included settings files. Options include:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Configuration Name</th><th>Configuration Description</th></tr></thead><tbody><tr><td>connection-exchangeOnlineURI</td><td>Providers a value to store the Exchange Online PowerShell URI</td></tr><tr><td>logging-loggingEnabled</td><td>Enables and disables logging to PowerShell error stream and local file system files (Note that this is a unified single log file for diagnostic purposes only</td></tr><tr><td>logging-loggingVerbose</td><td>Enable / disabled debug logging (standard info logging is controlled by loggingEnabled)</td></tr><tr><td>logging-filePath</td><td>Provides a path for log files</td></tr><tr><td>logging-fileNameImport</td><td>Single file target for import run profile logging</td></tr><tr><td>logging-fileNameExport</td><td>Single file target for export run profile logging</td></tr><tr><td>logging-fileNamePassword</td><td>Single file target for password sync transaction logging</td></tr><tr><td>import-userFilterType</td><td>Sets which AzureAD UserTypes should be imported &#8211; value include &#8220;all&#8221;, &#8220;guest&#8221;, &#8220;member&#8221;</td></tr><tr><td>import-userFilterManager</td><td>Sets if Get-AzureADUserManager should be run on each user <em>(disabled by default since this adds significant time to import)</em></td></tr><tr><td>import-userFilterRestrictImmutableId</td><td>Control if empty ImmutableID users should be ignored or not</td></tr><tr><td>import-userFilterHiddenFromAddressLists</td><td>Control if Get-MailUser should be executed on all guest to confirm Exo GAL visibility</td></tr><tr><td>import-userFilterPhoto</td><td>Control if Get-AzureADUserThumbnail is executed on all imported users&nbsp;<em>(not implemented at the moment)</em></td></tr><tr><td>import-userThumbnailPath</td><td>Sets a path to store user thumbnails on the local filesystem</td></tr><tr><td>export-inviteEmailSending</td><td>Controls of an AzureAD B2B email invite should be sent to new users</td></tr><tr><td>export-inviteRedirectionURL</td><td>Provides a URL to redirect users to after redemption of AzureAD B2B invite</td></tr></tbody></table></figure>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Source Available on GitHub</h2>



<p>There are still many things that I would like to update and tweak on the implementation, but this will come in due course hopefully. If in the meantime this is valuable to someone or anyone wishes to contribute something the project on <a href="https://github.com/puttyq/mim.psma.azureb2b">GitHub </a>is the place to visit.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">134</post-id>	</item>
	</channel>
</rss>
