diff --git a/Cache_SAMSetup/PermissionsTranslator.json b/Cache_SAMSetup/PermissionsTranslator.json
index 27c5a7e6463f..ecc57bd2649d 100644
--- a/Cache_SAMSetup/PermissionsTranslator.json
+++ b/Cache_SAMSetup/PermissionsTranslator.json
@@ -1004,8 +1004,15 @@
"description": "Allows the app to create, read, update, and delete events of all calendars without a signed-in user.",
"displayName": "Read and write calendars in all mailboxes",
"id": "ef54d2bf-783f-4e0f-bca1-3210c0444d99",
- "origin": "Application",
- "value": "Calendars.ReadWrite"
+ "origin": "Application (Office 365 Exchange Online)",
+ "value": "Calendars.ReadWrite.All"
+ },
+ {
+ "description": "Allows the app to create, read, update, and delete user's mailbox settings without a signed-in user. Does not include permission to send mail.",
+ "displayName": "Read and write all user mailbox settings",
+ "id": "f9156939-25cd-4ba8-abfe-7fabcf003749",
+ "origin": "Application (Office 365 Exchange Online)",
+ "value": "Mailbox.Settings.ReadWrite"
},
{
"description": "Allows the app to read your organization's user flows, without a signed-in user.",
@@ -5286,6 +5293,24 @@
"userConsentDisplayName": "Read Threat and Vulnerability Management vulnerability information",
"value": "Exchange.Manage"
},
+ {
+ "description": "Allows the app to create, read, update and delete events in all calendars in the organization user has permissions to access. This includes delegate and shared calendars",
+ "displayName": "Read and write user and shared calendars",
+ "id": "bbd1ca91-75e0-4814-ad94-9c5dbbae3415",
+ "Origin": "Delegated (Office 365 Exchange Online)",
+ "userConsentDescription": "Allows the app to read, update, create and delete events in all calendars in your organization you have permissions to access. This includes delegate and shared calendars",
+ "userConsentDisplayName": "Read and write to your and shared calendars",
+ "value": "Calendars.ReadWrite.All"
+ },
+ {
+ "description": "Allows the app to create, read, update, and delete user's mailbox settings. Does not include permission to send mail.",
+ "displayName": "Read and write user mailbox settings",
+ "id": "2e83d72d-8895-4b66-9eea-abb43449ab8b",
+ "Origin": "Delegated (Office 365 Exchange Online)",
+ "userConsentDescription": "Allows the app to read, update, create, and delete your mailbox settings.",
+ "userConsentDisplayName": "Read and write to your mailbox settings",
+ "value": "MailboxSettings.ReadWrite"
+ },
{
"description": "Allows the app to have full control of all site collections on behalf of the signed-in user.",
"displayName": "Manage Sharepoint Online",
@@ -5312,5 +5337,14 @@
"userConsentDescription": "Access Microsoft Teams and Skype for Business data as the signed in user",
"userConsentDisplayName": "Access Microsoft Teams and Skype for Business data based on the user's role membership",
"value": "user_impersonation"
+ },
+ {
+ "description": "Read and write all on-premises directory synchronization information",
+ "displayName": "Read and write all on-premises directory synchronization information",
+ "id": "c2d95988-7604-4ba1-aaed-38a5f82a51c7",
+ "Origin": "Delegated",
+ "userConsentDescription": "Access Microsoft Teams and Skype for Business data as the signed in user",
+ "userConsentDisplayName": "Access Microsoft Teams and Skype for Business data based on the user's role membership",
+ "value": "OnPremDirectorySynchronization.ReadWrite.All"
}
]
diff --git a/Cache_SAMSetup/SAMManifest.json b/Cache_SAMSetup/SAMManifest.json
index 6b1f6429af88..f94959dc3ac6 100644
--- a/Cache_SAMSetup/SAMManifest.json
+++ b/Cache_SAMSetup/SAMManifest.json
@@ -11,6 +11,12 @@
]
},
"requiredResourceAccess": [
+ {
+ "resourceAppId": "aeb86249-8ea3-49e2-900b-54cc8e308f85",
+ "resourceAccess": [
+ { "id": "fc946a4f-bc4d-413b-a090-b2c86113ec4f", "type": "Scope" }
+ ]
+ },
{
"resourceAppId": "fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd",
"resourceAccess": [
@@ -159,7 +165,11 @@
"resourceAppId": "00000002-0000-0ff1-ce00-000000000000",
"resourceAccess": [
{ "id": "ab4f2b77-0b06-4fc1-a9de-02113fc2ab7c", "type": "Scope" },
- { "id": "dc50a0fb-09a3-484d-be87-e023b12c6440", "type": "Role" }
+ { "id": "bbd1ca91-75e0-4814-ad94-9c5dbbae3415", "type": "Scope" },
+ { "id": "2e83d72d-8895-4b66-9eea-abb43449ab8b", "type": "Scope" },
+ { "id": "dc50a0fb-09a3-484d-be87-e023b12c6440", "type": "Role" },
+ { "id": "ef54d2bf-783f-4e0f-bca1-3210c0444d99", "type": "Role" },
+ { "id": "f9156939-25cd-4ba8-abfe-7fabcf003749", "type": "Role" }
]
},
{
diff --git a/Durable_BECRun/run.ps1 b/Durable_BECRun/run.ps1
index 377ca2c5533b..44eecff33d2f 100644
--- a/Durable_BECRun/run.ps1
+++ b/Durable_BECRun/run.ps1
@@ -10,7 +10,7 @@ Write-Host "Working on $UserName"
try {
$startDate = (Get-Date).AddDays(-7)
$endDate = (Get-Date)
- $auditLog = (New-ExoRequest -tenantid $Tenantfilter -cmdlet 'Get-AdminAuditLogConfig').UnifiedAuditLogIngestionEnabled
+ $auditLog = (New-ExoRequest -tenantid $Tenantfilter -cmdlet 'Get-AdminAuditLogConfig').UnifiedAuditLogIngestionEnabled
$7dayslog = if ($auditLog -eq $false) {
$ExtractResult = 'AuditLog is disabled. Cannot perform full analysis'
} else {
@@ -40,10 +40,10 @@ try {
Write-Host "Retrieved $($logsTenant.count) logs" -ForegroundColor Yellow
$logsTenant
} while ($LogsTenant.count % 5000 -eq 0 -and $LogsTenant.count -ne 0)
- $ExtractResult = 'Succesfully extracted logs from auditlog'
+ $ExtractResult = 'Successfully extracted logs from auditlog'
}
Try {
- $URI = "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=(userId eq '$SuspectUser')&`$top=1&`$orderby=createdDateTime desc"
+ $URI = "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=(userId eq '$SuspectUser')&`$top=1&`$orderby=createdDateTime desc"
$LastSignIn = New-GraphGetRequest -uri $URI -tenantid $TenantFilter -noPagination $true -verbose | Select-Object @{ Name = 'CreatedDateTime'; Expression = { $(($_.createdDateTime | Out-String) -replace '\r\n') } },
id,
@{ Name = 'AppDisplayName'; Expression = { $_.resourceDisplayName } },
diff --git a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1
index f0f4c6badf6d..8fb1f23c3537 100644
--- a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1
+++ b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1
@@ -6,7 +6,8 @@ function Add-CIPPApplicationPermission {
$Tenantfilter
)
if ($ApplicationId -eq $ENV:ApplicationID -and $Tenantfilter -eq $env:TenantID) {
- return @('Cannot modify application permissions for CIPP-SAM on partner tenant')
+ #return @('Cannot modify application permissions for CIPP-SAM on partner tenant')
+ $RequiredResourceAccess = 'CIPPDefaults'
}
Set-Location (Get-Item $PSScriptRoot).FullName
if ($RequiredResourceAccess -eq 'CIPPDefaults') {
diff --git a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1
index befa8155df6c..ffd25c2b50a8 100644
--- a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1
+++ b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1
@@ -6,54 +6,138 @@ function Add-CIPPAzDataTableEntity {
[switch]$Force,
[switch]$CreateTableIfNotExists
)
-
+
+ $MaxRowSize = 500000 - 100 # Maximum size of an entity
+ $MaxSize = 30kb # Maximum size of a property value
+
foreach ($SingleEnt in $Entity) {
try {
- Add-AzDataTableEntity -context $Context -force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $SingleEnt -ErrorAction Stop
+ Add-AzDataTableEntity -Context $Context -Force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $SingleEnt -ErrorAction Stop
} catch [System.Exception] {
if ($_.Exception.ErrorCode -eq 'PropertyValueTooLarge' -or $_.Exception.ErrorCode -eq 'EntityTooLarge') {
try {
- $MaxSize = 30kb
- $largePropertyName = $null
+ $largePropertyNames = [System.Collections.ArrayList]::new()
+ $entitySize = 0
foreach ($key in $SingleEnt.Keys) {
- if ($SingleEnt[$key].Length -gt $MaxSize) {
- $largePropertyName = $key
- break
+ $propertySize = [System.Text.Encoding]::UTF8.GetByteCount($SingleEnt[$key].ToString())
+ $entitySize = $entitySize + $propertySize
+ if ($propertySize -gt $MaxSize) {
+ $largePropertyNames.Add($key)
}
+
}
- if ($largePropertyName) {
- $dataString = $SingleEnt[$largePropertyName]
- $splitCount = [math]::Ceiling($dataString.Length / $MaxSize)
- $splitData = 0..($splitCount - 1) | ForEach-Object {
- $start = $_ * $MaxSize
- $dataString.Substring($start, [Math]::Min($MaxSize, $dataString.Length - $start))
- }
+ if ($largePropertyNames.Count -gt 0) {
+ $splitInfoList = [System.Collections.ArrayList]@()
+ foreach ($largePropertyName in $largePropertyNames) {
+ $dataString = $SingleEnt[$largePropertyName]
+ $splitCount = [math]::Ceiling($dataString.Length / $MaxSize)
+ $splitData = [System.Collections.ArrayList]@()
+ for ($i = 0; $i -lt $splitCount; $i++) {
+ $start = $i * $MaxSize
+ $splitData.Add($dataString.Substring($start, [Math]::Min($MaxSize, $dataString.Length - $start))) > $null
+ }
+
+ $splitPropertyNames = [System.Collections.ArrayList]@()
+ for ($i = 0; $i -lt $splitData.Count; $i++) {
+ $splitPropertyNames.Add("${largePropertyName}_Part$i") > $null
+ }
- $splitPropertyNames = 1..$splitData.Count | ForEach-Object {
- "${largePropertyName}_Part$_"
+ $splitInfo = @{
+ OriginalHeader = $largePropertyName
+ SplitHeaders = $splitPropertyNames
+ }
+ $splitInfoList.Add($splitInfo) > $null
+ $SingleEnt.Remove($largePropertyName)
+
+ for ($i = 0; $i -lt $splitData.Count; $i++) {
+ $SingleEnt[$splitPropertyNames[$i]] = $splitData[$i]
+ }
}
- $splitInfo = @{
- OriginalHeader = $largePropertyName
- SplitHeaders = $splitPropertyNames
+ $SingleEnt['SplitOverProps'] = ($splitInfoList | ConvertTo-Json).ToString()
+ }
+
+ # Check if the entity is still too large
+ $entitySize = [System.Text.Encoding]::UTF8.GetByteCount($($SingleEnt | ConvertTo-Json))
+ if ($entitySize -gt $MaxRowSize) {
+ $rows = [System.Collections.ArrayList]@()
+ $originalPartitionKey = $SingleEnt.PartitionKey
+ $originalRowKey = $SingleEnt.RowKey
+ $entityIndex = 0
+
+ while ($entitySize -gt $MaxRowSize) {
+ Write-Host "Entity size is $entitySize. Splitting entity into multiple parts."
+ $newEntity = @{}
+ $newEntity['PartitionKey'] = $originalPartitionKey
+ if ($entityIndex -eq 0) {
+ $newEntity['RowKey'] = $originalRowKey
+ } else {
+ $newEntity['RowKey'] = "$($originalRowKey)-part$entityIndex"
+ }
+ $newEntity['OriginalEntityId'] = $originalRowKey
+ $newEntity['PartIndex'] = $entityIndex
+ $entityIndex++
+
+ $propertiesToRemove = [System.Collections.ArrayList]@()
+ foreach ($key in $SingleEnt.Keys) {
+ $newEntitySize = [System.Text.Encoding]::UTF8.GetByteCount($($newEntity | ConvertTo-Json))
+ if ($newEntitySize -lt $MaxRowSize) {
+ $propertySize = [System.Text.Encoding]::UTF8.GetByteCount($SingleEnt[$key].ToString())
+ if ($propertySize -gt $MaxRowSize) {
+ $dataString = $SingleEnt[$key]
+ $splitCount = [math]::Ceiling($dataString.Length / $MaxSize)
+ $splitData = [System.Collections.ArrayList]@()
+ for ($i = 0; $i -lt $splitCount; $i++) {
+ $start = $i * $MaxSize
+ $splitData.Add($dataString.Substring($start, [Math]::Min($MaxSize, $dataString.Length - $start))) > $null
+ }
+
+ $splitPropertyNames = [System.Collections.ArrayList]@()
+ for ($i = 0; $i -lt $splitData.Count; $i++) {
+ $splitPropertyNames.Add("${key}_Part$i") > $null
+ }
+
+ for ($i = 0; $i -lt $splitData.Count; $i++) {
+ $newEntity[$splitPropertyNames[$i]] = $splitData[$i]
+ }
+ } else {
+ $newEntity[$key] = $SingleEnt[$key]
+ }
+ $propertiesToRemove.Add($key) > $null
+ }
+ }
+
+ foreach ($prop in $propertiesToRemove) {
+ $SingleEnt.Remove($prop)
+ }
+
+ $rows.Add($newEntity) > $null
+ $entitySize = [System.Text.Encoding]::UTF8.GetByteCount($($SingleEnt | ConvertTo-Json))
}
- $SingleEnt['SplitOverProps'] = ($splitInfo | ConvertTo-Json).ToString()
- $SingleEnt.Remove($largePropertyName)
- for ($i = 0; $i -lt $splitData.Count; $i++) {
- $SingleEnt[$splitPropertyNames[$i]] = $splitData[$i]
+ if ($SingleEnt.Count -gt 0) {
+ $SingleEnt['RowKey'] = "$($originalRowKey)-part$entityIndex"
+ $SingleEnt['OriginalEntityId'] = $originalRowKey
+ $SingleEnt['PartIndex'] = $entityIndex
+ $SingleEnt['PartitionKey'] = $originalPartitionKey
+
+ $rows.Add($SingleEnt) > $null
}
- Add-AzDataTableEntity -context $Context -force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $SingleEnt
+ foreach ($row in $rows) {
+ Write-Host "current entity is $($row.RowKey) with $($row.PartitionKey). Our size is $([System.Text.Encoding]::UTF8.GetByteCount($($row | ConvertTo-Json)))"
+ Add-AzDataTableEntity -Context $Context -Force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $row
+ }
+ } else {
+ Add-AzDataTableEntity -Context $Context -Force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $SingleEnt
}
} catch {
- throw "Error processing entity: $($_.Exception.Message)."
+ throw "Error processing entity: $($_.Exception.Message) Linenumner: $($_.InvocationInfo.ScriptLineNumber)"
}
} else {
- Write-Host "THE ERROR IS $($_.Exception.ErrorCode)"
-
+ Write-Host "THE ERROR IS $($_.Exception.ErrorCode). The size of the entity is $entitySize."
throw $_
}
}
diff --git a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1
index 921488c45f08..7701a5ff68cb 100644
--- a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1
+++ b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1
@@ -3,17 +3,21 @@ function Add-CIPPDelegatedPermission {
param(
$RequiredResourceAccess,
$ApplicationId,
+ $NoTranslateRequired,
$Tenantfilter
)
Write-Host 'Adding Delegated Permissions'
Set-Location (Get-Item $PSScriptRoot).FullName
if ($ApplicationId -eq $ENV:ApplicationID -and $Tenantfilter -eq $env:TenantID) {
- return @('Cannot modify delgated permissions for CIPP-SAM on partner tenant')
+ #return @('Cannot modify delgated permissions for CIPP-SAM on partner tenant')
+ $RequiredResourceAccess = 'CIPPDefaults'
}
if ($RequiredResourceAccess -eq 'CIPPDefaults') {
$RequiredResourceAccess = (Get-Content '.\SAMManifest.json' | ConvertFrom-Json).requiredResourceAccess
+ $AdditionalPermissions = Get-Content '.\AdditionalPermissions.json' | ConvertFrom-Json
+ $RequiredResourceAccess = $RequiredResourceAccess + ($AdditionalPermissions | Where-Object { $RequiredResourceAccess.resourceAppId -notcontains $_.resourceAppId })
}
$Translator = Get-Content '.\PermissionsTranslator.json' | ConvertFrom-Json
$ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -tenantid $Tenantfilter -skipTokenCache $true
@@ -22,10 +26,22 @@ function Add-CIPPDelegatedPermission {
$CurrentDelegatedScopes = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals/$($ourSVCPrincipal.id)/oauth2PermissionGrants" -skipTokenCache $true -tenantid $Tenantfilter
- foreach ($App in $requiredResourceAccess) {
+ foreach ($App in $RequiredResourceAccess) {
$svcPrincipalId = $ServicePrincipalList | Where-Object -Property AppId -EQ $App.resourceAppId
+ $AdditionalScopes = ($AdditionalPermissions | Where-Object -Property resourceAppId -EQ $App.resourceAppId).resourceAccess
if (!$svcPrincipalId) { continue }
- $NewScope = ($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value -join ' '
+ if ($AdditionalScopes) {
+ $NewScope = (($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value + $AdditionalScopes.id | Select-Object -Unique) -join ' '
+ Write-Host "NEW SCOPE: $NewScope"
+ } else {
+ if ($NoTranslateRequired) {
+ $NewScope = $App.resourceAccess | ForEach-Object { $_.id } -join ' '
+ } else {
+ $NewScope = ($Translator | Where-Object { $_.id -in $App.resourceAccess.id }).value -join ' '
+ }
+ $NewScope = ($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value -join ' '
+ }
+
$OldScope = ($CurrentDelegatedScopes | Where-Object -Property Resourceid -EQ $svcPrincipalId.id)
if (!$OldScope) {
diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1
index 952e3a68bc93..7e629e038798 100644
--- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1
+++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1
@@ -2,7 +2,8 @@ function Add-CIPPScheduledTask {
[CmdletBinding()]
param(
[pscustomobject]$Task,
- [bool]$Hidden
+ [bool]$Hidden,
+ [string]$SyncType = $null
)
$Table = Get-CIPPTable -TableName 'ScheduledTasks'
@@ -49,10 +50,13 @@ function Add-CIPPScheduledTask {
Hidden = [bool]$Hidden
Results = 'Planned'
}
+ if ($SyncType) {
+ $entity.SyncType = $SyncType
+ }
try {
Add-CIPPAzDataTableEntity @Table -Entity $entity -Force
} catch {
return "Could not add task: $($_.Exception.Message)"
}
return "Successfully added task: $($entity.Name)"
-}
\ No newline at end of file
+}
diff --git a/Modules/CIPPCore/Public/AdditionalPermissions.json b/Modules/CIPPCore/Public/AdditionalPermissions.json
new file mode 100644
index 000000000000..4983c6f5fd03
--- /dev/null
+++ b/Modules/CIPPCore/Public/AdditionalPermissions.json
@@ -0,0 +1,15 @@
+[
+ {
+ "resourceAppId": "00000003-0000-0ff1-ce00-000000000000",
+ "resourceAccess": [{ "id": "AllProfiles.Manage", "type": "Scope" }]
+ },
+ {
+ "resourceAppId": "fb78d390-0c51-40cd-8e17-fdbfab77341b",
+ "resourceAccess": [
+ { "id": "AdminApi.AccessAsUser.All", "type": "Scope" },
+ { "id": "FfoPowerShell.AccessAsUser.All", "type": "Scope" },
+ { "id": "RemotePowerShell.AccessAsUser.All", "type": "Scope" },
+ { "id": "VivaFeatureAccessPolicy.Manage.All", "type": "Scope" }
+ ]
+ }
+]
diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAddMultiTenantApp.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAddMultiTenantApp.ps1
index 62e04ac8ba5b..3f2009a0a950 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAddMultiTenantApp.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAddMultiTenantApp.ps1
@@ -18,4 +18,4 @@ function Push-ExecAddMultiTenantApp($QueueItem, $TriggerMetadata) {
} catch {
Write-LogMessage -message "Error adding application to tenant $($Queueitem.Tenant) - $($_.Exception.Message)" -tenant $Queueitem.Tenant -API 'Add Multitenant App' -sev Error
}
-}
\ No newline at end of file
+}
diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecApplicationCopy.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecApplicationCopy.ps1
new file mode 100644
index 000000000000..6437940809db
--- /dev/null
+++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecApplicationCopy.ps1
@@ -0,0 +1,13 @@
+function Push-ExecApplicationCopy($QueueItem, $TriggerMetadata) {
+ <#
+ .FUNCTIONALITY
+ Entrypoint
+ #>
+ try {
+ $Queueitem = $QueueItem | ConvertTo-Json -Depth 10 | ConvertFrom-Json
+ Write-Host "$($Queueitem | ConvertTo-Json -Depth 10)"
+ New-CIPPApplicationCopy -App $queueitem.AppId -Tenant $Queueitem.Tenant
+ } catch {
+ Write-LogMessage -message "Error adding application to tenant $($Queueitem.Tenant) - $($_.Exception.Message)" -tenant $Queueitem.Tenant -API 'Add Multitenant App' -sev Error
+ }
+}
diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1
index e94a5d904bf1..f157dd0884b9 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1
@@ -14,7 +14,7 @@ function Push-ExecScheduledCommand {
Write-Host "Started Task: $($Item.Command) for tenant: $tenant"
try {
try {
- Write-Host "Starting task: $($Item.Command) with parameters: "
+ Write-Host "Starting task: $($Item.Command) with parameters: $($commandParameters | ConvertTo-Json)"
$results = & $Item.Command @commandParameters
} catch {
$results = "Task Failed: $($_.Exception.Message)"
@@ -112,4 +112,4 @@ function Push-ExecScheduledCommand {
if ($TaskType -ne 'Alert') {
Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Successfully executed task: $($task.Name)" -sev Info
}
-}
\ No newline at end of file
+}
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecListBackup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecListBackup.ps1
index 90b7e41bc4c9..f04ea258bba7 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecListBackup.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecListBackup.ps1
@@ -10,7 +10,10 @@ Function Invoke-ExecListBackup {
[CmdletBinding()]
param($Request, $TriggerMetadata)
- $Result = Get-CIPPBackup -type $Request.body.Type -TenantFilter $Request.body.TenantFilter
+ $Result = Get-CIPPBackup -type $Request.query.Type -TenantFilter $Request.query.TenantFilter
+ if ($request.query.NameOnly) {
+ $Result = $Result | Select-Object RowKey, timestamp
+ }
Write-LogMessage -user $request.headers.'x-ms-client-principal' -API 'Alerts' -message $request.body.text -Sev $request.body.Severity
# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1
index 81231ca9df96..4a3869b56176 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1
@@ -19,6 +19,11 @@ Function Invoke-ListScheduledItems {
$HiddenTasks = $true
}
$Tasks = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'ScheduledTask'" | Where-Object { $_.Hidden -ne $HiddenTasks }
+ if ($Request.Query.Type) {
+ $tasks.Command
+ $Tasks = $Tasks | Where-Object { $_.command -eq $Request.Query.Type }
+ }
+
$AllowedTenants = Test-CIPPAccess -Request $Request -TenantList
if ($AllowedTenants -notcontains 'AllTenants') {
$Tasks = $Tasks | Where-Object -Property TenantId -In $AllowedTenants
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1
index 595bf587ef19..d8a5bfba10dc 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1
@@ -20,36 +20,42 @@ Function Invoke-ExecExtensionMapping {
if ($Request.Query.List) {
switch ($Request.Query.List) {
- 'Halo' {
+ 'HaloPSA' {
$body = Get-HaloMapping -CIPPMapping $Table
}
- 'NinjaOrgs' {
+ 'NinjaOne' {
$Body = Get-NinjaOneOrgMapping -CIPPMapping $Table
}
- 'NinjaFields' {
+ 'NinjaOneFields' {
$Body = Get-NinjaOneFieldMapping -CIPPMapping $Table
}
'Hudu' {
$Body = Get-HuduMapping -CIPPMapping $Table
}
+ 'HuduFields' {
+ $Body = Get-HuduFieldMapping -CIPPMapping $Table
+ }
}
}
try {
if ($Request.Query.AddMapping) {
switch ($Request.Query.AddMapping) {
- 'Halo' {
+ 'HaloPSA' {
$body = Set-HaloMapping -CIPPMapping $Table -APIName $APIName -Request $Request
}
- 'NinjaOrgs' {
+ 'NinjaOne' {
$Body = Set-NinjaOneOrgMapping -CIPPMapping $Table -APIName $APIName -Request $Request
}
- 'NinjaFields' {
+ 'NinjaOneFields' {
$Body = Set-NinjaOneFieldMapping -CIPPMapping $Table -APIName $APIName -Request $Request -TriggerMetadata $TriggerMetadata
}
'Hudu' {
$Body = Set-HuduMapping -CIPPMapping $Table -APIName $APIName -Request $Request
}
+ 'HuduFields' {
+ $Body = Set-ExtensionFieldMapping -CIPPMapping $Table -APIName $APIName -Request $Request -Extension 'Hudu'
+ }
}
}
} catch {
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionSync.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionSync.ps1
index 2f069988996a..9c090c91f094 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionSync.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionSync.ps1
@@ -23,7 +23,7 @@ Function Invoke-ExecExtensionSync {
'Gradient' {
If ($Configuration.Gradient.enabled -and $Configuration.Gradient.BillingEnabled) {
Push-OutputBinding -Name gradientqueue -Value 'LetsGo'
- $Results = [pscustomobject]@{'Results' = 'Succesfully started Gradient Sync' }
+ $Results = [pscustomobject]@{'Results' = 'Successfully started Gradient Sync' }
}
}
}
@@ -40,8 +40,8 @@ Function Invoke-ExecExtensionSync {
$Table = Get-CIPPTable -TableName NinjaOneSettings
$CIPPMapping = Get-CIPPTable -TableName CippMapping
- $Filter = "PartitionKey eq 'NinjaOrgsMapping'"
- $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.NinjaOne -and $_.NinjaOne -ne '' }
+ $Filter = "PartitionKey eq 'NinjaOneMapping'"
+ $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.IntegrationId -and $_.IntegrationId -ne '' }
if ($Request.Query.TenantID) {
$Tenant = $TenantsToProcess | Where-Object { $_.RowKey -eq $Request.Query.TenantID }
@@ -59,7 +59,7 @@ Function Invoke-ExecExtensionSync {
$InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress)
Write-Host "Started permissions orchestration with ID = '$InstanceId'"
- $Results = [pscustomobject]@{'Results' = "NinjaOne Synchronization Queued for $($Tenant.NinjaOneName)" }
+ $Results = [pscustomobject]@{'Results' = "NinjaOne Synchronization Queued for $($Tenant.IntegrationName)" }
} else {
$Results = [pscustomobject]@{'Results' = 'Tenant was not found.' }
}
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1
index 74b188742a97..e9a6465c4ff0 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1
@@ -27,7 +27,7 @@ Function Invoke-ExecExtensionTest {
if ($ExistingIntegrations.Status -ne 'active') {
$ActivateRequest = Invoke-RestMethod -Uri 'https://app.usegradient.com/api/vendor-api/organization/status/active' -Method PATCH -Headers $GradientToken
}
- $Results = [pscustomobject]@{'Results' = 'Succesfully Connected to Gradient' }
+ $Results = [pscustomobject]@{'Results' = 'Successfully Connected to Gradient' }
}
'CIPP-API' {
@@ -35,13 +35,13 @@ Function Invoke-ExecExtensionTest {
}
'NinjaOne' {
$token = Get-NinjaOneToken -configuration $Configuration.NinjaOne
- $Results = [pscustomobject]@{'Results' = 'Succesfully Connected to NinjaOne' }
+ $Results = [pscustomobject]@{'Results' = 'Successfully Connected to NinjaOne' }
}
'PWPush' {
$Payload = 'This is a test from CIPP'
$PasswordLink = New-PwPushLink -Payload $Payload
if ($PasswordLink) {
- $Results = [pscustomobject]@{'Results' = 'Succesfully generated PWPush'; 'Link' = $PasswordLink }
+ $Results = [pscustomobject]@{'Results' = 'Successfully generated PWPush'; 'Link' = $PasswordLink }
} else {
$Results = [pscustomobject]@{'Results' = 'PWPush is not enabled' }
}
@@ -50,7 +50,7 @@ Function Invoke-ExecExtensionTest {
Connect-HuduAPI -configuration $Configuration.Hudu
$Version = Get-HuduAppInfo
Write-Host ($Version | ConvertTo-Json)
- $Results = [pscustomobject]@{'Results' = ('Succesfully Connected to Hudu, version: {0}' -f $Version.version) }
+ $Results = [pscustomobject]@{'Results' = ('Successfully Connected to Hudu, version: {0}' -f $Version.version) }
}
}
} catch {
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1
index f40536cebcb4..ebbf5ca9dba4 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1
@@ -7,25 +7,26 @@ Function Invoke-ExecExtensionsConfig {
.ROLE
CIPP.Extension.ReadWrite
#>
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Scope = 'Function')]
[CmdletBinding()]
param($Request, $TriggerMetadata)
$APIName = $TriggerMetadata.FunctionName
- Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug'
+ Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug'
#Connect-AzAccount -UseDeviceAuthentication
# Write to the Azure Functions log stream.
- Write-Host 'PowerShell HTTP trigger function processed a request.'
+ Write-Information 'PowerShell HTTP trigger function processed a request.'
$results = try {
- if ($Request.body.CIPPAPI.Enabled) {
- $APIConfig = New-CIPPAPIConfig -ExecutingUser $request.headers.'x-ms-client-principal' -resetpassword $request.body.CIPPAPI.ResetPassword
+ if ($Request.Body.CIPPAPI.Enabled) {
+ $APIConfig = New-CIPPAPIConfig -ExecutingUser $Request.Headers.'x-ms-client-principal' -resetpassword $Request.Body.CIPPAPI.ResetPassword
$AddedText = $APIConfig.Results
}
# Check if NinjaOne URL is set correctly and the instance has at least version 5.6
- if ($request.body.NinjaOne) {
+ if ($Request.Body.NinjaOne) {
try {
- [version]$Version = (Invoke-WebRequest -Method GET -Uri "https://$(($request.body.NinjaOne.Instance -replace '/ws','') -replace 'https://','')/app-version.txt" -ea stop).content
+ [version]$Version = (Invoke-WebRequest -Method GET -Uri "https://$(($Request.Body.NinjaOne.Instance -replace '/ws','') -replace 'https://','')/app-version.txt" -ea stop).content
} catch {
throw "Failed to connect to NinjaOne check your Instance is set correctly eg 'app.ninjarmmm.com'"
}
@@ -35,30 +36,31 @@ Function Invoke-ExecExtensionsConfig {
}
$Table = Get-CIPPTable -TableName Extensionsconfig
- foreach ($APIKey in ([pscustomobject]$request.body).psobject.properties.name) {
- Write-Host "Working on $apikey"
- if ($request.body.$APIKey.APIKey -eq 'SentToKeyVault' -or $request.body.$APIKey.APIKey -eq '') {
- Write-Host 'Not sending to keyvault. Key previously set or left blank.'
+ foreach ($APIKey in ([pscustomobject]$Request.Body).psobject.properties.name) {
+ Write-Information "Working on $apikey"
+ if ($Request.Body.$APIKey.APIKey -eq 'SentToKeyVault' -or $Request.Body.$APIKey.APIKey -eq '') {
+ Write-Information 'Not sending to keyvault. Key previously set or left blank.'
} else {
- Write-Host 'writing API Key to keyvault, and clearing.'
- Write-Host "$ENV:WEBSITE_DEPLOYMENT_ID"
- if ($request.body.$APIKey.APIKey) {
+ Write-Information 'writing API Key to keyvault, and clearing.'
+ Write-Information "$ENV:WEBSITE_DEPLOYMENT_ID"
+ if ($Request.Body.$APIKey.APIKey) {
if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') {
$DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets'
$Secret = [PSCustomObject]@{
'PartitionKey' = $APIKey
'RowKey' = $APIKey
- 'APIKey' = $request.body.$APIKey.APIKey
+ 'APIKey' = $Request.Body.$APIKey.APIKey
}
Add-CIPPAzDataTableEntity @DevSecretsTable -Entity $Secret -Force
} else {
- $null = Set-AzKeyVaultSecret -VaultName $ENV:WEBSITE_DEPLOYMENT_ID -Name $APIKey -SecretValue (ConvertTo-SecureString -String $request.body.$APIKey.APIKey -AsPlainText -Force)
+ $null = Set-AzKeyVaultSecret -VaultName $ENV:WEBSITE_DEPLOYMENT_ID -Name $APIKey -SecretValue (ConvertTo-SecureString -AsPlainText -Force -String $Request.Body.$APIKey.APIKey)
}
}
- $request.body.$APIKey.APIKey = 'SentToKeyVault'
+ $Request.Body.$APIKey.APIKey = 'SentToKeyVault'
}
+ $Request.Body.$APIKey = $Request.Body.$APIKey | Select-Object * -ExcludeProperty ResetPassword
}
- $body = $request.body | Select-Object * -ExcludeProperty APIKey, Enabled | ConvertTo-Json -Depth 10 -Compress
+ $body = $Request.Body | Select-Object * -ExcludeProperty APIKey, Enabled | ConvertTo-Json -Depth 10 -Compress
$Config = @{
'PartitionKey' = 'CippExtensions'
'RowKey' = 'Config'
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRestoreBackup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRestoreBackup.ps1
index b9820352bfdd..476fffa02389 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRestoreBackup.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRestoreBackup.ps1
@@ -25,7 +25,7 @@ Function Invoke-ExecRestoreBackup {
Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Created backup' -Sev 'Debug'
$body = [pscustomobject]@{
- 'Results' = 'Succesfully restored backup.'
+ 'Results' = 'Successfully restored backup.'
}
} catch {
Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup: $($_.Exception.Message)" -Sev 'Error'
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1
index 7bb3f446bd11..bc94e7cf5851 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1
@@ -33,9 +33,9 @@ Function Invoke-AddDefenderDeployment {
allowPartnerToCollectIOSPersonalApplicationMetadata = [bool]$Compliance.ConnectIosCompliance
androidMobileApplicationManagementEnabled = [bool]$Compliance.ConnectAndroidCompliance
iosMobileApplicationManagementEnabled = [bool]$Compliance.appSync
- microsoftDefenderForEndpointAttachEnabled = [bool]$compliance.AllowMEMEnforceCompliance
+ microsoftDefenderForEndpointAttachEnabled = [bool]$true
} | ConvertTo-Json -Compress
- $SettingsRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/mobileThreatDefenseConnectors/' -tenantid $tenant -type POST -body $SettingsObj
+ $SettingsRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/mobileThreatDefenseConnectors/' -tenantid $tenant -type POST -body $SettingsObj -AsApp $true
"$($Tenant): Successfully set Defender Compliance and Reporting settings"
$Settings = switch ($PolicySettings) {
@@ -79,8 +79,7 @@ Function Invoke-AddDefenderDeployment {
Write-Host ($CheckExististing | ConvertTo-Json)
if ('Default AV Policy' -in $CheckExististing.Name) {
"$($Tenant): AV Policy already exists. Skipping"
- }
- else {
+ } else {
$PolBody = ConvertTo-Json -Depth 10 -Compress -InputObject @{
name = 'Default AV Policy'
description = ''
@@ -138,8 +137,7 @@ Function Invoke-AddDefenderDeployment {
$CheckExististingASR = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $tenant
if ('ASR Default rules' -in $CheckExististingASR.Name) {
"$($Tenant): ASR Policy already exists. Skipping"
- }
- else {
+ } else {
Write-Host $ASRbody
$ASRRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $tenant -type POST -body $ASRbody
Write-Host ($ASRRequest.id)
@@ -215,9 +213,8 @@ Function Invoke-AddDefenderDeployment {
$CheckExististingEDR = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $tenant
if ('EDR Configuration' -in $CheckExististingEDR.Name) {
"$($Tenant): EDR Policy already exists. Skipping"
- }
- else {
- $EDRRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $tenant -type POST -body $EDRbody
+ } else {
+ #$EDRRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $tenant -type POST -body $EDRbody
if ($ASR.AssignTo -ne 'none') {
$AssignBody = if ($ASR.AssignTo -ne 'AllDevicesAndUsers') { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.' + $($asr.AssignTo) + 'AssignmentTarget"}}]}' } else { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.allDevicesAssignmentTarget"}},{"id":"","target":{"@odata.type":"#microsoft.graph.allLicensedUsersAssignmentTarget"}}]}' }
$assign = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($EDRRequest.id)')/assign" -tenantid $tenant -type POST -body $AssignBody
@@ -226,8 +223,7 @@ Function Invoke-AddDefenderDeployment {
"$($Tenant): Successfully added EDR Settings"
}
- }
- catch {
+ } catch {
"Failed to add policy for $($Tenant): $($_.Exception.Message)"
Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Failed adding policy $($Displayname). Error: $($_.Exception.Message)" -Sev 'Error'
continue
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 498441852f6f..847c5f1174c7 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
@@ -41,82 +41,13 @@ Function Invoke-AddIntuneTemplate {
$TenantFilter = $Request.Query.TenantFilter
$URLName = $Request.Query.URLName
$ID = $Request.Query.id
- switch ($URLName) {
- 'deviceCompliancePolicies' {
- $Type = 'deviceCompliancePolicies'
- $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)/$($ID)?`$expand=scheduledActionsForRule(`$expand=scheduledActionConfigurations)" -tenantid $tenantfilter
- $DisplayName = $Template.displayName
- $TemplateJson = ConvertTo-Json -InputObject $Template -Depth 100 -Compress
- }
- 'managedAppPolicies' {
- $Type = 'AppProtection'
- $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$($urlname)('$($ID)')" -tenantid $tenantfilter
- $DisplayName = $Template.displayName
- $TemplateJson = ConvertTo-Json -InputObject $Template -Depth 100 -Compress
- }
- 'configurationPolicies' {
- $Type = 'Catalog'
- $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)('$($ID)')?`$expand=settings" -tenantid $tenantfilter | Select-Object name, description, settings, platforms, technologies, templateReference
- $TemplateJson = $Template | ConvertTo-Json -Depth 100
- $DisplayName = $Template.name
-
- }
- 'windowsDriverUpdateProfiles' {
- $Type = 'windowsDriverUpdateProfiles'
- $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)/$($ID)" -tenantid $tenantfilter | Select-Object * -ExcludeProperty id, lastModifiedDateTime, '@odata.context', 'ScopeTagIds', 'supportsScopeTags', 'createdDateTime'
- Write-Host ($Template | ConvertTo-Json)
- $DisplayName = $Template.displayName
- $TemplateJson = ConvertTo-Json -InputObject $Template -Depth 100 -Compress
- }
- 'deviceConfigurations' {
- $Type = 'Device'
- $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)/$($ID)" -tenantid $tenantfilter | Select-Object * -ExcludeProperty id, lastModifiedDateTime, '@odata.context', 'ScopeTagIds', 'supportsScopeTags', 'createdDateTime'
- Write-Host ($Template | ConvertTo-Json)
- $DisplayName = $Template.displayName
- $TemplateJson = ConvertTo-Json -InputObject $Template -Depth 100 -Compress
- }
- 'groupPolicyConfigurations' {
- $Type = 'Admin'
- $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)('$($ID)')" -tenantid $tenantfilter
- $DisplayName = $Template.displayName
- $TemplateJsonItems = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)('$($ID)')/definitionValues?`$expand=definition" -tenantid $tenantfilter
- $TemplateJsonSource = foreach ($TemplateJsonItem in $TemplateJsonItems) {
- $presentationValues = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)('$($ID)')/definitionValues('$($TemplateJsonItem.id)')/presentationValues?`$expand=presentation" -tenantid $tenantfilter | ForEach-Object {
- $obj = $_
- if ($obj.id) {
- $PresObj = @{
- id = $obj.id
- 'presentation@odata.bind' = "https://graph.microsoft.com/beta/deviceManagement/groupPolicyDefinitions('$($TemplateJsonItem.definition.id)')/presentations('$($obj.presentation.id)')"
- }
- if ($obj.values) { $PresObj['values'] = $obj.values }
- if ($obj.value) { $PresObj['value'] = $obj.value }
- if ($obj.'@odata.type') { $PresObj['@odata.type'] = $obj.'@odata.type' }
- [pscustomobject]$PresObj
- }
- }
- [PSCustomObject]@{
- 'definition@odata.bind' = "https://graph.microsoft.com/beta/deviceManagement/groupPolicyDefinitions('$($TemplateJsonItem.definition.id)')"
- enabled = $TemplateJsonItem.enabled
- presentationValues = @($presentationValues)
- }
- }
- $inputvar = [pscustomobject]@{
- added = @($TemplateJsonSource)
- updated = @()
- deletedIds = @()
-
- }
-
-
- $TemplateJson = (ConvertTo-Json -InputObject $inputvar -Depth 100 -Compress)
- }
- }
+ $Template = New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName $URLName -ID $ID
$object = [PSCustomObject]@{
- Displayname = $DisplayName
+ Displayname = $Template.DisplayName
Description = $Template.Description
- RAWJson = $TemplateJson
- Type = $Type
+ RAWJson = $Template.TemplateJson
+ Type = $Template.Type
GUID = $GUID
} | ConvertTo-Json
$Table = Get-CippTable -tablename 'templates'
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroupSenderAuthentication.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroupSenderAuthentication.ps1
new file mode 100644
index 000000000000..97ca6fe52147
--- /dev/null
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroupSenderAuthentication.ps1
@@ -0,0 +1,46 @@
+using namespace System.Net
+
+Function Invoke-ListGroupSenderAuthentication {
+ [CmdletBinding()]
+ param($Request, $TriggerMetadata)
+
+ $APIName = $TriggerMetadata.FunctionName
+ Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug'
+ # Write to the Azure Functions log stream.
+ Write-Host 'PowerShell HTTP trigger function processed a request.'
+
+ # Interact with query parameters or the body of the request.
+
+ $TenantFilter = $Request.Query.TenantFilter
+ $groupid = $Request.query.groupid
+ $GroupType = $Request.query.Type
+
+ $params = @{
+ Identity = $groupid
+ }
+
+
+ try {
+ switch ($GroupType) {
+ 'Distribution List' {
+ Write-Host 'Checking DL'
+ $State = (New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-DistributionGroup' -cmdParams $params -UseSystemMailbox $true).RequireSenderAuthenticationEnabled
+ }
+ 'Microsoft 365' {
+ Write-Host 'Checking M365 Group'
+ $State = (New-ExoRequest -tenantid $TenantFilter -cmdlet 'get-unifiedgroup' -cmdParams $params -UseSystemMailbox $true).RequireSenderAuthenticationEnabled
+
+ }
+ default { $state = $true }
+ }
+
+ } catch {
+ $state = $true
+ }
+
+ # We flip the value because the API is asking if the group is allowed to receive external mail
+ Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
+ StatusCode = [HttpStatusCode]::OK
+ Body = @{ allowedToReceiveExternal = !$state }
+ })
+}
\ No newline at end of file
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1
index 7b5ac0cd37b7..d94c6b0ce4bd 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1
@@ -29,7 +29,7 @@ Function Invoke-ListSites {
} else {
$ParsedRequest = $Result
}
- $GraphRequest = $ParsedRequest | Select-Object @{ Name = 'UPN'; Expression = { $_.'Owner Principal Name' } },
+ $GraphRequest = $ParsedRequest | Select-Object AutoMapUrl, @{ Name = 'UPN'; Expression = { $_.'Owner Principal Name' } },
@{ Name = 'displayName'; Expression = { $_.'Owner Display Name' } },
@{ Name = 'LastActive'; Expression = { $_.'Last Activity Date' } },
@{ Name = 'FileCount'; Expression = { [int]$_.'File Count' } },
@@ -41,14 +41,28 @@ Function Invoke-ListSites {
#Temporary workaround for url as report is broken.
#This API is so stupid its great.
- $URLs = (New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/sites/getAllSites?$select=SharePointIds' -asapp $true -tenantid $TenantFilter).SharePointIds
-
+ $URLs = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/getAllSites?$select=SharePointIds,name,webUrl,displayName,siteCollection' -asapp $true -tenantid $TenantFilter
+ $int = 0
+ if ($Type -eq 'SharePointSiteUsage') {
+ $Requests = foreach ($url in $URLs) {
+ @{
+ id = $int++
+ method = 'GET'
+ url = "sites/$($url.sharepointIds.siteId)/lists?`$select=id,name,list,parentReference"
+ }
+ }
+ $Requests = (New-GraphBulkRequest -tenantid $TenantFilter -scope 'https://graph.microsoft.com/.default' -Requests @($Requests) -asapp $true).body.value | Where-Object { $_.list.template -eq 'DocumentLibrary' }
+ }
$GraphRequest = foreach ($site in $GraphRequest) {
- $site.URL = ($URLs | Where-Object { $_.siteId -eq $site.SiteId }).siteUrl
+ $SiteURLs = ($URLs.SharePointIds | Where-Object { $_.siteId -eq $site.SiteId })
+ $site.URL = $SiteURLs.siteUrl
+ $ListId = ($Requests | Where-Object { $_.parentReference.siteId -like "*$($SiteURLs.siteId)*" }).id
+ $site.AutoMapUrl = "tenantId=$($SiteUrls.tenantId)&webId={$($SiteUrls.webId)}&siteid={$($SiteURLs.siteId)}&webUrl=$($SiteURLs.siteUrl)&listId={$($ListId)}"
$site
}
$StatusCode = [HttpStatusCode]::OK
+
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
$StatusCode = [HttpStatusCode]::Forbidden
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAlertsQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAlertsQueue.ps1
index 9b0e5aab48bb..0b90937f4feb 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAlertsQueue.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAlertsQueue.ps1
@@ -20,7 +20,7 @@ Function Invoke-ListAlertsQueue {
$WebhookRules = Get-CIPPAzDataTableEntity @WebhookTable
$ScheduledTasks = Get-CIPPTable -TableName 'ScheduledTasks'
- $ScheduledTasks = Get-CIPPAzDataTableEntity @ScheduledTasks | Where-Object { $_.hidden -eq $true }
+ $ScheduledTasks = Get-CIPPAzDataTableEntity @ScheduledTasks | Where-Object { $_.hidden -eq $true -and $_.command -like 'Get-CippAlert*' }
$AllowedTenants = Test-CIPPAccess -Request $Request -TenantList
$TenantList = Get-Tenants -IncludeErrors
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAddMultiTenantApp.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAddMultiTenantApp.ps1
index 08124ad41683..4cb38d9f9dc8 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAddMultiTenantApp.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAddMultiTenantApp.ps1
@@ -18,15 +18,10 @@ function Invoke-ExecAddMultiTenantApp {
$Results = try {
if ($request.body.CopyPermissions -eq $true) {
- try {
- $ExistingApp = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/applications(appId='$($Request.body.AppId)')" -tenantid $ENV:tenantid -NoAuthCheck $true
- $DelegateResourceAccess = $Existingapp.requiredResourceAccess
- $ApplicationResourceAccess = $Existingapp.requiredResourceAccess
- } catch {
- 'Failed to get existing permissions. The app does not exist in the partner tenant.'
- }
+ $Command = 'ExecApplicationCopy'
+ } else {
+ $Command = 'ExecAddMultiTenantApp'
}
- #This needs to be moved to a queue.
if ('allTenants' -in $Request.body.SelectedTenants.defaultDomainName) {
$TenantFilter = (Get-Tenants).defaultDomainName
} else {
@@ -36,7 +31,7 @@ function Invoke-ExecAddMultiTenantApp {
foreach ($Tenant in $TenantFilter) {
try {
Push-OutputBinding -Name QueueItem -Value ([pscustomobject]@{
- FunctionName = 'ExecAddMultiTenantApp'
+ FunctionName = $Command
Tenant = $tenant
appId = $Request.body.appid
applicationResourceAccess = $ApplicationResourceAccess
@@ -59,4 +54,4 @@ function Invoke-ExecAddMultiTenantApp {
Body = @{ Results = @($Results) }
})
-}
\ No newline at end of file
+}
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOffboardTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOffboardTenant.ps1
index adcb580e45cf..230816f2f58f 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOffboardTenant.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOffboardTenant.ps1
@@ -41,7 +41,7 @@ Function Invoke-ExecOffboardTenant {
$BulkResults = New-GraphBulkRequest -Requests $BulkRequests -tenantid $TenantFilter
- $results.Add('Succesfully removed guest users')
+ $results.Add('Successfully removed guest users')
Write-LogMessage -user $ExecutingUser -API $APIName -message "CSP Guest users were removed" -Sev "Info" -tenant $TenantFilter
} else {
$results.Add('No guest users found to remove')
@@ -83,7 +83,7 @@ Function Invoke-ExecOffboardTenant {
try {
New-GraphPostRequest -type PATCH -body $patchContactBody -Uri "https://graph.microsoft.com/v1.0/organization/$($orgContacts.id)" -tenantid $Tenantfilter -ContentType "application/json"
- $results.Add("Succesfully removed notification contacts from $($property): $(($propertyContacts | Where-Object { $domains -contains $_.Split("@")[1] }))")
+ $results.Add("Successfully removed notification contacts from $($property): $(($propertyContacts | Where-Object { $domains -contains $_.Split("@")[1] }))")
Write-LogMessage -user $ExecutingUser -API $APIName -message "Contacts were removed from $($property)" -Sev "Info" -tenant $TenantFilter
} catch {
$errors.Add("Failed to update property $($property): $($_.Exception.message)")
@@ -100,7 +100,7 @@ Function Invoke-ExecOffboardTenant {
$request.body.RemoveVendorApps | ForEach-Object {
try {
$delete = (New-GraphPostRequest -type 'DELETE' -Uri "https://graph.microsoft.com/v1.0/serviceprincipals/$($_.value)" -tenantid $Tenantfilter)
- $results.Add("Succesfully removed app $($_.label)")
+ $results.Add("Successfully removed app $($_.label)")
Write-LogMessage -user $ExecutingUser -API $APIName -message "App $($_.label) was removed" -Sev "Info" -tenant $TenantFilter
} catch {
#$results.Add("Failed to removed app $($_.displayName)")
@@ -118,7 +118,7 @@ Function Invoke-ExecOffboardTenant {
$sortedArray | ForEach-Object {
try {
$delete = (New-GraphPostRequest -type 'DELETE' -Uri "https://graph.microsoft.com/v1.0/serviceprincipals/$($_.id)" -tenantid $Tenantfilter)
- $results.Add("Succesfully removed app $($_.displayName)")
+ $results.Add("Successfully removed app $($_.displayName)")
Write-LogMessage -user $ExecutingUser -API $APIName -message "App $($_.displayName) was removed" -Sev "Info" -tenant $TenantFilter
} catch {
#$results.Add("Failed to removed app $($_.displayName)")
@@ -141,7 +141,7 @@ Function Invoke-ExecOffboardTenant {
$delegatedAdminRelationships | ForEach-Object {
try {
$terminate = (New-GraphPostRequest -type 'POST' -Uri "https://graph.microsoft.com/v1.0/tenantRelationships/delegatedAdminRelationships/$($_.id)/requests" -body '{"action":"terminate"}' -ContentType 'application/json' -tenantid $env:TenantID)
- $results.Add("Succesfully terminated GDAP relationship $($_.displayName) from tenant $TenantFilter")
+ $results.Add("Successfully terminated GDAP relationship $($_.displayName) from tenant $TenantFilter")
Write-LogMessage -user $ExecutingUser -API $APIName -message "GDAP Relationship $($_.displayName) has been terminated" -Sev "Info" -tenant $TenantFilter
} catch {
$($_.Exception.message)
@@ -160,7 +160,7 @@ Function Invoke-ExecOffboardTenant {
# Terminate contract relationship
try {
$terminate = (New-GraphPostRequest -type 'PATCH' -body '{ "relationshipToPartner": "none" }' -Uri "https://api.partnercenter.microsoft.com/v1/customers/$TenantFilter" -ContentType 'application/json' -scope 'https://api.partnercenter.microsoft.com/user_impersonation' -tenantid $env:TenantID)
- $results.Add('Succesfully terminated contract relationship')
+ $results.Add('Successfully terminated contract relationship')
Write-LogMessage -user $ExecutingUser -API $APIName -message "Contract relationship terminated" -Sev "Info" -tenant $TenantFilter
} catch {
#$results.Add("Failed to terminate contract relationship: $($_.Exception.message)")
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecUpdateSecureScore.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecUpdateSecureScore.ps1
index 26bc7332a928..237b5f3415e5 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecUpdateSecureScore.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecUpdateSecureScore.ps1
@@ -21,7 +21,7 @@ Function Invoke-ExecUpdateSecureScore {
}
try {
$GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/security/secureScoreControlProfiles/$($Request.body.ControlName)" -tenantid $Request.body.TenantFilter -type PATCH -Body $($Body | ConvertTo-Json -Compress)
- $Results = [pscustomobject]@{'Results' = "Succesfully set control to $($body.state) " }
+ $Results = [pscustomobject]@{'Results' = "Successfully set control to $($body.state) " }
} catch {
$Results = [pscustomobject]@{'Results' = "Failed to set Control to $($body.state) $($_.Exception.Message)" }
}
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1
index 9c46ceae1ec4..39dd529a4ceb 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1
@@ -19,8 +19,6 @@ Function Invoke-ListConditionalAccessPolicies {
param (
[Parameter()]
$ID,
-
- [Parameter(Mandatory = $true)]
$Locations
)
if ($id -eq 'All') {
@@ -39,8 +37,6 @@ Function Invoke-ListConditionalAccessPolicies {
param (
[Parameter()]
$ID,
-
- [Parameter(Mandatory = $true)]
$RoleDefinitions
)
if ($id -eq 'All') {
@@ -59,8 +55,6 @@ Function Invoke-ListConditionalAccessPolicies {
param (
[Parameter()]
$ID,
-
- [Parameter(Mandatory = $true)]
$Users
)
if ($id -eq 'All') {
@@ -78,8 +72,6 @@ Function Invoke-ListConditionalAccessPolicies {
param (
[Parameter()]
$ID,
-
- [Parameter(Mandatory = $true)]
$Groups
)
if ($id -eq 'All') {
@@ -98,8 +90,6 @@ Function Invoke-ListConditionalAccessPolicies {
param (
[Parameter()]
$ID,
-
- [Parameter(Mandatory = $true)]
$Applications
)
if ($id -eq 'All') {
diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddTenantAllowBlockList.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddTenantAllowBlockList.ps1
index 00c2cffc02e7..ff1464ea8e3b 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddTenantAllowBlockList.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddTenantAllowBlockList.ps1
@@ -14,40 +14,42 @@ Function Invoke-AddTenantAllowBlockList {
Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -message 'Accessed this API' -Sev 'Debug'
$blocklistobj = $Request.body
-
+ if ($Request.body.tenantId -eq 'AllTenants') { $Tenants = (Get-Tenants).defaultDomainName } else { $Tenants = @($Request.body.tenantId) }
# Write to the Azure Functions log stream.
Write-Host 'PowerShell HTTP trigger function processed a request.'
- try {
- $ExoRequest = @{
- tenantid = $Request.body.tenantid
- cmdlet = 'New-TenantAllowBlockListItems'
- cmdParams = @{
- Entries = [string[]]$blocklistobj.entries
- ListType = [string]$blocklistobj.listType
- Notes = [string]$blocklistobj.notes
- $blocklistobj.listMethod = [bool]$true
+ $Results = [System.Collections.Generic.List[string]]::new()
+ foreach ($Tenant in $Tenants) {
+ try {
+ $ExoRequest = @{
+ tenantid = $Tenant
+ cmdlet = 'New-TenantAllowBlockListItems'
+ cmdParams = @{
+ Entries = [string[]]$blocklistobj.entries
+ ListType = [string]$blocklistobj.listType
+ Notes = [string]$blocklistobj.notes
+ $blocklistobj.listMethod = [bool]$true
+ }
}
- }
- if ($blocklistobj.NoExpiration -eq $true) {
- $ExoRequest.cmdParams.NoExpiration = $true
- }
+ if ($blocklistobj.NoExpiration -eq $true) {
+ $ExoRequest.cmdParams.NoExpiration = $true
+ }
- New-ExoRequest @ExoRequest
+ New-ExoRequest @ExoRequest
- $result = "Successfully added $($blocklistobj.Entries) as type $($blocklistobj.ListType) to the $($blocklistobj.listMethod) list"
- Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -tenant $Request.body.tenantid -message $result -Sev 'Info'
- } catch {
- $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
- $result = "Failed to create blocklist. Error: $ErrorMessage"
- Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -tenant $Request.body.tenantid -message $result -Sev 'Error'
+ $results.add("Successfully added $($blocklistobj.Entries) as type $($blocklistobj.ListType) to the $($blocklistobj.listMethod) list for $tenant")
+ Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -tenant $Tenant -message $result -Sev 'Info'
+ } catch {
+ $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
+ $results.add("Failed to create blocklist. Error: $ErrorMessage")
+ Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -tenant $Tenant -message $result -Sev 'Error'
+ }
}
-
# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::OK
Body = @{
- 'Results' = $result
+ 'Results' = $results
'Request' = $ExoRequest
}
})
diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntuneTemplates.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntuneTemplates.ps1
index c94431612970..a11384cf8e85 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntuneTemplates.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntuneTemplates.ps1
@@ -31,7 +31,7 @@ Function Invoke-ListIntuneTemplates {
$Templates = (Get-CIPPAzDataTableEntity @Table -Filter $Filter).JSON | ConvertFrom-Json
if ($Request.query.View) {
$Templates = $Templates | ForEach-Object {
- $data = $_.RAWJson | ConvertFrom-Json
+ $data = $_.RAWJson | ConvertFrom-Json -Depth 100
$data | Add-Member -NotePropertyName 'displayName' -NotePropertyValue $_.Displayname -Force
$data | Add-Member -NotePropertyName 'description' -NotePropertyValue $_.Description -Force
$data | Add-Member -NotePropertyName 'Type' -NotePropertyValue $_.Type -Force
@@ -46,7 +46,7 @@ Function Invoke-ListIntuneTemplates {
# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::OK
- Body = ($Templates | ConvertTo-Json -Depth 10)
+ Body = ($Templates | ConvertTo-Json -Depth 100)
})
}
diff --git a/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 b/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1
index 4ce96415966d..0781e820a125 100644
--- a/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1
+++ b/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1
@@ -7,23 +7,71 @@ function Get-CIPPAzDataTableEntity {
$First,
$Skip,
$Sort,
- $Count
+ $Count
)
+
$Results = Get-AzDataTableEntity @PSBoundParameters
- $Results = $Results | ForEach-Object {
- $entity = $_
- if ($entity.SplitOverProps) {
- $splitInfo = $entity.SplitOverProps | ConvertFrom-Json
- $mergedData = -join ($splitInfo.SplitHeaders | ForEach-Object { $entity.$_ })
- $entity | Add-Member -NotePropertyName $splitInfo.OriginalHeader -NotePropertyValue $mergedData -Force
- $propsToRemove = $splitInfo.SplitHeaders + "SplitOverProps"
- $entity = $entity | Select-Object * -ExcludeProperty $propsToRemove
- $entity
+ $mergedResults = @{}
+
+ # First pass: Collect all parts and complete entities
+ foreach ($entity in $Results) {
+ if ($entity.OriginalEntityId) {
+ $entityId = $entity.OriginalEntityId
+ if (-not $mergedResults.ContainsKey($entityId)) {
+ $mergedResults[$entityId] = @{
+ Parts = New-Object 'System.Collections.ArrayList'
+ }
+ }
+ $mergedResults[$entityId]['Parts'].Add($entity) > $null
+ } else {
+ $mergedResults[$entity.RowKey] = @{
+ Entity = $entity
+ Parts = New-Object 'System.Collections.ArrayList'
+ }
+ }
+ }
+
+ # Second pass: Reassemble entities from parts
+ $finalResults = @()
+ foreach ($entityId in $mergedResults.Keys) {
+ $entityData = $mergedResults[$entityId]
+ if ($entityData.Parts.Count -gt 0) {
+ $fullEntity = [PSCustomObject]@{}
+ $parts = $entityData.Parts | Sort-Object PartIndex
+ foreach ($part in $parts) {
+ foreach ($key in $part.PSObject.Properties.Name) {
+ if ($key -notin @('OriginalEntityId', 'PartIndex', 'PartitionKey', 'RowKey')) {
+ if ($fullEntity.PSObject.Properties[$key]) {
+ $fullEntity | Add-Member -MemberType NoteProperty -Name $key -Value ($fullEntity.$key + $part.$key) -Force
+ } else {
+ $fullEntity | Add-Member -MemberType NoteProperty -Name $key -Value $part.$key
+ }
+ }
+ }
+ }
+ $fullEntity | Add-Member -MemberType NoteProperty -Name 'PartitionKey' -Value $parts[0].PartitionKey -Force
+ $fullEntity | Add-Member -MemberType NoteProperty -Name 'RowKey' -Value $entityId -Force
+ $finalResults = $finalResults + @($fullEntity)
+ } else {
+ $finalResults = $finalResults + @($entityData.Entity)
}
- else {
- $entity
+ }
+
+ # Third pass: Process split properties and remerge them
+ foreach ($entity in $finalResults) {
+ if ($entity.SplitOverProps) {
+ $splitInfoList = $entity.SplitOverProps | ConvertFrom-Json
+ foreach ($splitInfo in $splitInfoList) {
+ $mergedData = [string]::Join('', ($splitInfo.SplitHeaders | ForEach-Object { $entity.$_ }))
+ $entity | Add-Member -NotePropertyName $splitInfo.OriginalHeader -NotePropertyValue $mergedData -Force
+ $propsToRemove = $splitInfo.SplitHeaders
+ foreach ($prop in $propsToRemove) {
+ $entity.PSObject.Properties.Remove($prop)
+ }
+ }
+ $entity.PSObject.Properties.Remove('SplitOverProps')
}
}
-
- return $Results
+
+ return $finalResults
}
diff --git a/Modules/CIPPCore/Public/Get-CIPPBackup.ps1 b/Modules/CIPPCore/Public/Get-CIPPBackup.ps1
index e463202983a1..c172f40f1c90 100644
--- a/Modules/CIPPCore/Public/Get-CIPPBackup.ps1
+++ b/Modules/CIPPCore/Public/Get-CIPPBackup.ps1
@@ -4,6 +4,7 @@ function Get-CIPPBackup {
[string]$Type,
[string]$TenantFilter
)
+ Write-Host "Getting backup for $Type with TenantFilter $TenantFilter"
$Table = Get-CippTable -tablename "$($Type)Backup"
if ($TenantFilter) {
$Filter = "PartitionKey eq '$($Type)Backup' and TenantFilter eq '$($TenantFilter)'"
diff --git a/Modules/CIPPCore/Public/Get-CIPPSPOTenant.ps1 b/Modules/CIPPCore/Public/Get-CIPPSPOTenant.ps1
new file mode 100644
index 000000000000..80f6e83453aa
--- /dev/null
+++ b/Modules/CIPPCore/Public/Get-CIPPSPOTenant.ps1
@@ -0,0 +1,27 @@
+function Get-CIPPSPOTenant {
+ [CmdletBinding()]
+ Param(
+ [Parameter(Mandatory = $true)]
+ [string]$TenantFilter,
+ [string]$SharepointPrefix
+ )
+
+ if (!$SharepointPrefix) {
+ # get sharepoint admin site
+ $tenantName = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/root' -asApp $true -tenantid $TenantFilter).id.Split('.')[0]
+ } else {
+ $tenantName = $SharepointPrefix
+ }
+ $AdminUrl = "https://$($tenantName)-admin.sharepoint.com"
+
+ # Query tenant settings
+ $XML = @'
+
+'@
+ $AdditionalHeaders = @{
+ 'Accept' = 'application/json;odata=verbose'
+ }
+ $Results = New-GraphPostRequest -scope "$AdminURL/.default" -tenantid $TenantFilter -Uri "$AdminURL/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' -AddedHeaders $AdditionalHeaders
+
+ $Results | Select-Object -Last 1 *, @{n = 'SharepointPrefix'; e = { $tenantName } }, @{n = 'TenantFilter'; e = { $TenantFilter } }
+}
\ No newline at end of file
diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1
index 7c4eb8927b35..e5fe77f2e484 100644
--- a/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1
+++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1
@@ -88,6 +88,7 @@ function New-GraphGetRequest {
}
} until ([string]::IsNullOrEmpty($NextURL) -or $NextURL -is [object[]] -or ' ' -eq $NextURL)
$Tenant.LastGraphError = ''
+ $Tenant.GraphErrorCount = 0
Update-AzDataTableEntity @TenantsTable -Entity $Tenant
return $ReturnedData
} else {
diff --git a/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1
index 9087bd30deff..ba5ab6432fb8 100644
--- a/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1
+++ b/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1
@@ -72,6 +72,9 @@ function Invoke-CIPPOffboardingJob {
{ $_.'removeMobile' -eq 'true' } {
Remove-CIPPMobileDevice -userid $userid -username $Username -tenantFilter $Tenantfilter -ExecutingUser $ExecutingUser -APIName $APIName
}
+ { $_.'removeCalendarInvites' -eq 'true' } {
+ Remove-CIPPCalendarInvites -userid $userid -username $Username -tenantFilter $Tenantfilter -ExecutingUser $ExecutingUser -APIName $APIName
+ }
{ $_.'removePermissions' } {
if ($RunScheduled) {
Remove-CIPPMailboxPermissions -PermissionsLevel @('FullAccess', 'SendAs', 'SendOnBehalf') -userid 'AllUsers' -AccessUser $UserName -TenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $ExecutingUser
@@ -90,4 +93,4 @@ function Invoke-CIPPOffboardingJob {
}
return $Return
-}
\ No newline at end of file
+}
diff --git a/Modules/CIPPCore/Public/New-CIPPAPIConfig.ps1 b/Modules/CIPPCore/Public/New-CIPPAPIConfig.ps1
index 58231273000e..47d111209e0f 100644
--- a/Modules/CIPPCore/Public/New-CIPPAPIConfig.ps1
+++ b/Modules/CIPPCore/Public/New-CIPPAPIConfig.ps1
@@ -51,7 +51,7 @@ function New-CIPPAPIConfig {
Write-Host "writing to Azure"
$SetAPIAuth = New-GraphPOSTRequest -type "PUT" -uri "https://management.azure.com/subscriptions/$($subscription)/resourceGroups/$ENV:WEBSITE_RESOURCE_GROUP/providers/Microsoft.Web/sites/$ENV:WEBSITE_SITE_NAME/Config/authsettingsV2?api-version=2018-11-01" -scope "https://management.azure.com/.default" -NoAuthCheck $true -body $currentBody
$null = Set-AzKeyVaultSecret -VaultName $ENV:WEBSITE_DEPLOYMENT_ID -Name 'CIPPAPIAPP' -SecretValue (ConvertTo-SecureString -String $APIApp.AppID -AsPlainText -Force)
- Write-LogMessage -user $ExecutingUser -API $APINAME -tenant 'None '-message "Succesfully setup CIPP-API Access." -Sev "info"
+ Write-LogMessage -user $ExecutingUser -API $APINAME -tenant 'None '-message "Successfully setup CIPP-API Access." -Sev "info"
}
return @{
ApplicationID = $APIApp.AppId
diff --git a/Modules/CIPPCore/Public/New-CIPPApplicationCopy.ps1 b/Modules/CIPPCore/Public/New-CIPPApplicationCopy.ps1
new file mode 100644
index 000000000000..da8b584954f8
--- /dev/null
+++ b/Modules/CIPPCore/Public/New-CIPPApplicationCopy.ps1
@@ -0,0 +1,46 @@
+function New-CIPPApplicationCopy {
+ [CmdletBinding()]
+ param(
+ $App,
+ $Tenant
+ )
+ $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals?$top=999' -tenantid $env:TenantID -NoAuthCheck $true
+ try {
+ $ExistingApp = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/Applications(appId='$($app)')" -tenantid $ENV:tenantid -NoAuthCheck $true
+ $Type = 'Application'
+ } catch {
+ $ExistingApp = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals(appId='$($app)')/oauth2PermissionGrants" -tenantid $ENV:tenantid -NoAuthCheck $true
+ $ExistingAppRoleAssignments = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals(appId='$($app)')/appRoleAssignments" -tenantid $ENV:tenantid -NoAuthCheck $true
+ $Type = 'ServicePrincipal'
+ }
+ if (!$ExistingApp) {
+ Write-LogMessage -message "Failed to add $App to tenant. This app does not exist." -tenant $tenant -API 'Application Copy' -sev error
+ continue
+ }
+ if ($Type -eq 'Application') {
+ $DelegateResourceAccess = $Existingapp.requiredResourceAccess
+ $ApplicationResourceAccess = $Existingapp.requiredResourceAccess
+ $NoTranslateRequired = $false
+ } else {
+ $DelegateResourceAccess = $ExistingApp | Group-Object -Property resourceId | ForEach-Object {
+ [pscustomobject]@{ resourceAppId = ($CurrentInfo | Where-Object -Property id -EQ $_.Name).appId; resourceAccess = @($_.Group | ForEach-Object { [pscustomobject]@{ id = $_.scope; type = 'Scope' } } )
+ }
+ }
+ $ApplicationResourceAccess = $ExistingappRoleAssignments | Group-Object -Property ResourceId | ForEach-Object {
+ [pscustomobject]@{ resourceAppId = ($CurrentInfo | Where-Object -Property id -EQ $_.Name).appId; resourceAccess = @($_.Group | ForEach-Object { [pscustomobject]@{ id = $_.appRoleId; type = 'Role' } } )
+ }
+ }
+ $NoTranslateRequired = $true
+ }
+ $TenantInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals?$top=999' -tenantid $Tenant -NoAuthCheck $true
+
+ if ($App -Notin $TenantInfo.appId) {
+ $PostResults = New-GraphPostRequest 'https://graph.microsoft.com/beta/servicePrincipals' -type POST -tenantid $Tenant -body "{ `"appId`": `"$($App)`" }"
+ Write-LogMessage -message "Added $App as a service principal" -tenant $tenant -API 'Application Copy' -sev Info
+ }
+ Add-CIPPApplicationPermission -RequiredResourceAccess $ApplicationResourceAccess -ApplicationId $App -Tenantfilter $Tenant
+ Add-CIPPDelegatedPermission -RequiredResourceAccess $DelegateResourceAccess -ApplicationId $App -Tenantfilter $Tenant -NoTranslateRequired $NoTranslateRequired
+ Write-LogMessage -message "Added permissions to $app" -tenant $tenant -API 'Application Copy' -sev Info
+
+ return $Results
+}
diff --git a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 b/Modules/CIPPCore/Public/New-CIPPBackup.ps1
index 477ea7c2e690..b99530c6de79 100644
--- a/Modules/CIPPCore/Public/New-CIPPBackup.ps1
+++ b/Modules/CIPPCore/Public/New-CIPPBackup.ps1
@@ -4,13 +4,14 @@ function New-CIPPBackup {
$backupType,
$StorageOutput = 'default',
$TenantFilter,
+ $ScheduledBackupValues,
$APIName = 'CIPP Backup',
$ExecutingUser
)
$BackupData = switch ($backupType) {
#If backup type is CIPP, create CIPP backup.
- 'CIPP' {
+ 'CIPP' {
try {
$BackupTables = @(
'bpa'
@@ -26,7 +27,7 @@ function New-CIPPBackup {
Get-CIPPAzDataTableEntity @Table | Select-Object *, @{l = 'table'; e = { $CSVTable } }
}
Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Created backup' -Sev 'Debug'
- $CSVfile
+ $CSVfile
$RowKey = 'CIPPBackup' + '_' + (Get-Date).ToString('yyyy-MM-dd-HHmm')
$entity = [PSCustomObject]@{
PartitionKey = 'CIPPBackup'
@@ -42,7 +43,7 @@ function New-CIPPBackup {
Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup for CIPP: $($_.Exception.Message)" -Sev 'Error'
[pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" }
}
-
+
} catch {
Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup: $($_.Exception.Message)" -Sev 'Error'
[pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" }
@@ -50,36 +51,28 @@ function New-CIPPBackup {
}
#If Backup type is ConditionalAccess, create Conditional Access backup.
- 'ConditionalAccess' {
- $ConditionalAccessPolicyOutput = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $tenantfilter
- $AllNamedLocations = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations' -tenantid $tenantfilter
- switch ($StorageOutput) {
- 'default' {
- [PSCustomObject]@{
- ConditionalAccessPolicies = $ConditionalAccessPolicyOutput
- NamedLocations = $AllNamedLocations
- }
- }
- 'table' {
- #Store output in tablestorage for Recovery
- $RowKey = $TenantFilter + '_' + (Get-Date).ToString('yyyy-MM-dd-HHmm')
- $entity = [PSCustomObject]@{
- PartitionKey = 'ConditionalAccessBackup'
- RowKey = $RowKey
- TenantFilter = $TenantFilter
- Policies = [string]($ConditionalAccessPolicyOutput | ConvertTo-Json -Compress -Depth 10)
- NamedLocations = [string]($AllNamedLocations | ConvertTo-Json -Compress -Depth 10)
- }
- $Table = Get-CippTable -tablename 'ConditionalAccessBackup'
- try {
- $Result = Add-CIPPAzDataTableEntity @Table -entity $entity -Force
- Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Created backup for Conditional Access Policies' -Sev 'Debug'
- $Result
- } catch {
- Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup for Conditional Access Policies: $($_.Exception.Message)" -Sev 'Error'
- [pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" }
- }
- }
+ 'Scheduled' {
+ #Do a sub switch here based on the ScheduledBackupValues?
+ #Store output in tablestorage for Recovery
+ $RowKey = $TenantFilter + '_' + (Get-Date).ToString('yyyy-MM-dd-HHmm')
+ $entity = @{
+ PartitionKey = 'ScheduledBackup'
+ RowKey = $RowKey
+ TenantFilter = $TenantFilter
+ }
+ Write-Host "Scheduled backup value psproperties: $(([pscustomobject]$ScheduledBackupValues).psobject.Properties)"
+ foreach ($ScheduledBackup in ([pscustomobject]$ScheduledBackupValues).psobject.Properties.Name) {
+ $BackupResult = New-CIPPBackupTask -Task $ScheduledBackup -TenantFilter $TenantFilter | ConvertTo-Json -Depth 100 -Compress | Out-String
+ $entity[$ScheduledBackup] = "$BackupResult"
+ }
+ $Table = Get-CippTable -tablename 'ScheduledBackup'
+ try {
+ $Result = Add-CIPPAzDataTableEntity @Table -entity $entity -Force
+ Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Created backup' -Sev 'Debug'
+ $Result
+ } catch {
+ Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup for Conditional Access Policies: $($_.Exception.Message)" -Sev 'Error'
+ [pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" }
}
}
diff --git a/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1
new file mode 100644
index 000000000000..cbd216411068
--- /dev/null
+++ b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1
@@ -0,0 +1,76 @@
+function New-CIPPBackupTask {
+ [CmdletBinding()]
+ param (
+ $Task,
+ $TenantFilter
+ )
+
+ $BackupData = switch ($Task) {
+ 'users' {
+ Write-Host "Backup users for $TenantFilter"
+ New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $TenantFilter
+ }
+ 'groups' {
+ Write-Host "Backup groups for $TenantFilter"
+ New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $TenantFilter
+ }
+ 'ca' {
+ Write-Host "Backup Conditional Access Policies for $TenantFilter"
+ New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter
+ }
+ 'namedlocations' {
+ Write-Host "Backup Named Locations for $TenantFilter"
+ New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/namedLocations?$top=999' -tenantid $TenantFilter
+ }
+ 'authstrengths' {
+ Write-Host "Backup Authentication Strength Policies for $TenantFilter"
+ New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/authenticationStrength/policies' -tenantid $TenantFilter
+ }
+ 'intuneconfig' {
+ $GraphURLS = @("https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations?`$select=id,displayName,lastModifiedDateTime,roleScopeTagIds,microsoft.graph.unsupportedDeviceConfiguration/originalEntityTypeName&`$expand=assignments&top=1000"
+ 'https://graph.microsoft.com/beta/deviceManagement/windowsDriverUpdateProfiles'
+ "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations?`$expand=assignments&top=999"
+ "https://graph.microsoft.com/beta/deviceAppManagement/mobileAppConfigurations?`$expand=assignments&`$filter=microsoft.graph.androidManagedStoreAppConfiguration/appSupportsOemConfig%20eq%20true"
+ 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies'
+ )
+
+ $GraphURLS | ForEach-Object {
+ $URLName = (($_).split('?') | Select-Object -First 1) -replace 'https://graph.microsoft.com/beta/deviceManagement/', ''
+ New-GraphGetRequest -uri "$($_)" -tenantid $TenantFilter
+ } | ForEach-Object {
+ New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName $URLName -ID $_.ID
+ }
+ }
+ 'intunecompliance' {
+ New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies?$top=999' -tenantid $TenantFilter | ForEach-Object {
+ New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName 'deviceCompliancePolicies' -ID $_.ID
+ }
+ }
+
+ 'intuneprotection' {
+ New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceAppManagement/managedAppPolicies?$top=999' -tenantid $TenantFilter | ForEach-Object {
+ New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName 'managedAppPolicies' -ID $_.ID
+ }
+ }
+
+ 'CippWebhookAlerts' {
+ Write-Host "Backup Webhook Alerts for $TenantFilter"
+ $WebhookTable = Get-CIPPTable -TableName 'WebhookRules'
+ Get-CIPPAzDataTableEntity @WebhookTable | Where-Object { $TenantFilter -in ($_.Tenants | ConvertFrom-Json).fullvalue.defaultDomainName }
+ }
+ 'CippScriptedAlerts' {
+ Write-Host "Backup Scripted Alerts for $TenantFilter"
+ $ScheduledTasks = Get-CIPPTable -TableName 'ScheduledTasks'
+ Get-CIPPAzDataTableEntity @ScheduledTasks | Where-Object { $_.hidden -eq $true -and $_.command -like 'Get-CippAlert*' -and $TenantFilter -in $_.Tenant }
+ }
+ 'CippStandards' {
+ Write-Host "Backup Standards for $TenantFilter"
+ $Table = Get-CippTable -tablename 'standards'
+ $Filter = "PartitionKey eq 'standards' and RowKey eq '$($TenantFilter)'"
+ (Get-CIPPAzDataTableEntity @Table -Filter $Filter)
+ }
+
+ }
+ return $BackupData
+}
+
diff --git a/Modules/CIPPCore/Public/New-CIPPIntuneTemplate.ps1 b/Modules/CIPPCore/Public/New-CIPPIntuneTemplate.ps1
new file mode 100644
index 000000000000..0707b9824400
--- /dev/null
+++ b/Modules/CIPPCore/Public/New-CIPPIntuneTemplate.ps1
@@ -0,0 +1,83 @@
+function New-CIPPIntuneTemplate {
+ param(
+ $urlname,
+ $id,
+ $TenantFilter,
+ $ActionResults,
+ $CIPPURL
+ )
+ switch ($URLName) {
+ 'deviceCompliancePolicies' {
+ $Type = 'deviceCompliancePolicies'
+ $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)/$($ID)?`$expand=scheduledActionsForRule(`$expand=scheduledActionConfigurations)" -tenantid $tenantfilter
+ $DisplayName = $Template.displayName
+ $TemplateJson = ConvertTo-Json -InputObject $Template -Depth 100 -Compress
+ }
+ 'managedAppPolicies' {
+ $Type = 'AppProtection'
+ $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$($urlname)('$($ID)')" -tenantid $tenantfilter
+ $DisplayName = $Template.displayName
+ $TemplateJson = ConvertTo-Json -InputObject $Template -Depth 100 -Compress
+ }
+ 'configurationPolicies' {
+ $Type = 'Catalog'
+ $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)('$($ID)')?`$expand=settings" -tenantid $tenantfilter | Select-Object name, description, settings, platforms, technologies, templateReference
+ $TemplateJson = $Template | ConvertTo-Json -Depth 100
+ $DisplayName = $Template.name
+
+ }
+ 'windowsDriverUpdateProfiles' {
+ $Type = 'windowsDriverUpdateProfiles'
+ $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)/$($ID)" -tenantid $tenantfilter | Select-Object * -ExcludeProperty id, lastModifiedDateTime, '@odata.context', 'ScopeTagIds', 'supportsScopeTags', 'createdDateTime'
+ $DisplayName = $Template.displayName
+ $TemplateJson = ConvertTo-Json -InputObject $Template -Depth 100 -Compress
+ }
+ 'deviceConfigurations' {
+ $Type = 'Device'
+ $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)/$($ID)" -tenantid $tenantfilter | Select-Object * -ExcludeProperty id, lastModifiedDateTime, '@odata.context', 'ScopeTagIds', 'supportsScopeTags', 'createdDateTime'
+ $DisplayName = $Template.displayName
+ $TemplateJson = ConvertTo-Json -InputObject $Template -Depth 100 -Compress
+ }
+ 'groupPolicyConfigurations' {
+ $Type = 'Admin'
+ $Template = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)('$($ID)')" -tenantid $tenantfilter
+ $DisplayName = $Template.displayName
+ $TemplateJsonItems = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)('$($ID)')/definitionValues?`$expand=definition" -tenantid $tenantfilter
+ $TemplateJsonSource = foreach ($TemplateJsonItem in $TemplateJsonItems) {
+ $presentationValues = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)('$($ID)')/definitionValues('$($TemplateJsonItem.id)')/presentationValues?`$expand=presentation" -tenantid $tenantfilter | ForEach-Object {
+ $obj = $_
+ if ($obj.id) {
+ $PresObj = @{
+ id = $obj.id
+ 'presentation@odata.bind' = "https://graph.microsoft.com/beta/deviceManagement/groupPolicyDefinitions('$($TemplateJsonItem.definition.id)')/presentations('$($obj.presentation.id)')"
+ }
+ if ($obj.values) { $PresObj['values'] = $obj.values }
+ if ($obj.value) { $PresObj['value'] = $obj.value }
+ if ($obj.'@odata.type') { $PresObj['@odata.type'] = $obj.'@odata.type' }
+ [pscustomobject]$PresObj
+ }
+ }
+ [PSCustomObject]@{
+ 'definition@odata.bind' = "https://graph.microsoft.com/beta/deviceManagement/groupPolicyDefinitions('$($TemplateJsonItem.definition.id)')"
+ enabled = $TemplateJsonItem.enabled
+ presentationValues = @($presentationValues)
+ }
+ }
+ $inputvar = [pscustomobject]@{
+ added = @($TemplateJsonSource)
+ updated = @()
+ deletedIds = @()
+
+ }
+
+
+ $TemplateJson = (ConvertTo-Json -InputObject $inputvar -Depth 100 -Compress)
+ }
+ }
+ return [PSCustomObject]@{
+ TemplateJson = $TemplateJson
+ DisplayName = $DisplayName
+ Description = $Template.description
+ Type = $Type
+ }
+}
diff --git a/Modules/CIPPCore/Public/New-CIPPRestore.ps1 b/Modules/CIPPCore/Public/New-CIPPRestore.ps1
new file mode 100644
index 000000000000..399377a02451
--- /dev/null
+++ b/Modules/CIPPCore/Public/New-CIPPRestore.ps1
@@ -0,0 +1,17 @@
+function New-CIPPRestore {
+ [CmdletBinding()]
+ param (
+ $TenantFilter,
+ $RestoreValues,
+ $APIName = 'CIPP Restore',
+ $ExecutingUser
+ )
+
+ Write-Host "Scheduled Restore psproperties: $(([pscustomobject]$ScheduledBackupValues).psobject.Properties)"
+ Write-LogMessage -user $ExecutingUser -API $APINAME -message 'Restored backup' -Sev 'Debug'
+ $RestoreData = foreach ($ScheduledBackup in ([pscustomobject]$ScheduledBackupValues).psobject.Properties.Name | Where-Object { $_ -notin 'email', 'webhook', 'psa', 'backup', 'overwrite' }) {
+ New-CIPPRestoreTask -Task $ScheduledBackup -TenantFilter $TenantFilter -backup $RestoreValues.backup.value -overwrite $RestoreValues.overwrite
+ }
+ return $RestoreData
+}
+
diff --git a/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 b/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1
new file mode 100644
index 000000000000..64b9c4701cd9
--- /dev/null
+++ b/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1
@@ -0,0 +1,127 @@
+function New-CIPPRestoreTask {
+ [CmdletBinding()]
+ param (
+ $Task,
+ $TenantFilter,
+ $backup,
+ $overwrite
+ )
+ $Table = Get-CippTable -tablename 'ScheduledBackup'
+ $BackupData = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$backup'"
+ $RestoreData = switch ($Task) {
+ 'users' {
+ Write-Host "Restore users for $TenantFilter"
+ $currentUsers = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $TenantFilter
+ $BackupUsers | ForEach-Object {
+ try {
+ if ($overwrite) {
+ $currentUsers | Where-Object { $_.id -eq $_.id } | ForEach-Object {
+ New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/users/$($_.id)" -tenantid $TenantFilter -body $_ -type PATCH
+ Write-LogMessage -message "Restored $($_.userprincipalname) from backup" -Sev 'info'
+ "Restored $($_.userprincipalname) from backup"
+ }
+ } else {
+ if ($currentUsers.id -notin $_.id) {
+ New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/users' -tenantid $TenantFilter -body $_ -type POST
+ Write-LogMessage -message "Restored $($_.userprincipalname) from backup" -Sev 'info'
+ "Restored $($_.userprincipalname) from backup"
+
+ } else {
+ Write-LogMessage -message "User $($_.userPrincipalName) already exists in tenant $TenantFilter and overwrite is disabled" -Sev 'info'
+ "User $($_.userPrincipalName) already exists in tenant $TenantFilter and overwrite is disabled"
+ }
+ }
+ } catch {
+ "Could not restore user $($_.userPrincipalName): $($_.Exception.Message) "
+ Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore user $($_.userPrincipalName): $($_.Exception.Message) " -Sev 'error'
+ }
+ }
+ }
+ 'groups' {
+ Write-Host "Restore groups for $TenantFilter"
+ $Groups = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $TenantFilter
+ $BackupGroups | ForEach-Object {
+ try {
+ if ($overwrite) {
+ $currentUsers | Where-Object { $_.id -eq $_.id } | ForEach-Object {
+ New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/groups/$($_.id)" -tenantid $TenantFilter -body $_ -type PATCH
+ Write-LogMessage -message "Restored $($_.userprincipalname) from backup" -Sev 'info'
+ "Restored group $($_.displayName) from backup"
+ }
+ } else {
+ if ($currentUsers.id -notin $_.id) {
+ New-GraphPOSTRequest -uri 'https://graph.microsoft.com/groups/' -tenantid $TenantFilter -body $_ -type POST
+ Write-LogMessage -message "Restored $($_.userprincipalname) from backup" -Sev 'info'
+ "Restored group $($_.displayName) from backup"
+
+ } else {
+ Write-LogMessage -message "group $($_.group) already exists in tenant $TenantFilter and overwrite is disabled" -Sev 'info'
+ "group $($_.displayName) already exists in tenant $TenantFilter and overwrite is disabled"
+ }
+ }
+ } catch {
+ "Could not restore user $($_.userPrincipalName): $($_.Exception.Message) "
+ Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore user $($_.userPrincipalName): $($_.Exception.Message) " -Sev 'error'
+ }
+ }
+ }
+ 'ca' {
+ Write-Host "Backup Conditional Access Policies for $TenantFilter"
+ New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter
+ }
+ 'namedlocations' {
+ Write-Host "Backup Named Locations for $TenantFilter"
+ New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/namedLocations?$top=999' -tenantid $TenantFilter
+ }
+ 'authstrengths' {
+ Write-Host "Backup Authentication Strength Policies for $TenantFilter"
+ New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/authenticationStrength/policies' -tenantid $TenantFilter
+ }
+ 'intuneconfig' {
+ $GraphURLS = @("https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations?`$select=id,displayName,lastModifiedDateTime,roleScopeTagIds,microsoft.graph.unsupportedDeviceConfiguration/originalEntityTypeName&`$expand=assignments&top=1000"
+ 'https://graph.microsoft.com/beta/deviceManagement/windowsDriverUpdateProfiles'
+ "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations?`$expand=assignments&top=999"
+ "https://graph.microsoft.com/beta/deviceAppManagement/mobileAppConfigurations?`$expand=assignments&`$filter=microsoft.graph.androidManagedStoreAppConfiguration/appSupportsOemConfig%20eq%20true"
+ 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies'
+ )
+
+ $GraphURLS | ForEach-Object {
+ $URLName = (($_).split('?') | Select-Object -First 1) -replace 'https://graph.microsoft.com/beta/deviceManagement/', ''
+ New-GraphGetRequest -uri "$($_)" -tenantid $TenantFilter
+ } | ForEach-Object {
+ New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName $URLName -ID $_.ID
+ }
+ }
+ 'intunecompliance' {
+ New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies?$top=999' -tenantid $TenantFilter | ForEach-Object {
+ New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName 'deviceCompliancePolicies' -ID $_.ID
+ }
+ }
+
+ 'intuneprotection' {
+ New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceAppManagement/managedAppPolicies?$top=999' -tenantid $TenantFilter | ForEach-Object {
+ New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName 'managedAppPolicies' -ID $_.ID
+ }
+ }
+
+ 'CippWebhookAlerts' {
+ Write-Host "Backup Webhook Alerts for $TenantFilter"
+ $WebhookTable = Get-CIPPTable -TableName 'WebhookRules'
+ Get-CIPPAzDataTableEntity @WebhookTable | Where-Object { $TenantFilter -in ($_.Tenants | ConvertFrom-Json).fullvalue.defaultDomainName }
+ }
+ 'CippScriptedAlerts' {
+ Write-Host "Backup Scripted Alerts for $TenantFilter"
+ $ScheduledTasks = Get-CIPPTable -TableName 'ScheduledTasks'
+ Get-CIPPAzDataTableEntity @ScheduledTasks | Where-Object { $_.hidden -eq $true -and $_.command -like 'Get-CippAlert*' -and $TenantFilter -in $_.Tenant }
+ }
+ 'CippStandards' {
+ Write-Host "Backup Standards for $TenantFilter"
+ $Table = Get-CippTable -tablename 'standards'
+ $Filter = "PartitionKey eq 'standards' and RowKey eq '$($TenantFilter)'"
+ (Get-CIPPAzDataTableEntity @Table -Filter $Filter)
+ }
+
+ }
+ return $RestoreData
+}
+
diff --git a/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 b/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1
new file mode 100644
index 000000000000..ccf2e8b81b22
--- /dev/null
+++ b/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1
@@ -0,0 +1,145 @@
+function New-CIPPSharepointSite {
+ <#
+ .SYNOPSIS
+ Create a new SharePoint site
+
+ .DESCRIPTION
+ Create a new SharePoint site using the Modern REST API
+
+ .PARAMETER SiteName
+ The name of the site
+
+ .PARAMETER SiteDescription
+ The description of the site
+
+ .PARAMETER SiteOwner
+ The username of the site owner
+
+ .PARAMETER TemplateName
+ The template to use for the site. Default is Communication
+
+ .PARAMETER SiteDesign
+ The design to use for the site. Default is Topic
+
+ .PARAMETER WebTemplateExtensionId
+ The web template extension ID to use
+
+ .PARAMETER SensitivityLabel
+ The Purview sensitivity label to apply to the site
+
+ .PARAMETER TenantFilter
+ The tenant associated with the site
+
+ #>
+ [CmdletBinding(SupportsShouldProcess = $true)]
+ Param(
+ [Parameter(Mandatory = $true)]
+ [string]$SiteName,
+
+ [Parameter(Mandatory = $true)]
+ [string]$SiteDescription,
+
+ [Parameter(Mandatory = $true)]
+ [string]$SiteOwner,
+
+ [Parameter(Mandatory = $false)]
+ [ValidateSet('Communication', 'Team')]
+ [string]$TemplateName = 'Communication',
+
+ [Parameter(Mandatory = $false)]
+ [ValidateSet('Topic', 'Showcase', 'Blank', 'Custom')]
+ [string]$SiteDesign = 'Showcase',
+
+ [Parameter(Mandatory = $false)]
+ [ValidatePattern('(\{|\()?[A-Za-z0-9]{4}([A-Za-z0-9]{4}\-?){4}[A-Za-z0-9]{12}(\}|\()?')]
+ [string]$WebTemplateExtensionId,
+
+ [Parameter(Mandatory = $false)]
+ [ValidatePattern('(\{|\()?[A-Za-z0-9]{4}([A-Za-z0-9]{4}\-?){4}[A-Za-z0-9]{12}(\}|\()?')]
+ [string]$SensitivityLabel,
+
+ [string]$Classification,
+
+ [Parameter(Mandatory = $true)]
+ [string]$TenantFilter
+ )
+ $tenantName = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/root' -asApp $true -tenantid $TenantFilter).id.Split('.')[0]
+ $AdminUrl = "https://$($tenantName)-admin.sharepoint.com"
+ $SitePath = $SiteName -replace ' ' -replace '[^A-Za-z0-9-]'
+ $SiteUrl = "https://$tenantName.sharepoint.com/sites/$SitePath"
+
+
+
+
+ switch ($TemplateName) {
+ 'Communication' {
+ $WebTemplate = 'SITEPAGEPUBLISHING#0'
+ }
+ 'Team' {
+ $WebTemplate = 'STS#0'
+ }
+ }
+
+ $WebTemplateExtensionId = '00000000-0000-0000-0000-000000000000'
+ $DefaultSiteDesignIds = @( '96c933ac-3698-44c7-9f4a-5fd17d71af9e', '6142d2a0-63a5-4ba0-aede-d9fefca2c767', 'f6cc5403-0d63-442e-96c0-285923709ffc')
+
+ switch ($SiteDesign) {
+ 'Topic' {
+ $SiteDesignId = '96c933ac-3698-44c7-9f4a-5fd17d71af9e'
+ }
+ 'Showcase' {
+ $SiteDesignId = '6142d2a0-63a5-4ba0-aede-d9fefca2c767'
+ }
+ 'Blank' {
+ $SiteDesignId = 'f6cc5403-0d63-442e-96c0-285923709ffc'
+ }
+ 'Custom' {
+ if ($WebTemplateExtensionId -match '^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$') {
+ if ($WebTemplateExtensionId -notin $DefaultSiteDesignIds) {
+ $WebTemplateExtensionId = $SiteDesign
+ $SiteDesignId = '00000000-0000-0000-0000-000000000000'
+ } else {
+ $SiteDesignId = $WebTemplateExtensionId
+ }
+ } else {
+ $SiteDesignId = '96c933ac-3698-44c7-9f4a-5fd17d71af9e'
+ }
+ }
+ }
+
+ # Create the request body
+ $Request = @{
+ Title = $SiteName
+ Url = $SiteUrl
+ Lcid = 1033
+ ShareByEmailEnabled = $false
+ Description = $SiteDescription
+ WebTemplate = $WebTemplate
+ SiteDesignId = $SiteDesignId
+ Owner = $SiteOwner
+ WebTemplateExtensionId = $WebTemplateExtensionId
+ }
+
+ # Set the sensitivity label if provided
+ if ($SensitivityLabel) {
+ $Request.SensitivityLabel = $SensitivityLabel
+ }
+ if ($Classification) {
+ $Request.Classification = $Classification
+ }
+
+ Write-Verbose ($Request | ConvertTo-Json -Compress -Depth 10)
+
+ $body = @{
+ request = $Request
+ }
+
+ # Create the site
+ if ($PSCmdlet.ShouldProcess($SiteName, 'Create new SharePoint site')) {
+ $AddedHeaders = @{
+ 'accept' = 'application/json;odata.metadata=none'
+ 'odata-version' = '4.0'
+ }
+ New-GraphPostRequest -scope "$AdminUrl/.default" -uri "$AdminUrl/_api/SPSiteManager/create" -Body ($body | ConvertTo-Json -Compress -Depth 10) -tenantid $TenantFilter -ContentType 'application/json' -AddedHeaders $AddedHeaders
+ }
+}
diff --git a/Modules/CIPPCore/Public/PermissionsTranslator.json b/Modules/CIPPCore/Public/PermissionsTranslator.json
index aa7947e9374d..204fe8532c4f 100644
--- a/Modules/CIPPCore/Public/PermissionsTranslator.json
+++ b/Modules/CIPPCore/Public/PermissionsTranslator.json
@@ -1004,8 +1004,15 @@
"description": "Allows the app to create, read, update, and delete events of all calendars without a signed-in user.",
"displayName": "Read and write calendars in all mailboxes",
"id": "ef54d2bf-783f-4e0f-bca1-3210c0444d99",
- "origin": "Application",
- "value": "Calendars.ReadWrite"
+ "origin": "Application (Office 365 Exchange Online)",
+ "value": "Calendars.ReadWrite.All"
+ },
+ {
+ "description": "Allows the app to create, read, update, and delete user's mailbox settings without a signed-in user. Does not include permission to send mail.",
+ "displayName": "Read and write all user mailbox settings",
+ "id": "f9156939-25cd-4ba8-abfe-7fabcf003749",
+ "origin": "Application (Office 365 Exchange Online)",
+ "value": "Mailbox.Settings.ReadWrite"
},
{
"description": "Allows the app to read your organization's user flows, without a signed-in user.",
@@ -5286,6 +5293,24 @@
"userConsentDisplayName": "Read Threat and Vulnerability Management vulnerability information",
"value": "Exchange.Manage"
},
+ {
+ "description": "Allows the app to create, read, update and delete events in all calendars in the organization user has permissions to access. This includes delegate and shared calendars",
+ "displayName": "Read and write user and shared calendars",
+ "id": "bbd1ca91-75e0-4814-ad94-9c5dbbae3415",
+ "Origin": "Delegated (Office 365 Exchange Online)",
+ "userConsentDescription": "Allows the app to read, update, create and delete events in all calendars in your organization you have permissions to access. This includes delegate and shared calendars",
+ "userConsentDisplayName": "Read and write to your and shared calendars",
+ "value": "Calendars.ReadWrite.All"
+ },
+ {
+ "description": "Allows the app to create, read, update, and delete user's mailbox settings. Does not include permission to send mail.",
+ "displayName": "Read and write user mailbox settings",
+ "id": "2e83d72d-8895-4b66-9eea-abb43449ab8b",
+ "Origin": "Delegated (Office 365 Exchange Online)",
+ "userConsentDescription": "Allows the app to read, update, create, and delete your mailbox settings.",
+ "userConsentDisplayName": "Read and write to your mailbox settings",
+ "value": "MailboxSettings.ReadWrite"
+ },
{
"description": "Allows the app to have full control of all site collections on behalf of the signed-in user.",
"displayName": "Manage Sharepoint Online",
@@ -5295,15 +5320,6 @@
"userConsentDisplayName": "Allows the app to have full control of all site collections on your behalf.",
"value": "AllSites.FullControl"
},
- {
- "description": "Required for Request-SPOPeronalSite",
- "displayName": "Manage sharepoint profiles",
- "id": "ec4fc4c8-872e-442b-a2a2-d095575807b3",
- "Origin": "Delegated (Office 365 SharePoint Online)",
- "userConsentDescription": "",
- "userConsentDisplayName": "Manage sharepoint profiles",
- "value": "AllProfiles.Manage"
- },
{
"description": "Allows to read the LAPs passwords.",
"displayName": "Manage LAPs passwords",
diff --git a/Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1 b/Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1
new file mode 100644
index 000000000000..22e57c2acff8
--- /dev/null
+++ b/Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1
@@ -0,0 +1,21 @@
+function Remove-CIPPCalendarInvites {
+ [CmdletBinding()]
+ param(
+ $userid,
+ $tenantFilter,
+ $username,
+ $APIName = 'Remove Calendar Invites',
+ $ExecutingUser
+ )
+
+ try {
+
+ New-ExoRequest -tenantid $tenantFilter -cmdlet 'Remove-CalendarEvents' -Anchor $username -cmdParams @{Identity = $username; QueryWindowInDays = 730 ; CancelOrganizedMeetings = $true ; Confirm = $false}
+ Write-LogMessage -user $ExecutingUser -API $APIName -message "Cancelled all calendar invites for $($username)" -Sev 'Info' -tenant $tenantFilter
+ "Cancelled all calendar invites for $($username)"
+
+ } catch {
+ Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not cancel calendar invites for $($username): $($_.Exception.Message)" -Sev 'Error' -tenant $tenantFilter
+ return "Could not cancel calendar invites for $($username). Error: $($_.Exception.Message)"
+ }
+}
diff --git a/Modules/CIPPCore/Public/SAMManifest.json b/Modules/CIPPCore/Public/SAMManifest.json
index d545a87d25a5..50a03c019af1 100644
--- a/Modules/CIPPCore/Public/SAMManifest.json
+++ b/Modules/CIPPCore/Public/SAMManifest.json
@@ -11,6 +11,12 @@
]
},
"requiredResourceAccess": [
+ {
+ "resourceAppId": "aeb86249-8ea3-49e2-900b-54cc8e308f85",
+ "resourceAccess": [
+ { "id": "fc946a4f-bc4d-413b-a090-b2c86113ec4f", "type": "Scope" }
+ ]
+ },
{
"resourceAppId": "fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd",
"resourceAccess": [
@@ -145,7 +151,8 @@
{ "id": "b6890674-9dd5-4e42-bb15-5af07f541ae1", "type": "Role" },
{ "id": "9e4862a5-b68f-479e-848a-4e07e25c9916", "type": "Scope" },
{ "id": "bb6f654c-d7fd-4ae3-85c3-fc380934f515", "type": "Scope" },
- { "id": "e0a7cdbb-08b0-4697-8264-0069786e9674", "type": "Scope" }
+ { "id": "e0a7cdbb-08b0-4697-8264-0069786e9674", "type": "Scope" },
+ { "id": "19da66cb-0fb0-4390-b071-ebc76a349482", "type": "Role" }
]
},
{
@@ -159,14 +166,17 @@
"resourceAppId": "00000002-0000-0ff1-ce00-000000000000",
"resourceAccess": [
{ "id": "ab4f2b77-0b06-4fc1-a9de-02113fc2ab7c", "type": "Scope" },
- { "id": "dc50a0fb-09a3-484d-be87-e023b12c6440", "type": "Role" }
+ { "id": "bbd1ca91-75e0-4814-ad94-9c5dbbae3415", "type": "Scope" },
+ { "id": "2e83d72d-8895-4b66-9eea-abb43449ab8b", "type": "Scope" },
+ { "id": "dc50a0fb-09a3-484d-be87-e023b12c6440", "type": "Role" },
+ { "id": "ef54d2bf-783f-4e0f-bca1-3210c0444d99", "type": "Role" },
+ { "id": "f9156939-25cd-4ba8-abfe-7fabcf003749", "type": "Role" }
]
},
{
"resourceAppId": "00000003-0000-0ff1-ce00-000000000000",
"resourceAccess": [
- { "id": "56680e0d-d2a3-4ae1-80d8-3c4f2100e3d0", "type": "Scope" },
- { "id": "ec4fc4c8-872e-442b-a2a2-d095575807b3", "type": "Scope" }
+ { "id": "56680e0d-d2a3-4ae1-80d8-3c4f2100e3d0", "type": "Scope" }
]
},
{
diff --git a/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 b/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1
new file mode 100644
index 000000000000..ad6a9b115321
--- /dev/null
+++ b/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1
@@ -0,0 +1,92 @@
+function Set-CIPPSPOTenant {
+ <#
+ .SYNOPSIS
+ Set Sharepoint Tenant properties
+
+ .DESCRIPTION
+ Set Sharepoint Tenant properties via SPO API
+
+ .PARAMETER TenantFilter
+ Tenant to apply settings to
+
+ .PARAMETER Identity
+ Tenant Identity (Get from Get-CIPPSPOTenant)
+
+ .PARAMETER Properties
+ Hashtable of tenant properties to change
+
+ .PARAMETER SharepointPrefix
+ Prefix for the sharepoint tenant
+
+ .EXAMPLE
+ $Properties = @{
+ 'EnableAIPIntegration' = $true
+ }
+ Get-CippSPOTenant -TenantFilter 'contoso.onmicrosoft.com' | Set-CIPPSPOTenant -Properties $Properties
+
+ .FUNCTIONALITY
+ Internal
+
+ #>
+ [CmdletBinding(SupportsShouldProcess = $true)]
+ Param(
+ [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
+ [string]$TenantFilter,
+ [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
+ [Alias('_ObjectIdentity_')]
+ [string]$Identity,
+ [Parameter(Mandatory = $true)]
+ [hashtable]$Properties,
+ [Parameter(ValueFromPipelineByPropertyName = $true)]
+ [string]$SharepointPrefix
+ )
+
+ process {
+ if (!$SharepointPrefix) {
+ # get sharepoint admin site
+ $tenantName = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/root' -asApp $true -tenantid $TenantFilter).id.Split('.')[0]
+ } else {
+ $tenantName = $SharepointPrefix
+ }
+ $Identity = $Identity -replace "`n", '
'
+ $AdminUrl = "https://$($tenantName)-admin.sharepoint.com"
+ $AllowedTypes = @('Boolean', 'String', 'Int32')
+ $SetProperty = [System.Collections.Generic.List[string]]::new()
+ $x = 114
+ foreach ($Property in $Properties.Keys) {
+ # Get property type
+ $PropertyType = $Properties[$Property].GetType().Name
+ if ($PropertyType -in $AllowedTypes) {
+ if ($PropertyType -eq 'Boolean') {
+ $PropertyToSet = $Properties[$Property].ToString().ToLower()
+ } else {
+ $PropertyToSet = $Properties[$Property]
+ }
+ $xml = @"
+
+ $($PropertyToSet)
+
+"@
+ $SetProperty.Add($xml)
+ $x++
+ }
+ }
+
+ if (($SetProperty | Measure-Object).Count -eq 0) {
+ Write-Error 'No valid properties found'
+ return
+ }
+
+ # Query tenant settings
+ $XML = @"
+ $($SetProperty -join '')
+"@
+ $AdditionalHeaders = @{
+ 'Accept' = 'application/json;odata=verbose'
+ }
+
+ if ($PSCmdlet.ShouldProcess(($Properties.Keys -join ', '), 'Set Tenant Properties')) {
+ New-GraphPostRequest -scope "$AdminURL/.default" -tenantid $TenantFilter -Uri "$AdminURL/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' -AddedHeaders $AdditionalHeaders
+ }
+ }
+}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1
new file mode 100644
index 000000000000..c6c2509795ac
--- /dev/null
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1
@@ -0,0 +1,50 @@
+function Invoke-CIPPStandardAppDeploy {
+ <#
+ .FUNCTIONALITY
+ Internal
+ .APINAME
+ AppDeploy
+ .CAT
+ Entra Standards
+ .TAG
+ "lowimpact"
+ "CIS"
+ .HELPTEXT
+ Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access
+ .DOCSDESCRIPTION
+ Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access. This is a tenant wide setting and overrules any settings set on the site level
+ .ADDEDCOMPONENT
+ .LABEL
+ Disable Resharing by External Users
+ .IMPACT
+ High Impact
+ .POWERSHELLEQUIVALENT
+ Update-MgBetaAdminSharepointSetting
+ .RECOMMENDEDBY
+ "CIS"
+ .DOCSDESCRIPTION
+ Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access
+ .UPDATECOMMENTBLOCK
+ Run the Tools\Update-StandardsComments.ps1 script to update this comment block
+ #>
+
+ param($Tenant, $Settings)
+
+ If ($Settings.remediate -eq $true) {
+ $AppsToAdd = $Settings.appids -split ','
+ foreach ($App In $AppsToAdd) {
+ try {
+ New-CIPPApplicationCopy -App $App -Tenant $Tenant
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Added $App to $Tenant and update it's permissions" -sev Info
+ } catch {
+ $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to add app $App" -sev Error
+ }
+ }
+ }
+}
+
+
+
+
+
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1
index 147aa0fefe89..fa8b5cb537e2 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1
@@ -9,28 +9,87 @@ function Invoke-CIPPStandardDisableSelfServiceLicenses {
.TAG
"mediumimpact"
.HELPTEXT
- This standard currently does not function and can be safely disabled
+ This standard disables all self service licenses and enables all exclusions
.ADDEDCOMPONENT
.LABEL
Disable Self Service Licensing
.IMPACT
Medium Impact
.POWERSHELLEQUIVALENT
- Set-MsolCompanySettings -AllowAdHocSubscriptions $false
+ Update-MSCommerceProductPolicy -PolicyId AllowSelfServicePurchase -ProductId {productId} -Value "Disabled"
.RECOMMENDEDBY
.DOCSDESCRIPTION
- This standard currently does not function and can be safely disabled
+ This standard disables all self service licenses and enables all exclusions
.UPDATECOMMENTBLOCK
Run the Tools\Update-StandardsComments.ps1 script to update this comment block
#>
+ param($Tenant, $Settings)
+ #Write-LogMessage -API 'Standards' -tenant $tenant -message 'Self Service Licenses cannot be disabled' -sev Error
+ try {
+ $selfServiceItems = (New-GraphGETRequest -scope "aeb86249-8ea3-49e2-900b-54cc8e308f85/.default" -uri "https://licensing.m365.microsoft.com/v1.0/policies/AllowSelfServicePurchase/products" -tenantid $Tenant).items
+ #$selfServiceItems = (Invoke-RestMethod -Method GET -Uri "https://licensing.m365.microsoft.com/v1.0/policies/AllowSelfServicePurchase/products" -Headers $header).items
+ } catch {
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to retrieve self service products: $($_.Exception.Message)" -sev Error
+ throw "Failed to retrieve self service products: $($_.Exception.Message)"
+ }
+ if ($settings.remediate) {
+ if ($settings.exclusions -like "*;*") {
+ $exclusions = $settings.Exclusions -split(';')
+ } else {
+ $exclusions = $settings.Exclusions -split(',')
+ }
- param($Tenant, $Settings)
+ $selfServiceItems | ForEach-Object {
+ $body = $null
+
+ if ($_.policyValue -eq "Enabled" -AND ($_.productId -in $exclusions)) {
+ # Self service is enabled on product and productId is in exclusions, skip
+ }
+ if ($_.policyValue -eq "Disabled" -AND ($_.productId -in $exclusions)) {
+ # Self service is disabled on product and productId is in exclusions, enable
+ $body = '{ "policyValue": "Enabled" }'
+ }
+ if ($_.policyValue -eq "Enabled" -AND ($_.productId -notin $exclusions)) {
+ # Self service is enabled on product and productId is NOT in exclusions, disable
+ $body = '{ "policyValue": "Disabled" }'
+ }
+ if ($_.policyValue -eq "Disabled" -AND ($_.productId -notin $exclusions)) {
+ # Self service is disabled on product and productId is NOT in exclusions, skip
+ }
+
+ try {
+ if ($body) {
+ $product = $_
+ New-GraphPOSTRequest -scope "aeb86249-8ea3-49e2-900b-54cc8e308f85/.default" -uri "https://licensing.m365.microsoft.com/v1.0/policies/AllowSelfServicePurchase/products/$($product.productId)" -tenantid $Tenant -body $body -type PUT
+ }
+ } catch {
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set product status for $($product.productId) with body $($body) for reason: $($_.Exception.Message)" -sev Error
+ #Write-Error "Failed to disable product $($product.productName):$($_.Exception.Message)"
+ }
+ }
+
+ if (!$exclusions) {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message 'No exclusions set for self-service licenses, disabled all not excluded licenses for self-service.' -sev Info
+ } else {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Exclusions present for self-service licenses, disabled all not excluded licenses for self-service.' -sev Info
+ }
+ }
- Write-LogMessage -API 'Standards' -tenant $tenant -message 'Self Service Licenses cannot be disabled' -sev Error
+ if ($Settings.alert) {
+ $selfServiceItemsToAlert = $selfServiceItems | Where-Object { $_.policyValue -eq "Enabled"}
+ if (!$selfServiceItemsToAlert) {
+ Write-LogMessage -API 'Standards' -tenant $tenant -message 'All self-service licenses are disabled' -sev Info
+ } else {
+ Write-LogMessage -API 'Standards' -tenant $tenant -message 'One or more self-service licenses are enabled' -sev Alert
+ }
+ }
+ if ($Settings.report -eq $true) {
+ #Add-CIPPBPAField -FieldName '????' -FieldValue "????" -StoreAs bool -Tenant $tenant
+ }
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1
index 17e735496e88..cecc5e565a4f 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1
@@ -29,7 +29,7 @@ function Invoke-CIPPStandardEnableLitigationHold {
param($Tenant, $Settings)
- $MailboxesNoLitHold = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' -cmdparams @{ MailboxPlan = 'ExchangeOnlineEnterprise'; Filter = 'LitigationHoldEnabled -eq "False"'}
+ $MailboxesNoLitHold = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' -cmdparams @{ Filter = 'LitigationHoldEnabled -eq "False"'} | Where-Object {$_.PersistedCapabilities -contains "BPOS_S_DlpAddOn" -or $_.PersistedCapabilities -contains "BPOS_S_Enterprise"}
If ($Settings.remediate -eq $true) {
diff --git a/Modules/CippExtensions/Public/Extension Functions/Get-ExtensionMapping.ps1 b/Modules/CippExtensions/Public/Extension Functions/Get-ExtensionMapping.ps1
new file mode 100644
index 000000000000..6a0ac35728c6
--- /dev/null
+++ b/Modules/CippExtensions/Public/Extension Functions/Get-ExtensionMapping.ps1
@@ -0,0 +1,15 @@
+function Get-ExtensionMapping {
+ param(
+ $Extension
+ )
+
+ $Table = Get-CIPPTable -TableName CippMapping
+ $Mapping = @{}
+ Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq '$($Extension)Mapping'" | ForEach-Object {
+ $Mapping[$_.RowKey] = @{
+ label = "$($_.IntegrationName)"
+ value = "$($_.IntegrationId)"
+ }
+ }
+ return [PSCustomObject]$Mapping
+}
\ No newline at end of file
diff --git a/Modules/CippExtensions/Public/Extension Functions/Push-ExtensionSyncData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Push-ExtensionSyncData.ps1
new file mode 100644
index 000000000000..ebee9385b07a
--- /dev/null
+++ b/Modules/CippExtensions/Public/Extension Functions/Push-ExtensionSyncData.ps1
@@ -0,0 +1,6 @@
+function Push-ExtensionSyncData {
+ param(
+ $TenantFilter,
+ $Extension
+ )
+}
diff --git a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1
new file mode 100644
index 000000000000..32aff8ef8ff7
--- /dev/null
+++ b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1
@@ -0,0 +1,71 @@
+function Register-CIPPExtensionScheduledTasks {
+ Param(
+ [switch]$Reschedule
+ )
+
+ # get extension configuration and mappings table
+ $Table = Get-CIPPTable -TableName Extensionsconfig
+ $Config = ((Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -ea stop)
+ $MappingsTable = Get-CIPPTable -TableName CippMapping
+
+ # Get existing scheduled usertasks
+ $ScheduledTasksTable = Get-CIPPTable -TableName ScheduledTasks
+ $ScheduledTasks = Get-CIPPAzDataTableEntity @ScheduledTasksTable -Filter 'Hidden eq true' | Where-Object { $_.Command -match 'Sync-CippExtensionData' }
+ $Tenants = Get-Tenants -IncludeErrors
+
+ $Extensions = @('Hudu')
+
+ foreach ($Extension in $Extensions) {
+ $ExtensionConfig = $Config.$Extension
+ if ($ExtensionConfig.Enabled -eq $true) {
+ $Mappings = Get-CIPPAzDataTableEntity @MappingsTable -Filter "PartitionKey eq '$($Extension)Mapping'"
+ $FieldMapping = Get-CIPPAzDataTableEntity @MappingsTable -Filter "PartitionKey eq '$($Extension)FieldMapping'"
+ $FieldSync = @{}
+ $SyncTypes = [System.Collections.Generic.List[string]]::new()
+
+ foreach ($Mapping in $FieldMapping) {
+ $FieldSync[$Mapping.RowKey] = !([string]::IsNullOrEmpty($Mapping.IntegrationId))
+ }
+
+ $SyncTypes.Add('Overview')
+
+ if ($FieldSync.Users) {
+ $SyncTypes.Add('Users')
+ $SyncTypes.Add('Mailboxes')
+ }
+ if ($FieldSync.Devices) {
+ $SyncTypes.Add('Devices')
+ }
+
+ foreach ($Mapping in $Mappings) {
+ $Tenant = $Tenants | Where-Object { $_.customerId -eq $Mapping.RowKey }
+
+ foreach ($SyncType in $SyncTypes) {
+ $ExistingTask = $ScheduledTasks | Where-Object { $_.Tenant -eq $Tenant.defaultDomainName -and $_.SyncType -eq $SyncType }
+ if (!$ExistingTask -or $Reschedule.IsPresent) {
+ $unixtime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds
+ $Task = @{
+ Name = "Extension Sync - $SyncType"
+ Command = @{
+ value = 'Sync-CippExtensionData'
+ label = 'Sync-CippExtensionData'
+ }
+ Parameters = @{
+ TenantFilter = $Tenant.defaultDomainName
+ SyncType = $SyncType
+ }
+ Recurrence = '1d'
+ ScheduledTime = $unixtime
+ TenantFilter = $Tenant.defaultDomainName
+ }
+ if ($ExistingTask) {
+ $Task.RowKey = $ExistingTask.RowKey
+ }
+ $null = Add-CIPPScheduledTask -Task $Task -hidden $true -SyncType $SyncType
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/Modules/CippExtensions/Public/Extension Functions/Set-ExtensionFieldMapping.ps1 b/Modules/CippExtensions/Public/Extension Functions/Set-ExtensionFieldMapping.ps1
new file mode 100644
index 000000000000..52d59ab12d77
--- /dev/null
+++ b/Modules/CippExtensions/Public/Extension Functions/Set-ExtensionFieldMapping.ps1
@@ -0,0 +1,24 @@
+function Set-ExtensionFieldMapping {
+ [CmdletBinding()]
+ param (
+ $CIPPMapping,
+ $Extension,
+ $APIName,
+ $Request,
+ $TriggerMetadata
+ )
+
+ foreach ($Mapping in ([pscustomobject]$Request.body.mappings).psobject.properties) {
+ $AddObject = @{
+ PartitionKey = "$($Extension)FieldMapping"
+ RowKey = "$($mapping.name)"
+ IntegrationId = "$($mapping.value.value)"
+ IntegrationName = "$($mapping.value.label)"
+ }
+ Add-AzDataTableEntity @CIPPMapping -Entity $AddObject -Force
+ Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "Added mapping for $($mapping.name)." -Sev 'Info'
+ }
+ $Result = [pscustomobject]@{'Results' = 'Successfully edited mapping table.' }
+
+ Return $Result
+}
\ No newline at end of file
diff --git a/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1
new file mode 100644
index 000000000000..7cb1c9017a78
--- /dev/null
+++ b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1
@@ -0,0 +1,178 @@
+function Sync-CippExtensionData {
+ <#
+ .FUNCTIONALITY
+ Internal
+ #>
+ [CmdletBinding()]
+ param(
+ $TenantFilter,
+ $SyncType
+ )
+
+ $Table = Get-CIPPTable -TableName ExtensionSync
+ $Extensions = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq '$($SyncType)'"
+ $LastSync = $Extensions | Where-Object { $_.RowKey -eq $TenantFilter }
+ $CacheTable = Get-CIPPTable -tablename 'CacheExtensionSync'
+
+ if (!$LastSync) {
+ $LastSync = @{
+ PartitionKey = $SyncType
+ RowKey = $TenantFilter
+ Status = 'Not Synced'
+ Error = ''
+ LastSync = 'Never'
+ }
+ Add-CIPPAzDataTableEntity @Table -Entity $LastSync
+ }
+
+ try {
+ switch ($SyncType) {
+ 'Overview' {
+ # Build bulk requests array.
+ [System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @(
+ @{
+ id = 'TenantDetails'
+ method = 'GET'
+ url = '/organization'
+ },
+ @{
+ id = 'AllRoles'
+ method = 'GET'
+ url = '/directoryRoles?$top=999'
+ },
+ @{
+ id = 'Domains'
+ method = 'GET'
+ url = '/domains$top=999'
+ },
+ @{
+ id = 'Licenses'
+ method = 'GET'
+ url = '/subscribedSkus?$top=999'
+ },
+ @{
+ id = 'Groups'
+ method = 'GET'
+ url = '/groups?$top=999&$select=id,createdDateTime,displayName,description,mail,mailEnabled,mailNickname,resourceProvisioningOptions,securityEnabled,visibility,organizationId,onPremisesSamAccountName,membershipRule,grouptypes,onPremisesSyncEnabled,resourceProvisioningOptions,userPrincipalName'
+ },
+ @{
+ id = 'ConditionalAccess'
+ method = 'GET'
+ url = '/identity/conditionalAccess/policies'
+ },
+ @{
+ id = 'SecureScoreControlProfiles'
+ method = 'GET'
+ url = '/security/secureScoreControlProfiles?$top=999'
+ },
+ @{
+ id = 'Subscriptions'
+ method = 'GET'
+ url = '/directory/subscriptions?$top=999'
+ }
+ )
+
+ $SingleGraphQueries = @(@{
+ id = 'SecureScore'
+ graphRequest = @{
+ uri = 'https://graph.microsoft.com/beta/security/secureScores?$top=1'
+ noPagination = $true
+ }
+ })
+ }
+ 'Users' {
+ [System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @(
+ @{
+ id = 'Users'
+ method = 'GET'
+ url = '/users?$top=999&$select=id,accountEnabled,businessPhones,city,createdDateTime,companyName,country,department,displayName,faxNumber,givenName,isResourceAccount,jobTitle,mail,mailNickname,mobilePhone,onPremisesDistinguishedName,officeLocation,onPremisesLastSyncDateTime,otherMails,postalCode,preferredDataLocation,preferredLanguage,proxyAddresses,showInAddressList,state,streetAddress,surname,usageLocation,userPrincipalName,userType,assignedLicenses,onPremisesSyncEnabled'
+ }
+ )
+ }
+ 'Devices' {
+ [System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @(
+ @{
+ id = 'Devices'
+ method = 'GET'
+ url = '/deviceManagement/managedDevices?$top=999'
+ },
+ @{
+ id = 'DeviceCompliancePolicies'
+ method = 'GET'
+ url = '/deviceManagement/deviceCompliancePolicies'
+ },
+ @{
+ id = 'DeviceApps'
+ method = 'GET'
+ url = '/deviceAppManagement/mobileApps'
+ }
+ )
+ }
+ 'Mailboxes' {
+ $Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses,WhenSoftDeleted,IsInactiveMailbox'
+ $ExoRequest = @{
+ tenantid = $TenantFilter
+ cmdlet = 'Get-Mailbox'
+ cmdParams = @{}
+ Select = $Select
+ }
+ $Mailboxes = (New-ExoRequest @ExoRequest) | Select-Object id, ExchangeGuid, ArchiveGuid, WhenSoftDeleted, @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } },
+
+ @{ Name = 'displayName'; Expression = { $_.'DisplayName' } },
+ @{ Name = 'primarySmtpAddress'; Expression = { $_.'PrimarySMTPAddress' } },
+ @{ Name = 'recipientType'; Expression = { $_.'RecipientType' } },
+ @{ Name = 'recipientTypeDetails'; Expression = { $_.'RecipientTypeDetails' } },
+ @{ Name = 'AdditionalEmailAddresses'; Expression = { ($_.'EmailAddresses' | Where-Object { $_ -clike 'smtp:*' }).Replace('smtp:', '') -join ', ' } }
+
+ $Entity = @{
+ PartitionKey = $TenantFilter
+ SyncType = 'Mailboxes'
+ RowKey = 'Mailboxes'
+ Data = [string]($Mailboxes | ConvertTo-Json -Depth 10 -Compress)
+ }
+ Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force
+ }
+ }
+
+ if ($TenantRequests) {
+ try {
+ $TenantResults = New-GraphBulkRequest -Requests $TenantRequests -tenantid $TenantFilter
+ } catch {
+ Throw "Failed to fetch bulk company data: $_"
+ }
+
+ if ($SingleGraphQueries) {
+ foreach ($SingleGraphQuery in $SingleGraphQueries) {
+ $Request = $SingleGraphQuery.graphRequest
+ $Data = New-GraphGetRequest @Request -tenantid $TenantFilter
+ $Entity = @{
+ PartitionKey = $TenantFilter
+ SyncType = $SyncType
+ RowKey = $SingleGraphQuery.id
+ Data = [string]($Data | ConvertTo-Json -Depth 10 -Compress)
+ }
+ Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force
+ }
+ }
+
+ $TenantResults | Select-Object id, body | ForEach-Object {
+ $Entity = @{
+ PartitionKey = $TenantFilter
+ RowKey = $_.id
+ SyncType = $SyncType
+ Data = [string]($_.body.value | ConvertTo-Json -Depth 10 -Compress)
+ }
+ Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force
+ }
+ }
+ $LastSync.LastSync = [datetime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')
+ $LastSync.Status = 'Completed'
+ $LastSync.Error = ''
+ } catch {
+ $LastSync.Status = 'Failed'
+ $LastSync.Error = [string](Get-CippException -Exception $_ | ConvertTo-Json -Compress)
+ throw "Failed to sync data: $($_.Exception.Message)"
+ } finally {
+ Add-CIPPAzDataTableEntity @Table -Entity $LastSync -Force
+ }
+}
diff --git a/Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1 b/Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1
index fcae99cfd5d1..2a8aae7646ef 100644
--- a/Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1
+++ b/Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1
@@ -6,16 +6,28 @@ function Get-HaloMapping {
#Get available mappings
$Mappings = [pscustomobject]@{}
+ # Migrate legacy mappings
$Filter = "PartitionKey eq 'Mapping'"
- Get-CIPPAzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object {
- $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.HaloPSAName)"; value = "$($_.HaloPSA)" }
+ $MigrateRows = Get-CIPPAzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object {
+ [PSCustomObject]@{
+ PartitionKey = 'HaloMapping'
+ RowKey = $_.RowKey
+ IntegrationId = $_.HaloPSA
+ IntegrationName = $_.HaloPSAName
+ }
+ Remove-AzDataTableEntity @CIPPMapping -Entity $_ | Out-Null
+ }
+ if (($MigrateRows | Measure-Object).Count -gt 0) {
+ Add-CIPPAzDataTableEntity @CIPPMapping -Entity $MigrateRows -Force
}
+
+ $Mappings = Get-ExtensionMapping -Extension 'Halo'
+
$Tenants = Get-Tenants -IncludeErrors
$Table = Get-CIPPTable -TableName Extensionsconfig
try {
$Configuration = ((Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -ea stop).HaloPSA
-
$Token = Get-HaloToken -configuration $Configuration
$i = 1
$RawHaloClients = do {
@@ -32,7 +44,7 @@ function Get-HaloMapping {
}
Write-LogMessage -Message "Could not get HaloPSA Clients, error: $Message " -Level Error -tenant 'CIPP' -API 'HaloMapping'
- $RawHaloClients = @(@{name = "Could not get HaloPSA Clients, error: $Message"; value = '-1' })
+ $RawHaloClients = @(@{name = "Could not get HaloPSA Clients, error: $Message"; id = '-1' })
}
$HaloClients = $RawHaloClients | ForEach-Object {
[PSCustomObject]@{
@@ -41,9 +53,9 @@ function Get-HaloMapping {
}
}
$MappingObj = [PSCustomObject]@{
- Tenants = @($Tenants)
- HaloClients = @($HaloClients)
- Mappings = $Mappings
+ Tenants = @($Tenants)
+ Companies = @($HaloClients)
+ Mappings = $Mappings
}
return $MappingObj
diff --git a/Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1 b/Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1
index 527bbc94fd22..129b1578ad59 100644
--- a/Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1
+++ b/Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1
@@ -5,20 +5,20 @@ function Set-HaloMapping {
$APIName,
$Request
)
- Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'Mapping'" | ForEach-Object {
+ Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'HaloMapping'" | ForEach-Object {
Remove-AzDataTableEntity @CIPPMapping -Entity $_
}
foreach ($Mapping in ([pscustomobject]$Request.body.mappings).psobject.properties) {
$AddObject = @{
- PartitionKey = 'Mapping'
- RowKey = "$($mapping.name)"
- 'HaloPSA' = "$($mapping.value.value)"
- 'HaloPSAName' = "$($mapping.value.label)"
+ PartitionKey = 'HaloMapping'
+ RowKey = "$($mapping.name)"
+ IntegrationId = "$($mapping.value.value)"
+ IntegrationName = "$($mapping.value.label)"
}
Add-CIPPAzDataTableEntity @CIPPMapping -Entity $AddObject -Force
- Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "Added mapping for $($mapping.name)." -Sev 'Info'
+ Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "Added mapping for $($mapping.name)." -Sev 'Info'
}
$Result = [pscustomobject]@{'Results' = 'Successfully edited mapping table.' }
diff --git a/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1
new file mode 100644
index 000000000000..bb12d561104a
--- /dev/null
+++ b/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1
@@ -0,0 +1,66 @@
+function Get-HuduFieldMapping {
+ [CmdletBinding()]
+ param (
+ $CIPPMapping
+ )
+
+ $Mappings = Get-ExtensionMapping -Extension 'HuduField'
+
+ $CIPPFieldHeaders = @(
+ [PSCustomObject]@{
+ Title = 'Hudu Asset Layouts'
+ FieldType = 'Layouts'
+ Description = 'Use the table below to map your Hudu Asset Layouts to the correct CIPP Data Type. A new Rich Text asset layout field will be created if it does not exist.'
+ }
+ )
+ $CIPPFields = @(
+ [PSCustomObject]@{
+ FieldName = 'Users'
+ FieldLabel = 'Asset Layout for M365 Users'
+ FieldType = 'Layouts'
+ }
+ [PSCustomObject]@{
+ FieldName = 'Devices'
+ FieldLabel = 'Asset Layout for M365 Devices'
+ FieldType = 'Layouts'
+ }
+ [PSCustomObject]@{
+ FieldName = 'Licenses'
+ FieldLabel = 'Asset Layout for M365 Licenses'
+ FieldType = 'Layouts'
+ }
+ )
+
+ $Table = Get-CIPPTable -TableName Extensionsconfig
+ try {
+ $Configuration = ((Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -ea stop).Hudu
+ Connect-HuduAPI -configuration $Configuration
+
+ $AssetLayouts = Get-HuduAssetLayouts | Select-Object @{Name = 'FieldType' ; Expression = { 'Layouts' } }, @{Name = 'value'; Expression = { $_.id } }, name, fields
+ } catch {
+ $Message = if ($_.ErrorDetails.Message) {
+ Get-NormalizedError -Message $_.ErrorDetails.Message
+ } else {
+ $_.Exception.message
+ }
+
+ Write-LogMessage -Message "Could not get Hudu Asset Layouts, error: $Message " -Level Error -tenant 'CIPP' -API 'HuduMapping'
+ $AssetLayouts = @(@{name = "Could not get Hudu Asset Layouts, error: $Message"; value = '-1' })
+ }
+
+ $Unset = [PSCustomObject]@{
+ name = '--- Do not synchronize ---'
+ value = $null
+ type = 'unset'
+ }
+
+ $MappingObj = [PSCustomObject]@{
+ CIPPFields = $CIPPFields
+ CIPPFieldHeaders = $CIPPFieldHeaders
+ IntegrationFields = @($Unset) + @($AssetLayouts)
+ Mappings = $Mappings
+ }
+
+ return $MappingObj
+
+}
diff --git a/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1
index cff35483ca7a..a8d775168cf7 100644
--- a/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1
+++ b/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1
@@ -3,13 +3,9 @@ function Get-HuduMapping {
param (
$CIPPMapping
)
- #Get available mappings
- $Mappings = [pscustomobject]@{}
- $Filter = "PartitionKey eq 'HuduMapping'"
- Get-CIPPAzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object {
- $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.HuduCompany)"; value = "$($_.HuduCompanyId)" }
- }
+ $Mappings = Get-ExtensionMapping -Extension 'Hudu'
+
$Tenants = Get-Tenants -IncludeErrors
$Table = Get-CIPPTable -TableName Extensionsconfig
try {
@@ -35,9 +31,9 @@ function Get-HuduMapping {
}
}
$MappingObj = [PSCustomObject]@{
- Tenants = @($Tenants)
- HuduCompanies = @($HuduCompanies)
- Mappings = $Mappings
+ Tenants = @($Tenants)
+ Companies = @($HuduCompanies)
+ Mappings = $Mappings
}
return $MappingObj
diff --git a/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1
index a7ad9f8172b3..03c6dddb8fb3 100644
--- a/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1
+++ b/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1
@@ -10,10 +10,10 @@ function Set-HuduMapping {
}
foreach ($Mapping in ([pscustomobject]$Request.body.mappings).psobject.properties) {
$AddObject = @{
- PartitionKey = 'Mapping'
+ PartitionKey = 'HuduMapping'
RowKey = "$($mapping.name)"
- 'HuduCompanyId' = "$($mapping.value.value)"
- 'HuduCompany' = "$($mapping.value.label)"
+ IntegrationId = "$($mapping.value.value)"
+ IntegrationName = "$($mapping.value.label)"
}
Add-CIPPAzDataTableEntity @CIPPMapping -Entity $AddObject -Force
diff --git a/Modules/CippExtensions/Public/New-CippExtAlert.ps1 b/Modules/CippExtensions/Public/New-CippExtAlert.ps1
index 827347a613d2..21f5acf1923e 100644
--- a/Modules/CippExtensions/Public/New-CippExtAlert.ps1
+++ b/Modules/CippExtensions/Public/New-CippExtAlert.ps1
@@ -11,18 +11,18 @@ function New-CippExtAlert {
$MappingFile = (Get-CIPPAzDataTableEntity @MappingTable)
foreach ($ConfigItem in $Configuration.psobject.properties.name) {
switch ($ConfigItem) {
- "HaloPSA" {
+ 'HaloPSA' {
If ($Configuration.HaloPSA.enabled) {
$TenantId = (Get-Tenants | Where-Object defaultDomainName -EQ $Alert.TenantId).customerId
Write-Host "TenantId: $TenantId"
- $MappedId = ($MappingFile | Where-Object RowKey -EQ $TenantId).HaloPSA
+ $MappedId = ($MappingFile | Where-Object { $_.PartitionKey -eq 'HaloMapping' -and $_.RowKey -eq $TenantId }).IntegrationId
Write-Host "MappedId: $MappedId"
if (!$mappedId) { $MappedId = 1 }
Write-Host "MappedId: $MappedId"
- New-HaloPSATicket -Title $Alert.AlertTitle -Description $Alert.AlertText -Client $mappedId
+ New-HaloPSATicket -Title $Alert.AlertTitle -Description $Alert.AlertText -Client $mappedId
}
}
- "Gradient" {
+ 'Gradient' {
If ($Configuration.Gradient.enabled) {
New-GradientAlert -Title $Alert.AlertTitle -Description $Alert.AlertText -Client $Alert.TenantId
}
diff --git a/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1
index 8be773c2d030..e88f53ceba9c 100644
--- a/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1
+++ b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1
@@ -7,93 +7,109 @@ function Get-NinjaOneFieldMapping {
#Get available mappings
$Mappings = [pscustomobject]@{}
- [System.Collections.Generic.List[PSCustomObject]]$CIPPFields = @(
+ [System.Collections.Generic.List[object]]$CIPPFieldHeaders = @(
[PSCustomObject]@{
- InternalName = 'TenantLinks'
- Description = 'Microsoft 365 Tenant Links - Field Used to Display Links to Microsoft 365 Portals and CIPP'
- Scope = 'Organization'
- Type = 'WYSIWYG'
+ Title = 'NinjaOne Organization Global Custom Field Mapping'
+ FieldType = 'Organization'
+ Description = 'Use the table below to map your Organization Field to the correct NinjaOne Field'
+ }
+ [PSCustomObject]@{
+ Title = 'NinjaOne Device Custom Field Mapping'
+ FieldType = 'Device'
+ Description = 'Use the table below to map your Device Field to the correct NinjaOne Field'
+ }
+ )
+
+ [System.Collections.Generic.List[object]]$CIPPFields = @(
+ [PSCustomObject]@{
+ FieldName = 'TenantLinks'
+ FieldLabel = 'Microsoft 365 Tenant Links - Field Used to Display Links to Microsoft 365 Portals and CIPP'
+ FieldType = 'Organization'
+ Type = 'WYSIWYG'
},
[PSCustomObject]@{
- InternalName = 'TenantSummary'
- Description = 'Microsoft 365 Tenant Summary - Field Used to Display Tenant Summary Information'
- Scope = 'Organization'
- Type = 'WYSIWYG'
+ FieldName = 'TenantSummary'
+ FieldLabel = 'Microsoft 365 Tenant Summary - Field Used to Display Tenant Summary Information'
+ FieldType = 'Organization'
+ Type = 'WYSIWYG'
},
[PSCustomObject]@{
- InternalName = 'UsersSummary'
- Description = 'Microsoft 365 Users Summary - Field Used to Display User Summary Information'
- Scope = 'Organization'
- Type = 'WYSIWYG'
+ FieldName = 'UsersSummary'
+ FieldLabel = 'Microsoft 365 Users Summary - Field Used to Display User Summary Information'
+ FieldType = 'Organization'
+ Type = 'WYSIWYG'
},
[PSCustomObject]@{
- InternalName = 'DeviceLinks'
- Description = 'Microsoft 365 Device Links - Field Used to Display Links to Microsoft 365 Portals and CIPP'
- Scope = 'Device'
- Type = 'WYSIWYG'
+ FieldName = 'DeviceLinks'
+ FieldLabel = 'Microsoft 365 Device Links - Field Used to Display Links to Microsoft 365 Portals and CIPP'
+ FieldType = 'Device'
+ Type = 'WYSIWYG'
},
[PSCustomObject]@{
- InternalName = 'DeviceSummary'
- Description = 'Microsoft 365 Device Summary - Field Used to Display Device Summary Information'
- Scope = 'Device'
- Type = 'WYSIWYG'
+ FieldName = 'DeviceSummary'
+ FieldLabel = 'Microsoft 365 Device Summary - Field Used to Display Device Summary Information'
+ FieldType = 'Device'
+ Type = 'WYSIWYG'
},
[PSCustomObject]@{
- InternalName = 'DeviceCompliance'
- Description = 'Intune Device Compliance Status - Field Used to Monitor Device Compliance'
- Scope = 'Device'
- Type = 'TEXT'
+ FieldName = 'DeviceCompliance'
+ FieldLabel = 'Intune Device Compliance Status - Field Used to Monitor Device Compliance'
+ FieldType = 'Device'
+ Type = 'TEXT'
}
)
- $Filter = "PartitionKey eq 'NinjaFieldMapping'"
- Get-AzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object {
- $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.NinjaOneName)"; value = "$($_.NinjaOne)" }
+ $MappingFieldMigrate = Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'NinjaFieldMapping'" | ForEach-Object {
+ [PSCustomObject]@{
+ PartitionKey = 'NinjaOneFieldMapping'
+ RowKey = $_.RowKey
+ IntegrationId = $_.NinjaOne
+ IntegrationName = $_.NinjaOneName
+ }
+ Remove-AzDataTableEntity @CIPPMapping -Entity $_
+ }
+ if (($MappingFieldMigrate | Measure-Object).count -gt 0) {
+ Add-CIPPAzDataTableEntity @CIPPMapping -Entity $MappingFieldMigrate -Force
}
+ $Mappings = Get-ExtensionMapping -Extension 'NinjaOneField'
$Table = Get-CIPPTable -TableName Extensionsconfig
$Configuration = ((Get-AzDataTableEntity @Table).config | ConvertFrom-Json -ea stop).NinjaOne
-
-
$Token = Get-NinjaOneToken -configuration $Configuration
-
- $NinjaCustomFieldsNodeRaw = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/device-custom-fields?scopes=node" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -depth 100
- [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsNode = $NinjaCustomFieldsNodeRaw | Where-Object { $_.apiPermission -eq 'READ_WRITE' -and $_.type -in $CIPPFields.Type } | Select-Object @{n = 'name'; e = { $_.label } }, @{n = 'value'; e = { $_.name } }, type
-
- $NinjaCustomFieldsOrgRaw = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/device-custom-fields?scopes=organization" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -depth 100
- [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsOrg = $NinjaCustomFieldsOrgRaw | Where-Object { $_.apiPermission -eq 'READ_WRITE' -and $_.type -in $CIPPFields.Type } | Select-Object @{n = 'name'; e = { $_.label } }, @{n = 'value'; e = { $_.name } }, type
-
- if ($Null -eq $NinjaCustomFieldsNode){
- [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsNode = @()
+
+ $NinjaCustomFieldsNodeRaw = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/device-custom-fields?scopes=node" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100
+
+ [System.Collections.Generic.List[object]]$NinjaCustomFieldsNode = $NinjaCustomFieldsNodeRaw | Where-Object { $_.apiPermission -eq 'READ_WRITE' -and $_.type -in $CIPPFields.Type } | Select-Object @{n = 'name'; e = { $_.label } }, @{n = 'value'; e = { $_.name } }, type, @{n = 'FieldType'; e = { 'Device' } }
+
+ $NinjaCustomFieldsOrgRaw = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/device-custom-fields?scopes=organization" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100
+
+ [System.Collections.Generic.List[object]]$NinjaCustomFieldsOrg = $NinjaCustomFieldsOrgRaw | Where-Object { $_.apiPermission -eq 'READ_WRITE' -and $_.type -in $CIPPFields.Type } | Select-Object @{n = 'name'; e = { $_.label } }, @{n = 'value'; e = { $_.name } }, type, @{n = 'FieldType'; e = { 'Organization' } }
+
+ if ($Null -eq $NinjaCustomFieldsNode) {
+ [System.Collections.Generic.List[object]]$NinjaCustomFieldsNode = @()
}
-
- if ($Null -eq $NinjaCustomFieldsOrg){
- [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsOrg = @()
+
+ if ($Null -eq $NinjaCustomFieldsOrg) {
+ [System.Collections.Generic.List[object]]$NinjaCustomFieldsOrg = @()
+ }
+ $Unset = [PSCustomObject]@{
+ name = '--- Do not synchronize ---'
+ value = $null
+ type = 'unset'
}
-
- } catch {
- [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsNode = @()
- [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsOrg = @()
- }
- $DoNotSync = [PSCustomObject]@{
- name = '--- Do not synchronize ---'
- value = $null
- type = 'unset'
+ } catch {
+ [System.Collections.Generic.List[object]]$NinjaCustomFieldsNode = @()
+ [System.Collections.Generic.List[objecgt]]$NinjaCustomFieldsOrg = @()
}
- $NinjaCustomFieldsOrg.Insert(0, $DoNotSync)
- $NinjaCustomFieldsNode.Insert(0, $DoNotSync)
-
-
$MappingObj = [PSCustomObject]@{
- CIPPOrgFields = $CIPPFields | Where-Object { $_.Scope -eq 'Organization' }
- CIPPNodeFields = @($CIPPFields | Where-Object { $_.Scope -eq 'Device' })
- NinjaOrgFields = @($NinjaCustomFieldsOrg)
- NinjaNodeFields = @($NinjaCustomFieldsNode)
- Mappings = $Mappings
+ CIPPFields = $CIPPFields
+ CIPPFieldHeaders = $CIPPFieldHeaders
+ IntegrationFields = @($Unset) + @($NinjaCustomFieldsOrg) + @($NinjaCustomFieldsNode)
+ Mappings = $Mappings
}
return $MappingObj
diff --git a/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneOrgMapping.ps1
index d2d914c89589..24c7e6405560 100644
--- a/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneOrgMapping.ps1
+++ b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneOrgMapping.ps1
@@ -4,14 +4,25 @@ function Get-NinjaOneOrgMapping {
$CIPPMapping
)
try {
- #Get available mappings
- $Mappings = [pscustomobject]@{}
$Tenants = Get-Tenants -IncludeErrors
$Filter = "PartitionKey eq 'NinjaOrgsMapping'"
- Get-AzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object {
- $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.NinjaOneName)"; value = "$($_.NinjaOne)" }
+ $MigrateRows = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object {
+ #$Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.NinjaOneName)"; value = "$($_.NinjaOne)" }
+ [PSCustomObject]@{
+ RowKey = $_.RowKey
+ IntegrationName = $_.NinjaOneName
+ IntegrationId = $_.NinjaOne
+ PartitionKey = 'NinjaOneMapping'
+ }
+ Remove-AzDataTableEntity @CIPPMapping -Entity $_
}
+
+ if (($MigrateRows | Measure-Object).Count -gt 0) {
+ Add-AzDataTableEntity @CIPPMapping -Entity $MigrateRows -Force
+ }
+
+ $Mappings = Get-ExtensionMapping -Extension 'NinjaOne'
#Get Available Tenants
#Get available Ninja clients
@@ -43,7 +54,7 @@ function Get-NinjaOneOrgMapping {
$MappingObj = [PSCustomObject]@{
Tenants = @($Tenants)
- NinjaOrgs = @($NinjaOrgs | Sort-Object name)
+ Companies = @($NinjaOrgs | Sort-Object name)
Mappings = $Mappings
}
diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1
index a8363917efcc..9213a7015b1a 100644
--- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1
+++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1
@@ -8,9 +8,9 @@ function Invoke-NinjaOneDeviceWebhook {
Write-LogMessage -user $ExecutingUser -API $APIName -message "Webhook Recieved - Updating NinjaOne Device compliance for $($Data.resourceData.id) in $($Data.tenantId)" -Sev 'Info' -tenant $TenantFilter
$MappedFields = [pscustomobject]@{}
$CIPPMapping = Get-CIPPTable -TableName CippMapping
- $Filter = "PartitionKey eq 'NinjaFieldMapping'"
- Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.NinjaOne -and $_.NinjaOne -ne '' } | ForEach-Object {
- $MappedFields | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue $($_.NinjaOne)
+ $Filter = "PartitionKey eq 'NinjaOneFieldMapping'"
+ Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.IntegrationId -and $_.IntegrationId -ne '' } | ForEach-Object {
+ $MappedFields | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue $($_.IntegrationId)
}
if ($MappedFields.DeviceCompliance) {
@@ -18,14 +18,14 @@ function Invoke-NinjaOneDeviceWebhook {
$M365DeviceID = $Data.resourceData.id
$DeviceM365 = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/devices/$($M365DeviceID)" -Tenantid $tenantfilter
-
+
$DeviceFilter = "PartitionKey eq '$($tenantfilter)' and RowKey eq '$($DeviceM365.deviceID)'"
$DeviceMapTable = Get-CippTable -tablename 'NinjaOneDeviceMap'
$Device = Get-CIPPAzDataTableEntity @DeviceMapTable -Filter $DeviceFilter
-
+
if (($Device | Measure-Object).count -eq 1) {
- $Token = Get-NinjaOneToken -configuration $Configuration
-
+ $Token = Get-NinjaOneToken -configuration $Configuration
+
if ($DeviceM365.isCompliant -eq $True) {
$Compliant = 'Compliant'
} else {
@@ -37,16 +37,16 @@ function Invoke-NinjaOneDeviceWebhook {
} | ConvertTo-Json
$Null = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/device/$($Device.NinjaOneID)/custom-fields" -Method PATCH -Body $ComplianceBody -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json'
-
+
Write-Host 'Updated NinjaOne Device Compliance'
-
+
} else {
Write-LogMessage -API 'NinjaOneSync' -user 'CIPP' -message "$($DeviceM365.displayName) ($($M365DeviceID)) was not matched in Ninja for $($tenantfilter)" -Sev 'Info'
}
}
-
+
} catch {
$Message = if ($_.ErrorDetails.Message) {
Get-NormalizedError -Message $_.ErrorDetails.Message
@@ -56,7 +56,7 @@ function Invoke-NinjaOneDeviceWebhook {
Write-Error "Failed NinjaOne Device Webhook for: $($Data | ConvertTo-Json -Depth 100) Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $Message"
Write-LogMessage -API 'NinjaOneSync' -user 'CIPP' -message "Failed NinjaOne Device Webhook Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $Message" -Sev 'Error'
}
-
-
+
+
}
\ No newline at end of file
diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1
index 1faf7d92c833..ca69e5b10935 100644
--- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1
+++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1
@@ -26,8 +26,8 @@ function Invoke-NinjaOneExtensionScheduler {
Write-Host "Current Interval: $CurrentInterval"
$CIPPMapping = Get-CIPPTable -TableName CippMapping
- $Filter = "PartitionKey eq 'NinjaOrgsMapping'"
- $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.NinjaOne -and $_.NinjaOne -ne '' }
+ $Filter = "PartitionKey eq 'NinjaOneMapping'"
+ $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.IntegrationId -and $_.IntegrationId -ne '' }
if ($Null -eq $LastRunTime -or $LastRunTime -le (Get-Date).addhours(-25) -or $TimeSetting -eq $CurrentInterval) {
Write-Host 'Executing'
diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMapping.ps1
index 6ea239b73e36..6b5687d6059f 100644
--- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMapping.ps1
+++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMapping.ps1
@@ -9,9 +9,9 @@ function Invoke-NinjaOneOrgMapping {
#Get available mappings
$Mappings = [pscustomobject]@{}
- $Filter = "PartitionKey eq 'NinjaOrgsMapping'"
+ $Filter = "PartitionKey eq 'NinjaOneMapping'"
Get-AzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object {
- $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.NinjaOneName)"; value = "$($_.NinjaOne)" }
+ $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.IntegrationName)"; value = "$($_.IntegrationId)" }
}
#Get Available Tenants
@@ -81,10 +81,10 @@ function Invoke-NinjaOneOrgMapping {
$MatchedM365Tenants.add($Tenant)
$MatchedNinjaOrgs.add($MatchedOrg)
$AddObject = @{
- PartitionKey = 'NinjaOrgsMapping'
- RowKey = "$($Tenant.customerId)"
- 'NinjaOne' = "$($MatchedOrg.id)"
- 'NinjaOneName' = "$($MatchedOrg.name)"
+ PartitionKey = 'NinjaOneMapping'
+ RowKey = "$($Tenant.customerId)"
+ IntegrationId = "$($MatchedOrg.id)"
+ IntegrationName = "$($MatchedOrg.name)"
}
Add-AzDataTableEntity @CIPPMapping -Entity $AddObject -Force
Write-LogMessage -API 'NinjaOneAutoMap_Queue' -user 'CIPP' -message "Added mapping from Organization name match for $($Tenant.customerId). to $($($MatchedOrg.name))" -Sev 'Info'
diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1
index 317770c3bb78..c3f05acf1cc3 100644
--- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1
+++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1
@@ -14,7 +14,7 @@ function Invoke-NinjaOneOrgMappingTenant {
$TenantFilter = $Tenant.customerId
- $M365DevicesRaw = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/managedDevices" -Tenantid $tenantfilter
+ $M365DevicesRaw = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/managedDevices' -Tenantid $tenantfilter
$M365Devices = foreach ($Device in $M365DevicesRaw) {
[pscustomobject]@{
@@ -28,10 +28,10 @@ function Invoke-NinjaOneOrgMappingTenant {
[System.Collections.Generic.List[PSCustomObject]]$MatchedDevices = @()
# Match devices on serial
- $DevicesToMatchSerial = $M365Devices | where-object { $null -ne $_.DeviceSerial }
+ $DevicesToMatchSerial = $M365Devices | Where-Object { $null -ne $_.DeviceSerial }
foreach ($SerialMatchDevice in $DevicesToMatchSerial) {
- $MatchedDevice = $NinjaDevices | where-object { $_.Serial -eq $SerialMatchDevice.DeviceSerial -or $_.BiosSerialNumber -eq $SerialMatchDevice.DeviceSerial }
- if (($MatchedDevice | measure-object).count -eq 1) {
+ $MatchedDevice = $NinjaDevices | Where-Object { $_.Serial -eq $SerialMatchDevice.DeviceSerial -or $_.BiosSerialNumber -eq $SerialMatchDevice.DeviceSerial }
+ if (($MatchedDevice | Measure-Object).count -eq 1) {
$Match = [pscustomobject]@{
M365 = $SerialMatchDevice
Ninja = $MatchedDevice
@@ -41,10 +41,10 @@ function Invoke-NinjaOneOrgMappingTenant {
}
# Try to match on Name
- $DevicesToMatchName = $M365Devices | where-object { $_ -notin $MatchedDevices.M365 }
+ $DevicesToMatchName = $M365Devices | Where-Object { $_ -notin $MatchedDevices.M365 }
foreach ($NameMatchDevice in $DevicesToMatchName) {
- $MatchedDevice = $NinjaDevices | where-object { $_.SystemName -eq $NameMatchDevice.DeviceName -or $_.DNSName -eq $NameMatchDevice.DeviceName }
- if (($MatchedDevice | measure-object).count -eq 1) {
+ $MatchedDevice = $NinjaDevices | Where-Object { $_.SystemName -eq $NameMatchDevice.DeviceName -or $_.DNSName -eq $NameMatchDevice.DeviceName }
+ if (($MatchedDevice | Measure-Object).count -eq 1) {
$Match = [pscustomobject]@{
M365 = $NameMatchDevice
Ninja = $MatchedDevice
@@ -56,17 +56,17 @@ function Invoke-NinjaOneOrgMappingTenant {
# Match on the Org with the most devices that match
if (($MatchedDevices.Ninja.ID | Measure-Object).Count -eq 1) {
- $MatchedOrgID = ($MatchedDevices.Ninja | group-object OrgID | sort-object Count -desc)[0].name
+ $MatchedOrgID = ($MatchedDevices.Ninja | Group-Object OrgID | Sort-Object Count -desc)[0].name
$MatchedOrg = $NinjaOrgs | Where-Object { $_.id -eq $MatchedOrgID }
$AddObject = @{
- PartitionKey = 'NinjaOrgsMapping'
- RowKey = "$($Tenant.customerId)"
- 'NinjaOne' = "$($MatchedOrg.id)"
- 'NinjaOneName' = "$($MatchedOrg.name)"
+ PartitionKey = 'NinjaOneMapping'
+ RowKey = "$($Tenant.customerId)"
+ IntegrationId = "$($MatchedOrg.id)"
+ IntegrationName = "$($MatchedOrg.name)"
}
Add-AzDataTableEntity @CIPPMapping -Entity $AddObject -Force
- Write-LogMessage -API 'NinjaOneAutoMap_Queue' -user 'CIPP' -message "Added mapping from Device match for $($Tenant.displayName) to $($($MatchedOrg.name))" -Sev 'Info'
+ Write-LogMessage -API 'NinjaOneAutoMap_Queue' -user 'CIPP' -message "Added mapping from Device match for $($Tenant.displayName) to $($($MatchedOrg.name))" -Sev 'Info'
}
diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneSync.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneSync.ps1
index 5567ddb7c1b8..c6fb732eb30a 100644
--- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneSync.ps1
+++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneSync.ps1
@@ -3,8 +3,8 @@ function Invoke-NinjaOneSync {
$Table = Get-CIPPTable -TableName NinjaOneSettings
$CIPPMapping = Get-CIPPTable -TableName CippMapping
- $Filter = "PartitionKey eq 'NinjaOrgsMapping'"
- $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.NinjaOne -and $_.NinjaOne -ne '' }
+ $Filter = "PartitionKey eq 'NinjaOneMapping'"
+ $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.IntegrationId -and $_.IntegrationId -ne '' }
$Batch = foreach ($Tenant in $TenantsToProcess) {
diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1
index 9828682c6348..9a3b6949b056 100644
--- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1
+++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1
@@ -6,13 +6,13 @@ function Invoke-NinjaOneTenantSync {
try {
$StartQueueTime = Get-Date
Write-Host "$(Get-Date) - Starting NinjaOne Sync"
-
- # Stagger start
+
+ # Stagger start
# Check Global Rate Limiting
- $CurrentMap = Get-ExtensionRateLimit -ExtensionName 'NinjaOne' -ExtensionPartitionKey 'NinjaOrgsMapping' -RateLimit 5 -WaitTime 10
+ $CurrentMap = Get-ExtensionRateLimit -ExtensionName 'NinjaOne' -ExtensionPartitionKey 'NinjaOneMapping' -RateLimit 5 -WaitTime 10
$StartTime = Get-Date
-
+
# Parse out the Tenant we are processing
$MappedTenant = $QueueItem.MappedTenant
@@ -21,7 +21,7 @@ function Invoke-NinjaOneTenantSync {
$StartDate = try { Get-Date($CurrentItem.lastStartTime) } catch { $Null }
$EndDate = try { Get-Date($CurrentItem.lastEndTime) } catch { $Null }
-
+
if (($null -ne $CurrentItem.lastStartTime) -and ($StartDate -gt (Get-Date).AddMinutes(-10)) -and ( $Null -eq $CurrentItem.lastEndTime -or ($StartDate -gt $EndDate))) {
Throw "NinjaOne Sync for Tenant $($MappedTenant.RowKey) is still running, please wait 10 minutes and try again."
}
@@ -40,19 +40,19 @@ function Invoke-NinjaOneTenantSync {
$Table = Get-CIPPTable -TableName NinjaOneSettings
$NinjaSettings = (Get-CIPPAzDataTableEntity @Table)
$CIPPUrl = ($NinjaSettings | Where-Object { $_.RowKey -eq 'CIPPURL' }).SettingValue
-
-
- $Customer = Get-Tenants | Where-Object { $_.customerId -eq $MappedTenant.RowKey }
+
+
+ $Customer = Get-Tenants -IncludeErrors | Where-Object { $_.customerId -eq $MappedTenant.RowKey }
Write-Host "Processing: $($Customer.displayName) - Queued for $((New-TimeSpan -Start $StartQueueTime -End $StartTime).TotalSeconds)"
- Write-LogMessage -API 'NinjaOneSync' -user 'NinjaOneSync' -message "Processing NinjaOne Synchronization for $($Customer.displayName) - Queued for $((New-TimeSpan -Start $StartQueueTime -End $StartTime).TotalSeconds)" -Sev 'Info'
+ Write-LogMessage -API 'NinjaOneSync' -user 'NinjaOneSync' -message "Processing NinjaOne Synchronization for $($Customer.displayName) - Queued for $((New-TimeSpan -Start $StartQueueTime -End $StartTime).TotalSeconds)" -Sev 'Info'
if (($Customer | Measure-Object).count -ne 1) {
Throw "Unable to match the recieved ID to a tenant QueueItem: $($QueueItem | ConvertTo-Json -Depth 100 | Out-String) Matched Customer: $($Customer| ConvertTo-Json -Depth 100 | Out-String)"
}
$TenantFilter = $Customer.defaultDomainName
- $NinjaOneOrg = $MappedTenant.NinjaOne
+ $NinjaOneOrg = $MappedTenant.IntegrationId
# Get the NinjaOne general extension settings.
@@ -62,9 +62,9 @@ function Invoke-NinjaOneTenantSync {
# Pull the list of field Mappings so we know which fields to render.
$MappedFields = [pscustomobject]@{}
$CIPPMapping = Get-CIPPTable -TableName CippMapping
- $Filter = "PartitionKey eq 'NinjaFieldMapping'"
- Get-CIPPAzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.NinjaOne -and $_.NinjaOne -ne '' } | ForEach-Object {
- $MappedFields | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue $($_.NinjaOne)
+ $Filter = "PartitionKey eq 'NinjaOneFieldMapping'"
+ Get-CIPPAzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.IntegrationId -and $_.IntegrationId -ne '' } | ForEach-Object {
+ $MappedFields | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue $($_.IntegrationId)
}
# Get NinjaOne Devices
@@ -76,14 +76,14 @@ function Invoke-NinjaOneTenantSync {
$Result
$ResultCount = ($Result.id | Measure-Object -Maximum)
$After = $ResultCount.maximum
-
+
} while ($ResultCount.count -eq $PageSize)
Write-Host 'Fetched NinjaOne Devices'
-
+
[System.Collections.Generic.List[PSCustomObject]]$NinjaOneUserDocs = @()
- if ($Configuration.UserDocumentsEnabled -eq $True) {
+ if ($Configuration.UserDocumentsEnabled -eq $True) {
# Get NinjaOne User Documents
$UserDocTemplate = [PSCustomObject]@{
name = 'CIPP - Microsoft 365 Users'
@@ -169,7 +169,7 @@ function Invoke-NinjaOneTenantSync {
# Get NinjaOne Users
[System.Collections.Generic.List[PSCustomObject]]$NinjaOneUserDocs = ((Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents?organizationIds=$($NinjaOneOrg)&templateIds=$($NinjaOneUsersTemplate.id)" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100)
-
+
foreach ($NinjaDoc in $NinjaOneUserDocs) {
$ParsedFields = [pscustomobject]@{}
foreach ($Field in $NinjaDoc.Fields) {
@@ -185,7 +185,7 @@ function Invoke-NinjaOneTenantSync {
Write-Host 'Fetched NinjaOne User Docs'
}
-
+
[System.Collections.Generic.List[PSCustomObject]]$NinjaOneLicenseDocs = @()
if ($Configuration.LicenseDocumentsEnabled) {
# NinjaOne License Documents
@@ -236,10 +236,10 @@ function Invoke-NinjaOneTenantSync {
}
$NinjaOneLicenseTemplate = Invoke-NinjaOneDocumentTemplate -Template $LicenseDocTemplate -Token $Token
-
+
# Get NinjaOne Licenses
[System.Collections.Generic.List[PSCustomObject]]$NinjaOneLicenseDocs = ((Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents?organizationIds=$($NinjaOneOrg)&templateIds=$($NinjaOneLicenseTemplate.id)" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100)
-
+
foreach ($NinjaLic in $NinjaOneLicenseDocs) {
$ParsedFields = [pscustomobject]@{}
foreach ($Field in $NinjaLic.Fields) {
@@ -328,8 +328,8 @@ function Invoke-NinjaOneTenantSync {
id = 'Subscriptions'
method = 'GET'
url = '/directory/subscriptions'
- }
-
+ }
+
)
Write-Verbose "$(Get-Date) - Fetching Bulk Data"
@@ -346,14 +346,14 @@ function Invoke-NinjaOneTenantSync {
$SecureScore = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'SecureScore'
$Subscriptions = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'Subscriptions'
-
+
[System.Collections.Generic.List[PSCustomObject]]$SecureScoreProfiles = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'SecureScoreControlProfiles'
$CurrentSecureScore = ($SecureScore | Sort-Object createDateTime -Descending | Select-Object -First 1)
$MaxSecureScoreRank = ($SecureScoreProfiles.rank | Measure-Object -Maximum).maximum
$MaxSecureScore = $CurrentSecureScore.maxScore
-
+
[System.Collections.Generic.List[PSCustomObject]]$SecureScoreParsed = Foreach ($Score in $CurrentSecureScore.controlScores) {
$MatchedProfile = $SecureScoreProfiles | Where-Object { $_.id -eq $Score.controlName }
[PSCustomObject]@{
@@ -368,20 +368,20 @@ function Invoke-NinjaOneTenantSync {
maxScore = $MatchedProfile.maxScore
rank = $MatchedProfile.rank
adjustedRank = $MaxSecureScoreRank - $MatchedProfile.rank
-
+
}
}
$TenantDetails = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'TenantDetails'
Write-Verbose "$(Get-Date) - Parsing Users"
- # Grab licensed users
- $licensedUsers = $Users | Where-Object { $null -ne $_.AssignedLicenses.SkuId } | Sort-Object UserPrincipalName
-
- Write-Verbose "$(Get-Date) - Parsing Roles"
+ # Grab licensed users
+ $licensedUsers = $Users | Where-Object { $null -ne $_.AssignedLicenses.SkuId } | Sort-Object UserPrincipalName
+
+ Write-Verbose "$(Get-Date) - Parsing Roles"
# Get All Roles
$AllRoles = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'AllRoles'
-
+
$SelectList = 'id', 'displayName', 'userPrincipalName'
[System.Collections.Generic.List[PSCustomObject]]$RolesRequestArray = @()
@@ -410,11 +410,11 @@ function Invoke-NinjaOneTenantSync {
ParsedMembers = $Result.body.value.Displayname -join ', '
}
}
-
+
$AdminUsers = (($Roles | Where-Object { $_.Displayname -match 'Administrator' }).Members | Where-Object { $null -ne $_.displayName })
-
+
Write-Verbose "$(Get-Date) - Fetching Domains"
try {
$RawDomains = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'RawDomains'
@@ -422,8 +422,8 @@ function Invoke-NinjaOneTenantSync {
$RawDomains = $null
}
$customerDomains = ($RawDomains | Where-Object { $_.IsVerified -eq $True }).id -join ', ' | Out-String
-
-
+
+
Write-Verbose "$(Get-Date) - Parsing Licenses"
# Get Licenses
$Licenses = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'Licenses'
@@ -432,7 +432,7 @@ function Invoke-NinjaOneTenantSync {
if ($Licenses) {
$LicensesParsed = $Licenses | Where-Object { $_.PrepaidUnits.Enabled -gt 0 } | Select-Object @{N = 'License Name'; E = { (Get-Culture).TextInfo.ToTitleCase((convert-skuname -skuname $_.SkuPartNumber).Tolower()) } }, @{N = 'Active'; E = { $_.PrepaidUnits.Enabled } }, @{N = 'Consumed'; E = { $_.ConsumedUnits } }, @{N = 'Unused'; E = { $_.PrepaidUnits.Enabled - $_.ConsumedUnits } }
}
-
+
Write-Verbose "$(Get-Date) - Parsing Devices"
# Get all devices from Intune
$devices = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'Devices'
@@ -440,7 +440,7 @@ function Invoke-NinjaOneTenantSync {
Write-Verbose "$(Get-Date) - Parsing Device Compliance Polcies"
# Fetch Compliance Policy Status
$DeviceCompliancePolicies = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'DeviceCompliancePolicies'
-
+
# Get the status of each device for each policy
[System.Collections.Generic.List[PSCustomObject]]$PolicyRequestArray = @()
foreach ($CompliancePolicy in $DeviceCompliancePolicies) {
@@ -466,9 +466,9 @@ function Invoke-NinjaOneTenantSync {
DeviceStatuses = $Result.body.value
}
}
-
+
Write-Verbose "$(Get-Date) - Parsing Groups"
- # Fetch Groups
+ # Fetch Groups
$AllGroups = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'Groups'
# Fetch the App status for each device
@@ -492,7 +492,7 @@ function Invoke-NinjaOneTenantSync {
$Groups = foreach ($Result in $GroupMembersReturn) {
[pscustomobject]@{
ID = $Result.id
- DisplayName = ($AllGroups | Where-Object { $_.id -eq $Result.id }).DisplayName
+ DisplayName = ($AllGroups | Where-Object { $_.id -eq $Result.id }).DisplayName
Members = $result.body.value
}
}
@@ -555,10 +555,10 @@ function Invoke-NinjaOneTenantSync {
Members = $CAMembers
}
}
-
+
Write-Verbose "$(Get-Date) - Fetching One Drive Details"
try {
- $OneDriveDetails = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getOneDriveUsageAccountDetail(period='D7')" -tenantid $TenantFilter | ConvertFrom-Csv
+ $OneDriveDetails = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getOneDriveUsageAccountDetail(period='D7')" -tenantid $TenantFilter | ConvertFrom-Csv
} catch {
Write-Error "Failed to fetch Onedrive Details: $_"
$OneDriveDetails = $null
@@ -571,7 +571,7 @@ function Invoke-NinjaOneTenantSync {
Write-Error "Failed to fetch CAS Details: $_"
$CASFull = $null
}
-
+
Write-Verbose "$(Get-Date) - Fetching Mailbox Details"
try {
$MailboxDetailedFull = New-ExoRequest -TenantID $Customer.defaultDomainName -cmdlet 'Get-Mailbox'
@@ -590,12 +590,12 @@ function Invoke-NinjaOneTenantSync {
Write-Verbose "$(Get-Date) - Fetching Mailbox Stats"
try {
- $MailboxStatsFull = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/reports/getMailboxUsageDetail(period='D7')" -tenantid $TenantFilter | ConvertFrom-Csv
+ $MailboxStatsFull = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/reports/getMailboxUsageDetail(period='D7')" -tenantid $TenantFilter | ConvertFrom-Csv
} catch {
Write-Error "Failed to fetch Mailbox Stats: $_"
$MailboxStatsFull = $null
}
-
+
Write-Host 'Fetched M365 Additional Data'
@@ -604,7 +604,7 @@ function Invoke-NinjaOneTenantSync {
############################ Format and Synchronize to NinjaOne ############################
$DeviceTable = Get-CippTable -tablename 'CacheNinjaOneParsedDevices'
$DeviceMapTable = Get-CippTable -tablename 'NinjaOneDeviceMap'
-
+
$DeviceFilter = "PartitionKey eq '$($Customer.CustomerId)'"
[System.Collections.Generic.List[PSCustomObject]]$RawParsedDevices = Get-CIPPAzDataTableEntity @DeviceTable -Filter $DeviceFilter
@@ -621,13 +621,13 @@ function Invoke-NinjaOneTenantSync {
# Parse Devices
Foreach ($Device in $Devices | Where-Object { $_.id -notin $ParsedDevices.id }) {
-
+
# First lets match on serial
$MatchedNinjaDevice = $NinjaDevices | Where-Object { $_.system.biosSerialNumber -eq $Device.SerialNumber -or $_.system.serialNumber -eq $Device.SerialNumber }
# See if we found just one device, if not match on name
if (($MatchedNinjaDevice | Measure-Object).count -ne 1) {
- $MatchedNinjaDevice = $NinjaDevices | Where-Object { $_.systemName -eq $Device.Name -or $_.dnsName -eq $Device.Name }
+ $MatchedNinjaDevice = $NinjaDevices | Where-Object { $_.systemName -eq $Device.Name -or $_.dnsName -eq $Device.Name }
}
# Check on a match again and set name
@@ -658,8 +658,8 @@ function Invoke-NinjaOneTenantSync {
Add-CIPPAzDataTableEntity @DeviceMapTable -Entity $MappedDevice -Force
}
-
-
+
+
Foreach ($DeviceUser in $Device.usersloggedon) {
$FoundUser = ($Users | Where-Object { $_.id -eq $DeviceUser.userid })
@@ -690,7 +690,7 @@ function Invoke-NinjaOneTenantSync {
})
}
}
-
+
}
}
@@ -743,7 +743,7 @@ function Invoke-NinjaOneTenantSync {
} -Force
$ParsedDevices.add($ParsedDevice)
-
+
### Update NinjaOne Device Fields
if ($MatchedNinjaDevice) {
$NinjaDeviceUpdate = [PSCustomObject]@{}
@@ -767,7 +767,7 @@ function Invoke-NinjaOneTenantSync {
)
-
+
$DeviceLinksHTML = Get-NinjaOneLinks -Data $DeviceLinksData -SmallCols 2 -MedCols 3 -LargeCols 3 -XLCols 3
$DeviceLinksHtml = '
'
@@ -778,7 +778,7 @@ function Invoke-NinjaOneTenantSync {
}
if ($MappedFields.DeviceSummary) {
-
+
# Set Compliance Status
if ($Device.complianceState -eq 'compliant') {
$Compliance = ' Compliant'
@@ -795,9 +795,9 @@ function Invoke-NinjaOneTenantSync {
'Enrolled' = $Device.enrolledDateTime
'Last Checkin' = $Device.lastSyncDateTime
'Compliant' = $Compliance
- 'Management Type' = $Device.managementAgent
+ 'Management Type' = $Device.managementAgent
}
-
+
$DeviceDetailsCard = Get-NinjaOneInfoCard -Title 'Device Details' -Data $DeviceDetailsData -Icon 'fas fa-laptop'
# Device Hardware
@@ -808,8 +808,8 @@ function Invoke-NinjaOneTenantSync {
'Chassis' = $Device.chassisType
'Model' = $Device.model
'Manufacturer' = $Device.manufacturer
- }
-
+ }
+
$DeviceHardwareCard = Get-NinjaOneInfoCard -Title 'Device Details' -Data $DeviceHardwareData -Icon 'fas fa-microchip'
# Device Enrollment
@@ -821,8 +821,8 @@ function Invoke-NinjaOneTenantSync {
'Device Guard Requirements' = $Device.hardwareinformation.deviceGuardVirtualizationBasedSecurityHardwareRequirementState
'Virtualistation Based Security' = $Device.hardwareinformation.deviceGuardVirtualizationBasedSecurityState
'Credential Guard' = $Device.hardwareinformation.deviceGuardLocalSystemAuthorityCredentialGuardState
- }
-
+ }
+
$DeviceEnrollmentCard = Get-NinjaOneInfoCard -Title 'Device Enrollment' -Data $DeviceEnrollmentData -Icon 'fas fa-table-list'
@@ -831,7 +831,7 @@ function Invoke-NinjaOneTenantSync {
$DevicePoliciesHTML = ([System.Web.HttpUtility]::HtmlDecode($DevicePoliciesFormatted) -replace '', ' | ') -replace ' | ', ' | '
$TitleLink = "https://intune.microsoft.com/$($Customer.defaultDomainName)/#view/Microsoft_Intune_Devices/DeviceSettingsMenuBlade/~/compliance/mdmDeviceId/$($Device.id)/primaryUserId/"
$DeviceCompliancePoliciesCard = Get-NinjaOneCard -Title 'Device Compliance Policies' -Body $DevicePoliciesHTML -Icon 'fas fa-list-check' -TitleLink $TitleLink
-
+
# Device Groups
$DeviceGroupsTable = foreach ($Group in $Groups) {
if ($device.azureADDeviceId -in $Group.members.deviceId) {
@@ -844,16 +844,16 @@ function Invoke-NinjaOneTenantSync {
$DeviceGroupsHTML = ([System.Web.HttpUtility]::HtmlDecode($DeviceGroupsFormatted) -replace ' | ', ' | ') -replace ' | ', ' | '
$DeviceGroupsCard = Get-NinjaOneCard -Title 'Device Groups' -Body $DeviceGroupsHTML -Icon 'fas fa-layer-group'
- $DeviceSummaryHTML = '' +
- ' ' + $DeviceDetailsCard +
+ $DeviceSummaryHTML = ' ' +
+ ' ' + $DeviceDetailsCard +
' ' + $DeviceHardwareCard +
- ' ' + $DeviceEnrollmentCard +
+ ' ' + $DeviceEnrollmentCard +
' ' + $DeviceCompliancePoliciesCard +
' ' + $DeviceGroupsCard +
' '
-
- $NinjaDeviceUpdate | Add-Member -NotePropertyName $MappedFields.DeviceSummary -NotePropertyValue @{'html' = $DeviceSummaryHTML }
- }
+
+ $NinjaDeviceUpdate | Add-Member -NotePropertyName $MappedFields.DeviceSummary -NotePropertyValue @{'html' = $DeviceSummaryHTML }
+ }
}
if ($MappedFields.DeviceCompliance) {
@@ -863,7 +863,7 @@ function Invoke-NinjaOneTenantSync {
$Compliant = 'Non-Compliant'
}
$NinjaDeviceUpdate | Add-Member -NotePropertyName $MappedFields.DeviceCompliance -NotePropertyValue $Compliant
-
+
}
# Update Device
@@ -888,11 +888,11 @@ function Invoke-NinjaOneTenantSync {
$SyncUsers = $Users
}
-
+
$UsersTable = Get-CippTable -tablename 'CacheNinjaOneParsedUsers'
$UsersUpdateTable = Get-CippTable -tablename 'CacheNinjaOneUsersUpdate'
$UsersMapTable = Get-CippTable -tablename 'NinjaOneUserMap'
-
+
$UsersFilter = "PartitionKey eq '$($Customer.CustomerId)'"
[System.Collections.Generic.List[PSCustomObject]]$ParsedUsers = Get-CIPPAzDataTableEntity @UsersTable -Filter $UsersFilter
@@ -923,7 +923,7 @@ function Invoke-NinjaOneTenantSync {
foreach ($user in $SyncUsers | Where-Object { $_.id -notin $ParsedUsers.RowKey }) {
try {
-
+
$NinjaOneUser = $NinjaOneUserDocs | Where-Object { $_.ParsedFields.cippUserID -eq $User.ID }
if (($NinjaOneUser | Measure-Object).count -gt 1) {
Throw 'Multiple Users with the same ID found'
@@ -1010,7 +1010,7 @@ function Invoke-NinjaOneTenantSync {
$MatchedNinjaDevice = $UserDevice.NinjaDevice
$ParsedDeviceName = $UserDevice.DeviceLink
-
+
# Set Last Login Time
$LastLoginTime = ($UserDevice.UserDetails | Where-Object { $_.id -eq $User.id }).lastLogin
if (!$LastLoginTime) {
@@ -1033,7 +1033,7 @@ function Invoke-NinjaOneTenantSync {
}
' ' + "$ComplianceIcon $OSIcon $($ParsedDeviceName) ($LastLoginTime)"
-
+
}
@@ -1048,7 +1048,7 @@ function Invoke-NinjaOneTenantSync {
} catch {}
}) -join ''
-
+
$UserOneDriveStats = $OneDriveDetails | Where-Object { $_.'Owner Principal Name' -eq $User.userPrincipalName } | Select-Object -First 1
$UserOneDriveUse = $UserOneDriveStats.'Storage Used (Byte)' / 1GB
@@ -1083,7 +1083,7 @@ function Invoke-NinjaOneTenantSync {
$OneDriveParsed = 'Not Enabled'
}
-
+
if ($UserOneDriveStats) {
$OneDriveCardData = [PSCustomObject]@{
'One Drive URL' = ' ' + ($UserOneDriveStats.'Site URL') + ''
@@ -1100,9 +1100,9 @@ function Invoke-NinjaOneTenantSync {
$OneDriveCardData = [PSCustomObject]@{
'One Drive' = 'Disabled'
}
- }
+ }
+
-
$UserMailboxStats = $MailboxStatsFull | Where-Object { $_.'User Principal Name' -eq $User.userPrincipalName } | Select-Object -First 1
$UserMailUse = $UserMailboxStats.'Storage Used (Byte)' / 1GB
$UserMailTotal = $UserMailboxStats.'Prohibit Send/Receive Quota (Byte)' / 1GB
@@ -1243,7 +1243,7 @@ function Invoke-NinjaOneTenantSync {
"@
-
+
# Return Data for Users Summary Table
$ParsedUser = [PSCustomObject]@{
@@ -1264,8 +1264,8 @@ function Invoke-NinjaOneTenantSync {
Add-CIPPAzDataTableEntity @UsersTable -Entity $ParsedUser -Force
$ParsedUsers.add($ParsedUser)
-
-
+
+
if ($Configuration.UserDocumentsEnabled -eq $True) {
# Format into Ninja HTML
@@ -1283,13 +1283,13 @@ function Invoke-NinjaOneTenantSync {
$UserPolciesCard = Get-NinjaOneCard -Title 'Assigned Conditional Access Policies' -Body $UserPoliciesFormatted
- $UserSummaryHTML = ' ' +
- ' ' + $UserOverviewCardHTML +
+ $UserSummaryHTML = ' ' +
+ ' ' + $UserOverviewCardHTML +
' ' + $MailboxDetailsCardHTML +
' ' + $MailboxSettingsCardHTML +
- ' ' + $OneDriveCardHTML +
- ' ' + $UserPolciesCard +
- ' ' + $DeviceSummaryCardHTML +
+ ' ' + $OneDriveCardHTML +
+ ' ' + $UserPolciesCard +
+ ' ' + $DeviceSummaryCardHTML +
' '
@@ -1301,10 +1301,10 @@ function Invoke-NinjaOneTenantSync {
@{n = 'State'; e = { $_.Compliance } },
@{n = 'Model'; e = { $_.Model } },
@{n = 'Manufacturer'; e = { $_.Make } }
-
+
$UserDeviceDetailHTML = $UserDeviceDetailsTable | ConvertTo-Html -As Table -Fragment
$UserDeviceDetailHTML = ([System.Web.HttpUtility]::HtmlDecode($UserDeviceDetailHTML) -replace ' ', ' | ') -replace ' | ', ' | '
-
+
$UserFields = @{
cippUserLinks = @{'html' = $UserLinksHTML }
@@ -1361,7 +1361,7 @@ function Invoke-NinjaOneTenantSync {
} Catch {
Write-Host "Bulk Creation Error, but may have been successful as only 1 record with an issue could have been the cause: $_"
}
-
+
try {
# Update Users
if (($NinjaUserUpdates | Measure-Object).count -ge 100) {
@@ -1385,7 +1385,7 @@ function Invoke-NinjaOneTenantSync {
} else {
$Field = $UserDoc.fields | Where-Object { $_.name -eq 'cippUserID' }
}
-
+
if ($Null -ne $Field.value -and $Field.value -ne '') {
$MappedUser = ($UsersMap | Where-Object { $_.M365ID -eq $Field.value })
@@ -1411,15 +1411,15 @@ function Invoke-NinjaOneTenantSync {
}
-
+
}
} catch {
Write-Error "User $($User.UserPrincipalName): A fatal error occured while processing user $_"
}
-
+
}
-
+
$CreatedUsers = $Null
$UpdatedUsers = $Null
@@ -1431,12 +1431,12 @@ function Invoke-NinjaOneTenantSync {
Write-Host 'Creating NinjaOne Users'
[System.Collections.Generic.List[PSCustomObject]]$CreatedUsers = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserCreation.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100
Remove-AzDataTableEntity @UsersUpdateTable -Entity $NinjaUserCreation
-
+
}
} Catch {
Write-Host "Bulk Creation Error, but may have been successful as only 1 record with an issue could have been the cause: $_"
}
-
+
try {
# Update Users
if (($NinjaUserUpdates | Measure-Object).count -ge 1) {
@@ -1450,8 +1450,8 @@ function Invoke-NinjaOneTenantSync {
### Relationship Mapping
# Parse out the NinjaOne ID to MS ID
-
-
+
+
[System.Collections.Generic.List[PSCustomObject]]$UserDocResults = $UpdatedUsers + $CreatedUsers
if (($UserDocResults | Where-Object { $Null -ne $_ -and $_ -ne '' } | Measure-Object).count -ge 1) {
@@ -1462,7 +1462,7 @@ function Invoke-NinjaOneTenantSync {
} else {
$Field = $UserDoc.fields | Where-Object { $_.name -eq 'cippUserID' }
}
-
+
if ($Null -ne $Field.value -and $Field.value -ne '') {
$MappedUser = ($UsersMap | Where-Object { $_.M365ID -eq $Field.value })
@@ -1486,8 +1486,8 @@ function Invoke-NinjaOneTenantSync {
}
}
-
-
+
+
# Relate Users to Devices
Foreach ($LinkDevice in $ParsedDevices | Where-Object { $null -ne $_.NinjaDevice }) {
$RelatedItems = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/related-items/with-entity/NODE/$($LinkDevice.NinjaDevice.id)" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100
@@ -1507,7 +1507,7 @@ function Invoke-NinjaOneTenantSync {
}
}
-
+
try {
# Update Relations
@@ -1534,7 +1534,7 @@ function Invoke-NinjaOneTenantSync {
$FriendlyLicenseName = $License.SkuPartNumber
}
-
+
$LicenseUsers = foreach ($SubUser in $Users) {
$MatchedLicense = $SubUser.assignedLicenses | Where-Object { $License.skuId -in $_.skuId }
$MatchedPlans = $SubUser.AssignedPlans | Where-Object { $_.servicePlanId -in $License.servicePlans.servicePlanID }
@@ -1551,7 +1551,7 @@ function Invoke-NinjaOneTenantSync {
'License Assigned' = $(try { $(Get-Date(($MatchedPlans | Group-Object assignedDateTime | Sort-Object Count -Desc | Select-Object -First 1).name) -Format u) } catch { 'Unknown' })
NinjaUserDocID = $SubRelUserID
}
- }
+ }
}
$LicenseUsersHTML = $LicenseUsers | Select-Object -ExcludeProperty NinjaUserDocID | ConvertTo-Html -As Table -Fragment
@@ -1578,12 +1578,12 @@ function Invoke-NinjaOneTenantSync {
$LicenseItemsTable = $License.servicePlans | Select-Object @{n = 'Plan Name'; e = { convert-skuname -skuname $_.servicePlanName } }, @{n = 'Applies To'; e = { $_.appliesTo } }, @{n = 'Provisioning Status'; e = { $_.provisioningStatus } }
$LicenseItemsHTML = $LicenseItemsTable | ConvertTo-Html -As Table -Fragment
$LicenseItemsHTML = ([System.Web.HttpUtility]::HtmlDecode($LicenseItemsHTML) -replace ' | ', ' | ') -replace ' | ', ' | '
-
+
$LicenseItemsCardHTML = Get-NinjaOneCard -Title 'License Items' -Body $LicenseItemsHTML -Icon 'fas fa-chart-bar'
- $LicenseSummaryHTML = '' +
- ' ' + $LicenseOverviewCardHTML +
+ $LicenseSummaryHTML = ' ' +
+ ' ' + $LicenseOverviewCardHTML +
' ' + $SubscriptionCardHTML +
' ' + $LicenseItemsCardHTML +
' '
@@ -1630,7 +1630,7 @@ function Invoke-NinjaOneTenantSync {
} Catch {
Write-Host "Bulk Creation Error, but may have been successful as only 1 record with an issue could have been the cause: $_"
}
-
+
try {
# Update Subscriptions
if (($NinjaLicenseUpdates | Measure-Object).count -ge 1) {
@@ -1663,7 +1663,7 @@ function Invoke-NinjaOneTenantSync {
)
}
}
-
+
try {
# Update Relations
@@ -1750,7 +1750,7 @@ function Invoke-NinjaOneTenantSync {
$M365LinksHTML = Get-NinjaOneLinks -Data $ManagementLinksData -Title 'Portals' -SmallCols 2 -MedCols 3 -LargeCols 3 -XLCols 3
$CIPPLinksData = @(
-
+
@{
Name = 'CIPP Tenant Dashboard'
Link = "https://$CIPPUrl/home?customerId=$($Customer.CustomerId)"
@@ -1802,8 +1802,8 @@ function Invoke-NinjaOneTenantSync {
### Tenant Overview Card
$ParsedAdmins = [PSCustomObject]@{}
-
- $AdminUsers | Select-Object displayname, userPrincipalName -Unique | ForEach-Object {
+
+ $AdminUsers | Select-Object displayname, userPrincipalName -Unique | ForEach-Object {
$ParsedAdmins | Add-Member -NotePropertyName $_.displayname -NotePropertyValue $_.userPrincipalName
}
@@ -1814,7 +1814,7 @@ function Invoke-NinjaOneTenantSync {
'Creation Date' = $TenantDetails.createdDateTime
'Domains' = $customerDomains
'Admin Users' = ($AdminUsers | ForEach-Object { "$($_.DisplayName)" }) -join ', '
-
+
}
$TenantSummaryCard = Get-NinjaOneInfoCard -Title 'Tenant Details' -Data $TenantDetailsItems -Icon 'fas fa-building'
@@ -1826,8 +1826,8 @@ function Invoke-NinjaOneTenantSync {
$LicensedUsersCount = ($licensedUsers | Measure-Object).count
$UnlicensedUsersCount = $TotalUsersCount - $GuestUsersCount - $LicensedUsersCount
$UsersEnabledCount = ($Users | Where-Object { $_.accountEnabled -eq $True } | Measure-Object).count
-
- # Enabled Users
+
+ # Enabled Users
$Data = @(
@{
@@ -1841,10 +1841,10 @@ function Invoke-NinjaOneTenantSync {
Colour = '#D53948'
}
)
-
-
+
+
$UsersEnabledChartHTML = Get-NinjaInLineBarGraph -Title 'User Status' -Data $Data -KeyInLine
-
+
# User Types
$Data = @(
@@ -1863,8 +1863,8 @@ function Invoke-NinjaOneTenantSync {
Amount = $GuestUsersCount
Colour = '#8063BF'
}
- )
-
+ )
+
$UsersTypesChartHTML = Get-NinjaInLineBarGraph -Title 'User Types' -Data $Data -KeyInLine
# Create the Users Card
@@ -1901,8 +1901,8 @@ function Invoke-NinjaOneTenantSync {
Colour = '#D53948'
}
)
-
-
+
+
$DeviceComplianceChartHTML = Get-NinjaInLineBarGraph -Title 'Device Compliance' -Data $Data -KeyInLine
# Device OS Types
@@ -1928,8 +1928,8 @@ function Invoke-NinjaOneTenantSync {
Amount = $IOSCount
Colour = '#007AFF'
}
- )
-
+ )
+
$DeviceOsChartHTML = Get-NinjaInLineBarGraph -Title 'Device Operating Systems' -Data $Data -KeyInLine
# Last online time
@@ -1946,7 +1946,7 @@ function Invoke-NinjaOneTenantSync {
Colour = '#CCCCCC'
}
)
-
+
$DeviceOnlineChartHTML = Get-NinjaInLineBarGraph -Title 'Devices Online in the last 30 days' -Data $Data -KeyInLine
# Create the Devices Card
@@ -1974,7 +1974,7 @@ function Invoke-NinjaOneTenantSync {
Colour = '#CCCCCC'
}
)
-
+
$SecureScoreHTML = Get-NinjaInLineBarGraph -Title "Secure Score - $([System.Math]::Round((($CurrentSecureScore.currentScore / $MaxSecureScore) * 100),2))%" -Data $Data -KeyInLine -NoCount -NoSort
# Recommended Actions HTML
@@ -1995,7 +1995,7 @@ function Invoke-NinjaOneTenantSync {
$Table = Get-CippTable -tablename 'standards'
- $Filter = "PartitionKey eq 'standards'"
+ $Filter = "PartitionKey eq 'standards'"
$AllStandards = (Get-CIPPAzDataTableEntity @Table -Filter $Filter).JSON | ConvertFrom-Json -Depth 100
@@ -2025,11 +2025,11 @@ function Invoke-NinjaOneTenantSync {
Write-Host 'License Details'
$LicenseTableHTML = $LicensesParsed | Sort-Object 'License Name' | ConvertTo-Html -As Table -Fragment
$LicenseTableHTML = ' ' + (([System.Web.HttpUtility]::HtmlDecode($LicenseTableHTML) -replace ' ', ' | ') -replace ' | ', ' | ') + ''
-
+
$TitleLink = "https://$CIPPUrl/tenant/administration/list-licenses?customerId=$($Customer.customerId)"
$LicensesSummaryCardHTML = Get-NinjaOneCard -Title 'Licenses' -Body $LicenseTableHTML -Icon 'fas fa-chart-bar' -TitleLink $TitleLink
-
+
### Summary Stats
Write-Host 'Widget Details'
@@ -2056,7 +2056,7 @@ function Invoke-NinjaOneTenantSync {
# Colour = $ResultColour
# Link = "https://$CIPPUrl/tenant/standards/bpa-report?SearchNow=true&Report=CIPP+Best+Practices+v1.0+-+Tenant+view&tenantFilter=$($Customer.customerId)"
# })
-
+
# Unused Licenses
$WidgetData.add([PSCustomObject]@{
Value = $(
@@ -2076,15 +2076,15 @@ function Invoke-NinjaOneTenantSync {
Colour = $ResultColour
Link = "https://$CIPPUrl/tenant/standards/bpa-report?SearchNow=true&Report=CIPP+Best+Practices+v1.5+-+Tenant+view&tenantFilter=$($Customer.customerId)"
})
-
-
+
+
# Unified Audit Log
$WidgetData.add([PSCustomObject]@{
Value = $(if ($BPAData.UnifiedAuditLog -eq $True) {
- $ResultColour = '#26A644'
+ $ResultColour = '#26A644'
''
} else {
- $ResultColour = '#D53948'
+ $ResultColour = '#D53948'
''
}
)
@@ -2092,14 +2092,14 @@ function Invoke-NinjaOneTenantSync {
Colour = $ResultColour
Link = "https://security.microsoft.com/auditlogsearch?viewid=Async%20Search&tid=$($Customer.customerId)"
})
-
+
# Passwords Never Expire
$WidgetData.add([PSCustomObject]@{
Value = $(if ($BPAData.PasswordNeverExpires -eq $True) {
- $ResultColour = '#26A644'
+ $ResultColour = '#26A644'
''
} else {
- $ResultColour = '#D53948'
+ $ResultColour = '#D53948'
''
}
)
@@ -2111,10 +2111,10 @@ function Invoke-NinjaOneTenantSync {
# oAuth App Consent
$WidgetData.add([PSCustomObject]@{
Value = $(if ($BPAData.OAuthAppConsent -eq $True) {
- $ResultColour = '#26A644'
+ $ResultColour = '#26A644'
''
} else {
- $ResultColour = '#D53948'
+ $ResultColour = '#D53948'
''
}
)
@@ -2122,7 +2122,7 @@ function Invoke-NinjaOneTenantSync {
Colour = $ResultColour
Link = "https://entra.microsoft.com/$($Customer.customerId)/#view/Microsoft_AAD_IAM/ConsentPoliciesMenuBlade/~/UserSettings"
})
-
+
}
# Blocked Senders
@@ -2146,7 +2146,7 @@ function Invoke-NinjaOneTenantSync {
Colour = '#CCCCCC'
Link = "https://$CIPPUrl/identity/administration/users?customerId=$($Customer.customerId)"
})
-
+
# Devices
$WidgetData.add([PSCustomObject]@{
Value = ($Devices | Measure-Object).count
@@ -2211,11 +2211,11 @@ function Invoke-NinjaOneTenantSync {
Link = "https://entra.microsoft.com/$($Customer.customerId)/#view/Microsoft_AAD_IAM/DirectoriesADConnectBlade"
})
-
-
+
+
Write-Host 'Summary Details'
$SummaryDetailsCardHTML = Get-NinjaOneWidgetCard -Data $WidgetData -Icon 'fas fa-building' -SmallCols 2 -MedCols 3 -LargeCols 4 -XLCols 6 -NoCard
@@ -2223,15 +2223,15 @@ function Invoke-NinjaOneTenantSync {
# Create the Tenant Summary Field
Write-Host 'Complete Tenant Summary'
$TenantSummaryHTML = ' ' + $SummaryDetailsCardHTML + ' ' +
- '' +
- ' ' + $TenantSummaryCard +
+ ' ' +
+ ' ' + $TenantSummaryCard +
' ' + $LicensesSummaryCardHTML +
- ' ' + $DeviceSummaryCardHTML +
+ ' ' + $DeviceSummaryCardHTML +
' ' + $CIPPStandardsSummaryCardHTML +
- ' ' + $SecureScoreSummaryCardHTML +
- ' ' + $UserSummaryCardHTML +
+ ' ' + $SecureScoreSummaryCardHTML +
+ ' ' + $UserSummaryCardHTML +
' '
-
+
$NinjaOrgUpdate | Add-Member -NotePropertyName $MappedFields.TenantSummary -NotePropertyValue @{'html' = $TenantSummaryHTML }
@@ -2241,7 +2241,7 @@ function Invoke-NinjaOneTenantSync {
if ($MappedFields.UsersSummary) {
Write-Host 'User Details Section'
- $UsersTableFornatted = $ParsedUsers | Sort-Object name | Select-Object -First 100 Name,
+ $UsersTableFornatted = $ParsedUsers | Sort-Object name | Select-Object -First 100 Name,
@{n = 'User Principal Name'; e = { $_.UPN } },
#Aliases,
Licenses,
@@ -2250,7 +2250,7 @@ function Invoke-NinjaOneTenantSync {
@{n = 'Devices (Last Login)'; e = { $_.Devices } },
Actions
-
+
$UsersTableHTML = $UsersTableFornatted | ConvertTo-Html -As Table -Fragment
$UsersTableHTML = ([System.Web.HttpUtility]::HtmlDecode($UsersTableHTML) -replace ' ', ' | ') -replace ' | ', ' | '
@@ -2270,47 +2270,47 @@ function Invoke-NinjaOneTenantSync {
} else {
$Overflow = ''
}
-
+
$NinjaOrgUpdate | Add-Member -NotePropertyName $MappedFields.UsersSummary -NotePropertyValue @{'html' = $Overflow + $UsersTableHTML }
}
-
+
Write-Host 'Posting Details'
-
+
$Token = Get-NinjaOneToken -configuration $Configuration
Write-Host "Ninja Body: $($NinjaOrgUpdate | ConvertTo-Json -Depth 100)"
- $Result = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/$($MappedTenant.NinjaOne)/custom-fields" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ($NinjaOrgUpdate | ConvertTo-Json -Depth 100)
+ $Result = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/$($MappedTenant.IntegrationId)/custom-fields" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ($NinjaOrgUpdate | ConvertTo-Json -Depth 100)
Write-Host 'Cleaning Users Cache'
if (($ParsedUsers | Measure-Object).count -gt 0) {
Remove-AzDataTableEntity @UsersTable -Entity ($ParsedUsers | Select-Object PartitionKey, RowKey)
}
-
+
Write-Host 'Cleaning Device Cache'
if (($ParsedDevices | Measure-Object).count -gt 0) {
Remove-AzDataTableEntity @DeviceTable -Entity ($ParsedDevices | Select-Object PartitionKey, RowKey)
}
-
+
Write-Host "Total Fetch Time: $((New-TimeSpan -Start $StartTime -End $FetchEnd).TotalSeconds)"
- Write-Host "Completed Total Time: $((New-TimeSpan -Start $StartTime -End (Get-Date)).TotalSeconds)"
+ Write-Host "Completed Total Time: $((New-TimeSpan -Start $StartTime -End (Get-Date)).TotalSeconds)"
# Set Last End Time
$CurrentItem | Add-Member -NotePropertyName lastEndTime -NotePropertyValue ([string]$((Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))) -Force
$CurrentItem | Add-Member -NotePropertyName lastStatus -NotePropertyValue 'Completed' -Force
Add-CIPPAzDataTableEntity @MappingTable -Entity $CurrentItem -Force
- Write-LogMessage -API 'NinjaOneSync' -user 'NinjaOneSync' -message "Completed NinjaOne Sync for $($Customer.displayName). Queued for $((New-TimeSpan -Start $StartQueueTime -End $StartTime).TotalSeconds) seconds. Data fetched in $((New-TimeSpan -Start $StartTime -End $FetchEnd).TotalSeconds) seconds. Total processing time $((New-TimeSpan -Start $StartTime -End (Get-Date)).TotalSeconds) seconds" -Sev 'info'
+ Write-LogMessage -API 'NinjaOneSync' -user 'NinjaOneSync' -message "Completed NinjaOne Sync for $($Customer.displayName). Queued for $((New-TimeSpan -Start $StartQueueTime -End $StartTime).TotalSeconds) seconds. Data fetched in $((New-TimeSpan -Start $StartTime -End $FetchEnd).TotalSeconds) seconds. Total processing time $((New-TimeSpan -Start $StartTime -End (Get-Date)).TotalSeconds) seconds" -Sev 'info'
} catch {
$Message = if ($_.ErrorDetails.Message) {
Get-NormalizedError -Message $_.ErrorDetails.Message
} else {
$_.Exception.message
- }
+ }
Write-Error "Failed NinjaOne Processing for $($Customer.displayName) Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $Message"
Write-LogMessage -API 'NinjaOneSync' -user 'NinjaOneSync' -message "Failed NinjaOne Processing for $($Customer.displayName) Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $Message" -Sev 'Error'
$CurrentItem | Add-Member -NotePropertyName lastEndTime -NotePropertyValue ([string]$((Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))) -Force
diff --git a/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneFieldMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneFieldMapping.ps1
index 4653d51ed13a..87d243b8cda1 100644
--- a/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneFieldMapping.ps1
+++ b/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneFieldMapping.ps1
@@ -6,7 +6,7 @@ function Set-NinjaOneFieldMapping {
$Request,
$TriggerMetadata
)
-
+
$SettingsTable = Get-CIPPTable -TableName NinjaOneSettings
$AddObject = @{
PartitionKey = 'NinjaConfig'
@@ -17,15 +17,15 @@ function Set-NinjaOneFieldMapping {
foreach ($Mapping in ([pscustomobject]$Request.body.mappings).psobject.properties) {
$AddObject = @{
- PartitionKey = 'NinjaFieldMapping'
- RowKey = "$($mapping.name)"
- 'NinjaOne' = "$($mapping.value.value)"
- 'NinjaOneName' = "$($mapping.value.label)"
+ PartitionKey = 'NinjaOneFieldMapping'
+ RowKey = "$($mapping.name)"
+ IntegrationId = "$($mapping.value.value)"
+ IntegrationName = "$($mapping.value.label)"
}
Add-AzDataTableEntity @CIPPMapping -Entity $AddObject -Force
- Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "Added mapping for $($mapping.name)." -Sev 'Info'
+ Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "Added mapping for $($mapping.name)." -Sev 'Info'
}
- $Result = [pscustomobject]@{'Results' = "Successfully edited mapping table." }
+ $Result = [pscustomobject]@{'Results' = 'Successfully edited mapping table.' }
Return $Result
}
\ No newline at end of file
diff --git a/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1
index ee09580b94bf..43b1c597e3b0 100644
--- a/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1
+++ b/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1
@@ -6,18 +6,18 @@ function Set-NinjaOneOrgMapping {
$Request
)
- Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'NinjaOrgsMapping'" | ForEach-Object {
+ Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'NinjaOneMapping'" | ForEach-Object {
Remove-AzDataTableEntity @CIPPMapping -Entity $_
}
foreach ($Mapping in ([pscustomobject]$Request.body.mappings).psobject.properties) {
$AddObject = @{
- PartitionKey = 'NinjaOrgsMapping'
- RowKey = "$($mapping.name)"
- 'NinjaOne' = "$($mapping.value.value)"
- 'NinjaOneName' = "$($mapping.value.label)"
+ PartitionKey = 'NinjaOneMapping'
+ RowKey = "$($mapping.name)"
+ IntegrationId = "$($mapping.value.value)"
+ IntegrationName = "$($mapping.value.label)"
}
Add-AzDataTableEntity @CIPPMapping -Entity $AddObject -Force
- Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "Added mapping for $($mapping.name)." -Sev 'Info'
+ Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "Added mapping for $($mapping.name)." -Sev 'Info'
}
$Result = [pscustomobject]@{'Results' = 'Successfully edited mapping table.' }
diff --git a/Scheduler_UserTasks/function.json b/Scheduler_UserTasks/function.json
index f7af84092121..017acb166958 100644
--- a/Scheduler_UserTasks/function.json
+++ b/Scheduler_UserTasks/function.json
@@ -2,7 +2,7 @@
"bindings": [
{
"name": "Timer",
- "schedule": "0 */15 * * * *",
+ "schedule": "0 */5 * * * *",
"direction": "in",
"type": "timerTrigger"
},
| | | |