Public/Connect-Raidiness.ps1
|
function Connect-Raidiness { <# .SYNOPSIS Signs in to Microsoft Graph with the read-only scopes Raidiness needs. .DESCRIPTION A thin wrapper around Connect-MgGraph. By default it requests only the broadly applicable baseline in collect.manifest.json. Extra delegated permissions are grouped by capability and are requested only when named with OptionalScopeGroup. The module never requests write access. Sign-in is interactive (delegated). An account with the Global Reader role sees the most; a regular admin account still produces a useful (partial) assessment — anything unreadable degrades to "not measured". By default, Raidiness uses interactive browser authentication. Device code and Windows Web Account Manager (WAM) authentication are available as explicit alternatives through AuthenticationMode. .PARAMETER TenantId The tenant to sign in to. Optional for a single-tenant account. .PARAMETER Scopes Override the manifest scope selection with an exact list. This cannot be combined with OptionalScopeGroup. .PARAMETER OptionalScopeGroup Add one or more capability-specific scope groups to the baseline. Valid group names are listed by the parameter completion and in the collection manifest. Use only groups needed by the checks being run. .PARAMETER AuthenticationMode Select the interactive authentication flow. Browser (the default) opens an interactive browser and opts out of WAM. DeviceCode displays a code to enter at https://microsoft.com/devicelogin. WAM uses the native Windows Web Account Manager behavior supplied by Microsoft Graph. .EXAMPLE Connect-Raidiness Invoke-Raidiness Export-RaidinessReport .EXAMPLE Connect-Raidiness -AuthenticationMode DeviceCode #> [CmdletBinding()] param( [string] $TenantId, [string[]] $Scopes, [ValidateSet('AgentIdentity', 'Copilot', 'Defender', 'eDiscovery', 'Intune', 'Purview', 'SharePoint', 'Teams')] [string[]] $OptionalScopeGroup, [ValidateSet('Browser', 'DeviceCode', 'WAM')] [string] $AuthenticationMode = 'Browser' ) $loadedVersions = @( Get-Module Raidiness -All | ForEach-Object { $_.Version.ToString() } | Sort-Object -Unique ) if ($loadedVersions.Count -gt 1) { Write-Warning "Multiple Raidiness module versions are loaded: $($loadedVersions -join ', '). Open a clean PowerShell session, or remove all loaded copies with 'Remove-Module Raidiness -Force' before importing the intended version." } $ErrorActionPreference = 'Stop' if ($Scopes -and $OptionalScopeGroup) { throw 'Scopes is an exact override and cannot be combined with OptionalScopeGroup.' } $manifestPath = Join-Path $PSScriptRoot '..' 'collect.manifest.json' $manifest = Get-Content -Path $manifestPath -Raw | ConvertFrom-Json if (-not $Scopes) { if (-not ($manifest.PSObject.Properties['scopes'] -and $manifest.scopes.PSObject.Properties['baseline'])) { throw "The collection manifest at $manifestPath has no 'scopes.baseline' section. Reinstall the module, or pass -Scopes to request an explicit list." } $Scopes = @($manifest.scopes.baseline) # An omitted [string[]] parameter is $null, and @($null) still holds one # element, so filter before iterating; otherwise the loop runs once with # an empty group name and strict mode fails on the property lookup. foreach ($group in @($OptionalScopeGroup | Where-Object { $_ })) { $Scopes += @($manifest.scopes.optional.$group.scopes) } $Scopes = @($Scopes | Sort-Object -Unique) } $writeScopes = @($Scopes | Where-Object { $_ -notmatch '\.Read(\.|$)' -and $_ -ne 'openid' -and $_ -ne 'profile' }) if ($writeScopes.Count -gt 0) { throw "Refusing to request non-read scopes: $($writeScopes -join ', '). Raidiness is read-only." } Write-Host 'Raidiness — read-only sign-in to Microsoft Graph.' -ForegroundColor Cyan Write-Host "Requesting $($Scopes.Count) read-only scopes. Nothing in your tenant will be changed." -ForegroundColor Cyan $parameters = @{ Scopes = $Scopes; NoWelcome = $true } if ($TenantId) { $parameters.TenantId = $TenantId } if ($AuthenticationMode -eq 'DeviceCode') { $parameters.UseDeviceCode = $true $deviceLoginUrl = 'https://microsoft.com/devicelogin' Write-Host 'Opening the Microsoft sign-in page in your default browser. Enter the code shown below.' -ForegroundColor Cyan try { $null = Start-Process -FilePath $deviceLoginUrl } catch { Write-Warning "Could not open the default browser automatically. Open $deviceLoginUrl yourself, then enter the code shown below." } } if ($AuthenticationMode -eq 'Browser') { $previousUseWam = [Environment]::GetEnvironmentVariable('MSAL_USE_WAM', 'Process') try { # Graph Authentication reads this opt-out while it initializes the # interactive credential, so it must surround Connect-MgGraph. [Environment]::SetEnvironmentVariable('MSAL_USE_WAM', 'false', 'Process') Connect-MgGraph @parameters } finally { [Environment]::SetEnvironmentVariable('MSAL_USE_WAM', $previousUseWam, 'Process') } } else { Connect-MgGraph @parameters } $context = Get-MgContext $tenantName = $null $initialDomain = $null try { # The authentication context only contains the tenant GUID. Resolve the # organisation once so the confirmation identifies the tenant in the # same way an administrator sees it in Microsoft 365. $organization = Invoke-MgGraphRequest -Method GET -Uri '/v1.0/organization?$select=displayName,verifiedDomains' -OutputType PSObject $tenant = @($organization.value | Select-Object -First 1)[0] $tenantName = $tenant.displayName $initialDomain = @( $tenant.verifiedDomains | Where-Object { $_.isInitial -eq $true } | ForEach-Object { $_.name } | Where-Object { $_ } | Select-Object -First 1 )[0] } catch { Write-Verbose "Could not read the tenant display name from /organization: $($_.Exception.Message)" } if ($tenantName) { $tenantDescription = if ($initialDomain) { "$tenantName ($initialDomain)" } else { $tenantName } Write-Host "Connected to tenant $tenantDescription as $($context.Account)." -ForegroundColor Green } else { # A custom scope override may omit Organization.Read.All. Keep sign-in # usable in that case, but make the less-friendly fallback explicit. Write-Host "Connected to tenant $($context.TenantId) as $($context.Account)." -ForegroundColor Green } } |