Repository navigation
Ciao! 馃憢馃徎
Nebula.Core 1.3.0 is a bigger step than the version number might suggest, which is why it isn't a 1.2.3. Most of the work happened under the hood: bulk and pipeline commands now talk to Microsoft Graph in batches (20 requests per call), which makes them faster and, if you use a Graph module that opens a sign-in window for every request, a lot less noisy. Connections were reworked too, so Microsoft Graph and Exchange Online can live in the same PowerShell session without stepping on each other.
There are also new commands. Get-UserDevices answers a question I kept asking myself: "which devices does this user actually have?", combining what Entra ID and Intune know about each one. Get-QuarantineForMailbox checks quarantine across all the addresses of a mailbox at once. And four new commands let you export, import, copy and compare Enterprise Applications within the same tenant (the documentation lists exactly which settings are copied).
This release went through a long round of automated reviews, which caught quite a few edge cases before they could reach you. As usual, comments and feedback are welcome!
Important
Before you update
- Two defaults change: CSV files now use a comma as delimiter (it was
;), and dates are shown inEastern Standard Timeunless you setDateTimeTimeZone. You can restore the previous behavior in yoursettings.psd1(seeGet-NebulaConfig). - On PowerShell 7.6.6,
Out-GridViewhangs (PowerShell/PowerShell#27994): every-GridViewin Nebula.Core shows the results in the console on that release instead of freezing your session.
I'll leave you with the details of the changes.
What's Changed
New commands
Export-EnterpriseApplication,Import-EnterpriseApplication,Copy-EnterpriseApplication, andCompare-EnterpriseApplicationto snapshot, recreate, clone, and diff Enterprise Applications (App Registration + Service Principal) within the same Entra tenant, including optional App Role Assignment copying, owner sync, and CSV/JSON diff reports. Owners and App Role Assignments are only added, never removed, so a destination environment keeps its own. Exposed API scopes are copied together with the API settings (pre-authorized and known client applications, requested access token version, mapped claims), the token claim settings (group membership claims, optional claims), the fallback public client flag, the Web home page/logout URLs and implicit grant settings, the Service Principal's "Assignment required" and enabled settings, and the Service Principal's own owners (a separate list from the application's). The documentation lists exactly which settings are copied. SAML and password-based single sign-on are not supported: a warning tells you to configure them on the destination. The result object reportsOwnersFailedfor owners that could not be added. With-IncludeAppRoleAssignments,Import-andCopy-also requestAppRoleAssignment.ReadWrite.All. When updating an existing app, app roles and permission scopes the snapshot drops are disabled in a first request and removed in a second, as Graph requires.Import-refuses a file that isn't a complete version 1 snapshot, so a wrong or partial file can't clear an existing app's settings; a failed Service Principal update stops the import with an error instead of reporting success, andCompare-ignores the order in which Graph returns lists (owners, App Role Assignments, credentials, API permissions, redirect URIs, app roles and their member types, scopes, tags, optional claims, pre-authorized and known client applications). Client secrets and certificates are never copied; only their metadata is captured for reporting.Get-QuarantineForMailboxto check quarantine across all of a mailbox's SMTP aliases (primary and secondary) in one call, sinceGet-QuarantineMessage -RecipientAddressonly matches the exact address a message was sent to; defaults to a 15-day lookback window (vs.Get-QuarantineMessage's own 7-day default), overridable via-Daysor explicit-StartReceivedDate/-EndReceivedDate.Get-UserDevicesto list the devices of one or more users in one table, combining the devices they registered or own in Entra ID with the devices Intune manages for them (matched on the Azure AD device ID), including managed devices the user doesn't register or own in Entra, such as hybrid-joined PCs. Each row shows name, manufacturer, model, operating system, serial number, join type, ownership, compliance, last sign-in and sync, the source (Entra,IntuneorEntra+Intune) and the Entra/Intune IDs; the console shows a compact table, and-GridView,Select-Object *orExport-Csvgive every column. A user whose devices can't be read completely is reported as an error instead of returning partial rows. A user given more than once (e.g. by UPN and by object ID) is listed once.Get-IntuneAppPresencefor quick single-device app presence checks with one-row output, always includeLastInventory, and return the matched app name inAppName. When several Intune records share the device name (e.g. after re-enrollment), it uses the most recently synced one and warns with the other IDs.Remove-EntraUserfor direct UPN-based Entra user removal with Graph.Search-EntraUserto search users by display name, user principal name, or mail, including guest UPN fragments. Without-IndexOnlyit reads the whole tenant only when partial matching is needed, not for an exact UPN or object ID.
Improvements
Connect-EOLsuppressesConnect-ExchangeOnline's cosmetic "Sign in by Web Account Manager (WAM) is enabled by default" notice on every WAM sign-in, unless called with-Verbose; it never affected the existing WAM-failure detection, which inspects the thrown exception, not warnings.Connect-Nebula -GraphLoginHint(and-UserPrincipalName) passes the account to the Microsoft Graph sign-in so the WAM broker targets it without extra prompts. Without either parameter, an active Graph session keeps its own account and tenant instead of being switched to the Windows identity or its home tenant. The hint needs Microsoft.Graph.Authentication 2.39.0 or later; with older versions Nebula signs in without it instead of failing.Update-NebulaConnectionsnow re-checks Exchange Online and Microsoft Graph and reconnects unhealthy sessions instead of only reporting their status, keeping the account (and, for Graph, the tenant) each session is already using. A Graph session whose token fails the health probe is reconnected even if its context looks valid, and Graph is repaired before Exchange Online (without WAM), in the same order asConnect-Nebula.- Microsoft Graph requests now use URIs relative to the active Graph environment, and directory references use that environment's endpoint, so Enterprise Application, group and Intune cmdlets also work in national clouds (e.g. USGov) instead of always calling
graph.microsoft.com. Get-MboxPermissionnow shows the source mailboxRecipientTypeDetailsvalue in the output heading.Get-UserGroupskeeps the existing Exchange-first behavior for regular users while handling guest identities more gracefully.New-IntuneAppBasedGroupadds devices one reference at a time inside a Graph batch instead of a singlemembers@odata.bindupdate, so a device that is already a member is reported and no longer makes the whole update fail; duplicate Entra devices are added once.- Add culture-safe date parsing plus optional timezone-aware formatting through
DateTimeTimeZone. A time zone that can't be resolved is ignored with a warning, so local-time output still converts to local time. - Bulk and pipeline functions for Entra groups, licenses, security, users, mailbox sign-in checks and Intune now send Microsoft Graph requests through
$batch(20 per HTTP call) and look up tenant-wide data (connection, SKU list, license catalog, target group) once per run instead of once per item; with Microsoft.Graph.Authentication builds that open a WAM broker window per request, a 14-userAdd-EntraGroupUsernow opens at most 2 instead of ~28. Output arrives in bursts of up to 20 items, with a start line (only when the work spans more than one batch request) andWrite-Progress(including Graph throttling waits) in between; error details now carry the Graph batch error message instead of the SDK exception text, and-ShowErrorDetailsno longer changes user-lookup messages in the license functions. - Change the default CSV delimiter to comma for a more standard US-friendly baseline.
- The
LicenseMappingGitHub Action now also checksJSON/M365_licenses_custom.jsonfor entries already covered by the official catalog and opens/updates a GitHub issue when it finds any, so stale custom mappings get flagged for removal. - Set the default user-facing date/time zone to
Eastern Standard Timeto align with the module's US baseline. - The Pester test suite (
Tests/Public/*.Tests.ps1) now correctly scopes its fixtures insideBeforeAllblocks so tests run for real under Pester 5, instead of only succeeding at test discovery. - Unify user-facing date formatting through Nebula's configured date/time patterns, including Intune inventory and license catalog outputs. Date-only values, such as the
Set-MboxMrmCleanupcutoff date, are shown as given, without time-zone conversion. - User lookups in license, group, security and user functions now query Microsoft Graph first for UPN/mail/object ID input and fall back to Exchange (
Get-Recipient) only when Graph can't find the identity;Test-SharedMailboxCompliancefilters successful sign-ins server-side andGet-UserLastSeenreads only the latest page of sign-ins (dates are kept in UTC).
Fixes
-GridViewno longer freezes the session on PowerShell 7.6.6, whereOut-GridViewnever returns and opens no window (PowerShell/PowerShell#27994). On that release Nebula.Core warns and writes the results to the console instead;Get-QuarantineToRelease -GridViewselects nothing, so-ReleaseSelected/-DeleteSelectednever act on every message by mistake. Other PowerShell versions still open the grid as before.Add/Get/Remove-EntraGroupUsernow resolve invited Entra guests by external e-mail through a Graph-compatible fallback while preserving direct lookup for tenant members.Add/Remove-EntraGroupDevice,Add/Remove-EntraGroupOwner, andAdd/Remove-EntraGroupUsernow support the positional form<GroupName> <MemberIdentifier>in addition to named parameters.Add-UserMsolAccountSkuskips licenses a user already has, with a warning. They no longer take a seat from the availability count (which could skip a later user in the same pipeline), and they are no longer re-assigned with an emptydisabledPlanslist, which re-enabled the service plans disabled for that user. A user listed more than once in the same run is processed once.Compare-EnterpriseApplication's CSV report now renders non-scalar diff values (redirect URIs, permissions, app roles, owners) as readable JSON instead of an identical, uninformative string on both sides, and now honors the module's configured CSV encoding and delimiter.Connect-EOLnow detects whether the installedExchangeOnlineManagementversion actually supports-DisableWAM/-Device(introduced in 3.7.2) before passing them toConnect-ExchangeOnline, instead of always forwarding them; this prevents a parameter-binding error when Exchange Online is intentionally pinned below 3.7.2 as a workaround for the Graph/EOL assembly clash, where WAM isn't the default and those parameters don't exist.Connect-Nebulanow initializes Microsoft Graph before Exchange Online and uses a WAM-disabled EXO sign-in for the combined flow, avoiding the known cross-module authentication assembly and broker conflict.Copy-EntraGroup,Copy-EntraGroupOwner, andRemove-EntraGroupOwner -ClearAllnow readInvoke-MgGraphRequestresults as the Hashtables the cmdlet actually returns; owner and member copies (and owner clearing) previously found nothing to process, and owner labels could print asSystem.Collections.Hashtable.Copy-UserMsolAccountSkuandMove-UserMsolAccountSkunow check tenant seat availability per SKU before assigning; a license with no available units is skipped (or left on the source, forMove) with a warning instead of failing the entireSet-MgUserLicensebatch and copying/moving nothing.Export-IntuneAppInventoryno longer closes the whole PowerShell session when it fails (it calledexit 1); it now reports the error and returns.- Microsoft Graph collection reads now stop with an error when a page fails, instead of warning and carrying on with the pages read so far as if they were complete. This affects Intune inventory and app-based groups, group owner copies, multi-page membership reads in the batched cmdlets (the item is reported as failed), and Enterprise Application snapshots (no snapshot is written when owners or App Role Assignments can't be read).
Export-IntuneAppInventorynow normalizes cachedLastInventoryvalues through Nebula's configured date/time formatter so export output matches the single-device helper.Get-NormalizedLicenseKeynow strips invisible Unicode format characters (e.g. zero-width spaces occasionally present inSkuPartNumbervalues returned by Graph for some tenants/SKUs, such as Windows 365) before building lookup keys, so those licenses correctly resolve against the catalog instead of showing up as unmapped.- name lookups in the Enterprise Application cmdlets and
Get-IntuneAppPresencenow URI-encode their Graph filter, so names containing&,#or similar characters resolve instead of truncating the query. Get-QuarantineForMailbox,Get-QuarantineFrom,Get-QuarantineFromDomainandUnlock-QuarantineFromnow read every page of quarantine results; they only saw the first 100 messages, andGet-QuarantineFromDomainfiltered the domain within those 100.Get-UserGroupsnow falls back to Microsoft Graph resolution when Exchange mailbox lookup is not available, so Entra guest users can be queried without using the GUI.Invoke-NCGraphAllPagesCorenow distinguishes a Graph collection with zero items from a non-paged single object instead of relying on truthiness, fixing phantom-item failures (e.g. an app with no owners) in downstream cmdlets likeCopy-EnterpriseApplication.New-IntuneAppBasedGroup -UpdateExistingno longer removes group members when Entra device resolution fails or is incomplete, including when a matching device has no Azure AD device ID or no Entra device, or when adding a device to the group fails (it skips removals with a warning). It also no longer creates a duplicate group when the existing-group lookup itself fails (the target is skipped with an error).New-IntuneAppBasedGroupnow applies its$selectwhen reading managed devices; the query option was sent as literal text and ignored.Search-EntraUser -SearchIn DisplayName,UserPrincipalNameorMailno longer returns a user just because the search text is that user's UPN or object ID; the selected field must contain the text.Set-NCEnterpriseApplicationFromSnapshotno longer copiesidentifierUris(unique per tenant, caused Graph BadRequest on apps exposing an API); a warning reports the source value instead.Set-NCEnterpriseApplicationFromSnapshotnow applies the Service Principal'sTagsandHomepageon both create and update-in-place, so cloned apps correctly show up under Entra's "Enterprise applications" blade. An update also clears a homepage the snapshot doesn't have.Set-NCEnterpriseApplicationFromSnapshotstrips the read-onlyredirectUriSettingsbefore writingweb/spa/publicClient, avoiding a Graph BadRequest when both are sent together.Test-EOLConnectionno longer forces a reconnect back to the Windows AD-derived identity when a cmdlet is called without an explicit-UserPrincipalName; an already-active Exchange Online session (e.g. afterConnect-Nebula -UserPrincipalName <other account>) is now kept as-is instead of being silently swapped out.- License catalog download (
Get-LicenseSourceData) now falls back to the existing stale cache when GitHub is unreachable after all retry attempts, instead of failing outright; a warning reports the fallback and its cache age. The cache is then honored for the configuredLicenseCacheDays(minimum 1 day) before retrying, instead of re-attempting the full download on every subsequent call.