From 7f1339631e3f02699367579ddb9774719006e70a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= <31723128+kris6673@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:17:42 +0200 Subject: [PATCH 01/32] Merge pull request #58 from KelvinTegelaar/dev [pull] dev from KelvinTegelaar:dev From 5b98c82fbd2bca16924057b04ada750ef3a56532 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:40:37 +0200 Subject: [PATCH 02/32] Merge pull request #861 from Ren-Roros-Digital/editorconfig Create .editorconfig --- .editorconfig | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000000..b0c2c50851723 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +insert_final_newline = true + +[*.{ps1, psd1, psm1}] +indent_size = 4 +end_of_line = crlf +trim_trailing_whitespace = true + +[*.json] +indent_size = 2 +end_of_line = crlf +trim_trailing_whitespace = true + +[*.{md, txt}] +end_of_line = crlf +max_line_length = off +trim_trailing_whitespace = false From 38d45dc2d999af1916fa0511ce9f14001cb5051b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= <31723128+kris6673@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:38:51 +0200 Subject: [PATCH 03/32] Merge branch 'KelvinTegelaar:dev' into dev From a253d76c46b748b4af969751f6daef102eef4959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 10 Jun 2024 17:44:30 +0200 Subject: [PATCH 04/32] Apparently there is 2 of these --- Cache_SAMSetup/SAMManifest.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cache_SAMSetup/SAMManifest.json b/Cache_SAMSetup/SAMManifest.json index 82bab306ef89f..b6b291da57b4a 100644 --- a/Cache_SAMSetup/SAMManifest.json +++ b/Cache_SAMSetup/SAMManifest.json @@ -157,7 +157,9 @@ { "id": "885f682f-a990-4bad-a642-36736a74b0c7", "type": "Scope" }, { "id": "913b9306-0ce1-42b8-9137-6a7df690a760", "type": "Role" }, { "id": "cb8f45a0-5c2e-4ea1-b803-84b870a7d7ec", "type": "Scope" }, - { "id": "4c06a06a-098a-4063-868e-5dfee3827264", "type": "Scope" } + { "id": "4c06a06a-098a-4063-868e-5dfee3827264", "type": "Scope" }, + { "id": "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9", "type": "Role" }, + { "id": "e67e6727-c080-415e-b521-e3f35d5248e9", "type": "Scope" } ] }, { From e8f7a55cd26ee4870db0c1b870f7639caa6056a9 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 10 Jun 2024 23:17:01 +0200 Subject: [PATCH 05/32] Merge pull request #885 from kris6673/dev Add roles to second SAMManifest file From 64f4965b5bc6c116d8761f4f39ac2571e87d4ecc Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 10 Jun 2024 19:02:16 -0400 Subject: [PATCH 06/32] Custom Roles: Fix blocked tenants --- Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 | 4 +--- .../HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 index 1336247272bb7..c10a38349d7bf 100644 --- a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 @@ -47,7 +47,6 @@ function Test-CIPPAccess { $Permission.AllowedTenants | Where-Object { $Permission.BlockedTenants -notcontains $_ } } } - Write-Information ($LimitedTenantList | ConvertTo-Json) return $LimitedTenantList } @@ -77,11 +76,10 @@ function Test-CIPPAccess { } else { $Tenant = ($Tenants | Where-Object { $Request.Query.TenantFilter -eq $_.customerId -or $Request.Body.TenantFilter -eq $_.customerId -or $Request.Query.TenantFilter -eq $_.defaultDomainName -or $Request.Body.TenantFilter -eq $_.defaultDomainName }).customerId if ($Role.AllowedTenants -contains 'AllTenants') { - $AllowedTenants = $Tenants + $AllowedTenants = $Tenants.customerId } else { $AllowedTenants = $Role.AllowedTenants } - if ($Tenant) { $TenantAllowed = $AllowedTenants -contains $Tenant -and $Role.BlockedTenants -notcontains $Tenant if (!$TenantAllowed) { continue } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 index dafe35d226f8b..a1e92d2c3f893 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 @@ -43,12 +43,12 @@ function Invoke-ExecCustomRole { if ($Role.AllowedTenants) { $Role.AllowedTenants = @($Role.AllowedTenants | ConvertFrom-Json) } else { - $Role | Add-Member -NotePropertyName AllowedTenants -NotePropertyValue @() + $Role | Add-Member -NotePropertyName AllowedTenants -NotePropertyValue @() -Force } if ($Role.BlockedTenants) { $Role.BlockedTenants = @($Role.BlockedTenants | ConvertFrom-Json) } else { - $Role | Add-Member -NotePropertyName BlockedTenants -NotePropertyValue @() + $Role | Add-Member -NotePropertyName BlockedTenants -NotePropertyValue @() -Force } $Role } From 5056fcf2a533a8123f11c8d103ab0b0c36368f08 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 12 Jun 2024 20:57:30 +0200 Subject: [PATCH 07/32] per user MFA --- .../CIPPCore/Public/Set-CIPPPerUserMFA.ps1 | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 diff --git a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 new file mode 100644 index 0000000000000..74babb8fa8608 --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 @@ -0,0 +1,19 @@ +function Set-CIPPPerUserMFA { + [CmdletBinding()] + param( + $TenantFilter, + $userId, + [ValidateSet('enabled', 'disabled', 'enforced')] + $State = 'users', + $executingUser + ) + try { + $state = @{ 'perUserMfaState' = "$state" } | ConvertTo-Json + New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/users/$userId/authentication/requirements" -tenantid $tenantfilter -type PUT -body $state -ContentType $ContentType + "Successfully set Per user MFA State for $id" + Write-LogMessage -user $executingUser -API 'Set-CIPPPerUserMFA' -message "Successfully set Per user MFA State for $id" -Sev 'Info' -tenant $TenantFilter + } catch { + "Failed to set MFA State for $id : $_" + Write-LogMessage -user $executingUser -API 'Set-CIPPPerUserMFA' -message "Failed to set MFA State for $id : $_" -Sev 'Error' -tenant $TenantFilter + } +} \ No newline at end of file From 8d3b66b3518e6679e18ff8171bafb99b4c1cfd0f Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 12 Jun 2024 20:59:30 +0200 Subject: [PATCH 08/32] fix per user patch --- Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 index 74babb8fa8608..e044fe6797a3c 100644 --- a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 @@ -9,7 +9,7 @@ function Set-CIPPPerUserMFA { ) try { $state = @{ 'perUserMfaState' = "$state" } | ConvertTo-Json - New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/users/$userId/authentication/requirements" -tenantid $tenantfilter -type PUT -body $state -ContentType $ContentType + New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/users/$userId/authentication/requirements" -tenantid $tenantfilter -type PATCH -body $state "Successfully set Per user MFA State for $id" Write-LogMessage -user $executingUser -API 'Set-CIPPPerUserMFA' -message "Successfully set Per user MFA State for $id" -Sev 'Info' -tenant $TenantFilter } catch { From 62ee4fb81d2ee62c2e3d720012ee84156034f906 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 12 Jun 2024 15:43:02 -0400 Subject: [PATCH 09/32] Schema tweaks --- .../Public/Get-CIPPSchemaExtensions.ps1 | 11 ++++++++--- .../GraphHelper/New-GraphGetRequest.ps1 | 4 +++- .../Public/Set-CIPPUserSchemaProperties.ps1 | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 Modules/CIPPCore/Public/Set-CIPPUserSchemaProperties.ps1 diff --git a/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 b/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 index 26794daffea70..b85edb06af862 100644 --- a/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 @@ -30,6 +30,10 @@ function Get-CIPPSchemaExtensions { name = 'autoExpandingArchiveEnabled' type = 'Boolean' } + @{ + name = 'perUserMfaState' + type = 'String' + } ) } ) @@ -40,8 +44,8 @@ function Get-CIPPSchemaExtensions { $SchemaFound = $true $Schema = $Schemas | Where-Object { $_.id -match $SchemaDefinition.id } $Patch = @{} - if (Compare-Object -ReferenceObject ($SchemaDefinition.properties | Select-Object name, type) -DifferenceObject $Schema.properties) { - $Patch.properties = $Properties + if (Compare-Object -ReferenceObject ($SchemaDefinition.properties | Select-Object name, type) -DifferenceObject ($Schema.properties | Select-Object name, type)) { + $Patch.properties = $SchemaDefinitions.Properties } if ($Schema.status -ne 'Available') { $Patch.status = 'Available' @@ -49,9 +53,10 @@ function Get-CIPPSchemaExtensions { if ($Schema.targetTypes -ne $SchemaDefinition.targetTypes) { $Patch.targetTypes = $SchemaDefinition.targetTypes } - if ($Patch.Keys.Count -gt 0) { + if ($Patch -and $Patch.Keys.Count -gt 0) { Write-Information "Updating $($Schema.id)" $Json = ConvertTo-Json -Depth 5 -InputObject $Patch + Write-Information $Json New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/v1.0/schemaExtensions/$($Schema.id)" -Body $Json -AsApp $true -NoAuthCheck $true } else { $Schema diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 index 28e88e204d79d..65a5edca23f2f 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 @@ -52,7 +52,9 @@ function New-GraphGetRequest { if ($noPagination) { $nextURL = $null } else { $nextURL = $data.'@odata.nextLink' } } } catch { - $Message = ($_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue).error.message + try { + $Message = ($_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue).error.message + } catch { $Message = $null } if ($Message -eq $null) { $Message = $($_.Exception.Message) } if ($Message -ne 'Request not applicable to target tenant.' -and $Tenant) { $Tenant.LastGraphError = $Message diff --git a/Modules/CIPPCore/Public/Set-CIPPUserSchemaProperties.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserSchemaProperties.ps1 new file mode 100644 index 0000000000000..0c6f1960ad0d7 --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPUserSchemaProperties.ps1 @@ -0,0 +1,19 @@ +function Set-CIPPUserSchemaProperties { + [CmdletBinding(SupportsShouldProcess = $true)] + Param( + [string]$TenantFilter, + [string]$UserId, + [hashtable]$Properties + ) + + $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } + + $Body = [PSCustomObject]@{ + "$($Schema.id)" = $Properties + } + + $Json = ConvertTo-Json -Depth 5 -InputObject $Body + if ($PSCmdlet.ShouldProcess("User: $UserId", "Set Schema Properties to $($Properties|ConvertTo-Json -Compress)")) { + New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/beta/users/$UserId" -Body $Json -tenantid $TenantFilter + } +} \ No newline at end of file From cd8b2929fcc7e9047d15d42c49ca298cb609c01a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 12 Jun 2024 15:43:16 -0400 Subject: [PATCH 10/32] Fix casing on sam wizard --- .../CIPP/Setup/Invoke-ExecSAMSetup.ps1 | 73 ++++++++++--------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 index 3818794ddbb0d..9f38b50965a00 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSAMSetup.ps1 @@ -11,7 +11,7 @@ Function Invoke-ExecSAMSetup { param($Request, $TriggerMetadata) $UserCreds = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($request.headers.'x-ms-client-principal')) | ConvertFrom-Json) - if ($Request.query.error) { + if ($Request.Query.error) { Add-Type -AssemblyName System.Web Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ ContentType = 'text/html' @@ -61,25 +61,25 @@ Function Invoke-ExecSAMSetup { $Rows = Get-CIPPAzDataTableEntity @Table | Where-Object -Property Timestamp -GT (Get-Date).AddMinutes(-10) try { - if ($Request.query.count -lt 1 ) { $Results = 'No authentication code found. Please go back to the wizard.' } + if ($Request.Query.count -lt 1 ) { $Results = 'No authentication code found. Please go back to the wizard.' } - if ($request.body.setkeys) { + if ($Request.Body.setkeys) { if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') { - if ($request.body.TenantId) { $Secret.TenantId = $Request.body.tenantid } - if ($request.body.RefreshToken) { $Secret.RefreshToken = $Request.body.RefreshToken } - if ($request.body.applicationid) { $Secret.ApplicationId = $Request.body.ApplicationId } - if ($request.body.ApplicationSecret) { $Secret.ApplicationSecret = $Request.body.ApplicationSecret } + if ($Request.Body.TenantId) { $Secret.TenantId = $Request.Body.tenantid } + if ($Request.Body.RefreshToken) { $Secret.RefreshToken = $Request.Body.RefreshToken } + if ($Request.Body.applicationid) { $Secret.ApplicationId = $Request.Body.ApplicationId } + if ($Request.Body.ApplicationSecret) { $Secret.ApplicationSecret = $Request.Body.ApplicationSecret } Add-CIPPAzDataTableEntity @DevSecretsTable -Entity $Secret -Force } else { - if ($request.body.tenantid) { Set-AzKeyVaultSecret -VaultName $kv -Name 'tenantid' -SecretValue (ConvertTo-SecureString -String $request.body.tenantid -AsPlainText -Force) } - if ($request.body.RefreshToken) { Set-AzKeyVaultSecret -VaultName $kv -Name 'RefreshToken' -SecretValue (ConvertTo-SecureString -String $request.body.RefreshToken -AsPlainText -Force) } - if ($request.body.applicationid) { Set-AzKeyVaultSecret -VaultName $kv -Name 'applicationid' -SecretValue (ConvertTo-SecureString -String $request.body.applicationid -AsPlainText -Force) } - if ($request.body.applicationsecret) { Set-AzKeyVaultSecret -VaultName $kv -Name 'applicationsecret' -SecretValue (ConvertTo-SecureString -String $request.body.applicationsecret -AsPlainText -Force) } + if ($Request.Body.tenantid) { Set-AzKeyVaultSecret -VaultName $kv -Name 'tenantid' -SecretValue (ConvertTo-SecureString -String $Request.Body.tenantid -AsPlainText -Force) } + if ($Request.Body.RefreshToken) { Set-AzKeyVaultSecret -VaultName $kv -Name 'RefreshToken' -SecretValue (ConvertTo-SecureString -String $Request.Body.RefreshToken -AsPlainText -Force) } + if ($Request.Body.applicationid) { Set-AzKeyVaultSecret -VaultName $kv -Name 'applicationid' -SecretValue (ConvertTo-SecureString -String $Request.Body.applicationid -AsPlainText -Force) } + if ($Request.Body.applicationsecret) { Set-AzKeyVaultSecret -VaultName $kv -Name 'applicationsecret' -SecretValue (ConvertTo-SecureString -String $Request.Body.applicationsecret -AsPlainText -Force) } } $Results = @{ Results = 'The keys have been replaced. Please perform a permissions check.' } } - if ($Request.query.error -eq 'invalid_client') { $Results = 'Client ID was not found in Azure. Try waiting 10 seconds to try again, if you have gotten this error after 5 minutes, please restart the process.' } - if ($request.query.code) { + if ($Request.Query.error -eq 'invalid_client') { $Results = 'Client ID was not found in Azure. Try waiting 10 seconds to try again, if you have gotten this error after 5 minutes, please restart the process.' } + if ($Request.Query.code) { try { $TenantId = $Rows.tenantid if (!$TenantId) { $TenantId = $ENV:TenantId } @@ -89,11 +89,11 @@ Function Invoke-ExecSAMSetup { if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') { $clientsecret = $Secret.ApplicationSecret } else { - $clientsecret = Get-AzKeyVaultSecret -VaultName $kv -Name 'applicationsecret' -AsPlainText + $clientsecret = Get-AzKeyVaultSecret -VaultName $kv -Name 'ApplicationSecret' -AsPlainText } if (!$clientsecret) { $clientsecret = $ENV:ApplicationSecret } - Write-Host "client_id=$appid&scope=https://graph.microsoft.com/.default+offline_access+openid+profile&code=$($request.query.code)&grant_type=authorization_code&redirect_uri=$($url)&client_secret=$clientsecret" -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" - $RefreshToken = Invoke-RestMethod -Method POST -Body "client_id=$appid&scope=https://graph.microsoft.com/.default+offline_access+openid+profile&code=$($request.query.code)&grant_type=authorization_code&redirect_uri=$($url)&client_secret=$clientsecret" -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" + Write-Host "client_id=$appid&scope=https://graph.microsoft.com/.default+offline_access+openid+profile&code=$($Request.Query.code)&grant_type=authorization_code&redirect_uri=$($url)&client_secret=$clientsecret" -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" + $RefreshToken = Invoke-RestMethod -Method POST -Body "client_id=$appid&scope=https://graph.microsoft.com/.default+offline_access+openid+profile&code=$($Request.Query.code)&grant_type=authorization_code&redirect_uri=$($url)&client_secret=$clientsecret" -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" -ContentType 'application/x-www-form-urlencoded' if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') { $Secret.RefreshToken = $RefreshToken.refresh_token @@ -113,7 +113,7 @@ Function Invoke-ExecSAMSetup { $Results = "Authentication failed. $($_.Exception.message)" } } - if ($request.query.CreateSAM) { + if ($Request.Query.CreateSAM) { $Rows = @{ RowKey = 'setup' PartitionKey = 'setup' @@ -126,7 +126,7 @@ Function Invoke-ExecSAMSetup { Add-CIPPAzDataTableEntity @Table -Entity $Rows -Force | Out-Null $Rows = Get-CIPPAzDataTableEntity @Table | Where-Object -Property Timestamp -GT (Get-Date).AddMinutes(-10) - if ($Request.query.partnersetup) { + if ($Request.Query.partnersetup) { $SetupPhase = $Rows.partnersetup = $true Add-CIPPAzDataTableEntity @Table -Entity $Rows -Force | Out-Null } @@ -136,46 +136,46 @@ Function Invoke-ExecSAMSetup { Add-CIPPAzDataTableEntity @Table -Entity $Rows -Force | Out-Null $Results = @{ message = "Your code is $($DeviceLogon.user_code). Enter the code" ; step = $step; url = $DeviceLogon.verification_uri } } - if ($Request.query.CheckSetupProcess -and $request.query.step -eq 1) { + if ($Request.Query.CheckSetupProcess -and $Request.Query.step -eq 1) { $SAMSetup = $Rows.SamSetup | ConvertFrom-Json -ErrorAction SilentlyContinue $Token = (New-DeviceLogin -clientid '1b730954-1685-4b74-9bfd-dac224a7b894' -Scope 'https://graph.microsoft.com/.default' -device_code $SAMSetup.device_code) - if ($token.Access_Token) { + if ($Token.access_token) { $step = 2 $URL = ($Request.headers.'x-ms-original-url').split('?') | Select-Object -First 1 $PartnerSetup = $Rows.partnersetup - $TenantId = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/organization' -Headers @{ authorization = "Bearer $($Token.Access_Token)" } -Method GET -ContentType 'application/json').value.id + $TenantId = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/organization' -Headers @{ authorization = "Bearer $($Token.access_token)" } -Method GET -ContentType 'application/json').value.id $SetupPhase = $rows.tenantid = [string]($TenantId) Add-CIPPAzDataTableEntity @Table -Entity $Rows -Force | Out-Null if ($PartnerSetup) { $app = Get-Content '.\Cache_SAMSetup\SAMManifest.json' | ConvertFrom-Json $App.web.redirectUris = @($App.web.redirectUris + $URL) $app = $app | ConvertTo-Json -Depth 15 - $AppId = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/applications' -Headers @{ authorization = "Bearer $($Token.Access_Token)" } -Method POST -Body $app -ContentType 'application/json') + $AppId = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/applications' -Headers @{ authorization = "Bearer $($Token.access_token)" } -Method POST -Body $app -ContentType 'application/json') $rows.appid = [string]($AppId.appId) Add-CIPPAzDataTableEntity @Table -Entity $Rows -Force | Out-Null $attempt = 0 do { try { try { - $SPNDefender = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/servicePrincipals' -Headers @{ authorization = "Bearer $($Token.Access_Token)" } -Method POST -Body "{ `"appId`": `"fc780465-2017-40d4-a0c5-307022471b92`" }" -ContentType 'application/json') + $SPNDefender = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/servicePrincipals' -Headers @{ authorization = "Bearer $($Token.access_token)" } -Method POST -Body "{ `"appId`": `"fc780465-2017-40d4-a0c5-307022471b92`" }" -ContentType 'application/json') } catch { Write-Host "didn't deploy spn for defender, probably already there." } try { - $SPNTeams = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/servicePrincipals' -Headers @{ authorization = "Bearer $($Token.Access_Token)" } -Method POST -Body "{ `"appId`": `"48ac35b8-9aa8-4d74-927d-1f4a14a0b239`" }" -ContentType 'application/json') + $SPNTeams = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/servicePrincipals' -Headers @{ authorization = "Bearer $($Token.access_token)" } -Method POST -Body "{ `"appId`": `"48ac35b8-9aa8-4d74-927d-1f4a14a0b239`" }" -ContentType 'application/json') } catch { Write-Host "didn't deploy spn for Teams, probably already there." } try { - $SPNPartnerCenter = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/servicePrincipals' -Headers @{ authorization = "Bearer $($Token.Access_Token)" } -Method POST -Body "{ `"appId`": `"fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd`" }" -ContentType 'application/json') + $SPNPartnerCenter = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/servicePrincipals' -Headers @{ authorization = "Bearer $($Token.access_token)" } -Method POST -Body "{ `"appId`": `"fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd`" }" -ContentType 'application/json') } catch { Write-Host "didn't deploy spn for PartnerCenter, probably already there." } - $SPN = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/servicePrincipals' -Headers @{ authorization = "Bearer $($Token.Access_Token)" } -Method POST -Body "{ `"appId`": `"$($AppId.appId)`" }" -ContentType 'application/json') + $SPN = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/servicePrincipals' -Headers @{ authorization = "Bearer $($Token.access_token)" } -Method POST -Body "{ `"appId`": `"$($AppId.appId)`" }" -ContentType 'application/json') Start-Sleep 3 - $GroupID = (Invoke-RestMethod "https://graph.microsoft.com/v1.0/groups?`$filter=startswith(displayName,'AdminAgents')" -Headers @{ authorization = "Bearer $($Token.Access_Token)" } -Method Get -ContentType 'application/json').value.id + $GroupID = (Invoke-RestMethod "https://graph.microsoft.com/v1.0/groups?`$filter=startswith(displayName,'AdminAgents')" -Headers @{ authorization = "Bearer $($Token.access_token)" } -Method Get -ContentType 'application/json').value.id Write-Host "Id is $GroupID" - $AddingToAdminAgent = (Invoke-RestMethod "https://graph.microsoft.com/v1.0/groups/$($GroupID)/members/`$ref" -Headers @{ authorization = "Bearer $($Token.Access_Token)" } -Method POST -Body "{ `"@odata.id`": `"https://graph.microsoft.com/v1.0/directoryObjects/$($SPN.id)`"}" -ContentType 'application/json') + $AddingToAdminAgent = (Invoke-RestMethod "https://graph.microsoft.com/v1.0/groups/$($GroupID)/members/`$ref" -Headers @{ authorization = "Bearer $($Token.access_token)" } -Method POST -Body "{ `"@odata.id`": `"https://graph.microsoft.com/v1.0/directoryObjects/$($SPN.id)`"}" -ContentType 'application/json') Write-Host 'Added to adminagents' $attempt ++ } catch { @@ -184,21 +184,22 @@ Function Invoke-ExecSAMSetup { } until ($attempt -gt 5) } else { $app = Get-Content '.\Cache_SAMSetup\SAMManifestNoPartner.json' - $AppId = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/applications' -Headers @{ authorization = "Bearer $($Token.Access_Token)" } -Method POST -Body $app -ContentType 'application/json') - $rows.appid = [string]($AppId.appId) + $AppId = (Invoke-RestMethod 'https://graph.microsoft.com/v1.0/applications' -Headers @{ authorization = "Bearer $($Token.access_token)" } -Method POST -Body $app -ContentType 'application/json') + $Rows.appid = [string]($AppId.appId) Add-CIPPAzDataTableEntity @Table -Entity $Rows -Force | Out-Null } - $AppPassword = (Invoke-RestMethod "https://graph.microsoft.com/v1.0/applications/$($AppID.id)/addPassword" -Headers @{ authorization = "Bearer $($Token.Access_Token)" } -Method POST -Body '{"passwordCredential":{"displayName":"CIPPInstall"}}' -ContentType 'application/json').secretText + $AppPassword = (Invoke-RestMethod "https://graph.microsoft.com/v1.0/applications/$($AppId.id)/addPassword" -Headers @{ authorization = "Bearer $($Token.access_token)" } -Method POST -Body '{"passwordCredential":{"displayName":"CIPPInstall"}}' -ContentType 'application/json').secretText if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') { - $Secret.TenantId = $Request.body.tenantid - $Secret.ApplicationId = $Request.body.ApplicationId - $Secret.ApplicationSecret = $Request.body.ApplicationSecret + $Secret.TenantId = $TenantId + $Secret.ApplicationId = $AppId.appId + $Secret.ApplicationSecret = $AppPassword Add-CIPPAzDataTableEntity @DevSecretsTable -Entity $Secret -Force + Write-Information ($Secret | ConvertTo-Json -Depth 5) } else { Set-AzKeyVaultSecret -VaultName $kv -Name 'tenantid' -SecretValue (ConvertTo-SecureString -String $TenantId -AsPlainText -Force) - Set-AzKeyVaultSecret -VaultName $kv -Name 'applicationid' -SecretValue (ConvertTo-SecureString -String $Appid.appid -AsPlainText -Force) + Set-AzKeyVaultSecret -VaultName $kv -Name 'applicationid' -SecretValue (ConvertTo-SecureString -String $Appid.appId -AsPlainText -Force) Set-AzKeyVaultSecret -VaultName $kv -Name 'applicationsecret' -SecretValue (ConvertTo-SecureString -String $AppPassword -AsPlainText -Force) } $Results = @{'message' = 'Created application. Waiting 30 seconds for Azure propagation'; step = $step } @@ -208,7 +209,7 @@ Function Invoke-ExecSAMSetup { } } - switch ($request.query.step) { + switch ($Request.Query.step) { 2 { $step = 2 $TenantId = $Rows.tenantid From 7d61557ced6385d885b646be5fb2227fb2323766 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 13 Jun 2024 00:04:13 +0200 Subject: [PATCH 11/32] Get per user MFA --- Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 diff --git a/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 new file mode 100644 index 0000000000000..86acfc5010827 --- /dev/null +++ b/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 @@ -0,0 +1,17 @@ +function Get-CIPPPerUserMFA { + [CmdletBinding()] + param( + $TenantFilter, + $userId, + $executingUser + ) + try { + $MFAState = New-graphGetRequest -Uri "https://graph.microsoft.com/beta/users/$($userId)/authentication/requirements" -tenantid $tenantfilter + return [PSCustomObject]@{ + user = $userId + PerUserMFA = $MFAState.perUserMfaState + } + } catch { + "Failed to get MFA State for $id : $_" + } +} \ No newline at end of file From cda510e736803575fe2f7ba26fc4a6185d303298 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 13 Jun 2024 01:36:30 +0200 Subject: [PATCH 12/32] bulk request support --- .../GraphHelper/New-GraphBulkRequest.ps1 | 3 +- .../CIPPCore/Public/Set-CIPPPerUserMFA.ps1 | 20 ++++++++--- .../Invoke-CIPPStandardPerUserMFA.ps1 | 33 +++++++++++++++++++ 3 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 index ebe6af9675b92..f37be378e39de 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 @@ -33,7 +33,8 @@ function New-GraphBulkRequest { $req = @{} # Use select to create hashtables of id, method and url for each call $req['requests'] = ($Requests[$i..($i + 19)]) - Invoke-RestMethod -Uri $URL -Method POST -Headers $headers -ContentType 'application/json; charset=utf-8' -Body ($req | ConvertTo-Json -Depth 10) + $ReqBody = ($req | ConvertTo-Json -Depth 10) + Invoke-RestMethod -Uri $URL -Method POST -Headers $headers -ContentType 'application/json; charset=utf-8' -Body $ReqBody } foreach ($MoreData in $ReturnedData.Responses | Where-Object { $_.body.'@odata.nextLink' }) { diff --git a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 index e044fe6797a3c..74c3ecbb5d669 100644 --- a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 @@ -4,13 +4,25 @@ function Set-CIPPPerUserMFA { $TenantFilter, $userId, [ValidateSet('enabled', 'disabled', 'enforced')] - $State = 'users', + $State = 'enabled', $executingUser ) try { - $state = @{ 'perUserMfaState' = "$state" } | ConvertTo-Json - New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/users/$userId/authentication/requirements" -tenantid $tenantfilter -type PATCH -body $state - "Successfully set Per user MFA State for $id" + $int = 0 + $Requests = foreach ($id in $userId) { + @{ + id = $int++ + method = 'PATCH' + url = "users/$id/authentication/requirements" + body = @{ 'perUserMfaState' = "$state" } + 'headers' = @{ + 'Content-Type' = 'application/json' + } + } + } + #Split the requests by batches of 20, we can have anywhere from 1 to 1000 batches and execute New-GraphBulkRequest -tenantid $tenantfilter -scope 'https://graph.microsoft.com/.default' -Requests $batch + $Requests = New-GraphBulkRequest -tenantid $tenantfilter -scope 'https://graph.microsoft.com/.default' -Requests $Requests -asapp $true + "Successfully set Per user MFA State for $userId" Write-LogMessage -user $executingUser -API 'Set-CIPPPerUserMFA' -message "Successfully set Per user MFA State for $id" -Sev 'Info' -tenant $TenantFilter } catch { "Failed to set MFA State for $id : $_" diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 new file mode 100644 index 0000000000000..c651e9171a288 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 @@ -0,0 +1,33 @@ +function Invoke-CIPPStandardPerUserMFA { + <# + .FUNCTIONALITY + Internal + #> + param($Tenant, $Settings) + + $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$top=999&`$select=UserPrincipalName,accountEnabled" -scope 'https://graph.microsoft.com/.default' -tenantid $Tenant | Where-Object { $_.AccountEnabled -EQ $true } + + If ($Settings.remediate -eq $true) { + if ($GraphRequest) { + try { + Set-CIPPPeruserMFA -TenantFilter $Tenant -UserId $GraphRequest.UserPrincipalName -State 'Enforced' + Write-LogMessage -API 'Standards' -tenant $tenant -message '' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable guest $($guest.UserPrincipalName) ($($guest.id)): $ErrorMessage" -sev Error + } + } + } + if ($Settings.alert -eq $true) { + + if ($GraphRequest) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Guests accounts with a login longer than 90 days ago: $($GraphRequest.count)" -sev Alert + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'No guests accounts with a login longer than 90 days ago.' -sev Info + } + } + if ($Settings.report -eq $true) { + $filtered = $GraphRequest | Select-Object -Property UserPrincipalName, id, signInActivity, mail, userType, accountEnabled + Add-CIPPBPAField -FieldName 'DisableGuests' -FieldValue $filtered -StoreAs json -Tenant $tenant + } +} From 2f97db6ed4530f18f090d5994edf131141dbfb75 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 13 Jun 2024 01:38:16 +0200 Subject: [PATCH 13/32] batching single item fix --- Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 index 74c3ecbb5d669..9675b104bc829 100644 --- a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 @@ -21,7 +21,7 @@ function Set-CIPPPerUserMFA { } } #Split the requests by batches of 20, we can have anywhere from 1 to 1000 batches and execute New-GraphBulkRequest -tenantid $tenantfilter -scope 'https://graph.microsoft.com/.default' -Requests $batch - $Requests = New-GraphBulkRequest -tenantid $tenantfilter -scope 'https://graph.microsoft.com/.default' -Requests $Requests -asapp $true + $Requests = New-GraphBulkRequest -tenantid $tenantfilter -scope 'https://graph.microsoft.com/.default' -Requests @($Requests) -asapp $true "Successfully set Per user MFA State for $userId" Write-LogMessage -user $executingUser -API 'Set-CIPPPerUserMFA' -message "Successfully set Per user MFA State for $id" -Sev 'Info' -tenant $TenantFilter } catch { From 0914e2f161531f023c59831d17f660f337ff858b Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 13 Jun 2024 02:20:40 +0200 Subject: [PATCH 14/32] add more bulk features --- Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 | 1 - .../Invoke-CIPPStandardPerUserMFA.ps1 | 18 +++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 index 9675b104bc829..eedab329061c7 100644 --- a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 @@ -20,7 +20,6 @@ function Set-CIPPPerUserMFA { } } } - #Split the requests by batches of 20, we can have anywhere from 1 to 1000 batches and execute New-GraphBulkRequest -tenantid $tenantfilter -scope 'https://graph.microsoft.com/.default' -Requests $batch $Requests = New-GraphBulkRequest -tenantid $tenantfilter -scope 'https://graph.microsoft.com/.default' -Requests @($Requests) -asapp $true "Successfully set Per user MFA State for $userId" Write-LogMessage -user $executingUser -API 'Set-CIPPPerUserMFA' -message "Successfully set Per user MFA State for $id" -Sev 'Info' -tenant $TenantFilter diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 index c651e9171a288..16b0c037f2237 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 @@ -6,15 +6,23 @@ function Invoke-CIPPStandardPerUserMFA { param($Tenant, $Settings) $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$top=999&`$select=UserPrincipalName,accountEnabled" -scope 'https://graph.microsoft.com/.default' -tenantid $Tenant | Where-Object { $_.AccountEnabled -EQ $true } - + $int = 0 + $Requests = foreach ($id in $GraphRequest.userPrincipalName) { + @{ + id = $int++ + method = 'GET' + url = "/users/$id/authentication/requirements" + } + } + $UsersWithoutMFA = (New-GraphBulkRequest -tenantid $tenant -scope 'https://graph.microsoft.com/.default' -Requests @($Requests) -asapp $true).body | Where-Object { $_.perUserMfaState -ne 'enforced' } | Select-Object peruserMFAState, @{Name = 'UserPrincipalName'; Expression = { [System.Web.HttpUtility]::UrlDecode($_.'@odata.context'.split("'")[1]) } } If ($Settings.remediate -eq $true) { - if ($GraphRequest) { + if ($UsersWithoutMFA) { try { - Set-CIPPPeruserMFA -TenantFilter $Tenant -UserId $GraphRequest.UserPrincipalName -State 'Enforced' - Write-LogMessage -API 'Standards' -tenant $tenant -message '' -sev Info + $MFAMessage = Set-CIPPPeruserMFA -TenantFilter $Tenant -UserId $GraphRequest.UserPrincipalName -State 'Enforced' + Write-LogMessage -API 'Standards' -tenant $tenant -message $MFAMessage -sev Info } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable guest $($guest.UserPrincipalName) ($($guest.id)): $ErrorMessage" -sev Error + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to enforce MFA for all users: $ErrorMessage" -sev Error } } } From 82a0f19ec177707949c2ae650d7ef9a28f5457b3 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 12 Jun 2024 23:53:01 -0400 Subject: [PATCH 15/32] Per user mfa tweaks --- .../Users/Invoke-ExecPerUserMFA.ps1 | 28 +++++++++++ .../CIPPCore/Public/Set-CIPPPerUserMFA.ps1 | 50 ++++++++++++++++--- .../Public/Set-CIPPUserSchemaProperties.ps1 | 43 +++++++++++++--- 3 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFA.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFA.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFA.ps1 new file mode 100644 index 0000000000000..b52a1595f5139 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecPerUserMFA.ps1 @@ -0,0 +1,28 @@ +function Invoke-ExecPerUserMFA { + <# + .FUNCTIONALITY + Entrypoint + + .ROLE + Identity.User.ReadWrite + #> + Param( + $Request, + $TriggerMetadata + ) + + $Request = @{ + userId = $Request.Body.userId + TenantFilter = $Request.Body.TenantFilter + State = $Request.Body.State + executingUser = $Request.Headers.'x-ms-client-principal' + } + $Result = Set-CIPPPerUserMFA @Request + $Body = @{ + Results = @($Result) + } + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Body + }) +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 index eedab329061c7..5bb406658e370 100644 --- a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 @@ -1,30 +1,68 @@ function Set-CIPPPerUserMFA { + <# + .SYNOPSIS + Change Per-User MFA State for a User + + .DESCRIPTION + Change the Per-User MFA State for a user via the /users/{id}/authentication/requirements endpoint + + .PARAMETER TenantFilter + Tenant where the user resides + + .PARAMETER userId + One or more User IDs to set the MFA state for (GUID or UserPrincipalName) + + .PARAMETER State + State to set the user to (enabled, disabled, enforced) + + .PARAMETER executingUser + User executing the command + + .EXAMPLE + Set-CIPPPerUserMFA -TenantFilter 'contoso.onmicrosoft.com' -userId user@contoso.onmicrosoft.com -State 'disabled' -executingUser 'mspuser@partner.com' + #> [CmdletBinding()] param( - $TenantFilter, - $userId, + [Parameter(Mandatory = $true)] + [string]$TenantFilter, + [Parameter(Mandatory = $true)] + [string[]]$userId, [ValidateSet('enabled', 'disabled', 'enforced')] $State = 'enabled', - $executingUser + [string]$executingUser = 'CIPP' ) try { $int = 0 + $Body = @{ + PerUserState = $State + } $Requests = foreach ($id in $userId) { @{ id = $int++ method = 'PATCH' url = "users/$id/authentication/requirements" - body = @{ 'perUserMfaState' = "$state" } + body = $Body 'headers' = @{ 'Content-Type' = 'application/json' } } } + $Requests = New-GraphBulkRequest -tenantid $tenantfilter -scope 'https://graph.microsoft.com/.default' -Requests @($Requests) -asapp $true "Successfully set Per user MFA State for $userId" - Write-LogMessage -user $executingUser -API 'Set-CIPPPerUserMFA' -message "Successfully set Per user MFA State for $id" -Sev 'Info' -tenant $TenantFilter + + $Users = foreach ($id in $userId) { + @{ + userId = $id + Properties = @{ + perUserMfaState = $State + } + } + } + Set-CIPPUserSchemaProperties -TenantFilter $TenantFilter -Users $Users + Write-LogMessage -user $executingUser -API 'Set-CIPPPerUserMFA' -message "Successfully set Per user MFA State to $State for $id" -Sev 'Info' -tenant $TenantFilter } catch { "Failed to set MFA State for $id : $_" - Write-LogMessage -user $executingUser -API 'Set-CIPPPerUserMFA' -message "Failed to set MFA State for $id : $_" -Sev 'Error' -tenant $TenantFilter + Write-LogMessage -user $executingUser -API 'Set-CIPPPerUserMFA' -message "Failed to set MFA State to $State for $id : $_" -Sev 'Error' -tenant $TenantFilter } } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Set-CIPPUserSchemaProperties.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserSchemaProperties.ps1 index 0c6f1960ad0d7..b006a27069efd 100644 --- a/Modules/CIPPCore/Public/Set-CIPPUserSchemaProperties.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPUserSchemaProperties.ps1 @@ -1,19 +1,46 @@ function Set-CIPPUserSchemaProperties { + <# + .SYNOPSIS + Set Schema Properties for a user + + .DESCRIPTION + Uses scheam extensions to set properties for a user + + .PARAMETER TenantFilter + Tenant for user + + .PARAMETER UserId + One or more user ids to set properties for + + .PARAMETER Properties + Hashtable of properties to set + + #> [CmdletBinding(SupportsShouldProcess = $true)] Param( + [Parameter(Mandatory = $true)] [string]$TenantFilter, - [string]$UserId, - [hashtable]$Properties + [Parameter(Mandatory = $true)] + [object]$Users ) $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } - - $Body = [PSCustomObject]@{ - "$($Schema.id)" = $Properties + $int = 0 + $Requests = foreach ($User in $Users) { + @{ + id = $int++ + method = 'PATCH' + url = "users/$($User.userId)" + body = @{ + "$($Schema.id)" = $User.Properties + } + 'headers' = @{ + 'Content-Type' = 'application/json' + } + } } - $Json = ConvertTo-Json -Depth 5 -InputObject $Body - if ($PSCmdlet.ShouldProcess("User: $UserId", "Set Schema Properties to $($Properties|ConvertTo-Json -Compress)")) { - New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/beta/users/$UserId" -Body $Json -tenantid $TenantFilter + if ($PSCmdlet.ShouldProcess("User: $($Users.userId -join ', ')", 'Set Schema Properties')) { + $Requests = New-GraphBulkRequest -tenantid $tenantfilter -Requests @($Requests) } } \ No newline at end of file From 3f7d424de3600dbe384fc1ccd8e092df7b99bf94 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 13 Jun 2024 12:16:45 +0200 Subject: [PATCH 16/32] changes for CIPP From 425830028e56a7f67845980588ed3ffd289d3d07 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 13 Jun 2024 12:22:04 +0200 Subject: [PATCH 17/32] added per user MFA standard --- .../Standards/Invoke-CIPPStandardPerUserMFA.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 index 16b0c037f2237..9ecd363c44a3f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 @@ -15,10 +15,11 @@ function Invoke-CIPPStandardPerUserMFA { } } $UsersWithoutMFA = (New-GraphBulkRequest -tenantid $tenant -scope 'https://graph.microsoft.com/.default' -Requests @($Requests) -asapp $true).body | Where-Object { $_.perUserMfaState -ne 'enforced' } | Select-Object peruserMFAState, @{Name = 'UserPrincipalName'; Expression = { [System.Web.HttpUtility]::UrlDecode($_.'@odata.context'.split("'")[1]) } } + If ($Settings.remediate -eq $true) { if ($UsersWithoutMFA) { try { - $MFAMessage = Set-CIPPPeruserMFA -TenantFilter $Tenant -UserId $GraphRequest.UserPrincipalName -State 'Enforced' + $MFAMessage = Set-CIPPPeruserMFA -TenantFilter $Tenant -UserId $UsersWithoutMFA.UserPrincipalName -State 'Enforced' Write-LogMessage -API 'Standards' -tenant $tenant -message $MFAMessage -sev Info } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message @@ -28,14 +29,13 @@ function Invoke-CIPPStandardPerUserMFA { } if ($Settings.alert -eq $true) { - if ($GraphRequest) { - Write-LogMessage -API 'Standards' -tenant $tenant -message "Guests accounts with a login longer than 90 days ago: $($GraphRequest.count)" -sev Alert + if ($UsersWithoutMFA) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "The following accounts do not have Legacy MFA Enforced: $($UsersWithoutMFA.UserPrincipalName -join ', ')" -sev Alert } else { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'No guests accounts with a login longer than 90 days ago.' -sev Info + Write-LogMessage -API 'Standards' -tenant $tenant -message 'No accounts do not have legacy per user MFA Enforced' -sev Info } } if ($Settings.report -eq $true) { - $filtered = $GraphRequest | Select-Object -Property UserPrincipalName, id, signInActivity, mail, userType, accountEnabled - Add-CIPPBPAField -FieldName 'DisableGuests' -FieldValue $filtered -StoreAs json -Tenant $tenant + Add-CIPPBPAField -FieldName 'LegacyMFAUsers' -FieldValue $UsersWithoutMFA -StoreAs json -Tenant $tenant } } From aa755aec0c9a101b479fae135470bdf74151e25f Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 13 Jun 2024 12:48:07 +0200 Subject: [PATCH 18/32] Add All Tenants to per user MFA --- .../CIPPCore/Public/Get-CIPPPerUserMFA.ps1 | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 index 86acfc5010827..05e879a834968 100644 --- a/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 @@ -3,13 +3,30 @@ function Get-CIPPPerUserMFA { param( $TenantFilter, $userId, - $executingUser + $executingUser, + $AllUsers = $false ) try { - $MFAState = New-graphGetRequest -Uri "https://graph.microsoft.com/beta/users/$($userId)/authentication/requirements" -tenantid $tenantfilter - return [PSCustomObject]@{ - user = $userId - PerUserMFA = $MFAState.perUserMfaState + if ($AllUsers -eq $true) { + $AllUsers = New-graphGetRequest -Uri "https://graph.microsoft.com/beta/users?`$top=999&`$select=UserPrincipalName,Id" -tenantid $tenantfilter + $Requests = foreach ($id in $AllUsers.userPrincipalName) { + @{ + id = $int++ + method = 'GET' + url = "users/$id/authentication/requirements" + } + } + $Requests = New-GraphBulkRequest -tenantid $tenantfilter -scope 'https://graph.microsoft.com/.default' -Requests @($Requests) -asapp $true + if ($Requests.body) { + $UsersWithoutMFA = $Requests.body | Select-Object peruserMFAState, @{Name = 'UserPrincipalName'; Expression = { [System.Web.HttpUtility]::UrlDecode($_.'@odata.context'.split("'")[1]) } } + return $UsersWithoutMFA + } + } else { + $MFAState = New-graphGetRequest -Uri "https://graph.microsoft.com/beta/users/$($userId)/authentication/requirements" -tenantid $tenantfilter + return [PSCustomObject]@{ + UserPrincipalName = $userId + PerUserMFAState = $MFAState.perUserMfaState + } } } catch { "Failed to get MFA State for $id : $_" From 72412d479b16f71434e76557d019d663f34fdc70 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 13 Jun 2024 13:56:56 +0200 Subject: [PATCH 19/32] updated MFA REport --- Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 | 20 +++++++++---------- .../CIPPCore/Public/Get-CIPPPerUserMFA.ps1 | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 b/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 index 319f9a81e7261..28d526a9944da 100644 --- a/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 @@ -6,18 +6,17 @@ function Get-CIPPMFAState { $APIName = 'Get MFA Status', $ExecutingUser ) - - $users = foreach ($user in (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$select=id,UserPrincipalName,DisplayName,accountEnabled,assignedLicenses' -tenantid $TenantFilter)) { + $PerUserMFAState = Get-CIPPPerUserMFA -TenantFilter $TenantFilter -AllUsers $true + $users = foreach ($user in (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999&$select=id,UserPrincipalName,DisplayName,accountEnabled,assignedLicenses' -tenantid $TenantFilter)) { [PSCustomObject]@{ - UserPrincipalName = $user.UserPrincipalName - isLicensed = [boolean]$user.assignedLicenses.skuid - accountEnabled = $user.accountEnabled - DisplayName = $user.DisplayName - ObjectId = $user.id - StrongAuthenticationRequirements = @{StrongAuthenticationRequirement = @{state = 'See Documentation' } } + UserPrincipalName = $user.UserPrincipalName + isLicensed = [boolean]$user.assignedLicenses.skuid + accountEnabled = $user.accountEnabled + DisplayName = $user.DisplayName + ObjectId = $user.id } } - + $SecureDefaultsState = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -tenantid $TenantFilter ).IsEnabled $CAState = New-Object System.Collections.ArrayList @@ -62,7 +61,6 @@ function Get-CIPPMFAState { Write-Host 'Processing users' $UserCAState = New-Object System.Collections.ArrayList foreach ($CA in $CAState) { - Write-Host 'Looping CAState' if ($CA -like '*All Users*') { if ($ExcludeAllUsers -contains $_.ObjectId) { $UserCAState.Add("Excluded from $($policy.displayName) - All Users") | Out-Null } else { $UserCAState.Add($CA) | Out-Null } @@ -75,7 +73,7 @@ function Get-CIPPMFAState { } } - $PerUser = if ($_.StrongAuthenticationRequirements.StrongAuthenticationRequirement.state -ne $null) { $_.StrongAuthenticationRequirements.StrongAuthenticationRequirement.state } else { 'Disabled' } + $PerUser = if ($PerUserMFAState -eq $null) { $null } else { ($PerUserMFAState | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName).PerUserMFAState } $MFARegUser = if (($MFARegistration | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName).IsMFARegistered -eq $null) { $false } else { ($MFARegistration | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName) } diff --git a/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 index 05e879a834968..5c525962009f9 100644 --- a/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 @@ -24,8 +24,8 @@ function Get-CIPPPerUserMFA { } else { $MFAState = New-graphGetRequest -Uri "https://graph.microsoft.com/beta/users/$($userId)/authentication/requirements" -tenantid $tenantfilter return [PSCustomObject]@{ - UserPrincipalName = $userId PerUserMFAState = $MFAState.perUserMfaState + UserPrincipalName = $userId } } } catch { From 92c253dc357e11acaecde7108083b3d0a25e56c2 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 14 Jun 2024 00:14:15 +0200 Subject: [PATCH 20/32] add instant allow for sam setup --- Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 index c10a38349d7bf..acdaba4e0cee4 100644 --- a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 @@ -3,7 +3,7 @@ function Test-CIPPAccess { $Request, [switch]$TenantList ) - + if ($Request.Params.CIPPEndpoint -eq 'ExecSAMSetup') { return $true } if (!$Request.Headers.'x-ms-client-principal') { # Direct API Access $CustomRoles = @('CIPP-API') From 09c2cf5986b29fa32a20ca06bbe7243001e26d22 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 14 Jun 2024 12:03:02 +0200 Subject: [PATCH 21/32] added anchor conversion --- Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 index 4a05cf778fe7c..d9bca21efd697 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 @@ -22,6 +22,7 @@ function New-ExoRequest ($tenantid, $cmdlet, $cmdParams, $useSystemMailbox, $Anc if ($cmdparams.Identity) { $Anchor = $cmdparams.Identity } if ($cmdparams.anr) { $Anchor = $cmdparams.anr } if ($cmdparams.User) { $Anchor = $cmdparams.User } + if ($cmdparams.mailbox) { $Anchor = $cmdparams.mailbox } if (!$Anchor -or $useSystemMailbox) { if (!$Tenant.initialDomainName) { @@ -31,6 +32,15 @@ function New-ExoRequest ($tenantid, $cmdlet, $cmdParams, $useSystemMailbox, $Anc } $anchor = "UPN:SystemMailbox{8cc370d3-822a-4ab8-a926-bb94bd0641a9}@$($OnMicrosoft)" } + #if the anchor is a GUID, try looking up the user. + if ($Anchor -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') { + $Anchor = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$Anchor" -tenantid $tenantid -NoAuthCheck $NoAuthCheck + if ($Anchor) { + $Anchor = $Anchor.UserPrincipalName + } else { + Write-Error "Failed to find user with GUID $Anchor" + } + } } Write-Host "Using $Anchor" $Headers = @{ From 455ad75884349e21c9a6efe903f35330aef04e13 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 14 Jun 2024 12:06:02 +0200 Subject: [PATCH 22/32] added support for incorrect initial domain --- Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 index d9bca21efd697..8ef890eaf10d8 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 @@ -25,7 +25,7 @@ function New-ExoRequest ($tenantid, $cmdlet, $cmdParams, $useSystemMailbox, $Anc if ($cmdparams.mailbox) { $Anchor = $cmdparams.mailbox } if (!$Anchor -or $useSystemMailbox) { - if (!$Tenant.initialDomainName) { + if (!$Tenant.initialDomainName -or $Tenant.initialDomainName -notlike '*onmicrosoft.com*') { $OnMicrosoft = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/domains?$top=999' -tenantid $tenantid -NoAuthCheck $NoAuthCheck | Where-Object -Property isInitial -EQ $true).id } else { $OnMicrosoft = $Tenant.initialDomainName From e094d8cecbdeeb5488265d18b883fba1d2077c58 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 14 Jun 2024 12:26:27 +0200 Subject: [PATCH 23/32] fix pagination issue --- .../CippExtensions/NinjaOne/Invoke-NinjaOneTenantSync.ps1 | 8 ++++---- SendStats/run.ps1 | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneTenantSync.ps1 b/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneTenantSync.ps1 index ebf491cec05af..9828682c6348d 100644 --- a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneTenantSync.ps1 +++ b/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneTenantSync.ps1 @@ -267,7 +267,7 @@ function Invoke-NinjaOneTenantSync { @{ id = 'Users' method = 'GET' - url = '/users' + url = '/users?$top=999' }, @{ id = 'TenantDetails' @@ -292,7 +292,7 @@ function Invoke-NinjaOneTenantSync { @{ id = 'Devices' method = 'GET' - url = '/deviceManagement/managedDevices' + url = '/deviceManagement/managedDevices?$top=999' }, @{ id = 'DeviceCompliancePolicies' @@ -317,12 +317,12 @@ function Invoke-NinjaOneTenantSync { @{ id = 'SecureScore' method = 'GET' - url = '/security/secureScores' + url = '/security/secureScores?$top=999' }, @{ id = 'SecureScoreControlProfiles' method = 'GET' - url = '/security/secureScoreControlProfiles' + url = '/security/secureScoreControlProfiles?$top=999' }, @{ id = 'Subscriptions' diff --git a/SendStats/run.ps1 b/SendStats/run.ps1 index b4427d230338d..2e30b113ab555 100644 --- a/SendStats/run.ps1 +++ b/SendStats/run.ps1 @@ -17,6 +17,7 @@ $SendingObject = [PSCustomObject]@{ SetupComplete = $SetupComplete RunningVersionAPI = $APIVersion.trim() CountOfTotalTenants = $tenantcount + uid = $env:TenantID } | ConvertTo-Json Invoke-RestMethod -Uri 'https://management.cipp.app/api/stats' -Method POST -Body $SendingObject -ContentType 'application/json' \ No newline at end of file From 9b3b9de38e888917268bf1e70811f85802e1c4c4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 14 Jun 2024 12:27:42 +0200 Subject: [PATCH 24/32] fixes ninja bug --- Modules/CippExtensions/Public/Get-ExtensionRateLimit.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/CippExtensions/Public/Get-ExtensionRateLimit.ps1 b/Modules/CippExtensions/Public/Get-ExtensionRateLimit.ps1 index 2a0e718402f45..242d4dd86390a 100644 --- a/Modules/CippExtensions/Public/Get-ExtensionRateLimit.ps1 +++ b/Modules/CippExtensions/Public/Get-ExtensionRateLimit.ps1 @@ -24,7 +24,6 @@ function Get-ExtensionRateLimit($ExtensionName, $ExtensionPartitionKey, $RateLim } if (($ActiveJobs | Measure-Object).count -ge $RateLimit) { Write-Host "Rate Limiting. Currently $($ActiveJobs.count) Active Jobs" - Start-Sleep -Seconds $WaitTime $CurrentMap = Get-ExtensionRateLimit -ExtensionName $ExtensionName -ExtensionPartitionKey $ExtensionPartitionKey -RateLimit $RateLimit -WaitTime $WaitTime } From d5f7c5e245193be58f0281189aac556fbb373295 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 14 Jun 2024 14:59:43 +0200 Subject: [PATCH 25/32] version up --- version_latest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_latest.txt b/version_latest.txt index edb1d397cf282..a94a88fbb8897 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.8.0 \ No newline at end of file +5.8.5 \ No newline at end of file From 285e24c791375e1ecfb36b434487ebde9e9ccdca Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 14 Jun 2024 15:27:54 +0200 Subject: [PATCH 26/32] final touches prerelease --- Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 | 3 ++- .../Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 index 5bb406658e370..1c92a5e9c5d29 100644 --- a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 @@ -34,7 +34,7 @@ function Set-CIPPPerUserMFA { try { $int = 0 $Body = @{ - PerUserState = $State + perUserMFAstate = $State } $Requests = foreach ($id in $userId) { @{ @@ -48,6 +48,7 @@ function Set-CIPPPerUserMFA { } } + $Requests = New-GraphBulkRequest -tenantid $tenantfilter -scope 'https://graph.microsoft.com/.default' -Requests @($Requests) -asapp $true "Successfully set Per user MFA State for $userId" diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 index 9ecd363c44a3f..c832045294232 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 @@ -19,7 +19,7 @@ function Invoke-CIPPStandardPerUserMFA { If ($Settings.remediate -eq $true) { if ($UsersWithoutMFA) { try { - $MFAMessage = Set-CIPPPeruserMFA -TenantFilter $Tenant -UserId $UsersWithoutMFA.UserPrincipalName -State 'Enforced' + $MFAMessage = Set-CIPPPeruserMFA -TenantFilter $Tenant -UserId $UsersWithoutMFA.UserPrincipalName -State 'enforced' Write-LogMessage -API 'Standards' -tenant $tenant -message $MFAMessage -sev Info } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message From 95bd847b38b8da069d9c257a3868d57f56e1fd09 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 21 Jun 2024 02:37:01 +0200 Subject: [PATCH 27/32] fixes ca issues --- .../Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 | 1 - Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 index 60d86236ca224..498441852f6fc 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 @@ -60,7 +60,6 @@ Function Invoke-AddIntuneTemplate { $TemplateJson = $Template | ConvertTo-Json -Depth 100 $DisplayName = $Template.name - } 'windowsDriverUpdateProfiles' { $Type = 'windowsDriverUpdateProfiles' diff --git a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 index 15ecab0ea7493..2999f0705b177 100644 --- a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 @@ -5,11 +5,11 @@ function Set-CIPPAssignedPolicy { $PolicyId, $Type, $TenantFilter, - $PlatformType = 'deviceManagement', + $PlatformType, $APIName = 'Assign Policy', $ExecutingUser ) - + if (!$PlatformType) { $PlatformType = 'deviceManagement' } try { $assignmentsObject = switch ($GroupName) { 'allLicensedUsers' { @@ -70,9 +70,11 @@ function Set-CIPPAssignedPolicy { assignments = @($assignmentsObject) } if ($PSCmdlet.ShouldProcess($GroupName, "Assigning policy $PolicyId")) { + Write-Host "https://graph.microsoft.com/beta/$($PlatformType)/$Type('$($PolicyId)')/assign" $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/$($PlatformType)/$Type('$($PolicyId)')/assign" -tenantid $tenantFilter -type POST -body ($assignmentsObject | ConvertTo-Json -Depth 10) Write-LogMessage -user $ExecutingUser -API $APIName -message "Assigned Policy to $($GroupName)" -Sev 'Info' -tenant $TenantFilter } + return "Assigned policy to $($GroupName) Policy ID is $($PolicyId)." } catch { Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to assign Policy to $GroupName. Policy ID is $($PolicyId)." -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) From 82077fc299fb4cf7894623891fe26ff6bfae9cbe Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 21 Jun 2024 13:46:28 +0200 Subject: [PATCH 28/32] fixes bpa table error in console --- .../HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 index 22e0786174757..d597a8d6bb87a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 @@ -19,7 +19,6 @@ Function Invoke-ListBPA { # Get all possible JSON files for reports, find the correct one, select the Columns $JSONFields = @() - $Columns = $null $BPATemplateTable = Get-CippTable -tablename 'templates' $Filter = "PartitionKey eq 'BPATemplate'" $Templates = (Get-CIPPAzDataTableEntity @BPATemplateTable -Filter $Filter).JSON | ConvertFrom-Json @@ -74,7 +73,7 @@ Function Invoke-ListBPA { $Results = [PSCustomObject]@{ Data = @($Data) - Columns = $Columns + Columns = @($Columns) Style = $Style } From db9bc325ecb600375eb28ff20b2c640ed1c9b93b Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 21 Jun 2024 15:29:09 +0200 Subject: [PATCH 29/32] remove bpa file reads --- BestPracticeAnalyser_OrchestrationStarter/run.ps1 | 10 ++++------ BestPracticeAnalyser_OrchestrationStarterTimer/run.ps1 | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/BestPracticeAnalyser_OrchestrationStarter/run.ps1 b/BestPracticeAnalyser_OrchestrationStarter/run.ps1 index 0afc6fdc07e0f..3135509f9b3e3 100644 --- a/BestPracticeAnalyser_OrchestrationStarter/run.ps1 +++ b/BestPracticeAnalyser_OrchestrationStarter/run.ps1 @@ -8,12 +8,10 @@ if ($Request.Query.TenantFilter) { $TenantList = Get-Tenants $Name = 'Best Practice Analyser (All Tenants)' } -$CippRoot = (Get-Item $PSScriptRoot).Parent.FullName -$TemplatesLoc = Get-ChildItem "$CippRoot\Config\*.BPATemplate.json" -$Templates = $TemplatesLoc | ForEach-Object { - $Template = $(Get-Content $_) | ConvertFrom-Json - $Template.Name -} + +$BPATemplateTable = Get-CippTable -tablename 'templates' +$Filter = "PartitionKey eq 'BPATemplate'" +$Templates = ((Get-CIPPAzDataTableEntity @BPATemplateTable -Filter $Filter).JSON | ConvertFrom-Json).Name $BPAReports = foreach ($Tenant in $TenantList) { foreach ($Template in $Templates) { diff --git a/BestPracticeAnalyser_OrchestrationStarterTimer/run.ps1 b/BestPracticeAnalyser_OrchestrationStarterTimer/run.ps1 index f111844160d46..0b9faa0a7c8b4 100644 --- a/BestPracticeAnalyser_OrchestrationStarterTimer/run.ps1 +++ b/BestPracticeAnalyser_OrchestrationStarterTimer/run.ps1 @@ -7,12 +7,10 @@ if ($env:DEV_SKIP_BPA_TIMER) { $TenantList = Get-Tenants -$CippRoot = (Get-Item $PSScriptRoot).Parent.FullName -$TemplatesLoc = Get-ChildItem "$CippRoot\Config\*.BPATemplate.json" -$Templates = $TemplatesLoc | ForEach-Object { - $Template = $(Get-Content $_) | ConvertFrom-Json - $Template.Name -} +$BPATemplateTable = Get-CippTable -tablename 'templates' +$Filter = "PartitionKey eq 'BPATemplate'" +$Templates = ((Get-CIPPAzDataTableEntity @BPATemplateTable -Filter $Filter).JSON | ConvertFrom-Json).Name + $BPAReports = foreach ($Tenant in $TenantList) { foreach ($Template in $Templates) { From a29142f3ab29adddebf8917a6bba6b79479f4983 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 21 Jun 2024 15:59:33 +0200 Subject: [PATCH 30/32] added delete report --- .../Public/Invoke-RemoveBPATemplate.ps1 | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Modules/CIPPCore/Public/Invoke-RemoveBPATemplate.ps1 diff --git a/Modules/CIPPCore/Public/Invoke-RemoveBPATemplate.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveBPATemplate.ps1 new file mode 100644 index 0000000000000..b9ae2a8c13e11 --- /dev/null +++ b/Modules/CIPPCore/Public/Invoke-RemoveBPATemplate.ps1 @@ -0,0 +1,38 @@ +using namespace System.Net + +Function Invoke-RemoveBPATemplate { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Tenant.ConditionalAccess.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + $ID = $request.query.TemplateName + try { + $Table = Get-CippTable -tablename 'templates' + + $Filter = "PartitionKey eq 'BPATemplate' and RowKey eq '$id'" + $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey + Remove-AzDataTableEntity @Table -Entity $clearRow + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Removed BPA Template with ID $ID." -Sev 'Info' + $body = [pscustomobject]@{'Results' = 'Successfully removed BPA Template' } + } catch { + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to remove BPA template $ID. $($_.Exception.Message)" -Sev 'Error' + $body = [pscustomobject]@{'Results' = "Failed to remove template: $($_.Exception.Message)" } + } + + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $body + }) + + +} From 002cdab1b81f9d8744187212b41b98324c1f03da Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 21 Jun 2024 16:27:50 +0200 Subject: [PATCH 31/32] remove file support bpa --- .../Activity Triggers/BPA/Push-BPACollectData.ps1 | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/BPA/Push-BPACollectData.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/BPA/Push-BPACollectData.ps1 index 71d41acb852b0..af2c6092ff217 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/BPA/Push-BPACollectData.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/BPA/Push-BPACollectData.ps1 @@ -6,10 +6,12 @@ function Push-BPACollectData { param($Item) $TenantName = Get-Tenants | Where-Object -Property defaultDomainName -EQ $Item.Tenant - $CippRoot = (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent.Parent.Parent.FullName - $TemplatesLoc = Get-ChildItem "$CippRoot\Config\*.BPATemplate.json" + $BPATemplateTable = Get-CippTable -tablename 'templates' + $Filter = "PartitionKey eq 'BPATemplate'" + $TemplatesLoc = (Get-CIPPAzDataTableEntity @BPATemplateTable -Filter $Filter).JSON | ConvertFrom-Json + $Templates = $TemplatesLoc | ForEach-Object { - $Template = $(Get-Content $_) | ConvertFrom-Json + $Template = $_ [PSCustomObject]@{ Data = $Template Name = $Template.Name @@ -17,7 +19,7 @@ function Push-BPACollectData { } } $Table = Get-CippTable -tablename 'cachebpav2' - + Write-Host "Working on BPA for $($TenantName.displayName) with GUID $($TenantName.customerId) - Report ID $($Item.Template)" $Template = $Templates | Where-Object -Property Name -EQ -Value $Item.Template # Build up the result object that will be stored in tables $Result = @{ @@ -39,13 +41,13 @@ function Push-BPACollectData { } if ($Field.parameters.psobject.properties.name) { $field.Parameters | ForEach-Object { - Write-Information "Doing: $($_.psobject.properties.name) with value $($_.psobject.properties.value)" $paramsField[$_.psobject.properties.name] = $_.psobject.properties.value } } $FieldInfo = New-GraphGetRequest @paramsField | Where-Object $filterscript | Select-Object $field.ExtractFields } 'Exchange' { + Write-Host "Trying to execute $($field.Command) for $($TenantName.displayName) with GUID $($TenantName.customerId)" if ($field.Command -notlike 'get-*') { Write-LogMessage -API 'BPA' -tenant $tenant -message 'The BPA only supports get- exchange commands. A set or update command was used.' -sev Error break @@ -93,6 +95,7 @@ function Push-BPACollectData { } 'JSON' { if ($FieldInfo -eq $null) { $JsonString = '{}' } else { $JsonString = (ConvertTo-Json -Depth 15 -InputObject $FieldInfo -Compress) } + Write-Host "Adding $($field.Name) to table with value $JsonString" $Result.Add($field.Name, $JSONString) } 'string' { From 149f68bbc726da005f7a9cd8e99b70b8f17372cc Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 21 Jun 2024 17:31:54 +0200 Subject: [PATCH 32/32] added lost sku exclusion --- Modules/CippExtensions/Private/New-GradientServiceSyncRun.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CippExtensions/Private/New-GradientServiceSyncRun.ps1 b/Modules/CippExtensions/Private/New-GradientServiceSyncRun.ps1 index 3200c46782c51..b2fdba7b43ae5 100644 --- a/Modules/CippExtensions/Private/New-GradientServiceSyncRun.ps1 +++ b/Modules/CippExtensions/Private/New-GradientServiceSyncRun.ps1 @@ -41,7 +41,7 @@ function New-GradientServiceSyncRun { Import-Module '.\Modules\CIPPCore' Write-Host "Doing $domainName" try { - $Licrequest = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $_.defaultDomainName -ErrorAction Stop + $Licrequest = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $_.defaultDomainName -ErrorAction Stop | Where-Object -Property skuId -NotIn $ExcludedSkuList.RowKey [PSCustomObject]@{ Tenant = $domainName Licenses = $Licrequest