Msec.format.ps1xml
|
<?xml version="1.0" encoding="utf-8"?> <!-- Table views for msec types whose columns are COLLECTIONS. Every other msec type gets its default table from a DefaultDisplayPropertySet in msec.psm1, which is lighter and needs no XML. That mechanism can only pick which properties appear, though - not how they render. A string[] column therefore comes out as PowerShell's collection literal, '{sg-pilot-ring, sg-broad-ring}', braces and all. The fix is not to store a joined string. AssignmentGroup and AssignmentType are arrays so that callers can test them exactly: Where-Object AssignmentGroup -contains 'sg-pilot-ring' Joining them at the source would force -like '*sg-pilot-ring*' on every such query, and a substring match happily reports 'sg-pilot' as a hit for 'sg-pilot-ring'. This module has been bitten by loose matching on display values more than once. So the data stays typed and only the rendering is flattened, here, where flattening is the whole point. --> <Configuration> <ViewDefinitions> <View> <Name>MsecIntuneConfigurationProfile</Name> <ViewSelectedBy> <TypeName>MsecIntuneConfigurationProfile</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>DisplayName</Label><Width>26</Width></TableColumnHeader> <TableColumnHeader><Label>Source</Label><Width>15</Width></TableColumnHeader> <TableColumnHeader><Label>Platform</Label><Width>11</Width></TableColumnHeader> <!-- 28 fits 'AllDevices, ExclusionGroup', the widest realistic pairing; truncating it would hide the carve-out. --> <TableColumnHeader><Label>AssignmentType</Label><Width>28</Width></TableColumnHeader> <TableColumnHeader><Label>AssignmentGroup</Label></TableColumnHeader> <TableColumnHeader><Label>Status</Label><Width>12</Width></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>DisplayName</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Source</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Platform</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>@($_.AssignmentType) -join ', '</ScriptBlock> </TableColumnItem> <!-- Included groups, then the exclusions after the word 'excluding'. Both live in this one column because a seventh would not fit, and because an empty group cell next to an AssignmentType of 'ExclusionGroup' reads as a bug rather than as "the only group here is a carve-out". Rendering only - the two sets stay in AssignmentGroup and AssignmentExcludedGroup, apart. --> <TableColumnItem> <ScriptBlock> $inc = @($_.AssignmentGroup) -join ', ' $exc = @($_.AssignmentExcludedGroup) -join ', ' if ($exc) { (@($inc, "excluding $exc") | Where-Object { $_ }) -join ', ' } else { $inc } </ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>Status</PropertyName></TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecAzureDevOpsWorkItem</Name> <ViewSelectedBy> <TypeName>MsecAzureDevOpsWorkItem</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>Id</Label><Width>7</Width><Alignment>Right</Alignment></TableColumnHeader> <TableColumnHeader><Label>Type</Label><Width>18</Width></TableColumnHeader> <TableColumnHeader><Label>State</Label><Width>11</Width></TableColumnHeader> <!-- Tags is a string[] so callers can test it exactly - Where-Object Tags -contains 'Internal IT'. Storing Azure DevOps' own 'Exchange; Internal IT' string instead forced -like '*Internal IT*' on every query, which also matches a tag named 'Internal IT Legacy', and silently returned nothing for the -contains and -in people reach for first. Joined here, for the table only. --> <TableColumnHeader><Label>Tags</Label><Width>28</Width></TableColumnHeader> <TableColumnHeader><Label>AgeDays</Label><Width>8</Width><Alignment>Right</Alignment></TableColumnHeader> <TableColumnHeader><Label>Title</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>Id</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Type</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>State</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>@($_.Tags) -join '; '</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>AgeDays</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Title</PropertyName></TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecIntuneAuditEvent</Name> <ViewSelectedBy> <TypeName>MsecIntuneAuditEvent</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>ActivityDateTime</Label><Width>20</Width></TableColumnHeader> <TableColumnHeader><Label>Activity</Label><Width>30</Width></TableColumnHeader> <TableColumnHeader><Label>Actor</Label><Width>30</Width></TableColumnHeader> <!-- Resource is a string[] because a bulk edit names several, and a caller filtering with -contains must get the names apart. Joined here for the table only. --> <TableColumnHeader><Label>Resource</Label><Width>34</Width></TableColumnHeader> <!-- The COUNT of changed settings, not the settings themselves: one event can carry dozens, and a table cell cannot hold them. ChangedProperties has them in full - reach for Format-List or $_.ChangedProperties. A zero here is meaningful rather than missing data: a delete or an assignment change moves no property values. --> <TableColumnHeader><Label>Changes</Label><Width>8</Width><Alignment>Right</Alignment></TableColumnHeader> <TableColumnHeader><Label>Result</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <ScriptBlock>if ($_.ActivityDateTime) { $_.ActivityDateTime.ToString('yyyy-MM-dd HH:mm') } else { '' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>Activity</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Actor</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>@($_.Resource) -join ', '</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>ChangedCount</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Result</PropertyName></TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecIntuneAsrRule</Name> <ViewSelectedBy> <TypeName>MsecIntuneAsrRule</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>RuleName</Label><Width>52</Width></TableColumnHeader> <!-- Blank, not 'off', where nothing configures the rule. The two are different states - an explicit off wins a policy conflict and an absent rule does not - so rendering the gap as a value would be a lie in the one column people scan first. --> <TableColumnHeader><Label>Mode</Label><Width>6</Width></TableColumnHeader> <TableColumnHeader><Label>Policy</Label><Width>32</Width></TableColumnHeader> <!-- Included groups, then exclusions after 'excluding'. Both here because an ASR ring is usually defined BY its carve-out - 'everyone excluding developers' - and an empty cell next to an all-users assignment would hide exactly that. --> <TableColumnHeader><Label>AssignedTo</Label></TableColumnHeader> <TableColumnHeader><Label>Excl</Label><Width>5</Width><Alignment>Right</Alignment></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>RuleName</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Mode</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>PolicyName</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock> $inc = @(@($_.AssignedGroup) + @(if ($_.AssignedAllUsers) { 'All users' }) + @(if ($_.AssignedAllDevices) { 'All devices' })) -join ', ' $exc = @($_.ExcludedGroup) -join ', ' if ($exc) { (@($inc, "excluding $exc") | Where-Object { $_ }) -join ', ' } else { $inc } </ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock>if (@($_.PerRuleExclusion).Count) { @($_.PerRuleExclusion).Count } else { '' }</ScriptBlock> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecDefenderCertificateUsage</Name> <ViewSelectedBy> <TypeName>MsecDefenderCertificateUsage</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>Signer</Label><Width>44</Width></TableColumnHeader> <!-- Blank, not a number, where the record carries no expiry. A sentinel here would sort alongside real values and put "unknown" at whichever end the sentinel happened to fall - which for an expiry column is a wrong answer either way. --> <TableColumnHeader><Label>DaysLeft</Label><Width>9</Width><Alignment>Right</Alignment></TableColumnHeader> <TableColumnHeader><Label>Devices</Label><Width>8</Width><Alignment>Right</Alignment></TableColumnHeader> <TableColumnHeader><Label>Files</Label><Width>7</Width><Alignment>Right</Alignment></TableColumnHeader> <TableColumnHeader><Label>Trusted</Label><Width>8</Width></TableColumnHeader> <!-- The thumbprint an indicator takes. Full width: a truncated one is unusable. --> <TableColumnHeader><Label>SignerHash</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>Signer</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($null -eq $_.DaysUntilExpiry) { '' } else { $_.DaysUntilExpiry }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>Devices</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Files</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>IsTrusted</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>SignerHash</PropertyName></TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecIntuneReusableSetting</Name> <ViewSelectedBy> <TypeName>MsecIntuneReusableSetting</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>DisplayName</Label><Width>44</Width></TableColumnHeader> <TableColumnHeader><Label>Kind</Label><Width>20</Width></TableColumnHeader> <!-- Blank where the count is unknown rather than 0. A zero here means "no policy uses this, delete it"; printing an unknown as zero could get something in use removed. --> <TableColumnHeader><Label>Policies</Label><Width>9</Width><Alignment>Right</Alignment></TableColumnHeader> <TableColumnHeader><Label>Entries</Label><Width>8</Width><Alignment>Right</Alignment></TableColumnHeader> <TableColumnHeader><Label>LastModified</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>DisplayName</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Kind</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($null -eq $_.ReferencingPolicyCount) { '' } else { $_.ReferencingPolicyCount }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>EntryCount</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.LastModified) { $_.LastModified.ToString('yyyy-MM-dd') } else { '' }</ScriptBlock> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecDefenderDetectionRule</Name> <ViewSelectedBy> <TypeName>MsecDefenderDetectionRule</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>DisplayName</Label><Width>40</Width></TableColumnHeader> <!-- 'autoDisabled' is why this column is wide enough to show it in full. Truncated to 'autoDisab...' it reads as a variant of 'disabled', which is the one conclusion that must not be drawn: nobody chose it, Defender switched the rule off when its query broke, and the rule still looks live in the portal. --> <TableColumnHeader><Label>Status</Label><Width>13</Width></TableColumnHeader> <TableColumnHeader><Label>Frequency</Label><Width>10</Width></TableColumnHeader> <TableColumnHeader><Label>Severity</Label><Width>14</Width></TableColumnHeader> <TableColumnHeader><Label>NextRun</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>DisplayName</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Status</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Frequency</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Severity</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.NextRun) { $_.NextRun.ToString('yyyy-MM-dd HH:mm') } else { '' }</ScriptBlock> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecEntraConditionalAccessChange</Name> <ViewSelectedBy> <TypeName>MsecEntraConditionalAccessChange</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>ActivityDateTime</Label><Width>17</Width></TableColumnHeader> <TableColumnHeader><Label>PolicyName</Label><Width>44</Width></TableColumnHeader> <TableColumnHeader><Label>Actor</Label><Width>30</Width></TableColumnHeader> <!-- State transition, not the two values separately: 'enabled -> disabled' is the thing worth seeing at a glance, and a blank where nothing moved keeps the column quiet. --> <TableColumnHeader><Label>State</Label><Width>34</Width></TableColumnHeader> <TableColumnHeader><Label>Changes</Label><Width>8</Width><Alignment>Right</Alignment></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <ScriptBlock>if ($_.ActivityDateTime) { $_.ActivityDateTime.ToString('yyyy-MM-dd HH:mm') } else { '' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>PolicyName</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Actor</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.StateChanged) { "$($_.StateBefore) -> $($_.StateAfter)" } else { '' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>ChangedCount</PropertyName></TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecAppGatewayClientActivity</Name> <ViewSelectedBy> <TypeName>MsecAppGatewayClientActivity</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>TimeGenerated</Label><Width>17</Width></TableColumnHeader> <TableColumnHeader><Label>ClientIp</Label><Width>16</Width></TableColumnHeader> <TableColumnHeader><Label>Method</Label><Width>7</Width></TableColumnHeader> <TableColumnHeader><Label>Status</Label><Width>7</Width><Alignment>Right</Alignment></TableColumnHeader> <!-- Blank, not False, where the row cannot answer. A summary row has no HTTP method, so 'did this authenticate' is unanswerable there - printing False would assert something nobody measured, which is the one mistake this command is built to avoid. --> <TableColumnHeader><Label>Auth</Label><Width>6</Width></TableColumnHeader> <TableColumnHeader><Label>Uri</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <ScriptBlock>if ($_.TimeGenerated) { $_.TimeGenerated.ToString('MM-dd HH:mm:ss') } else { '' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>ClientIp</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Method</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($null -eq $_.Status) { '' } else { $_.Status }</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock>if ($null -eq $_.Authenticated) { '' } elseif ($_.Authenticated) { 'YES' } else { '' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>Uri</PropertyName></TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecDefenderEmail</Name> <ViewSelectedBy> <TypeName>MsecDefenderEmail</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>Time</Label><Width>14</Width></TableColumnHeader> <TableColumnHeader><Label>Dir</Label><Width>9</Width></TableColumnHeader> <!-- Where the message was INJECTED - the last SMTP hop - not where its author was. Relayed mail geolocates to the relay, so this is a statement about infrastructure. --> <TableColumnHeader><Label>SenderCountry</Label><Width>16</Width></TableColumnHeader> <TableColumnHeader><Label>From</Label><Width>34</Width></TableColumnHeader> <TableColumnHeader><Label>To</Label><Width>30</Width></TableColumnHeader> <!-- Blank on clean mail. Printing 'None' would read as a verdict Defender never gave; an empty cell says only that no threat type was recorded. --> <TableColumnHeader><Label>Threat</Label><Width>12</Width></TableColumnHeader> <!-- Where the message ENDED UP, after ZAP - what a user could actually have opened - rather than where it was first delivered. --> <TableColumnHeader><Label>LandedIn</Label><Width>14</Width></TableColumnHeader> <TableColumnHeader><Label>Subject</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <ScriptBlock>if ($_.Timestamp) { $_.Timestamp.ToString('MM-dd HH:mm') } else { '' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>EmailDirection</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>SenderCountry</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>SenderFromAddress</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>RecipientEmailAddress</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>ThreatTypes</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>LatestDeliveryLocation</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Subject</PropertyName></TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecDefenderTeamsMessage</Name> <ViewSelectedBy> <TypeName>MsecDefenderTeamsMessage</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>Time</Label><Width>14</Width></TableColumnHeader> <TableColumnHeader><Label>Thread</Label><Width>9</Width></TableColumnHeader> <!-- Teams has no sender IP, so the trust boundary is the nearest thing to mail's direction. EXT is loud because an external thread is the one worth noticing. --> <TableColumnHeader><Label>Ext</Label><Width>4</Width></TableColumnHeader> <TableColumnHeader><Label>Sender</Label><Width>34</Width></TableColumnHeader> <!-- The COUNT of recipients, not the addresses: one row is one message and a group chat carries dozens. RecipientAddress has them in full, as a string[] for -contains. --> <TableColumnHeader><Label>To</Label><Width>4</Width><Alignment>Right</Alignment></TableColumnHeader> <TableColumnHeader><Label>Urls</Label><Width>5</Width><Alignment>Right</Alignment></TableColumnHeader> <!-- Blank where Defender gave no verdict. Printing 'None' would read as a judgement nobody made; in a tenant Defender has never acted on, this column is blank throughout and that absence is itself the finding. --> <TableColumnHeader><Label>Threat</Label><Width>12</Width></TableColumnHeader> <!-- Subject is empty for chat and meeting messages, so the conversation name is the only usable handle for most of the table. Falls back to Subject for channel posts. --> <TableColumnHeader><Label>Thread/Subject</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <ScriptBlock>if ($_.Timestamp) { $_.Timestamp.ToString('MM-dd HH:mm') } else { '' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>ThreadType</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.IsExternalThread) { 'EXT' } else { '' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>SenderEmailAddress</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>@($_.RecipientAddress).Count</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.UrlCount) { $_.UrlCount } else { '' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>ThreatTypes</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.ThreadName) { $_.ThreadName } else { $_.Subject }</ScriptBlock> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecPurviewActivity</Name> <ViewSelectedBy> <TypeName>MsecPurviewActivity</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>Happened</Label><Width>17</Width></TableColumnHeader> <TableColumnHeader><Label>Activity</Label><Width>22</Width></TableColumnHeader> <TableColumnHeader><Label>Workload</Label><Width>17</Width></TableColumnHeader> <TableColumnHeader><Label>User</Label><Width>32</Width></TableColumnHeader> <!-- Blank on a non-DLP event. Flattened out of PolicyMatchInfo, because grouping by the raw API's PolicyName puts every row under one empty key. --> <TableColumnHeader><Label>Policy</Label><Width>26</Width></TableColumnHeader> <!-- Resolved from the GUID. A label deleted since the event shows as its GUID rather than blank - historical events outlive their labels, and blank would read as "unlabelled". --> <TableColumnHeader><Label>Label</Label><Width>14</Width></TableColumnHeader> <TableColumnHeader><Label>Item</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <ScriptBlock>if ($_.Happened) { $_.Happened.ToString('MM-dd HH:mm:ss') } else { '' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>Activity</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Workload</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>User</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>PolicyName</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>SensitivityLabelName</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>ItemName</PropertyName></TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecBusinessCentralEnvironment</Name> <ViewSelectedBy> <TypeName>MsecBusinessCentralEnvironment</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>Name</Label><Width>26</Width></TableColumnHeader> <TableColumnHeader><Label>Type</Label><Width>11</Width></TableColumnHeader> <TableColumnHeader><Label>Country</Label><Width>8</Width></TableColumnHeader> <!-- Blank, not 0, where the count could not be taken. An environment that refused enumeration must not render as an empty one - that is the reading which turns an unreviewed sandbox into one that looks harmless. --> <TableColumnHeader><Label>Companies</Label><Width>10</Width><Alignment>Right</Alignment></TableColumnHeader> <TableColumnHeader><Label>Users</Label><Width>7</Width><Alignment>Right</Alignment></TableColumnHeader> <TableColumnHeader><Label>Version</Label><Width>18</Width></TableColumnHeader> <TableColumnHeader><Label>Location</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>Name</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Type</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>CountryCode</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($null -eq $_.Companies) { '' } else { $_.Companies }</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock>if ($null -eq $_.Users) { '' } else { $_.Users }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>ApplicationVersion</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>LocationName</PropertyName></TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecBusinessCentralAccess</Name> <ViewSelectedBy> <TypeName>MsecBusinessCentralAccess</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>Environment</Label><Width>23</Width></TableColumnHeader> <TableColumnHeader><Label>Type</Label><Width>11</Width></TableColumnHeader> <TableColumnHeader><Label>User</Label><Width>34</Width></TableColumnHeader> <!-- APP marks an Entra application rather than a person. Its real access is set here, not in Entra, which shows only that the app may reach Business Central at all. --> <TableColumnHeader><Label>Kind</Label><Width>5</Width></TableColumnHeader> <TableColumnHeader><Label>State</Label><Width>9</Width></TableColumnHeader> <TableColumnHeader><Label>Role</Label><Width>22</Width></TableColumnHeader> <!-- '(all companies)' rather than blank: an empty company on a System-scope assignment means EVERY company, and a blank cell would make the broadest grant in the system look like the narrowest. --> <TableColumnHeader><Label>Company</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>Environment</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>EnvironmentType</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>UserName</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.IsApplication) { 'APP' } else { '' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>State</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>RoleId</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Company</PropertyName></TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecEntraAccessPackage</Name> <ViewSelectedBy> <TypeName>MsecEntraAccessPackage</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>Catalog</Label><Width>26</Width></TableColumnHeader> <TableColumnHeader><Label>Package</Label><Width>42</Width></TableColumnHeader> <!-- allMembers = anyone in the directory may request it. Blank where the package has no policy at all, which means nobody can - not that everybody can. --> <TableColumnHeader><Label>WhoCanAsk</Label><Width>12</Width></TableColumnHeader> <!-- Blank, not 'no', where there is no policy. "Nobody can request this" and "anyone gets it unapproved" are opposite findings and must not share a rendering. --> <TableColumnHeader><Label>Approval</Label><Width>9</Width></TableColumnHeader> <!-- 'never' is printed as a value. An empty expiry cell reads as "not configured yet" and invites the assumption that some default applies - noExpiration IS the configuration, and it means permanent. --> <TableColumnHeader><Label>Expires</Label><Width>10</Width></TableColumnHeader> <!-- Usually a group, not a role. What that group actually carries is a separate question - see Get-MsecEntraRoleHolder. --> <TableColumnHeader><Label>Grants</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>Catalog</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Package</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>switch ($_.AllowedTargetScope) { 'allMemberUsers' { 'allMembers' } 'specificDirectoryUsers' { 'named' } default { $_.AllowedTargetScope } }</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock>if ($null -eq $_.ApprovalRequired) { '' } elseif ($_.ApprovalRequired) { 'yes' } else { 'NONE' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>Expiration</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>@($_.ResourceRole) -join ', '</ScriptBlock> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecEntraPimActivation</Name> <ViewSelectedBy> <TypeName>MsecEntraPimActivation</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>When</Label><Width>17</Width></TableColumnHeader> <TableColumnHeader><Label>Type</Label><Width>15</Width></TableColumnHeader> <TableColumnHeader><Label>Actor</Label><Width>30</Width></TableColumnHeader> <TableColumnHeader><Label>Role</Label><Width>34</Width></TableColumnHeader> <TableColumnHeader><Label>Hrs</Label><Width>5</Width><Alignment>Right</Alignment></TableColumnHeader> <!-- Blank where MFA was not RECORDED on this record type, 'no' only where it genuinely was not used. Rendering the absence as 'no' would report every role-setting change as unauthenticated and bury the one activation that matters. --> <TableColumnHeader><Label>MFA</Label><Width>5</Width></TableColumnHeader> <TableColumnHeader><Label>Justification</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <ScriptBlock>if ($_.ActivityDateTime) { $_.ActivityDateTime.ToString('MM-dd HH:mm:ss') } else { '' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>AuditType</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Actor</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Role</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($null -eq $_.DurationHours) { '' } else { $_.DurationHours }</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock>if ($null -eq $_.AuthenticatedWithMfa) { '' } elseif ($_.AuthenticatedWithMfa) { 'yes' } else { 'NO' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>Justification</PropertyName></TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecEntraPimAlert</Name> <ViewSelectedBy> <TypeName>MsecEntraPimAlert</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <!-- FIRING vs clean. False means PIM scanned and found nothing - a measurement, not the alert being switched off. IsEnabled carries that separately. --> <TableColumnHeader><Label>State</Label><Width>7</Width></TableColumnHeader> <!-- Blank, not 0, where the count was not reported. An alert whose size is unknown is not an alert with nothing behind it. --> <TableColumnHeader><Label>Count</Label><Width>7</Width><Alignment>Right</Alignment></TableColumnHeader> <TableColumnHeader><Label>Severity</Label><Width>10</Width></TableColumnHeader> <TableColumnHeader><Label>LastScanned</Label><Width>17</Width></TableColumnHeader> <TableColumnHeader><Label>Alert</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <ScriptBlock>if ($_.IsActive) { 'FIRING' } else { 'clean' }</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock>if ($null -eq $_.IncidentCount) { '' } else { $_.IncidentCount }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>Severity</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.LastScannedUtc) { $_.LastScannedUtc.ToString('yyyy-MM-dd HH:mm') } else { '' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>AlertName</PropertyName></TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecEntraAuthenticationMethod</Name> <ViewSelectedBy> <TypeName>MsecEntraAuthenticationMethod</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>Method</Label><Width>24</Width></TableColumnHeader> <TableColumnHeader><Label>State</Label><Width>9</Width></TableColumnHeader> <!-- THE COLUMN THE STATE COLUMN LIES WITHOUT. An enabled method scoped to one group is enabled for the tenant and available to almost nobody - measured on a tenant where Fido2 read 'enabled' while targeting only the two emergency-access accounts. 'all' means every user can use it; 'scoped' means a named group only; '-' means off. --> <TableColumnHeader><Label>Reach</Label><Width>7</Width></TableColumnHeader> <!-- Microsoft's classification (FIDO2, certificate, Windows Hello), not a judgement made here. Blank rather than 'no' on a disabled method: resistance is a property of a method in use. --> <TableColumnHeader><Label>Phish-res</Label><Width>10</Width></TableColumnHeader> <TableColumnHeader><Label>Targets</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>Method</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>State</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.State -ne 'enabled') { '-' } elseif ($_.AvailableToAllUsers) { 'all' } else { 'scoped' }</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.State -ne 'enabled') { '' } elseif ($_.IsPhishingResistant) { 'yes' } else { 'no' }</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock> $parts = @() if ($_.IncludeTarget) { $parts += ($_.IncludeTarget -join ', ') } if ($_.ExcludeTarget) { $parts += ('except ' + ($_.ExcludeTarget -join ', ')) } $parts -join ' ' </ScriptBlock> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecEntraGuest</Name> <ViewSelectedBy> <TypeName>MsecEntraGuest</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>Mail</Label><Width>38</Width></TableColumnHeader> <!-- 'pending' means the invitation was never redeemed: access granted to nobody. --> <TableColumnHeader><Label>State</Label><Width>9</Width></TableColumnHeader> <TableColumnHeader><Label>On</Label><Width>4</Width></TableColumnHeader> <!-- 'never' only where sign-in data was READABLE. Where it was not, the column reads 'n/a' rather than 'never' - the two must not look alike, because one is a deletion candidate and the other is a missing permission. --> <TableColumnHeader><Label>LastSignIn</Label><Width>12</Width></TableColumnHeader> <!-- NonInteractive here means the account is in use by tokens, not by a person. --> <TableColumnHeader><Label>Kind</Label><Width>14</Width></TableColumnHeader> <TableColumnHeader><Label>Invited</Label><Width>8</Width><Alignment>Right</Alignment></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>Mail</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.InvitationState -eq 'PendingAcceptance') { 'pending' } else { 'accepted' }</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.AccountEnabled) { 'yes' } else { 'NO' }</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock> if (-not $_.SignInDataAvailable) { 'n/a' } elseif ($null -eq $_.LastSignInUtc) { 'never' } else { $_.LastSignInUtc.ToString('yyyy-MM-dd') } </ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>LastSignInKind</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($null -eq $_.DaysSinceInvite) { '' } else { "$($_.DaysSinceInvite)d" }</ScriptBlock> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecEntraCrossTenantAccess</Name> <ViewSelectedBy> <TypeName>MsecEntraCrossTenantAccess</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>Tenant</Label><Width>34</Width></TableColumnHeader> <TableColumnHeader><Label>Inbound</Label><Width>8</Width></TableColumnHeader> <!-- EFFECTIVE values, with '(inh)' marking the ones that merely follow the default. A partner inheriting the default is not a partner set to false - it moves the moment the default moves, and the two must not look the same. --> <TableColumnHeader><Label>TrustMFA</Label><Width>10</Width></TableColumnHeader> <TableColumnHeader><Label>TrustDevice</Label><Width>12</Width></TableColumnHeader> <TableColumnHeader><Label>Scope</Label><Width>8</Width></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>TenantName</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>InboundB2B</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>$(if ($_.TrustsMfa) { 'yes' } else { 'no' }) + $(if ($_.IsInherited) { ' (inh)' } else { '' })</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock>$(if ($_.TrustsCompliantDevice) { 'yes' } else { 'no' }) + $(if ($_.IsInherited) { ' (inh)' } else { '' })</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>Scope</PropertyName></TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecEntraNamedLocation</Name> <ViewSelectedBy> <TypeName>MsecEntraNamedLocation</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>Location</Label><Width>28</Width></TableColumnHeader> <TableColumnHeader><Label>Type</Label><Width>7</Width></TableColumnHeader> <!-- Trust permits exclusion and lowers Identity Protection risk for sign-ins here. --> <TableColumnHeader><Label>Trusted</Label><Width>7</Width></TableColumnHeader> <TableColumnHeader><Label>Ranges</Label><Width>6</Width><Alignment>Right</Alignment></TableColumnHeader> <!-- 'unused' is the point of the column: a location no policy names does nothing at all, so a trusted-but-unused one is inert rather than alarming. --> <TableColumnHeader><Label>UsedBy</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>DisplayName</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>LocationType</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.IsTrusted) { 'yes' } else { 'no' }</ScriptBlock> </TableColumnItem> <TableColumnItem><PropertyName>RangeCount</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if (-not $_.IsReferenced) { 'unused' } else { ($_.UsedByPolicy | Select-Object -First 2) -join '; ' }</ScriptBlock> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> <View> <Name>MsecIntuneDeviceCompliance</Name> <ViewSelectedBy> <TypeName>MsecIntuneDeviceCompliance</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader><Label>Device</Label><Width>22</Width></TableColumnHeader> <TableColumnHeader><Label>Os</Label><Width>8</Width></TableColumnHeader> <!-- SECURITY vs stale. 'Unattributed' is neither: the device is flagged non-compliant and the reason could not be read, which must not read as nothing-wrong. --> <TableColumnHeader><Label>Category</Label><Width>15</Width></TableColumnHeader> <!-- A device that has not checked in cannot be remediated by Intune at all. --> <TableColumnHeader><Label>Quiet</Label><Width>6</Width><Alignment>Right</Alignment></TableColumnHeader> <TableColumnHeader><Label>Setting</Label></TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem><PropertyName>DeviceName</PropertyName></TableColumnItem> <TableColumnItem><PropertyName>Os</PropertyName></TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.Category -eq 'SecurityControl') { 'SECURITY' } else { $_.Category }</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock>if ($null -eq $_.DaysQuiet) { '' } else { "$($_.DaysQuiet)d" }</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock>if ($_.Setting) { $_.Setting -replace '^DefaultDeviceCompliancePolicy\.', '' } else { '(not reported)' }</ScriptBlock> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> </ViewDefinitions> </Configuration> |