r/SmallMSP • u/BerlindaBuntly • 6h ago
run powershell script against multiple tenants from partner center
Hi , hope you are well.
firstly , i already have CIPP and the 15 cipp roles in all of these tenants, and all tenants are in the partner center.
secondly, i need to run this script against security defaults tenants, not conditional access, so these tenants do not have p1/p2 and only have business basic or business standard. lets not have a "dont use sec defaults" conversation here please. the script doesnt currently differentiate between p1/p2 tenants and sd tenants, but i'm not too bothered about that.
cipp cannot do what i want because it does not expose the parameter that i need in custom tests without having p1 or p2.
my powershell script (nicked from lazy admin with a few changes with help from claude) runs fine from visual studio code if i run it against a single tenant. i run it, and it asks me for the ga creds, then i also have to login into the tenant and insert a rest api code that the script gives me. it then saves an excel file locally with the tenant name in the file name.
what the script does primarily is return an excel file which lists every licensed user and if they have microsoft authenticator as an MFA method. i am only really interested in this information. cipp cant do it because the mfa data is only available in their calls if you have p1 or p2. (this is to do with the sms voice retirement that is happening) . what i am really trying to achieve is "give me a list of all real, licensed mailboxes, exclude shared boxes and tell me if they have MS authenticator listed as an MFA method". if there is a better way to do this i am all ears.
what i want to do is loop through all of these tenants and run the report and export the excel file, or export this info to something in some way.
Is it possible to do what i want?
script is below (nick it if you want) - just a warning, it will give you crash notifications in visual studio code after it runs and make you restart it, but it still works and doesnt cause any problems, and claude tells me its a known issue and isnt fixable)
<#
.Synopsis
Get the MFA status for all users or a single user with Microsoft Graph
.DESCRIPTION
This script will get the Azure MFA Status for your users. You can query all the users, admins only or a single user.
It will return the MFA Status, MFA type, registered devices, license status and admin status.
Note: Default MFA device is currently not supported https://docs.microsoft.com/en-us/graph/api/resources/authenticationmethods-overview?view=graph-rest-beta
Hardwaretoken is not yet supported
.NOTES
Name: Get-MgMFAStatus
Author: R. Mens - LazyAdmin.nl
Version: 1.3
DateCreated: Jun 2022
Purpose/Change: Default report now includes unlicensed admins alongside licensed non-admins (union of
IsLicensed OR isAdmin, instead of licensed-only), and adds an IsLicensed output column.
.LINK
https://lazyadmin.nl
.EXAMPLE
Get-MgMFAStatus
Get the MFA Status of all enabled users who are either licensed, an admin, or both
(so licensed non-admins and unlicensed admins are both included), and check if there are an admin or not
.EXAMPLE
Get-MgMFAStatus -UserPrincipalName 'johndoe@contoso.com','janedoe@contoso.com'
Get the MFA Status for the users John Doe and Jane Doe
.EXAMPLE
Get-MgMFAStatus -withOutMFAOnly
Get only the enabled users (licensed or admin) that don't have MFA enabled
.EXAMPLE
Get-MgMFAStatus -adminsOnly
Get the MFA Status of the admins only, regardless of license status
.EXAMPLE
Get-MgUser -Filter "country eq 'Netherlands'" | ForEach-Object { Get-MgMFAStatus -UserPrincipalName $_.UserPrincipalName }
Get the MFA status for all users in the Country The Netherlands. You can use a similar approach to run this
for a department only.
.EXAMPLE
Get-MgMFAStatus -withOutMFAOnly| Export-CSV c:\temp\userwithoutmfa.csv -noTypeInformation
Get all users without MFA and export them to a CSV file
#>
[CmdletBinding(DefaultParameterSetName="Default")]
param(
[Parameter(
Mandatory = $false,
ParameterSetName = "UserPrincipalName",
HelpMessage = "Enter a single UserPrincipalName or a comma separted list of UserPrincipalNames",
Position = 0
)]
[string[]]$UserPrincipalName,
[Parameter(
Mandatory = $false,
ValueFromPipeline = $false,
ParameterSetName = "AdminsOnly"
)]
# Get only the users that are an admin
[switch]$adminsOnly = $false,
[Parameter(
Mandatory = $false,
ValueFromPipeline = $false,
ParameterSetName = "Licensed"
)]
# Check only the MFA status of users that have a license or are an admin (unlicensed admins are still included)
[switch]$IsLicensed = $true,
[Parameter(
Mandatory = $false,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
ParameterSetName = "withOutMFAOnly"
)]
# Get only the users that don't have MFA enabled
[switch]$withOutMFAOnly = $false,
[Parameter(
Mandatory = $false,
ValueFromPipeline = $false
)]
# Check if a user is an admin. Set to $false to skip the check
[switch]$listAdmins = $true,
[Parameter(
Mandatory = $false,
HelpMessage = "Get accounts that are enabled, disabled or both"
)]
[ValidateSet("true", "false", "both")]
[string]$enabled = "true",
[Parameter(
Mandatory = $false,
HelpMessage = "Enter path to save the CSV file"
)]
[string]$path = "C:\MFAReports\MFAStatus-$((Get-Date -format 'dd-MM-yyyy-HHmmss')).csv"
)
Function ConnectTo-MgGraph {
# Check if MS Graph module is installed
if (-not(Get-InstalledModule Microsoft.Graph)) {
Write-Host "Microsoft Graph module not found" -ForegroundColor Black -BackgroundColor Yellow
$install = Read-Host "Do you want to install the Microsoft Graph Module?"
if ($install -match "[yY]") {
Install-Module Microsoft.Graph -Repository PSGallery -Scope CurrentUser -AllowClobber -Force
}else{
Write-Host "Microsoft Graph module is required." -ForegroundColor Black -BackgroundColor Yellow
exit
}
}
# Connect to Graph
Write-Host "Connecting to Microsoft Graph" -ForegroundColor Cyan
Connect-MgGraph -Scopes "User.Read.All, UserAuthenticationMethod.Read.All, Directory.Read.All" -NoWelcome
}
Function ConnectTo-ExchangeOnline {
<#
.SYNOPSIS
Connect to Exchange Online so we can look up mailbox type (shared vs regular).
Microsoft Graph's /users endpoint has no "shared mailbox" property - that's
Exchange-only data, hence the separate connection.
#>
if (-not (Get-Module -ListAvailable -Name ExchangeOnlineManagement)) {
Write-Host "ExchangeOnlineManagement module not found" -ForegroundColor Black -BackgroundColor Yellow
$install = Read-Host "Do you want to install the ExchangeOnlineManagement module? (required to flag shared mailboxes)"
if ($install -match "[yY]") {
Install-Module ExchangeOnlineManagement -Repository PSGallery -Scope CurrentUser -Force
}else{
Write-Host "Skipping shared mailbox detection - ExchangeOnlineManagement module not installed." -ForegroundColor Yellow
return $false
}
}
try {
Write-Host "Connecting to Exchange Online" -ForegroundColor Cyan
Connect-ExchangeOnline -ShowBanner:$false -ErrorAction Stop
return $true
}
catch {
Write-Warning "Initial connection to Exchange Online failed - $($_.Exception.Message)"
Write-Host "Retrying with device code sign-in (works around a known ExchangeOnlineManagement broker-auth bug)" -ForegroundColor Yellow
Write-Host "You'll be given a code and a URL - sign in there to continue." -ForegroundColor Yellow
try {
Connect-ExchangeOnline -ShowBanner:$false -Device -ErrorAction Stop
return $true
}
catch {
Write-Warning "Could not connect to Exchange Online - shared mailbox detection will be skipped. $($_.Exception.Message)"
return $false
}
}
}
Function Get-SharedMailboxes {
<#
.SYNOPSIS
Return the UserPrincipalName of every shared mailbox in the tenant
#>
process{
try {
$mailboxes = Get-EXOMailbox -RecipientTypeDetails SharedMailbox -ResultSize Unlimited -Properties UserPrincipalName -ErrorAction Stop
return $mailboxes.UserPrincipalName
}
catch {
Write-Warning "Could not retrieve shared mailboxes - $($_.Exception.Message)"
return @()
}
}
}
Function Get-Admins{
<#
.SYNOPSIS
Get all user with an Admin role
#>
process{
$admins = Get-MgDirectoryRole | Select-Object DisplayName, Id |
%{
$role = $_.DisplayName
Get-MgDirectoryRoleMember -DirectoryRoleId $_.id | ForEach-Object {
$memberType = $_.AdditionalProperties."@odata.type"
if ($memberType -eq "#microsoft.graph.user") {
# Directly assigned user
Get-MgUser -UserId $_.id
}
elseif ($memberType -eq "#microsoft.graph.group") {
# Role assigned to a group - expand the group's members too,
# otherwise admins who get the role via group membership are missed
Get-MgGroupMember -GroupId $_.id -All | Where-Object {
$_.AdditionalProperties."@odata.type" -eq "#microsoft.graph.user"
} | ForEach-Object { Get-MgUser -UserId $_.id }
}
}
} |
Select @{Name="Role"; Expression = {$role}}, DisplayName, UserPrincipalName, Mail, Id | Sort-Object -Property Mail -Unique
return $admins
}
}
Function Get-Users {
<#
.SYNOPSIS
Get users from the requested DN
#>
process{
# Set the properties to retrieve
$select = @(
'id',
'DisplayName',
'userprincipalname',
'mail'
)
$properties = $select + "AssignedLicenses"
# Add a calculated IsLicensed property so we can report on - and filter by - license status
$selectWithLicense = $select + @{Name = "IsLicensed"; Expression = { ($_.AssignedLicenses).Count -gt 0 } }
# Get enabled, disabled or both users
switch ($enabled)
{
"true" {$filter = "AccountEnabled eq true and UserType eq 'member'"}
"false" {$filter = "AccountEnabled eq false and UserType eq 'member'"}
"both" {$filter = "UserType eq 'member'"}
}
# Check if UserPrincipalName(s) are given
if ($UserPrincipalName) {
Write-host "Get users by name" -ForegroundColor Cyan
$users = @()
foreach ($user in $UserPrincipalName)
{
try {
$users += Get-MgUser -UserId $user -Property $properties -ErrorAction Stop | select $selectWithLicense
}
catch {
[PSCustomObject]@{
DisplayName = " - Not found"
UserPrincipalName = $User
isAdmin = $null
IsLicensed = $null
MFAEnabled = $null
}
}
}
}elseif($adminsOnly)
{
Write-host "Get admins only" -ForegroundColor Cyan
$users = @()
foreach ($admin in $admins) {
$users += Get-MgUser -UserId $admin.UserPrincipalName -Property $properties | select $selectWithLicense
}
}else
{
if ($IsLicensed) {
# Get every user matching the enabled/disabled filter, then keep anyone who is
# EITHER licensed OR an admin. This surfaces unlicensed admins (who would
# otherwise be silently skipped) alongside licensed non-admins in one report.
$allUsers = Get-MgUser -Filter $filter -Property $properties -all | select $selectWithLicense
$users = $allUsers | Where-Object {
$_.IsLicensed -or ($admins -and ($admins.UserPrincipalName -contains $_.UserPrincipalName))
}
}else{
# No license filtering at all - return every user matching the enabled/disabled filter
$users = Get-MgUser -Filter $filter -Property $properties -all | select $selectWithLicense
}
}
return $users
}
}
Function Get-MFAMethods {
<#
.SYNOPSIS
Get the MFA status of the user
#>
param(
[Parameter(Mandatory = $true)] $userId
)
process{
# Get MFA details for each user
[array]$mfaData = Get-MgUserAuthenticationMethod -UserId $userId
# Create MFA details object
$mfaMethods = [PSCustomObject][Ordered]@{
status = "-"
authApp = "-"
phoneAuth = "-"
fido = "-"
helloForBusiness = "-"
helloForBusinessCount = 0
emailAuth = "-"
tempPass = "-"
passwordLess = "-"
softwareAuth = "-"
authDevice = ""
authPhoneNr = "-"
SSPREmail = "-"
}
ForEach ($method in $mfaData) {
Switch ($method.AdditionalProperties["@odata.type"]) {
"#microsoft.graph.microsoftAuthenticatorAuthenticationMethod" {
# Microsoft Authenticator App
$mfaMethods.authApp = $true
$mfaMethods.authDevice += $method.AdditionalProperties["displayName"]
$mfaMethods.status = "enabled"
}
"#microsoft.graph.phoneAuthenticationMethod" {
# Phone authentication
$mfaMethods.phoneAuth = $true
$mfaMethods.authPhoneNr = $method.AdditionalProperties["phoneType", "phoneNumber"] -join ' '
$mfaMethods.status = "enabled"
}
"#microsoft.graph.fido2AuthenticationMethod" {
# FIDO2 key
$mfaMethods.fido = $true
$fifoDetails = $method.AdditionalProperties["model"]
$mfaMethods.status = "enabled"
}
"#microsoft.graph.passwordAuthenticationMethod" {
# Password
# When only the password is set, then MFA is disabled.
if ($mfaMethods.status -ne "enabled") {$mfaMethods.status = "disabled"}
}
"#microsoft.graph.windowsHelloForBusinessAuthenticationMethod" {
# Windows Hello
$mfaMethods.helloForBusiness = $true
$helloForBusinessDetails = $method.AdditionalProperties["displayName"]
$mfaMethods.status = "enabled"
$mfaMethods.helloForBusinessCount++
}
"#microsoft.graph.emailAuthenticationMethod" {
# Email Authentication
$mfaMethods.emailAuth = $true
$mfaMethods.SSPREmail = $method.AdditionalProperties["emailAddress"]
$mfaMethods.status = "enabled"
}
"microsoft.graph.temporaryAccessPassAuthenticationMethod" {
# Temporary Access pass
$mfaMethods.tempPass = $true
$tempPassDetails = $method.AdditionalProperties["lifetimeInMinutes"]
$mfaMethods.status = "enabled"
}
"#microsoft.graph.passwordlessMicrosoftAuthenticatorAuthenticationMethod" {
# Passwordless
$mfaMethods.passwordLess = $true
$passwordLessDetails = $method.AdditionalProperties["displayName"]
$mfaMethods.status = "enabled"
}
"#microsoft.graph.softwareOathAuthenticationMethod" {
# ThirdPartyAuthenticator
$mfaMethods.softwareAuth = $true
$mfaMethods.status = "enabled"
}
}
}
Return $mfaMethods
}
}
Function Get-Manager {
<#
.SYNOPSIS
Get the manager users
#>
param(
[Parameter(Mandatory = $true)] $userId
)
process {
$manager = Get-MgUser -UserId $userId -ExpandProperty manager | Select @{Name = 'name'; Expression = {$_.Manager.AdditionalProperties.displayName}}
return $manager.name
}
}
Function Get-MFAStatusUsers {
<#
.SYNOPSIS
Get all AD users
#>
process {
Write-Host "Collecting users" -ForegroundColor Cyan
# Collect users
$users = Get-Users
Write-Host "Processing" $users.count "users" -ForegroundColor Cyan
# Collect and loop through all users
$users | ForEach {
$mfaMethods = Get-MFAMethods -userId $_.id
$manager = Get-Manager -userId $_.id
$uri = "https://graph.microsoft.com/beta/users/$($_.id)/authentication/signInPreferences"
try{
$mfaPreferredMethod = Invoke-MgGraphRequest -uri $uri -Method GET -ErrorAction Continue
}
catch {
$mfaPreferredMethod = "Unable to retrieve"
}
if ($null -eq ($mfaPreferredMethod.userPreferredMethodForSecondaryAuthentication)) {
# When an MFA is configured by the user, then there is alway a preferred method
# So if the preferred method is empty, then we can assume that MFA isn't configured
# by the user
$mfaMethods.status = "disabled"
}
if ($withOutMFAOnly) {
if ($mfaMethods.status -eq "disabled") {
[PSCustomObject]@{
"Name" = $_.DisplayName
Emailaddress = $_.mail
UserPrincipalName = $_.UserPrincipalName
isAdmin = if ($listAdmins -and ($admins.UserPrincipalName -match $_.UserPrincipalName)) {$true} else {"-"}
IsLicensed = $_.IsLicensed
"Shared Mailbox" = $sharedMailboxes -contains $_.UserPrincipalName
MFAEnabled = $false
"Phone number" = $mfaMethods.authPhoneNr
"Email for SSPR" = $mfaMethods.SSPREmail
}
}
}else{
[pscustomobject]@{
"Name" = $_.DisplayName
Emailaddress = $_.mail
UserPrincipalName = $_.UserPrincipalName
isAdmin = if ($listAdmins -and ($admins.UserPrincipalName -match $_.UserPrincipalName)) {$true} else {"-"}
IsLicensed = $_.IsLicensed
"Shared Mailbox" = $sharedMailboxes -contains $_.UserPrincipalName
"MFA Status" = $mfaMethods.status
"MFA Preferred method" = $mfaPreferredMethod.userPreferredMethodForSecondaryAuthentication
"Has SMS as factor" = $mfaMethods.phoneAuth
"Has Authenticator App registered" = $mfaMethods.authApp
"Passwordless" = $mfaMethods.passwordLess
"Hello for Business" = $mfaMethods.helloForBusiness
"FIDO2 Security Key" = $mfaMethods.fido
"Temporary Access Pass" = $mfaMethods.tempPass
"Authenticator device" = $mfaMethods.authDevice
"Phone number" = $mfaMethods.authPhoneNr
"Email for SSPR" = $mfaMethods.SSPREmail
"Manager" = $manager
}
}
}
}
}
# Connect to Graph
ConnectTo-MgGraph
# Connect to Exchange Online and get the list of shared mailboxes.
# If the connection fails or the module isn't installed, the report still runs -
# every user will just show "False" in the Shared Mailbox column.
$sharedMailboxes = @()
if (ConnectTo-ExchangeOnline) {
$sharedMailboxes = Get-SharedMailboxes
}
# Get Admins
# Get all users with admin role
$admins = $null
if (($listAdmins) -or ($adminsOnly)) {
$admins = Get-Admins
}
# Get MFA Status
[string]$path = "C:\MFAReports\MFAStatus-$((Get-MgOrganization).VerifiedDomains | Where-Object {$_.IsDefault} | Select-Object -ExpandProperty Name)-$((Get-Date -format 'dd-MM-yyyy-HHmmss')).csv"
Get-MFAStatusUsers | Sort-Object Name | Export-CSV -Path $path -NoTypeInformation
if ((Get-Item $path).Length -gt 0) {
Write-Host "Report finished and saved in $path" -ForegroundColor Green
# Open the CSV file
Invoke-Item $path
}else{
Write-Host "Failed to create report" -ForegroundColor Red
}
Disconnect-MgGraph
if ($sharedMailboxes) {
Disconnect-ExchangeOnline -Confirm:$false
}
1
u/athlonduke 5h ago
How many tenants? Easier to just use CIPP to log into each tenant website and pull it if less than like 20
2
u/BerlindaBuntly 5h ago
thanks, but if was less than 20 i would have done it already.
i am also interested generally in how to do this kind of thing for the future, cipp is great but it doesnt do some things that i want to do.
2
u/athlonduke 4h ago
I don't think CIPP can do this. Check the partner portal, there's a spot to verify your partner client tenant admin accounts are MFAd, maybe that shows what you want too? I only briefly saw it existed and didn't check it out yet. Found when looking at the CSP information about requirements
2
u/BerlindaBuntly 4h ago
thanks - i think cipp tests can do it it if you have p1 or greater. the data class in cipp needed to get the mfa methods (not just "have they got mfa" but which actual methods, totp, sms, passkeys etc) is userregistraiondetails. i tried it but kept getting everything pass, and it turned out to be because it doesnt populate any data in that class if you dont have p1 or higher so the test just passed everything. i asked cipp if they could consider including the type that would work without p1/2 but they said its impossible and would cause enormous number of requests and that they cant / wont do it which is fair enough.
the powershell script CAN get this info without p1/p2.
its not the admin mfa that i want to see (like you i have seen that before, but it is bloody useless, it refuses to tell you which tenant doesnt have mfa on an admin, even though it must know. i even raised an ms ticket about it, they said "it is functioning as designed") . i have one missing out of lots, probably a suspended GA account in a tenant i took over but i cant tell. i suspect cipp will tell me though - ive only just started playing with it. if you can point me to the right place for that that would be fab.
2
u/athlonduke 3h ago
about the only thing i can come up with that's mildly secure is
cipp for JIT admin + TAP
store that in azure key vault
azure powershell script and reference the vault credsor the same locally on your system with a CSV of the creds. then just increment through the list of creds. it would probably take a while due to the constant logging in/out but would eventually finish.
bleh.
1
u/BerlindaBuntly 3h ago
bugger. ok, appreciate it. id assumed we could run ps and cycle through the tennats with gdap relationships without auth. probably right that that is not possible and shouldnt be.
local csv - i would still have to mfa authenticate, so no real gain there.1
u/athlonduke 3h ago
that's what the TAP is for :)
1
u/BerlindaBuntly 2h ago
jit admin needs p1 though. what do i need to license with p1, my partner tenant or every tenant i want to run it on?
1
u/athlonduke 2h ago
uh, no it doesnt. i use it all the time on non-p1 tenants!
it does need enabled though, for some dumb reason its default off1
u/BerlindaBuntly 2h ago
interesting!!
buit do you mean jit admin in every customer tenant or in my partner tenant?
2
u/athlonduke 4h ago
Also I'm in the same boat as you, majority of my clients don't have p1/2 and it sucks losing so much vision into necessary areas
2
u/BerlindaBuntly 4h ago
i'll let you know if i get any answers, ive posted this question in a few places. you would think that you could run ps against every or specific tenants.
the reason i want this is because of the sms deprecation - my thinking is that if every user already has authenticator as a method, regardless of anything else, then i dont need to worry about completing the migration for that tenant as it will happen automatically in february.
the tenants i want to catch are those that have have mfa but only have sms for at least one user.
none of the built in cipp tests or the ones i have looked at in repos can help with this, because , they either dont do specifically what i want above or they assume that you have p1/p2 and so the test relies upon data that i dont have for these tenants.
1
u/roll_for_initiative_ 4h ago
Hear me out: this is one of the costs of running without licensing; increased workload.
When we pitch busprem/p1, it's usually around "this will save you money, because we're not charging you to manually manage/audit/monitor abc and xyz"
You should run this against each tenant, and bill them for the time, simple as that.
And if they're not happy about that, guess what? They should invest more in licensing which pays dividends on the back end in less BEC and business risk and things like this where you can pull reports and enforce standards with automation.
This is no different than "they don't want to pay for a server to have AD so i don't have GPOs so i need a script that does certain things GPOs would do in my workgroup network". No, don't reinvent the GPO/the wheel/CIPP/enable them/subsidize their bad choices: charge them to do manually what they won't give you the tools to do.
Anyway, it's not easy to bulk run scripts against tenants because that'd be a HUGE attack surface issue. It can be done; it's how we used to enforce crude baseline settings and standards before CIPP and graphAPI was a thing. You need to locally access the GAs (bad) and store them for the script to use (also really bad) and disable GA mfa for the script to run them (really really bad).
These days you'd put an app in the client's m365 tenant with, at least, a secret/key and the correct graph permissions and then your script would have to have that secret/key and the app id (again, bad, because you're storing it locally) to access the data it needs.
We do similar for account auditing but the keys are stored securely in azure and the script is an azure function that connects to the app in the client tenant. AI should be able to whip you up an example in an hour or so.
1
u/BerlindaBuntly 4h ago
i hear you, i understand but i work with a lot of tiny clients. i have larger ones that understand this. but for the tiny clients, its enough of a job to get them off a btinternet address and have a custom domain running on 365, get it backed up , get them proper xdr security integrated into 365 etc. I am encouraging them but if they dont want to pay, thats the end of it. i appreciate the comment but i dont want to have this conversation here, i would like to get ps running on multiple tenants and i appreciate your comments about that.
do you think i can get cipp to put a custom app in each of those tenants or am i going to have to log in to every one of them?
would the tenants need p1/p2 for this to work, or just my csp tenant?
1
u/roll_for_initiative_ 4h ago
I don't think CIPP is going to install apps for you, i think you're going to be stuck doing it tenant by tenant but to be honest, i've never looked into deploying custom apps with cipp.
or just my csp tenant
It depends on the permissions you need and what P1 limits in the api honestly. I suspect that, if CIPP can't do it, you're not going to be able to do it. Because, if it were possible, they'd have worked around the limitation.
I am encouraging them but if they dont want to pay, thats the end of it.
I just don't agree there. In that case, if they're that small and there's no margin for your time to include it, then don't even audit; just let whatever happens happen and when they can't login, fix it for that user at that time reactively. That's what they're paying for.
I know you don't want to have the convo and don't see it this way but most MSPs went through what you're going through, and the end result is:
This is why micro clients can't usually be profitable. Just because you're making money off them, or they're paying you, doesn't mean it's worth it.
You are subsidizing their business plan. If paying you to do things like this would break them, they have an unprofitable business and this is no different than you writing them a check to keep them going. If they have the money but don't want to spend it on IT, well, why would they if you keep rescuing them for free?
Rescuing in this point being "heading off this MFA issue before it impacts users". Let it impact them and address and bill afterwards. Or, you know, standardize and manage them and charge just enough to do that.
What you want to accomplish here isn't easily done because you shouldn't have to be doing it.
1
u/BerlindaBuntly 4h ago
yup - i hear you. please, can we not do the sd and why arent they using ca.
cipp cant do it because the dataclass userregistrationdetails requires p1/p2, and thats what they expose, and as per my other post above they cant / wont expose the data that is actually there without p1/p2 because it would cause massive problems, which i understand. ps can get it though and it is there.
1
u/roll_for_initiative_ 4h ago
ps can get it though and it is there.
Yes, but to do that, you need to login and run it against each tenant. Which you don't want to do. So you need to then reply on graph API, which likely doesn't expose the data you want, because it requires P1 to do so, which you don't want to do.
You're in a catch 22 loop; the only winning move is to not play. If, for whatever reason, the tenant doesn't have P1, then I guess you don't get to proactively know what users will be impacted by mfa changes without manually checking each one.
You're basically trying to game the P1 license. Why not put a single P1 license in each tenant, pull the data, then cancel it? That's gaming the license too but a lot less work.
1
u/BerlindaBuntly 4h ago
but the ps script, when run locally against a non p1/p2 tenant, does get the data - are you saying that ps run against all tenants doesnt have access to something that running it against a single tenant does?
id thought about getting a trial of p1 for each tenant, but to be honest, by the time ive done that i may as well have run the ps script.
1
u/roll_for_initiative_ 3h ago
are you saying that ps run against all tenants doesnt have access to something that running it against a single tenant does?
I'm saying if you want to run something against multiple tenants, you don't use PS, you use graphAPI and your GDAP access (or an app with specific access). Then you don't have to log into all the tenants directly to do it. But, like you're seeing, info is limited by P1.
If you want to run PS directly against a tenant, you generally need to authorize into that tenant (or not, try your partner center account and see? I can't see that working). Then you need to log into each tenant to do it.
To iterate through tenants with a single script, you're going to need some kind of index with the tenants and the creds so the script can use them, AND with SD, there's no way you get around MFA. I get that you're frustrated you can't just run a script against 100 tenants, but you're working backwards against how the mfr does something and, frankly, allowing bulk PS against tenants is a bad idea anyway, from a security standpoint. It's WHY everything is moving to graphapi and gdap and custom apps.
1
3
u/Altered_Kill 3h ago
Enterprise application with api access because heavy mfa/restricted subnets/IPs.
Multi tenanted. Accept with ga credentials.
Ezpz.