Skip to content

v1.3.0

Latest

Choose a tag to compare

@gioxx gioxx released this 09 Oct 14:11
· 0 commits to aa8038ca3205aadfe3bb34d1891a8e0207554309 since this release
e19d6ab

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 in Eastern Standard Time unless you set DateTimeTimeZone. You can restore the previous behavior in your settings.psd1 (see Get-NebulaConfig).
  • On PowerShell 7.6.6, Out-GridView hangs (PowerShell/PowerShell#27994): every -GridView in 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, and Compare-EnterpriseApplication to 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 reports OwnersFailed for owners that could not be added. With -IncludeAppRoleAssignments, Import- and Copy- also request AppRoleAssignment.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, and Compare- 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-QuarantineForMailbox to check quarantine across all of a mailbox's SMTP aliases (primary and secondary) in one call, since Get-QuarantineMessage -RecipientAddress only 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 -Days or explicit -StartReceivedDate/-EndReceivedDate.
  • Get-UserDevices to 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, Intune or Entra+Intune) and the Entra/Intune IDs; the console shows a compact table, and -GridView, Select-Object * or Export-Csv give 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-IntuneAppPresence for quick single-device app presence checks with one-row output, always include LastInventory, and return the matched app name in AppName. 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-EntraUser for direct UPN-based Entra user removal with Graph.
  • Search-EntraUser to search users by display name, user principal name, or mail, including guest UPN fragments. Without -IndexOnly it reads the whole tenant only when partial matching is needed, not for an exact UPN or object ID.

Improvements

  • Connect-EOL suppresses Connect-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-NebulaConnections now 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 as Connect-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-MboxPermission now shows the source mailbox RecipientTypeDetails value in the output heading.
  • Get-UserGroups keeps the existing Exchange-first behavior for regular users while handling guest identities more gracefully.
  • New-IntuneAppBasedGroup adds devices one reference at a time inside a Graph batch instead of a single members@odata.bind update, 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-user Add-EntraGroupUser now 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) and Write-Progress (including Graph throttling waits) in between; error details now carry the Graph batch error message instead of the SDK exception text, and -ShowErrorDetails no longer changes user-lookup messages in the license functions.
  • Change the default CSV delimiter to comma for a more standard US-friendly baseline.
  • The LicenseMapping GitHub Action now also checks JSON/M365_licenses_custom.json for 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 Time to align with the module's US baseline.
  • The Pester test suite (Tests/Public/*.Tests.ps1) now correctly scopes its fixtures inside BeforeAll blocks 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-MboxMrmCleanup cutoff 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-SharedMailboxCompliance filters successful sign-ins server-side and Get-UserLastSeen reads only the latest page of sign-ins (dates are kept in UTC).

Fixes

  • -GridView no longer freezes the session on PowerShell 7.6.6, where Out-GridView never returns and opens no window (PowerShell/PowerShell#27994). On that release Nebula.Core warns and writes the results to the console instead; Get-QuarantineToRelease -GridView selects nothing, so -ReleaseSelected/-DeleteSelected never act on every message by mistake. Other PowerShell versions still open the grid as before.
  • Add/Get/Remove-EntraGroupUser now 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, and Add/Remove-EntraGroupUser now support the positional form <GroupName> <MemberIdentifier> in addition to named parameters.
  • Add-UserMsolAccountSku skips 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 empty disabledPlans list, 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-EOL now detects whether the installed ExchangeOnlineManagement version actually supports -DisableWAM/-Device (introduced in 3.7.2) before passing them to Connect-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-Nebula now 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, and Remove-EntraGroupOwner -ClearAll now read Invoke-MgGraphRequest results as the Hashtables the cmdlet actually returns; owner and member copies (and owner clearing) previously found nothing to process, and owner labels could print as System.Collections.Hashtable.
  • Copy-UserMsolAccountSku and Move-UserMsolAccountSku now check tenant seat availability per SKU before assigning; a license with no available units is skipped (or left on the source, for Move) with a warning instead of failing the entire Set-MgUserLicense batch and copying/moving nothing.
  • Export-IntuneAppInventory no longer closes the whole PowerShell session when it fails (it called exit 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-IntuneAppInventory now normalizes cached LastInventory values through Nebula's configured date/time formatter so export output matches the single-device helper.
  • Get-NormalizedLicenseKey now strips invisible Unicode format characters (e.g. zero-width spaces occasionally present in SkuPartNumber values 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-IntuneAppPresence now URI-encode their Graph filter, so names containing &, # or similar characters resolve instead of truncating the query.
  • Get-QuarantineForMailbox, Get-QuarantineFrom, Get-QuarantineFromDomain and Unlock-QuarantineFrom now read every page of quarantine results; they only saw the first 100 messages, and Get-QuarantineFromDomain filtered the domain within those 100.
  • Get-UserGroups now 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-NCGraphAllPagesCore now 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 like Copy-EnterpriseApplication.
  • New-IntuneAppBasedGroup -UpdateExisting no 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-IntuneAppBasedGroup now applies its $select when reading managed devices; the query option was sent as literal text and ignored.
  • Search-EntraUser -SearchIn DisplayName, UserPrincipalName or Mail no 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-NCEnterpriseApplicationFromSnapshot no longer copies identifierUris (unique per tenant, caused Graph BadRequest on apps exposing an API); a warning reports the source value instead.
  • Set-NCEnterpriseApplicationFromSnapshot now applies the Service Principal's Tags and Homepage on 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-NCEnterpriseApplicationFromSnapshot strips the read-only redirectUriSettings before writing web/spa/publicClient, avoiding a Graph BadRequest when both are sent together.
  • Test-EOLConnection no 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. after Connect-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 configured LicenseCacheDays (minimum 1 day) before retrying, instead of re-attempting the full download on every subsequent call.