diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000000..c2795fa0dd8c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "editorconfig.editorconfig" + ] +} 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/CIPPCore.psm1 b/Modules/CIPPCore/CIPPCore.psm1 index f69a353414d9..12f13762d19c 100644 --- a/Modules/CIPPCore/CIPPCore.psm1 +++ b/Modules/CIPPCore/CIPPCore.psm1 @@ -4,8 +4,7 @@ $Functions = $Public + $Private foreach ($import in @($Functions)) { try { . $import.FullName - } - catch { + } catch { Write-Error -Message "Failed to import function $($import.FullName): $_" } } diff --git a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 index f0f4c6badf6d..f41f2729a5c8 100644 --- a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 @@ -6,24 +6,25 @@ 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') { $RequiredResourceAccess = (Get-Content '.\SAMManifest.json' | ConvertFrom-Json).requiredResourceAccess } - $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -skipTokenCache $true -tenantid $Tenantfilter + $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -skipTokenCache $true -tenantid $Tenantfilter -NoAuthCheck $true $ourSVCPrincipal = $ServicePrincipalList | Where-Object -Property AppId -EQ $ApplicationId if (!$ourSVCPrincipal) { #Our Service Principal isn't available yet. We do a sleep and reexecute after 3 seconds. Start-Sleep -Seconds 5 - $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -skipTokenCache $true -tenantid $Tenantfilter + $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -skipTokenCache $true -tenantid $Tenantfilter -NoAuthCheck $true $ourSVCPrincipal = $ServicePrincipalList | Where-Object -Property AppId -EQ $ApplicationId } $Results = [System.Collections.ArrayList]@() - $CurrentRoles = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals/$($ourSVCPrincipal.id)/appRoleAssignments" -tenantid $Tenantfilter -skipTokenCache $true + $CurrentRoles = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals/$($ourSVCPrincipal.id)/appRoleAssignments" -tenantid $Tenantfilter -skipTokenCache $true -NoAuthCheck $true $Grants = foreach ($App in $RequiredResourceAccess) { $svcPrincipalId = $ServicePrincipalList | Where-Object -Property AppId -EQ $App.resourceAppId @@ -40,12 +41,13 @@ function Add-CIPPApplicationPermission { $counter = 0 foreach ($Grant in $Grants) { try { - $SettingsRequest = New-GraphPOSTRequest -body ($Grant | ConvertTo-Json) -uri "https://graph.microsoft.com/beta/servicePrincipals/$($ourSVCPrincipal.id)/appRoleAssignedTo" -tenantid $Tenantfilter -type POST + $SettingsRequest = New-GraphPOSTRequest -body (ConvertTo-Json -InputObject $Grant -Depth 5) -uri "https://graph.microsoft.com/beta/servicePrincipals/$($ourSVCPrincipal.id)/appRoleAssignedTo" -tenantid $Tenantfilter -type POST -NoAuthCheck $true $counter++ } catch { - $Results.add("Failed to grant $($Grant.appRoleId) to $($Grant.resourceId): $($_.Exception.Message)") | Out-Null + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + $Results.add("Failed to grant $($Grant.appRoleId) to $($Grant.resourceId): $ErrorMessage") | Out-Null } } "Added $counter Application permissions to $($ourSVCPrincipal.displayName)" return $Results -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 index befa8155df6c..38c7e2c8c1f6 100644 --- a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 @@ -6,54 +6,139 @@ 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') { + if ($_.Exception.ErrorCode -eq 'PropertyValueTooLarge' -or $_.Exception.ErrorCode -eq 'EntityTooLarge' -or $_.Exception.ErrorCode -eq 'RequestBodyTooLarge') { 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 -Compress).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-Information "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-Information "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)." + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + throw "Error processing entity: $ErrorMessage Linenumber: $($_.InvocationInfo.ScriptLineNumber)" } } else { - Write-Host "THE ERROR IS $($_.Exception.ErrorCode)" - + Write-Information "THE ERROR IS $($_.Exception.ErrorCode). The size of the entity is $entitySize." throw $_ } } diff --git a/Modules/CIPPCore/Public/Add-CIPPBPAField.ps1 b/Modules/CIPPCore/Public/Add-CIPPBPAField.ps1 index 1cc394c9fbf5..bed52e8cc786 100644 --- a/Modules/CIPPCore/Public/Add-CIPPBPAField.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPBPAField.ps1 @@ -30,7 +30,7 @@ function Add-CIPPBPAField { $Result["$fieldName"] = [bool]$FieldValue } 'JSON' { - if ($FieldValue -eq $null) { $JsonString = '{}' } else { $JsonString = (ConvertTo-Json -Depth 15 -InputObject $FieldValue -Compress) } + if ($null -eq $FieldValue) { $JsonString = '{}' } else { $JsonString = (ConvertTo-Json -Depth 15 -InputObject $FieldValue -Compress) } $Result[$fieldName] = [string]$JsonString } 'string' { @@ -38,4 +38,4 @@ function Add-CIPPBPAField { } } Add-CIPPAzDataTableEntity @Table -Entity $Result -Force -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 index 921488c45f08..df1e80e1de37 100644 --- a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 @@ -3,29 +3,51 @@ 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 + + if ($Tenantfilter -eq $env:TenantID) { + $RequiredResourceAccess = $RequiredResourceAccess + ($AdditionalPermissions | Where-Object { $RequiredResourceAccess.resourceAppId -notcontains $_.resourceAppId }) + } else { + # remove the partner center permission if not pushing to partner tenant + $RequiredResourceAccess = $RequiredResourceAccess | Where-Object { $_.resourceAppId -ne 'fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd' } + } + $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 + $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -tenantid $Tenantfilter -skipTokenCache $true -NoAuthCheck $true $ourSVCPrincipal = $ServicePrincipalList | Where-Object -Property AppId -EQ $ApplicationId $Results = [System.Collections.ArrayList]@() - $CurrentDelegatedScopes = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals/$($ourSVCPrincipal.id)/oauth2PermissionGrants" -skipTokenCache $true -tenantid $Tenantfilter + $CurrentDelegatedScopes = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals/$($ourSVCPrincipal.id)/oauth2PermissionGrants" -skipTokenCache $true -tenantid $Tenantfilter -NoAuthCheck $true - 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 ' ' + } 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) { @@ -35,7 +57,7 @@ function Add-CIPPDelegatedPermission { resourceId = $svcPrincipalId.id scope = $NewScope } | ConvertTo-Json -Compress - $CreateRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/v1.0/oauth2PermissionGrants' -tenantid $Tenantfilter -body $Createbody -type POST + $CreateRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/v1.0/oauth2PermissionGrants' -tenantid $Tenantfilter -body $Createbody -type POST -NoAuthCheck $true $Results.add("Successfully added permissions for $($svcPrincipalId.displayName)") | Out-Null } else { $compare = Compare-Object -ReferenceObject $OldScope.scope.Split(' ') -DifferenceObject $NewScope.Split(' ') @@ -46,7 +68,7 @@ function Add-CIPPDelegatedPermission { $Patchbody = @{ scope = "$NewScope" } | ConvertTo-Json -Compress - $Patchrequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/oauth2PermissionGrants/$($OldScope.id)" -tenantid $Tenantfilter -body $Patchbody -type PATCH + $Patchrequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/oauth2PermissionGrants/$($OldScope.id)" -tenantid $Tenantfilter -body $Patchbody -type PATCH -NoAuthCheck $true $Results.add("Successfully updated permissions for $($svcPrincipalId.displayName): $($NewScope)") | Out-Null } } diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index 952e3a68bc93..4b08eda522bb 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -2,19 +2,28 @@ function Add-CIPPScheduledTask { [CmdletBinding()] param( [pscustomobject]$Task, - [bool]$Hidden + [bool]$Hidden, + $DisallowDuplicateName = $false, + [string]$SyncType = $null ) $Table = Get-CIPPTable -TableName 'ScheduledTasks' + if ($DisallowDuplicateName) { + $Filter = "PartitionKey eq 'ScheduledTask' and Name eq '$($Task.Name)'" + $ExistingTask = (Get-CIPPAzDataTableEntity @Table -Filter $Filter) + if ($ExistingTask) { + return "Task with name $($Task.Name) already exists" + } + } + $propertiesToCheck = @('Webhook', 'Email', 'PSA') $PostExecution = ($propertiesToCheck | Where-Object { $task.PostExecution.$_ -eq $true }) -join ',' $Parameters = [System.Collections.Hashtable]@{} - foreach ($Key in $task.Parameters.Keys) { + foreach ($Key in $task.Parameters.PSObject.Properties.Name) { $Param = $task.Parameters.$Key - if ($Param.Key) { + if ($Param -is [System.Collections.IDictionary]) { $ht = @{} - foreach ($p in $Param) { - Write-Host $p.Key + foreach ($p in $Param.GetEnumerator()) { $ht[$p.Key] = $p.Value } $Parameters[$Key] = [PSCustomObject]$ht @@ -22,6 +31,7 @@ function Add-CIPPScheduledTask { $Parameters[$Key] = $Param } } + $Parameters = ($Parameters | ConvertTo-Json -Depth 10 -Compress) $AdditionalProperties = [System.Collections.Hashtable]@{} foreach ($Prop in $task.AdditionalProperties) { @@ -34,6 +44,13 @@ function Add-CIPPScheduledTask { } else { $RowKey = $Task.RowKey } + + $Recurrence = if ([string]::IsNullOrEmpty($task.Recurrence.value)) { + $task.Recurrence + } else { + $task.Recurrence.value + } + $entity = @{ PartitionKey = [string]'ScheduledTask' TaskState = [string]'Planned' @@ -43,16 +60,20 @@ function Add-CIPPScheduledTask { Command = [string]$task.Command.value Parameters = [string]$Parameters ScheduledTime = [string]$task.ScheduledTime - Recurrence = [string]$task.Recurrence.value + Recurrence = [string]$Recurrence PostExecution = [string]$PostExecution AdditionalProperties = [string]$AdditionalProperties 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)" + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + return "Could not add task: $ErrorMessage" } 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..815fe9e59248 --- /dev/null +++ b/Modules/CIPPCore/Public/AdditionalPermissions.json @@ -0,0 +1,6 @@ +[ + { + "resourceAppId": "00000003-0000-0ff1-ce00-000000000000", + "resourceAccess": [{ "id": "AllProfiles.Manage", "type": "Scope" }] + } +] diff --git a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertDepTokenExpiry.ps1 b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertDepTokenExpiry.ps1 index 1bf1e9c4463e..ec55c10bb283 100644 --- a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertDepTokenExpiry.ps1 +++ b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertDepTokenExpiry.ps1 @@ -16,7 +16,8 @@ function Get-CIPPAlertDepTokenExpiry { $DepTokens = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/depOnboardingSettings' -tenantid $TenantFilter).value $AlertData = foreach ($Dep in $DepTokens) { if ($Dep.tokenExpirationDateTime -lt (Get-Date).AddDays(30) -and $Dep.tokenExpirationDateTime -gt (Get-Date).AddDays(-7)) { - 'Apple Device Enrollment Program token expiring on {0}' -f $Dep.tokenExpirationDateTime + $Message = 'Apple Device Enrollment Program token expiring on {0}' -f $Dep.tokenExpirationDateTime + $Dep | Select-Object -Property tokenName, @{Name = 'Message'; Expression = { $Message } } } } Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData diff --git a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertSoftDeletedMailboxes.ps1 b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertSoftDeletedMailboxes.ps1 new file mode 100644 index 000000000000..68070c18497e --- /dev/null +++ b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertSoftDeletedMailboxes.ps1 @@ -0,0 +1,28 @@ +function Get-CIPPAlertSoftDeletedMailboxes { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $false)] + [Alias('input')] + $InputValue, + $TenantFilter + ) + + $Select = 'ExchangeGuid,ArchiveGuid,WhenSoftDeleted,UserPrincipalName,IsInactiveMailbox' + + try { + $SoftDeletedMailBoxes = New-ExoRequest -tenantid $TenantFilter -cmdlet 'get-mailbox' -cmdParams @{SoftDeletedMailbox = $true} -Select $Select | Select-Object ExchangeGuid, ArchiveGuid, WhenSoftDeleted, @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } }, IsInactiveMailbox + + # Filter out the mailboxes where IsInactiveMailbox is $true + $AlertData = $SoftDeletedMailBoxes | Where-Object { $_.IsInactiveMailbox -ne $true } + + # Write the alert trace with the filtered data + Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData + + } catch { + Write-AlertMessage -tenant $($TenantFilter) -message "Failed to check for soft deleted mailboxes in $($TenantFilter): $(Get-NormalizedError -message $_.Exception.message)" + } +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertVppTokenExpiry.ps1 b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertVppTokenExpiry.ps1 index 224d23857005..9767e24fd5c4 100644 --- a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertVppTokenExpiry.ps1 +++ b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertVppTokenExpiry.ps1 @@ -15,10 +15,12 @@ function Get-CIPPAlertVppTokenExpiry { $VppTokens = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceAppManagement/vppTokens' -tenantid $TenantFilter).value $AlertData = foreach ($Vpp in $VppTokens) { if ($Vpp.state -ne 'valid') { - 'Apple Volume Purchase Program Token is not valid, new token required' + $Message = 'Apple Volume Purchase Program Token is not valid, new token required' + $Vpp | Select-Object -Property organizationName, appleId, vppTokenAccountType, @{Name = 'Message'; Expression = { $Message } } } if ($Vpp.expirationDateTime -lt (Get-Date).AddDays(30) -and $Vpp.expirationDateTime -gt (Get-Date).AddDays(-7)) { - 'Apple Volume Purchase Program token expiring on {0}' -f $Vpp.expirationDateTime + $Message = 'Apple Volume Purchase Program token expiring on {0}' -f $Vpp.expirationDateTime + $Vpp | Select-Object -Property organizationName, appleId, vppTokenAccountType, @{Name = 'Message'; Expression = { $Message } } } } Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData @@ -28,4 +30,4 @@ function Get-CIPPAlertVppTokenExpiry { } catch { # Error handling } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Assert-CippVersion.ps1 b/Modules/CIPPCore/Public/Assert-CippVersion.ps1 index 83f77f43edd0..621c1f6d4cac 100644 --- a/Modules/CIPPCore/Public/Assert-CippVersion.ps1 +++ b/Modules/CIPPCore/Public/Assert-CippVersion.ps1 @@ -24,4 +24,4 @@ function Assert-CippVersion { OutOfDateCIPP = ([version]$RemoteCIPPVersion -gt [version]$CIPPVersion) OutOfDateCIPPAPI = ([version]$RemoteAPIVersion -gt [version]$APIVersion) } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Clear-CippDurables.ps1 b/Modules/CIPPCore/Public/Clear-CippDurables.ps1 index eb1949a39078..089ca10282a7 100644 --- a/Modules/CIPPCore/Public/Clear-CippDurables.ps1 +++ b/Modules/CIPPCore/Public/Clear-CippDurables.ps1 @@ -59,4 +59,4 @@ function Clear-CippDurables { } $null = Get-CippTable -TableName ('{0}History' -f $FunctionName) Write-Information 'Durable Orchestrators and Queues have been cleared' -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-GetTenantDomains.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-GetTenantDomains.ps1 index 5fb9c64cdad1..071bb5289139 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-GetTenantDomains.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-GetTenantDomains.ps1 @@ -2,6 +2,6 @@ function Push-GetTenantDomains { Param($Item) $DomainTable = Get-CippTable -tablename 'Domains' $Filter = "PartitionKey eq 'TenantDomains' and TenantGUID eq '{0}'" -f $Item.TenantGUID - $Domains = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter -Property RowKey | Select-Object RowKey, @{n = 'FunctionName'; exp = { 'DomainAnalyserDomain' } } + $Domains = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter -Property PartitionKey, RowKey | Select-Object RowKey, @{n = 'FunctionName'; exp = { 'DomainAnalyserDomain' } } return @($Domains) -} \ No newline at end of file +} 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-ExecOnboardTenantQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 index 3e597fab6553..0e3d211d6718 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 @@ -264,14 +264,14 @@ Function Push-ExecOnboardTenantQueue { $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop - $IsExcluded = (Get-Tenants -SkipList | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Measure-Object).Count -gt 0 + $ExcludedTenant = Get-Tenants -SkipList | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } + $IsExcluded = ($ExcludedTenant | Measure-Object).Count -gt 0 if ($IsExcluded) { - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Tenant is excluded from CIPP, onboarding cannot continue.' }) + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = ('Tenant is excluded from CIPP, onboarding cannot continue. Remove the exclusion from "{0}" ({1})' -f $ExcludedTenant.displayName, $ExcludedTenant.customerId) }) $TenantOnboarding.Status = 'failed' $OnboardingSteps.Step4.Status = 'failed' $OnboardingSteps.Step4.Message = 'Tenant excluded from CIPP, remove the exclusion and retry onboarding.' } else { - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Clearing tenant cache' }) $y = 0 do { 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/Activity Triggers/Push-GetPendingWebhooks.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-GetPendingWebhooks.ps1 index 4b292fdd7041..2cdf56c8a6b5 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-GetPendingWebhooks.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-GetPendingWebhooks.ps1 @@ -5,9 +5,9 @@ function Push-GetPendingWebhooks { #> Param($Item) $Table = Get-CIPPTable -TableName WebhookIncoming - $Webhooks = Get-CIPPAzDataTableEntity @Table -Property RowKey, FunctionName -First 10000 + $Webhooks = Get-CIPPAzDataTableEntity @Table -Property PartitionKey, RowKey, FunctionName -First 10000 $WebhookCount = ($Webhooks | Measure-Object).Count $Message = 'Processing {0} webhooks' -f $WebhookCount Write-LogMessage -API 'Webhooks' -message $Message -sev Info return $Webhooks -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 index 0f4c03e1c833..873af42e2e43 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 @@ -7,9 +7,17 @@ function Push-AuditLogTenant { $WebhookTable = Get-CippTable -tablename 'webhookTable' $Webhooks = Get-CIPPAzDataTableEntity @WebhookTable -Filter "PartitionKey eq '$($Item.TenantFilter)' and Version eq '3'" | Where-Object { $_.Resource -match '^Audit' } $ExistingBundles = Get-CIPPAzDataTableEntity @AuditBundleTable -Filter "PartitionKey eq '$($Item.TenantFilter)' and ContentType eq '$ContentType'" + $ConfigTable = Get-CIPPTable -TableName 'WebhookRules' + $ConfigEntries = Get-CIPPAzDataTableEntity @ConfigTable $NewBundles = [System.Collections.Generic.List[object]]::new() foreach ($Webhook in $Webhooks) { + # only process webhooks that are configured in the webhookrules table + $Configuration = $ConfigEntries | Where-Object { ($_.Tenants -match $TenantFilter -or $_.Tenants -match 'AllTenants') } + if ($Configuration.Type -notcontains $Webhook.Resource) { + continue + } + $TenantFilter = $Webhook.PartitionKey $LogType = $Webhook.Resource Write-Information "Querying for $LogType on $TenantFilter" diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDurableFunctions.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDurableFunctions.ps1 index 09da6bd2c990..f01062fe5720 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDurableFunctions.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDurableFunctions.ps1 @@ -51,7 +51,7 @@ function Invoke-ExecDurableFunctions { if ($Request.Query.PartitionKey) { $HistoryTable = Get-CippTable -TableName ('{0}History' -f $FunctionName) $Filter = "PartitionKey eq '{0}'" -f $Request.Query.PartitionKey - $History = Get-CippAzDataTableEntity @HistoryTable -Filter $Filter -Property RowKey, Timestamp, EventType, Name, IsPlayed, OrchestrationStatus | Select-Object * -ExcludeProperty ETag + $History = Get-CippAzDataTableEntity @HistoryTable -Filter $Filter -Property PartitionKey, RowKey, Timestamp, EventType, Name, IsPlayed, OrchestrationStatus | Select-Object * -ExcludeProperty ETag $Body = [PSCustomObject]@{ Results = @($History) @@ -173,4 +173,4 @@ function Invoke-ExecDurableFunctions { StatusCode = [HttpStatusCode]::OK Body = $Body }) -} \ 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/Core/Invoke-ExecSetCIPPAutoBackup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecSetCIPPAutoBackup.ps1 index 2d04df48933c..b705c1da9fc5 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecSetCIPPAutoBackup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecSetCIPPAutoBackup.ps1 @@ -19,14 +19,14 @@ Function Invoke-ExecSetCIPPAutoBackup { } Remove-AzDataTableEntity @Table -Entity $task | Out-Null - $TaskBody = @{ + $TaskBody = [pscustomobject]@{ TenantFilter = 'AllTenants' Name = 'Automated CIPP Backup' Command = @{ value = 'New-CIPPBackup' label = 'New-CIPPBackup' } - Parameters = @{ backupType = 'CIPP' } + Parameters = [pscustomobject]@{ backupType = 'CIPP' } ScheduledTime = $unixtime Recurrence = '1d' } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionMapping.ps1 similarity index 80% rename from Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 rename to Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionMapping.ps1 index 77b8e277c780..32a21c2119f4 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionMapping.ps1 @@ -20,17 +20,20 @@ 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 } } } @@ -38,15 +41,23 @@ Function Invoke-ExecExtensionMapping { 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 + Register-CIPPExtensionScheduledTasks + } + 'HuduFields' { + $Body = Set-ExtensionFieldMapping -CIPPMapping $Table -APIName $APIName -Request $Request -Extension 'Hudu' + Register-CIPPExtensionScheduledTasks + } } } } catch { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionSync.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionSync.ps1 new file mode 100644 index 000000000000..911ce71d7e14 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionSync.ps1 @@ -0,0 +1,100 @@ +using namespace System.Net + +Function Invoke-ExecExtensionSync { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + CIPP.Extension.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + switch ($Request.Query.Extension) { + 'Gradient' { + try { + Write-LogMessage -API 'Scheduler_Billing' -tenant 'none' -message 'Starting billing processing.' -sev Info + $Table = Get-CIPPTable -TableName Extensionsconfig + $Configuration = (Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -Depth 10 + foreach ($ConfigItem in $Configuration.psobject.properties.name) { + switch ($ConfigItem) { + 'Gradient' { + If ($Configuration.Gradient.enabled -and $Configuration.Gradient.BillingEnabled) { + Push-OutputBinding -Name gradientqueue -Value 'LetsGo' + $Results = [pscustomobject]@{'Results' = 'Successfully started Gradient Sync' } + } + } + } + } + } catch { + $Results = [pscustomobject]@{'Results' = "Could not start Gradient Sync: $($_.Exception.Message)" } + + Write-LogMessage -API 'Scheduler_Billing' -tenant 'none' -message "Could not start billing processing $($_.Exception.Message)" -sev Error + } + } + + 'NinjaOne' { + try { + $Table = Get-CIPPTable -TableName NinjaOneSettings + + $CIPPMapping = Get-CIPPTable -TableName CippMapping + $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 } + if (($Tenant | Measure-Object).count -eq 1) { + $Batch = [PSCustomObject]@{ + 'NinjaAction' = 'SyncTenant' + 'MappedTenant' = $Tenant + 'FunctionName' = 'NinjaOneQueue' + } + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'NinjaOneOrchestrator' + Batch = @($Batch) + } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress) + + $Results = [pscustomobject]@{'Results' = "NinjaOne Synchronization Queued for $($Tenant.IntegrationName)" } + } else { + $Results = [pscustomobject]@{'Results' = 'Tenant was not found.' } + } + + } else { + $Batch = [PSCustomObject]@{ + 'NinjaAction' = 'SyncTenants' + 'FunctionName' = 'NinjaOneQueue' + } + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'NinjaOneOrchestrator' + Batch = @($Batch) + } + #Write-Host ($InputObject | ConvertTo-Json) + $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 Queuing $(($TenantsToProcess | Measure-Object).count) Tenants" } + + } + } catch { + $Results = [pscustomobject]@{'Results' = "Could not start NinjaOne Sync: $($_.Exception.Message)" } + Write-LogMessage -API 'Scheduler_Billing' -tenant 'none' -message "Could not start NinjaOne Sync $($_.Exception.Message)" -sev Error + } + } + 'Hudu' { + Register-CIPPExtensionScheduledTasks -Reschedule + $Results = [pscustomobject]@{'Results' = 'Extension sync tasks have been rescheduled and will start within 15 minutes' } + } + + } + + + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Results + }) + +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionTest.ps1 similarity index 82% rename from Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 rename to Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionTest.ps1 index 78e82e78121a..1262ab6260a9 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/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,17 +35,26 @@ 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' } } } + 'Hudu' { + Connect-HuduAPI -configuration $Configuration.Hudu + $Version = Get-HuduAppInfo + if ($Version.version) { + $Results = [pscustomobject]@{'Results' = ('Successfully Connected to Hudu, version: {0}' -f $Version.version) } + } else { + $Results = [pscustomobject]@{'Results' = 'Failed to connect to Hudu' } + } + } } } catch { $Results = [pscustomobject]@{'Results' = "Failed to connect: $($_.Exception.Message) $($_.InvocationInfo.ScriptLineNumber)" } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionsConfig.ps1 similarity index 53% rename from Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 rename to Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionsConfig.ps1 index fd3b8764959d..2484fd9885dd 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/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 intance has at least version 5.6 - if ($request.body.NinjaOne) { + # Check if NinjaOne URL is set correctly and the instance has at least version 5.6 + 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,35 @@ 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' + if ($Request.Body.$APIKey.PSObject.Properties -notcontains 'APIKey') { + $Request.Body.$APIKey | Add-Member -MemberType NoteProperty -Name APIKey -Value 'SentToKeyVault' + } else { + $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' @@ -66,6 +72,18 @@ Function Invoke-ExecExtensionsConfig { } Add-CIPPAzDataTableEntity @Table -Entity $Config -Force | Out-Null + + #Write-Information ($Request.Headers | ConvertTo-Json) + $AddObject = @{ + PartitionKey = 'InstanceProperties' + RowKey = 'CIPPURL' + Value = [string]([System.Uri]$Request.Headers.'x-ms-original-url').Host + } + Write-Information ($AddObject | ConvertTo-Json -Compress) + $ConfigTable = Get-CIPPTable -tablename 'Config' + Add-AzDataTableEntity @ConfigTable -Entity $AddObject -Force + + Register-CIPPExtensionScheduledTasks "Successfully set the configuration. $AddedText" } catch { "Failed to set configuration: $($_.Exception.message) Linenumber: $($_.InvocationInfo.ScriptLineNumber)" diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ListExtensionSync.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ListExtensionSync.ps1 new file mode 100644 index 000000000000..8ccf26abd1cc --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ListExtensionSync.ps1 @@ -0,0 +1,59 @@ +using namespace System.Net + +Function Invoke-ListExtensionSync { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + CIPP.Extension.Read + #> + [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.' + $ScheduledTasksTable = Get-CIPPTable -TableName 'ScheduledTasks' + $ScheduledTasks = Get-CIPPAzDataTableEntity @ScheduledTasksTable -Filter 'Hidden eq true' | Where-Object { $_.Command -match 'CippExtension' } + + $AllowedTenants = Test-CIPPAccess -Request $Request -TenantList + $TenantList = Get-Tenants -IncludeErrors + $AllTasksArrayList = [system.collections.generic.list[object]]::new() + + foreach ($Task in $ScheduledTasks) { + if ($Task.Results -and (Test-Json -Json $Task.Results -ErrorAction SilentlyContinue)) { + $Results = $Task.Results | ConvertFrom-Json + } else { + $Results = $Task.Results + } + + $TaskEntry = [PSCustomObject]@{ + RowKey = $Task.RowKey + PartitionKey = $Task.PartitionKey + Tenant = $Task.Tenant + Name = $Task.Name + SyncType = $Task.SyncType + ScheduledTime = $Task.ScheduledTime + ExecutedTime = $Task.ExecutedTime + RepeatsEvery = $Task.Recurrence + Results = $Results + } + + if ($AllowedTenants -notcontains 'AllTenants') { + $Tenant = $TenantList | Where-Object -Property defaultDomainName -EQ $Task.Tenant + if ($AllowedTenants -contains $Tenant.customerId) { + $AllTasksArrayList.Add($TaskEntry) + } + } else { + $AllTasksArrayList.Add($TaskEntry) + } + } + Write-Host ($AllTasksArrayList | ConvertTo-Json -Depth 5 -Compress) + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = ConvertTo-Json -Depth 5 -InputObject $($AllTasksArrayList) + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-AddScheduledItem.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-AddScheduledItem.ps1 index 50860f6e034b..86a74a58a459 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-AddScheduledItem.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-AddScheduledItem.ps1 @@ -9,12 +9,12 @@ Function Invoke-AddScheduledItem { #> [CmdletBinding()] param($Request, $TriggerMetadata) - if ($Request.query.hidden -eq $null) { + if ($null -eq $Request.query.hidden) { $hidden = $false } else { $hidden = $true } - $Result = Add-CIPPScheduledTask -Task $Request.body -hidden $hidden + $Result = Add-CIPPScheduledTask -Task $Request.body -hidden $hidden -DisallowDuplicateName $Request.query.DisallowDuplicateName Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message $Result -Sev 'Info' 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/Scheduler/Invoke-RemoveScheduledItem.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 index f21b1b88e275..2da498adaf36 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 @@ -10,6 +10,9 @@ Function Invoke-RemoveScheduledItem { [CmdletBinding()] param($Request, $TriggerMetadata) + $APIName = 'RemoveScheduledItem' + $User = $request.headers.'x-ms-client-principal' + $task = @{ RowKey = $Request.Query.ID PartitionKey = 'ScheduledTask' @@ -17,7 +20,7 @@ Function Invoke-RemoveScheduledItem { $Table = Get-CIPPTable -TableName 'ScheduledTasks' Remove-AzDataTableEntity @Table -Entity $task - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Task removed: $($task.Name)" -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Task removed: $($task.RowKey)" -Sev 'Info' Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAccessChecks.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAccessChecks.ps1 index f655b21c2a91..88a663ad990b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAccessChecks.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAccessChecks.ps1 @@ -21,7 +21,7 @@ Function Invoke-ExecAccessChecks { } if ($Request.Query.Tenants -eq 'true') { - $Results = Test-CIPPAccessTenant -TenantCSV $Request.Body.tenantid + $Results = Test-CIPPAccessTenant -TenantCSV $Request.Body.tenantid -ExecutingUser $Request.Headers.'x-ms-client-principal' } if ($Request.Query.GDAP -eq 'true') { $Results = Test-CIPPGDAPRelationships 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 deleted file mode 100644 index 2f069988996a..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionSync.ps1 +++ /dev/null @@ -1,97 +0,0 @@ -using namespace System.Net - -Function Invoke-ExecExtensionSync { - <# - .FUNCTIONALITY - Entrypoint - .ROLE - CIPP.Extension.ReadWrite - #> - [CmdletBinding()] - param($Request, $TriggerMetadata) - - $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - - if ($Request.Query.Extension -eq 'Gradient') { - try { - Write-LogMessage -API 'Scheduler_Billing' -tenant 'none' -message 'Starting billing processing.' -sev Info - $Table = Get-CIPPTable -TableName Extensionsconfig - $Configuration = (Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -Depth 10 - foreach ($ConfigItem in $Configuration.psobject.properties.name) { - switch ($ConfigItem) { - 'Gradient' { - If ($Configuration.Gradient.enabled -and $Configuration.Gradient.BillingEnabled) { - Push-OutputBinding -Name gradientqueue -Value 'LetsGo' - $Results = [pscustomobject]@{'Results' = 'Succesfully started Gradient Sync' } - } - } - } - } - } catch { - $Results = [pscustomobject]@{'Results' = "Could not start Gradient Sync: $($_.Exception.Message)" } - - Write-LogMessage -API 'Scheduler_Billing' -tenant 'none' -message "Could not start billing processing $($_.Exception.Message)" -sev Error - } - } - - if ($Request.Query.Extension -eq 'NinjaOne') { - try { - $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 '' } - - if ($Request.Query.TenantID) { - $Tenant = $TenantsToProcess | Where-Object { $_.RowKey -eq $Request.Query.TenantID } - if (($Tenant | Measure-Object).count -eq 1) { - $Batch = [PSCustomObject]@{ - 'NinjaAction' = 'SyncTenant' - 'MappedTenant' = $Tenant - 'FunctionName' = 'NinjaOneQueue' - } - $InputObject = [PSCustomObject]@{ - OrchestratorName = 'NinjaOneOrchestrator' - Batch = @($Batch) - } - #Write-Host ($InputObject | ConvertTo-Json) - $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)" } - } else { - $Results = [pscustomobject]@{'Results' = 'Tenant was not found.' } - } - - } else { - $Batch = [PSCustomObject]@{ - 'NinjaAction' = 'SyncTenants' - 'FunctionName' = 'NinjaOneQueue' - } - $InputObject = [PSCustomObject]@{ - OrchestratorName = 'NinjaOneOrchestrator' - Batch = @($Batch) - } - #Write-Host ($InputObject | ConvertTo-Json) - $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 Queuing $(($TenantsToProcess | Measure-Object).count) Tenants" } - - } - - - } catch { - $Results = [pscustomobject]@{'Results' = "Could not start NinjaOne Sync: $($_.Exception.Message)" } - Write-LogMessage -API 'Scheduler_Billing' -tenant 'none' -message "Could not start NinjaOne Sync $($_.Exception.Message)" -sev Error - } - - } - - - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $Results - }) -clobber - -} 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/CIPP/Settings/Invoke-ExecRunBackup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRunBackup.ps1 index ef8d564acdca..afdd570d8910 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRunBackup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRunBackup.ps1 @@ -15,7 +15,7 @@ Function Invoke-ExecRunBackup { $CSVfile = New-CIPPBackup -BackupType 'CIPP' $body = [pscustomobject]@{ 'Results' = 'Created backup' - backup = $CSVfile + backup = $CSVfile.BackupData } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecDisableEmailForward.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecDisableEmailForward.ps1 deleted file mode 100644 index d2894b583ee4..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecDisableEmailForward.ps1 +++ /dev/null @@ -1,34 +0,0 @@ -using namespace System.Net - -Function Invoke-ExecDisableEmailForward { - <# - .FUNCTIONALITY - Entrypoint - .ROLE - Exchange.Mailbox.ReadWrite - #> - [CmdletBinding()] - param($Request, $TriggerMetadata) - try { - $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - $Username = $request.body.user - $Tenantfilter = $request.body.tenantfilter - $Results = try { - Set-CIPPForwarding -userid $Request.body.user -tenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' -Forward $null -keepCopy $false -ForwardingSMTPAddress $null -Disable $true - } catch { - "Could not disable forwarding message for $($username). Error: $($_.Exception.Message)" - } - - $body = [pscustomobject]@{'Results' = @($results) } - } catch { - $body = [pscustomobject]@{'Results' = @("Could not disable forwarding user: $($_.Exception.message)") } - } - - # Associate values to output bindings by calling 'Push-OutputBinding'. - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $Body - }) - -} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecEmailForward.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecEmailForward.ps1 index 9ddf5aa7b523..e64c821acd68 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecEmailForward.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecEmailForward.ps1 @@ -16,15 +16,14 @@ Function Invoke-ExecEmailForward { $ForwardingSMTPAddress = $request.body.ForwardExternal $DisableForwarding = $request.body.disableForwarding $APIName = $TriggerMetadata.FunctionName + [bool]$KeepCopy = if ($request.body.keepCopy -eq 'true') { $true } else { $false } if ($ForwardingAddress) { try { - New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-mailbox' -cmdParams @{Identity = $Username; ForwardingAddress = $ForwardingAddress ; DeliverToMailboxAndForward = [bool]$request.body.keepCopy } -Anchor $username + Set-CIPPForwarding -userid $username -tenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' -Forward $ForwardingAddress -keepCopy $KeepCopy if (-not $request.body.KeepCopy) { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Set Forwarding for $($username) to $($ForwardingAddress) and not keeping a copy" -Sev 'Info' -tenant $TenantFilter $results = "Forwarding all email for $($username) to $($ForwardingAddress) and not keeping a copy" - } elseif ($request.body.KeepCopy) { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Set Forwarding for $($username) to $($ForwardingAddress) and keeping a copy" -Sev 'Info' -tenant $TenantFilter + } else { $results = "Forwarding all email for $($username) to $($ForwardingAddress) and keeping a copy" } } catch { @@ -34,14 +33,12 @@ Function Invoke-ExecEmailForward { } } - elseif ($ForwardingSMTPAddress) { + if ($ForwardingSMTPAddress) { try { - New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-mailbox' -cmdParams @{Identity = $Username; ForwardingSMTPAddress = $ForwardingSMTPAddress ; DeliverToMailboxAndForward = [bool]$request.body.keepCopy } -Anchor $username + Set-CIPPForwarding -userid $username -tenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' -forwardingSMTPAddress $ForwardingSMTPAddress -keepCopy $KeepCopy if (-not $request.body.KeepCopy) { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Set forwarding for $($username) to $($ForwardingSMTPAddress) and not keeping a copy" -Sev 'Info' -tenant $TenantFilter $results = "Forwarding all email for $($username) to $($ForwardingSMTPAddress) and not keeping a copy" - } elseif ($request.body.KeepCopy) { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Set forwarding for $($username) to $($ForwardingSMTPAddress) and keeping a copy" -Sev 'Info' -tenant $TenantFilter + } else { $results = "Forwarding all email for $($username) to $($ForwardingSMTPAddress) and keeping a copy" } } catch { @@ -52,10 +49,9 @@ Function Invoke-ExecEmailForward { } - elseif ($DisableForwarding -eq 'True') { + if ($DisableForwarding -eq 'True') { try { - New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-Mailbox' -cmdParams @{Identity = $Username; ForwardingAddress = $null; ForwardingSMTPAddress = $null; DeliverToMailboxAndForward = $false } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Disabled Email forwarding for $($username)" -Sev 'Info' -tenant $TenantFilter + Set-CIPPForwarding -userid $username -username $username -tenantFilter $Tenantfilter -ExecutingUser $ExecutingUser -APIName $APIName -Disable $true $results = "Disabled Email Forwarding for $($username)" } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Could not disable Email forwarding for $($username)" -Sev 'Error' -tenant $TenantFilter diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecMailboxRestore.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecMailboxRestore.ps1 index fb66074d979d..7e787acacfc8 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecMailboxRestore.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecMailboxRestore.ps1 @@ -46,7 +46,7 @@ function Invoke-ExecMailboxRestore { $TenantFilter = $Request.Body.TenantFilter $RequestName = $Request.Body.RequestName $SourceMailbox = $Request.Body.SourceMailbox - $TargetMailbox = $Request.Body.TargetMailbox + $TargetMailbox = if (!$Request.Body.input) {$Request.Body.TargetMailbox} else {$Request.Body.input} $ExoRequest = @{ tenantid = $TenantFilter diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-AddWinGetApp.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-AddWinGetApp.ps1 index 8097c6d328e2..f80645694331 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-AddWinGetApp.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-AddWinGetApp.ps1 @@ -24,7 +24,7 @@ Function Invoke-AddWinGetApp { 'packageIdentifier' = "$($WinGetApp.PackageName)" 'installExperience' = @{ '@odata.type' = 'microsoft.graph.winGetAppInstallExperience' - 'runAsAccount' = 'user' + 'runAsAccount' = 'system' } } 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/Endpoint/MEM/Invoke-AddPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 index aca7c04ca269..dea49aa6c1ab 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddPolicy.ps1 @@ -25,83 +25,9 @@ Function Invoke-AddPolicy { ([pscustomobject]$Request.Body.replacemap.$tenant).psobject.properties | ForEach-Object { $RawJson = $RawJson -replace $_.name, $_.value } } try { - switch ($Request.Body.TemplateType) { - 'AppProtection' { - $PlatformType = 'deviceAppManagement' - $TemplateType = ($RawJSON | ConvertFrom-Json).'@odata.type' -replace '#microsoft.graph.', '' - $TemplateTypeURL = "$($TemplateType)s" - $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL" -tenantid $tenant - if ($displayname -in $CheckExististing.displayName) { - Throw "Policy with Display Name $($Displayname) Already exists" - } - $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL" -tenantid $tenant -type POST -body $RawJSON - } - 'deviceCompliancePolicies' { - $TemplateTypeURL = 'deviceCompliancePolicies' - $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant - if ($displayname -in $CheckExististing.displayName) { - Throw "Policy with Display Name $($Displayname) Already exists" - } - $JSON = $RawJSON | ConvertFrom-Json | Select-Object * -ExcludeProperty id, createdDateTime, lastModifiedDateTime, version, 'scheduledActionsForRule@odata.context', '@odata.context' - $JSON.scheduledActionsForRule = @($JSON.scheduledActionsForRule | Select-Object * -ExcludeProperty 'scheduledActionConfigurations@odata.context') - $RawJSON = ConvertTo-Json -InputObject $JSON -Depth 20 -Compress - Write-Host $RawJSON - $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant -type POST -body $RawJson - } - 'Admin' { - $TemplateTypeURL = 'groupPolicyConfigurations' - $CreateBody = '{"description":"' + $description + '","displayName":"' + $displayname + '","roleScopeTagIds":["0"]}' - $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant - if ($displayname -in $CheckExististing.displayName) { - Throw "Policy with Display Name $($Displayname) Already exists" - } - $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant -type POST -body $CreateBody - $UpdateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL('$($CreateRequest.id)')/updateDefinitionValues" -tenantid $tenant -type POST -body $RawJSON - } - 'Device' { - $TemplateTypeURL = 'deviceConfigurations' - $PolicyName = ($RawJSON | ConvertFrom-Json).displayName - $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant - Write-Host $PolicyName - if ($PolicyName -in $CheckExististing.displayName) { - Throw "Policy with Display Name $($Displayname) Already exists" - } - $PolicyFile = $RawJSON | ConvertFrom-Json - $Null = $PolicyFile | Add-Member -MemberType NoteProperty -Name 'description' -Value $description -Force - $null = $PolicyFile | Add-Member -MemberType NoteProperty -Name 'displayName' -Value $displayname -Force - $RawJSON = ConvertTo-Json -InputObject $PolicyFile -Depth 20 - $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant -type POST -body $RawJSON - } - 'Catalog' { - $TemplateTypeURL = 'configurationPolicies' - $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant - $PolicyName = ($RawJSON | ConvertFrom-Json).Name - $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant - if ($PolicyName -in $CheckExististing.name) { - Throw "Policy with Display Name $($Displayname) Already exists" - } - $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant -type POST -body $RawJSON - } - 'windowsDriverUpdateProfiles' { - $TemplateTypeURL = 'windowsDriverUpdateProfiles' - $PolicyName = ($RawJSON | ConvertFrom-Json).Name - $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant - if ($PolicyName -in $CheckExististing.name) { - $ExistingID = $CheckExististing | Where-Object -Property Name -EQ $PolicyName - $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenant -type PUT -body $RawJSON - - } else { - $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenant -type POST -body $RawJSON - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Added policy $($PolicyName) via template" -Sev 'info' - } - } - - } + Write-Host 'Calling Adding policy' + Set-CIPPIntunePolicy -TemplateType $Request.body.TemplateType -Description $description -DisplayName $displayname -RawJSON $RawJSON -AssignTo $AssignTo -tenantFilter $Tenant Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Added policy $($Displayname)" -Sev 'Info' - if ($AssignTo) { - Set-CIPPAssignedPolicy -GroupName $AssignTo -PolicyId $CreateRequest.id -Type $TemplateTypeURL -TenantFilter $tenant -PlatformType $PlatformType - } - "Successfully added policy for $($Tenant)" } 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' diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-AddGroupTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-AddGroupTemplate.ps1 index 83d8e9a64743..6574633a16c7 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-AddGroupTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-AddGroupTemplate.ps1 @@ -9,7 +9,6 @@ Function Invoke-AddGroupTemplate { #> [CmdletBinding()] param($Request, $TriggerMetadata) - $APIName = $TriggerMetadata.FunctionName Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' @@ -36,8 +35,7 @@ Function Invoke-AddGroupTemplate { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created Group template named $($Request.body.displayname) with GUID $GUID" -Sev 'Debug' $body = [pscustomobject]@{'Results' = 'Successfully added template' } - } - catch { + } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Group Template Creation failed: $($_.Exception.Message)" -Sev 'Error' $body = [pscustomobject]@{'Results' = "Group Template Creation failed: $($_.Exception.Message)" } } 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/Identity/Administration/Users/Invoke-ExecClrImmId.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecClrImmId.ps1 index 9241f5feae7d..6ebf2e66750b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecClrImmId.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecClrImmId.ps1 @@ -20,13 +20,13 @@ Function Invoke-ExecClrImmId { Try { $TenantFilter = $Request.Query.TenantFilter $UserID = $Request.Query.ID - $Body = [pscustomobject] @{ - onPremisesImmutableId = $null - } | ConvertTo-Json - $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$UserID" -tenantid $TenantFilter -type PATCH -body $Body + $Body = [pscustomobject]@{ onPremisesImmutableId = $null } + $Body = ConvertTo-Json -InputObject $Body -Depth 5 -Compress + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$UserID" -tenantid $TenantFilter -type PATCH -body $Body $Results = [pscustomobject]@{'Results' = 'Successfully Cleared ImmutableId' } } catch { - $Results = [pscustomobject]@{'Results' = "Failed. $_.Exception.Message"; colour = 'danger' } + $ErrorMessage = Get-NormalizedError -Message $_.Exception + $Results = [pscustomobject]@{'Results' = "Failed. $ErrorMessage"; colour = 'danger' } $_.Exception } @@ -35,5 +35,4 @@ Function Invoke-ExecClrImmId { StatusCode = [HttpStatusCode]::OK Body = $Results }) - } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 index f9dbca2ab349..86558c5daa54 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 @@ -11,7 +11,9 @@ Function Invoke-ExecJITAdmin { param($Request, $TriggerMetadata) $APIName = 'ExecJITAdmin' - Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $Request.Headers.'x-ms-client-principal' + + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' if ($Request.Query.Action -eq 'List') { $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } @@ -61,14 +63,14 @@ Function Invoke-ExecJITAdmin { if ($Request.Body.UserId -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.Body.UserId)" -tenantid $Request.Body.TenantFilter).userPrincipalName } - Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APINAME -message "Executing JIT Admin for $Username" -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Executing JIT Admin for $Username" -Sev 'Info' $Start = ([System.DateTimeOffset]::FromUnixTimeSeconds($Request.Body.StartDate)).DateTime.ToLocalTime() $Expiration = ([System.DateTimeOffset]::FromUnixTimeSeconds($Request.Body.EndDate)).DateTime.ToLocalTime() $Results = [System.Collections.Generic.List[string]]::new() - if ($Request.Body.useraction -eq 'create') { - Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APINAME -message "Creating JIT Admin user $($Request.Body.UserPrincipalName)" -Sev 'Info' + if ($Request.Body.useraction -eq 'Create') { + Write-LogMessage -user $User -API $APINAME -message "Creating JIT Admin user $($Request.Body.UserPrincipalName)" -Sev 'Info' Write-Information "Creating JIT Admin user $($Request.Body.UserPrincipalName)" $JITAdmin = @{ User = @{ @@ -86,7 +88,7 @@ Function Invoke-ExecJITAdmin { if (!$Request.Body.UseTAP) { $Results.Add("Password: $($CreateResult.password)") } - $Results.Add("JIT Expires: $($Expiration)") + $Results.Add("JIT Admin Expires: $($Expiration)") Start-Sleep -Seconds 1 } @@ -101,7 +103,18 @@ Function Invoke-ExecJITAdmin { $TapBody = '{}' } Write-Information "https://graph.microsoft.com/beta/users/$Username/authentication/temporaryAccessPassMethods" - $TapRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($Username)/authentication/temporaryAccessPassMethods" -tenantid $Request.Body.TenantFilter -type POST -body $TapBody + # Retry creating the TAP up to 5 times, since it can fail due to the user not being fully created yet + $Retries = 0 + do { + try { + $TapRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($Username)/authentication/temporaryAccessPassMethods" -tenantid $Request.Body.TenantFilter -type POST -body $TapBody + } catch { + Start-Sleep -Seconds 2 + Write-Information 'ERROR: Failed to create TAP, retrying' + Write-Information ( ConvertTo-Json -Depth 5 -InputObject (Get-CippException -Exception $_)) + } + $Retries++ + } while ( $null -eq $TapRequest.temporaryAccessPass -and $Retries -le 5 ) $TempPass = $TapRequest.temporaryAccessPass $PasswordExpiration = $TapRequest.LifetimeInMinutes @@ -109,6 +122,8 @@ Function Invoke-ExecJITAdmin { $PasswordLink = New-PwPushLink -Payload $TempPass if ($PasswordLink) { $Password = $PasswordLink + } else { + $Password = $TempPass } $Results.Add("Temporary Access Pass: $Password") $Results.Add("This TAP is usable starting at $($TapRequest.startDateTime) UTC for the next $PasswordExpiration minutes") @@ -147,21 +162,23 @@ Function Invoke-ExecJITAdmin { } } Add-CIPPScheduledTask -Task $TaskBody -hidden $false - Set-CIPPUserJITAdminProperties -TenantFilter $Request.Body.TenantFilter -UserId $Request.Body.UserId -Expiration $Expiration + if ($Request.Body.useraction -ne 'Create') { + Set-CIPPUserJITAdminProperties -TenantFilter $Request.Body.TenantFilter -UserId $Request.Body.UserId -Expiration $Expiration + } $Results.Add("Scheduling JIT Admin enable task for $Username") } else { $Results.Add("Executing JIT Admin enable task for $Username") Set-CIPPUserJITAdmin @Parameters } - $DisableTaskBody = @{ + $DisableTaskBody = [pscustomobject]@{ TenantFilter = $Request.Body.TenantFilter - Name = "JIT Admin (disable): $Username" + Name = "JIT Admin ($($Request.Body.ExpireAction)): $Username" Command = @{ value = 'Set-CIPPUserJITAdmin' label = 'Set-CIPPUserJITAdmin' } - Parameters = @{ + Parameters = [pscustomobject]@{ TenantFilter = $Request.Body.TenantFilter User = @{ 'UserPrincipalName' = $Username @@ -176,8 +193,8 @@ Function Invoke-ExecJITAdmin { } ScheduledTime = $Request.Body.EndDate } - Add-CIPPScheduledTask -Task $DisableTaskBody -hidden $false - $Results.Add("Scheduling JIT Admin disable task for $Username") + $null = Add-CIPPScheduledTask -Task $DisableTaskBody -hidden $false + $Results.Add("Scheduling JIT Admin $($Request.Body.ExpireAction) task for $Username") $Body = @{ Results = @($Results) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOffboardUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOffboardUser.ps1 index 0bf9feaf3dff..81707b1b22ef 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOffboardUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecOffboardUser.ps1 @@ -23,7 +23,7 @@ Function Invoke-ExecOffboardUser { Command = @{ value = 'Invoke-CIPPOffboardingJob' } - Parameters = @{ + Parameters = [pscustomobject]@{ Username = $Username APIName = 'Scheduled Offboarding' options = $request.body 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/Administration/Invoke-SetAuthMethod.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-SetAuthMethod.ps1 new file mode 100644 index 000000000000..2b00af589c58 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-SetAuthMethod.ps1 @@ -0,0 +1,32 @@ +function Invoke-SetAuthMethod { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Tenant.Administration.ReadWrite + #> + Param( + $Request, + $TriggerMetadata + ) + + $APIName = "Set Authentication Policy" + $state = if ($Request.Body.state -eq 'enabled') { $true } else { $false } + $Tenantfilter = $Request.Body.TenantFilter + + try { + Set-CIPPAuthenticationPolicy -Tenant $Tenantfilter -APIName $APIName -AuthenticationMethodId $($Request.Body.Id) -Enabled $state + $StatusCode = [HttpStatusCode]::OK + $SuccessMessage = "Authentication Policy for $($Request.Body.Id) has been set to $state" + } catch { + $ErrorMsg = Get-NormalizedError -message $($_.Exception.Message) + $SuccessMessage = "Function Error: $($_.InvocationInfo.ScriptLineNumber) - $ErrorMsg" + $StatusCode = [HttpStatusCode]::BadRequest + } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = [pscustomobject]@{'Results' = "$SuccessMessage" } + }) +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 index b0f64582c5c4..339cbf077481 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 @@ -16,67 +16,7 @@ Function Invoke-AddCATemplate { $TenantFilter = $Request.Query.TenantFilter try { $GUID = (New-Guid).GUID - $JSON = if ($request.body.rawjson) { - ConvertFrom-Json -InputObject ([pscustomobject]$request.body.rawjson) - } else { - ([pscustomobject]$Request.body) | ForEach-Object { - $NonEmptyProperties = $_.psobject.Properties | Where-Object { $null -ne $_.Value } | Select-Object -ExpandProperty Name - $_ | Select-Object -Property $NonEmptyProperties - } - } - - $includelocations = New-Object System.Collections.ArrayList - $IncludeJSON = foreach ($Location in $JSON.conditions.locations.includeLocations) { - $locationinfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations' -tenantid $TenantFilter | Where-Object -Property id -EQ $location | Select-Object * -ExcludeProperty id, *time* - $null = if ($locationinfo) { $includelocations.add($locationinfo.displayName) } else { $includelocations.add($location) } - $locationinfo - } - if ($includelocations) { $JSON.conditions.locations.includeLocations = $includelocations } - - - $excludelocations = New-Object System.Collections.ArrayList - $ExcludeJSON = foreach ($Location in $JSON.conditions.locations.excludeLocations) { - $locationinfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations' -tenantid $TenantFilter | Where-Object -Property id -EQ $location | Select-Object * -ExcludeProperty id, *time* - $null = if ($locationinfo) { $excludelocations.add($locationinfo.displayName) } else { $excludelocations.add($location) } - $locationinfo - } - - if ($excludelocations) { $JSON.conditions.locations.excludeLocations = $excludelocations } - if ($JSON.conditions.users.includeUsers) { - $JSON.conditions.users.includeUsers = @($JSON.conditions.users.includeUsers | ForEach-Object { - if ($_ -in 'All', 'None', 'GuestOrExternalUsers') { return $_ } - (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_)" -tenantid $TenantFilter).displayName - }) - } - - if ($JSON.conditions.users.excludeUsers) { - $JSON.conditions.users.excludeUsers = @($JSON.conditions.users.excludeUsers | ForEach-Object { - if ($_ -in 'All', 'None', 'GuestOrExternalUsers') { return $_ } - (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_)" -tenantid $TenantFilter).displayName - }) - } - - # Function to check if a string is a GUID - function Test-IsGuid($string) { - return [guid]::tryparse($string, [ref][guid]::Empty) - } - - if ($JSON.conditions.users.includeGroups) { - $JSON.conditions.users.includeGroups = @($JSON.conditions.users.includeGroups | ForEach-Object { - if ($_ -in 'All', 'None', 'GuestOrExternalUsers' -or -not (Test-IsGuid $_)) { return $_ } - (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($_)" -tenantid $TenantFilter).displayName - }) - } - if ($JSON.conditions.users.excludeGroups) { - $JSON.conditions.users.excludeGroups = @($JSON.conditions.users.excludeGroups | ForEach-Object { - if ($_ -in 'All', 'None', 'GuestOrExternalUsers' -or -not (Test-IsGuid $_)) { return $_ } - (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($_)" -tenantid $TenantFilter).displayName - }) - } - - $JSON | Add-Member -NotePropertyName 'LocationInfo' -NotePropertyValue @($IncludeJSON, $ExcludeJSON) - - $JSON = (ConvertTo-Json -Depth 100 -InputObject $JSON ) + $JSON = New-CIPPCATemplate -TenantFilter $TenantFilter -JSON $request.body $Table = Get-CippTable -tablename 'templates' $Table.Force = $true Add-CIPPAzDataTableEntity @Table -Entity @{ diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 index 86d06bf95796..0ee8ffc437db 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 @@ -18,14 +18,14 @@ Function Invoke-ExecCAExclusion { } if ($Request.body.vacation -eq 'true') { $StartDate = $Request.body.StartDate - $TaskBody = @{ + $TaskBody = [pscustomobject]@{ TenantFilter = $Request.body.TenantFilter Name = "Add CA Exclusion Vacation Mode: $Username - $($Request.body.TenantFilter)" Command = @{ value = 'Set-CIPPCAExclusion' label = 'Set-CIPPCAExclusion' } - Parameters = @{ + Parameters = [pscustomobject]@{ ExclusionType = 'Add' UserID = $Request.body.UserID PolicyId = $Request.body.PolicyId 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-ListDomains.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListDomains.ps1 index 149eb8fa9a04..ccc23f75aad7 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListDomains.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListDomains.ps1 @@ -21,7 +21,7 @@ Function Invoke-ListDomains { $TenantFilter = $Request.Query.TenantFilter try { - $GraphRequest = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/domains' -tenantid $TenantFilter | Select-Object id, isdefault, isinitial | Sort-Object isdefault + $GraphRequest = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/domains' -tenantid $TenantFilter | Select-Object id, isdefault, isinitial | Sort-Object isdefault -Descending $StatusCode = [HttpStatusCode]::OK } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message 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/Entrypoints/Invoke-ListMailboxes.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 index 7a125550426a..f136d38cbd5e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 @@ -20,12 +20,12 @@ Function Invoke-ListMailboxes { # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter try { - $Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses' + $Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses,WhenSoftDeleted,IsInactiveMailbox' $ExoRequest = @{ tenantid = $TenantFilter cmdlet = 'Get-Mailbox' - cmdParams = @{resultsize = 'unlimited' } - Select = $select + cmdParams = @{} + Select = $Select } $AllowedParameters = @( @@ -57,7 +57,7 @@ Function Invoke-ListMailboxes { } Write-Host ($ExoRequest | ConvertTo-Json) - $GraphRequest = (New-ExoRequest @ExoRequest) | Select-Object id, ExchangeGuid, ArchiveGuid, @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } }, + $GraphRequest = (New-ExoRequest @ExoRequest) | Select-Object id, ExchangeGuid, ArchiveGuid, WhenSoftDeleted, @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } }, @{ Name = 'displayName'; Expression = { $_.'DisplayName' } }, @{ Name = 'primarySmtpAddress'; Expression = { $_.'PrimarySMTPAddress' } }, diff --git a/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 b/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 index 4ce96415966d..fd5676683860 100644 --- a/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 @@ -7,23 +7,79 @@ 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 + $partitionKey = $entity.PartitionKey + if (-not $mergedResults.ContainsKey($partitionKey)) { + $mergedResults[$partitionKey] = @{} + } + if (-not $mergedResults[$partitionKey].ContainsKey($entityId)) { + $mergedResults[$partitionKey][$entityId] = @{ + Parts = New-Object 'System.Collections.ArrayList' + } + } + $mergedResults[$partitionKey][$entityId]['Parts'].Add($entity) > $null + } else { + $partitionKey = $entity.PartitionKey + if (-not $mergedResults.ContainsKey($partitionKey)) { + $mergedResults[$partitionKey] = @{} + } + $mergedResults[$partitionKey][$entity.RowKey] = @{ + Entity = $entity + Parts = New-Object 'System.Collections.ArrayList' + } + } + } + + $finalResults = @() + foreach ($partitionKey in $mergedResults.Keys) { + foreach ($entityId in $mergedResults[$partitionKey].Keys) { + $entityData = $mergedResults[$partitionKey][$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', 'Timestamp')) { + 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 + } + + 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-CIPPBitlockerKey.ps1 b/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 index a80a5d3b002e..a72d598639a2 100644 --- a/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 @@ -14,9 +14,8 @@ function Get-CIPPBitlockerKey { } return $GraphRequest } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) - return "Could not add out of office message for $($userid). Error: $($_.Exception.Message)" + return "Could not add out of office message for $($userid). Error: $ErrorMessage" } } - - diff --git a/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 b/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 index 0bc50c4acaad..1d3ba51f1dd8 100644 --- a/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 @@ -36,4 +36,4 @@ function Get-CIPPDomainAnalyser { $Results = @() } return $Results -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Get-CIPPLAPSPassword.ps1 b/Modules/CIPPCore/Public/Get-CIPPLAPSPassword.ps1 index 06d74edae438..011ab9f4552a 100644 --- a/Modules/CIPPCore/Public/Get-CIPPLAPSPassword.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPLAPSPassword.ps1 @@ -4,7 +4,7 @@ function Get-CIPPLapsPassword { param ( $device, $TenantFilter, - $APIName = "Get LAPS Password", + $APIName = 'Get LAPS Password', $ExecutingUser ) @@ -15,10 +15,10 @@ function Get-CIPPLapsPassword { "The password for $($_.AccountName) is $($PlainText) generated at $($date)" } if ($GraphRequest) { return $GraphRequest } else { return "No LAPS password found for $device" } - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev "Error" -tenant $TenantFilter - return "Could not add out of office message for $($userid). Error: $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not add out of office message for $($userid). Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 b/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 index 9668cd51b50e..84bab8d378cb 100644 --- a/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 @@ -7,7 +7,7 @@ function Get-CIPPLicenseOverview { $ExecutingUser ) - + $LicRequest = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $TenantFilter $SkuIDs = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/directory/subscriptions' -tenantid $TenantFilter @@ -25,7 +25,7 @@ function Get-CIPPLicenseOverview { if ($sku.skuId -in $ExcludedSkuList.GUID) { continue } $PrettyName = ($ConvertTable | Where-Object { $_.guid -eq $sku.skuid }).'Product_Display_Name' | Select-Object -Last 1 if (!$PrettyName) { $PrettyName = $sku.skuPartNumber } - + # Initialize $Term with the default value $TermInfo = foreach ($Subscription in $sku.subscriptionIds) { $SubInfo = $SkuIDs | Where-Object { $_.id -eq $Subscription } @@ -63,7 +63,7 @@ function Get-CIPPLicenseOverview { TermInfo = [string]($TermInfo | ConvertTo-Json -Depth 10 -Compress) 'PartitionKey' = 'License' 'RowKey' = "$($singlereq.Tenant) - $($sku.skuid)" - } + } } } return $GraphRequest diff --git a/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 b/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 index 28d526a9944d..0a6b0a4b3fba 100644 --- a/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 @@ -47,7 +47,7 @@ function Get-CIPPMFAState { $Policy.conditions.users.excludeUsers.foreach({ $ExcludeAllUsers.Add($_) | Out-Null }) continue } - } + } } } catch { } @@ -76,7 +76,7 @@ function Get-CIPPMFAState { $PerUser = if ($PerUserMFAState -eq $null) { $null } else { ($PerUserMFAState | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName).PerUserMFAState } $MFARegUser = if (($MFARegistration | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName).IsMFARegistered -eq $null) { $false } else { ($MFARegistration | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName) } - + [PSCustomObject]@{ Tenant = $TenantFilter ID = $_.ObjectId @@ -92,7 +92,7 @@ function Get-CIPPMFAState { RowKey = [string]($_.UserPrincipalName).replace('#', '') PartitionKey = 'users' } - + } return $GraphRequest } diff --git a/Modules/CIPPCore/Public/Get-CIPPOutOfOffice.ps1 b/Modules/CIPPCore/Public/Get-CIPPOutOfOffice.ps1 index 2cbc13b0a4f3..caca21766253 100644 --- a/Modules/CIPPCore/Public/Get-CIPPOutOfOffice.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPOutOfOffice.ps1 @@ -3,22 +3,22 @@ function Get-CIPPOutOfOffice { param ( $userid, $TenantFilter, - $APIName = "Get Out of Office", + $APIName = 'Get Out of Office', $ExecutingUser ) try { - $OutOfOffice = New-ExoRequest -tenantid $TenantFilter -cmdlet "Get-MailboxAutoReplyConfiguration" -cmdParams @{Identity = $userid } -Anchor $userid + $OutOfOffice = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-MailboxAutoReplyConfiguration' -cmdParams @{Identity = $userid } -Anchor $userid $Results = @{ - AutoReplyState = $OutOfOffice.AutoReplyState - StartTime = $OutOfOffice.StartTime.ToString("yyyy-MM-dd HH:mm") - EndTime = $OutOfOffice.EndTime.ToString("yyyy-MM-dd HH:mm") + AutoReplyState = $OutOfOffice.AutoReplyState + StartTime = $OutOfOffice.StartTime.ToString('yyyy-MM-dd HH:mm') + EndTime = $OutOfOffice.EndTime.ToString('yyyy-MM-dd HH:mm') InternalMessage = $OutOfOffice.InternalMessage ExternalMessage = $OutOfOffice.ExternalMessage } | ConvertTo-Json return $Results - } - catch { - return "Could not retrieve out of office message for $($userid). Error: $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + return "Could not retrieve out of office message for $($userid). Error: $ErrorMessage" } } diff --git a/Modules/CIPPCore/Public/Get-CIPPPartnerAzSubscriptions.ps1 b/Modules/CIPPCore/Public/Get-CIPPPartnerAzSubscriptions.ps1 index ea67ec1a3f9e..9c06f76edd0a 100644 --- a/Modules/CIPPCore/Public/Get-CIPPPartnerAzSubscriptions.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPPartnerAzSubscriptions.ps1 @@ -1,7 +1,7 @@ function Get-CIPPPartnerAzSubscriptions { param ( $TenantFilter, - $APIName = "Get-CIPPPartnerAzSubscriptions" + $APIName = 'Get-CIPPPartnerAzSubscriptions' ) try { @@ -15,38 +15,39 @@ function Get-CIPPPartnerAzSubscriptions { $subsCache = [system.collections.generic.list[hashtable]]::new() try { try { - $usageRecords = (New-GraphGETRequest -Uri "https://api.partnercenter.microsoft.com/v1/customers/$($TenantFilter)/subscriptions/usagerecords" -scope "https://api.partnercenter.microsoft.com/user_impersonation").items + $usageRecords = (New-GraphGETRequest -Uri "https://api.partnercenter.microsoft.com/v1/customers/$($TenantFilter)/subscriptions/usagerecords" -scope 'https://api.partnercenter.microsoft.com/user_impersonation').items } catch { - throw "Unable to retrieve usagerecord(s): $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + throw "Unable to retrieve usagerecord(s): $($ErrorMessage.NormalizedError)" } foreach ($usageRecord in $usageRecords) { # if condition probably needs more refining - if ($usageRecord.offerId -notlike "DZH318Z0BPS6*") { + if ($usageRecord.offerId -notlike 'DZH318Z0BPS6*') { # Legacy subscriptions are directly accessible $subDetails = @{ - tenantId = $tenantFilter + tenantId = $tenantFilter subscriptionId = ($usageRecord.id).ToLower() - isLegacy = $true - POR = "Legacy subscription" - status = $usageRecord.status + isLegacy = $true + POR = 'Legacy subscription' + status = $usageRecord.status } - + $subsCache.Add($subDetails) } else { # For modern subscriptions we need to dig a little deeper try { - $subid = (New-GraphGETRequest -Uri "https://api.partnercenter.microsoft.com/v1/customers/$($TenantFilter)/subscriptions/$($usageRecord.id)/azureEntitlements" -scope "https://api.partnercenter.microsoft.com/user_impersonation").items #| Where-Object { $_.status -eq "active" } - + $subid = (New-GraphGETRequest -Uri "https://api.partnercenter.microsoft.com/v1/customers/$($TenantFilter)/subscriptions/$($usageRecord.id)/azureEntitlements" -scope 'https://api.partnercenter.microsoft.com/user_impersonation').items #| Where-Object { $_.status -eq "active" } + foreach ($id in $subid) { $subDetails = @{ - tenantId = $tenantFilter + tenantId = $tenantFilter subscriptionId = ($id.id) - isLegacy = $false - POR = $id.partnerOnRecord - status = $id.status + isLegacy = $false + POR = $id.partnerOnRecord + status = $id.status } - + $subsCache.Add($subDetails) } } catch { @@ -59,6 +60,7 @@ function Get-CIPPPartnerAzSubscriptions { return $subsCache } catch { - Write-LogMessage -message "Unable to retrieve CSP Azure subscriptions for $($TenantFilter): $($_.Exception.Message)" -Sev 'ERROR' -API $APINAME + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -message "Unable to retrieve CSP Azure subscriptions for $($TenantFilter): $($ErrorMessage.NormalizedError)" -Sev 'ERROR' -API $APINAME -LogData $ErrorMessage } } diff --git a/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 index 5c525962009f..26a6033055ad 100644 --- a/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPPerUserMFA.ps1 @@ -29,6 +29,7 @@ function Get-CIPPPerUserMFA { } } } catch { - "Failed to get MFA State for $id : $_" + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + "Failed to get MFA State for $id : $ErrorMessage" } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Get-CIPPSPOTenant.ps1 b/Modules/CIPPCore/Public/Get-CIPPSPOTenant.ps1 new file mode 100644 index 000000000000..fec489bc729d --- /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 } } +} diff --git a/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 b/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 index b85edb06af86..7b6df00aae24 100644 --- a/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 @@ -81,4 +81,4 @@ function Get-CIPPSchemaExtensions { New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/v1.0/schemaExtensions/$($Schema.id)" -Body $PatchJson -AsApp $true -NoAuthCheck $true } } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 b/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 index 60e42aee5ced..52a0fbb2a1f8 100644 --- a/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 +++ b/Modules/CIPPCore/Public/Get-SlackAlertBlocks.ps1 @@ -262,4 +262,4 @@ function Get-SlackAlertBlocks { blocks = $Blocks } } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/GraphHelper/Convert-SKUName.ps1 b/Modules/CIPPCore/Public/GraphHelper/Convert-SKUName.ps1 index 7d0d5e874aea..61e8e0acd4b6 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Convert-SKUName.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Convert-SKUName.ps1 @@ -1,9 +1,17 @@ -function Convert-SKUname($skuname, $skuID) { +function Convert-SKUname { <# .FUNCTIONALITY Internal #> - $ConvertTable = Import-Csv Conversiontable.csv + param( + $skuname, + $skuID, + $ConvertTable + ) + if (!$ConvertTable) { + Set-Location (Get-Item $PSScriptRoot).Parent.FullName + $ConvertTable = Import-Csv Conversiontable.csv + } if ($skuname) { $ReturnedName = ($ConvertTable | Where-Object { $_.String_Id -eq $skuname } | Select-Object -Last 1).'Product_Display_Name' } if ($skuID) { $ReturnedName = ($ConvertTable | Where-Object { $_.guid -eq $skuid } | Select-Object -Last 1).'Product_Display_Name' } if ($ReturnedName) { return $ReturnedName } else { return $skuname, $skuID } diff --git a/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 index fac7a03612a0..8d5aa4e372dc 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 @@ -1,29 +1,66 @@ -function New-ExoRequest ($tenantid, $cmdlet, $cmdParams, $useSystemMailbox, $Anchor, $NoAuthCheck, $Select) { +function New-ExoRequest { <# .FUNCTIONALITY Internal #> + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true)] + [string]$cmdlet, + + [Parameter(Mandatory = $false)] + $cmdParams, + + [Parameter(Mandatory = $false)] + [string]$Select, + + [Parameter(Mandatory = $false)] + [string]$Anchor, + + [Parameter(Mandatory = $false)] + [bool]$useSystemMailbox, + + [Parameter(Mandatory = $false)] + [string]$tenantid, + + [Parameter(Mandatory = $false)] + [bool]$NoAuthCheck, + + [switch]$Compliance, + [ValidateSet('v1.0', 'beta')] + [string]$ApiVersion = 'beta' + ) if ((Get-AuthorisedRequest -TenantID $tenantid) -or $NoAuthCheck -eq $True) { - $token = Get-ClassicAPIToken -resource 'https://outlook.office365.com' -Tenantid $tenantid - $Tenant = Get-Tenants -IncludeErrors | Where-Object { $_.defaultDomainName -eq $tenantid -or $_.customerId -eq $tenantid } + + if ($Compliance.IsPresent) { + $Resource = 'https://ps.compliance.protection.outlook.com' + $token = Get-GraphToken -tenantid $tenantid -scope "$Resource/.default" + $token = @{ 'access_token' = $token.Authorization -replace 'Bearer ' } + } else { + $Resource = 'https://outlook.office365.com' + $token = Get-ClassicAPIToken -resource $Resource -Tenantid $tenantid + } if ($cmdParams) { + #if cmdparams is a pscustomobject, convert to hashtable, otherwise leave as is $Params = $cmdParams } else { $Params = @{} } - $ExoBody = ConvertTo-Json -Depth 5 -InputObject @{ + $ExoBody = ConvertTo-Json -Depth 5 -Compress -InputObject @{ CmdletInput = @{ CmdletName = $cmdlet Parameters = $Params } } + + $Tenant = Get-Tenants -IncludeErrors | Where-Object { $_.defaultDomainName -eq $tenantid -or $_.customerId -eq $tenantid } + if (!$Anchor) { if ($cmdparams.Identity) { $Anchor = $cmdparams.Identity } if ($cmdparams.anr) { $Anchor = $cmdparams.anr } if ($cmdparams.User) { $Anchor = $cmdparams.User } if ($cmdparams.mailbox) { $Anchor = $cmdparams.mailbox } - if ($cmdlet -eq 'Set-AdminAuditLogConfig') { $anchor = "UPN:SystemMailbox{8cc370d3-822a-4ab8-a926-bb94bd0641a9}@$($OnMicrosoft)" } if (!$Anchor -or $useSystemMailbox) { if (!$Tenant.initialDomainName -or $Tenant.initialDomainName -notlike '*onmicrosoft.com*') { $OnMicrosoft = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/domains?$top=999' -tenantid $tenantid -NoAuthCheck $NoAuthCheck | Where-Object -Property isInitial -EQ $true).id @@ -31,6 +68,8 @@ function New-ExoRequest ($tenantid, $cmdlet, $cmdParams, $useSystemMailbox, $Anc $OnMicrosoft = $Tenant.initialDomainName } $anchor = "UPN:SystemMailbox{bb558c35-97f1-4cb9-8ff7-d53741dc928c}@$($OnMicrosoft)" + if ($cmdlet -in 'Set-AdminAuditLogConfig', 'Get-AdminAuditLogConfig', 'Enable-OrganizationCustomization', 'Get-OrganizationConfig') { $anchor = "UPN:SystemMailbox{8cc370d3-822a-4ab8-a926-bb94bd0641a9}@$($OnMicrosoft)" } + } #if the anchor is a GUID, try looking up the user. if ($Anchor -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') { @@ -42,21 +81,40 @@ function New-ExoRequest ($tenantid, $cmdlet, $cmdParams, $useSystemMailbox, $Anc } } } - Write-Host "Using $Anchor" + + Write-Verbose "Using $Anchor" + $Headers = @{ - Authorization = "Bearer $($token.access_token)" - Prefer = 'odata.maxpagesize = 1000' - 'parameter-based-routing' = $true - 'X-AnchorMailbox' = $anchor + Authorization = "Bearer $($token.access_token)" + Prefer = 'odata.maxpagesize=1000' + 'X-AnchorMailbox' = $anchor + } + # Compliance API trickery. Capture Location headers on redirect, extract subdomain and prepend to compliance URL + if ($Compliance.IsPresent) { + $URL = "$Resource/adminapi/$ApiVersion/$($tenant.customerId)/EXOBanner('AutogenSession')?Version=3.4.0" + Invoke-RestMethod -ResponseHeadersVariable ComplianceHeaders -MaximumRedirection 0 -ErrorAction SilentlyContinue -Uri $URL -Headers $Headers -SkipHttpErrorCheck | Out-Null + $RedirectedHost = ([System.Uri]($ComplianceHeaders.Location | Select-Object -First 1)).Host + $RedirectedHostname = '{0}.ps.compliance.protection.outlook.com' -f ($RedirectedHost -split '\.' | Select-Object -First 1) + $Resource = "https://$($RedirectedHostname)" + Write-Verbose "Redirecting to $Resource" } + try { - if ($Select) { $Select = "`$select=$Select" } - $URL = "https://outlook.office365.com/adminapi/beta/$($tenant.customerId)/InvokeCommand?$Select" + if ($Select) { $Select = "?`$select=$Select" } + $URL = "$Resource/adminapi/$ApiVersion/$($tenant.customerId)/InvokeCommand$Select" + + Write-Verbose "POST [ $URL ]" + $ReturnedData = do { + $ExoRequestParams = @{ + Uri = $URL + Method = 'POST' + Body = $ExoBody + Headers = $Headers + ContentType = 'application/json' + } - $ReturnedData = - do { - $Return = Invoke-RestMethod $URL -Method POST -Body $ExoBody -Headers $Headers -ContentType 'application/json; charset=utf-8' + $Return = Invoke-RestMethod @ExoRequestParams $URL = $Return.'@odata.nextLink' $Return } until ($null -eq $URL) @@ -66,11 +124,14 @@ function New-ExoRequest ($tenantid, $cmdlet, $cmdParams, $useSystemMailbox, $Anc } } catch { $ErrorMess = $($_.Exception.Message) - $ReportedError = ($_.ErrorDetails | ConvertFrom-Json -ErrorAction SilentlyContinue) - $Message = if ($ReportedError.error.details.message) { - $ReportedError.error.details.message - } elseif ($ReportedError.error.message) { $ReportedError.error.message } - else { $ReportedError.error.innererror.internalException.message } + try { + $ReportedError = ($_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue) + $Message = if ($ReportedError.error.details.message) { + $ReportedError.error.details.message + } elseif ($ReportedError.error.innererror) { + $ReportedError.error.innererror.internalException.message + } elseif ($ReportedError.error.message) { $ReportedError.error.message } + } catch { $Message = $_.ErrorDetails } if ($null -eq $Message) { $Message = $ErrorMess } throw $Message } @@ -78,4 +139,4 @@ function New-ExoRequest ($tenantid, $cmdlet, $cmdParams, $useSystemMailbox, $Anc } else { Write-Error 'Not allowed. You cannot manage your own tenant or tenants not under your scope' } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 index f37be378e39d..ae2777fa2ac7 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 @@ -33,10 +33,15 @@ function New-GraphBulkRequest { $req = @{} # Use select to create hashtables of id, method and url for each call $req['requests'] = ($Requests[$i..($i + 19)]) - $ReqBody = ($req | ConvertTo-Json -Depth 10) - Invoke-RestMethod -Uri $URL -Method POST -Headers $headers -ContentType 'application/json; charset=utf-8' -Body $ReqBody + $ReqBody = (ConvertTo-Json -InputObject $req -Compress -Depth 100) + $Return = Invoke-RestMethod -Uri $URL -Method POST -Headers $headers -ContentType 'application/json; charset=utf-8' -Body $ReqBody + if ($Return.headers.'retry-after') { + #Revist this when we are pushing this data into our custom schema instead. + $headers = Get-GraphToken -tenantid $tenantid -scope $scope -AsApp $asapp + Invoke-RestMethod -Uri $URL -Method POST -Headers $headers -ContentType 'application/json; charset=utf-8' -Body $ReqBody + } + $Return } - foreach ($MoreData in $ReturnedData.Responses | Where-Object { $_.body.'@odata.nextLink' }) { Write-Host 'Getting more' $AdditionalValues = New-GraphGetRequest -ComplexFilter -uri $MoreData.body.'@odata.nextLink' -tenantid $tenantid -NoAuthCheck:$NoAuthCheck @@ -47,7 +52,7 @@ function New-GraphBulkRequest { } catch { $Message = ($_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue).error.message - if ($Message -eq $null) { $Message = $($_.Exception.Message) } + if ($null -eq $Message) { $Message = $($_.Exception.Message) } if ($Message -ne 'Request not applicable to target tenant.') { $Tenant.LastGraphError = $Message $Tenant.GraphErrorCount++ 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/GraphHelper/New-passwordString.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-passwordString.ps1 index 5869c370108e..c8393455c326 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-passwordString.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-passwordString.ps1 @@ -23,7 +23,7 @@ function New-passwordString { $containsUppercase = $Password -cmatch '[A-Z]' $containsLowercase = $Password -cmatch '[a-z]' $containsDigit = $Password -cmatch '\d' - $containsSpecialChar = $Password -cmatch "[$%&*#]" + $containsSpecialChar = $Password -cmatch '[$%&*#]' $isComplex = $containsUppercase -and $containsLowercase -and $containsDigit -and $containsSpecialChar diff --git a/Modules/CIPPCore/Public/GraphHelper/Write-CippFunctionStats.ps1 b/Modules/CIPPCore/Public/GraphHelper/Write-CippFunctionStats.ps1 index f73051f931a6..0e295e96cc46 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Write-CippFunctionStats.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Write-CippFunctionStats.ps1 @@ -11,12 +11,23 @@ function Write-CippFunctionStats { [string]$ErrorMsg = '' ) try { + $Start = Get-Date $Start + $End = Get-Date $End + $Table = Get-CIPPTable -tablename CippFunctionStats $RowKey = [string](New-Guid).Guid $TimeSpan = New-TimeSpan -Start $Start -End $End $Duration = [int]$TimeSpan.TotalSeconds $DurationMS = [int]$TimeSpan.TotalMilliseconds + # if datetime is local, convert to UTC + if ($Start.Kind -eq 'Local') { + $Start = $Start.ToUniversalTime() + } + if ($End.Kind -eq 'Local') { + $End = $End.ToUniversalTime() + } + $StatEntity = @{} # Flatten data to json string $StatEntity.PartitionKey = $FunctionType @@ -28,13 +39,16 @@ function Write-CippFunctionStats { $StatEntity.ErrorMsg = $ErrorMsg $Entity = [PSCustomObject]$Entity foreach ($Property in $Entity.PSObject.Properties.Name) { - if ($Entity.$Property.GetType().Name -in ('Hashtable', 'PSCustomObject', 'OrderedHashtable')) { - $StatEntity.$Property = [string]($Entity.$Property | ConvertTo-Json -Compress) - } elseif ($Property -notin ('ETag', 'RowKey', 'PartitionKey', 'Timestamp', 'LastRefresh')) { - $StatEntity.$Property = $Entity.$Property + if ($Entity.$Property) { + if ($Entity.$Property.GetType().Name -in ('Hashtable', 'PSCustomObject', 'OrderedHashtable')) { + $StatEntity.$Property = [string]($Entity.$Property | ConvertTo-Json -Compress) + } elseif ($Property -notin ('ETag', 'RowKey', 'PartitionKey', 'Timestamp', 'LastRefresh')) { + $StatEntity.$Property = $Entity.$Property + } } } $StatEntity = [PSCustomObject]$StatEntity + Add-CIPPAzDataTableEntity @Table -Entity $StatEntity -Force } catch { Write-Host "Exception logging stats $($_.Exception.Message)" diff --git a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 index 20620029e0e9..0c0af71bc86e 100644 --- a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 +++ b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 @@ -285,12 +285,17 @@ function Get-GraphRequestList { if (!$QueueThresholdExceeded) { $GraphRequestResults = New-GraphGetRequest @GraphRequest -ErrorAction Stop | Select-Object *, @{l = 'Tenant'; e = { $TenantFilter } }, @{l = 'CippStatus'; e = { 'Good' } } if ($ReverseTenantLookup -and $GraphRequestResults) { - $TenantInfo = $GraphRequestResults.$ReverseTenantLookupProperty | Sort-Object -Unique | ForEach-Object { - New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/findTenantInformationByTenantId(tenantId='$_')" -noauthcheck $true -asApp:$true -tenant $env:TenantId - } - foreach ($Result in $GraphRequestResults) { - $Result | Select-Object @{n = 'TenantInfo'; e = { $TenantInfo | Where-Object { $Result.$ReverseTenantLookupProperty -eq $_.tenantId } } }, * + $ReverseLookupRequests = $GraphRequestResults.$ReverseTenantLookupProperty | Sort-Object -Unique | ForEach-Object { + @{ + id = $_ + url = "tenantRelationships/findTenantInformationByTenantId(tenantId='$_')" + method = 'GET' + } } + $TenantInfo = New-GraphBulkRequest -Requests @($ReverseLookupRequests) -tenantid $env:TenantId -NoAuthCheck $true -asapp $true + + $GraphRequestResults | Select-Object @{n = 'TenantInfo'; e = { Get-GraphBulkResultByID -Results @($TenantInfo) -ID $_.$ReverseTenantLookupProperty } }, * + } else { $GraphRequestResults } @@ -306,4 +311,4 @@ function Get-GraphRequestList { $_.Data | ConvertFrom-Json } } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 index e051a2a0144b..70244fc0ff3b 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 @@ -1,4 +1,4 @@ - + function Invoke-CIPPOffboardingJob { [CmdletBinding()] param ( @@ -18,13 +18,13 @@ function Invoke-CIPPOffboardingJob { { $_.'ConvertToShared' -eq 'true' } { Set-CIPPMailboxType -ExecutingUser $ExecutingUser -tenantFilter $tenantFilter -userid $username -username $username -MailboxType 'Shared' -APIName $APIName } - { $_.RevokeSessions -eq 'true' } { + { $_.RevokeSessions -eq 'true' } { Revoke-CIPPSessions -tenantFilter $tenantFilter -username $username -userid $userid -ExecutingUser $ExecutingUser -APIName $APIName } - { $_.ResetPass -eq 'true' } { + { $_.ResetPass -eq 'true' } { Set-CIPPResetPassword -tenantFilter $tenantFilter -userid $username -ExecutingUser $ExecutingUser -APIName $APIName } - { $_.RemoveGroups -eq 'true' } { + { $_.RemoveGroups -eq 'true' } { Remove-CIPPGroups -userid $userid -tenantFilter $Tenantfilter -ExecutingUser $ExecutingUser -APIName $APIName -Username "$Username" } @@ -35,22 +35,26 @@ function Invoke-CIPPOffboardingJob { Set-CIPPSignInState -TenantFilter $tenantFilter -userid $username -AccountEnabled $false -ExecutingUser $ExecutingUser -APIName $APIName } - { $_.'OnedriveAccess' -ne '' } { + { $_.'OnedriveAccess' -ne '' } { $Options.OnedriveAccess | ForEach-Object { Set-CIPPSharePointPerms -tenantFilter $tenantFilter -userid $username -OnedriveAccessUser $_.value -ExecutingUser $ExecutingUser -APIName $APIName } } - { $_.'AccessNoAutomap' -ne '' } { + { $_.'AccessNoAutomap' -ne '' } { $Options.AccessNoAutomap | ForEach-Object { Set-CIPPMailboxAccess -tenantFilter $tenantFilter -userid $username -AccessUser $_.value -Automap $false -AccessRights @('FullAccess') -ExecutingUser $ExecutingUser -APIName $APIName } } - { $_.'AccessAutomap' -ne '' } { + { $_.'AccessAutomap' -ne '' } { $Options.AccessAutomap | ForEach-Object { Set-CIPPMailboxAccess -tenantFilter $tenantFilter -userid $username -AccessUser $_.value -Automap $true -AccessRights @('FullAccess') -ExecutingUser $ExecutingUser -APIName $APIName } } - - { $_.'OOO' -ne '' } { + + { $_.'OOO' -ne '' } { Set-CIPPOutOfOffice -tenantFilter $tenantFilter -userid $username -InternalMessage $Options.OOO -ExternalMessage $Options.OOO -ExecutingUser $ExecutingUser -APIName $APIName -state 'Enabled' } - { $_.'forward' -ne '' } { - Set-CIPPForwarding -userid $userid -username $username -tenantFilter $Tenantfilter -Forward $Options.forward -KeepCopy [bool]$Options.keepCopy -ExecutingUser $ExecutingUser -APIName $APIName + { $_.'forward' -ne '' } { + if (!$options.keepcopy) { + Set-CIPPForwarding -userid $userid -username $username -tenantFilter $Tenantfilter -Forward $Options.forward -ExecutingUser $ExecutingUser -APIName $APIName + } else { + Set-CIPPForwarding -userid $userid -username $username -tenantFilter $Tenantfilter -Forward $Options.forward -KeepCopy [boolean]$Options.keepCopy -ExecutingUser $ExecutingUser -APIName $APIName + } } { $_.'RemoveLicenses' -eq 'true' } { Remove-CIPPLicense -userid $userid -username $Username -tenantFilter $Tenantfilter -ExecutingUser $ExecutingUser -APIName $APIName @@ -68,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 @@ -82,8 +89,8 @@ function Invoke-CIPPOffboardingJob { "Removal of permissions queued. This task will run in the background and send it's results to the logbook." } } - + } return $Return -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Invoke-RemoveAPDevice.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveAPDevice.ps1 index 1c474b20a44f..db939b3ee098 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveAPDevice.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveAPDevice.ps1 @@ -20,19 +20,20 @@ Function Invoke-RemoveAPDevice { $Deviceid = $Request.Query.ID try { - if ($TenantFilter -eq $null -or $TenantFilter -eq 'null') { - $GraphRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeviceIdentities/$Deviceid" -type DELETE + if ($null -eq $TenantFilter -or $TenantFilter -eq 'null') { + $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeviceIdentities/$Deviceid" -type DELETE } else { - $GraphRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeviceIdentities/$Deviceid" -tenantid $TenantFilter -type DELETE + $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeviceIdentities/$Deviceid" -tenantid $TenantFilter -type DELETE } Write-LogMessage -user $request.headers.'x-ms-client-principal' -tenant $TenantFilter -API $APINAME -message "Deleted autopilot device $Deviceid" -Sev 'Info' $body = [pscustomobject]@{'Results' = 'Successfully deleted the autopilot device' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -tenant $TenantFilter -API $APINAME -message "Autopilot Delete API failed for $deviceid. The error is: $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Failed to delete device: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $request.headers.'x-ms-client-principal' -tenant $TenantFilter -API $APINAME -message "Autopilot Delete API failed for $deviceid. The error is: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Failed to delete device: $($ErrorMessage.NormalizedError)" } } #force a sync, this can give "too many requests" if deleleting a bunch of devices though. - $GraphRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotSettings/sync' -tenantid $TenantFilter -type POST -body '{}' + $null = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotSettings/sync' -tenantid $TenantFilter -type POST -body '{}' # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/Modules/CIPPCore/Public/Invoke-RemoveApp.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveApp.ps1 index b154d77568eb..10430868b406 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveApp.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveApp.ps1 @@ -11,7 +11,8 @@ Function Invoke-RemoveApp { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter @@ -19,12 +20,13 @@ Function Invoke-RemoveApp { if (!$policyId) { exit } try { #$unAssignRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($policyId)')/assign" -type POST -Body '{"assignments":[]}' -tenant $TenantFilter - $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$($policyId)" -type DELETE -tenant $TenantFilter - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Deleted $policyId" -Sev 'Info' -tenant $TenantFilter + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$($policyId)" -type DELETE -tenant $TenantFilter + Write-LogMessage -user $User -API $APINAME -message "Deleted $policyId" -Sev 'Info' -tenant $TenantFilter $body = [pscustomobject]@{'Results' = 'Successfully deleted the application' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Could not delete app $policyId. $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter - $body = [pscustomobject]@{'Results' = "Could not delete this application: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Could not delete app $policyId. $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Could not delete this application: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Invoke-RemoveBPATemplate.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveBPATemplate.ps1 index b9ae2a8c13e1..6e247411b394 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveBPATemplate.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveBPATemplate.ps1 @@ -11,7 +11,8 @@ Function Invoke-RemoveBPATemplate { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' $ID = $request.query.TemplateName try { @@ -20,11 +21,12 @@ Function Invoke-RemoveBPATemplate { $Filter = "PartitionKey eq 'BPATemplate' and RowKey eq '$id'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey Remove-AzDataTableEntity @Table -Entity $clearRow - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Removed BPA Template with ID $ID." -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Removed BPA Template with ID $ID." -Sev 'Info' $body = [pscustomobject]@{'Results' = 'Successfully removed BPA Template' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to remove BPA template $ID. $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Failed to remove template: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Failed to remove BPA template $ID. $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Failed to remove template: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Invoke-RemoveCAPolicy.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveCAPolicy.ps1 index da878e3dbba2..f84ed7466f45 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveCAPolicy.ps1 @@ -11,20 +11,22 @@ Function Invoke-RemoveCAPolicy { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter $policyId = $Request.Query.GUID if (!$policyId) { exit } try { - $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/$($policyId)" -type DELETE -tenant $TenantFilter - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Deleted CA Policy $policyId" -Sev 'Info' -tenant $TenantFilter + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/$($policyId)" -type DELETE -tenant $TenantFilter + Write-LogMessage -user $User -API $APINAME -message "Deleted CA Policy $policyId" -Sev 'Info' -tenant $TenantFilter $body = [pscustomobject]@{'Results' = 'Successfully deleted the policy' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Could not delete CA policy $policyId. $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter - $body = [pscustomobject]@{'Results' = "Could not delete policy: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Could not delete CA policy $policyId. $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Could not delete policy: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Invoke-RemoveCATemplate.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveCATemplate.ps1 index 7ff2f2bf5df8..b3024895b3a5 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveCATemplate.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveCATemplate.ps1 @@ -11,20 +11,22 @@ Function Invoke-RemoveCATemplate { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - + $User = $request.headers.'x-ms-client-principal' $ID = $request.query.id + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' + try { $Table = Get-CippTable -tablename 'templates' $Filter = "PartitionKey eq 'CATemplate' and RowKey eq '$id'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey Remove-AzDataTableEntity @Table -Entity $clearRow - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Removed Conditional Access Template with ID $ID." -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Removed Conditional Access Template with ID $ID." -Sev 'Info' $body = [pscustomobject]@{'Results' = 'Successfully removed Conditional Access Template' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to remove Conditional Access template $ID. $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Failed to remove template: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Failed to remove Conditional Access template $ID. $($ErrorMessage.NormalizedError)" -Sev 'Error' + $body = [pscustomobject]@{'Results' = "Failed to remove template: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Invoke-RemoveContact.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveContact.ps1 index fc34c0073005..3635459a6f1b 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveContact.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveContact.ps1 @@ -11,8 +11,9 @@ Function Invoke-RemoveContact { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' $Tenantfilter = $request.Query.tenantfilter + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' $Params = @{ @@ -22,12 +23,13 @@ Function Invoke-RemoveContact { try { $Params = @{ Identity = $request.query.GUID } - $GraphRequest = New-ExoRequest -tenantid $Tenantfilter -cmdlet 'Remove-MailContact' -cmdParams $params -UseSystemMailbox $true + $null = New-ExoRequest -tenantid $Tenantfilter -cmdlet 'Remove-MailContact' -cmdParams $params -UseSystemMailbox $true $Result = "Deleted $($Request.query.guid)" - Write-LogMessage -API 'TransportRules' -tenant $tenantfilter -message "Deleted contact $($Request.query.guid)" -sev Debug + Write-LogMessage -user $User -API $APIName -tenant $tenantfilter -message "Deleted contact $($Request.query.guid)" -sev Debug } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception - $Result = $ErrorMessage + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APIName -tenant $tenantfilter -message "Failed to delete contact $($Request.query.guid). $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + $Result = $ErrorMessage.NormalizedError } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/Modules/CIPPCore/Public/Invoke-RemoveExConnector.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveExConnector.ps1 index 79b201c068b4..84cdfc72e91c 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveExConnector.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveExConnector.ps1 @@ -11,19 +11,20 @@ Function Invoke-RemoveExConnector { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' $Tenantfilter = $request.Query.tenantfilter try { $Params = @{ Identity = $request.query.GUID } - $GraphRequest = New-ExoRequest -tenantid $Tenantfilter -cmdlet "Remove-$($Request.query.Type)Connector" -cmdParams $params -useSystemMailbox $true + $null = New-ExoRequest -tenantid $Tenantfilter -cmdlet "Remove-$($Request.query.Type)Connector" -cmdParams $params -useSystemMailbox $true $Result = "Deleted $($Request.query.guid)" - Write-LogMessage -API 'TransportRules' -tenant $tenantfilter -message "Deleted transport rule $($Request.query.guid)" -sev Debug - } - catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception - $Result = $ErrorMessage + Write-LogMessage -user $User -API $APIName -tenant $tenantfilter -message "Deleted transport rule $($Request.query.guid)" -sev Debug + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APIName -tenant $tenantfilter -message "Failed deleting transport rule $($Request.query.guid). Error:$($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage + $Result = $ErrorMessage.NormalizedError } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/Modules/CIPPCore/Public/Invoke-RemoveExConnectorTemplate.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveExConnectorTemplate.ps1 index b12df6a3d0aa..f603904daec4 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveExConnectorTemplate.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveExConnectorTemplate.ps1 @@ -11,7 +11,8 @@ Function Invoke-RemoveExConnectorTemplate { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' $ID = $request.query.id try { @@ -19,11 +20,12 @@ Function Invoke-RemoveExConnectorTemplate { $Filter = "PartitionKey eq 'ExConnectorTemplate' and RowKey eq '$id'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey Remove-AzDataTableEntity @Table -Entity $clearRow - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Removed Exchange Connector Template with ID $ID." -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Removed Exchange Connector Template with ID $ID." -Sev 'Info' $body = [pscustomobject]@{'Results' = 'Successfully removed Exchange Connector Template' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to remove Exchange Connector Template $ID. $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Failed to remove template: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Failed to remove Exchange Connector Template $ID. $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Failed to remove template: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Invoke-RemoveGroupTemplate.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveGroupTemplate.ps1 index 85e4346a2b81..51d5d9d03ce6 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveGroupTemplate.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveGroupTemplate.ps1 @@ -11,7 +11,8 @@ Function Invoke-RemoveGroupTemplate { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' $ID = $request.query.id try { @@ -22,11 +23,12 @@ Function Invoke-RemoveGroupTemplate { Write-Host $Filter $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey Remove-AzDataTableEntity @Table -Entity $clearRow - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Removed Intune Template with ID $ID." -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Removed Intune Template with ID $ID." -Sev 'Info' $body = [pscustomobject]@{'Results' = 'Successfully removed Template' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to remove intune template $ID. $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Failed to remove template: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Failed to remove intune template $ID. $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Failed to remove template: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Invoke-RemoveIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveIntuneTemplate.ps1 index f1b2c51ca2bf..4c66d297fc66 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveIntuneTemplate.ps1 @@ -11,7 +11,8 @@ Function Invoke-RemoveIntuneTemplate { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' $ID = $request.query.id try { @@ -22,14 +23,14 @@ Function Invoke-RemoveIntuneTemplate { Write-Host $Filter $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey Remove-AzDataTableEntity @Table -Entity $clearRow - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Removed Intune Template with ID $ID." -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Removed Intune Template with ID $ID." -Sev 'Info' $body = [pscustomobject]@{'Results' = 'Successfully removed Intune Template' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to remove intune template $ID. $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Failed to remove template: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Failed to remove intune template $ID. $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Failed to remove template: $($ErrorMessage.NormalizedError)" } } - # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPCore/Public/Invoke-RemovePolicy.ps1 b/Modules/CIPPCore/Public/Invoke-RemovePolicy.ps1 index c7805fb46a48..aa94e6d3213c 100644 --- a/Modules/CIPPCore/Public/Invoke-RemovePolicy.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemovePolicy.ps1 @@ -11,7 +11,8 @@ Function Invoke-RemovePolicy { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter @@ -20,13 +21,14 @@ Function Invoke-RemovePolicy { try { #$unAssignRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($policyId)')/assign" -type POST -Body '{"assignments":[]}' -tenant $TenantFilter - $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($Request.Query.URLName)('$($policyId)')" -type DELETE -tenant $TenantFilter - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Deleted $policyId" -Sev 'Info' -tenant $TenantFilter + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($Request.Query.URLName)('$($policyId)')" -type DELETE -tenant $TenantFilter + Write-LogMessage -user $User -API $APINAME -message "Deleted $policyId" -Sev 'Info' -tenant $TenantFilter $body = [pscustomobject]@{'Results' = 'Successfully deleted the policy' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Could not delete policy $policyId. $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter - $body = [pscustomobject]@{'Results' = "Could not delete policy: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Could not delete policy $policyId. $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Could not delete policy: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Invoke-RemoveQueuedApp.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveQueuedApp.ps1 index 1e1d3fd4de14..f1de92bdeab7 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveQueuedApp.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveQueuedApp.ps1 @@ -11,7 +11,8 @@ Function Invoke-RemoveQueuedApp { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' $ID = $request.query.id try { @@ -19,14 +20,14 @@ Function Invoke-RemoveQueuedApp { $Filter = "PartitionKey eq 'apps' and RowKey eq '$id'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey Remove-AzDataTableEntity @Table -Entity $clearRow - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Removed application queue for $ID." -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Removed application queue for $ID." -Sev 'Info' $body = [pscustomobject]@{'Results' = 'Successfully removed from queue.' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to remove application queue for $ID. $($_.Exception.Message)" -Sev 'Error' + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Failed to remove application queue for $ID. $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage $body = [pscustomobject]@{'Results' = 'Failed to remove standard)' } } - # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPCore/Public/Invoke-RemoveSpamfilter.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveSpamfilter.ps1 index d1d0160aaa34..736b834d0d72 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveSpamfilter.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveSpamfilter.ps1 @@ -11,7 +11,8 @@ Function Invoke-RemoveSpamfilter { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' $Tenantfilter = $request.Query.tenantfilter $Params = @{ @@ -24,10 +25,10 @@ Function Invoke-RemoveSpamfilter { $cmdlet = 'Remove-HostedContentFilterPolicy' $null = New-ExoRequest -tenantid $Tenantfilter -cmdlet $cmdlet -cmdParams $params -useSystemmailbox $true $Result = "Deleted $($Request.query.name)" - Write-LogMessage -API 'TransportRules' -tenant $tenantfilter -message "Deleted transport rule $($Request.query.name)" -sev Debug + Write-LogMessage -user $User -API 'TransportRules' -tenant $tenantfilter -message "Deleted transport rule $($Request.query.name)" -sev Debug } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception - Write-LogMessage -API 'TransportRules' -tenant $tenantfilter -message "Failed deleting transport rule $($Request.query.name). Error:$ErrorMessage" -Sev Error + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API 'TransportRules' -tenant $tenantfilter -message "Failed deleting transport rule $($Request.query.name). Error:$($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage $Result = $ErrorMessage } # Associate values to output bindings by calling 'Push-OutputBinding'. diff --git a/Modules/CIPPCore/Public/Invoke-RemoveSpamfilterTemplate.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveSpamfilterTemplate.ps1 index eaa19ff08df6..8e4f8d870eed 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveSpamfilterTemplate.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveSpamfilterTemplate.ps1 @@ -11,7 +11,8 @@ Function Invoke-RemoveSpamfilterTemplate { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' $ID = $request.query.id try { @@ -19,11 +20,12 @@ Function Invoke-RemoveSpamfilterTemplate { $Filter = "PartitionKey eq 'SpamfilterTemplate' and RowKey eq '$id'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey Remove-AzDataTableEntity @Table -Entity $clearRow - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Removed Transport Rule Template with ID $ID." -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Removed Transport Rule Template with ID $ID." -Sev 'Info' $body = [pscustomobject]@{'Results' = 'Successfully removed Transport Rule Template' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to remove Transport Rule template $ID. $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Failed to remove template: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Failed to remove Transport Rule template $ID. $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Failed to remove template: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Invoke-RemoveStandard.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveStandard.ps1 index 88d7f21e3212..06f864c69222 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveStandard.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveStandard.ps1 @@ -11,7 +11,8 @@ Function Invoke-RemoveStandard { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' $ID = $request.query.id try { @@ -19,12 +20,13 @@ Function Invoke-RemoveStandard { $Filter = "PartitionKey eq 'standards' and RowKey eq '$id'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey Remove-AzDataTableEntity @Table -Entity $clearRow - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Removed standards for $ID." -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Removed standards for $ID." -Sev 'Info' $body = [pscustomobject]@{'Results' = 'Successfully removed standards deployment' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to remove standard for $ID. $($_.Exception.Message)" -Sev 'Error' + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Failed to remove standard for $ID. $($ErrorMessage.NormalizedError)" -Sev 'Error' $body = [pscustomobject]@{'Results' = 'Failed to remove standard)' } } diff --git a/Modules/CIPPCore/Public/Invoke-RemoveStandardTemplate.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveStandardTemplate.ps1 index b7c11bd2d584..d00b4da7bffa 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveStandardTemplate.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveStandardTemplate.ps1 @@ -11,7 +11,8 @@ Function Invoke-RemoveStandardTemplate { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' $ID = $request.query.ID try { @@ -20,11 +21,12 @@ Function Invoke-RemoveStandardTemplate { $Filter = "PartitionKey eq 'StandardsTemplate' and RowKey eq '$id'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey Remove-AzDataTableEntity @Table -Entity $clearRow - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Removed Standards Template named $($ClearRow.name) and id $($id)" -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Removed Standards Template named $($ClearRow.name) and id $($id)" -Sev 'Info' $body = [pscustomobject]@{'Results' = 'Successfully removed Template' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to remove Standards template $ID. $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Failed to remove template: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Failed to remove Standards template $ID. $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Failed to remove template: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Invoke-RemoveTransportRule.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveTransportRule.ps1 index aa358ad25202..8db570555836 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveTransportRule.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveTransportRule.ps1 @@ -11,7 +11,8 @@ Function Invoke-RemoveTransportRule { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' $Tenantfilter = $request.Query.tenantfilter @@ -23,9 +24,10 @@ Function Invoke-RemoveTransportRule { $cmdlet = 'Remove-TransportRule' $null = New-ExoRequest -tenantid $Tenantfilter -cmdlet $cmdlet -cmdParams $params -UseSystemMailbox $true $Result = "Deleted $($Request.query.guid)" - Write-LogMessage -API 'TransportRules' -tenant $tenantfilter -message "Deleted transport rule $($Request.query.guid)" -sev Debug + Write-LogMessage -user $User -API $APIName -tenant $tenantfilter -message "Deleted transport rule $($Request.query.guid)" -sev Debug } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception + Write-LogMessage -user $User -API $APIName -tenant $tenantfilter -message "Failed deleting transport rule $($Request.query.guid). Error:$($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage $Result = $ErrorMessage } # Associate values to output bindings by calling 'Push-OutputBinding'. diff --git a/Modules/CIPPCore/Public/Invoke-RemoveTransportRuleTemplate.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveTransportRuleTemplate.ps1 index 3510d02b34a2..f01c97da7adb 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveTransportRuleTemplate.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveTransportRuleTemplate.ps1 @@ -11,7 +11,8 @@ Function Invoke-RemoveTransportRuleTemplate { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' $ID = $request.query.id try { @@ -19,11 +20,12 @@ Function Invoke-RemoveTransportRuleTemplate { $Filter = "PartitionKey eq 'TransportTemplate' and RowKey eq '$id'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey Remove-AzDataTableEntity @Table -Entity $clearRow - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Removed Transport Rule Template with ID $ID." -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Removed Transport Rule Template with ID $ID." -Sev 'Info' $body = [pscustomobject]@{'Results' = 'Successfully removed Transport Rule Template' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to remove Transport Rule template $ID. $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Failed to remove template: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Failed to remove Transport Rule template $ID. $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Failed to remove template: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Invoke-RemoveUser.ps1 b/Modules/CIPPCore/Public/Invoke-RemoveUser.ps1 index 040278ba9d80..a018e2ffd710 100644 --- a/Modules/CIPPCore/Public/Invoke-RemoveUser.ps1 +++ b/Modules/CIPPCore/Public/Invoke-RemoveUser.ps1 @@ -11,20 +11,22 @@ Function Invoke-RemoveUser { param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $User = $request.headers.'x-ms-client-principal' + Write-LogMessage -user $User -API $APINAME -message 'Accessed this API' -Sev 'Debug' # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter $userid = $Request.Query.ID if (!$userid) { exit } try { - $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($userid)" -type DELETE -tenant $TenantFilter - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Deleted $userid" -Sev 'Info' -tenant $TenantFilter + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($userid)" -type DELETE -tenant $TenantFilter + Write-LogMessage -user $User -API $APINAME -message "Deleted $userid" -Sev 'Info' -tenant $TenantFilter $body = [pscustomobject]@{'Results' = 'Successfully deleted the user.' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Could not delete user $userid. $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter - $body = [pscustomobject]@{'Results' = "Could not delete user: $($_.Exception.Message)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Could not delete user $userid. $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + $body = [pscustomobject]@{'Results' = "Could not delete user: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/New-CIPPAPIConfig.ps1 b/Modules/CIPPCore/Public/New-CIPPAPIConfig.ps1 index 58231273000e..873e264b11ec 100644 --- a/Modules/CIPPCore/Public/New-CIPPAPIConfig.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPAPIConfig.ps1 @@ -3,41 +3,39 @@ function New-CIPPAPIConfig { [CmdletBinding()] param ( - $APIName = "CIPP API Config", + $APIName = 'CIPP API Config', $ExecutingUser, $resetpassword ) $null = Connect-AzAccount -Identity - $currentapp = (Get-AzKeyVaultSecret -VaultName $ENV:WEBSITE_DEPLOYMENT_ID -Name "CIPPAPIAPP" -AsPlainText) + $currentapp = (Get-AzKeyVaultSecret -VaultName $ENV:WEBSITE_DEPLOYMENT_ID -Name 'CIPPAPIAPP' -AsPlainText) $subscription = $($ENV:WEBSITE_OWNER_NAME).Split('+')[0] try { if ($currentapp) { $APIApp = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications(appid='$($currentapp)')" -NoAuthCheck $true - } - else { + } else { $CreateBody = @" {"api":{"oauth2PermissionScopes":[{"adminConsentDescription":"Allow the application to access CIPP-API on behalf of the signed-in user.","adminConsentDisplayName":"Access CIPP-API","id":"ba7ffeff-96ea-4ac4-9822-1bcfee9adaa4","isEnabled":true,"type":"User","userConsentDescription":"Allow the application to access CIPP-API on your behalf.","userConsentDisplayName":"Access CIPP-API","value":"user_impersonation"}]},"displayName":"CIPP-API","requiredResourceAccess":[{"resourceAccess":[{"id":"e1fe6dd8-ba31-4d61-89e7-88639da4683d","type":"Scope"}],"resourceAppId":"00000003-0000-0000-c000-000000000000"}],"signInAudience":"AzureADMyOrg","web":{"homePageUrl":"https://cipp.app","implicitGrantSettings":{"enableAccessTokenIssuance":false,"enableIdTokenIssuance":true},"redirectUris":["https://$($ENV:Website_hostname)/.auth/login/aad/callback"]}} "@ - Write-Host "Creating app" - $APIApp = New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications" -NoAuthCheck $true -type POST -body $CreateBody - Write-Host "Creating password" - $APIPassword = New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($APIApp.id)/addPassword" -NoAuthCheck $true -type POST -body "{`"passwordCredential`":{`"displayName`":`"Generated by API Setup`"}}" - Write-Host "Adding App URL" - $APIIdUrl = New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($APIApp.id)" -NoAuthCheck $true -type PATCH -body "{`"identifierUris`":[`"api://$($APIApp.appId)`"]}" - Write-Host "Adding serviceprincipal" - $ServicePrincipal = New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/serviceprincipals" -NoAuthCheck $true -type POST -body "{`"accountEnabled`":true,`"appId`":`"$($APIApp.appId)`",`"displayName`":`"CIPP-API`",`"tags`":[`"WindowsAzureActiveDirectoryIntegratedApp`",`"AppServiceIntegratedApp`"]}" + Write-Host 'Creating app' + $APIApp = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/v1.0/applications' -NoAuthCheck $true -type POST -body $CreateBody + Write-Host 'Creating password' + $APIPassword = New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($APIApp.id)/addPassword" -NoAuthCheck $true -type POST -body "{`"passwordCredential`":{`"displayName`":`"Generated by API Setup`"}}" + Write-Host 'Adding App URL' + $APIIdUrl = New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($APIApp.id)" -NoAuthCheck $true -type PATCH -body "{`"identifierUris`":[`"api://$($APIApp.appId)`"]}" + Write-Host 'Adding serviceprincipal' + $ServicePrincipal = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/v1.0/serviceprincipals' -NoAuthCheck $true -type POST -body "{`"accountEnabled`":true,`"appId`":`"$($APIApp.appId)`",`"displayName`":`"CIPP-API`",`"tags`":[`"WindowsAzureActiveDirectoryIntegratedApp`",`"AppServiceIntegratedApp`"]}" } if ($resetpassword) { - Write-Host "Removing all old passwords" + Write-Host 'Removing all old passwords' $RemovePasswords = New-GraphPOSTRequest -type Patch -uri "https://graph.microsoft.com/v1.0/applications/$($APIApp.id)/" -body '{"passwordCredentials":[]}' -NoAuthCheck $true - $passwordDate = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") - $APIPassword = New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($APIApp.id)/addPassword" -NoAuthCheck $true -type POST -body "{`"passwordCredential`":{`"displayName`":`"Generated by API Setup`"}}" - Write-LogMessage -user $ExecutingUser -API $APINAME -tenant 'None '-message "Reset CIPP API Password." -Sev "info" - } - else { - $CurrentSettings = New-GraphGetRequest -uri "https://management.azure.com/subscriptions/$($subscription)/resourceGroups/$ENV:WEBSITE_RESOURCE_GROUP/providers/Microsoft.Web/sites/$ENV:WEBSITE_SITE_NAME/Config/authsettingsV2/list?api-version=2018-11-01" -NoAuthCheck $true -scope "https://management.azure.com/.default" - Write-Host "setting settings" + $passwordDate = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') + $APIPassword = New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($APIApp.id)/addPassword" -NoAuthCheck $true -type POST -body "{`"passwordCredential`":{`"displayName`":`"Generated by API Setup`"}}" + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant 'None '-message 'Reset CIPP API Password.' -Sev 'info' + } else { + $CurrentSettings = New-GraphGetRequest -uri "https://management.azure.com/subscriptions/$($subscription)/resourceGroups/$ENV:WEBSITE_RESOURCE_GROUP/providers/Microsoft.Web/sites/$ENV:WEBSITE_SITE_NAME/Config/authsettingsV2/list?api-version=2018-11-01" -NoAuthCheck $true -scope 'https://management.azure.com/.default' + Write-Host 'setting settings' $currentSettings.properties.identityProviders.azureActiveDirectory = @{ registration = @{ clientId = $APIApp.appId @@ -48,10 +46,10 @@ function New-CIPPAPIConfig { } } $currentBody = ConvertTo-Json -Depth 15 -InputObject ($currentSettings | Select-Object Properties) - 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 + 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 @@ -59,12 +57,12 @@ function New-CIPPAPIConfig { Results = "API Enabled. Your API URL is https://$($ENV:Website_hostname). Your Application ID is $($APIApp.AppId) and your Application Secret is $($APIPassword.secretText) - Copy these keys, they are only shown once." } - } - catch { - Write-LogMessage -user $ExecutingUser -API $APINAME -tenant 'None' -message "Failed to setup CIPP-API Access: $($_.Exception.Message) Linenumber: $($_.InvocationInfo.ScriptLineNumber)" -Sev "Error" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant 'None' -message "Failed to setup CIPP-API Access: $($ErrorMessage.NormalizedError) Linenumber: $($_.InvocationInfo.ScriptLineNumber)" -Sev 'Error' -LogData $ErrorMessage return @{ - Results = " but could not set API configuration: $($_.Exception.Message)" + Results = " but could not set API configuration: $($ErrorMessage.NormalizedError)" } } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/New-CIPPApplicationCopy.ps1 b/Modules/CIPPCore/Public/New-CIPPApplicationCopy.ps1 new file mode 100644 index 000000000000..1b20dea8eb81 --- /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) { + $null = 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..266c724ff39b 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' @@ -39,51 +40,52 @@ function New-CIPPBackup { $Result = Add-CIPPAzDataTableEntity @Table -entity $entity -Force Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Created CIPP Backup' -Sev 'Debug' } catch { - 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)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup for CIPP: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + [pscustomobject]@{'Results' = "Backup Creation failed: $($ErrorMessage.NormalizedError)" } } - + } 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)" } + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + [pscustomobject]@{'Results' = "Backup Creation failed: $($ErrorMessage.NormalizedError)" } } } #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' + $State = 'Backup finished succesfully' + $Result + } catch { + $State = 'Failed to write backup to table storage' + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup for Conditional Access Policies: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + [pscustomobject]@{'Results' = "Backup Creation failed: $($ErrorMessage.NormalizedError)" } } } } - return $BackupData + return [pscustomobject]@{ + BackupName = $RowKey + BackupState = $State + BackupData = $BackupData + } } diff --git a/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 new file mode 100644 index 000000000000..e1905ac3d49e --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 @@ -0,0 +1,98 @@ +function New-CIPPBackupTask { + [CmdletBinding()] + param ( + $Task, + $TenantFilter + ) + + $BackupData = switch ($Task) { + 'users' { + Write-Host "Backup users for $TenantFilter" + $Users = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $TenantFilter | Select-Object * -ExcludeProperty mail, provisionedPlans, onPrem*, *passwordProfile*, *serviceProvisioningErrors*, isLicenseReconciliationNeeded, isManagementRestricted, isResourceAccount, *date*, *external*, identities, deletedDateTime, isSipEnabled, assignedPlans, cloudRealtimeCommunicationInfo, deviceKeys, provisionedPlan, securityIdentifier + #remove the property if the value is $null + $Users | ForEach-Object { + $_.psobject.properties | Where-Object { $null -eq $_.Value } | ForEach-Object { + $_.psobject.properties.Remove($_.Name) + } + } + $Users + } + '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" + $Policies = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter + Write-Host 'Creating templates for found Conditional Access Policies' + foreach ($policy in $policies) { + try { + New-CIPPCATemplate -TenantFilter $TenantFilter -JSON $policy + } catch { + "Failed to create a template of the Conditional Access Policy with ID: $($policy.id). Error: $($_.Exception.Message)" + } + } + } + 'intuneconfig' { + Write-Host "Backup Intune Configuration Policies for $TenantFilter" + $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' + ) + + $Policies = foreach ($url in $GraphURLS) { + try { + $Policies = New-GraphGetRequest -uri "$($url)" -tenantid $TenantFilter + $URLName = (($url).split('?') | Select-Object -First 1) -replace 'https://graph.microsoft.com/beta/deviceManagement/', '' + foreach ($Policy in $Policies) { + try { + New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName $URLName -ID $Policy.ID + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + "Failed to create a template of the Intune Configuration Policy with ID: $($Policy.id). Error: $ErrorMessage" + } + } + } catch { + Write-Host "Failed to backup $url" + } + } + } + 'intunecompliance' { + Write-Host "Backup Intune Configuration Policies for $TenantFilter" + + 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' { + Write-Host "Backup Intune Configuration Policies for $TenantFilter" + + 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-CIPPCAPolicy.ps1 b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 index 10f92773ee22..e75847a094cf 100644 --- a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 @@ -10,6 +10,9 @@ function New-CIPPCAPolicy { $APIName = 'Create CA Policy', $ExecutingUser ) + + $User = $request.headers.'x-ms-client-principal' + function Remove-EmptyArrays ($Object) { if ($Object -is [Array]) { foreach ($Item in $Object) { Remove-EmptyArrays $Item } @@ -23,7 +26,7 @@ function New-CIPPCAPolicy { foreach ($Name in @($Object.psobject.properties.Name)) { if ($Object.$Name -is [Array] -and $Object.$Name.get_Count() -eq 0) { $Object.PSObject.Properties.Remove($Name) - } elseif ($object.$name -eq $null) { + } elseif ($null -eq $object.$name) { $Object.PSObject.Properties.Remove($Name) } else { Remove-EmptyArrays $Object.$Name } } @@ -38,11 +41,11 @@ function New-CIPPCAPolicy { param($groupNames) return $groupNames | ForEach-Object { if (Test-IsGuid $_) { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Already GUID, no need to replace: $_" -Sev 'Debug' + Write-LogMessage -user $User -API $APINAME -message "Already GUID, no need to replace: $_" -Sev 'Debug' $_ # it's a GUID, so we keep it } else { $groupId = ($groups | Where-Object -Property displayName -EQ $_).id # it's a display name, so we get the group ID - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Replaced group name $_ with ID $groupId" -Sev 'Debug' + Write-LogMessage -user $User -API $APINAME -message "Replaced group name $_ with ID $groupId" -Sev 'Debug' $groupId } } @@ -75,7 +78,7 @@ function New-CIPPCAPolicy { $Body = ConvertTo-Json -InputObject $JSONObj.GrantControls.authenticationStrength $GraphRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/authenticationStrength/policies' -body $body -Type POST -tenantid $tenantfilter $JSONObj.GrantControls.authenticationStrength = @{ id = $ExistingStrength.id } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created new Authentication Strength Policy: $($JSONObj.GrantControls.authenticationStrength.displayName)" -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Created new Authentication Strength Policy: $($JSONObj.GrantControls.authenticationStrength.displayName)" -Sev 'Info' } } @@ -90,14 +93,13 @@ function New-CIPPCAPolicy { id = ($CheckExististing | Where-Object -Property displayName -EQ $Location.displayName).id name = ($CheckExististing | Where-Object -Property displayName -EQ $Location.displayName).displayName } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Matched a CA policy with the existing Named Location: $($location.displayName)" -Sev 'Info' - + Write-LogMessage -user $User -API $APINAME -message "Matched a CA policy with the existing Named Location: $($location.displayName)" -Sev 'Info' + } else { if ($location.countriesAndRegions) { $location.countriesAndRegions = @($location.countriesAndRegions) } $Body = ConvertTo-Json -InputObject $Location - Write-Host "Trying to create named location with: $body" $GraphRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations' -body $body -Type POST -tenantid $tenantfilter - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created new Named Location: $($location.displayName)" -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -message "Created new Named Location: $($location.displayName)" -Sev 'Info' [pscustomobject]@{ id = $GraphRequest.id name = $GraphRequest.displayName @@ -138,10 +140,10 @@ function New-CIPPCAPolicy { Write-Host 'Replacement pattern for inclusions and exclusions is displayName.' $users = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/users?$select=id,displayName' -tenantid $TenantFilter $groups = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/groups?$select=id,displayName' -tenantid $TenantFilter - + if ($JSONObj.conditions.users.includeUsers -and $JSONObj.conditions.users.includeUsers -notin 'All', 'None', 'GuestOrExternalUsers') { $JSONObj.conditions.users.includeUsers = @(($users | Where-Object -Property displayName -In $JSONObj.conditions.users.includeUsers).id) } if ($JSONObj.conditions.users.excludeUsers) { $JSONObj.conditions.users.excludeUsers = @(($users | Where-Object -Property displayName -In $JSONObj.conditions.users.excludeUsers).id) } - + # Check the included and excluded groups foreach ($groupType in 'includeGroups', 'excludeGroups') { if ($JSONObj.conditions.users.PSObject.Properties.Name -contains $groupType) { @@ -149,13 +151,14 @@ function New-CIPPCAPolicy { } } } catch { - throw "Failed to replace displayNames for conditional access rule $($JSONObj.displayName): $($_.exception.message)" - Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to replace displayNames for conditional access rule $($JSONObj.displayName)" -sev 'Error' + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to replace displayNames for conditional access rule $($JSONObj.displayName). Error: $($ErrorMessage.NormalizedError)" -sev 'Error' -LogData $ErrorMessage + throw "Failed to replace displayNames for conditional access rule $($JSONObj.displayName): $($ErrorMessage.NormalizedError)" } - } + } } $JsonObj.PSObject.Properties.Remove('LocationInfo') - $RawJSON = $JSONObj | ConvertTo-Json -Depth 10 -Compress + $RawJSON = ConvertTo-Json -InputObject $JSONObj -Depth 10 -Compress Write-Host $RawJSON try { Write-Host 'Checking' @@ -167,18 +170,18 @@ function New-CIPPCAPolicy { } else { Write-Host "overwriting $($CheckExististing.id)" $PatchRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExististing.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Updated Conditional Access Policy $($JSONObj.Displayname) to the template standard." -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -tenant $($Tenant) -message "Updated Conditional Access Policy $($JSONObj.Displayname) to the template standard." -Sev 'Info' return "Updated policy $displayname for $tenantfilter" } } else { Write-Host 'Creating' $CreateRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $tenantfilter -type POST -body $RawJSON - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Added Conditional Access Policy $($JSONObj.Displayname)" -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -tenant $($Tenant) -message "Added Conditional Access Policy $($JSONObj.Displayname)" -Sev 'Info' return "Created policy $displayname for $tenantfilter" } } catch { - Write-Host "$($_.exception | ConvertTo-Json)" - throw "Failed to create or update conditional access rule $($JSONObj.displayName): $($_.exception.message)" - Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to create or update conditional access rule $($JSONObj.displayName): $($_.exception.message) " -sev 'Error' + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to create or update conditional access rule $($JSONObj.displayName): $($ErrorMessage.NormalizedError) " -sev 'Error' -LogData $ErrorMessage + throw "Failed to create or update conditional access rule $($JSONObj.displayName): $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 b/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 new file mode 100644 index 000000000000..37577fd35f72 --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 @@ -0,0 +1,85 @@ +function New-CIPPCATemplate { + [CmdletBinding()] + param ( + $TenantFilter, + $JSON, + $APIName = 'Add CIPP CA Template', + $ExecutingUser + ) + + $JSON = ([pscustomobject]$JSON) | ForEach-Object { + $NonEmptyProperties = $_.psobject.Properties | Where-Object { $null -ne $_.Value } | Select-Object -ExpandProperty Name + $_ | Select-Object -Property $NonEmptyProperties + } + + $includelocations = New-Object System.Collections.ArrayList + $IncludeJSON = foreach ($Location in $JSON.conditions.locations.includeLocations) { + $locationinfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations' -tenantid $TenantFilter | Where-Object -Property id -EQ $location | Select-Object * -ExcludeProperty id, *time* + $null = if ($locationinfo) { $includelocations.add($locationinfo.displayName) } else { $includelocations.add($location) } + $locationinfo + } + if ($includelocations) { $JSON.conditions.locations.includeLocations = $includelocations } + + + $excludelocations = New-Object System.Collections.ArrayList + $ExcludeJSON = foreach ($Location in $JSON.conditions.locations.excludeLocations) { + $locationinfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations' -tenantid $TenantFilter | Where-Object -Property id -EQ $location | Select-Object * -ExcludeProperty id, *time* + $null = if ($locationinfo) { $excludelocations.add($locationinfo.displayName) } else { $excludelocations.add($location) } + $locationinfo + } + + if ($excludelocations) { $JSON.conditions.locations.excludeLocations = $excludelocations } + if ($JSON.conditions.users.includeUsers) { + $JSON.conditions.users.includeUsers = @($JSON.conditions.users.includeUsers | ForEach-Object { + if ($_ -in 'All', 'None', 'GuestOrExternalUsers') { return $_ } + try { + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_)" -tenantid $TenantFilter).displayName + } catch { + return $_ + } + }) + } + + if ($JSON.conditions.users.excludeUsers) { + $JSON.conditions.users.excludeUsers = @($JSON.conditions.users.excludeUsers | ForEach-Object { + if ($_ -in 'All', 'None', 'GuestOrExternalUsers') { return $_ } + try { + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_)" -tenantid $TenantFilter).displayName + } catch { + return $_ + } + }) + } + + # Function to check if a string is a GUID + function Test-IsGuid($string) { + return [guid]::tryparse($string, [ref][guid]::Empty) + } + + if ($JSON.conditions.users.includeGroups) { + $JSON.conditions.users.includeGroups = @($JSON.conditions.users.includeGroups | ForEach-Object { + if ($_ -in 'All', 'None', 'GuestOrExternalUsers' -or -not (Test-IsGuid $_)) { return $_ } + try { + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($_)" -tenantid $TenantFilter).displayName + } catch { + return $_ + } + }) + } + if ($JSON.conditions.users.excludeGroups) { + $JSON.conditions.users.excludeGroups = @($JSON.conditions.users.excludeGroups | ForEach-Object { + if ($_ -in 'All', 'None', 'GuestOrExternalUsers' -or -not (Test-IsGuid $_)) { return $_ } + try { + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($_)" -tenantid $TenantFilter).displayName + } catch { + return $_ + } + }) + } + + $JSON | Add-Member -NotePropertyName 'LocationInfo' -NotePropertyValue @($IncludeJSON, $ExcludeJSON) + + $JSON = (ConvertTo-Json -Compress -Depth 100 -InputObject $JSON) + return $JSON +} + diff --git a/Modules/CIPPCore/Public/New-CIPPDeviceAction.ps1 b/Modules/CIPPCore/Public/New-CIPPDeviceAction.ps1 index 3e0312750dc8..8a1d2eaf5476 100644 --- a/Modules/CIPPCore/Public/New-CIPPDeviceAction.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPDeviceAction.ps1 @@ -8,13 +8,13 @@ function New-CIPPDeviceAction { $ExecutingUser, $APINAME ) - try { - $GraphRequest = New-Graphpostrequest -uri "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$DeviceFilter')/$($Action)" -type POST -tenantid $TenantFilter -body $ActionBody - Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $TenantFilter -message "Queued $Action on $DeviceFilter" -Sev "Info" + try { + $null = New-Graphpostrequest -uri "https://graph.microsoft.com/beta/deviceManagement/managedDevices('$DeviceFilter')/$($Action)" -type POST -tenantid $TenantFilter -body $ActionBody + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $TenantFilter -message "Queued $Action on $DeviceFilter" -Sev 'Info' return "Queued $Action on $DeviceFilter" - } - catch { - Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $TenantFilter -message "Failed to queue action $Action on $DeviceFilter : $($_.Exception.Message)" -Sev "Error" - return "Failed to queue action $Action on $DeviceFilter $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $TenantFilter -message "Failed to queue action $Action on $DeviceFilter : $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + return "Failed to queue action $Action on $DeviceFilter $($ErrorMessage.NormalizedError)" } } 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-CIPPOneDriveShortCut.ps1 b/Modules/CIPPCore/Public/New-CIPPOneDriveShortCut.ps1 index e140b1e6052c..d270b17efde3 100644 --- a/Modules/CIPPCore/Public/New-CIPPOneDriveShortCut.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPOneDriveShortCut.ps1 @@ -6,33 +6,33 @@ function New-CIPPOneDriveShortCut { $userid, $URL, $TenantFilter, - $APIName = "Create OneDrive shortcut", + $APIName = 'Create OneDrive shortcut', $ExecutingUser ) Write-Host "Received $username and $userid. We're using $url and $TenantFilter" try { - $SiteInfo = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/sites/" -tenantid $TenantFilter -asapp $true | Where-Object -Property weburl -EQ $url + $SiteInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/' -tenantid $TenantFilter -asapp $true | Where-Object -Property weburl -EQ $url $ListItemUniqueId = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/sites/$($siteInfo.id)/drive?`$select=SharepointIds" -tenantid $TenantFilter -asapp $true).SharePointIds $body = [PSCustomObject]@{ name = "$($SiteInfo.displayName)" remoteItem = @{ sharepointIds = @{ listId = $($ListItemUniqueId.listid) - listItemUniqueId = "root" + listItemUniqueId = 'root' siteId = $($ListItemUniqueId.siteId) siteUrl = $($ListItemUniqueId.siteUrl) webId = $($ListItemUniqueId.webId) } } - '@microsoft.graph.conflictBehavior' = "rename" + '@microsoft.graph.conflictBehavior' = 'rename' } | ConvertTo-Json -Depth 10 New-GraphPOSTRequest -method POST "https://graph.microsoft.com/beta/users/$username/drive/root/children" -body $body -tenantid $TenantFilter -asapp $true - Write-LogMessage -message "Created OneDrive shortcut called $($SiteInfo.displayName) for $($username)" -Sev 'info' -API $APIName -user $ExecutingUser + Write-LogMessage -API $APIName -user $ExecutingUser -message "Created OneDrive shortcut called $($SiteInfo.displayName) for $($username)" -Sev 'info' return "Created OneDrive Shortcut for $username called $($SiteInfo.displayName) " - } - catch { - Write-LogMessage -message "Could not add Onedrive shortcut to $username : $($_.Exception.Message)" -Sev 'error' -API $APIName -user $ExecutingUser - return "Could not add Onedrive shortcut to $username : $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add Onedrive shortcut to $username : $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + return "Could not add Onedrive shortcut to $username : $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/New-CIPPRestore.ps1 b/Modules/CIPPCore/Public/New-CIPPRestore.ps1 new file mode 100644 index 000000000000..f3dd2ca21cbd --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPRestore.ps1 @@ -0,0 +1,18 @@ +function New-CIPPRestore { + [CmdletBinding()] + param ( + $TenantFilter, + $Type = 'Scheduled', + $RestoreValues, + $APIName = 'CIPP Restore', + $ExecutingUser + ) + + Write-Host "Scheduled Restore psproperties: $(([pscustomobject]$RestoreValues).psobject.Properties)" + Write-LogMessage -user $ExecutingUser -API $APINAME -message 'Restored backup' -Sev 'Debug' + $RestoreData = foreach ($ScheduledBackup in ([pscustomobject]$RestoreValues).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..afa3463b4fd0 --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 @@ -0,0 +1,176 @@ +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' { + $currentUsers = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999&select=id,userPrincipalName' -tenantid $TenantFilter + $backupUsers = $BackupData.users | ConvertFrom-Json + $BackupUsers | ForEach-Object { + try { + $JSON = $_ | ConvertTo-Json -Depth 100 -Compress + $DisplayName = $_.displayName + $UPN = $_.userPrincipalName + if ($overwrite) { + if ($_.id -in $currentUsers.id) { + New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/users/$($_.id)" -tenantid $TenantFilter -body $JSON -type PATCH + Write-LogMessage -message "Restored $($UPN) from backup by patching the existing object." -Sev 'info' + "The user existed. Restored $($UPN) from backup" + } else { + New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/users' -tenantid $TenantFilter -body $JSON -type POST + Write-LogMessage -message "Restored $($UPN) from backup by creating a new object." -Sev 'info' + "The user did not exist. Restored $($UPN) from backup" + } + } + if (!$overwrite) { + if ($_.id -notin $backupUsers.id) { + New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/users' -tenantid $TenantFilter -body $JSON -type POST + Write-LogMessage -message "Restored $($UPN) from backup" -Sev 'info' + "Restored $($UPN) from backup" + } else { + Write-LogMessage -message "User $($UPN) already exists in tenant $TenantFilter and overwrite is disabled" -Sev 'info' + "User $($UPN) already exists in tenant $TenantFilter and overwrite is disabled" + } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + "Could not restore user $($UPN): $($ErrorMessage.NormalizedError) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore user $($UPN): $($ErrorMessage.NormalizedError) " -Sev 'Error' -LogData $ErrorMessage + } + } + } + 'groups' { + Write-Host "Restore groups for $TenantFilter" + $backupGroups = $BackupData.groups | ConvertFrom-Json + $Groups = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $TenantFilter + $BackupGroups | ForEach-Object { + try { + $JSON = $_ | ConvertTo-Json -Depth 100 -Compress + $DisplayName = $_.displayName + if ($overwrite) { + if ($_.id -in $Groups.id) { + New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/groups/$($_.id)" -tenantid $TenantFilter -body $JSON -type PATCH + Write-LogMessage -message "Restored $DisplayName from backup by patching the existing object." -Sev 'info' + "The group existed. Restored $DisplayName from backup" + } else { + New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/groups' -tenantid $TenantFilter -body $JSON -type POST + Write-LogMessage -message "Restored $DisplayName from backup" -Sev 'info' + "Restored $DisplayName from backup" + } + } + if (!$overwrite) { + if ($_.id -notin $Groups.id) { + New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/groups' -tenantid $TenantFilter -body $JSON -type POST + Write-LogMessage -message "Restored $DisplayName from backup" -Sev 'info' + "Restored $DisplayName from backup" + } else { + Write-LogMessage -message "Group $DisplayName already exists in tenant $TenantFilter and overwrite is disabled" -Sev 'info' + "Group $DisplayName already exists in tenant $TenantFilter and overwrite is disabled" + } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + "Could not restore group $DisplayName : $($ErrorMessage.NormalizedError) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore group $DisplayName : $($ErrorMessage.NormalizedError) " -Sev 'Error' -LogData $ErrorMessage + } + } + } + 'ca' { + Write-Host "Restore Conditional Access Policies for $TenantFilter" + $BackupCAPolicies = $BackupData.ca | ConvertFrom-Json + $BackupCAPolicies | ForEach-Object { + $JSON = $_ + try { + New-CIPPCAPolicy -replacePattern 'displayName' -Overwrite $overwrite -TenantFilter $TenantFilter -state 'donotchange' -RawJSON $JSON -APIName 'CIPP Restore' -ErrorAction SilentlyContinue + } catch { + $ErrorMessage = Get-CippException -Exception $_ + "Could not restore Conditional Access Policy $DisplayName : $($ErrorMessage.NormalizedError) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore Conditional Access Policy $DisplayName : $($ErrorMessage.NormalizedError) " -Sev 'Error' -LogData $ErrorMessage + } + } + } + 'intuneconfig' { + $BackupConfig = $BackupData.intuneconfig | ConvertFrom-Json + foreach ($backup in $backupConfig) { + try { + Set-CIPPIntunePolicy -TemplateType $backup.Type -TenantFilter $TenantFilter -DisplayName $backup.DisplayName -Description $backup.Description -RawJSON ($backup.TemplateJson) -ErrorAction SilentlyContinue + } catch { + $ErrorMessage = Get-CippException -Exception $_ + "Could not restore Intune Configuration $DisplayName : $($ErrorMessage.NormalizedError) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore Intune Configuration $DisplayName : $($ErrorMessage.NormalizedError) " -Sev 'Error' -LogData $ErrorMessage + } + } + #Convert the manual method to a function + } + 'intunecompliance' { + $BackupConfig = $BackupData.intunecompliance | ConvertFrom-Json + foreach ($backup in $backupConfig) { + try { + Set-CIPPIntunePolicy -TemplateType $backup.Type -TenantFilter $TenantFilter -DisplayName $backup.DisplayName -Description $backup.Description -RawJSON ($backup.TemplateJson) -ErrorAction SilentlyContinue + } catch { + $ErrorMessage = Get-CippException -Exception $_ + "Could not restore Intune Compliance $DisplayName : $($ErrorMessage.NormalizedError) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore Intune Configuration $DisplayName : $($ErrorMessage.NormalizedError) " -Sev 'Error' -LogData $ErrorMessage + } + } + + } + + 'intuneprotection' { + $BackupConfig = $BackupData.intuneprotection | ConvertFrom-Json + foreach ($backup in $backupConfig) { + try { + Set-CIPPIntunePolicy -TemplateType $backup.Type -TenantFilter $TenantFilter -DisplayName $backup.DisplayName -Description $backup.Description -RawJSON ($backup.TemplateJson) -ErrorAction SilentlyContinue + } catch { + $ErrorMessage = Get-CippException -Exception $_ + "Could not restore Intune Protection $DisplayName : $($ErrorMessage.NormalizedError) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore Intune Configuration $DisplayName : $($ErrorMessage.NormalizedError) " -Sev 'Error' -LogData $ErrorMessage + } + } + + } + + 'CippWebhookAlerts' { + Write-Host "Restore Webhook Alerts for $TenantFilter" + $WebhookTable = Get-CIPPTable -TableName 'WebhookRules' + $Backup = $BackupData.CippWebhookAlerts | ConvertFrom-Json + try { + Add-CIPPAzDataTableEntity @WebhookTable -Entity $Backup -Force + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + "Could not restore Webhook Alerts $ErrorMessage" + } + } + 'CippScriptedAlerts' { + Write-Host "Restore Scripted Alerts for $TenantFilter" + $ScheduledTasks = Get-CIPPTable -TableName 'ScheduledTasks' + $Backup = $BackupData.CippScriptedAlerts | ConvertFrom-Json + try { + Add-CIPPAzDataTableEntity @ScheduledTasks -Entity $Backup -Force + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + "Could not restore Scripted Alerts $ErrorMessage " + } + } + 'CippStandards' { + Write-Host "Restore Standards for $TenantFilter" + $Table = Get-CippTable -tablename 'standards' + $StandardsBackup = $BackupData.CippStandards | ConvertFrom-Json + try { + Add-CIPPAzDataTableEntity @Table -Entity $StandardsBackup -Force + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + "Could not restore Standards $ErrorMessage " + } + } + + } + return $RestoreData +} + diff --git a/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 b/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 new file mode 100644 index 000000000000..9959defd302e --- /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 (ConvertTo-Json -InputObject $Request -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/New-CIPPTAP.ps1 b/Modules/CIPPCore/Public/New-CIPPTAP.ps1 index 7b0427a5cf20..c997c6d62daf 100644 --- a/Modules/CIPPCore/Public/New-CIPPTAP.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPTAP.ps1 @@ -3,20 +3,19 @@ function New-CIPPTAP { param ( $userid, $TenantFilter, - $APIName = "Create TAP", + $APIName = 'Create TAP', $ExecutingUser ) try { - $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($userid)/authentication/temporaryAccessPassMethods" -tenantid $TenantFilter -type POST -body "{}" -verbose - $GraphRequest - Write-LogMessage -user $ExecutingUser -API $APIName -message "Created Temporary Access Password (TAP) for $userid" -Sev "Info" -tenant $TenantFilter + $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($userid)/authentication/temporaryAccessPassMethods" -tenantid $TenantFilter -type POST -body '{}' -verbose + Write-LogMessage -user $ExecutingUser -API $APIName -message "Created Temporary Access Password (TAP) for $userid" -Sev 'Info' -tenant $TenantFilter return "The TAP for this user is $($GraphRequest.temporaryAccessPass) - This TAP is usable for the next $($GraphRequest.LifetimeInMinutes) minutes" - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to created TAP for $($userid): $($_.Exception.Message)" -Sev "Error" -tenant $TenantFilter - Return "Failed to create TAP: $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to created TAP for $($userid): $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + Return "Failed to create TAP: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/PermissionsTranslator.json b/Modules/CIPPCore/Public/PermissionsTranslator.json index a0ba05d3dc02..1a88f6dbf708 100644 --- a/Modules/CIPPCore/Public/PermissionsTranslator.json +++ b/Modules/CIPPCore/Public/PermissionsTranslator.json @@ -1,4 +1,11 @@ [ + { + "description": "Allows the app to impersonate the signed-in user to access the Partner Center API.", + "displayName": "Partner Center as User", + "id": "1cebfa2a-fb4d-419e-b5f9-839b4383e05a", + "origin": "Delegated (Microsoft Partner Center)", + "value": "user_impersonation" + }, { "description": "Allows Exchange Management as app", "displayName": "Manage Exchange As Application ", @@ -1004,8 +1011,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 +5300,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", diff --git a/Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1 b/Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1 new file mode 100644 index 000000000000..3c0d9c326024 --- /dev/null +++ b/Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1 @@ -0,0 +1,22 @@ +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 { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not cancel calendar invites for $($username): $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $tenantFilter -LogData $ErrorMessage + return "Could not cancel calendar invites for $($username). Error: $($ErrorMessage.NormalizedError)" + } +} diff --git a/Modules/CIPPCore/Public/Remove-CIPPGroup.ps1 b/Modules/CIPPCore/Public/Remove-CIPPGroup.ps1 index a99750cda781..4dcd10d02988 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPGroup.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPGroup.ps1 @@ -5,26 +5,26 @@ function Remove-CIPPGroup { $GroupType, $ID, $DisplayName, - $APIName = "Remove Group", + $APIName = 'Remove Group', $TenantFilter ) try { - if ($GroupType -eq "Distribution List" -or $GroupType -eq "Mail-Enabled Security") { - New-ExoRequest -tenantid $TenantFilter -cmdlet "Remove-DistributionGroup" -cmdParams @{Identity = $id; BypassSecurityGroupManagerCheck = $true } -useSystemMailbox $true - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantfilter) -message "$($DisplayName) Deleted" -Sev "Info" + if ($GroupType -eq 'Distribution List' -or $GroupType -eq 'Mail-Enabled Security') { + New-ExoRequest -tenantid $TenantFilter -cmdlet 'Remove-DistributionGroup' -cmdParams @{Identity = $id; BypassSecurityGroupManagerCheck = $true } -useSystemMailbox $true + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantfilter) -message "$($DisplayName) Deleted" -Sev 'Info' return "Successfully Deleted $($GroupType) group $($DisplayName)" - } - elseif ($GroupType -eq "Microsoft 365" -or $GroupType -eq "Security") { - $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/groups/$($ID)" -tenantid $TenantFilter -type Delete -verbose - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantfilter) -message "$($DisplayName) Deleted" -Sev "Info" + + } elseif ($GroupType -eq 'Microsoft 365' -or $GroupType -eq 'Security') { + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/groups/$($ID)" -tenantid $TenantFilter -type Delete -verbose + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantfilter) -message "$($DisplayName) Deleted" -Sev 'Info' return "Successfully Deleted $($GroupType) group $($DisplayName)" } - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not delete $DisplayName" -Sev "Error" -tenant $TenantFilter - return "Could not delete $DisplayName. Error: $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not delete $DisplayName. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not delete $DisplayName. Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1 b/Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1 index 54c6a33e1a9d..c434625a3537 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1 @@ -1,28 +1,29 @@ function Remove-CIPPGroupMember( [string]$ExecutingUser, - [string]$GroupType, + [string]$GroupType, [string]$GroupId, - [string]$Member, + [string]$Member, [string]$TenantFilter, [string]$APIName = 'Remove Group Member' ) { try { if ($member -like '*#EXT#*') { $member = [System.Web.HttpUtility]::UrlEncode($member) } - $MemberIDs = 'https://graph.microsoft.com/v1.0/directoryObjects/' + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($member)" -tenantid $TenantFilter).id - $addmemberbody = "{ `"members@odata.bind`": $(ConvertTo-Json @($MemberIDs)) }" + # $MemberIDs = 'https://graph.microsoft.com/v1.0/directoryObjects/' + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($member)" -tenantid $TenantFilter).id + # $addmemberbody = "{ `"members@odata.bind`": $(ConvertTo-Json @($MemberIDs)) }" if ($GroupType -eq 'Distribution list' -or $GroupType -eq 'Mail-Enabled Security') { $Params = @{ Identity = $GroupId; Member = $member; BypassSecurityGroupManagerCheck = $true } - New-ExoRequest -tenantid $TenantFilter -cmdlet 'Remove-DistributionGroupMember' -cmdParams $params -UseSystemMailbox $true + New-ExoRequest -tenantid $TenantFilter -cmdlet 'Remove-DistributionGroupMember' -cmdParams $params -UseSystemMailbox $true } else { New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)/members/$($Member)/`$ref" -tenantid $TenantFilter -type DELETE -body '{}' -Verbose } $Message = "Successfully removed user $($Member) from $($GroupId)." Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message $Message -Sev 'Info' return $message + } catch { - $message = "Failed to remove user $($Member) from $($GroupId): $($_.Exception.Message)" - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message $message -Sev 'error' - return $message + $ErrorMessage = Get-CippException -Exception $_ + $message = "Failed to remove user $($Member) from $($GroupId): $($ErrorMessage.NormalizedError)" + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message $message -Sev 'error' -LogData $ErrorMessage + return $message } - } diff --git a/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 b/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 index e37df34f2bf0..e9cb0b078b23 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 @@ -17,16 +17,16 @@ function Remove-CIPPGroups { Import-Module '.\Modules\AzBobbyTables' Import-Module '.\Modules\CIPPCore' $group = $_ - - try { + + try { $Groupname = ($using:AllGroups | Where-Object -Property id -EQ $group).displayName $IsMailEnabled = ($using:AllGroups | Where-Object -Property id -EQ $group).mailEnabled - $IsM365Group = ($using:AllGroups | Where-Object { $_.id -eq $group -and $_.groupTypes -contains 'Unified' }) -ne $null + $IsM365Group = $null -ne ($using:AllGroups | Where-Object { $_.id -eq $group -and $_.groupTypes -contains 'Unified' }) if ($IsM365Group) { - $RemoveRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$_/members/$($using:userid)/`$ref" -tenantid $using:tenantFilter -type DELETE -body '' -Verbose + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$_/members/$($using:userid)/`$ref" -tenantid $using:tenantFilter -type DELETE -body '' -Verbose } elseif (-not $IsMailEnabled) { - $RemoveRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$_/members/$($using:userid)/`$ref" -tenantid $using:tenantFilter -type DELETE -body '' -Verbose + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$_/members/$($using:userid)/`$ref" -tenantid $using:tenantFilter -type DELETE -body '' -Verbose } elseif ($IsMailEnabled) { $Params = @{ Identity = $Groupname; Member = $using:userid ; BypassSecurityGroupManagerCheck = $true } New-ExoRequest -tenantid $using:tenantFilter -cmdlet 'Remove-DistributionGroupMember' -cmdParams $params -UseSystemMailbox $true @@ -35,13 +35,14 @@ function Remove-CIPPGroups { Write-LogMessage -user $using:ExecutingUser -API $($using:APIName) -message "Removed $($using:Username) from $groupname" -Sev 'Info' -tenant $using:TenantFilter "Successfully removed $($using:Username) from group $Groupname" } catch { - Write-LogMessage -user $using:ExecutingUser -API $($using:APIName) -message "Could not remove $($using:Username) from group $groupname" -Sev 'Error' -tenant $using:TenantFilter - "Could not remove $($using:Username) from group $($Groupname): $($_.Exception.Message). This is likely because its a Dynamic Group or synched with active directory" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $using:ExecutingUser -API $($using:APIName) -message "Could not remove $($using:Username) from group $groupname : $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $using:TenantFilter -LogData $ErrorMessage + "Could not remove $($using:Username) from group $($Groupname): $($ErrorMessage.NormalizedError). This is likely because its a Dynamic Group or synched with active directory" } } if (!$Returnval) { $Returnval = "$($Username) is not a member of any groups." Write-LogMessage -user $ExecutingUser -API $APIName -message "$($Username) is not a member of any groups" -Sev 'Info' -tenant $TenantFilter - } + } return $Returnval } diff --git a/Modules/CIPPCore/Public/Remove-CIPPLicense.ps1 b/Modules/CIPPCore/Public/Remove-CIPPLicense.ps1 index 68c037b495fc..390b276efa20 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPLicense.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPLicense.ps1 @@ -29,7 +29,8 @@ function Remove-CIPPLicense { return "No licenses to remove for $username" } } catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not remove license for $username" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) - return "Could not remove license for $($username). Error: $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not remove license for $username. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not remove license for $($username). Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Remove-CIPPMailboxPermissions.ps1 b/Modules/CIPPCore/Public/Remove-CIPPMailboxPermissions.ps1 index 7035a083ff0f..1e039e8bb8e5 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPMailboxPermissions.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPMailboxPermissions.ps1 @@ -25,7 +25,7 @@ function Remove-CIPPMailboxPermissions { $MailboxPerms = New-ExoRequest -Anchor $UserId -tenantid $Tenantfilter -cmdlet 'Set-Mailbox' -cmdParams @{Identity = $userid; GrantSendonBehalfTo = @{'@odata.type' = '#Exchange.GenericHashTable'; remove = $AccessUser }; } if ($MailboxPerms -notlike '*completed successfully but no settings of*') { Write-LogMessage -user $ExecutingUser -API $APIName -message "Removed SendOnBehalf permissions for $($AccessUser) from $($userid)'s mailbox." -Sev 'Info' -tenant $TenantFilter - "Removed SendOnBehalf permissions for $($AccessUser) from $($userid)'s mailbox." + "Removed SendOnBehalf permissions for $($AccessUser) from $($userid)'s mailbox." } } 'SendAS' { @@ -47,7 +47,8 @@ function Remove-CIPPMailboxPermissions { } return $Results } catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not remove mailbox permissions for $($userid). Error: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter - return "Could not remove mailbox permissions for $($userid). Error: $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not remove mailbox permissions for $($userid). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not remove mailbox permissions for $($userid). Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Remove-CIPPMobileDevice.ps1 b/Modules/CIPPCore/Public/Remove-CIPPMobileDevice.ps1 index e5ae407bf35e..54706e3e97f3 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPMobileDevice.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPMobileDevice.ps1 @@ -17,11 +17,12 @@ function Remove-CIPPMobileDevice { "Could not remove device: $($_.FriendlyName)" } } - if (!$Devices) { $Devices ='No mobile devices have been removed as we could not find any' } + if (!$Devices) { $Devices = 'No mobile devices have been removed as we could not find any' } Write-LogMessage -user $ExecutingUser -API $APIName -message "Deleted mobile devices for $($username)" -Sev 'Info' -tenant $tenantFilter return $devices } catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not delete mobile devices for $($username): $($_.Exception.Message)" -Sev 'Error' -tenant $tenantFilter - return "Could not delete mobile devices for $($username). Error: $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not delete mobile devices for $($username): $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $tenantFilter -LogData $ErrorMessage + return "Could not delete mobile devices for $($username). Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Remove-CIPPRules.ps1 b/Modules/CIPPCore/Public/Remove-CIPPRules.ps1 index c7e481bd0943..67a87f053ab8 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPRules.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPRules.ps1 @@ -4,28 +4,27 @@ function Remove-CIPPRules { $userid, $username, $TenantFilter, - $APIName = "Rules Removal", + $APIName = 'Rules Removal', $ExecutingUser ) try { Write-Host "Checking rules for $username" - $rules = New-ExoRequest -tenantid $TenantFilter -cmdlet "Get-InboxRule" -cmdParams @{mailbox = $username } + $rules = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{mailbox = $username } Write-Host "$($rules.count) rules found" - if ($rules -eq $null) { - Write-LogMessage -user $ExecutingUser -API $APIName -message "No Rules for $($username) to delete" -Sev "Info" -tenant $TenantFilter + if ($null -eq $rules) { + Write-LogMessage -user $ExecutingUser -API $APIName -message "No Rules for $($username) to delete" -Sev 'Info' -tenant $TenantFilter return "No rules for $($username) to delete" - } - else { + } else { ForEach ($rule in $rules) { - New-ExoRequest -tenantid $TenantFilter -cmdlet "Remove-InboxRule" -Anchor $username -cmdParams @{Identity = $rule.Identity } + New-ExoRequest -tenantid $TenantFilter -cmdlet 'Remove-InboxRule' -Anchor $username -cmdParams @{Identity = $rule.Identity } } - Write-LogMessage -user $ExecutingUser -API $APIName -message "Deleted Rules for $($username)" -Sev "Info" -tenant $TenantFilter + Write-LogMessage -user $ExecutingUser -API $APIName -message "Deleted Rules for $($username)" -Sev 'Info' -tenant $TenantFilter return "Deleted Rules for $($username)" } - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not delete rules for $($username): $($_.Exception.Message)" -Sev "Error" -tenant $TenantFilter - return "Could not delete rules for $($username). Error: $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not delete rules for $($username): $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not delete rules for $($username). Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Remove-CIPPUser.ps1 b/Modules/CIPPCore/Public/Remove-CIPPUser.ps1 index 788a4e3332d3..f144b7f1213f 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPUser.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPUser.ps1 @@ -4,19 +4,19 @@ function Remove-CIPPUser { $ExecutingUser, $userid, $username, - $APIName = "Remove User", + $APIName = 'Remove User', $TenantFilter ) try { - $DeleteRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($userid)" -type DELETE -tenant $TenantFilter - Write-LogMessage -user $ExecutingUser, -API $APIName -message "Deleted account $username" -Sev "Info" -tenant $TenantFilter + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($userid)" -type DELETE -tenant $TenantFilter + Write-LogMessage -user $ExecutingUser, -API $APIName -message "Deleted account $username" -Sev 'Info' -tenant $TenantFilter return "Deleted the user account $username" - } - catch { - Write-LogMessage -user $ExecutingUser, -API $APIName -message "Could not delete $username" -Sev "Error" -tenant $TenantFilter - return "Could not delete $username. Error: $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser, -API $APIName -message "Could not delete $username. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not delete $username. Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Request-CIPPSPOPersonalSite.ps1 b/Modules/CIPPCore/Public/Request-CIPPSPOPersonalSite.ps1 new file mode 100644 index 000000000000..1cfaec1e75ef --- /dev/null +++ b/Modules/CIPPCore/Public/Request-CIPPSPOPersonalSite.ps1 @@ -0,0 +1,52 @@ +function Request-CIPPSPOPersonalSite { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$TenantFilter, + [Parameter(Mandatory = $true)] + [string[]]$UserEmails, + [string]$ExecutingUser = 'CIPP', + [string]$APIName = 'Request-CIPPSPOPersonalSite' + ) + $UserList = [System.Collections.Generic.List[string]]::new() + foreach ($User in $UserEmails) { + $UserList.Add("$User") + } + + $XML = @" + + + + + + + + + + + + + + + + $($UserList -join '') + + + + + +"@ + $tenantName = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/root' -asApp $true -tenantid $TenantFilter).id.Split('.')[0] + $AdminUrl = "https://$($tenantName)-admin.sharepoint.com" + + try { + $Request = New-GraphPostRequest -scope "$AdminURL/.default" -tenantid $TenantFilter -Uri "$AdminURL/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' + if (!$Request.IsComplete) { throw } + Write-LogMessage -user $ExecutingUser -API $APIName -message "Requested personal site for $($Users -join ', ')" -Sev 'Info' -tenant $TenantFilter + return "Requested personal site for $($Users -join ', ')" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not request personal site for $($Users -join ', '). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not request personal site for $($Users -join ', '). Error: $($ErrorMessage.NormalizedError)" + } +} diff --git a/Modules/CIPPCore/Public/Revoke-CIPPSessions.ps1 b/Modules/CIPPCore/Public/Revoke-CIPPSessions.ps1 index a43996e3fe68..a20a0df59c8e 100644 --- a/Modules/CIPPCore/Public/Revoke-CIPPSessions.ps1 +++ b/Modules/CIPPCore/Public/Revoke-CIPPSessions.ps1 @@ -4,18 +4,18 @@ function Revoke-CIPPSessions { $ExecutingUser, $userid, $username, - $APIName = "Revoke Sessions", + $APIName = 'Revoke Sessions', $TenantFilter ) try { - $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($userid)/invalidateAllRefreshTokens" -tenantid $TenantFilter -type POST -body '{}' -verbose - Write-LogMessage -user $ExecutingUser -API $APIName -message "Revoked sessions for $($username)" -Sev "Info" -tenant $TenantFilter + $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($userid)/invalidateAllRefreshTokens" -tenantid $TenantFilter -type POST -body '{}' -verbose + Write-LogMessage -user $ExecutingUser -API $APIName -message "Revoked sessions for $($username)" -Sev 'Info' -tenant $TenantFilter return "Success. All sessions by $username have been revoked" - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to revoke sessions for $($username): $($_.Exception.Message)" -Sev "Error" -tenant $TenantFilter - return "Revoke Session Failed: $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to revoke sessions for $($username): $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Revoke Session Failed: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/SAMManifest.json b/Modules/CIPPCore/Public/SAMManifest.json index 6b1f6429af88..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,7 +166,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/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 b/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 index 89bd2ade79d2..380431faa98f 100644 --- a/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 +++ b/Modules/CIPPCore/Public/Send-CIPPAlert.ps1 @@ -39,8 +39,9 @@ function Send-CIPPAlert { Write-LogMessage -API 'Webhook Alerts' -message "Sent a webhook alert to email: $Title" -tenant $TenantFilter -sev info } catch { - Write-Information "Could not send webhook alert to email: $($_.Exception.message)" - Write-LogMessage -API 'Webhook Alerts' -message "Could not send webhook alerts to email. $($_.Exception.message)" -tenant $TenantFilter -sev info + $ErrorMessage = Get-CippException -Exception $_ + Write-Information "Could not send webhook alert to email: $($ErrorMessage.NormalizedError)" + Write-LogMessage -API 'Webhook Alerts' -message "Could not send webhook alerts to email. $($ErrorMessage.NormalizedError)" -tenant $TenantFilter -sev Error -LogData $ErrorMessage } } @@ -78,8 +79,9 @@ function Send-CIPPAlert { Write-LogMessage -API 'Webhook Alerts' -message "Sent Webhook alert $title to External webhook" -tenant $TenantFilter -sev info } catch { - Write-Information "Could not send alerts to webhook: $($_.Exception.message)" - Write-LogMessage -API 'Webhook Alerts' -message "Could not send alerts to webhook: $($_.Exception.message)" -tenant $TenantFilter -sev error -LogData (Get-CippException -Exception $_) + $ErrorMessage = Get-CippException -Exception $_ + Write-Information "Could not send alerts to webhook: $($ErrorMessage.NormalizedError)" + Write-LogMessage -API 'Webhook Alerts' -message "Could not send alerts to webhook: $($ErrorMessage.NormalizedError)" -tenant $TenantFilter -sev error -LogData $ErrorMessage } } Write-Information 'Trying to send to PSA' @@ -97,8 +99,9 @@ function Send-CIPPAlert { Write-LogMessage -API 'Webhook Alerts' -tenant $TenantFilter -message "Sent PSA alert $title" -sev info } catch { - Write-Information "Could not send alerts to ticketing system: $($_.Exception.message)" - Write-LogMessage -API 'Webhook Alerts' -tenant $TenantFilter -message "Could not send alerts to ticketing system: $($_.Exception.message)" -sev info + $ErrorMessage = Get-CippException -Exception $_ + Write-Information "Could not send alerts to ticketing system: $($ErrorMessage.NormalizedError)" + Write-LogMessage -API 'Webhook Alerts' -tenant $TenantFilter -message "Could not send alerts to ticketing system: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage } } } diff --git a/Modules/CIPPCore/Public/Set-CIPPAssignedApplication.ps1 b/Modules/CIPPCore/Public/Set-CIPPAssignedApplication.ps1 index 1938c35fabb8..bdd1ec2da682 100644 --- a/Modules/CIPPCore/Public/Set-CIPPAssignedApplication.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPAssignedApplication.ps1 @@ -114,7 +114,8 @@ function Set-CIPPAssignedApplication { } return "Assigned Application to $($GroupName)" } catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not assign application to $GroupName" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) - return "Could not assign application to $GroupName. Error: $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not assign application to $GroupName. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not assign application to $GroupName. Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 index 2999f0705b17..e3e7c0b11ce4 100644 --- a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 @@ -70,14 +70,15 @@ function Set-CIPPAssignedPolicy { assignments = @($assignmentsObject) } if ($PSCmdlet.ShouldProcess($GroupName, "Assigning policy $PolicyId")) { - Write-Host "https://graph.microsoft.com/beta/$($PlatformType)/$Type('$($PolicyId)')/assign" + Write-Host "https://graph.microsoft.com/beta/$($PlatformType)/$Type('$($PolicyId)')/assign" $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/$($PlatformType)/$Type('$($PolicyId)')/assign" -tenantid $tenantFilter -type POST -body ($assignmentsObject | ConvertTo-Json -Depth 10) Write-LogMessage -user $ExecutingUser -API $APIName -message "Assigned Policy to $($GroupName)" -Sev 'Info' -tenant $TenantFilter } return "Assigned policy to $($GroupName) Policy ID is $($PolicyId)." } catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to assign Policy to $GroupName. Policy ID is $($PolicyId)." -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) - return "Could not assign policy to $GroupName. Policy ID is $($PolicyId). Error: $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to assign Policy to $GroupName. Policy ID is $($PolicyId)." -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not assign policy to $GroupName. Policy ID is $($PolicyId). Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 index 380e7b2a4b30..77cdabfa6374 100644 --- a/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 @@ -21,9 +21,9 @@ function Set-CIPPAuthenticationPolicy { $CurrentInfo = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/$AuthenticationMethodId" -tenantid $Tenant $CurrentInfo.state = $State } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $Tenant -message "Could not get CurrentInfo for $AuthenticationMethodId. Error:$ErrorMessage" -sev Error - Return "Could not get CurrentInfo for $AuthenticationMethodId. Error:$($_.exception.message)" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $Tenant -message "Could not get CurrentInfo for $AuthenticationMethodId. Error:$($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + Return "Could not get CurrentInfo for $AuthenticationMethodId. Error:$($ErrorMessage.NormalizedError)" } switch ($AuthenticationMethodId) { @@ -118,8 +118,8 @@ function Set-CIPPAuthenticationPolicy { return "Set $AuthenticationMethodId state to $State $OptionalLogMessage" } catch { - Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $Tenant -message "Failed to $State $AuthenticationMethodId Support: $ErrorMessage" -sev Error -LogData (Get-CippException -Exception $_) - return "Failed to $State $AuthenticationMethodId Support: $ErrorMessage" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $Tenant -message "Failed to $State $AuthenticationMethodId Support: $ErrorMessage" -sev Error -LogData $ErrorMessage + return "Failed to $State $AuthenticationMethodId Support. Error: $($ErrorMessage.NormalizedError)" } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Set-CIPPCPVConsent.ps1 b/Modules/CIPPCore/Public/Set-CIPPCPVConsent.ps1 index 5d530312d6f0..382b78d90e48 100644 --- a/Modules/CIPPCore/Public/Set-CIPPCPVConsent.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPCPVConsent.ps1 @@ -9,6 +9,7 @@ function Set-CIPPCPVConsent { $Results = [System.Collections.Generic.List[string]]::new() $Tenant = Get-Tenants -IncludeAll | Where-Object -Property customerId -EQ $TenantFilter | Select-Object -First 1 $TenantName = $Tenant.displayName + $User = $request.headers.'x-ms-client-principal' if ($TenantFilter -eq $env:TenantID) { return @('Cannot modify CPV consent on partner tenant') @@ -24,7 +25,8 @@ function Set-CIPPCPVConsent { } $Results.add("Deleted Service Principal from $TenantName") } catch { - $Results.add("Error deleting SP - $($_.Exception.Message)") + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + $Results.add("Error deleting SP - $($ErrorMessage)") } } @@ -57,10 +59,10 @@ function Set-CIPPCPVConsent { Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force } $Results.add("Successfully added CPV Application to tenant $($TenantName)") | Out-Null - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Added our Service Principal to $($TenantName)" -Sev 'Info' -tenant $Tenant.defaultDomainName -tenantId $TenantFilter + Write-LogMessage -user $User -API $APINAME -message "Added our Service Principal to $($TenantName)" -Sev 'Info' -tenant $Tenant.defaultDomainName -tenantId $TenantFilter } catch { - $ErrorMessage = Get-NormalizedError -message $_.Exception.Message - if ($ErrorMessage -like '*Permission entry already exists*') { + $ErrorMessage = Get-CippException -Exception $_ + if ($ErrorMessage.NormalizedError -like '*Permission entry already exists*') { $Table = Get-CIPPTable -TableName cpvtenants $unixtime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds $GraphRequest = @{ @@ -73,8 +75,8 @@ function Set-CIPPCPVConsent { Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force return @("We've already added our Service Principal to $($TenantName)") } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Could not add our Service Principal to the client tenant $($TenantName): $($_.Exception.message)" -Sev 'Error' -tenant $Tenant.defaultDomainName -tenantId $TenantFilter -LogData (Get-CippException -Exception $_) - return @("Could not add our Service Principal to the client tenant $($TenantName): $ErrorMessage") + Write-LogMessage -user $User -API $APINAME -message "Could not add our Service Principal to the client tenant $($TenantName): $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $Tenant.defaultDomainName -tenantId $TenantFilter -LogData $ErrorMessage + return @("Could not add our Service Principal to the client tenant $($TenantName). Error: $($ErrorMessage.NormalizedError)") } return $Results } diff --git a/Modules/CIPPCore/Public/Set-CIPPCopyGroupMembers.ps1 b/Modules/CIPPCore/Public/Set-CIPPCopyGroupMembers.ps1 index 0d437374b69e..41d60a70b521 100644 --- a/Modules/CIPPCore/Public/Set-CIPPCopyGroupMembers.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPCopyGroupMembers.ps1 @@ -26,9 +26,9 @@ function Set-CIPPCopyGroupMembers { Write-LogMessage -user $ExecutingUser -API $APIName -message "Added $UserId to group $($_.displayName)" -Sev 'Info' -tenant $TenantFilter $Success.Add("Added group: $($MailGroup.displayName)") | Out-Null } catch { - $NormalizedError = Get-NormalizedError -message $($_.Exception.Message) - $Errors.Add("We've failed to add the group $($MailGroup.displayName): $NormalizedError") | Out-Null - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Group adding failed for group $($_.displayName): $($_.Exception.Message)" -Sev 'Error' -LogData (Get-CippException -Exception $_) + $ErrorMessage = Get-CippException -Exception $_ + $Errors.Add("We've failed to add the group $($MailGroup.displayName): $($ErrorMessage.NormalizedError)") | Out-Null + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Group adding failed for group $($_.displayName): $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage } } diff --git a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 index 07f2646a7243..1b56d3816aa9 100644 --- a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 @@ -18,6 +18,9 @@ function Set-CIPPDefaultAPDeploymentProfile { $Language = 'os-default', $APIName = 'Add Default Enrollment Status Page' ) + + $User = $request.headers.'x-ms-client-principal-name' + try { $ObjBody = [pscustomobject]@{ '@odata.type' = '#microsoft.graph.azureADWindowsAutopilotDeploymentProfile' @@ -47,7 +50,7 @@ function Set-CIPPDefaultAPDeploymentProfile { if ($_.id -ne $Profiles[0].id) { if ($PSCmdlet.ShouldProcess($_.displayName, 'Delete duplicate Autopilot profile')) { $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($_.id)" -tenantid $tenantfilter -type DELETE - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -tenant $($tenantfilter) -message "Deleted duplicate Autopilot profile $($displayname)" -Sev 'Info' + Write-LogMessage -user $User -API $APIName -tenant $($tenantfilter) -message "Deleted duplicate Autopilot profile $($displayname)" -Sev 'Info' } } } @@ -56,9 +59,11 @@ function Set-CIPPDefaultAPDeploymentProfile { if (!$Profiles) { if ($PSCmdlet.ShouldProcess($displayName, 'Add Autopilot profile')) { $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles' -body $body -tenantid $tenantfilter - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -tenant $($tenantfilter) -message "Added Autopilot profile $($displayname)" -Sev 'Info' + Write-LogMessage -user $User -API $APIName -tenant $($tenantfilter) -message "Added Autopilot profile $($displayname)" -Sev 'Info' } } else { + #patch the profile + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($Profiles.id)" -tenantid $tenantfilter -body $body -type PATCH $GraphRequest = $Profiles } @@ -66,12 +71,13 @@ function Set-CIPPDefaultAPDeploymentProfile { $AssignBody = '{"target":{"@odata.type":"#microsoft.graph.allDevicesAssignmentTarget"}}' if ($PSCmdlet.ShouldProcess($AssignTo, "Assign Autopilot profile $displayname")) { $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($GraphRequest.id)/assignments" -tenantid $tenantfilter -type POST -body $AssignBody - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -tenant $($tenantfilter) -message "Assigned autopilot profile $($Displayname) to $AssignTo" -Sev 'Info' + Write-LogMessage -user $User -API $APIName -tenant $($tenantfilter) -message "Assigned autopilot profile $($Displayname) to $AssignTo" -Sev 'Info' } } "Successfully added profile for $($tenantfilter)" } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APIName -tenant $($tenantfilter) -message "Failed adding Autopilot Profile $($Displayname). Error: $($_.Exception.Message)" -Sev 'Error' -LogData (Get-CippException -Exception $_) - throw "Failed to add profile for $($tenantfilter): $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APIName -tenant $($tenantfilter) -message "Failed adding Autopilot Profile $($Displayname). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + throw "Failed to add profile for $($tenantfilter): $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPDefaultAPEnrollment.ps1 b/Modules/CIPPCore/Public/Set-CIPPDefaultAPEnrollment.ps1 index 798dfdb71553..d0533c020201 100644 --- a/Modules/CIPPCore/Public/Set-CIPPDefaultAPEnrollment.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPDefaultAPEnrollment.ps1 @@ -13,6 +13,9 @@ function Set-CIPPDefaultAPEnrollment { $ExecutingUser, $APIName = 'Add Default Enrollment Status Page' ) + + $User = $request.headers.'x-ms-client-principal-name' + try { $ObjBody = [pscustomobject]@{ '@odata.type' = '#microsoft.graph.windows10EnrollmentCompletionPageConfiguration' @@ -37,10 +40,11 @@ function Set-CIPPDefaultAPEnrollment { if ($PSCmdlet.ShouldProcess($ExistingStatusPage.ID, 'Set Default Enrollment Status Page')) { $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/deviceEnrollmentConfigurations/$($ExistingStatusPage.ID)" -body $body -Type PATCH -tenantid $($TenantFilter) "Successfully changed default enrollment status page for $($($TenantFilter))" - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($TenantFilter) -message "Added Autopilot Enrollment Status Page $($Displayname)" -Sev 'Info' + Write-LogMessage -user $User -API $APINAME -tenant $($TenantFilter) -message "Added Autopilot Enrollment Status Page $($Displayname)" -Sev 'Info' } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($TenantFilter) -message "Failed adding Autopilot Enrollment Status Page $($Displayname). Error: $($_.Exception.Message)" -Sev 'Error' - throw "Failed to change default enrollment status page for $($($TenantFilter)): $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -tenant $($TenantFilter) -message "Failed adding Autopilot Enrollment Status Page $($Displayname). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + throw "Failed to change default enrollment status page for $($($TenantFilter)): $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 b/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 index 130ff65ff430..23bce649ca3e 100644 --- a/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 @@ -43,24 +43,31 @@ function Set-CIPPForwarding { [string]$ExecutingUser, [string]$APIName = 'Forwarding', [string]$Forward, - [bool]$KeepCopy, + $KeepCopy, [bool]$Disable ) + try { if (!$username) { $username = $userid } if ($PSCmdlet.ShouldProcess($username, 'Set forwarding')) { - $null = New-ExoRequest -tenantid $tenantFilter -cmdlet 'Set-mailbox' -cmdParams @{Identity = $userid; ForwardingSMTPAddress = $forwardingSMTPAddress; ForwardingAddress = $Forward ; DeliverToMailboxAndForward = [bool]$KeepCopy } -Anchor $username - } - if (!$Disable) { - $Message = "Forwarding all email for $username to $Forward" - } else { - $Message = "Disabled forwarding for $username" + if ($Disable -eq $true) { + Write-Output "Disabling forwarding for $username" + $null = New-ExoRequest -tenantid $tenantFilter -cmdlet 'Set-mailbox' -cmdParams @{Identity = $userid; ForwardingSMTPAddress = $null; ForwardingAddress = $null ; DeliverToMailboxAndForward = $false } -Anchor $username + $Message = "Disabled forwarding for $username" + } elseif ($Forward) { + $null = New-ExoRequest -tenantid $tenantFilter -cmdlet 'Set-mailbox' -cmdParams @{Identity = $userid; ForwardingSMTPAddress = $null; ForwardingAddress = $Forward ; DeliverToMailboxAndForward = $KeepCopy } -Anchor $username + $Message = "Forwarding all email for $username to Internal Address $Forward and keeping a copy set to $KeepCopy" + } elseif ($forwardingSMTPAddress) { + $null = New-ExoRequest -tenantid $tenantFilter -cmdlet 'Set-mailbox' -cmdParams @{Identity = $userid; ForwardingSMTPAddress = $forwardingSMTPAddress; ForwardingAddress = $null ; DeliverToMailboxAndForward = $KeepCopy } -Anchor $username + $Message = "Forwarding all email for $username to External Address $ForwardingSMTPAddress and keeping a copy set to $KeepCopy" + } } Write-LogMessage -user $ExecutingUser -API $APIName -message $Message -Sev 'Info' -tenant $TenantFilter return $Message } catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add forwarding for $($username)" -Sev 'Error' -tenant $TenantFilter - return "Could not add forwarding for $($username). Error: $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add forwarding for $($username). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not add forwarding for $($username). Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 b/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 index c648d1300446..bfba35fa1103 100644 --- a/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 @@ -28,7 +28,8 @@ function Set-CIPPGDAPInviteGroups { Start-Sleep -Milliseconds 100 } } catch { - Write-LogMessage -API $APINAME -message "GDAP Group mapping failed for $($Relationship.customer.displayName) - Group: $($role.GroupId) - Exception: $($_.Exception.Message)" -Sev Error -LogData (Get-CippException -Exception $_) + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APINAME -message "GDAP Group mapping failed for $($Relationship.customer.displayName) - Group: $($role.GroupId) - Exception: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage return $false } } @@ -57,7 +58,7 @@ function Set-CIPPGDAPInviteGroups { SkipLog = $true } #Write-Information ($InputObject | ConvertTo-Json) - $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject (ConvertTo-Json -InputObject $InputObject -Depth 5 -Compress) Write-Information "Started GDAP Invite orchestration with ID = '$InstanceId'" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPGraphSubscription.ps1 b/Modules/CIPPCore/Public/Set-CIPPGraphSubscription.ps1 index 1efd17294e93..c5cd14a1a7aa 100644 --- a/Modules/CIPPCore/Public/Set-CIPPGraphSubscription.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPGraphSubscription.ps1 @@ -5,16 +5,16 @@ function Set-CIPPGraphSubscription { $RenewSubscriptions, $Resource, $EventType, - $APIName = "Set Graph Webhook", + $APIName = 'Set Graph Webhook', $ExecutingUser ) if ($RenewSubscriptions) { - $RenewalDate = (Get-Date).AddDays(1).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + $RenewalDate = (Get-Date).AddDays(1).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') $body = @{ - "expirationDateTime" = "$RenewalDate" + 'expirationDateTime' = "$RenewalDate" } | ConvertTo-Json - $ExistingSub = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/subscriptions" -tenantid $TenantFilter) | ForEach-Object { + $null = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscriptions' -tenantid $TenantFilter) | ForEach-Object { try { $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/subscriptions/$($_.id)" -tenantid $TenantFilter -type PATCH -body $body -Verbose $WebhookTable = Get-CIPPTable -TableName webhookTable @@ -22,13 +22,13 @@ function Set-CIPPGraphSubscription { $WebhookRow = Get-CIPPAzDataTableEntity @WebhookTable | Where-Object { $_.WebhookNotificationUrl -eq $GraphRequest.notificationUrl } $WebhookRow.Expiration = $RenewalDate $null = Add-CIPPAzDataTableEntity @WebhookTable -Entity $WebhookRow -Force - return "Renewed $($GraphRequest.notificationUrl)" + return "Renewed $($GraphRequest.notificationUrl)" - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to renew Webhook Subscription: $($_.Exception.Message)" -Sev "Error" -tenant $TenantFilter - return "Failed to renew Webhook Subscription $($WebhookRow.RowKey): $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to renew Webhook Subscription: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Failed to renew Webhook Subscription $($WebhookRow.RowKey): $($ErrorMessage.NormalizedError)" } } } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Set-CIPPGroupAuthentication.ps1 b/Modules/CIPPCore/Public/Set-CIPPGroupAuthentication.ps1 index 13b224a70efa..e8d65c89fb5a 100644 --- a/Modules/CIPPCore/Public/Set-CIPPGroupAuthentication.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPGroupAuthentication.ps1 @@ -1,31 +1,29 @@ function Set-CIPPGroupAuthentication( [string]$ExecutingUser, - [string]$GroupType, - [string]$Id, - [string]$OnlyAllowInternalString, + [string]$GroupType, + [string]$Id, + [string]$OnlyAllowInternalString, [string]$TenantFilter, - [string]$APIName = "Group Sender Authentication" + [string]$APIName = 'Group Sender Authentication' ) { try { - $OnlyAllowInternal = if ($OnlyAllowInternalString -eq 'true') { "true" } else { "false" } - $messageSuffix = if ($OnlyAllowInternal -eq 'true') { "inside the organisation." } else { "inside and outside the organisation." } + $OnlyAllowInternal = if ($OnlyAllowInternalString -eq 'true') { 'true' } else { 'false' } + $messageSuffix = if ($OnlyAllowInternal -eq 'true') { 'inside the organisation.' } else { 'inside and outside the organisation.' } - if ($GroupType -eq "Distribution List" -or $GroupType -eq "Mail-Enabled Security") { - New-ExoRequest -tenantid $TenantFilter -cmdlet "Set-DistributionGroup" -cmdParams @{Identity = $Id; RequireSenderAuthenticationEnabled = $OnlyAllowInternal } - } - elseif ($GroupType -eq "Microsoft 365") { - New-ExoRequest -tenantid $TenantFilter -cmdlet "Set-UnifiedGroup" -cmdParams @{Identity = $Id; RequireSenderAuthenticationEnabled = $OnlyAllowInternal } - } - elseif ($GroupType -eq "Security") { - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "This setting cannot be set on a security group." -Sev "Error" + if ($GroupType -eq 'Distribution List' -or $GroupType -eq 'Mail-Enabled Security') { + New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-DistributionGroup' -cmdParams @{Identity = $Id; RequireSenderAuthenticationEnabled = $OnlyAllowInternal } + } elseif ($GroupType -eq 'Microsoft 365') { + New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-UnifiedGroup' -cmdParams @{Identity = $Id; RequireSenderAuthenticationEnabled = $OnlyAllowInternal } + } elseif ($GroupType -eq 'Security') { + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message 'This setting cannot be set on a security group.' -Sev 'Error' return "$GroupType's group cannot have this setting changed" } - - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "$Id set to allow messages from people $messageSuffix" -Sev "Info" + + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "$Id set to allow messages from people $messageSuffix" -Sev 'Info' return "Set $GroupType group $Id to allow messages from people $messageSuffix" - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Delivery Management failed: $($_.Exception.Message)" -Sev "Error" - return "Failed. $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Delivery Management failed: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + return "Failed. $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPGroupGAL.ps1 b/Modules/CIPPCore/Public/Set-CIPPGroupGAL.ps1 index aec510dfe806..0da6ddf798a6 100644 --- a/Modules/CIPPCore/Public/Set-CIPPGroupGAL.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPGroupGAL.ps1 @@ -1,25 +1,29 @@ function Set-CIPPGroupGAL( [string]$ExecutingUser, - [string]$GroupType, - [string]$Id, - [string]$HiddenString, + [string]$GroupType, + [string]$Id, + [string]$HiddenString, [string]$TenantFilter, - [string]$APIName = "Group GAL Status" + [string]$APIName = 'Group GAL Status' ) { - $Hidden = if ($HiddenString -eq 'true') { "true" } else { "false" } - $messageSuffix = if ($Hidden -eq 'true') { "hidden" } else { "unhidden" } + $Hidden = if ($HiddenString -eq 'true') { 'true' } else { 'false' } + $messageSuffix = if ($Hidden -eq 'true') { 'hidden' } else { 'unhidden' } - if ($GroupType -eq "Distribution List" -or $GroupType -eq "Mail-Enabled Security") { - New-ExoRequest -tenantid $TenantFilter -cmdlet "Set-DistributionGroup" -cmdParams @{Identity = $Id; HiddenFromAddressListsEnabled = $Hidden } - } - elseif ($GroupType -eq "Microsoft 365") { - New-ExoRequest -tenantid $TenantFilter -cmdlet "Set-UnifiedGroup" -cmdParams @{Identity = $Id; HiddenFromAddressListsEnabled = $Hidden } - } - elseif ($GroupType -eq "Security") { - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "This setting cannot be set on a security group." -Sev "Error" - return "$GroupType's group cannot have this setting changed" + try { + if ($GroupType -eq 'Distribution List' -or $GroupType -eq 'Mail-Enabled Security') { + New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-DistributionGroup' -cmdParams @{Identity = $Id; HiddenFromAddressListsEnabled = $Hidden } + } elseif ($GroupType -eq 'Microsoft 365') { + New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-UnifiedGroup' -cmdParams @{Identity = $Id; HiddenFromAddressListsEnabled = $Hidden } + } elseif ($GroupType -eq 'Security') { + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message 'This setting cannot be set on a security group.' -Sev 'Error' + return "$GroupType's group cannot have this setting changed" + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "$Id $messageSuffix from GAL failed: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + return "Failed. $($ErrorMessage.NormalizedError)" } - - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "$Id $messageSuffix from GAL" -Sev "Info" + + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "$Id $messageSuffix from GAL" -Sev 'Info' return "Successfully $messageSuffix $GroupType group $Id from GAL." } diff --git a/Modules/CIPPCore/Public/Set-CIPPHideFromGAL.ps1 b/Modules/CIPPCore/Public/Set-CIPPHideFromGAL.ps1 index 31daa7020fd4..13c70a474602 100644 --- a/Modules/CIPPCore/Public/Set-CIPPHideFromGAL.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPHideFromGAL.ps1 @@ -3,18 +3,18 @@ function Set-CIPPHideFromGAL { param ( $userid, $tenantFilter, - $APIName = "Hide From Address List", + $APIName = 'Hide From Address List', [bool]$HideFromGAL, $ExecutingUser ) - $Text = if ($HideFromGAL) { "hidden" } else { "unhidden" } + $Text = if ($HideFromGAL) { 'hidden' } else { 'unhidden' } try { - $Request = New-ExoRequest -tenantid $TenantFilter -cmdlet "Set-mailbox" -cmdParams @{Identity = $userid ; HiddenFromAddressListsEnabled = $HideFromGAL } - Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $($tenantfilter) -message "$($userid) $Text from GAL" -Sev "Info" + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-mailbox' -cmdParams @{Identity = $userid ; HiddenFromAddressListsEnabled = $HideFromGAL } + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $($tenantfilter) -message "$($userid) $Text from GAL" -Sev 'Info' return "Successfully $Text $($userid) from GAL." - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not hide $($userid) from address list" -Sev "Error" -tenant $TenantFilter - return "Could not hide $($userid) from address list. Error: $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not hide $($userid) from address list. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not hide $($userid) from address list. Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 new file mode 100644 index 000000000000..cb05c24df99f --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 @@ -0,0 +1,131 @@ +function Set-CIPPIntunePolicy { + param ( + [Parameter(Mandatory = $true)] + $TemplateType, + $Description, + $DisplayName, + $RawJSON, + $AssignTo, + $ExecutingUser, + $tenantFilter + ) + $ReturnValue = try { + switch ($TemplateType) { + 'AppProtection' { + $TemplateType = ($RawJSON | ConvertFrom-Json).'@odata.type' -replace '#microsoft.graph.', '' + $TemplateTypeURL = "$($TemplateType)s" + $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL" -tenantid $tenantFilter + if ($displayname -in $CheckExististing.displayName) { + $PostType = 'edited' + $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $PolicyName + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PATCH -body $RawJSON + } else { + $PostType = 'added' + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL" -tenantid $tenantFilter -type POST -body $RawJSON + } + } + 'deviceCompliancePolicies' { + $TemplateTypeURL = 'deviceCompliancePolicies' + $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter + $JSON = $RawJSON | ConvertFrom-Json | Select-Object * -ExcludeProperty id, createdDateTime, lastModifiedDateTime, version, 'scheduledActionsForRule@odata.context', '@odata.context' + $JSON.scheduledActionsForRule = @($JSON.scheduledActionsForRule | Select-Object * -ExcludeProperty 'scheduledActionConfigurations@odata.context') + $RawJSON = ConvertTo-Json -InputObject $JSON -Depth 20 -Compress + Write-Host $RawJSON + if ($displayname -in $CheckExististing.displayName) { + $PostType = 'edited' + $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $PolicyName + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PATCH -body $RawJSON + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Updated policy $($PolicyName) to template defaults" -Sev 'info' + } else { + $PostType = 'added' + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter -type POST -body $RawJSON + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($PolicyName) via template" -Sev 'info' + } + } + 'Admin' { + $TemplateTypeURL = 'groupPolicyConfigurations' + $CreateBody = '{"description":"' + $description + '","displayName":"' + $displayname + '","roleScopeTagIds":["0"]}' + $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter + if ($displayname -in $CheckExististing.displayName) { + $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $displayname + $ExistingData = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL('$($existingId.id)')/definitionValues" -tenantid $tenantFilter + $DeleteJson = $RawJSON | ConvertFrom-Json -Depth 10 + $DeleteJson.deletedIds = @($ExistingData.id) + $DeleteJson.added = @() + $DeleteJson = ConvertTo-Json -Depth 10 -InputObject $DeleteJson + $DeleteRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL('$($existingId.id)')/updateDefinitionValues" -tenantid $tenantFilter -type POST -body $DeleteJson + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL('$($existingId.id)')/updateDefinitionValues" -tenantid $tenantFilter -type POST -body $RawJSON + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Updated policy $($Displayname) to template defaults" -Sev 'info' + $PostType = 'edited' + } else { + $PostType = 'added' + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter -type POST -body $CreateBody + $UpdateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL('$($CreateRequest.id)')/updateDefinitionValues" -tenantid $tenantFilter -type POST -body $RawJSON + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($Displayname) to template defaults" -Sev 'info' + + } + } + 'Device' { + $TemplateTypeURL = 'deviceConfigurations' + + $PolicyName = ($RawJSON | ConvertFrom-Json).displayName + $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter + if ($PolicyName -in $CheckExististing.displayName) { + $PostType = 'edited' + $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $PolicyName + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PATCH -body $RawJSON + $CreateRequest = $CheckExististing | Where-Object -Property displayName -EQ $PolicyName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Updated policy $($PolicyName) to template defaults" -Sev 'info' + } else { + $PostType = 'added' + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter -type POST -body $RawJSON + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($PolicyName) via template" -Sev 'info' + + } + } + 'Catalog' { + $TemplateTypeURL = 'configurationPolicies' + $PolicyName = ($RawJSON | ConvertFrom-Json).Name + $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter + if ($PolicyName -in $CheckExististing.name) { + $ExistingID = $CheckExististing | Where-Object -Property Name -EQ $PolicyName + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PUT -body $RawJSON + $CreateRequest = $CheckExististing | Where-Object -Property Name -EQ $PolicyName + $PostType = 'edited' + } else { + $PostType = 'added' + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter -type POST -body $RawJSON + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($PolicyName) via template" -Sev 'info' + } + } + 'windowsDriverUpdateProfiles' { + $TemplateTypeURL = 'windowsDriverUpdateProfiles' + $PolicyName = ($RawJSON | ConvertFrom-Json).Name + $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter + if ($PolicyName -in $CheckExististing.name) { + $PostType = 'edited' + $ExistingID = $CheckExististing | Where-Object -Property Name -EQ $PolicyName + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PUT -body $RawJSON + } else { + $PostType = 'added' + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter -type POST -body $RawJSON + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($PolicyName) via template" -Sev 'info' + } + } + + } + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $($tenantFilter) -message "$($PostType) policy $($Displayname)" -Sev 'Info' + if ($AssignTo) { + Write-Host "Assigning policy to $($AssignTo) with ID $($CreateRequest.id) and type $TemplateTypeURL for tenant $tenantFilter" + Set-CIPPAssignedPolicy -GroupName $AssignTo -PolicyId $CreateRequest.id -Type $TemplateTypeURL -TenantFilter $tenantFilter + } + "Successfully $($PostType) policy for $($tenantFilter) with display name $($Displayname)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + "Failed to add or set policy for $($tenantFilter) with display name $($Displayname): $($ErrorMessage.NormalizedError)" + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $($tenantFilter) -message "Failed $($PostType) policy $($Displayname). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + continue + } + + return $ReturnValue +} diff --git a/Modules/CIPPCore/Public/Set-CIPPIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Set-CIPPIntuneTemplate.ps1 index 65f40a06449d..b216c5b56672 100644 --- a/Modules/CIPPCore/Public/Set-CIPPIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPIntuneTemplate.ps1 @@ -8,8 +8,8 @@ function Set-CIPPIntuneTemplate { $templateType ) - if (!$DisplayName) { throw "You must enter a displayname" } - if ($null -eq ($RawJSON | ConvertFrom-Json)) { throw "the JSON is invalid" } + if (!$DisplayName) { throw 'You must enter a displayname' } + if ($null -eq ($RawJSON | ConvertFrom-Json)) { throw 'the JSON is invalid' } $object = [PSCustomObject]@{ Displayname = $DisplayName @@ -24,9 +24,9 @@ function Set-CIPPIntuneTemplate { JSON = "$object" RowKey = "$GUID" GUID = "$GUID" - PartitionKey = "IntuneTemplate" + PartitionKey = 'IntuneTemplate' } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created intune policy template named $($Request.body.displayname) with GUID $GUID" -Sev "Debug" + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created intune policy template named $($Request.body.displayname) with GUID $GUID" -Sev 'Debug' - return "Successfully added template" + return 'Successfully added template' } diff --git a/Modules/CIPPCore/Public/Set-CIPPMailboxAccess.ps1 b/Modules/CIPPCore/Public/Set-CIPPMailboxAccess.ps1 index f19762545ac1..e9efdf9eaa10 100644 --- a/Modules/CIPPCore/Public/Set-CIPPMailboxAccess.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPMailboxAccess.ps1 @@ -5,25 +5,24 @@ function Set-CIPPMailboxAccess { $AccessUser, [bool]$Automap, $TenantFilter, - $APIName = "Manage Shared Mailbox Access", + $APIName = 'Manage Shared Mailbox Access', $ExecutingUser, [array]$AccessRights ) try { - $permissions = New-ExoRequest -tenantid $TenantFilter -cmdlet "Add-MailboxPermission" -cmdParams @{Identity = $userid; user = $AccessUser; automapping = $Automap; accessRights = $AccessRights; InheritanceType = "all" } -Anchor $userid - + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Add-MailboxPermission' -cmdParams @{Identity = $userid; user = $AccessUser; automapping = $Automap; accessRights = $AccessRights; InheritanceType = 'all' } -Anchor $userid + if ($Automap) { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Gave $AccessRights permissions to $($AccessUser) on $($userid) with automapping" -Sev "Info" -tenant $TenantFilter + Write-LogMessage -user $ExecutingUser -API $APIName -message "Gave $AccessRights permissions to $($AccessUser) on $($userid) with automapping" -Sev 'Info' -tenant $TenantFilter return "added $($AccessUser) to $($userid) Shared Mailbox with automapping, with the following permissions: $AccessRights" - } - else { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Gave $AccessRights permissions to $($AccessUser) on $($userid) without automapping" -Sev "Info" -tenant $TenantFilter + } else { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Gave $AccessRights permissions to $($AccessUser) on $($userid) without automapping" -Sev 'Info' -tenant $TenantFilter return "added $($AccessUser) to $($userid) Shared Mailbox without automapping, with the following permissions: $AccessRights" } - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add mailbox permissions for $($AccessUser) on $($userid)" -Sev "Error" -tenant $TenantFilter - return "Could not add shared mailbox permissions for $($userid). Error: $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add mailbox permissions for $($AccessUser) on $($userid). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not add shared mailbox permissions for $($userid). Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPMailboxArchive.ps1 b/Modules/CIPPCore/Public/Set-CIPPMailboxArchive.ps1 index 8ab1795e7fb5..0df033a87d15 100644 --- a/Modules/CIPPCore/Public/Set-CIPPMailboxArchive.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPMailboxArchive.ps1 @@ -4,19 +4,21 @@ function Set-CIPPMailboxArchive { $ExecutingUser, $userid, $username, - $APIName = "Mailbox Archive", + $APIName = 'Mailbox Archive', $TenantFilter, [bool]$ArchiveEnabled ) + $User = $request.headers.'x-ms-client-principal-name' + Try { if (!$username) { $username = $userid } - New-ExoRequest -tenantid $TenantFilter -cmdlet "Enable-Mailbox" -cmdParams @{Identity = $userid; Archive = $ArchiveEnabled } + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Enable-Mailbox' -cmdParams @{Identity = $userid; Archive = $ArchiveEnabled } "Successfully set archive for $username to $ArchiveEnabled" - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantfilter) -message "Successfully set archive for $username to $ArchiveEnabled" -Sev "Info" - } - catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantfilter) -message "Failed to set archive $($_.Exception.Message)" -Sev "Error" - "Failed. $($_.Exception.Message)" + Write-LogMessage -user $User -API $APINAME -tenant $($tenantfilter) -message "Successfully set archive for $username to $ArchiveEnabled" -Sev 'Info' + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -tenant $($tenantfilter) -message "Failed to set archive for $username. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + "Failed. $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPMailboxType.ps1 b/Modules/CIPPCore/Public/Set-CIPPMailboxType.ps1 index 03fc1236f269..fdde77d25a09 100644 --- a/Modules/CIPPCore/Public/Set-CIPPMailboxType.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPMailboxType.ps1 @@ -4,20 +4,20 @@ function Set-CIPPMailboxType { $ExecutingUser, $userid, $username, - $APIName = "Mailbox Conversion", + $APIName = 'Mailbox Conversion', $TenantFilter, [Parameter()] - [ValidateSet('shared', 'Regular', 'Room', 'Equipment')]$MailboxType + [ValidateSet('Shared', 'Regular', 'Room', 'Equipment')]$MailboxType ) try { - $Mailbox = New-ExoRequest -tenantid $TenantFilter -cmdlet "Set-mailbox" -cmdParams @{Identity = $userid; type = $MailboxType } -Anchor $username - Write-LogMessage -user $ExecutingUser -API $APIName -message "Converted $($username) to a $MailboxType mailbox" -Sev "Info" -tenant $TenantFilter + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-mailbox' -cmdParams @{Identity = $userid; type = $MailboxType } -Anchor $username + Write-LogMessage -user $ExecutingUser -API $APIName -message "Converted $($username) to a $MailboxType mailbox" -Sev 'Info' -tenant $TenantFilter if (!$username) { $username = $userid } return "Converted $($username) to a $MailboxType mailbox" - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not convert $username to $MailboxType mailbox" -Sev "Error" -tenant $TenantFilter - return "Could not convert $($username) to a $MailboxType mailbox. Error: $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not convert $username to $MailboxType mailbox. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not convert $($username) to a $MailboxType mailbox. Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPMessageCopy.ps1 b/Modules/CIPPCore/Public/Set-CIPPMessageCopy.ps1 index 745c138bebae..dae07baf9bf4 100644 --- a/Modules/CIPPCore/Public/Set-CIPPMessageCopy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPMessageCopy.ps1 @@ -4,16 +4,16 @@ function Set-CIPPMessageCopy { $userid, $MessageCopyForSentAsEnabled, $TenantFilter, - $APIName = "Manage OneDrive Access", + $APIName = 'Manage OneDrive Access', $ExecutingUser ) Try { - New-ExoRequest -tenantid $TenantFilter -cmdlet "Set-mailbox" -cmdParams @{Identity = $userid; MessageCopyForSentAsEnabled = $MessageCopyForSentAsEnabled } - Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $($tenantfilter) -message "Successfully set MessageCopyForSentAsEnabled as $MessageCopyForSentAsEnabled on $($userid)." -Sev "Info" + New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-Mailbox' -cmdParams @{Identity = $userid; MessageCopyForSentAsEnabled = $MessageCopyForSentAsEnabled } + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $($tenantfilter) -message "Successfully set MessageCopyForSentAsEnabled as $MessageCopyForSentAsEnabled on $($userid)." -Sev 'Info' return "Successfully set MessageCopyForSentAsEnabled as $MessageCopyForSentAsEnabled on $($userid)." + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $($tenantfilter) -message "set MessageCopyForSentAsEnabled to $MessageCopyForSentAsEnabled failed: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + return "set MessageCopyForSentAsEnabled to $MessageCopyForSentAsEnabled failed - $($ErrorMessage.NormalizedError)" } - catch { - Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $($tenantfilter) -message "set MessageCopyForSentAsEnabled to $MessageCopyForSentAsEnabled failed: $($_.Exception.Message)" -Sev "Error" - return "set MessageCopyForSentAsEnabled to $MessageCopyForSentAsEnabled failed - $($_.Exception.Message)" - } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Set-CIPPMobileDevice.ps1 b/Modules/CIPPCore/Public/Set-CIPPMobileDevice.ps1 index f3a3392eeb75..830a02cd2564 100644 --- a/Modules/CIPPCore/Public/Set-CIPPMobileDevice.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPMobileDevice.ps1 @@ -6,41 +6,39 @@ function Set-CIPPMobileDevice( [string]$TenantFilter, [string]$Delete, [string]$Guid, - [string]$APIName = "Mobile Device" + [string]$APIName = 'Mobile Device' ) { - + try { - if ($Quarantine -eq "false") { - New-ExoRequest -tenantid $TenantFilter -cmdlet "Set-CASMailbox" -cmdParams @{Identity = $UserId; ActiveSyncAllowedDeviceIDs = @{'@odata.type' = '#Exchange.GenericHashTable'; add = $DeviceId } } - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Allow Active Sync Device for $UserId" -Sev "Info" + if ($Quarantine -eq 'false') { + New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-CASMailbox' -cmdParams @{Identity = $UserId; ActiveSyncAllowedDeviceIDs = @{'@odata.type' = '#Exchange.GenericHashTable'; add = $DeviceId } } + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Allow Active Sync Device for $UserId" -Sev 'Info' return "Allowed Active Sync Device for $UserId" - } - elseif ($Quarantine -eq "true") { - New-ExoRequest -tenantid $TenantFilter -cmdlet "Set-CASMailbox" -cmdParams @{Identity = $UserId; ActiveSyncBlockedDeviceIDs = @{'@odata.type' = '#Exchange.GenericHashTable'; add = $DeviceId } } - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Blocked Active Sync Device for $UserId" -Sev "Info" + } elseif ($Quarantine -eq 'true') { + New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-CASMailbox' -cmdParams @{Identity = $UserId; ActiveSyncBlockedDeviceIDs = @{'@odata.type' = '#Exchange.GenericHashTable'; add = $DeviceId } } + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Blocked Active Sync Device for $UserId" -Sev 'Info' return "Blocked Active Sync Device for $UserId" } - } - catch { + } catch { + $ErrorMessage = Get-CippException -Exception $_ if ($Quarantine -eq 'false') { - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Failed to Allow Active Sync Device for $($UserId): $($_.Exception.Message)" -Sev "Error" - return "Failed to Allow Active Sync Device for $($UserId): $($_.Exception.Message)" - } - elseif ($Quarantine -eq 'true') { - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Failed to Block Active Sync Device for $($UserId): $($_.Exception.Message)" -Sev "Error" - return "Failed to Block Active Sync Device for $($UserId): $($_.Exception.Message)" + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Failed to Allow Active Sync Device for $($UserId): $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + return "Failed to Allow Active Sync Device for $($UserId): $($ErrorMessage.NormalizedError)" + } elseif ($Quarantine -eq 'true') { + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Failed to Block Active Sync Device for $($UserId): $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + return "Failed to Block Active Sync Device for $($UserId): $($ErrorMessage.NormalizedError)" } } try { if ($Delete -eq 'true') { - New-ExoRequest -tenant $TenantFilter -cmdlet "Remove-MobileDevice" -cmdParams @{Identity = $Guid; Confirm = $false } -UseSystemMailbox $true - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Deleted Active Sync Device for $UserId" -Sev "Info" + New-ExoRequest -tenant $TenantFilter -cmdlet 'Remove-MobileDevice' -cmdParams @{Identity = $Guid; Confirm = $false } -UseSystemMailbox $true + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Deleted Active Sync Device for $UserId" -Sev 'Info' return "Deleted Active Sync Device for $UserId" } - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Failed to delete Mobile Device $($Guid): $($_.Exception.Message)" -Sev "Error" - return "Failed to delete Mobile Device $($Guid): $($_.Exception.Message)" + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message "Failed to delete Mobile Device $($Guid): $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + return "Failed to delete Mobile Device $($Guid): $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPOutOfoffice.ps1 b/Modules/CIPPCore/Public/Set-CIPPOutOfoffice.ps1 index ee1266ca949d..116c503486c1 100644 --- a/Modules/CIPPCore/Public/Set-CIPPOutOfoffice.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPOutOfoffice.ps1 @@ -20,16 +20,17 @@ function Set-CIPPOutOfOffice { $EndTime = (Get-Date $StartTime).AddDays(7) } if ($State -ne 'Scheduled') { - $OutOfOffice = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-MailboxAutoReplyConfiguration' -cmdParams @{Identity = $userid; AutoReplyState = $State; InternalMessage = $InternalMessage; ExternalMessage = $ExternalMessage } -Anchor $userid + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-MailboxAutoReplyConfiguration' -cmdParams @{Identity = $userid; AutoReplyState = $State; InternalMessage = $InternalMessage; ExternalMessage = $ExternalMessage } -Anchor $userid Write-LogMessage -user $ExecutingUser -API $APIName -message "Set Out-of-office for $($userid) to $state" -Sev 'Info' -tenant $TenantFilter return "Set Out-of-office for $($userid) to $state." } else { - $OutOfOffice = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-MailboxAutoReplyConfiguration' -cmdParams @{Identity = $userid; AutoReplyState = $State; InternalMessage = $InternalMessage; ExternalMessage = $ExternalMessage; StartTime = $StartTime; EndTime = $EndTime } -Anchor $userid + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-MailboxAutoReplyConfiguration' -cmdParams @{Identity = $userid; AutoReplyState = $State; InternalMessage = $InternalMessage; ExternalMessage = $ExternalMessage; StartTime = $StartTime; EndTime = $EndTime } -Anchor $userid Write-LogMessage -user $ExecutingUser -API $APIName -message "Scheduled Out-of-office for $($userid) between $StartTime and $EndTime" -Sev 'Info' -tenant $TenantFilter return "Scheduled Out-of-office for $($userid) between $($StartTime.toString()) and $($EndTime.toString())" } } catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) - return "Could not add out of office message for $($userid). Error: $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not add out of office message for $($userid). Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 index f7f88a53fe11..f1fd6b5c2121 100644 --- a/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPPerUserMFA.ps1 @@ -63,7 +63,8 @@ function Set-CIPPPerUserMFA { Set-CIPPUserSchemaProperties -TenantFilter $TenantFilter -Users $Users Write-LogMessage -user $executingUser -API 'Set-CIPPPerUserMFA' -message "Successfully set Per user MFA State to $State for $id" -Sev 'Info' -tenant $TenantFilter } catch { - "Failed to set MFA State for $id : $_" - Write-LogMessage -user $executingUser -API 'Set-CIPPPerUserMFA' -message "Failed to set MFA State to $State for $id : $_" -Sev 'Error' -tenant $TenantFilter + $ErrorMessage = Get-CippException -Exception $_ + "Failed to set MFA State for $id. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -user $executingUser -API 'Set-CIPPPerUserMFA' -message "Failed to set MFA State to $State for $id. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Set-CIPPProfilePhoto.ps1 b/Modules/CIPPCore/Public/Set-CIPPProfilePhoto.ps1 index 5ea431302eec..fb829701390c 100644 --- a/Modules/CIPPCore/Public/Set-CIPPProfilePhoto.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPProfilePhoto.ps1 @@ -15,7 +15,8 @@ function Set-CIPPProfilePhoto { "Successfully set profile photo for $id" Write-LogMessage -user $executingUser -API 'Set-CIPPUserProfilePhoto' -message "Successfully set profile photo for $id" -Sev 'Info' -tenant $TenantFilter } catch { - "Failed to set profile photo for $id : $_" - Write-LogMessage -user $executingUser -API 'Set-CIPPUserProfilePhoto' -message "Failed to set profile photo for $id : $_" -Sev 'Error' -tenant $TenantFilter + $ErrorMessage = Get-CippException -Exception $_ + "Failed to set profile photo for $id. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -user $executingUser -API 'Set-CIPPUserProfilePhoto' -message "Failed to set profile photo for $id. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 b/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 index aff8463210be..e4d69e2c05d7 100644 --- a/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 @@ -17,7 +17,8 @@ function Set-CIPPResetPassword { } } | ConvertTo-Json -Compress - $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/users/$($userid)" -tenantid $TenantFilter -type PATCH -body $passwordProfile -verbose + $UserDetails = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserId)?`$select=onPremisesSyncEnabled" -noPagination $true -tenantid $TenantFilter -verbose + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/users/$($userid)" -tenantid $TenantFilter -type PATCH -body $passwordProfile -verbose #PWPush $PasswordLink = New-PwPushLink -Payload $password @@ -25,9 +26,15 @@ function Set-CIPPResetPassword { $password = $PasswordLink } Write-LogMessage -user $ExecutingUser -API $APIName -message "Reset the password for $($userid). User must change password is set to $forceChangePasswordNextSignIn" -Sev 'Info' -tenant $TenantFilter - return "Reset the password for $($userid). User must change password is set to $forceChangePasswordNextSignIn. The new password is $password" + + if($UserDetails.onPremisesSyncEnabled -eq $true){ + return "Reset the password for $($userid). User must change password is set to $forceChangePasswordNextSignIn. The new password is $password. WARNING: This user is AD synced. Please confirm passthrough or writeback is enabled." + }else{ + return "Reset the password for $($userid). User must change password is set to $forceChangePasswordNextSignIn. The new password is $password" + } } catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not reset password for $($userid)" -Sev 'Error' -tenant $TenantFilter - return "Could not reset password for $($userid). Error: $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not reset password for $($userid). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not reset password for $($userid). Error: $($ErrorMessage.NormalizedError)" } } 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/Set-CIPPSharePointPerms.ps1 b/Modules/CIPPCore/Public/Set-CIPPSharePointPerms.ps1 index d7c6419c32df..2a0a97be79a2 100644 --- a/Modules/CIPPCore/Public/Set-CIPPSharePointPerms.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPSharePointPerms.ps1 @@ -1,27 +1,27 @@ function Set-CIPPSharePointPerms { - [CmdletBinding()] - param ( - $userid, - $OnedriveAccessUser, - $TenantFilter, - $APIName = 'Manage SharePoint Owner', - $RemovePermission, - $ExecutingUser, - $URL - ) - if ($RemovePermission -eq $true) { - $SiteAdmin = 'false' - } else { - $SiteAdmin = 'true' - } - - try { - if (!$URL) { - $URL = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserId)/Drives" -asapp $true -tenantid $TenantFilter).WebUrl + [CmdletBinding()] + param ( + $userid, + $OnedriveAccessUser, + $TenantFilter, + $APIName = 'Manage SharePoint Owner', + $RemovePermission, + $ExecutingUser, + $URL + ) + if ($RemovePermission -eq $true) { + $SiteAdmin = 'false' + } else { + $SiteAdmin = 'true' } - $tenantName = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/root' -asApp $true -tenantid $TenantFilter).id.Split('.')[0] - $AdminUrl = "https://$($tenantName)-admin.sharepoint.com" - $XML = @" + + try { + if (!$URL) { + $URL = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserId)/Drives" -asapp $true -tenantid $TenantFilter).WebUrl + } + $tenantName = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/root' -asApp $true -tenantid $TenantFilter).id.Split('.')[0] + $AdminUrl = "https://$($tenantName)-admin.sharepoint.com" + $XML = @" @@ -38,19 +38,20 @@ function Set-CIPPSharePointPerms { "@ - $request = New-GraphPostRequest -scope "$AdminURL/.default" -tenantid $TenantFilter -Uri "$AdminURL/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' - Write-Host $($request) - if (!$request.ErrorInfo.ErrorMessage) { - $Message = "$($OnedriveAccessUser) has been $($RemovePermission ? 'removed from' : 'given') access to $URL" - Write-LogMessage -user $ExecutingUser -API $APIName -message $Message -Sev 'Info' -tenant $TenantFilter - return $Message - } else { - $message = "Failed to change access: $($request.ErrorInfo.ErrorMessage)" - Write-LogMessage -user $ExecutingUser -API $APIName -message $message -Sev 'Info' -tenant $TenantFilter - return $message + $request = New-GraphPostRequest -scope "$AdminURL/.default" -tenantid $TenantFilter -Uri "$AdminURL/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' + Write-Host $($request) + if (!$request.ErrorInfo.ErrorMessage) { + $Message = "$($OnedriveAccessUser) has been $($RemovePermission ? 'removed from' : 'given') access to $URL" + Write-LogMessage -user $ExecutingUser -API $APIName -message $Message -Sev 'Info' -tenant $TenantFilter + return $Message + } else { + $message = "Failed to change access: $($request.ErrorInfo.ErrorMessage)" + Write-LogMessage -user $ExecutingUser -API $APIName -message $message -Sev 'Info' -tenant $TenantFilter + return $message + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add new owner to $($OnedriveAccessUser) on $URL. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not add owner for $($URL). Error: $($ErrorMessage.NormalizedError)" } - } catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add new owner to $($OnedriveAccessUser) on $URL" -Sev 'Error' -tenant $TenantFilter - return "Could not add owner for $($URL). Error: $($_.Exception.Message)" - } } diff --git a/Modules/CIPPCore/Public/Set-CIPPSignInState.ps1 b/Modules/CIPPCore/Public/Set-CIPPSignInState.ps1 index 4c99b82c84df..5daf3173b974 100644 --- a/Modules/CIPPCore/Public/Set-CIPPSignInState.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPSignInState.ps1 @@ -13,12 +13,19 @@ function Set-CIPPSignInState { accountEnabled = [bool]$AccountEnabled } $body = ConvertTo-Json -InputObject $body -Compress -Depth 5 + $UserDetails = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserId)?`$select=onPremisesSyncEnabled" -noPagination $true -tenantid $TenantFilter -verbose $null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserId)" -tenantid $TenantFilter -type PATCH -body $body -verbose Write-LogMessage -user $ExecutingUser -API $APIName -message "Set account enabled state to $AccountEnabled for $UserId" -Sev 'Info' -tenant $TenantFilter - return "Set account enabled state to $AccountEnabled for $UserId" + + if($UserDetails.onPremisesSyncEnabled -eq $true){ + return "WARNING: User is AD Sync enabled. Please enable/disable in AD." + }else{ + return "Set account enabled state to $AccountEnabled for $UserId" + } + } catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not disable sign in for $UserId. Error: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter - return "Could not disable $UserId. Error: $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not disable sign in for $UserId. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not disable $UserId. Error: $($ErrorMessage.NormalizedError)" } } - diff --git a/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 b/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 index e3ad3c8dd83e..81a985bf6d36 100644 --- a/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 @@ -16,12 +16,13 @@ function Set-CIPPSignature { $SignatureProfile = @' [{"name":"Roaming_New_Signature","itemClass":"","id":"","scope":"AdeleV@M365x42953883.OnMicrosoft.com","parentSetting":"","secondaryKey":"","type":"String","timestamp":638296273181532792,"metadata":"","value":"Kelvin","isFirstSync":"true","source":"UserOverride"}] '@ - $GraphRequest = New-GraphPostRequest -uri 'https://substrate.office.com/ows/beta/outlookcloudsettings/settings/global' -tenantid $TenantFilter -type PATCH -contentType 'application/json' -verbose -scope 'https://outlook.office.com/.default' + $null = New-GraphPostRequest -uri 'https://substrate.office.com/ows/beta/outlookcloudsettings/settings/global' -tenantid $TenantFilter -type PATCH -contentType 'application/json' -verbose -scope 'https://outlook.office.com/.default' Write-LogMessage -user $ExecutingUser -API $APIName -message "Set Out-of-office for $($userid) to $state" -Sev 'Info' -tenant $TenantFilter return "Set Out-of-office for $($userid) to $state." } catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev 'Error' -tenant $TenantFilter - return "Could not add out of office message for $($userid). Error: $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not add out of office message for $($userid). Error: $($ErrorMessage.NormalizedError)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 index e25bf6443acd..51da2f11d762 100644 --- a/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPUserJITAdmin.ps1 @@ -50,6 +50,8 @@ function Set-CIPPUserJITAdmin { switch ($Action) { 'Create' { $Password = New-passwordString + $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } + $Body = @{ givenName = $User.FirstName surname = $User.LastName @@ -62,6 +64,10 @@ function Set-CIPPUserJITAdmin { forceChangePasswordNextSignInWithMfa = $false password = $Password } + $Schema.id = @{ + jitAdminEnabled = $false + jitAdminExpiration = $Expiration.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + } } $Json = ConvertTo-Json -Depth 5 -InputObject $Body try { @@ -77,8 +83,9 @@ function Set-CIPPUserJITAdmin { password = $Password } } catch { - Write-Information "Error creating user: $($_.Exception.Message)" - throw $_.Exception.Message + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-Information "Error creating user: $ErrorMessage" + throw $ErrorMessage } } 'AddRoles' { @@ -119,7 +126,8 @@ function Set-CIPPUserJITAdmin { $null = New-GraphPOSTRequest -type DELETE -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)" -tenantid $TenantFilter return "Deleted user $($UserObj.displayName) ($($UserObj.userPrincipalName)) with id $($UserObj.id)" } catch { - return "Error deleting user $($UserObj.displayName) ($($UserObj.userPrincipalName)): $($_.Exception.Message)" + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + return "Error deleting user $($UserObj.displayName) ($($UserObj.userPrincipalName)): $ErrorMessage" } } 'DisableUser' { @@ -135,9 +143,11 @@ function Set-CIPPUserJITAdmin { Set-CIPPUserJITAdminProperties -TenantFilter $TenantFilter -UserId $User.UserPrincipalName -Clear | Out-Null return "Disabled user $($UserObj.displayName) ($($UserObj.userPrincipalName))" } catch { - return "Error disabling user $($UserObj.displayName) ($($UserObj.userPrincipalName)): $($_.Exception.Message)" + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + return "Error disabling user $($UserObj.displayName) ($($UserObj.userPrincipalName)): $ErrorMessage" + } } } } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 index c6203128f2f9..b4b2bd5fe88f 100644 --- a/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPUserJITAdminProperties.ps1 @@ -7,25 +7,28 @@ function Set-CIPPUserJITAdminProperties { $Expiration, [switch]$Clear ) - - $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } - if ($Clear.IsPresent) { - $Body = [PSCustomObject]@{ - "$($Schema.id)" = @{ - jitAdminEnabled = $null - jitAdminExpiration = $null + try { + $Schema = Get-CIPPSchemaExtensions | Where-Object { $_.id -match '_cippUser' } + if ($Clear.IsPresent) { + $Body = [PSCustomObject]@{ + "$($Schema.id)" = @{ + jitAdminEnabled = $null + jitAdminExpiration = $null + } } - } - } else { - $Body = [PSCustomObject]@{ - "$($Schema.id)" = @{ - jitAdminEnabled = $Enabled.IsPresent - jitAdminExpiration = $Expiration.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + } else { + $Body = [PSCustomObject]@{ + "$($Schema.id)" = @{ + jitAdminEnabled = $Enabled.IsPresent + jitAdminExpiration = $Expiration.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + } } } + + $Json = ConvertTo-Json -Depth 5 -InputObject $Body + Write-Information $Json + New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/beta/users/$UserId" -Body $Json -tenantid $TenantFilter | Out-Null + } catch { + Write-Information "Error setting JIT Admin properties: $($_.Exception.Message) - $($_.InvocationInfo.PositionMessage)" } - - $Json = ConvertTo-Json -Depth 5 -InputObject $Body - Write-Information $Json - New-GraphPOSTRequest -type PATCH -Uri "https://graph.microsoft.com/beta/users/$UserId" -Body $Json -tenantid $TenantFilter | Out-Null -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Set-CIPPUserSchemaProperties.ps1 b/Modules/CIPPCore/Public/Set-CIPPUserSchemaProperties.ps1 index b006a27069ef..975bd401e179 100644 --- a/Modules/CIPPCore/Public/Set-CIPPUserSchemaProperties.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPUserSchemaProperties.ps1 @@ -43,4 +43,4 @@ function Set-CIPPUserSchemaProperties { if ($PSCmdlet.ShouldProcess("User: $($Users.userId -join ', ')", 'Set Schema Properties')) { $Requests = New-GraphBulkRequest -tenantid $tenantfilter -Requests @($Requests) } -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAPConfig.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAPConfig.ps1 index 4f168c0e6d2b..59f5203229fa 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAPConfig.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAPConfig.ps1 @@ -20,7 +20,7 @@ function Invoke-CIPPStandardAPConfig { DeploymentMode = $DeploymentMode assignto = $settings.Assignto devicenameTemplate = $Settings.DeviceNameTemplate - allowWhiteGlove = $Settings.allowWhiteGlove + allowWhiteGlove = $Settings.allowWhiteglove CollectHash = $Settings.CollectHash hideChangeAccount = $Settings.HideChangeAccount hidePrivacy = $Settings.HidePrivacy diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardActivityBasedTimeout.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardActivityBasedTimeout.ps1 index ebf92d21e199..98b72c1a1e9e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardActivityBasedTimeout.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardActivityBasedTimeout.ps1 @@ -1,8 +1,35 @@ function Invoke-CIPPStandardActivityBasedTimeout { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) ActivityBasedTimeout + .SYNOPSIS + (Label) Enable Activity based Timeout + .DESCRIPTION + (Helptext) Enables and sets Idle session timeout for Microsoft 365 to 1 hour. This policy affects most M365 web apps + (DocsDescription) Enables and sets Idle session timeout for Microsoft 365 to 1 hour. This policy affects most M365 web apps + .NOTES + CAT + Global Standards + TAG + "mediumimpact" + "CIS" + "spo_idle_session_timeout" + ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.ActivityBasedTimeout.timeout","values":[{"label":"1 Hour","value":"01:00:00"},{"label":"3 Hours","value":"03:00:00"},{"label":"6 Hours","value":"06:00:00"},{"label":"12 Hours","value":"12:00:00"},{"label":"24 Hours","value":"1.00:00:00"}]} + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Portal or Graph API + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) # Input validation @@ -63,4 +90,3 @@ function Invoke-CIPPStandardActivityBasedTimeout { } } - diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 index d4b8bd35166c..c4c430d99f7c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 @@ -1,8 +1,33 @@ function Invoke-CIPPStandardAddDKIM { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) AddDKIM + .SYNOPSIS + (Label) Enables DKIM for all domains that currently support it + .DESCRIPTION + (Helptext) Enables DKIM for all domains that currently support it + (DocsDescription) Enables DKIM for all domains that currently support it + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + "CIS" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + New-DkimSigningConfig and Set-DkimSigningConfig + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $AllDomains = (New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/domains?$top=999' -tenantid $Tenant | Where-Object { $_.supportedServices -contains 'Email' -or $_.id -like '*mail.onmicrosoft.com' }).id diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAnonReportDisable.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAnonReportDisable.ps1 index 411342e5ab3e..9851c82daf07 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAnonReportDisable.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAnonReportDisable.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardAnonReportDisable { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) AnonReportDisable + .SYNOPSIS + (Label) Enable Usernames instead of pseudo anonymised names in reports + .DESCRIPTION + (Helptext) Shows usernames instead of pseudo anonymised names in reports. This standard is required for reporting to work correctly. + (DocsDescription) Microsoft announced some APIs and reports no longer return names, to comply with compliance and legal requirements in specific countries. This proves an issue for a lot of MSPs because those reports are often helpful for engineers. This standard applies a setting that shows usernames in those API calls / reports. + .NOTES + CAT + Global Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgBetaAdminReportSetting -BodyParameter @{displayConcealedNames = $true} + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/reportSettings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 index dc7695e459c1..f9dea367763e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 @@ -1,15 +1,58 @@ function Invoke-CIPPStandardAntiPhishPolicy { <# .FUNCTIONALITY - Internal - #> - + Internal + .COMPONENT + (APIName) AntiPhishPolicy + .SYNOPSIS + (Label) Default Anti-Phishing Policy + .DESCRIPTION + (Helptext) This creates a Anti-Phishing policy that automatically enables Mailbox Intelligence and spoofing, optional switches for Mailtips. + (DocsDescription) This creates a Anti-Phishing policy that automatically enables Mailbox Intelligence and spoofing, optional switches for Mailtips. + .NOTES + CAT + Defender Standards + TAG + "lowimpact" + "CIS" + "mdo_safeattachments" + "mdo_highconfidencespamaction" + "mdo_highconfidencephishaction" + "mdo_phisspamacation" + "mdo_spam_notifications_only_for_admins" + "mdo_antiphishingpolicies" + ADDEDCOMPONENT + {"type":"number","label":"Phishing email threshold. (Default 1)","name":"standards.AntiPhishPolicy.PhishThresholdLevel","default":1} + {"type":"boolean","label":"Show first contact safety tip","name":"standards.AntiPhishPolicy.EnableFirstContactSafetyTips","default":true} + {"type":"boolean","label":"Show user impersonation safety tip","name":"standards.AntiPhishPolicy.EnableSimilarUsersSafetyTips","default":true} + {"type":"boolean","label":"Show domain impersonation safety tip","name":"standards.AntiPhishPolicy.EnableSimilarDomainsSafetyTips","default":true} + {"type":"boolean","label":"Show user impersonation unusual characters safety tip","name":"standards.AntiPhishPolicy.EnableUnusualCharactersSafetyTips","default":true} + {"type":"Select","label":"If the message is detected as spoof by spoof intelligence","name":"standards.AntiPhishPolicy.AuthenticationFailAction","values":[{"label":"Quarantine the message","value":"Quarantine"},{"label":"Move to Junk Folder","value":"MoveToJmf"}]} + {"type":"Select","label":"Quarantine policy for Spoof","name":"standards.AntiPhishPolicy.SpoofQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + {"type":"Select","label":"If a message is detected as user impersonation","name":"standards.AntiPhishPolicy.TargetedUserProtectionAction","values":[{"label":"Move to Junk Folder","value":"MoveToJmf"},{"label":"Delete the message before its delivered","value":"Delete"},{"label":"Quarantine the message","value":"Quarantine"}]} + {"type":"Select","label":"Quarantine policy for user impersonation","name":"standards.AntiPhishPolicy.TargetedUserQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + {"type":"Select","label":"If a message is detected as domain impersonation","name":"standards.AntiPhishPolicy.TargetedDomainProtectionAction","values":[{"label":"Move to Junk Folder","value":"MoveToJmf"},{"label":"Delete the message before its delivered","value":"Delete"},{"label":"Quarantine the message","value":"Quarantine"}]} + {"type":"Select","label":"Quarantine policy for domain impersonation","name":"standards.AntiPhishPolicy.TargetedDomainQuarantineTag","values":[{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"},{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"}]} + {"type":"Select","label":"If Mailbox Intelligence detects an impersonated user","name":"standards.AntiPhishPolicy.MailboxIntelligenceProtectionAction","values":[{"label":"Move to Junk Folder","value":"MoveToJmf"},{"label":"Delete the message before its delivered","value":"Delete"},{"label":"Quarantine the message","value":"Quarantine"}]} + {"type":"Select","label":"Apply quarantine policy","name":"standards.AntiPhishPolicy.MailboxIntelligenceQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-AntiphishPolicy or New-AntiphishPolicy + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + param($Tenant, $Settings) $PolicyName = 'Default Anti-Phishing Policy' $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AntiPhishPolicy' | Where-Object -Property Name -EQ $PolicyName | - Select-Object Name, Enabled, PhishThresholdLevel, EnableMailboxIntelligence, EnableMailboxIntelligenceProtection, EnableSpoofIntelligence, EnableFirstContactSafetyTips, EnableSimilarUsersSafetyTips, EnableSimilarDomainsSafetyTips, EnableUnusualCharactersSafetyTips, EnableUnauthenticatedSender, EnableViaTag, MailboxIntelligenceProtectionAction, MailboxIntelligenceQuarantineTag + Select-Object Name, Enabled, PhishThresholdLevel, EnableMailboxIntelligence, EnableMailboxIntelligenceProtection, EnableSpoofIntelligence, EnableFirstContactSafetyTips, EnableSimilarUsersSafetyTips, EnableSimilarDomainsSafetyTips, EnableUnusualCharactersSafetyTips, EnableUnauthenticatedSender, EnableViaTag, AuthenticationFailAction, SpoofQuarantineTag, MailboxIntelligenceProtectionAction, MailboxIntelligenceQuarantineTag, TargetedUserProtectionAction, TargetedUserQuarantineTag, TargetedDomainProtectionAction, TargetedDomainQuarantineTag, EnableOrganizationDomainsProtection $StateIsCorrect = ($CurrentState.Name -eq $PolicyName) -and ($CurrentState.Enabled -eq $true) -and @@ -23,10 +66,14 @@ function Invoke-CIPPStandardAntiPhishPolicy { ($CurrentState.EnableUnusualCharactersSafetyTips -eq $Settings.EnableUnusualCharactersSafetyTips) -and ($CurrentState.EnableUnauthenticatedSender -eq $true) -and ($CurrentState.EnableViaTag -eq $true) -and + ($CurrentState.AuthenticationFailAction -eq $Settings.AuthenticationFailAction) -and + ($CurrentState.SpoofQuarantineTag -eq $Settings.SpoofQuarantineTag) -and ($CurrentState.MailboxIntelligenceProtectionAction -eq $Settings.MailboxIntelligenceProtectionAction) -and ($CurrentState.MailboxIntelligenceQuarantineTag -eq $Settings.MailboxIntelligenceQuarantineTag) -and ($CurrentState.TargetedUserProtectionAction -eq $Settings.TargetedUserProtectionAction) -and + ($CurrentState.TargetedUserQuarantineTag -eq $Settings.TargetedUserQuarantineTag) -and ($CurrentState.TargetedDomainProtectionAction -eq $Settings.TargetedDomainProtectionAction) -and + ($CurrentState.TargetedDomainQuarantineTag -eq $Settings.TargetedDomainQuarantineTag) -and ($CurrentState.EnableOrganizationDomainsProtection -eq $true) $AcceptedDomains = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AcceptedDomain' @@ -56,10 +103,14 @@ function Invoke-CIPPStandardAntiPhishPolicy { EnableUnusualCharactersSafetyTips = $Settings.EnableUnusualCharactersSafetyTips EnableUnauthenticatedSender = $true EnableViaTag = $true + AuthenticationFailAction = $Settings.AuthenticationFailAction + SpoofQuarantineTag = $Settings.SpoofQuarantineTag MailboxIntelligenceProtectionAction = $Settings.MailboxIntelligenceProtectionAction MailboxIntelligenceQuarantineTag = $Settings.MailboxIntelligenceQuarantineTag TargetedUserProtectionAction = $Settings.TargetedUserProtectionAction + TargetedUserQuarantineTag = $Settings.TargetedUserQuarantineTag TargetedDomainProtectionAction = $Settings.TargetedDomainProtectionAction + TargetedDomainQuarantineTag = $Settings.TargetedDomainQuarantineTag EnableOrganizationDomainsProtection = $true } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 new file mode 100644 index 000000000000..e3f950550f71 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 @@ -0,0 +1,44 @@ +function Invoke-CIPPStandardAppDeploy { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) AppDeploy + .SYNOPSIS + (Label) Deploy Application + .DESCRIPTION + (Helptext) Deploys selected applications to the tenant. Use a comma separated list of application IDs to deploy multiple applications. Permissions will be copied from the source application. + (DocsDescription) Uses the CIPP functionality that deploys applications across an entire tenant base as a standard. + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"input","name":"standards.AppDeploy.appids","label":"Application IDs, comma separated"} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Portal or Graph API + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + 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. Error: $ErrorMessage" -sev Error + } + } + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 index 4538eef5aed7..bb1bd639fc75 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 @@ -1,9 +1,34 @@ function Invoke-CIPPStandardAtpPolicyForO365 { <# .FUNCTIONALITY - Internal - #> - + Internal + .COMPONENT + (APIName) AtpPolicyForO365 + .SYNOPSIS + (Label) Default Atp Policy For O365 + .DESCRIPTION + (Helptext) This creates a Atp policy that enables Defender for Office 365 for Sharepoint, OneDrive and Microsoft Teams. + (DocsDescription) This creates a Atp policy that enables Defender for Office 365 for Sharepoint, OneDrive and Microsoft Teams. + .NOTES + CAT + Defender Standards + TAG + "lowimpact" + "CIS" + ADDEDCOMPONENT + {"type":"boolean","label":"Allow people to click through Protected View even if Safe Documents identified the file as malicious","name":"standards.AtpPolicyForO365.AllowSafeDocsOpen","default":false} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-AtpPolicyForO365 + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + param($Tenant, $Settings) $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AtpPolicyForO365' | Select-Object EnableATPForSPOTeamsODB, EnableSafeDocs, AllowSafeDocsOpen diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 index acb6bf9834a1..076a9112586a 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardAuditLog { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) AuditLog + .SYNOPSIS + (Label) Enable the Unified Audit Log + .DESCRIPTION + (Helptext) Enables the Unified Audit Log for tracking and auditing activities. Also runs Enable-OrganizationCustomization if necessary. + (DocsDescription) Enables the Unified Audit Log for tracking and auditing activities. Also runs Enable-OrganizationCustomization if necessary. + .NOTES + CAT + Global Standards + TAG + "lowimpact" + "CIS" + "mip_search_auditlog" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Enable-OrganizationCustomization + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) Write-Host ($Settings | ConvertTo-Json) $AuditLogEnabled = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AdminAuditLogConfig' -Select UnifiedAuditLogIngestionEnabled).UnifiedAuditLogIngestionEnabled diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 index 53d29e442822..0aaefee3441c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardAutoExpandArchive { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) AutoExpandArchive + .SYNOPSIS + (Label) Enable Auto-expanding archives + .DESCRIPTION + (Helptext) Enables auto-expanding archives for the tenant + (DocsDescription) Enables auto-expanding archives for the tenant. Does not enable archives for users. + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-OrganizationConfig -AutoExpandingArchive + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentState = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').AutoExpandingArchiveEnabled diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1 index 406f0c06bd84..593afe1a6b62 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1 @@ -1,8 +1,32 @@ function Invoke-CIPPStandardBookings { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) Bookings + .SYNOPSIS + (Label) Set Bookings state + .DESCRIPTION + (Helptext) Sets the state of Bookings on the tenant. Bookings is a scheduling tool that allows users to book appointments with others both internal and external. + (DocsDescription) Sets the state of Bookings on the tenant. Bookings is a scheduling tool that allows users to book appointments with others both internal and external. + .NOTES + CAT + Exchange Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.Bookings.state","values":[{"label":"Enabled","value":"true"},{"label":"Disabled","value":"false"}]} + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-OrganizationConfig -BookingsEnabled + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentState = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').BookingsEnabled diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1 index 2f3841588d28..605256e64581 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1 @@ -1,7 +1,35 @@ function Invoke-CIPPStandardBranding { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) Branding + .SYNOPSIS + (Label) Set branding for the tenant + .DESCRIPTION + (Helptext) Sets the branding for the tenant. This includes the login page, and the Office 365 portal. + (DocsDescription) Sets the branding for the tenant. This includes the login page, and the Office 365 portal. + .NOTES + CAT + Global Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"input","name":"standards.Branding.signInPageText","label":"Sign-in page text"} + {"type":"input","name":"standards.Branding.usernameHintText","label":"Username hint Text"} + {"type":"boolean","name":"standards.Branding.hideAccountResetCredentials","label":"Hide self-service password reset"} + {"type":"Select","label":"Visual Template","name":"standards.Branding.layoutTemplateType","values":[{"label":"Full-screen background","value":"default"},{"label":"Parial-screen background","value":"verticalSplit"}]} + {"type":"boolean","name":"standards.Branding.isHeaderShown","label":"Show header"} + {"type":"boolean","name":"standards.Branding.isFooterShown","label":"Show footer"} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Portal only + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> param($Tenant, $Settings) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 index 19c6926d5af4..54edbd2f3670 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 @@ -1,8 +1,32 @@ function Invoke-CIPPStandardCloudMessageRecall { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) CloudMessageRecall + .SYNOPSIS + (Label) Set Cloud Message Recall state + .DESCRIPTION + (Helptext) Sets the Cloud Message Recall state for the tenant. This allows users to recall messages from the cloud. + (DocsDescription) Sets the default state for Cloud Message Recall for the tenant. By default this is enabled. You can read more about the feature [here.](https://techcommunity.microsoft.com/t5/exchange-team-blog/cloud-based-message-recall-in-exchange-online/ba-p/3744714) + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.CloudMessageRecall.state","values":[{"label":"Enabled","value":"true"},{"label":"Disabled","value":"false"}]} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-OrganizationConfig -MessageRecallEnabled + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentState = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').MessageRecallEnabled diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 index 81cbdc6e5167..b182da26e89c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardDelegateSentItems { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DelegateSentItems + .SYNOPSIS + (Label) Set mailbox Sent Items delegation (Sent items for shared mailboxes) + .DESCRIPTION + (Helptext) Sets emails sent as and on behalf of shared mailboxes to also be stored in the shared mailbox sent items folder + (DocsDescription) This makes sure that e-mails sent from shared mailboxes or delegate mailboxes, end up in the mailbox of the shared/delegate mailbox instead of the sender, allowing you to keep replies in the same mailbox as the original e-mail. + .NOTES + CAT + Exchange Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-Mailbox + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $Mailboxes = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' -cmdParams @{ RecipientTypeDetails = @('UserMailbox', 'SharedMailbox') } | Where-Object { $_.MessageCopyForSendOnBehalfEnabled -eq $false -or $_.MessageCopyForSentAsEnabled -eq $false } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 index 157bf5fbf690..88d8455b08fe 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 @@ -1,41 +1,81 @@ function Invoke-CIPPStandardDeletedUserRentention { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DeletedUserRentention + .SYNOPSIS + (Label) Set deleted user retention time in OneDrive + .DESCRIPTION + (Helptext) Sets the retention period for deleted users OneDrive to the specified number of years. The default is 1 year. + (DocsDescription) When a OneDrive user gets deleted, the personal SharePoint site is saved for selected time in years and data can be retrieved from it. + .NOTES + CAT + SharePoint Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"Select","name":"standards.DeletedUserRentention.Days","label":"Retention in years (Default 1)","values":[{"label":"1 year","value":"365"},{"label":"2 years","value":"730"},{"label":"3 years","value":"1095"},{"label":"4 years","value":"1460"},{"label":"5 years","value":"1825"},{"label":"6 years","value":"2190"},{"label":"7 years","value":"2555"},{"label":"8 years","value":"2920"},{"label":"9 years","value":"3285"},{"label":"10 years","value":"3650"}]} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgBetaAdminSharepointSetting + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true - $StateSetCorrectly = if ($CurrentInfo.deletedUserPersonalSiteRetentionPeriodInDays -eq 365) { $true } else { $false } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'DeletedUserRentention' -FieldValue $CurrentInfo.deletedUserPersonalSiteRetentionPeriodInDays -StoreAs string -Tenant $tenant + } + + # Input validation + if (($Settings.Days -eq 'Select a value') -and ($Settings.remediate -eq $true -or $Settings.alert -eq $true)) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'DeletedUserRententio: Invalid Days parameter set' -sev Error + Return + } + + # Backwards compatibility for v5.9.4 and back + if ($null -eq $Settings.Days) { + $WantedState = 365 + } else { + $WantedState = [int]$Settings.Days + } + + $StateSetCorrectly = if ($CurrentInfo.deletedUserPersonalSiteRetentionPeriodInDays -eq $WantedState) { $true } else { $false } + $RetentionInYears = $WantedState / 365 If ($Settings.remediate -eq $true) { Write-Host 'Time to remediate' if ($StateSetCorrectly -eq $false) { try { - $body = '{"deletedUserPersonalSiteRetentionPeriodInDays": 365}' + $body = [PSCustomObject]@{ + deletedUserPersonalSiteRetentionPeriodInDays = $Settings.Days + } + $body = ConvertTo-Json -InputObject $body -Depth 5 -Compress New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -AsApp $true -Type PATCH -Body $body -ContentType 'application/json' - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Set deleted user rentention of OneDrive to 1 year' -sev Info + Write-LogMessage -API 'Standards' -tenant $tenant -message "Set deleted user rentention of OneDrive to $RetentionInYears year(s)" -sev Info } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set deleted user rentention of OneDrive to 1 year. Error: $ErrorMessage" -sev Error + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set deleted user rentention of OneDrive to $RetentionInYears year(s). Error: $ErrorMessage" -sev Error } } else { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Deleted user rentention of OneDrive is already set to 1 year' -sev Info - + Write-LogMessage -API 'Standards' -tenant $tenant -message "Deleted user rentention of OneDrive is already set to $RetentionInYears year(s)" -sev Info } } if ($Settings.alert -eq $true) { - if ($StateSetCorrectly) { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Deleted user rentention of OneDrive is set to 1 year' -sev Info + if ($StateSetCorrectly -eq $true) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Deleted user rentention of OneDrive is set to $RetentionInYears year(s)" -sev Info } else { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Deleted user rentention of OneDrive is not set to 1 year' -sev Alert + Write-LogMessage -API 'Standards' -tenant $tenant -message "Deleted user rentention of OneDrive is not set to $RetentionInYears year(s). Value is: $($CurrentInfo.deletedUserPersonalSiteRetentionPeriodInDays) " -sev Alert } } - - if ($Settings.report -eq $true) { - - Add-CIPPBPAField -FieldName 'DeletedUserRentention' -FieldValue $StateSetCorrectly -StoreAs bool -Tenant $tenant - } } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 index 485fe370c59d..31d04d0cf86c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 @@ -1,8 +1,33 @@ function Invoke-CIPPStandardDisableAddShortcutsToOneDrive { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableAddShortcutsToOneDrive + .SYNOPSIS + (Label) Disable Add Shortcuts To OneDrive + .DESCRIPTION + (Helptext) When the feature is disabled the option Add shortcut to OneDrive will be removed. Any folders that have already been added will remain on the user's computer. + (DocsDescription) When the feature is disabled the option Add shortcut to OneDrive will be removed. Any folders that have already been added will remain on the user's computer. + .NOTES + CAT + SharePoint Standards + TAG + "mediumimpact" + DISABLEDFEATURES + + ADDEDCOMPONENT + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Graph API or Portal + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) If ($Settings.remediate -eq $true) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 index 6612d7090240..bc929660a0f8 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardDisableAdditionalStorageProviders { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableAdditionalStorageProviders + .SYNOPSIS + (Label) Disable additional storage providers in OWA + .DESCRIPTION + (Helptext) Disables the ability for users to open files in Outlook on the Web, from other providers such as Box, Dropbox, Facebook, Google Drive, OneDrive Personal, etc. + (DocsDescription) Disables additional storage providers in OWA. This is to prevent users from using personal storage providers like Dropbox, Google Drive, etc. Usually this has little user impact. + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + "CIS" + "exo_storageproviderrestricted" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Get-OwaMailboxPolicy | Set-OwaMailboxPolicy -AdditionalStorageProvidersEnabled $False + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $AdditionalStorageProvidersState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OwaMailboxPolicy' -cmdParams @{Identity = 'OwaMailboxPolicy-Default' } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAppCreation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAppCreation.ps1 index 7204971fae4e..a807ec53817e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAppCreation.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAppCreation.ps1 @@ -1,8 +1,33 @@ function Invoke-CIPPStandardDisableAppCreation { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableAppCreation + .SYNOPSIS + (Label) Disable App creation by users + .DESCRIPTION + (Helptext) Disables the ability for users to create App registrations in the tenant. + (DocsDescription) Disables the ability for users to create applications in Entra. Done to prevent breached accounts from creating an app to maintain access to the tenant, even after the breached account has been secured. + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + "CIS" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgPolicyAuthorizationPolicy + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy?$select=defaultUserRolePermissions' -tenantid $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 index ed8a7b256ff7..313aa87db35b 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardDisableBasicAuthSMTP { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableBasicAuthSMTP + .SYNOPSIS + (Label) Disable SMTP Basic Authentication + .DESCRIPTION + (Helptext) Disables SMTP AUTH for the organization and all users. This is the default for new tenants. + (DocsDescription) Disables SMTP basic authentication for the tenant and all users with it explicitly enabled. + .NOTES + CAT + Global Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-TransportConfig -SmtpClientAuthenticationDisabled $true + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-TransportConfig' $SMTPusers = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-CASMailbox' -cmdParams @{ ResultSize = 'Unlimited' } | Where-Object { ($_.SmtpClientAuthenticationDisabled -eq $false) } @@ -35,18 +58,19 @@ function Invoke-CIPPStandardDisableBasicAuthSMTP { } } + $LogMessage = [System.Collections.Generic.List[string]]::new() if ($Settings.alert -eq $true -or $Settings.report -eq $true) { # Build the log message for use in the alert and report if ($CurrentInfo.SmtpClientAuthenticationDisabled) { - $LogMessage = 'SMTP Basic Authentication for tenant is disabled. ' + $LogMessage.add('SMTP Basic Authentication for tenant is disabled. ') } else { - $LogMessage = 'SMTP Basic Authentication for tenant is not disabled. ' + $LogMessage.add('SMTP Basic Authentication for tenant is not disabled. ') } if ($SMTPusers.Count -eq 0) { - $LogMessage += 'SMTP Basic Authentication for all users is disabled' + $LogMessage.add('SMTP Basic Authentication for all users is disabled') } else { - $LogMessage += "SMTP Basic Authentication for the following $($SMTPusers.Count) users is not disabled: $($SMTPusers.PrimarySmtpAddress -join ',')" + $LogMessage.add("SMTP Basic Authentication for the following $($SMTPusers.Count) users is not disabled: $($SMTPusers.PrimarySmtpAddress -join ',')") } if ($Settings.alert -eq $true) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableEmail.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableEmail.ps1 index 43eb1f36db3a..1c27528f003e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableEmail.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableEmail.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardDisableEmail { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableEmail + .SYNOPSIS + (Label) Disables Email as an MFA method + .DESCRIPTION + (Helptext) This blocks users from using email as an MFA method. This disables the email OTP option for guest users, and instead promts them to create a Microsoft account. + (DocsDescription) This blocks users from using email as an MFA method. This disables the email OTP option for guest users, and instead promts them to create a Microsoft account. + .NOTES + CAT + Entra (AAD) Standards + TAG + "highimpact" + ADDEDCOMPONENT + IMPACT + High Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/Email' -tenantid $Tenant $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 index 2393e7c3994d..aa048965fbad 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardDisableExternalCalendarSharing { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableExternalCalendarSharing + .SYNOPSIS + (Label) Disable external calendar sharing + .DESCRIPTION + (Helptext) Disables the ability for users to share their calendar with external users. Only for the default policy, so exclusions can be made if needed. + (DocsDescription) Disables external calendar sharing for the entire tenant. This is not a widely used feature, and it's therefore unlikely that this will impact users. Only for the default policy, so exclusions can be made if needed by making a new policy and assigning it to users. + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + "CIS" + "exo_individualsharing" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Get-SharingPolicy | Set-SharingPolicy -Enabled $False + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SharingPolicy' | Where-Object { $_.Default -eq $true } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuestDirectory.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuestDirectory.ps1 index 8fbcb35110d5..8678a5d95591 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuestDirectory.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuestDirectory.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardDisableGuestDirectory { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableGuestDirectory + .SYNOPSIS + (Label) Restrict guest user access to directory objects + .DESCRIPTION + (Helptext) Disables Guest access to enumerate directory objects. This prevents guest users from seeing other users or guests in the directory. + (DocsDescription) Sets it so guests can view only their own user profile. Permission to view other users isn't allowed. Also restricts guest users from seeing the membership of groups they're in. See exactly what get locked down in the [Microsoft documentation.](https://learn.microsoft.com/en-us/entra/fundamentals/users-default-permissions) + .NOTES + CAT + Global Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-AzureADMSAuthorizationPolicy -GuestUserRoleId '2af84b1e-32c8-42b7-82bc-daa82404023b' + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -tenantid $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 index 7b13fffd147f..bc5ba6148d72 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardDisableGuests { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableGuests + .SYNOPSIS + (Label) Disable Guest accounts that have not logged on for 90 days + .DESCRIPTION + (Helptext) Blocks login for guest users that have not logged in for 90 days + (DocsDescription) Blocks login for guest users that have not logged in for 90 days + .NOTES + CAT + Entra (AAD) Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Graph API + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $Lookup = (Get-Date).AddDays(-90).ToUniversalTime().ToString('o') $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$filter=(signInActivity/lastNonInteractiveSignInDateTime le $Lookup)&`$select=id,UserPrincipalName,signInActivity,mail,userType,accountEnabled" -scope 'https://graph.microsoft.com/.default' -tenantid $Tenant | Where-Object { $_.userType -EQ 'Guest' -and $_.AccountEnabled -EQ $true } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 index a173eaba6759..299397f8ab59 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardDisableM365GroupUsers { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableM365GroupUsers + .SYNOPSIS + (Label) Disable M365 Group creation by users + .DESCRIPTION + (Helptext) Restricts M365 group creation to certain admin roles. This disables the ability to create Teams, Sharepoint sites, Planner, etc + (DocsDescription) Users by default are allowed to create M365 groups. This restricts M365 group creation to certain admin roles. This disables the ability to create Teams, SharePoint sites, Planner, etc + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgBetaDirectorySetting + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentState = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/settings' -tenantid $tenant) | Where-Object -Property displayname -EQ 'Group.unified' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 index 230920781a6a..e74fe81dfa22 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardDisableOutlookAddins { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableOutlookAddins + .SYNOPSIS + (Label) Disable users from installing add-ins in Outlook + .DESCRIPTION + (Helptext) Disables the ability for users to install add-ins in Outlook. This is to prevent users from installing malicious add-ins. + (DocsDescription) Disables users from being able to install add-ins in Outlook. Only admins are able to approve add-ins for the users. This is done to reduce the threat surface for data exfiltration. + .NOTES + CAT + Exchange Standards + TAG + "mediumimpact" + "CIS" + "exo_outlookaddins" + ADDEDCOMPONENT + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Get-ManagementRoleAssignment | Remove-ManagementRoleAssignment + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-RoleAssignmentPolicy' | Where-Object { $_.IsDefault -eq $true } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 index 0893d8fda2d2..b37877ef1ed2 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 @@ -1,8 +1,33 @@ function Invoke-CIPPStandardDisableReshare { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableReshare + .SYNOPSIS + (Label) Disable Resharing by External Users + .DESCRIPTION + (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 + .NOTES + CAT + SharePoint Standards + TAG + "highimpact" + "CIS" + ADDEDCOMPONENT + IMPACT + High Impact + POWERSHELLEQUIVALENT + Update-MgBetaAdminSharepointSetting + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSMS.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSMS.ps1 index 43dd0198d1b3..f6ce58e33a56 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSMS.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSMS.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardDisableSMS { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableSMS + .SYNOPSIS + (Label) Disables SMS as an MFA method + .DESCRIPTION + (Helptext) This blocks users from using SMS as an MFA method. If a user only has SMS as a MFA method, they will be unable to log in. + (DocsDescription) Disables SMS as an MFA method for the tenant. If a user only has SMS as a MFA method, they will be unable to sign in. + .NOTES + CAT + Entra (AAD) Standards + TAG + "highimpact" + ADDEDCOMPONENT + IMPACT + High Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/SMS' -tenantid $Tenant $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSecurityGroupUsers.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSecurityGroupUsers.ps1 index edaf91dfde7d..0db8db2c4cc5 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSecurityGroupUsers.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSecurityGroupUsers.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardDisableSecurityGroupUsers { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableSecurityGroupUsers + .SYNOPSIS + (Label) Disable Security Group creation by users + .DESCRIPTION + (Helptext) Completely disables the creation of security groups by users. This also breaks the ability to manage groups themselves, or create Teams + (DocsDescription) Completely disables the creation of security groups by users. This also breaks the ability to manage groups themselves, or create Teams + .NOTES + CAT + Entra (AAD) Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthorizationPolicy + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -tenantid $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 index 6431f6053dc5..39a0e78629ef 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 @@ -1,10 +1,96 @@ function Invoke-CIPPStandardDisableSelfServiceLicenses { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableSelfServiceLicenses + .SYNOPSIS + (Label) Disable Self Service Licensing + .DESCRIPTION + (Helptext) This standard disables all self service licenses and enables all exclusions + (DocsDescription) This standard disables all self service licenses and enables all exclusions + .NOTES + CAT + Entra (AAD) Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + {"type":"input","name":"standards.DisableSelfServiceLicenses.Exclusions","label":"License Ids to exclude from this standard"} + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-MsolCompanySettings -AllowAdHocSubscriptions $false + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Self Service Licenses cannot be disabled' -sev Error + #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(',') + } + + $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 + } + } + + 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-CIPPStandardDisableSharePointLegacyAuth.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 index 1e109b41a3aa..7665efb69b22 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 @@ -1,8 +1,33 @@ function Invoke-CIPPStandardDisableSharePointLegacyAuth { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableSharePointLegacyAuth + .SYNOPSIS + (Label) Disable legacy basic authentication for SharePoint + .DESCRIPTION + (Helptext) Disables the ability to authenticate with SharePoint using legacy authentication methods. Any applications that use legacy authentication will need to be updated to use modern authentication. + (DocsDescription) Disables the ability for users and applications to access SharePoint via legacy basic authentication. This will likely not have any user impact, but will block systems/applications depending on basic auth or the SharePointOnlineCredentials class. + .NOTES + CAT + SharePoint Standards + TAG + "mediumimpact" + "CIS" + ADDEDCOMPONENT + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-SPOTenant -LegacyAuthProtocolsEnabled $false + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings?$select=isLegacyAuthProtocolsEnabled' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharedMailbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharedMailbox.ps1 index d9d3356eba00..cbc159e30dd8 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharedMailbox.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharedMailbox.ps1 @@ -1,8 +1,33 @@ function Invoke-CIPPStandardDisableSharedMailbox { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableSharedMailbox + .SYNOPSIS + (Label) Disable Shared Mailbox AAD accounts + .DESCRIPTION + (Helptext) Blocks login for all accounts that are marked as a shared mailbox. This is Microsoft best practice to prevent direct logons to shared mailboxes. + (DocsDescription) Shared mailboxes can be directly logged into if the password is reset, this presents a security risk as do all shared login credentials. Microsoft's recommendation is to disable the user account for shared mailboxes. It would be a good idea to review the sign-in reports to establish potential impact. + .NOTES + CAT + Exchange Standards + TAG + "mediumimpact" + "CIS" + ADDEDCOMPONENT + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Get-Mailbox & Update-MgUser + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $UserList = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/users?$top=999&$filter=accountEnabled eq true' -Tenantid $tenant -scope 'https://graph.microsoft.com/.default' $SharedMailboxList = (New-GraphGetRequest -uri "https://outlook.office365.com/adminapi/beta/$($Tenant)/Mailbox" -Tenantid $tenant -scope ExchangeOnline | Where-Object { $_.RecipientTypeDetails -EQ 'SharedMailbox' -or $_.RecipientTypeDetails -eq 'SchedulingMailbox' -and $_.UserPrincipalName -in $UserList.UserPrincipalName }) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 index 022f21807864..256d3ba80dc1 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 @@ -1,7 +1,29 @@ function Invoke-CIPPStandardDisableTNEF { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableTNEF + .SYNOPSIS + (Label) Disable TNEF/winmail.dat + .DESCRIPTION + (Helptext) Disables Transport Neutral Encapsulation Format (TNEF)/winmail.dat for the tenant. TNEF can cause issues if the recipient is not using a client supporting TNEF. + (DocsDescription) Disables Transport Neutral Encapsulation Format (TNEF)/winmail.dat for the tenant. TNEF can cause issues if the recipient is not using a client supporting TNEF. Cannot be overridden by the user. For more information, see [Microsoft's documentation.](https://learn.microsoft.com/en-us/exchange/mail-flow/content-conversion/tnef-conversion?view=exchserver-2019) + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-RemoteDomain -Identity 'Default' -TNEFEnabled $false + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> param ($Tenant, $Settings) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTenantCreation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTenantCreation.ps1 index 97616ca367ea..34ce20e2c9fc 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTenantCreation.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTenantCreation.ps1 @@ -1,8 +1,33 @@ function Invoke-CIPPStandardDisableTenantCreation { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableTenantCreation + .SYNOPSIS + (Label) Disable M365 Tenant creation by users + .DESCRIPTION + (Helptext) Restricts creation of M365 tenants to the Global Administrator or Tenant Creator roles. + (DocsDescription) Users by default are allowed to create M365 tenants. This disables that so only admins can create new M365 tenants. + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + "CIS" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgPolicyAuthorizationPolicy + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -tenantid $Tenant $State = $CurrentInfo.defaultUserRolePermissions.allowedToCreateTenants diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 index 97bac09d7668..3f183300bf27 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardDisableUserSiteCreate { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableUserSiteCreate + .SYNOPSIS + (Label) Disable site creation by standard users + .DESCRIPTION + (Helptext) Disables users from creating new SharePoint sites + (DocsDescription) Disables standard users from creating SharePoint sites, also disables the ability to fully create teams + .NOTES + CAT + SharePoint Standards + TAG + "highimpact" + ADDEDCOMPONENT + IMPACT + High Impact + POWERSHELLEQUIVALENT + Update-MgAdminSharepointSetting + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableViva.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableViva.ps1 index 2a87da3fef09..1235c3564853 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableViva.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableViva.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardDisableViva { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableViva + .SYNOPSIS + (Label) Disable daily Insight/Viva reports + .DESCRIPTION + (Helptext) Disables the daily viva reports for all users. + (DocsDescription) Disables the daily viva reports for all users. + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-UserBriefingConfig + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) try { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableVoice.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableVoice.ps1 index 0c064013b444..4d274249bae1 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableVoice.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableVoice.ps1 @@ -1,8 +1,28 @@ function Invoke-CIPPStandardDisableVoice { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) DisableVoice + .SYNOPSIS + (Label) Disables Voice call as an MFA method + .DESCRIPTION + (Helptext) This blocks users from using Voice call as an MFA method. If a user only has Voice as a MFA method, they will be unable to log in. + (DocsDescription) Disables Voice call as an MFA method for the tenant. If a user only has Voice call as a MFA method, they will be unable to sign in. + .NOTES + CAT + Entra (AAD) Standards + TAG + "highimpact" + ADDEDCOMPONENT + IMPACT + High Impact + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/Voice' -tenantid $Tenant $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisablex509Certificate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisablex509Certificate.ps1 index d59042f1f6c8..0b463008f7f7 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisablex509Certificate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisablex509Certificate.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardDisablex509Certificate { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) Disablex509Certificate + .SYNOPSIS + (Label) Disables Certificates as an MFA method + .DESCRIPTION + (Helptext) This blocks users from using Certificates as an MFA method. + (DocsDescription) This blocks users from using Certificates as an MFA method. + .NOTES + CAT + Entra (AAD) Standards + TAG + "highimpact" + ADDEDCOMPONENT + IMPACT + High Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/x509Certificate' -tenantid $Tenant $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableAppConsentRequests.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableAppConsentRequests.ps1 index 835d6a9dfe94..607e69c00c3e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableAppConsentRequests.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableAppConsentRequests.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardEnableAppConsentRequests { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) EnableAppConsentRequests + .SYNOPSIS + (Label) Enable App consent admin requests + .DESCRIPTION + (Helptext) Enables App consent admin requests for the tenant via the GA role. Does not overwrite existing reviewer settings + (DocsDescription) Enables the ability for users to request admin consent for applications. Should be used in conjunction with the "Require admin consent for applications" standards + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + "CIS" + ADDEDCOMPONENT + {"type":"AdminRolesMultiSelect","label":"App Consent Reviewer Roles","name":"standards.EnableAppConsentRequests.ReviewerRoles"} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgPolicyAdminConsentRequestPolicy + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/adminConsentRequestPolicy' -tenantid $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1 index b5741d27ac4d..e977f041eca5 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardEnableCustomerLockbox { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) EnableCustomerLockbox + .SYNOPSIS + (Label) Enable Customer Lockbox + .DESCRIPTION + (Helptext) Enables Customer Lockbox that offers an approval process for Microsoft support to access organization data + (DocsDescription) Customer Lockbox ensures that Microsoft can't access your content to do service operations without your explicit approval. Customer Lockbox ensures only authorized requests allow access to your organizations data. + .NOTES + CAT + Global Standards + TAG + "lowimpact" + "CIS" + "CustomerLockBoxEnabled" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-OrganizationConfig -CustomerLockBoxEnabled $true + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CustomerLockboxStatus = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').CustomerLockboxEnabled diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableFIDO2.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableFIDO2.ps1 index d5d84aa3d8e6..89314821af23 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableFIDO2.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableFIDO2.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardEnableFIDO2 { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) EnableFIDO2 + .SYNOPSIS + (Label) Enable FIDO2 capabilities + .DESCRIPTION + (Helptext) Enables the FIDO2 authenticationMethod for the tenant + (DocsDescription) Enables FIDO2 capabilities for the tenant. This allows users to use FIDO2 keys like a Yubikey for authentication. + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/Fido2' -tenantid $Tenant $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableHardwareOAuth.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableHardwareOAuth.ps1 index 67b0cf7e7bc4..8bef17107b58 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableHardwareOAuth.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableHardwareOAuth.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardEnableHardwareOAuth { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) EnableHardwareOAuth + .SYNOPSIS + (Label) Enable Hardware OAuth tokens + .DESCRIPTION + (Helptext) Enables the HardwareOath authenticationMethod for the tenant. This allows you to use hardware tokens for generating 6 digit MFA codes. + (DocsDescription) Enables Hardware OAuth tokens for the tenant. This allows users to use hardware tokens like a Yubikey for authentication. + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/HardwareOath' -tenantid $Tenant $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } @@ -29,4 +52,3 @@ function Invoke-CIPPStandardEnableHardwareOAuth { Add-CIPPBPAField -FieldName 'EnableHardwareOAuth' -FieldValue $State -StoreAs bool -Tenant $tenant } } - diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 new file mode 100644 index 000000000000..4394928eb5aa --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 @@ -0,0 +1,77 @@ +function Invoke-CIPPStandardEnableLitigationHold { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) EnableLitigationHold + .SYNOPSIS + (Label) Enable Litigation Hold for all users + .DESCRIPTION + (Helptext) Enables litigation hold for all UserMailboxes with a valid license. + (DocsDescription) Enables litigation hold for all UserMailboxes with a valid license. + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-Mailbox -LitigationHoldEnabled $true + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param($Tenant, $Settings) + + $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) { + + if ($null -eq $MailboxesNoLitHold) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Litigation Hold already enabled for all accounts' -sev Info + } else { + try { + $Request = $MailboxesNoLitHold | ForEach-Object { + @{ + CmdletInput = @{ + CmdletName = 'Set-Mailbox' + Parameters = @{ Identity = $_.UserPrincipalName; LitigationHoldEnabled = $true } + } + } + } + + $BatchResults = New-ExoBulkRequest -tenantid $tenant -cmdletArray @($Request) + $BatchResults | ForEach-Object { + if ($_.error) { + $ErrorMessage = Get-NormalizedError -Message $_.error + Write-Host "Failed to Enable Litigation Hold for $($_.Target). Error: $ErrorMessage" + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to Enable Litigation Hold for $($_.Target). Error: $ErrorMessage" -sev Error + } + } + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to Enable Litigation Hold for all accounts. Error: $ErrorMessage" -sev Error + } + } + + } + + if ($Settings.alert -eq $true) { + + if ($MailboxesNoLitHold) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Mailboxes without Litigation Hold: $($MailboxesNoLitHold.Count)" -sev Alert + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'All mailboxes have Litigation Hold enabled' -sev Info + } + } + + if ($Settings.report -eq $true) { + $filtered = $MailboxesNoLitHold | Select-Object -Property UserPrincipalName + Add-CIPPBPAField -FieldName 'EnableLitHold' -FieldValue $filtered -StoreAs json -Tenant $Tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 index 52d3e3294c18..1a73089b2763 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 @@ -1,7 +1,33 @@ function Invoke-CIPPStandardEnableMailTips { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) EnableMailTips + .SYNOPSIS + (Label) Enable all MailTips + .DESCRIPTION + (Helptext) Enables all MailTips in Outlook. MailTips are the notifications Outlook and Outlook on the web shows when an email you create, meets some requirements + (DocsDescription) Enables all MailTips in Outlook. MailTips are the notifications Outlook and Outlook on the web shows when an email you create, meets some requirements + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + "CIS" + "exo_mailtipsenabled" + ADDEDCOMPONENT + {"type":"number","name":"standards.EnableMailTips.MailTipsLargeAudienceThreshold","label":"Number of recipients to trigger the large audience MailTip (Default is 25)","placeholder":"Enter a profile name","default":25} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-OrganizationConfig + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> param($Tenant, $Settings) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 index 2374206fc6d0..45eb620a65ae 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardEnableMailboxAuditing { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) EnableMailboxAuditing + .SYNOPSIS + (Label) Enable Mailbox auditing + .DESCRIPTION + (Helptext) Enables Mailbox auditing for all mailboxes and on tenant level. Disables audit bypass on all mailboxes. Unified Audit Log needs to be enabled for this standard to function. + (DocsDescription) Enables mailbox auditing on tenant level and for all mailboxes. Disables audit bypass on all mailboxes. By default Microsoft does not enable mailbox auditing for Resource Mailboxes, Public Folder Mailboxes and DiscoverySearch Mailboxes. Unified Audit Log needs to be enabled for this standard to function. + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + "CIS" + "exo_mailboxaudit" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-OrganizationConfig -AuditDisabled $false + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $AuditState = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').AuditDisabled diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 index 3c3dbd004c45..15cd00b97e9c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardEnableOnlineArchiving { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) EnableOnlineArchiving + .SYNOPSIS + (Label) Enable Online Archive for all users + .DESCRIPTION + (Helptext) Enables the In-Place Online Archive for all UserMailboxes with a valid license. + (DocsDescription) Enables the In-Place Online Archive for all UserMailboxes with a valid license. + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Enable-Mailbox -Archive $true + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $MailboxPlans = @( 'ExchangeOnline', 'ExchangeOnlineEnterprise' ) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1 index bd4d6c85e70e..d67a218334bb 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardEnablePronouns { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) EnablePronouns + .SYNOPSIS + (Label) Enable Pronouns + .DESCRIPTION + (Helptext) Enables the Pronouns feature for the tenant. This allows users to set their pronouns in their profile. + (DocsDescription) Enables the Pronouns feature for the tenant. This allows users to set their pronouns in their profile. + .NOTES + CAT + Global Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgBetaAdminPeoplePronoun -IsEnabledInOrganization:$true + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param ($Tenant, $Settings) $Uri = 'https://graph.microsoft.com/v1.0/admin/people/pronouns' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 index 321c13c46ad8..89c1ab4c6731 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 @@ -1,8 +1,32 @@ function Invoke-CIPPStandardExcludedfileExt { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) ExcludedfileExt + .SYNOPSIS + (Label) Exclude File Extensions from Syncing + .DESCRIPTION + (Helptext) Sets the file extensions that are excluded from syncing with OneDrive. These files will be blocked from upload. '*.' is automatically added to the extension and can be omitted. + (DocsDescription) Sets the file extensions that are excluded from syncing with OneDrive. These files will be blocked from upload. '*.' is automatically added to the extension and can be omitted. + .NOTES + CAT + SharePoint Standards + TAG + "highimpact" + ADDEDCOMPONENT + {"type":"input","name":"standards.ExcludedfileExt.ext","label":"Extensions, Comma separated"} + IMPACT + High Impact + POWERSHELLEQUIVALENT + Update-MgAdminSharepointSetting + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true $Exts = ($Settings.ext -replace ' ', '') -split ',' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 index 6fefb63e5b59..7f3fc99018b3 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 @@ -1,8 +1,32 @@ function Invoke-CIPPStandardExternalMFATrusted { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) ExternalMFATrusted + .SYNOPSIS + (Label) Sets the Cross-tenant access setting to trust external MFA + .DESCRIPTION + (Helptext) Sets the state of the Cross-tenant access setting to trust external MFA. This allows guest users to use their home tenant MFA to access your tenant. + (DocsDescription) Sets the state of the Cross-tenant access setting to trust external MFA. This allows guest users to use their home tenant MFA to access your tenant. + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.ExternalMFATrusted.state","values":[{"label":"Enabled","value":"true"},{"label":"Disabled","value":"false"}]} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyCrossTenantAccessPolicyDefault + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $ExternalMFATrusted = (New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy/default?$select=inboundTrust' -tenantid $Tenant) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 index 6dccab45117f..c1e67448cef0 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 @@ -1,8 +1,32 @@ function Invoke-CIPPStandardFocusedInbox { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) FocusedInbox + .SYNOPSIS + (Label) Set Focused Inbox state + .DESCRIPTION + (Helptext) Sets the default Focused Inbox state for the tenant. This can be overridden by the user. + (DocsDescription) Sets the default Focused Inbox state for the tenant. This can be overridden by the user in their Outlook settings. For more information, see [Microsoft's documentation.](https://support.microsoft.com/en-us/office/focused-inbox-for-outlook-f445ad7f-02f4-4294-a82e-71d8964e3978) + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.FocusedInbox.state","values":[{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}]} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-OrganizationConfig -FocusedInboxOn $true or $false + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) # Input validation diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 index 583a39ecb8f8..aa835e4e51f2 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 @@ -1,8 +1,32 @@ function Invoke-CIPPStandardGlobalQuarantineNotifications { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) GlobalQuarantineNotifications + .SYNOPSIS + (Label) Set Global Quarantine Notification Interval + .DESCRIPTION + (Helptext) Sets the Global Quarantine Notification Interval to the selected value. Determines how often the quarantine notification is sent to users. + (DocsDescription) Sets the global quarantine notification interval for the tenant. This is the time between the quarantine notification emails are sent out to users. Default is 24 hours. + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.GlobalQuarantineNotifications.NotificationInterval","values":[{"label":"4 hours","value":"04:00:00"},{"label":"1 day/Daily","value":"1.00:00:00"},{"label":"7 days/Weekly","value":"7.00:00:00"}]} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-QuarantinePolicy -EndUserSpamNotificationFrequency + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param ($Tenant, $Settings) $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-QuarantinePolicy' -cmdParams @{ QuarantinePolicyType = 'GlobalQuarantinePolicy' } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 index 4c1aeeebc2de..62d7a7678aca 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 @@ -12,24 +12,23 @@ function Invoke-CIPPStandardGroupTemplate { $Filter = "PartitionKey eq 'GroupTemplate' and RowKey eq '$($Template.value)'" $groupobj = (Get-AzDataTableEntity @Table -Filter $Filter).JSON | ConvertFrom-Json $email = if ($groupobj.domain) { "$($groupobj.username)@$($groupobj.domain)" } else { "$($groupobj.username)@$($Tenant)" } - $CheckExististing = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/groups' -tenantid $tenant | Where-Object -Property displayName -EQ $groupobj.displayname + $CheckExististing = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $tenant | Where-Object -Property displayName -EQ $groupobj.displayname + $BodyToship = [pscustomobject] @{ + 'displayName' = $groupobj.Displayname + 'description' = $groupobj.Description + 'mailNickname' = $groupobj.username + mailEnabled = [bool]$false + securityEnabled = [bool]$true + isAssignableToRole = [bool]($groupobj | Where-Object -Property groupType -EQ 'AzureRole') + + } + if ($groupobj.membershipRules) { + $BodyToship | Add-Member -NotePropertyName 'membershipRule' -NotePropertyValue ($groupobj.membershipRules) + $BodyToship | Add-Member -NotePropertyName 'groupTypes' -NotePropertyValue @('DynamicMembership') + $BodyToship | Add-Member -NotePropertyName 'membershipRuleProcessingState' -NotePropertyValue 'On' + } if (!$CheckExististing) { if ($groupobj.groupType -in 'Generic', 'azurerole', 'dynamic') { - - $BodyToship = [pscustomobject] @{ - 'displayName' = $groupobj.Displayname - 'description' = $groupobj.Description - 'mailNickname' = $groupobj.username - mailEnabled = [bool]$false - securityEnabled = [bool]$true - isAssignableToRole = [bool]($groupobj | Where-Object -Property groupType -EQ 'AzureRole') - - } - if ($groupobj.membershipRules) { - $BodyToship | Add-Member -NotePropertyName 'membershipRule' -NotePropertyValue ($groupobj.membershipRules) - $BodyToship | Add-Member -NotePropertyName 'groupTypes' -NotePropertyValue @('DynamicMembership') - $BodyToship | Add-Member -NotePropertyName 'membershipRuleProcessingState' -NotePropertyValue 'On' - } $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/groups' -tenantid $tenant -type POST -body (ConvertTo-Json -InputObject $BodyToship -Depth 10) -verbose } else { if ($groupobj.groupType -eq 'dynamicdistribution') { @@ -52,9 +51,30 @@ function Invoke-CIPPStandardGroupTemplate { } } Write-LogMessage -user $request.headers.'x-ms-client-principal' -API 'Standards' -tenant $tenant -message "Created group $($groupobj.displayname) with id $($GraphRequest.id) " -Sev 'Info' - } else { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API 'Standards' -tenant $tenant -message "Group exists $($groupobj.displayname). Did not create" -Sev 'Info' + if ($groupobj.groupType -in 'Generic', 'azurerole', 'dynamic') { + $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($CheckExististing.id)" -tenantid $tenant -type PATCH -body (ConvertTo-Json -InputObject $BodyToship -Depth 10) -verbose + } else { + if ($groupobj.groupType -eq 'dynamicdistribution') { + $Params = @{ + Name = $groupobj.Displayname + RecipientFilter = $groupobj.membershipRules + PrimarySmtpAddress = $email + } + $GraphRequest = New-ExoRequest -tenantid $tenant -cmdlet 'Set-DynamicDistributionGroup' -cmdParams $params + } else { + $Params = @{ + Identity = $groupobj.Displayname + Alias = $groupobj.username + Description = $groupobj.Description + PrimarySmtpAddress = $email + Type = $groupobj.groupType + RequireSenderAuthenticationEnabled = [bool]!$groupobj.AllowExternal + } + $GraphRequest = New-ExoRequest -tenantid $tenant -cmdlet 'Set-DistributionGroup' -cmdParams $params + } + } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API 'Standards' -tenant $tenant -message "Group exists $($groupobj.displayname). Updated to latest settings." -Sev 'Info' } } catch { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardLegacyMFACleanup.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardLegacyMFACleanup.ps1 index 08f43267469f..ca0ba11aa346 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardLegacyMFACleanup.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardLegacyMFACleanup.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardLegacyMFACleanup { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) LegacyMFACleanup + .SYNOPSIS + (Label) Remove Legacy MFA if SD or CA is active + .DESCRIPTION + (Helptext) This standard currently does not function and can be safely disabled + (DocsDescription) This standard currently does not function and can be safely disabled + .NOTES + CAT + Entra (AAD) Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-MsolUser -StrongAuthenticationRequirements $null + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) Write-LogMessage -API 'Standards' -tenant $tenant -message 'Per User MFA APIs have been disabled.' -sev Info diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 index 8cc14082f3a3..001ff63dce34 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 @@ -1,8 +1,37 @@ function Invoke-CIPPStandardMailContacts { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) MailContacts + .SYNOPSIS + (Label) Set contact e-mails + .DESCRIPTION + (Helptext) Defines the email address to receive general updates and information related to M365 subscriptions. Leave a contact field blank if you do not want to update the contact information. + (DocsDescription) Defines the email address to receive general updates and information related to M365 subscriptions. Leave a contact field blank if you do not want to update the contact information. + .NOTES + CAT + Global Standards + TAG + "lowimpact" + DISABLEDFEATURES + + ADDEDCOMPONENT + {"type":"input","name":"standards.MailContacts.GeneralContact","label":"General Contact"} + {"type":"input","name":"standards.MailContacts.SecurityContact","label":"Security Contact"} + {"type":"input","name":"standards.MailContacts.MarketingContact","label":"Marketing Contact"} + {"type":"input","name":"standards.MailContacts.TechContact","label":"Technical Contact"} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-MsolCompanyContactInformation + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $TenantID = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/organization' -tenantid $tenant) $CurrentInfo = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/organization/$($TenantID.id)" -tenantid $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 index 863110cdac69..c865022210a4 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 @@ -1,9 +1,42 @@ function Invoke-CIPPStandardMalwareFilterPolicy { <# .FUNCTIONALITY - Internal - #> - + Internal + .COMPONENT + (APIName) MalwareFilterPolicy + .SYNOPSIS + (Label) Default Malware Filter Policy + .DESCRIPTION + (Helptext) This creates a Malware filter policy that enables the default File filter and Zero-hour auto purge for malware. + (DocsDescription) This creates a Malware filter policy that enables the default File filter and Zero-hour auto purge for malware. + .NOTES + CAT + Defender Standards + TAG + "lowimpact" + "CIS" + "mdo_zapspam" + "mdo_zapphish" + "mdo_zapmalware" + ADDEDCOMPONENT + {"type":"Select","label":"FileTypeAction","name":"standards.MalwareFilterPolicy.FileTypeAction","values":[{"label":"Reject","value":"Reject"},{"label":"Quarantine the message","value":"Quarantine"}]} + {"type":"Select","label":"QuarantineTag","name":"standards.MalwareFilterPolicy.QuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + {"type":"boolean","label":"Enable Internal Sender Admin Notifications","name":"standards.MalwareFilterPolicy.EnableInternalSenderAdminNotifications"} + {"type":"input","name":"standards.MalwareFilterPolicy.InternalSenderAdminAddress","label":"Internal Sender Admin Address"} + {"type":"boolean","label":"Enable External Sender Admin Notifications","name":"standards.MalwareFilterPolicy.EnableExternalSenderAdminNotifications"} + {"type":"input","name":"standards.MalwareFilterPolicy.ExternalSenderAdminAddress","label":"External Sender Admin Address"} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-MalwareFilterPolicy or New-MalwareFilterPolicy + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + param($Tenant, $Settings) $PolicyName = 'Default Malware Policy' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 index 820486c52ef3..686b7796e6bd 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardMessageExpiration { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) MessageExpiration + .SYNOPSIS + (Label) Lower Transport Message Expiration to 12 hours + .DESCRIPTION + (Helptext) Sets the transport message configuration to timeout a message at 12 hours. + (DocsDescription) Expires messages in the transport queue after 12 hours. Makes the NDR for failed messages show up faster for users. Default is 24 hours. + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-TransportConfig -MessageExpirationTimeout 12.00:00:00 + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $MessageExpiration = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-TransportConfig').messageExpiration diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 index 6d387b212767..069d25550518 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 @@ -1,8 +1,33 @@ function Invoke-CIPPStandardNudgeMFA { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) NudgeMFA + .SYNOPSIS + (Label) Sets the state for the request to setup Authenticator + .DESCRIPTION + (Helptext) Sets the state of the registration campaign for the tenant + (DocsDescription) Sets the state of the registration campaign for the tenant. If enabled nudges users to set up the Microsoft Authenticator during sign-in. + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.NudgeMFA.state","values":[{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}]} + {"type":"number","name":"standards.NudgeMFA.snoozeDurationInDays","label":"Number of days to allow users to skip registering Authenticator (0-14, default is 1)","default":1} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgPolicyAuthenticationMethodPolicy + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy' -tenantid $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1 index 38d2b41dfce3..2654b914836d 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardOauthConsent { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) OauthConsent + .SYNOPSIS + (Label) Require admin consent for applications (Prevent OAuth phishing) + .DESCRIPTION + (Helptext) Disables users from being able to consent to applications, except for those specified in the field below + (DocsDescription) Requires users to get administrator consent before sharing data with applications. You can preapprove specific applications. + .NOTES + CAT + Entra (AAD) Standards + TAG + "mediumimpact" + "CIS" + ADDEDCOMPONENT + {"type":"input","name":"standards.OauthConsent.AllowedApps","label":"Allowed application IDs, comma separated"} + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Update-MgPolicyAuthorizationPolicy + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($tenant, $settings) $State = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -tenantid $tenant $StateIsCorrect = if ($State.permissionGrantPolicyIdsAssignedToDefaultUserRole -eq 'managePermissionGrantsForSelf.cipp-consent-policy') { $true } else { $false } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1 index 42814d48cfb6..b30070a34da5 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1 @@ -1,8 +1,30 @@ function Invoke-CIPPStandardOauthConsentLowSec { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) OauthConsentLowSec + .SYNOPSIS + (Label) Allow users to consent to applications with low security risk (Prevent OAuth phishing. Lower impact, less secure) + .DESCRIPTION + (Helptext) Sets the default oauth consent level so users can consent to applications that have low risks. + (DocsDescription) Allows users to consent to applications with low assigned risk. + .NOTES + CAT + Entra (AAD) Standards + TAG + "mediumimpact" + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Update-MgPolicyAuthorizationPolicy + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $State = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -tenantid $tenant) If ($Settings.remediate -eq $true) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 index a786a7d044b8..ea5d4d413e31 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardOutBoundSpamAlert { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) OutBoundSpamAlert + .SYNOPSIS + (Label) Set Outbound Spam Alert e-mail + .DESCRIPTION + (Helptext) Set the Outbound Spam Alert e-mail address + (DocsDescription) Sets the e-mail address to which outbound spam alerts are sent. + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + "CIS" + ADDEDCOMPONENT + {"type":"input","name":"standards.OutBoundSpamAlert.OutboundSpamContact","label":"Outbound spam contact"} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-HostedOutboundSpamFilterPolicy + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-HostedOutboundSpamFilterPolicy' -useSystemMailbox $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWcompanionAppAllowedState.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWcompanionAppAllowedState.ps1 index c2e3c7a687b7..dda360cf1b01 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWcompanionAppAllowedState.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWcompanionAppAllowedState.ps1 @@ -1,8 +1,32 @@ function Invoke-CIPPStandardPWcompanionAppAllowedState { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) PWcompanionAppAllowedState + .SYNOPSIS + (Label) Set Authenticator Lite state + .DESCRIPTION + (Helptext) Sets the state of Authenticator Lite, Authenticator lite is a companion app for passwordless authentication. + (DocsDescription) Sets the Authenticator Lite state to enabled. This allows users to use the Authenticator Lite built into the Outlook app instead of the full Authenticator app. + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.PWcompanionAppAllowedState.state","values":[{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}]} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $authenticatorFeaturesState = (New-GraphGetRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator' -Type GET) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 index 2f85c01bf859..2ba267e34744 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 @@ -1,8 +1,33 @@ function Invoke-CIPPStandardPWdisplayAppInformationRequiredState { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) PWdisplayAppInformationRequiredState + .SYNOPSIS + (Label) Enable Passwordless with Location information and Number Matching + .DESCRIPTION + (Helptext) Enables the MS authenticator app to display information about the app that is requesting authentication. This displays the application name. + (DocsDescription) Allows users to use Passwordless with Number Matching and adds location information from the last request + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + "CIS" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator' -tenantid $Tenant $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPasswordExpireDisabled.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPasswordExpireDisabled.ps1 index 5cf8dac138a1..001ba177caff 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPasswordExpireDisabled.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPasswordExpireDisabled.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardPasswordExpireDisabled { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) PasswordExpireDisabled + .SYNOPSIS + (Label) Do not expire passwords + .DESCRIPTION + (Helptext) Disables the expiration of passwords for the tenant by setting the password expiration policy to never expire for any user. + (DocsDescription) Sets passwords to never expire for tenant, recommended to use in conjunction with secure password requirements. + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + "CIS" + "PWAgePolicyNew" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgDomain + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $GraphRequest = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/domains' -tenantid $Tenant $DomainswithoutPassExpire = $GraphRequest | Where-Object -Property passwordValidityPeriodInDays -NE '2147483647' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 index c83204529423..32301d96f50c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardPerUserMFA { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) PerUserMFA + .SYNOPSIS + (Label) Enables per user MFA for all users. + .DESCRIPTION + (Helptext) Enables per user MFA for all users. + (DocsDescription) Enables per user MFA for all users. + .NOTES + CAT + Entra (AAD) Standards + TAG + "highimpact" + ADDEDCOMPONENT + IMPACT + High Impact + POWERSHELLEQUIVALENT + Graph API + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$top=999&`$select=UserPrincipalName,accountEnabled" -scope 'https://graph.microsoft.com/.default' -tenantid $Tenant | Where-Object { $_.AccountEnabled -EQ $true } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 index 8d0e37ffe6b0..9632d3ba7129 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardPhishProtection { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) PhishProtection + .SYNOPSIS + (Label) Enable Phishing Protection system via branding CSS + .DESCRIPTION + (Helptext) Adds branding to the logon page that only appears if the url is not login.microsoftonline.com. This potentially prevents AITM attacks via EvilNginx. This will also automatically generate alerts if a clone of your login page has been found when set to Remediate. + (DocsDescription) Adds branding to the logon page that only appears if the url is not login.microsoftonline.com. This potentially prevents AITM attacks via EvilNginx. This will also automatically generate alerts if a clone of your login page has been found when set to Remediate. + .NOTES + CAT + Global Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + DISABLEDFEATURES + + POWERSHELLEQUIVALENT + Portal only + RECOMMENDEDBY + "CIPP" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $TenantId = Get-Tenants | Where-Object -Property defaultDomainName -EQ $tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1 new file mode 100644 index 000000000000..18d829bc3915 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1 @@ -0,0 +1,89 @@ +function Invoke-CIPPStandardQuarantineRequestAlert { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) QuarantineRequestAlert + .SYNOPSIS + (Label) Quarantine Release Request Alert + .DESCRIPTION + (Helptext) Sets a e-mail address to alert when a User requests to release a quarantined message. + (DocsDescription) Sets a e-mail address to alert when a User requests to release a quarantined message. This is useful for monitoring and ensuring that the correct messages are released. + .NOTES + CAT + Defender Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"input","name":"standards.QuarantineRequestAlert.NotifyUser","label":"E-mail to receive the alert"} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + New-ProtectionAlert and Set-ProtectionAlert + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param ($Tenant, $Settings) + $PolicyName = 'CIPP User requested to release a quarantined message' + + $CurrentState = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-ProtectionAlert' -Compliance | + Where-Object { $_.Name -eq $PolicyName } | + Select-Object -Property * + + $StateIsCorrect = ($CurrentState.NotifyUser -contains $Settings.NotifyUser) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Quarantine Request Alert is configured correctly' -sev Info + } else { + $cmdparams = @{ + 'NotifyUser' = $Settings.NotifyUser + 'Category' = 'ThreatManagement' + 'Operation' = 'QuarantineRequestReleaseMessage' + 'Severity' = 'Informational' + 'AggregationType' = 'None' + } + + if ($CurrentState.Name -eq $PolicyName) { + try { + $cmdparams += @{ + 'Identity' = $PolicyName + } + New-ExoRequest -TenantId $Tenant -cmdlet 'Set-ProtectionAlert' -Compliance -cmdparams $cmdparams -UseSystemMailbox $true + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Successfully configured Quarantine Request Alert' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Failed to configure Quarantine Request Alert. Error: $ErrorMessage" -sev Error + } + } else { + try { + $cmdparams += @{ + 'Name' = $PolicyName + 'ThreatType' = 'Activity' + } + New-ExoRequest -TenantId $Tenant -cmdlet 'New-ProtectionAlert' -Compliance -cmdparams $cmdparams -UseSystemMailbox $true + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Successfully created Quarantine Request Alert' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Failed to create Quarantine Request Alert. Error: $ErrorMessage" -sev Error + } + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Quarantine Request Alert is enabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Quarantine Request Alert is disabled' -sev Info + } + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'QuarantineRequestAlert' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 index e149d3aec70d..18fbc9babace 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 @@ -1,8 +1,33 @@ function Invoke-CIPPStandardRotateDKIM { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) RotateDKIM + .SYNOPSIS + (Label) Rotate DKIM keys that are 1024 bit to 2048 bit + .DESCRIPTION + (Helptext) Rotate DKIM keys that are 1024 bit to 2048 bit + (DocsDescription) Rotate DKIM keys that are 1024 bit to 2048 bit + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + "CIS" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Rotate-DkimSigningConfig + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $DKIM = (New-ExoRequest -tenantid $tenant -cmdlet 'Get-DkimSigningConfig') | Where-Object { $_.Selector1KeySize -Eq 1024 -and $_.Enabled -eq $true } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 new file mode 100644 index 000000000000..712772b92b1d --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 @@ -0,0 +1,66 @@ +function Invoke-CIPPStandardSPAzureB2B { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) SPAzureB2B + .SYNOPSIS + (Label) Enable SharePoint and OneDrive integration with Azure AD B2B + .DESCRIPTION + (Helptext) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled + (DocsDescription) Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled + .NOTES + CAT + SharePoint Standards + TAG + "lowimpact" + "CIS" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-SPOTenant -EnableAzureADB2BIntegration $true + RECOMMENDEDBY + "CIS 3.0" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param($Tenant, $Settings) + $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | + Select-Object -Property EnableAzureADB2BIntegration + + $StateIsCorrect = ($CurrentState.EnableAzureADB2BIntegration -eq $true) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Message 'SharePoint Azure B2B is already enabled' -Sev Info + } else { + $Properties = @{ + EnableAzureADB2BIntegration = $true + } + + try { + Get-CIPPSPOTenant -TenantFilter $Tenant | Set-CIPPSPOTenant -Properties $Properties + Write-LogMessage -API 'Standards' -Message 'Successfully set the SharePoint Azure B2B to enabled' -Sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -Message "Failed to set the SharePoint Azure B2B to enabled. Error: $ErrorMessage" -Sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Message 'SharePoint Azure B2B is enabled' -Sev Info + } else { + Write-LogMessage -API 'Standards' -Message 'SharePoint Azure B2B is not enabled' -Sev Alert + } + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'AzureB2B' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 new file mode 100644 index 000000000000..9678a7cf5719 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 @@ -0,0 +1,66 @@ +function Invoke-CIPPStandardSPDirectSharing { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) SPDirectSharing + .SYNOPSIS + (Label) Default sharing to Direct users + .DESCRIPTION + (Helptext) Ensure default link sharing is set to Direct in SharePoint and OneDrive + (DocsDescription) Ensure default link sharing is set to Direct in SharePoint and OneDrive + .NOTES + CAT + SharePoint Standards + TAG + "mediumimpact" + "CIS" + ADDEDCOMPONENT + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-SPOTenant -DefaultSharingLinkType Direct + RECOMMENDEDBY + "CIS 3.0" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param($Tenant, $Settings) + $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | + Select-Object -Property DefaultSharingLinkType + + $StateIsCorrect = ($CurrentState.DefaultSharingLinkType -eq 'Direct') + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Message 'SharePoint Sharing Restriction is already enabled' -Sev Info + } else { + $Properties = @{ + DefaultSharingLinkType = 1 + } + + try { + Get-CIPPSPOTenant -TenantFilter $Tenant | Set-CIPPSPOTenant -Properties $Properties + Write-LogMessage -API 'Standards' -Message 'Successfully set the SharePoint Sharing Restriction to Direct' -Sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -Message "Failed to set the SharePoint Sharing Restriction to Direct. Error: $ErrorMessage" -Sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Message 'SharePoint Sharing Restriction is enabled' -Sev Info + } else { + Write-LogMessage -API 'Standards' -Message 'SharePoint Sharing Restriction is not enabled' -Sev Alert + } + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'DirectSharing' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 new file mode 100644 index 000000000000..a01122e3c188 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 @@ -0,0 +1,67 @@ +function Invoke-CIPPStandardSPDisableLegacyWorkflows { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) SPDisableLegacyWorkflows + .SYNOPSIS + (Label) Disable Legacy Workflows + .DESCRIPTION + (Helptext) Disables the creation of new SharePoint 2010 and 2013 classic workflows and removes the 'Return to classic SharePoint' link on modern SharePoint list and library pages. + (DocsDescription) Disables the creation of new SharePoint 2010 and 2013 classic workflows and removes the 'Return to classic SharePoint' link on modern SharePoint list and library pages. + .NOTES + CAT + SharePoint Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-SPOTenant -DisableWorkflow2010 $true -DisableWorkflow2013 $true -DisableBackToClassic $true + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + param($Tenant, $Settings) + $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | + Select-Object -Property * + + $StateIsCorrect = ($CurrentState.StopNew2010Workflows -eq $true) -and + ($CurrentState.StopNew2013Workflows -eq $true) -and + ($CurrentState.DisableBackToClassic -eq $true) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Message 'Legacy Workflows are already disabled.' -Sev Info + } else { + $Properties = @{ + StopNew2010Workflows = $true + StopNew2013Workflows = $true + DisableBackToClassic = $true + } + + try { + Get-CIPPSPOTenant -TenantFilter $Tenant | Set-CIPPSPOTenant -Properties $Properties + Write-LogMessage -API 'Standards' -Message 'Successfully disabled Legacy Workflows' -Sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -Message "Failed to disable Legacy Workflows. Error: $ErrorMessage" -Sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Message 'Legacy Workflows are disabled' -Sev Info + } else { + Write-LogMessage -API 'Standards' -Message 'Legacy Workflows are enabled' -Sev Info + } + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'SPDisableLegacyWorkflows' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 new file mode 100644 index 000000000000..f4103cf7268b --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 @@ -0,0 +1,66 @@ +function Invoke-CIPPStandardSPDisallowInfectedFiles { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) SPDisallowInfectedFiles + .SYNOPSIS + (Label) Disallow downloading infected files from SharePoint + .DESCRIPTION + (Helptext) Ensure Office 365 SharePoint infected files are disallowed for download + (DocsDescription) Ensure Office 365 SharePoint infected files are disallowed for download + .NOTES + CAT + SharePoint Standards + TAG + "lowimpact" + "CIS" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-SPOTenant -DisallowInfectedFileDownload $true + RECOMMENDEDBY + "CIS 3.0" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param($Tenant, $Settings) + $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | + Select-Object -Property DisallowInfectedFileDownload + + $StateIsCorrect = ($CurrentState.DisallowInfectedFileDownload -eq $true) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Message 'Downloading Sharepoint infected files are already disallowed.' -Sev Info + } else { + $Properties = @{ + DisallowInfectedFileDownload = $true + } + + try { + Get-CIPPSPOTenant -TenantFilter $Tenant | Set-CIPPSPOTenant -Properties $Properties + Write-LogMessage -API 'Standards' -Message 'Successfully disallowed downloading SharePoint infected files.' -Sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -Message "Failed to disallow downloading Sharepoint infected files. Error: $ErrorMessage" -Sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Message 'Downloading Sharepoint infected files are disallowed.' -Sev Info + } else { + Write-LogMessage -API 'Standards' -Message 'Downloading Sharepoint infected files are allowed.' -Sev Alert + } + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'SPDisallowInfectedFiles' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 new file mode 100644 index 000000000000..733168b7c98e --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 @@ -0,0 +1,69 @@ +function Invoke-CIPPStandardSPEmailAttestation { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) SPEmailAttestation + .SYNOPSIS + (Label) Require reauthentication with verification code + .DESCRIPTION + (Helptext) Ensure reauthentication with verification code is restricted + (DocsDescription) Ensure reauthentication with verification code is restricted + .NOTES + CAT + SharePoint Standards + TAG + "mediumimpact" + "CIS" + ADDEDCOMPONENT + {"type":"number","name":"standards.SPEmailAttestation.Days","label":"Require reauth every X Days (Default 15)"} + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-SPOTenant -EmailAttestationRequired $true -EmailAttestationReAuthDays 15 + RECOMMENDEDBY + "CIS 3.0" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param($Tenant, $Settings) + $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | + Select-Object -Property EmailAttestationReAuthDays, EmailAttestationRequired + + $StateIsCorrect = ($CurrentState.EmailAttestationReAuthDays -eq $Settings.Days) -and + ($CurrentState.EmailAttestationRequired -eq $true) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Message 'Sharepoint reauthentication with verification code is already restriction.' -Sev Info + } else { + $Properties = @{ + EmailAttestationReAuthDays = $Settings.Days + EmailAttestationRequired = $true + } + + try { + Get-CIPPSPOTenant -TenantFilter $Tenant | Set-CIPPSPOTenant -Properties $Properties + Write-LogMessage -API 'Standards' -Message 'Successfully set reauthentication with verification code restriction.' -Sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -Message "Failed to set reauthentication with verification code restriction. Error: $ErrorMessage" -Sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Message 'Reauthentication with verification code is restriction' -Sev Info + } else { + Write-LogMessage -API 'Standards' -Message 'Reauthentication with verification code is not restricted' -Sev Alert + } + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'SPEmailAttestation' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 new file mode 100644 index 000000000000..5d7b40a65c14 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 @@ -0,0 +1,69 @@ +function Invoke-CIPPStandardSPExternalUserExpiration { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) SPExternalUserExpiration + .SYNOPSIS + (Label) Set guest access to expire automatically + .DESCRIPTION + (Helptext) Ensure guest access to a site or OneDrive will expire automatically + (DocsDescription) Ensure guest access to a site or OneDrive will expire automatically + .NOTES + CAT + SharePoint Standards + TAG + "mediumimpact" + "CIS" + ADDEDCOMPONENT + {"type":"number","name":"standards.SPExternalUserExpiration.Days","label":"Days until expiration (Default 60)"} + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-SPOTenant -ExternalUserExpireInDays 30 -ExternalUserExpirationRequired $True + RECOMMENDEDBY + "CIS 3.0" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param($Tenant, $Settings) + $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | + Select-Object -Property ExternalUserExpireInDays, ExternalUserExpirationRequired + + $StateIsCorrect = ($CurrentState.ExternalUserExpireInDays -eq $Settings.Days) -and + ($CurrentState.ExternalUserExpirationRequired -eq $true) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Message 'Sharepoint External User Expiration is already enabled.' -Sev Info + } else { + $Properties = @{ + ExternalUserExpireInDays = $Settings.Days + ExternalUserExpirationRequired = $true + } + + try { + Get-CIPPSPOTenant -TenantFilter $Tenant | Set-CIPPSPOTenant -Properties $Properties + Write-LogMessage -API 'Standards' -Message 'Successfully set External User Expiration' -Sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -Message "Failed to set External User Expiration. Error: $ErrorMessage" -Sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Message 'External User Expiration is enabled' -Sev Info + } else { + Write-LogMessage -API 'Standards' -Message 'External User Expiration is not enabled' -Sev Alert + } + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'ExternalUserExpiration' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 index bad693702eb5..26cb8e67ad79 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 @@ -1,9 +1,40 @@ function Invoke-CIPPStandardSafeAttachmentPolicy { <# .FUNCTIONALITY - Internal - #> - + Internal + .COMPONENT + (APIName) SafeAttachmentPolicy + .SYNOPSIS + (Label) Default Safe Attachment Policy + .DESCRIPTION + (Helptext) This creates a Safe Attachment policy + (DocsDescription) This creates a Safe Attachment policy + .NOTES + CAT + Defender Standards + TAG + "lowimpact" + "CIS" + "mdo_safedocuments" + "mdo_commonattachmentsfilter" + "mdo_safeattachmentpolicy" + ADDEDCOMPONENT + {"type":"Select","label":"Action","name":"standards.SafeAttachmentPolicy.Action","values":[{"label":"Allow","value":"Allow"},{"label":"Block","value":"Block"},{"label":"DynamicDelivery","value":"DynamicDelivery"}]} + {"type":"Select","label":"QuarantineTag","name":"standards.SafeAttachmentPolicy.QuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + {"type":"boolean","label":"Redirect","name":"standards.SafeAttachmentPolicy.Redirect"} + {"type":"input","name":"standards.SafeAttachmentPolicy.RedirectAddress","label":"Redirect Address"} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-SafeAttachmentPolicy or New-SafeAttachmentPolicy + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + param($Tenant, $Settings) $PolicyName = 'Default Safe Attachment Policy' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 index 1e68ef5a2475..ea21cb1ac464 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 @@ -1,9 +1,38 @@ function Invoke-CIPPStandardSafeLinksPolicy { <# .FUNCTIONALITY - Internal - #> - + Internal + .COMPONENT + (APIName) SafeLinksPolicy + .SYNOPSIS + (Label) Default SafeLinks Policy + .DESCRIPTION + (Helptext) This creates a safelink policy that automatically scans, tracks, and and enables safe links for Email, Office, and Teams for both external and internal senders + (DocsDescription) This creates a safelink policy that automatically scans, tracks, and and enables safe links for Email, Office, and Teams for both external and internal senders + .NOTES + CAT + Defender Standards + TAG + "lowimpact" + "CIS" + "mdo_safelinksforemail" + "mdo_safelinksforOfficeApps" + ADDEDCOMPONENT + {"type":"boolean","label":"AllowClickThrough","name":"standards.SafeLinksPolicy.AllowClickThrough"} + {"type":"boolean","label":"DisableUrlRewrite","name":"standards.SafeLinksPolicy.DisableUrlRewrite"} + {"type":"boolean","label":"EnableOrganizationBranding","name":"standards.SafeLinksPolicy.EnableOrganizationBranding"} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-SafeLinksPolicy or New-SafeLinksPolicy + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + param($Tenant, $Settings) $PolicyName = 'Default SafeLinks Policy' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 index d70fe30cef99..e13957c32d89 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 @@ -1,8 +1,33 @@ function Invoke-CIPPStandardSafeSendersDisable { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) SafeSendersDisable + .SYNOPSIS + (Label) Remove Safe Senders to prevent SPF bypass + .DESCRIPTION + (Helptext) Loops through all users and removes the Safe Senders list. This is to prevent SPF bypass attacks, as the Safe Senders list is not checked by SPF. + (DocsDescription) Loops through all users and removes the Safe Senders list. This is to prevent SPF bypass attacks, as the Safe Senders list is not checked by SPF. + .NOTES + CAT + Exchange Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + DISABLEDFEATURES + + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-MailboxJunkEmailConfiguration + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) If ($Settings.remediate -eq $true) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecurityDefaults.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecurityDefaults.ps1 index 07bc25df5021..b6982ad21a02 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecurityDefaults.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecurityDefaults.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardSecurityDefaults { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) SecurityDefaults + .SYNOPSIS + (Label) Enable Security Defaults + .DESCRIPTION + (Helptext) Enables security defaults for the tenant, for newer tenants this is enabled by default. Do not enable this feature if you use Conditional Access. + (DocsDescription) Enables SD for the tenant, which disables all forms of basic authentication and enforces users to configure MFA. Users are only prompted for MFA when a logon is considered 'suspect' by Microsoft. + .NOTES + CAT + Entra (AAD) Standards + TAG + "highimpact" + ADDEDCOMPONENT + IMPACT + High Impact + POWERSHELLEQUIVALENT + [Read more here](https://www.cyberdrain.com/automating-with-powershell-enabling-secure-defaults-and-sd-explained/) + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $SecureDefaultsState = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -tenantid $tenant) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 index ce7d56f76454..d50ad6c5fcfd 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardSendFromAlias { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) SendFromAlias + .SYNOPSIS + (Label) Allow users to send from their alias addresses + .DESCRIPTION + (Helptext) Enables the ability for users to send from their alias addresses. + (DocsDescription) Allows users to change the 'from' address to any set in their Azure AD Profile. + .NOTES + CAT + Exchange Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-Mailbox + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').SendFromAliasEnabled diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 index 68c7519f5e25..6345e322e422 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 @@ -1,18 +1,43 @@ function Invoke-CIPPStandardSendReceiveLimitTenant { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) SendReceiveLimitTenant + .SYNOPSIS + (Label) Set send/receive size limits + .DESCRIPTION + (Helptext) Sets the Send and Receive limits for new users. Valid values are 1MB to 150MB + (DocsDescription) Sets the Send and Receive limits for new users. Valid values are 1MB to 150MB + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"number","name":"standards.SendReceiveLimitTenant.SendLimit","label":"Send limit in MB (Default is 35)","default":35} + {"type":"number","name":"standards.SendReceiveLimitTenant.ReceiveLimit","label":"Receive Limit in MB (Default is 36)","default":36} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-MailboxPlan + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) # Input validation - if ($Settings.SendLimit -lt 1 -or $Settings.SendLimit -gt 150) { + if ([Int32]$Settings.SendLimit -lt 1 -or [Int32]$Settings.SendLimit -gt 150) { Write-LogMessage -API 'Standards' -tenant $tenant -message 'SendReceiveLimitTenant: Invalid SendLimit parameter set' -sev Error Return } # Input validation - if ($Settings.ReceiveLimit -lt 1 -or $Settings.ReceiveLimit -gt 150) { + if ([Int32]$Settings.ReceiveLimit -lt 1 -or [Int32]$Settings.ReceiveLimit -gt 150) { Write-LogMessage -API 'Standards' -tenant $tenant -message 'SendReceiveLimitTenant: Invalid ReceiveLimit parameter set' -sev Error Return } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 index f6605904777f..82b784d75fd8 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardShortenMeetings { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) ShortenMeetings + .SYNOPSIS + (Label) Set shorten meetings state + .DESCRIPTION + (Helptext) Sets the shorten meetings settings on a tenant level. This will shorten meetings by the selected amount of minutes. Valid values are 0 to 29. Short meetings are under 60 minutes, long meetings are over 60 minutes. + (DocsDescription) Sets the shorten meetings settings on a tenant level. This will shorten meetings by the selected amount of minutes. Valid values are 0 to 29. Short meetings are under 60 minutes, long meetings are over 60 minutes. + .NOTES + CAT + Exchange Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.ShortenMeetings.ShortenEventScopeDefault","values":[{"label":"Disabled/None","value":"None"},{"label":"End early","value":"EndEarly"},{"label":"Start late","value":"StartLate"}]} + {"type":"number","name":"standards.ShortenMeetings.DefaultMinutesToReduceShortEventsBy","label":"Minutes to reduce short calendar events by (Default is 5)","default":5} + {"type":"number","name":"standards.ShortenMeetings.DefaultMinutesToReduceLongEventsBy","label":"Minutes to reduce long calendar events by (Default is 10)","default":10} + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Set-OrganizationConfig -ShortenEventScopeDefault -DefaultMinutesToReduceShortEventsBy -DefaultMinutesToReduceLongEventsBy + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) # Input validation diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 new file mode 100644 index 000000000000..4357e0ae7ba0 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 @@ -0,0 +1,171 @@ +function Invoke-CIPPStandardSpamFilterPolicy { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) SpamFilterPolicy + .SYNOPSIS + (Label) Default Spam Filter Policy + .DESCRIPTION + (Helptext) This standard creates a Spam filter policy similar to the default strict policy. + (DocsDescription) This standard creates a Spam filter policy similar to the default strict policy. + .NOTES + CAT + Defender Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + {"type":"Select","label":"Spam Action","name":"standards.SpamFilterPolicy.SpamAction","values":[{"label":"Move message to Junk Email folder","value":"MoveToJmf"},{"label":"Quarantine the message","value":"Quarantine"}]} + {"type":"Select","label":"Spam Quarantine Tag","name":"standards.SpamFilterPolicy.SpamQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + {"type":"Select","label":"High Confidence Spam Quarantine Tag","name":"standards.SpamFilterPolicy.HighConfidenceSpamQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + {"type":"Select","label":"Bulk Quarantine Tag","name":"standards.SpamFilterPolicy.BulkQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + {"type":"Select","label":"Phish Quarantine Tag","name":"standards.SpamFilterPolicy.PhishQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + {"type":"Select","label":"High Confidence Phish Quarantine Tag","name":"standards.SpamFilterPolicy.HighConfidencePhishQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + New-HostedContentFilterPolicy or Set-HostedContentFilterPolicy + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param($Tenant, $Settings) + $PolicyName = 'CIPP Default Spam Filter Policy' + + $CurrentState = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-HostedContentFilterPolicy' | + Where-Object -Property Name -EQ $PolicyName | + Select-Object -Property * + + $StateIsCorrect = ($CurrentState.Name -eq $PolicyName) -and + ($CurrentState.HighConfidenceSpamAction -eq 'Quarantine') -and + ($CurrentState.HighConfidenceSpamQuarantineTag -eq $Settings.HighConfidenceSpamQuarantineTag) -and + ($CurrentState.SpamAction -eq $Settings.SpamAction) -and + ($CurrentState.SpamQuarantineTag -eq $Settings.SpamQuarantineTag) -and + ($CurrentState.PhishSpamAction -eq 'MoveToJmf') -and + ($CurrentState.BulkSpamAction -eq 'MoveToJmf') -and + ($CurrentState.BulkQuarantineTag -eq $Settings.BulkQuarantineTag) -and + ($CurrentState.PhishQuarantineTag -eq $Settings.PhishQuarantineTag) -and + ($CurrentState.HighConfidencePhishAction -eq 'Quarantine') -and + ($CurrentState.HighConfidencePhishQuarantineTag -eq $Settings.HighConfidencePhishQuarantineTag) -and + ($CurrentState.BulkThreshold -eq 7) -and + ($CurrentState.QuarantineRetentionPeriod -eq 30) -and + ($CurrentState.IncreaseScoreWithNumericIps -eq 'On') -and + ($CurrentState.IncreaseScoreWithRedirectToOtherPort -eq 'On') -and + ($CurrentState.MarkAsSpamEmptyMessages -eq 'On') -and + ($CurrentState.MarkAsSpamJavaScriptInHtml -eq 'On') -and + ($CurrentState.MarkAsSpamSpfRecordHardFail -eq 'On') -and + ($CurrentState.MarkAsSpamFromAddressAuthFail -eq 'On') -and + ($CurrentState.MarkAsSpamNdrBackscatter -eq 'On') -and + ($CurrentState.MarkAsSpamBulkMail -eq 'On') -and + ($CurrentState.InlineSafetyTipsEnabled -eq $true) -and + ($CurrentState.PhishZapEnabled -eq $true) -and + ($CurrentState.SpamZapEnabled -eq $true) + + $AcceptedDomains = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-AcceptedDomain' + + $RuleState = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-HostedContentFilterRule' | + Where-Object -Property Name -EQ $PolicyName | + Select-Object -Property * + + $RuleStateIsCorrect = ($RuleState.Name -eq $PolicyName) -and + ($RuleState.HostedContentFilterPolicy -eq $PolicyName) -and + ($RuleState.Priority -eq 0) -and + (!(Compare-Object -ReferenceObject $RuleState.RecipientDomainIs -DifferenceObject $AcceptedDomains.Name)) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Tenant $Tenant -message 'Spam Filter Policy already correctly configured' -sev Info + } else { + $cmdparams = @{ + HighConfidenceSpamAction = 'Quarantine' + HighConfidenceSpamQuarantineTag = $Settings.HighConfidenceSpamQuarantineTag + SpamAction = $Settings.SpamAction + SpamQuarantineTag = $Settings.SpamQuarantineTag + PhishSpamAction = 'MoveToJmf' + BulkSpamAction = 'MoveToJmf' + BulkQuarantineTag = $Settings.BulkQuarantineTag + PhishQuarantineTag = $Settings.PhishQuarantineTag + HighConfidencePhishAction = 'Quarantine' + HighConfidencePhishQuarantineTag = $Settings.HighConfidencePhishQuarantineTag + BulkThreshold = 7 + QuarantineRetentionPeriod = 30 + IncreaseScoreWithNumericIps = 'On' + IncreaseScoreWithRedirectToOtherPort= 'On' + MarkAsSpamEmptyMessages = 'On' + MarkAsSpamJavaScriptInHtml = 'On' + MarkAsSpamSpfRecordHardFail = 'On' + MarkAsSpamFromAddressAuthFail = 'On' + MarkAsSpamNdrBackscatter = 'On' + MarkAsSpamBulkMail = 'On' + InlineSafetyTipsEnabled = $true + PhishZapEnabled = $true + SpamZapEnabled = $true + } + + if ($CurrentState.Name -eq $PolicyName) { + try { + $cmdparams.Add('Identity', $PolicyName) + New-ExoRequest -TenantId $Tenant -cmdlet 'Set-HostedContentFilterPolicy' -cmdparams $cmdparams -UseSystemMailbox $true + Write-LogMessage -API 'Standards' -Tenant $Tenant -message 'Updated Spam Filter Policy' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -Tenant $Tenant -message "Failed to update Spam Filter Policy. Error: $ErrorMessage" -sev Error + } + } else { + try { + $cmdparams.Add('Name', $PolicyName) + New-ExoRequest -TenantId $Tenant -cmdlet 'New-HostedContentFilterPolicy' -cmdparams $cmdparams -UseSystemMailbox $true + Write-LogMessage -API 'Standards' -Tenant $Tenant -message 'Created Spam Filter Policy' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -Tenant $Tenant -message "Failed to create Spam Filter Policy. Error: $ErrorMessage" -sev Error + } + } + } + + if ($RuleStateIsCorrect -eq $false) { + $cmdparams = @{ + HostedContentFilterPolicy = $PolicyName + Priority = 0 + RecipientDomainIs = $AcceptedDomains.Name + } + + if ($RuleState.Name -eq $PolicyName) { + try { + $cmdparams.Add('Identity', "$PolicyName") + New-ExoRequest -TenantId $Tenant -cmdlet 'Set-HostedContentFilterRule' -cmdparams $cmdparams -UseSystemMailbox $true + Write-LogMessage -API 'Standards' -Tenant $Tenant -message 'Updated Spam Filter Rule' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -Tenant $Tenant -message "Failed to update Spam Filter Rule. Error: $ErrorMessage" -sev Error + } + } else { + try { + $cmdparams.Add('Name', "$PolicyName") + New-ExoRequest -TenantId $Tenant -cmdlet 'New-HostedContentFilterRule' -cmdparams $cmdparams -UseSystemMailbox $true + Write-LogMessage -API 'Standards' -Tenant $Tenant -message 'Created Spam Filter Rule' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -Tenant $Tenant -message "Failed to create Spam Filter Rule. Error: $ErrorMessage" -sev Error + } + } + } + } + + if ($Settings.alert -eq $true) { + + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Tenant $Tenant -message 'Spam Filter Policy is enabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -Tenant $Tenant -message 'Spam Filter Policy is not enabled' -sev Alert + } + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'SpamFilterPolicy' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant + } + +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 index af9e03c6122c..9f780f3aa60e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardSpoofWarn { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) SpoofWarn + .SYNOPSIS + (Label) Enable or disable 'external' warning in Outlook + .DESCRIPTION + (Helptext) Adds or removes indicators to e-mail messages received from external senders in Outlook. Works on all Outlook clients/OWA + (DocsDescription) Adds or removes indicators to e-mail messages received from external senders in Outlook. You can read more about this feature on [Microsoft's Exchange Team Blog.](https://techcommunity.microsoft.com/t5/exchange-team-blog/native-external-sender-callouts-on-email-in-outlook/ba-p/2250098) + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + "CIS" + ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.SpoofWarn.state","values":[{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}]} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + et-ExternalInOutlook –Enabled $true or $false + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-ExternalInOutlook') diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 index ce9a3c95ef62..b1598efd7ad1 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 @@ -1,8 +1,32 @@ function Invoke-CIPPStandardTAP { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) TAP + .SYNOPSIS + (Label) Enable Temporary Access Passwords + .DESCRIPTION + (Helptext) Enables TAP and sets the default TAP lifetime to 1 hour. This configuration also allows you to select is a TAP is single use or multi-logon. + (DocsDescription) Enables Temporary Password generation for the tenant. + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"Select","label":"Select TAP Lifetime","name":"standards.TAP.config","values":[{"label":"Only Once","value":"true"},{"label":"Multiple Logons","value":"false"}]} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/TemporaryAccessPass' -tenantid $Tenant @@ -13,7 +37,7 @@ function Invoke-CIPPStandardTAP { } # Input validation - if (([string]::IsNullOrWhiteSpace($Settings.state) -or $Settings.state -eq 'Select a value') -and ($Settings.remediate -eq $true -or $Settings.alert -eq $true)) { + if (([string]::IsNullOrWhiteSpace($Settings.config) -or $Settings.config -eq 'Select a value') -and ($Settings.remediate -eq $true -or $Settings.alert -eq $true)) { Write-LogMessage -API 'Standards' -tenant $tenant -message 'TAP: Invalid state parameter set' -sev Error Return } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 index 615dd6e94237..aa4920153c3f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 @@ -1,8 +1,32 @@ function Invoke-CIPPStandardTeamsMeetingsByDefault { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) TeamsMeetingsByDefault + .SYNOPSIS + (Label) Set Teams Meetings by default state + .DESCRIPTION + (Helptext) Sets the default state for automatically turning meetings into Teams meetings for the tenant. This can be overridden by the user in Outlook. + (DocsDescription) Sets the default state for automatically turning meetings into Teams meetings for the tenant. This can be overridden by the user in Outlook. + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.TeamsMeetingsByDefault.state","values":[{"label":"Enabled","value":"true"},{"label":"Disabled","value":"false"}]} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-OrganizationConfig -OnlineMeetingsByDefaultEnabled + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentState = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').OnlineMeetingsByDefaultEnabled diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 index c2651ae346a9..a7012b7c1cc2 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 @@ -1,7 +1,30 @@ function Invoke-CIPPStandardTenantDefaultTimezone { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) TenantDefaultTimezone + .SYNOPSIS + (Label) Set Default Timezone for Tenant + .DESCRIPTION + (Helptext) Sets the default timezone for the tenant. This will be used for all new users and sites. + (DocsDescription) Sets the default timezone for the tenant. This will be used for all new users and sites. + .NOTES + CAT + SharePoint Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"TimezoneSelect","name":"standards.TenantDefaultTimezone.Timezone","label":"Timezone"} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgBetaAdminSharepointSetting + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> param($Tenant, $Settings) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUndoOauth.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUndoOauth.ps1 index 51cef2225307..3d546d2fc76a 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUndoOauth.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUndoOauth.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardUndoOauth { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) UndoOauth + .SYNOPSIS + (Label) Undo App Consent Standard + .DESCRIPTION + (Helptext) Disables App consent and set to Allow user consent for apps + (DocsDescription) Disables App consent and set to Allow user consent for apps + .NOTES + CAT + Entra (AAD) Standards + TAG + "highimpact" + ADDEDCOMPONENT + IMPACT + High Impact + POWERSHELLEQUIVALENT + Update-MgPolicyAuthorizationPolicy + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentState = New-GraphGetRequest -tenantid $Tenant -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy?$select=permissionGrantPolicyIdsAssignedToDefaultUserRole' $State = if ($CurrentState.permissionGrantPolicyIdsAssignedToDefaultUserRole -eq 'ManagePermissionGrantsForSelf.microsoft-user-default-legacy') { $true } else { $false } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1 new file mode 100644 index 000000000000..810de7f28a52 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1 @@ -0,0 +1,78 @@ +function Invoke-CIPPStandardUserReportDestinationEmail { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) UserReportDestinationEmail + .SYNOPSIS + (Label) Set the destination email for user reported emails + .DESCRIPTION + (Helptext) Sets the destination for email when users report them as spam or phishing. Works well together with the 'Set the state of the built-in Report button in Outlook standard'. + (DocsDescription) Sets the destination for email when users report them as spam or phishing. Works well together with the 'Set the state of the built-in Report button in Outlook standard'. + .NOTES + CAT + Exchange Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + {"type":"input","name":"standards.UserReportDestinationEmail.Email","label":"Destination email address"} + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + New-ReportSubmissionRule or Set-ReportSubmissionRule + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + + param($Tenant, $Settings) + + # Input validation + if (([string]::IsNullOrWhiteSpace($Settings.Email) -or $Settings.Email -eq 'Select a value' -or $Settings.Email -notmatch '@') -and + ($Settings.remediate -eq $true -or $Settings.alert -eq $true)) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'UserReportDestinationEmail: Invalid Email parameter set' -sev Error + Return + } + + $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-ReportSubmissionRule' + $StateIsCorrect = if ($CurrentState.SentTo -eq $Settings.Email) { $true } else { $false } + + # Write-Host 'Current State:' + # Write-Host (ConvertTo-Json -InputObject $CurrentState -Depth 5) + # Write-Host 'State is correct: ' $StateIsCorrect + + If ($Settings.remediate -eq $true) { + Write-Host 'Time to remediate!' + + if ($StateIsCorrect -eq $false) { + try { + if ($null -eq $CurrentState) { + New-ExoRequest -tenantid $Tenant -cmdlet 'New-ReportSubmissionRule' -cmdParams @{ Name = 'DefaultReportSubmissionRule'; ReportSubmissionPolicy = 'DefaultReportSubmissionPolicy'; SentTo = ($Settings.Email.Trim()); } -UseSystemMailbox $true + Write-LogMessage -API 'Standards' -tenant $tenant -message "User Report Destination Email set to $($Settings.Email)." -sev Info + } else { + New-ExoRequest -tenantid $Tenant -cmdlet 'Set-ReportSubmissionRule' -cmdParams @{ Identity = $CurrentState.Identity; SentTo = ($Settings.Email.Trim()) } -UseSystemMailbox $true + Write-LogMessage -API 'Standards' -tenant $tenant -message "User Report Destination Email set to $($Settings.Email)." -sev Info + } + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $tenant -message "Could not set User Report Destination Email to $($Settings.Email). Error: $ErrorMessage" -sev Error + } + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message "User Report Destination Email is already set to $($Settings.Email)." -sev Info + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "User Report Destination Email is set to $($Settings.Email)." -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message "User Report Destination Email is not set to $($Settings.Email)." -sev Alert + } + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'UserReportDestinationEmail' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant + } +} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 index 0d20abaeb57a..9fec83930945 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 @@ -1,8 +1,32 @@ function Invoke-CIPPStandardUserSubmissions { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) UserSubmissions + .SYNOPSIS + (Label) Set the state of the built-in Report button in Outlook + .DESCRIPTION + (Helptext) Set the state of the spam submission button in Outlook + (DocsDescription) Set the state of the built-in Report button in Outlook. This gives the users the ability to report emails as spam or phish. + .NOTES + CAT + Exchange Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.UserSubmissions.state","values":[{"label":"Enabled","value":"enable"},{"label":"Disabled","value":"disable"}]} + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + New-ReportSubmissionPolicy or Set-ReportSubmissionPolicy + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $Policy = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-ReportSubmissionPolicy' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOAuthTokens.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOAuthTokens.ps1 index a5f43f175998..93a53ee99758 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOAuthTokens.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOAuthTokens.ps1 @@ -1,25 +1,36 @@ function Invoke-CIPPStandardallowOAuthTokens { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) allowOAuthTokens + .SYNOPSIS + (Label) Enable OTP Software OAuth tokens + .DESCRIPTION + (Helptext) Allows you to use any software OAuth token generator + (DocsDescription) Enables OTP Software OAuth tokens for the tenant. This allows users to use OTP codes generated via software, like a password manager to be used as an authentication method. + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/softwareOath' -tenantid $Tenant $State = if ($CurrentInfo.state -eq 'enabled') { $true } else { $false } - if ($Settings.report -eq $true) { - Add-CIPPBPAField -FieldName 'softwareOath' -FieldValue $State -StoreAs bool -Tenant $tenant - } - - # Input validation - if (([string]::IsNullOrWhiteSpace($Settings.state) -or $Settings.state -eq 'Select a value') -and ($Settings.remediate -eq $true -or $Settings.alert -eq $true)) { - Write-LogMessage -API 'Standards' -tenant $tenant -message 'allowOAuthTokens: Invalid state parameter set' -sev Error - Return - } - - - If ($Settings.remediate -eq $true) { if ($State) { Write-LogMessage -API 'Standards' -tenant $tenant -message 'Software OTP/oAuth tokens is already enabled.' -sev Info @@ -37,5 +48,8 @@ function Invoke-CIPPStandardallowOAuthTokens { } } + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'softwareOath' -FieldValue $State -StoreAs bool -Tenant $tenant + } } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOTPTokens.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOTPTokens.ps1 index 8459fce1aadc..50890ca483d5 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOTPTokens.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOTPTokens.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardallowOTPTokens { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) allowOTPTokens + .SYNOPSIS + (Label) Enable OTP via Authenticator + .DESCRIPTION + (Helptext) Allows you to use MS authenticator OTP token generator + (DocsDescription) Allows you to use Microsoft Authenticator OTP token generator. Useful for using the NPS extension as MFA on VPN clients. + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator' -tenantid $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 index 491d35ab7eb5..d3609ffc7f11 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardcalDefault { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) calDefault + .SYNOPSIS + (Label) Set Sharing Level for Default calendar + .DESCRIPTION + (Helptext) Sets the default sharing level for the default calendar, for all users + (DocsDescription) Sets the default sharing level for the default calendar for all users in the tenant. You can read about the different sharing levels [here.](https://learn.microsoft.com/en-us/powershell/module/exchange/set-mailboxfolderpermission?view=exchange-ps#-accessrights) + .NOTES + CAT + Exchange Standards + TAG + "lowimpact" + DISABLEDFEATURES + + ADDEDCOMPONENT + {"type":"Select","label":"Select Sharing Level","name":"standards.calDefault.permissionlevel","values":[{"label":"Owner - The user can create, read, edit, and delete all items in the folder, and create subfolders. The user is both folder owner and folder contact.","value":"Owner"},{"label":"Publishing Editor - The user can create, read, edit, and delete all items in the folder, and create subfolders.","value":"PublishingEditor"},{"label":"Editor - The user can create items in the folder. The contents of the folder do not appear.","value":"Editor"},{"label":"Publishing Author. The user can read, create all items/subfolders. Can modify and delete only items they create.","value":"PublishingAuthor"},{"label":"Author - The user can create and read items, and modify and delete items that they create.","value":"Author"},{"label":"Non Editing Author - The user has full read access and create items. Can can delete only own items.","value":"NonEditingAuthor"},{"label":"Reviewer - The user can read all items in the folder.","value":"Reviewer"},{"label":"Contributor - The user can create items and folders.","value":"Contributor"},{"label":"Availability Only - Indicates that the user can view only free/busy time within the calendar.","value":"AvailabilityOnly"},{"label":"Limited Details - The user can view free/busy time within the calendar and the subject and location of appointments.","value":"LimitedDetails"},{"label":"None - The user has no permissions on the folder.","value":"none"}]} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Set-MailboxFolderPermission + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings, $QueueItem) # Input validation diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 index b096ade25384..cc887e54f176 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandarddisableMacSync { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) disableMacSync + .SYNOPSIS + (Label) Do not allow Mac devices to sync using OneDrive + .DESCRIPTION + (Helptext) Disables the ability for Mac devices to sync with OneDrive. + (DocsDescription) Disables the ability for Mac devices to sync with OneDrive. + .NOTES + CAT + SharePoint Standards + TAG + "highimpact" + ADDEDCOMPONENT + IMPACT + High Impact + POWERSHELLEQUIVALENT + Update-MgAdminSharepointSetting + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 index c049ebb95749..7d3591aeb1c9 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 @@ -1,9 +1,41 @@ function Invoke-CIPPStandardintuneBrandingProfile { <# .FUNCTIONALITY - Internal - #> - + Internal + .COMPONENT + (APIName) intuneBrandingProfile + .SYNOPSIS + (Label) Set Intune Company Portal branding profile + .DESCRIPTION + (Helptext) Sets the branding profile for the Intune Company Portal app. This is a tenant wide setting and overrules any settings set on the app level. + (DocsDescription) Sets the branding profile for the Intune Company Portal app. This is a tenant wide setting and overrules any settings set on the app level. + .NOTES + CAT + Intune Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"input","name":"standards.intuneBrandingProfile.displayName","label":"Organization name"} + {"type":"boolean","name":"standards.intuneBrandingProfile.showLogo","label":"Show logo"} + {"type":"boolean","name":"standards.intuneBrandingProfile.showDisplayNameNextToLogo","label":"Show organization name next to logo"} + {"type":"input","name":"standards.intuneBrandingProfile.contactITName","label":"Contact IT name"} + {"type":"input","name":"standards.intuneBrandingProfile.contactITPhoneNumber","label":"Contact IT phone number"} + {"type":"input","name":"standards.intuneBrandingProfile.contactITEmailAddress","label":"Contact IT email address"} + {"type":"input","name":"standards.intuneBrandingProfile.contactITNotes","label":"Contact IT notes"} + {"type":"input","name":"standards.intuneBrandingProfile.onlineSupportSiteName","label":"Online support site name"} + {"type":"input","name":"standards.intuneBrandingProfile.onlineSupportSiteUrl","label":"Online support site URL"} + {"type":"input","name":"standards.intuneBrandingProfile.privacyUrl","label":"Privacy statement URL"} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Graph API + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + param($Tenant, $Settings) $CurrentState = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/deviceManagement/intuneBrandingProfiles/c3a59481-1bf2-46ce-94b3-66eec07a8d60/' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 index e261dbfb859c..120655ef7368 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 @@ -1,8 +1,32 @@ function Invoke-CIPPStandardintuneDeviceReg { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) intuneDeviceReg + .SYNOPSIS + (Label) Set Maximum Number of Devices per user + .DESCRIPTION + (Helptext) sets the maximum number of devices that can be registered by a user. A value of 0 disables device registration by users + (DocsDescription) sets the maximum number of devices that can be registered by a user. A value of 0 disables device registration by users + .NOTES + CAT + Intune Standards + TAG + "mediumimpact" + ADDEDCOMPONENT + {"type":"number","name":"standards.intuneDeviceReg.max","label":"Maximum devices (Enter 2147483647 for unlimited.)"} + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyDeviceRegistrationPolicy + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $PreviousSetting = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy' -tenantid $Tenant $StateIsCorrect = if ($PreviousSetting.userDeviceQuota -eq $Settings.max) { $true } else { $false } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 index b150c84e2f0a..b745d4001823 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 @@ -1,8 +1,32 @@ function Invoke-CIPPStandardintuneDeviceRetirementDays { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) intuneDeviceRetirementDays + .SYNOPSIS + (Label) Set inactive device retirement days + .DESCRIPTION + (Helptext) A value between 0 and 270 is supported. A value of 0 disables retirement, retired devices are removed from Intune after the specified number of days. + (DocsDescription) A value between 0 and 270 is supported. A value of 0 disables retirement, retired devices are removed from Intune after the specified number of days. + .NOTES + CAT + Intune Standards + TAG + "lowimpact" + ADDEDCOMPONENT + {"type":"number","name":"standards.intuneDeviceRetirementDays.days","label":"Maximum days (0 equals disabled)"} + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Graph API + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/deviceManagement/managedDeviceCleanupSettings' -tenantid $Tenant) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneRequireMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneRequireMFA.ps1 index 9698085e6cb7..0d62ae33b793 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneRequireMFA.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneRequireMFA.ps1 @@ -1,8 +1,30 @@ function Invoke-CIPPStandardintuneRequireMFA { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) intuneRequireMFA + .SYNOPSIS + (Label) Require Multifactor Authentication to register or join devices with Microsoft Entra + .DESCRIPTION + (Helptext) Requires MFA for all users to register devices with Intune. This is useful when not using Conditional Access. + (DocsDescription) Requires MFA for all users to register devices with Intune. This is useful when not using Conditional Access. + .NOTES + CAT + Intune Standards + TAG + "mediumimpact" + IMPACT + Medium Impact + POWERSHELLEQUIVALENT + Update-MgBetaPolicyDeviceRegistrationPolicy + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $PreviousSetting = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy' -tenantid $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardlaps.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardlaps.ps1 index 84f24cffa73d..2aef8abb64a6 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardlaps.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardlaps.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardlaps { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) laps + .SYNOPSIS + (Label) Enable LAPS on the tenant + .DESCRIPTION + (Helptext) Enables the tenant to use LAPS. You must still create a policy for LAPS to be active on all devices. Use the template standards to deploy this by default. + (DocsDescription) Enables the LAPS functionality on the tenant. Prerequisite for using Windows LAPS via Azure AD. + .NOTES + CAT + Entra (AAD) Standards + TAG + "lowimpact" + ADDEDCOMPONENT + IMPACT + Low Impact + POWERSHELLEQUIVALENT + Portal or Graph API + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $PreviousSetting = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy' -tenantid $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 index 95dfcec5a26d..9dd41b467644 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 @@ -1,8 +1,34 @@ function Invoke-CIPPStandardsharingCapability { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) sharingCapability + .SYNOPSIS + (Label) Set Sharing Level for OneDrive and Sharepoint + .DESCRIPTION + (Helptext) Sets the default sharing level for OneDrive and Sharepoint. This is a tenant wide setting and overrules any settings set on the site level + (DocsDescription) Sets the default sharing level for OneDrive and Sharepoint. This is a tenant wide setting and overrules any settings set on the site level + .NOTES + CAT + SharePoint Standards + TAG + "highimpact" + "CIS" + ADDEDCOMPONENT + {"type":"Select","label":"Select Sharing Level","name":"standards.sharingCapability.Level","values":[{"label":"Users can share only with people in the organization. No external sharing is allowed.","value":"disabled"},{"label":"Users can share with new and existing guests. Guests must sign in or provide a verification code.","value":"externalUserSharingOnly"},{"label":"Users can share with anyone by using links that do not require sign-in.","value":"externalUserAndGuestSharing"},{"label":"Users can share with existing guests (those already in the directory of the organization).","value":"existingExternalUserSharingOnly"}]} + IMPACT + High Impact + POWERSHELLEQUIVALENT + Update-MgBetaAdminSharepointSetting + RECOMMENDEDBY + "CIS" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 index 9c7e7d11f555..5fe6efe90b5c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 @@ -1,9 +1,34 @@ function Invoke-CIPPStandardsharingDomainRestriction { <# .FUNCTIONALITY - Internal - #> - + Internal + .COMPONENT + (APIName) sharingDomainRestriction + .SYNOPSIS + (Label) Restrict sharing to a specific domain + .DESCRIPTION + (Helptext) Restricts sharing to only users with the specified domain. This is useful for organizations that only want to share with their own domain. + (DocsDescription) Restricts sharing to only users with the specified domain. This is useful for organizations that only want to share with their own domain. + .NOTES + CAT + SharePoint Standards + TAG + "highimpact" + "CIS" + ADDEDCOMPONENT + {"type":"Select","name":"standards.sharingDomainRestriction.Mode","label":"Limit external sharing by domains","values":[{"label":"Off","value":"none"},{"label":"Restirct sharing to specific domains","value":"allowList"},{"label":"Block sharing to specific domains","value":"blockList"}]} + {"type":"input","name":"standards.sharingDomainRestriction.Domains","label":"Domains to allow/block, comma separated"} + IMPACT + High Impact + POWERSHELLEQUIVALENT + Update-MgAdminSharepointSetting + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards + #> + param($Tenant, $Settings) $CurrentState = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 index 8a234d8eff2a..7ffad1a2bc47 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 @@ -1,8 +1,31 @@ function Invoke-CIPPStandardunmanagedSync { <# .FUNCTIONALITY - Internal + Internal + .COMPONENT + (APIName) unmanagedSync + .SYNOPSIS + (Label) Only allow users to sync OneDrive from AAD joined devices + .DESCRIPTION + (Helptext) The unmanaged Sync standard has been temporarily disabled and does nothing. + (DocsDescription) The unmanaged Sync standard has been temporarily disabled and does nothing. + .NOTES + CAT + SharePoint Standards + TAG + "highimpact" + ADDEDCOMPONENT + IMPACT + High Impact + POWERSHELLEQUIVALENT + Update-MgAdminSharepointSetting + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/edit-standards #> + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 index f8a83a85bcaf..68709c2fac10 100644 --- a/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 @@ -5,7 +5,9 @@ function Test-CIPPAccessPermissions { $APIName = 'Access Check', $ExecutingUser ) - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Started permissions check' -Sev 'Debug' + + $User = $request.headers.'x-ms-client-principal-name' + Write-LogMessage -user $User -API $APINAME -message 'Started permissions check' -Sev 'Debug' $Messages = [System.Collections.Generic.List[string]]::new() $ErrorMessages = [System.Collections.Generic.List[string]]::new() $MissingPermissions = [System.Collections.Generic.List[string]]::new() @@ -52,18 +54,20 @@ function Test-CIPPAccessPermissions { $Messages.Add('Your refresh token matches key vault.') | Out-Null } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Key vault exception: $($_) " -Sev 'Error' + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -tenant $tenant -message "Key vault exception: $($ErrorMessage.NormalizedError) " -Sev 'Error' -LogData $ErrorMessage } } try { $AccessTokenDetails = Read-JwtAccessDetails -Token $GraphToken.access_token -erroraction SilentlyContinue } catch { + $ErrorMessage = Get-CippException -Exception $_ $AccessTokenDetails = [PSCustomObject]@{ Name = '' AuthMethods = @() } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Token exception: $($_) " -Sev 'Error' + Write-LogMessage -user $User -API $APINAME -tenant $tenant -message "Token exception: $($ErrorMessage.NormalizedError_) " -Sev 'Error' -LogData $ErrorMessage $Success = $false Write-Host 'Setting success to false due to not able to decode token.' @@ -108,8 +112,9 @@ function Test-CIPPAccessPermissions { } } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Permissions check failed: $($_) " -Sev 'Error' - $ErrorMessages.Add("We could not connect to the API to retrieve the permissions. There might be a problem with the secure application model configuration. The returned error is: $(Get-NormalizedError -message $_)") | Out-Null + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $User -API $APINAME -message "Permissions check failed: $($ErrorMessage.NormalizedError) " -Sev 'Error' -LogData $ErrorMessage + $ErrorMessages.Add("We could not connect to the API to retrieve the permissions. There might be a problem with the secure application model configuration. The returned error is: $($ErrorMessage.NormalizedError)") | Out-Null Write-Host 'Setting success to False due to not being able to connect.' $Success = $false diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 index 842b4f4b66aa..3cce53bebe39 100644 --- a/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 @@ -68,34 +68,36 @@ function Test-CIPPAccessTenant { GDAPRoles = $GDAPRoles MissingRoles = $MissingRoles } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message 'Tenant access check executed successfully' -Sev 'Info' + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $tenant -message 'Tenant access check executed successfully' -Sev 'Info' } catch { + $ErrorMessage = Get-CippException -Exception $_ @{ TenantName = "$($tenant)" - Status = "Failed to connect: $(Get-NormalizedError -message $_.Exception.Message)" + Status = "Failed to connect: $($ErrorMessage.NormalizedError)" GDAP = '' } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Tenant access check failed: $(Get-NormalizedError -message $_) " -Sev 'Error' + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $tenant -message "Tenant access check failed: $($ErrorMessage.NormalizedError) " -Sev 'Error' -LogData $ErrorMessage } try { - $GraphRequest = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig' -ErrorAction Stop + $null = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig' -ErrorAction Stop @{ TenantName = "$($Tenant)" Status = 'Successfully connected to Exchange' } } catch { + $ErrorMessage = Get-CippException -Exception $_ $ReportedError = ($_.ErrorDetails | ConvertFrom-Json -ErrorAction SilentlyContinue) $Message = if ($ReportedError.error.details.message) { $ReportedError.error.details.message } else { $ReportedError.error.innererror.internalException.message } if ($null -eq $Message) { $Message = $($_.Exception.Message) } @{ TenantName = "$($Tenant)" - Status = "Failed to connect to Exchange: $(Get-NormalizedError -message $Message)" + Status = "Failed to connect to Exchange: $($ErrorMessage.NormalizedError)" } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Tenant access check for Exchange failed: $(Get-NormalizedError -message $Message) " -Sev 'Error' + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $tenant -message "Tenant access check for Exchange failed: $($ErrorMessage.NormalizedError) " -Sev 'Error' -LogData $ErrorMessage } } if (!$Tenants) { $results = 'Could not load the tenants list from cache. Please run permissions check first, or visit the tenants page.' } diff --git a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 index d527bdd0ff3b..2d8b95ff2ea2 100644 --- a/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPGDAPRelationships.ps1 @@ -89,7 +89,8 @@ function Test-CIPPGDAPRelationships { } } catch { - Write-LogMessage -user $ExecutingUser -API $APINAME -message "Failed to run GDAP check for $($TenantFilter): $($_.Exception.Message)" -Sev 'Error' + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Failed to run GDAP check for $($TenantFilter): $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage } return [PSCustomObject]@{ diff --git a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 index d5db2cf47076..a600f7e41ede 100644 --- a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 @@ -10,13 +10,7 @@ function Invoke-CippWebhookProcessing { $ExecutingUser ) - <# $ExtendedPropertiesIgnoreList = @( - 'OAuth2:Authorize' - 'OAuth2:Token' - 'SAS:EndAuth' - 'SAS:ProcessAuth' - 'Login:reprocess' - ) #> + Write-Host "Received data. Our Action List is $($data.CIPPAction)" $ActionList = ($data.CIPPAction | ConvertFrom-Json -ErrorAction SilentlyContinue).value diff --git a/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 b/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 index dd3158876945..39b8957832c6 100644 --- a/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 @@ -51,11 +51,13 @@ function Test-CIPPAuditLogRules { try { if ($Data.ExtendedProperties) { $Data.CIPPExtendedProperties = ($Data.ExtendedProperties | ConvertTo-Json) - if ($Data.CIPPExtendedProperties.RequestType -in $ExtendedPropertiesIgnoreList) { - Write-Information 'No need to process this operation as its in our ignore list' - continue + $Data.ExtendedProperties | ForEach-Object { + if ($_.Value -in $ExtendedPropertiesIgnoreList) { + Write-Information 'No need to process this operation as its in our ignore list' + continue + } + $Data | Add-Member -NotePropertyName $_.Name -NotePropertyValue $_.Value -Force -ErrorAction SilentlyContinue } - $Data.ExtendedProperties | ForEach-Object { $Data | Add-Member -NotePropertyName $_.Name -NotePropertyValue $_.Value -Force -ErrorAction SilentlyContinue } } if ($Data.DeviceProperties) { $Data.CIPPDeviceProperties = ($Data.DeviceProperties | ConvertTo-Json) @@ -187,4 +189,4 @@ function Test-CIPPAuditLogRules { $Results.DataToProcess = $DataToProcess } $Results -} \ No newline at end of file +} diff --git a/Modules/CippEntrypoints/CippEntrypoints.psm1 b/Modules/CippEntrypoints/CippEntrypoints.psm1 index 994dab0633bf..aae4096c53a1 100644 --- a/Modules/CippEntrypoints/CippEntrypoints.psm1 +++ b/Modules/CippEntrypoints/CippEntrypoints.psm1 @@ -9,13 +9,14 @@ function Receive-CippHttpTrigger { $Request, $TriggerMetadata ) - + # Convert the request to a PSCustomObject because the httpContext is case sensitive since 7.3 + $Request = $Request | ConvertTo-Json -Depth 100 | ConvertFrom-Json Set-Location (Get-Item $PSScriptRoot).Parent.Parent.FullName $FunctionName = 'Invoke-{0}' -f $Request.Params.CIPPEndpoint Write-Host "Function: $($Request.Params.CIPPEndpoint)" $HttpTrigger = @{ - Request = $Request + Request = [pscustomobject]($Request) TriggerMetadata = $TriggerMetadata } @@ -43,6 +44,7 @@ function Receive-CippHttpTrigger { function Receive-CippQueueTrigger { Param($QueueItem, $TriggerMetadata) + Set-Location (Get-Item $PSScriptRoot).Parent.Parent.FullName $Start = (Get-Date).ToUniversalTime() $APIName = $TriggerMetadata.FunctionName @@ -144,7 +146,7 @@ function Receive-CippOrchestrationTrigger { function Receive-CippActivityTrigger { Param($Item) try { - $Start = (Get-Date).ToUniversalTime() + $Start = Get-Date Set-Location (Get-Item $PSScriptRoot).Parent.Parent.FullName if ($Item.QueueId) { @@ -188,7 +190,7 @@ function Receive-CippActivityTrigger { } } - $End = (Get-Date).ToUniversalTime() + $End = Get-Date try { $Stats = @{ diff --git a/Modules/CippExtensions/CippExtensions.psm1 b/Modules/CippExtensions/CippExtensions.psm1 index ad69dcdfdb5f..d2bab13c84b9 100644 --- a/Modules/CippExtensions/CippExtensions.psm1 +++ b/Modules/CippExtensions/CippExtensions.psm1 @@ -1,7 +1,6 @@ -$Public = @(Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue) -$Private = @(Get-ChildItem -Path $PSScriptRoot\private\*.ps1 -ErrorAction SilentlyContinue) -$NinjaOne = @(Get-ChildItem -Path $PSScriptRoot\NinjaOne\*.ps1 -ErrorAction SilentlyContinue) -$Functions = $Public + $Private + $NinjaOne +$Public = @(Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -Recurse -ErrorAction SilentlyContinue) +$Private = @(Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -Recurse -ErrorAction SilentlyContinue) +$Functions = $Public + $Private foreach ($import in @($Functions)) { try { . $import.FullName diff --git a/Modules/CippExtensions/NinjaOne/Get-NinjaOneFieldMapping.ps1 b/Modules/CippExtensions/NinjaOne/Get-NinjaOneFieldMapping.ps1 deleted file mode 100644 index 8be773c2d030..000000000000 --- a/Modules/CippExtensions/NinjaOne/Get-NinjaOneFieldMapping.ps1 +++ /dev/null @@ -1,101 +0,0 @@ -function Get-NinjaOneFieldMapping { - [CmdletBinding()] - param ( - $CIPPMapping - ) - try { - #Get available mappings - $Mappings = [pscustomobject]@{} - - [System.Collections.Generic.List[PSCustomObject]]$CIPPFields = @( - [PSCustomObject]@{ - InternalName = 'TenantLinks' - Description = 'Microsoft 365 Tenant Links - Field Used to Display Links to Microsoft 365 Portals and CIPP' - Scope = 'Organization' - Type = 'WYSIWYG' - }, - [PSCustomObject]@{ - InternalName = 'TenantSummary' - Description = 'Microsoft 365 Tenant Summary - Field Used to Display Tenant Summary Information' - Scope = 'Organization' - Type = 'WYSIWYG' - }, - [PSCustomObject]@{ - InternalName = 'UsersSummary' - Description = 'Microsoft 365 Users Summary - Field Used to Display User Summary Information' - Scope = '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' - }, - [PSCustomObject]@{ - InternalName = 'DeviceSummary' - Description = 'Microsoft 365 Device Summary - Field Used to Display Device Summary Information' - Scope = 'Device' - Type = 'WYSIWYG' - }, - [PSCustomObject]@{ - InternalName = 'DeviceCompliance' - Description = 'Intune Device Compliance Status - Field Used to Monitor Device Compliance' - Scope = 'Device' - Type = 'TEXT' - } - ) - - $Filter = "PartitionKey eq 'NinjaFieldMapping'" - Get-AzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object { - $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.NinjaOneName)"; value = "$($_.NinjaOne)" } - } - - - $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 = @() - } - - if ($Null -eq $NinjaCustomFieldsOrg){ - [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsOrg = @() - } - - } catch { - [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsNode = @() - [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsOrg = @() - } - - $DoNotSync = [PSCustomObject]@{ - name = '--- Do not synchronize ---' - value = $null - type = 'unset' - } - - $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 - } - - return $MappingObj - -} \ No newline at end of file diff --git a/Modules/CippExtensions/Private/Get-StringHash.ps1 b/Modules/CippExtensions/Private/Get-StringHash.ps1 new file mode 100644 index 000000000000..a5c94f62b511 --- /dev/null +++ b/Modules/CippExtensions/Private/Get-StringHash.ps1 @@ -0,0 +1,8 @@ +function Get-StringHash { + Param($String) + $StringBuilder = New-Object System.Text.StringBuilder + [System.Security.Cryptography.HashAlgorithm]::Create('SHA1').ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String)) | ForEach-Object { + [Void]$StringBuilder.Append($_.ToString('x2')) + } + $StringBuilder.ToString() +} \ No newline at end of file diff --git a/Modules/CippExtensions/Private/Hudu/Get-HuduFormattedBlock.ps1 b/Modules/CippExtensions/Private/Hudu/Get-HuduFormattedBlock.ps1 new file mode 100644 index 000000000000..c27d9656c819 --- /dev/null +++ b/Modules/CippExtensions/Private/Hudu/Get-HuduFormattedBlock.ps1 @@ -0,0 +1,12 @@ +function Get-HuduFormattedBlock ($Heading, $Body) { + return @" +
+
+

$Heading

+
+
+ $Body +
+
+"@ +} diff --git a/Modules/CippExtensions/Private/Hudu/Get-HuduFormattedField.ps1 b/Modules/CippExtensions/Private/Hudu/Get-HuduFormattedField.ps1 new file mode 100644 index 000000000000..7aa73e9a3521 --- /dev/null +++ b/Modules/CippExtensions/Private/Hudu/Get-HuduFormattedField.ps1 @@ -0,0 +1,13 @@ +function Get-HuduFormattedField ($Title, $Value) { + return @" +
+
+ $Title +
+
+ $Value +
+
+"@ +} + diff --git a/Modules/CippExtensions/Private/Hudu/Get-HuduLinkBlock.ps1 b/Modules/CippExtensions/Private/Hudu/Get-HuduLinkBlock.ps1 new file mode 100644 index 000000000000..69bc48342d06 --- /dev/null +++ b/Modules/CippExtensions/Private/Hudu/Get-HuduLinkBlock.ps1 @@ -0,0 +1,3 @@ +function Get-HuduLinkBlock($URL, $Icon, $Title) { + return '' -f $URL, $Icon, $Title +} diff --git a/Modules/CippExtensions/Public/Extension Functions/Add-HuduAssetLayoutM365Field.ps1 b/Modules/CippExtensions/Public/Extension Functions/Add-HuduAssetLayoutM365Field.ps1 new file mode 100644 index 000000000000..5ab07cbc3887 --- /dev/null +++ b/Modules/CippExtensions/Public/Extension Functions/Add-HuduAssetLayoutM365Field.ps1 @@ -0,0 +1,28 @@ +function Add-HuduAssetLayoutM365Field { + Param( + $AssetLayoutId + ) + + $M365Field = @{ + position = 0 + label = 'Microsoft 365' + field_type = 'RichText' + show_in_list = $false + required = $false + expiration = $false + } + + $AssetLayout = Get-HuduAssetLayouts -LayoutId $AssetLayoutId + + if ($AssetLayout.fields.label -contains 'Microsoft 365') { + return $AssetLayout + } + + $AssetLayoutFields = [System.Collections.Generic.List[object]]::new() + $AssetLayoutFields.Add($M365Field) + foreach ($Field in $AssetLayout.fields) { + $Field.position++ + $AssetLayoutFields.Add($Field) + } + Set-HuduAssetLayout -Id $AssetLayoutId -Fields $AssetLayoutFields +} diff --git a/Modules/CippExtensions/Public/Extension Functions/Get-ExtensionCacheData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Get-ExtensionCacheData.ps1 new file mode 100644 index 000000000000..f360a3a873bb --- /dev/null +++ b/Modules/CippExtensions/Public/Extension Functions/Get-ExtensionCacheData.ps1 @@ -0,0 +1,14 @@ +function Get-ExtensionCacheData { + param( + $TenantFilter + ) + + $Table = Get-CIPPTable -TableName CacheExtensionSync + $CacheData = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq '$TenantFilter'" + + $Return = @{} + foreach ($Data in $CacheData) { + $Return[$Data.RowKey] = $Data.Data | ConvertFrom-Json -ErrorAction SilentlyContinue + } + return [PSCustomObject]$Return +} 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-CippExtensionData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Push-CippExtensionData.ps1 new file mode 100644 index 000000000000..95e74b54838b --- /dev/null +++ b/Modules/CippExtensions/Public/Extension Functions/Push-CippExtensionData.ps1 @@ -0,0 +1,18 @@ +function Push-CippExtensionData { + param( + $TenantFilter, + $Extension + ) + + $Table = Get-CIPPTable -TableName Extensionsconfig + $Config = (Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -ea stop + + switch ($Extension) { + 'Hudu' { + if ($Config.Hudu.Enabled) { + Write-Host 'Perfoming Hudu Extension Sync...' + Invoke-HuduExtensionSync -Configuration $Config.Hudu -TenantFilter $TenantFilter + } + } + } +} 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..d8b2d6cfd01f --- /dev/null +++ b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 @@ -0,0 +1,125 @@ +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' } + $PushTasks = Get-CIPPAzDataTableEntity @ScheduledTasksTable -Filter 'Hidden eq true' | Where-Object { $_.Command -match 'Push-CippExtensionData' } + $Tenants = Get-Tenants -IncludeErrors + + $Extensions = @('Hudu') + $MappedTenants = [System.Collections.Generic.List[string]]::new() + 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') + $SyncTypes.Add('Groups') + + 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 } + if (!$Tenant) { + Write-Warning "Tenant $($Mapping.RowKey) not found" + continue + } + $MappedTenants.Add($Tenant.defaultDomainName) + 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 = [pscustomobject]@{ + Name = "Extension Sync - $SyncType" + Command = @{ + value = 'Sync-CippExtensionData' + label = 'Sync-CippExtensionData' + } + Parameters = [pscustomobject]@{ + TenantFilter = $Tenant.defaultDomainName + SyncType = $SyncType + } + Recurrence = '1d' + ScheduledTime = $unixtime + TenantFilter = $Tenant.defaultDomainName + } + if ($ExistingTask) { + $Task | Add-Member -NotePropertyName 'RowKey' -NotePropertyValue $ExistingTask.RowKey -Force + } + $null = Add-CIPPScheduledTask -Task $Task -hidden $true -SyncType $SyncType + Write-Information "Creating $SyncType task for tenant $($Tenant.defaultDomainName)" + } + } + + $ExistingPushTask = $PushTasks | Where-Object { $_.Tenant -eq $Tenant.defaultDomainName -and $_.SyncType -eq $Extension } + if (!$ExistingPushTask -or $Reschedule.IsPresent) { + # push cached data to extension + $in30mins = [int64](([datetime]::UtcNow.AddMinutes(30)) - (Get-Date '1/1/1970')).TotalSeconds + $Task = [pscustomobject]@{ + Name = "$Extension Extension Sync" + Command = @{ + value = 'Push-CippExtensionData' + label = 'Push-CippExtensionData' + } + Parameters = [pscustomobject]@{ + TenantFilter = $Tenant.defaultDomainName + Extension = $Extension + } + Recurrence = '1d' + ScheduledTime = $in30mins + TenantFilter = $Tenant.defaultDomainName + } + if ($ExistingPushTask) { + $task | Add-Member -NotePropertyName 'RowKey' -NotePropertyValue $ExistingPushTask.RowKey -Force + } + $null = Add-CIPPScheduledTask -Task $Task -hidden $true -SyncType $Extension + Write-Information "Creating $Extension task for tenant $($Tenant.defaultDomainName)" + } + } + } else { + # remove existing scheduled tasks + $PushTasks | Where-Object { $_.SyncType -eq $Extension } | ForEach-Object { + Write-Information "Extension Disabled: Cleaning up scheduled task $($_.Name) for tenant $($_.Tenant)" + $Entity = $_ | Select-Object -Property PartitionKey, RowKey + Remove-AzDataTableEntity @ScheduledTasksTable -Entity $Entity + } + } + } + $MappedTenants = $MappedTenants | Sort-Object -Unique + + foreach ($Task in $ScheduledTasks) { + if ($Task.Tenant -notin $MappedTenants) { + Write-Information "Tenant Removed: Cleaning up scheduled task $($Task.Name) for tenant $($Task.TenantFilter)" + $Entity = $Task | Select-Object -Property PartitionKey, RowKey + Remove-AzDataTableEntity @ScheduledTasksTable -Entity $Entity + } + } + foreach ($Task in $PushTasks) { + if ($Task.Tenant -notin $MappedTenants) { + Write-Information "Tenant Removed: Cleaning up scheduled task $($Task.Name) for tenant $($Task.TenantFilter)" + $Entity = $Task | Select-Object -Property PartitionKey, RowKey + Remove-AzDataTableEntity @ScheduledTasksTable -Entity $Entity + } + } +} 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..98a08fe48f49 --- /dev/null +++ b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 @@ -0,0 +1,305 @@ +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' + } + $null = 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' + }, + @{ + id = 'Domains' + method = 'GET' + url = '/domains?$top=99' + }, + @{ + id = 'Licenses' + method = 'GET' + url = '/subscribedSkus' + }, + @{ + 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' + }, + @{ + id = 'OneDriveUsage' + method = 'GET' + url = "reports/getOneDriveUsageAccountDetail(period='D7')?`$format=application%2fjson" + }, + @{ + id = 'MailboxUsage' + method = 'GET' + url = "reports/getMailboxUsageDetail(period='D7')?`$format=application%2fjson" + } + ) + + $SingleGraphQueries = @(@{ + id = 'SecureScore' + graphRequest = @{ + uri = 'https://graph.microsoft.com/beta/security/secureScores?$top=1' + noPagination = $true + } + }) + $AdditionalRequests = @( + @{ + ParentId = 'AllRoles' + graphRequest = @{ + url = '/directoryRoles/{0}/members?$select=id,displayName,userPrincipalName' + method = 'GET' + } + } + ) + } + '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' + } + ) + } + 'Groups' { + [System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @( + @{ + 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' + } + ) + $AdditionalRequests = @( + @{ + ParentId = 'Groups' + graphRequest = @{ + url = '/groups/{0}/members?$select=id,displayName,userPrincipalName' + method = 'GET' + } + } + ) + } + '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' + } + ) + + $AdditionalRequests = @( + @{ + ParentId = 'DeviceCompliancePolicies' + graphRequest = @{ + url = '/deviceManagement/deviceCompliancePolicies/{0}/deviceStatuses?$top=999' + method = 'GET' + } + } + ) + } + 'Mailboxes' { + $Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses,WhenSoftDeleted,IsInactiveMailbox,ProhibitSendQuota,ProhibitSendReceiveQuota,LitigationHoldEnabled,InPlaceHolds,HiddenFromAddressListsEnabled' + $ExoRequest = @{ + tenantid = $TenantFilter + cmdlet = 'Get-Mailbox' + cmdParams = @{} + Select = $Select + } + $Mailboxes = (New-ExoRequest @ExoRequest) | Select-Object id, ExchangeGuid, ArchiveGuid, WhenSoftDeleted, ProhibitSendQuota, ProhibitSendReceiveQuota, LitigationHoldEnabled, InplaceHolds, HiddenFromAddressListsEnabled, @{ 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) + } + $null = Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force + + $SingleGraphQueries = @( + @{ + id = 'CASMailbox' + graphRequest = @{ + uri = "https://outlook.office365.com/adminapi/beta/$($tenantfilter)/CasMailbox" + Tenantid = $tenantfilter + scope = 'ExchangeOnline' + noPagination = $true + } + } + ) + + # Bulk request mailbox permissions using New-ExoBulkRequest for each mailbox - mailboxPermissions is not a valid graph query + $ExoBulkRequests = foreach ($Mailbox in $Mailboxes) { + @{ + CmdletInput = @{ + CmdletName = 'Get-MailboxPermission' + Parameters = @{ Identity = $Mailbox.UPN } + } + } + } + $MailboxPermissions = New-ExoBulkRequest -cmdletArray @($ExoBulkRequests) -tenantid $TenantFilter + $Entity = @{ + PartitionKey = $TenantFilter + SyncType = 'Mailboxes' + RowKey = 'MailboxPermissions' + Data = [string]($MailboxPermissions | ConvertTo-Json -Depth 10 -Compress) + } + $null = Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force + } + } + + if ($TenantRequests) { + Write-Information "Requesting tenant information for $TenantFilter $SyncType" + try { + $TenantResults = New-GraphBulkRequest -Requests @($TenantRequests) -tenantid $TenantFilter + } catch { + Throw "Failed to fetch bulk company data: $_" + } + + $TenantResults | Select-Object id, body | ForEach-Object { + $Data = $_.body.value ?? $_.body + if ($Data -match '^eyJ') { + # base64 decode + $Data = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Data)) | ConvertFrom-Json + $Data = $Data.Value + } + + $Entity = @{ + PartitionKey = $TenantFilter + RowKey = $_.id + SyncType = $SyncType + Data = [string]($Data | ConvertTo-Json -Depth 10 -Compress) + } + $null = Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force + } + + if ($AdditionalRequests) { + foreach ($AdditionalRequest in $AdditionalRequests) { + $ParentId = $AdditionalRequest.ParentId + $GraphRequest = $AdditionalRequest.graphRequest.PSObject.Copy() + $AdditionalRequestQueries = ($TenantResults | Where-Object { $_.id -eq $ParentId }).body.value | ForEach-Object { + if ($_.id) { + [PSCustomObject]@{ + id = $_.id + method = $GraphRequest.method + url = $GraphRequest.url -f $_.id + } + } + } + if (($AdditionalRequestQueries | Measure-Object).Count -gt 0) { + try { + $AdditionalResults = New-GraphBulkRequest -Requests @($AdditionalRequestQueries) -tenantid $TenantFilter + } catch { + throw $_ + } + if ($AdditionalResults) { + $AdditionalResults | ForEach-Object { + $Data = $_.body.value ?? $_.body + if ($Data -match '^eyJ') { + # base64 decode + $Data = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Data)) | ConvertFrom-Json + $Data = $Data.Value + } + $Entity = @{ + PartitionKey = $TenantFilter + SyncType = $SyncType + RowKey = '{0}_{1}' -f $ParentId, $_.id + Data = [string]($Data | ConvertTo-Json -Depth 10 -Compress) + } + try { + $null = Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force + } catch { + throw $_ + } + } + } + + } + } + } + } + + 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) + } + $null = 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: $(Get-NormalizedError -message $_.Exception.Message)" + } finally { + Add-CIPPAzDataTableEntity @Table -Entity $LastSync -Force + } +} diff --git a/Modules/CippExtensions/Private/Get-GradientToken.ps1 b/Modules/CippExtensions/Public/Gradient/Get-GradientToken.ps1 similarity index 100% rename from Modules/CippExtensions/Private/Get-GradientToken.ps1 rename to Modules/CippExtensions/Public/Gradient/Get-GradientToken.ps1 diff --git a/Modules/CippExtensions/Private/New-GradientAlert.ps1 b/Modules/CippExtensions/Public/Gradient/New-GradientAlert.ps1 similarity index 100% rename from Modules/CippExtensions/Private/New-GradientAlert.ps1 rename to Modules/CippExtensions/Public/Gradient/New-GradientAlert.ps1 diff --git a/Modules/CippExtensions/Private/New-GradientServiceSyncRun.ps1 b/Modules/CippExtensions/Public/Gradient/New-GradientServiceSyncRun.ps1 similarity index 100% rename from Modules/CippExtensions/Private/New-GradientServiceSyncRun.ps1 rename to Modules/CippExtensions/Public/Gradient/New-GradientServiceSyncRun.ps1 diff --git a/Modules/CippExtensions/Private/Get-HaloMapping.ps1 b/Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1 similarity index 68% rename from Modules/CippExtensions/Private/Get-HaloMapping.ps1 rename to Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1 index fcae99cfd5d1..2a8aae7646ef 100644 --- a/Modules/CippExtensions/Private/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/Private/Get-HaloToken.ps1 b/Modules/CippExtensions/Public/Halo/Get-HaloToken.ps1 similarity index 100% rename from Modules/CippExtensions/Private/Get-HaloToken.ps1 rename to Modules/CippExtensions/Public/Halo/Get-HaloToken.ps1 diff --git a/Modules/CippExtensions/Private/New-HaloPSATicket.ps1 b/Modules/CippExtensions/Public/Halo/New-HaloPSATicket.ps1 similarity index 100% rename from Modules/CippExtensions/Private/New-HaloPSATicket.ps1 rename to Modules/CippExtensions/Public/Halo/New-HaloPSATicket.ps1 diff --git a/Modules/CippExtensions/Private/Set-HaloMapping.ps1 b/Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1 similarity index 72% rename from Modules/CippExtensions/Private/Set-HaloMapping.ps1 rename to Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1 index 527bbc94fd22..129b1578ad59 100644 --- a/Modules/CippExtensions/Private/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/Connect-HuduAPI.ps1 b/Modules/CippExtensions/Public/Hudu/Connect-HuduAPI.ps1 new file mode 100644 index 000000000000..3117d343ec55 --- /dev/null +++ b/Modules/CippExtensions/Public/Hudu/Connect-HuduAPI.ps1 @@ -0,0 +1,16 @@ +function Connect-HuduAPI { + [CmdletBinding()] + param ( + $Configuration + ) + + if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') { + $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' + $APIKey = (Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'Hudu' and RowKey eq 'Hudu'").APIKey + } else { + $null = Connect-AzAccount -Identity + $APIKey = (Get-AzKeyVaultSecret -VaultName $ENV:WEBSITE_DEPLOYMENT_ID -Name 'Hudu' -AsPlainText) + } + New-HuduBaseURL -BaseURL $Configuration.BaseURL + New-HuduAPIKey -ApiKey $APIKey +} \ No newline at end of file diff --git a/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 new file mode 100644 index 000000000000..7004401fd33d --- /dev/null +++ b/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 @@ -0,0 +1,61 @@ +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' + } + ) + + $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 new file mode 100644 index 000000000000..7ffbddfa57a0 --- /dev/null +++ b/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1 @@ -0,0 +1,41 @@ +function Get-HuduMapping { + [CmdletBinding()] + param ( + $CIPPMapping + ) + + $Mappings = Get-ExtensionMapping -Extension 'Hudu' + + $Tenants = Get-Tenants -IncludeErrors + $Table = Get-CIPPTable -TableName Extensionsconfig + try { + $Configuration = ((Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -ea stop).Hudu + + Connect-HuduAPI -configuration $Configuration + $HuduCompanies = Get-HuduCompanies + + } catch { + $Message = if ($_.ErrorDetails.Message) { + Get-NormalizedError -Message $_.ErrorDetails.Message + } else { + $_.Exception.message + } + + Write-LogMessage -Message "Could not get Hudu Companies, error: $Message " -Level Error -tenant 'CIPP' -API 'HuduMapping' + $HuduCompanies = @(@{name = "Could not get Hudu Companies, error: $Message"; value = '-1' }) + } + $HuduCompanies = $HuduCompanies | ForEach-Object { + [PSCustomObject]@{ + name = $_.name + value = "$($_.id)" + } + } + $MappingObj = [PSCustomObject]@{ + Tenants = @($Tenants) + Companies = @($HuduCompanies) + Mappings = $Mappings + } + + return $MappingObj + +} diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 new file mode 100644 index 000000000000..77a429e2d0ca --- /dev/null +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -0,0 +1,905 @@ +function Invoke-HuduExtensionSync { + <# + .FUNCTIONALITY + Internal + #> + Param( + $Configuration, + $TenantFilter + ) + Connect-HuduAPI -configuration $Configuration + + # Get mapping configuration + $MappingTable = Get-CIPPTable -TableName 'CippMapping' + $Mappings = Get-CIPPAzDataTableEntity @MappingTable -Filter "PartitionKey eq 'HuduMapping' or PartitionKey eq 'HuduFieldMapping'" + #Write-Host ($Mappings | ConvertTo-Json) + $defaultdomain = $TenantFilter + $Tenant = Get-Tenants -IncludeErrors | Where-Object { $_.defaultDomainName -eq $TenantFilter } + $TenantMap = $Mappings | Where-Object { $_.RowKey -eq $Tenant.customerId } + + $HuduAssetCache = Get-CippTable -tablename 'CacheHuduAssets' + + if (!$TenantMap) { + return 'Tenant not found in mapping table' + } + + $CompanyResult = [PSCustomObject]@{ + Name = $Tenant.displayName + Users = 0 + Devices = 0 + Errors = [System.Collections.Generic.List[string]]@() + } + + $PeopleLayoutId = $Mappings | Where-Object { $_.RowKey -eq 'Users' } | Select-Object -ExpandProperty IntegrationId + $CreateUsers = $Configuration.CreateMissingUsers + $DeviceLayoutId = $Mappings | Where-Object { $_.RowKey -eq 'Devices' } | Select-Object -ExpandProperty IntegrationId + $CreateDevices = $Configuration.CreateMissingDevices + + if ($PeopleLayoutId) { + $null = Add-HuduAssetLayoutM365Field -AssetLayoutId $PeopleLayoutId + } + if ($DeviceLayoutId) { + $null = Add-HuduAssetLayoutM365Field -AssetLayoutId $DeviceLayoutId + } + + $importDomains = $false + #$monitorDomains = [System.Convert]::ToBoolean($env:monitorDomains) + $IntuneDesktopDeviceTypes = $env:IntuneDesktopDeviceTypes -split ',' + $ExcludeSerials = $env:ExcludeSerials -split ',' + + Set-Location (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent.FullName + $LicTable = Import-Csv Conversiontable.csv + + #$AssignedMap = Get-AssignedMap + #$AssignedNameMap = Get-AssignedNameMap + + $EnableCIPP = $true + + $ConfigTable = Get-Cipptable -tablename 'Config' + $Config = Get-CippAzDataTableEntity @ConfigTable -Filter "PartitionKey eq 'InstanceProperties' and RowKey eq 'CIPPURL'" + $CIPPURL = 'https://{0}' -f $Config.Value + + $ExtensionCache = Get-ExtensionCacheData -TenantFilter $Tenant.defaultDomainName + + try { + $company_id = $TenantMap.IntegrationId + + if ($PeopleLayoutId) { + $PeopleLayout = Get-HuduAssetLayouts -Id $PeopleLayoutId + $People = Get-HuduAssets -CompanyId $company_id -AssetLayoutId $PeopleLayout.id + } + + if ($DeviceLayoutId) { + $DesktopsLayout = Get-HuduAssetLayouts -Id $DeviceLayoutId + $HuduDesktopDevices = Get-HuduAssets -CompanyId $company_id -AssetLayoutId $DesktopsLayout.id + } + + $HuduRelations = Get-HuduRelations + + $HuduDevices = $HuduDesktopDevices + + $Links = @( + @{ + Title = 'M365 Admin Portal' + URL = 'https://admin.microsoft.com/Partner/BeginClientSession.aspx?CTID={0}&CSDEST=o365admincenter' -f $Tenant.customerId + Icon = 'fas fa-cogs' + } + @{ + Title = 'Exchange Admin Portal' + URL = 'https://outlook.office365.com/ecp/?rfr=Admin_o365&exsvurl=1&delegatedOrg={0}' -f $Tenant.initialDomainName + Icon = 'fas fa-mail-bulk' + } + @{ + Title = 'Entra Portal' + URL = 'https://entra.microsoft.com/{0}' -f $Tenant.defaultDomainName + Icon = 'fas fa-users-cog' + } + @{ + Title = 'Intune' + URL = 'https://intune.microsoft.com/{0}/' -f $Tenant.defaultDomainName + Icon = 'fas fa-laptop' + } + @{ + Title = 'Teams Portal' + URL = 'https://admin.teams.microsoft.com/?delegatedOrg={0}' -f $Tenant.defaultDomainName + Icon = 'fas fa-users' + } + @{ + Title = 'Azure Portal' + URL = 'https://portal.azure.com/{0}' -f $Tenant.defaultDomainName + Icon = 'fas fa-server' + } + ) + $FormattedLinks = foreach ($Link in $Links) { + Get-HuduLinkBlock @Link + } + + + $CustomerLinks = $FormattedLinks -join "`n" + + $Users = $ExtensionCache.Users + $licensedUsers = $Users | Where-Object { $null -ne $_.assignedLicenses.skuId } | Sort-Object userPrincipalName + + $CompanyResult.users = ($licensedUsers | Measure-Object).count + + $AllRoles = $ExtensionCache.AllRoles + + + $Roles = foreach ($Role in $AllRoles) { + # Get members from cache + $Members = ($ExtensionCache."AllRoles_$($Role.id)") + [PSCustomObject]@{ + ID = $Result.id + DisplayName = $Role.displayName + Description = $Role.description + Members = $Members + ParsedMembers = $Members.displayName -join ', ' + } + } + + $pre = "
+

Assigned Roles

+
" + + $post = '
' + $RolesHtml = $Roles | Select-Object DisplayName, Description, ParsedMembers | ConvertTo-Html -PreContent $pre -PostContent $post -Fragment | ForEach-Object { $tmp = $_ -replace '<', '<'; $tmp -replace '>', '>'; } | Out-String + + $AdminUsers = (($Roles | Where-Object { $_.Displayname -match 'Administrator' }).Members | Where-Object { $null -ne $_.displayName } | Select-Object @{N = 'Name'; E = { "$($_.DisplayName) - $($_.UserPrincipalName)" } } -Unique).name -join '
' + + $Domains = $ExtensionCache.Domains + + $customerDomains = ($Domains | Where-Object { $_.isVerified -eq $True }).id -join ', ' | Out-String + + $detailstable = "
+
+

Basic Info

+
+
+
+
+

Tenant Name

+

+ $($Tenant.displayName) +

+
+
+

Tenant ID

+

+ $($Tenant.customerId) +

+
+
+

Default Domain

+

+ $defaultdomain +

+
+
+

Customer Domains

+

+ $customerDomains +

+
+
+

Admin Users

+

+ $AdminUsers +

+
+
+

Last Updated

+

+ $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') +

+
+
+
+
+" + $Licenses = $ExtensionCache.Licenses + if ($Licenses) { + $pre = "
+

Current Licenses

+
" + + $post = '
' + + $licenseOut = $Licenses | Where-Object { $_.PrepaidUnits.Enabled -gt 0 } | Select-Object @{N = 'License Name'; E = { Convert-SKUname -skuName $_.SkuPartNumber -ConvertTable $LicTable } }, @{N = 'Active'; E = { $_.PrepaidUnits.Enabled } }, @{N = 'Consumed'; E = { $_.ConsumedUnits } }, @{N = 'Unused'; E = { $_.PrepaidUnits.Enabled - $_.ConsumedUnits } } + $licenseHTML = $licenseOut | ConvertTo-Html -PreContent $pre -PostContent $post -Fragment | Out-String + } + + $devices = $ExtensionCache.Devices + $CompanyResult.Devices = ($Devices | Measure-Object).count + + $DeviceCompliancePolicies = $ExtensionCache.DeviceCompliancePolicies + + $DeviceComplianceDetails = foreach ($Policy in $DeviceCompliancePolicies) { + $DeviceStatuses = $ExtensionCache."DeviceCompliancePolicy_$($Policy.id)" + [pscustomobject]@{ + ID = $Policy.id + DisplayName = $Policy.displayName + DeviceStatuses = $DeviceStatuses + } + } + + <#$DeviceApps = Get-BulkResultByID -Results $TenantResults -ID 'DeviceApps' + + [System.Collections.Generic.List[PSCustomObject]]$RequestArray = @() + foreach ($InstalledApp in $DeviceApps | Where-Object { $_.isAssigned -eq $True }) { + $RequestArray.add(@{ + id = $InstalledApp.id + method = 'GET' + url = "/deviceAppManagement/mobileApps/$($InstalledApp.id)/deviceStatuses" + }) + } + try { + $InstalledAppDetailsReturn = New-GraphBulkRequest -Headers $AuthHeaders -Requests $RequestArray -tenantid $TenantFilter + } catch { + $CompanyResult.Errors.add("Company: Unable to fetch Installed Device Details $_") + $InstalledAppDetailsReturn = $null + } + + $DeviceAppInstallDetails = foreach ($Result in $InstalledAppDetailsReturn) { + [pscustomobject]@{ + ID = $Result.id + DisplayName = ($DeviceApps | Where-Object { $_.id -eq $Result.id }).DisplayName + InstalledAppDetails = $result.body.value + } + } +#> + $AllGroups = $ExtensionCache.Groups + + $Groups = foreach ($Group in $AllGroups) { + $Members = $ExtensionCache."Groups_$($Result.id)" + [pscustomobject]@{ + ID = $Group.id + DisplayName = $Group.displayName + Members = $Members + } + } + + $AllConditionalAccessPolicies = $ExtensionCache.ConditionalAccess + + $ConditionalAccessMembers = foreach ($CAPolicy in $AllConditionalAccessPolicies) { + [System.Collections.Generic.List[PSCustomObject]]$CAMembers = @() + + if ($CAPolicy.conditions.users.includeUsers -contains 'All') { + $Users | ForEach-Object { $null = $CAMembers.add($_.id) } + } else { + $CAPolicy.conditions.users.includeUsers | ForEach-Object { $null = $CAMembers.add($_) } + } + + foreach ($CAIGroup in $CAPolicy.conditions.users.includeGroups) { + foreach ($Member in ($Groups | Where-Object { $_.id -eq $CAIGroup }).Members) { + $null = $CAMembers.add($Member.id) + } + } + + foreach ($CAIRole in $CAPolicy.conditions.users.includeRoles) { + foreach ($Member in ($Roles | Where-Object { $_.id -eq $CAIRole }).Members) { + $null = $CAMembers.add($Member.id) + } + } + + $CAMembers = $CAMembers | Select-Object -Unique + + if ($CAMembers) { + $CAPolicy.conditions.users.excludeUsers | ForEach-Object { $null = $CAMembers.remove($_) } + + foreach ($CAEGroup in $CAPolicy.conditions.users.excludeGroups) { + foreach ($Member in ($Groups | Where-Object { $_.id -eq $CAEGroup }).Members) { + $null = $CAMembers.remove($Member.id) + } + } + + foreach ($CAIRole in $CAPolicy.conditions.users.excludeRoles) { + foreach ($Member in ($Roles | Where-Object { $_.id -eq $CAERole }).Members) { + $null = $CAMembers.remove($Member.id) + } + } + } + + [pscustomobject]@{ + ID = $CAPolicy.id + DisplayName = $CAPolicy.DisplayName + Members = $CAMembers + } + } + + if ($ExtensionCache.OneDriveUsage) { + $OneDriveDetails = $ExtensionCache.OneDriveUsage + } else { + $CompanyResult.Errors.add("Company: Unable to fetch One Drive Details $_") + $OneDriveDetails = $null + } + + + + if ($ExtensionCache.CASMailbox) { + $CASFull = $ExtensionCache.CASMailbox + } else { + $CompanyResult.Errors.add('Company: Unable to fetch CAS Mailbox Details') + $CASFull = $null + + } + + + + if ($ExtensionCache.Mailboxes) { + $MailboxDetailedFull = $ExtensionCache.Mailboxes + } else { + $CompanyResult.Errors.add('Company: Unable to fetch Mailbox Details') + $MailboxDetailedFull = $null + } + + + if ($ExtensionCache.MailboxUsage) { + $MailboxStatsFull = $ExtensionCache.MailboxUsage + } else { + $MailboxStatsFull = $null + $CompanyResult.Errors.add('Company: Unable to fetch Mailbox Statistic Details') + } + + $Permissions = $ExtensionCache.MailboxPermissions + if ($licensedUsers) { + $pre = "
+

Licensed Users

+
" + + $post = '
' + + $OutputUsers = foreach ($user in $licensedUsers) { + try { + + $UserGroups = foreach ($Group in $Groups) { + if ($User.id -in $Group.Members.id) { + $FoundGroup = $AllGroups | Where-Object { $_.id -eq $Group.id } + [PSCustomObject]@{ + 'Display Name' = $FoundGroup.displayName + 'Mail Enabled' = $FoundGroup.mailEnabled + 'Mail' = $FoundGroup.mail + 'Security Group' = $FoundGroup.securityEnabled + 'Group Types' = $FoundGroup.groupTypes -join ',' + } + } + } + + $UserPolicies = foreach ($cap in $ConditionalAccessMembers) { + if ($User.id -in $Cap.Members) { + $temp = [PSCustomObject]@{ + displayName = $cap.displayName + } + $temp + } + } + + $PermsRequest = '' + $StatsRequest = '' + $MailboxDetailedRequest = '' + $CASRequest = '' + + $CASRequest = $CASFull | Where-Object { $_.ExternalDirectoryObjectId -eq $User.id } + $MailboxDetailedRequest = $MailboxDetailedFull | Where-Object { $_.Id -eq $User.id } + $StatsRequest = $MailboxStatsFull | Where-Object { $_.'userPrincipalName' -eq $User.UserPrincipalName } + + + $PermsRequest = $Permissions | Where-Object { $_.Identity -eq $User.ID } + + $ParsedPerms = foreach ($Perm in $PermsRequest) { + if ($Perm.User -ne 'NT AUTHORITY\SELF') { + [pscustomobject]@{ + User = $Perm.User + AccessRights = $Perm.PermissionList.AccessRights -join ', ' + } + } + } + + try { + $TotalItemSize = [math]::Round($StatsRequest.storageUsedInBytes / 1Gb, 2) + } catch { + $TotalItemSize = 0 + } + + $UserMailSettings = [pscustomobject]@{ + ForwardAndDeliver = $MailboxDetailedRequest.DeliverToMailboxAndForward + ForwardingAddress = $MailboxDetailedRequest.ForwardingAddress + ' ' + $MailboxDetailedRequest.ForwardingSmtpAddress + LitiationHold = $MailboxDetailedRequest.LitigationHoldEnabled + HiddenFromAddressLists = $MailboxDetailedRequest.HiddenFromAddressListsEnabled + EWSEnabled = $CASRequest.EwsEnabled + MailboxMAPIEnabled = $CASRequest.MAPIEnabled + MailboxOWAEnabled = $CASRequest.OWAEnabled + MailboxImapEnabled = $CASRequest.ImapEnabled + MailboxPopEnabled = $CASRequest.PopEnabled + MailboxActiveSyncEnabled = $CASRequest.ActiveSyncEnabled + Permissions = $ParsedPerms + ProhibitSendQuota = [math]::Round([float]($MailboxDetailedRequest.ProhibitSendQuota -split ' GB')[0], 2) + ProhibitSendReceiveQuota = [math]::Round([float]($MailboxDetailedRequest.ProhibitSendReceiveQuota -split ' GB')[0], 2) + ItemCount = [math]::Round($StatsRequest.'itemCount', 2) + TotalItemSize = $TotalItemSize + } + + $userDevices = ($devices | Where-Object { $_.userPrincipalName -eq $user.UserPrincipalName } | Select-Object @{N = 'Name'; E = { "$($_.deviceName) ($($_.operatingSystem))" } }).name -join '
' + + $UserDevicesDetailsRaw = $devices | Where-Object { $_.userPrincipalName -eq $user.UserPrincipalName } | Select-Object @{N = 'Name'; E = { "
$($_.deviceName)" } }, @{n = 'Owner'; e = { $_.managedDeviceOwnerType } }, ` + @{n = 'Enrolled'; e = { $_.enrolledDateTime } }, ` + @{n = 'Last Sync'; e = { $_.lastSyncDateTime } }, ` + @{n = 'OS'; e = { $_.operatingSystem } }, ` + @{n = 'OS Version'; e = { $_.osVersion } }, ` + @{n = 'State'; e = { $_.complianceState } }, ` + @{n = 'Model'; e = { $_.model } }, ` + @{n = 'Manufacturer'; e = { $_.manufacturer } }, + deviceName, + @{n = 'url'; e = { "https://endpoint.microsoft.com/$($Tenant.defaultDomainName)/#blade/Microsoft_Intune_Devices/DeviceSettingsBlade/overview/mdmDeviceId/$($_.id)" } } + + $aliases = (($user.ProxyAddresses | Where-Object { $_ -cnotmatch 'SMTP' -and $_ -notmatch '.onmicrosoft.com' }) -replace 'SMTP:', ' ') -join ', ' + + $userLicenses = ($user.AssignedLicenses.SkuID | ForEach-Object { + $UserLic = $_ + $SkuPartNumber = ($Licenses | Where-Object { $_.SkuId -eq $UserLic }).SkuPartNumber + $DisplayName = Convert-SKUname -skuName $SkuPartNumber -ConvertTable $LicTable + if (!$DisplayName) { + $DisplayName = $SkuPartNumber + } + $DisplayName + }) -join ', ' + + $UserOneDriveDetails = $OneDriveDetails | Where-Object { $_.ownerPrincipalName -eq $user.UserPrincipalName } + + + + [System.Collections.Generic.List[PSCustomObject]]$OneDriveFormatted = @() + if ($UserOneDriveDetails) { + try { + $OneDriveUsePercent = [math]::Round([float](($UserOneDriveDetails.storageUsedInBytes / $UserOneDriveDetails.storageAllocatedInBytes) * 100), 2) + $StorageUsed = [math]::Round($UserOneDriveDetails.storageUsedInBytes / 1024 / 1024 / 1024, 2) + $StorageAllocated = [math]::Round($UserOneDriveDetails.storageAllocatedInBytes / 1024 / 1024 / 1024, 2) + } catch { + $OneDriveUsePercent = 100 + $StorageUsed = 0 + $StorageAllocated = 0 + } + + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Owner Principal Name' -Value "$($UserOneDriveDetails.ownerPrincipalName)")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'One Drive URL' -Value "$($UserOneDriveDetails.siteUrl)")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Is Deleted' -Value "$($UserOneDriveDetails.isDeleted)")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Last Activity Date' -Value "$($UserOneDriveDetails.lastActivityDate)")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'File Count' -Value "$($UserOneDriveDetails.fileCount)")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Active File Count' -Value "$($UserOneDriveDetails.activeFileCount)")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Storage Used (Byte)' -Value "$($UserOneDriveDetails.storageUsedInBytes)")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Storage Allocated (Byte)' -Value "$($UserOneDriveDetails.storageAllocatedInBytes)")) + $OneDriveUserUsage = @" +
+
+
+
+
$($StorageUsed) GB used, $OneDriveUsePercent% of $($StorageAllocated) GB
+
+"@ + + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'One Drive Usage' -Value $OneDriveUserUsage)) + } + + [System.Collections.Generic.List[PSCustomObject]]$UserMailSettingsFormatted = @() + [System.Collections.Generic.List[PSCustomObject]]$UserMailboxDetailsFormatted = @() + if ($UserMailSettings) { + $UserMailSettingsFormatted.add($(Get-HuduFormattedField -Title 'Forward and Deliver' -Value "$($UserMailSettings.ForwardAndDeliver)")) + $UserMailSettingsFormatted.add($(Get-HuduFormattedField -Title 'Forwarding Address' -Value "$($UserMailSettings.ForwardingAddress)")) + $UserMailSettingsFormatted.add($(Get-HuduFormattedField -Title 'Litiation Hold' -Value "$($UserMailSettings.LitiationHold)")) + $UserMailSettingsFormatted.add($(Get-HuduFormattedField -Title 'Hidden From Address Lists' -Value "$($UserMailSettings.HiddenFromAddressLists)")) + $UserMailSettingsFormatted.add($(Get-HuduFormattedField -Title 'EWS Enabled' -Value "$($UserMailSettings.EWSEnabled)")) + $UserMailSettingsFormatted.add($(Get-HuduFormattedField -Title 'MAPI Enabled' -Value "$($UserMailSettings.MailboxMAPIEnabled)")) + $UserMailSettingsFormatted.add($(Get-HuduFormattedField -Title 'OWA Enabled' -Value "$($UserMailSettings.MailboxOWAEnabled)")) + $UserMailSettingsFormatted.add($(Get-HuduFormattedField -Title 'IMAP Enabled' -Value "$($UserMailSettings.MailboxImapEnabled)")) + $UserMailSettingsFormatted.add($(Get-HuduFormattedField -Title 'POP Enabled' -Value "$($UserMailSettings.MailboxPopEnabled)")) + $UserMailSettingsFormatted.add($(Get-HuduFormattedField -Title 'Active Sync Enabled' -Value "$($UserMailSettings.MailboxActiveSyncEnabled)")) + + + $UserMailboxDetailsFormatted.add($(Get-HuduFormattedField -Title 'Permissions' -Value "$($UserMailSettings.Permissions | ConvertTo-Html -Fragment | Out-String)")) + $UserMailboxDetailsFormatted.add($(Get-HuduFormattedField -Title 'Prohibit Send Quota' -Value "$($UserMailSettings.ProhibitSendQuota)")) + $UserMailboxDetailsFormatted.add($(Get-HuduFormattedField -Title 'Prohibit Send Receive Quota' -Value "$($UserMailSettings.ProhibitSendReceiveQuota)")) + $UserMailboxDetailsFormatted.add($(Get-HuduFormattedField -Title 'Item Count' -Value "$($UserMailSettings.ItemCount)")) + $UserMailboxDetailsFormatted.add($(Get-HuduFormattedField -Title 'Total Mailbox Size' -Value "$($UserMailSettings.TotalItemSize)")) + try { + $UserMailboxUsePercent = [math]::Round([float](($UserMailSettings.TotalItemSize / $UserMailSettings.ProhibitSendReceiveQuota) * 100), 2) + } catch { + $UserMailboxUsePercent = 100 + } + $UserMailboxUsage = @" +
+
+
+
+
$([math]::Round($UserMailSettings.TotalItemSize,2)) GB used, $UserMailboxUsePercent% of $([math]::Round($UserMailSettings.ProhibitSendReceiveQuota, 2)) GB
+
+"@ + $UserMailboxDetailsFormatted.add($(Get-HuduFormattedField -Title 'Mailbox Usage' -Value $UserMailboxUsage)) + + } + + $UserPoliciesFormatted = '' + + [System.Collections.Generic.List[PSCustomObject]]$UserOverviewFormatted = @() + $UserOverviewFormatted.add($(Get-HuduFormattedField -Title 'User Name' -Value "$($User.displayName)")) + $UserOverviewFormatted.add($(Get-HuduFormattedField -Title 'User Principal Name' -Value "$($User.userPrincipalName)")) + $UserOverviewFormatted.add($(Get-HuduFormattedField -Title 'User ID' -Value "$($User.ID)")) + $UserOverviewFormatted.add($(Get-HuduFormattedField -Title 'User Enabled' -Value "$($User.accountEnabled)")) + $UserOverviewFormatted.add($(Get-HuduFormattedField -Title 'Job Title' -Value "$($User.jobTitle)")) + $UserOverviewFormatted.add($(Get-HuduFormattedField -Title 'Mobile Phone' -Value "$($User.mobilePhone)")) + $UserOverviewFormatted.add($(Get-HuduFormattedField -Title 'Business Phones' -Value "$($User.businessPhones -join ', ')")) + $UserOverviewFormatted.add($(Get-HuduFormattedField -Title 'Office Location' -Value "$($User.officeLocation)")) + $UserOverviewFormatted.add($(Get-HuduFormattedField -Title 'Aliases' -Value "$aliases")) + $UserOverviewFormatted.add($(Get-HuduFormattedField -Title 'Licenses' -Value "$($userLicenses)")) + + $AssignedPlans = $User.assignedplans | Where-Object { $_.capabilityStatus -eq 'Enabled' } | Select-Object @{n = 'Assigned'; e = { $_.assignedDateTime } }, @{n = 'Service'; e = { $_.service } } -Unique + [System.Collections.Generic.List[PSCustomObject]]$AssignedPlansFormatted = @() + foreach ($AssignedPlan in $AssignedPlans) { + if ($AssignedPlan.service -in ($AssignedMap | Get-Member -MemberType NoteProperty).name) { + $CSSClass = $AssignedMap."$($AssignedPlan.service)" + $PlanDisplayName = $AssignedNameMap."$($AssignedPlan.service)" + $ParsedDate = Get-Date($AssignedPlan.Assigned) -Format 'yyyy-MM-dd HH:mm:ss' + $AssignedPlansFormatted.add("
$PlanDisplayNameAssigned $($ParsedDate)
") + } + } + $AssignedPlansBlock = "
$($AssignedPlansFormatted -join '')
" + + if ($UserMailSettingsFormatted) { + $UserMailSettingsBlock = Get-HuduFormattedBlock -Heading 'Mailbox Settings' -Body ($UserMailSettingsFormatted -join '') + } else { + $UserMailSettingsBlock = $null + } + + if ($UserMailboxDetailsFormatted) { + $UserMailDetailsBlock = Get-HuduFormattedBlock -Heading 'Mailbox Details' -Body ($UserMailboxDetailsFormatted -join '') + } else { + $UserMailDetailsBlock = $null + } + + if ($UserGroups) { + $UserGroupsBlock = Get-HuduFormattedBlock -Heading 'User Groups' -Body $($UserGroups | ConvertTo-Html -Fragment -As Table | Out-String) + } else { + $UserGroupsBlock = $null + } + + if ($UserPoliciesFormatted) { + $UserPoliciesBlock = Get-HuduFormattedBlock -Heading 'Assigned Conditional Access Policies' -Body $UserPoliciesFormatted + } else { + $UserPoliciesBlock = $null + } + + if ($OneDriveFormatted) { + $OneDriveBlock = Get-HuduFormattedBlock -Heading 'One Drive Details' -Body ($OneDriveFormatted -join '') + } else { + $OneDriveBlock = $null + } + + if ($UserOverviewFormatted) { + $UserOverviewBlock = Get-HuduFormattedBlock -Heading 'User Details' -Body $UserOverviewFormatted + } else { + $UserOverviewBlock = $null + } + + if ($UserDevicesDetailsRaw) { + $UserDevicesDetailsBlock = Get-HuduFormattedBlock -Heading 'Intune Devices' -Body $($UserDevicesDetailsRaw | Select-Object -ExcludeProperty deviceName, url | ConvertTo-Html -Fragment | ForEach-Object { $tmp = $_ -replace '<', '<'; $tmp -replace '>', '>'; } | Out-String) + } else { + $UserDevicesDetailsBlock = $null + } + + $HuduUser = $People | Where-Object { $_.email_address -eq $user.UserPrincipalName -or $_.primary_mail -eq $user.UserPrincipalName -or ($_.cards.integrator_name -eq 'cw_manage' -and $_.cards.data.communicationItems.communicationType -eq 'Email' -and $_.cards.data.communicationItems.value -eq $user.UserPrincipalName) } + + [System.Collections.Generic.List[PSCustomObject]]$CIPPLinksFormatted = @() + if ($EnableCIPP) { + $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/identity/administration/users/view?customerId=$($Tenant.customerid)&userId=$($User.id)&tenantDomain=$($Tenant.defaultDomainName)&userEmail=$($User.UserPrincipalName)" -Icon 'far fa-eye' -Title 'CIPP - View User')) + $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/identity/administration/users/edit?customerId=$($Tenant.customerid)&userId=$($User.id)&tenantDomain=$($Tenant.defaultDomainName)&userEmail=$($User.UserPrincipalName)" -Icon 'fas fa-user-cog' -Title 'CIPP - Edit User')) + $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/identity/administration/ViewBec?customerId=$($Tenant.customerid)&userId=$($User.id)&tenantDomain=$($Tenant.defaultDomainName)&userEmail=$($User.UserPrincipalName)" -Icon 'fas fa-user-secret' -Title 'CIPP - BEC Tool')) + } + + [System.Collections.Generic.List[PSCustomObject]]$UserLinksFormatted = @() + $UserLinksFormatted.add((Get-HuduLinkBlock -URL "https://aad.portal.azure.com/$($Tenant.defaultDomainName)/#blade/Microsoft_AAD_IAM/UserDetailsMenuBlade/Profile/userId/$($User.id)" -Icon 'fas fa-users-cog' -Title 'Entra ID')) + $UserLinksFormatted.add((Get-HuduLinkBlock -URL "https://aad.portal.azure.com/$($Tenant.defaultDomainName)/#blade/Microsoft_AAD_IAM/UserDetailsMenuBlade/SignIns/userId/$($User.id)" -Icon 'fas fa-history' -Title 'Sign Ins')) + $UserLinksFormatted.add((Get-HuduLinkBlock -URL "https://admin.teams.microsoft.com/users/$($User.id)/account?delegatedOrg=$($Tenant.defaultDomainName)" -Icon 'fas fa-users' -Title 'Teams Admin')) + $UserLinksFormatted.add((Get-HuduLinkBlock -URL "https://intune.microsoft.com/$($Tenant.defaultDomainName)/#blade/Microsoft_AAD_IAM/UserDetailsMenuBlade/Profile/userId/$($User.ID)" -Icon 'fas fa-laptop' -Title 'Intune (User)')) + $UserLinksFormatted.add((Get-HuduLinkBlock -URL "https://intune.microsoft.com/$($Tenant.defaultDomainName)/#blade/Microsoft_AAD_IAM/UserDetailsMenuBlade/Devices/userId/$($User.ID)" -Icon 'fas fa-laptop' -Title 'Intune (Devices)')) + + if ($HuduUser) { + $HaloCard = $HuduUser.cards | Where-Object { $_.integrator_name -eq 'halo' } + if ($HaloCard) { + $UserLinksFormatted.add((Get-HuduLinkBlock -URL "$($PSAUserUrl)$($HaloCard.sync_id)" -Icon 'far fa-id-card' -Title 'Halo PSA')) + } + } + + $UserLinksBlock = "
Management Links
$($UserLinksFormatted -join '')$($CIPPLinksFormatted -join '')
" + + $UserBody = "
$AssignedPlansBlock
$UserLinksBlock
$($UserOverviewBlock)$($UserMailDetailsBlock)$($OneDriveBlock)$($UserMailSettingsBlock)$($UserPoliciesBlock)
$($UserDevicesDetailsBlock)
$($UserGroupsBlock)
" + + if ($PeopleLayoutId) { + $UserAssetFields = @{ + microsoft_365 = $UserBody + email_address = $user.UserPrincipalName + } + $NewHash = Get-StringHash -String $UserBody + + $HuduUserCount = ($HuduUser | Measure-Object).count + if ($HuduUserCount -eq 1) { + $ExistingAsset = Get-CIPPAzDataTableEntity @HuduAssetCache -Filter "PartitionKey eq 'HuduUser' and CompanyId eq '$company_id' and RowKey eq '$($HuduUser.id)'" + $ExistingHash = $ExistingAsset.Hash + + if (!$ExistingAsset -or $ExistingHash -ne $NewHash) { + $null = Set-HuduAsset -asset_id $HuduUser.id -Name $HuduUser.name -company_id $company_id -asset_layout_id $PeopleLayout.id -Fields $UserAssetFields + $AssetCache = [PSCustomObject]@{ + PartitionKey = 'HuduUser' + RowKey = [string]$HuduUser.id + CompanyId = [string]$company_id + Hash = [string]$NewHash + } + Add-CIPPAzDataTableEntity @HuduAssetCache -Entity $AssetCache -Force + } + + } elseif ($HuduUserCount -eq 0) { + if ($CreateUsers -eq $True) { + $HuduUser = (New-HuduAsset -Name $User.DisplayName -company_id $company_id -asset_layout_id $PeopleLayout.id -Fields $UserAssetFields -primary_mail $user.UserPrincipalName).asset + $AssetCache = [PSCustomObject]@{ + PartitionKey = 'HuduUser' + RowKey = [string]$HuduUser.id + CompanyId = [string]$company_id + Hash = [string]$NewHash + } + Add-CIPPAzDataTableEntity @HuduAssetCache -Entity $AssetCache -Force + } + } else { + $CompanyResult.Errors.add("User $($User.UserPrincipalName): Multiple Users Matched to email address in Hudu: ($($HuduUser.name -join ', ') - $($($HuduUser.id -join ', '))) $_") + } + } + + $UserLink = "$($user.DisplayName)" + + [PSCustomObject]@{ + 'Display Name' = $UserLink + 'Addresses' = "$($user.UserPrincipalName)
$aliases" + 'EPM Devices' = $userDevices + 'Assigned Licenses' = $userLicenses + 'Options' = "Azure AD | M365 Admin" + } + } catch { + $CompanyResult.Errors.add("User $($User.UserPrincipalName): A fatal error occured while processing user $_") + } + } + + $licensedUserHTML = $OutputUsers | ConvertTo-Html -PreContent $pre -PostContent $post -Fragment | ForEach-Object { $tmp = $_ -replace '<', '<'; $tmp -replace '>', '>'; } | Out-String + + } + + foreach ($Device in $Devices) { + try { + [System.Collections.Generic.List[PSCustomObject]]$DeviceOverviewFormatted = @() + $DeviceOverviewFormatted.add($(Get-HuduFormattedField -Title 'Device Name' -Value "$($Device.deviceName)")) + $DeviceOverviewFormatted.add($(Get-HuduFormattedField -Title 'User' -Value "$($Device.userDisplayName)")) + $DeviceOverviewFormatted.add($(Get-HuduFormattedField -Title 'User Email' -Value "$($Device.userPrincipalName)")) + $DeviceOverviewFormatted.add($(Get-HuduFormattedField -Title 'Owner' -Value "$($Device.ownerType)")) + $DeviceOverviewFormatted.add($(Get-HuduFormattedField -Title 'Enrolled' -Value "$($Device.enrolledDateTime)")) + $DeviceOverviewFormatted.add($(Get-HuduFormattedField -Title 'Last Checkin' -Value "$($Device.lastSyncDateTime)")) + if ($Device.complianceState -eq 'compliant') { + $CompliantSymbol = '   ' + } else { + $CompliantSymbol = '   ' + } + $DeviceOverviewFormatted.add($(Get-HuduFormattedField -Title 'Compliant' -Value "$($CompliantSymbol)$($Device.complianceState)")) + $DeviceOverviewFormatted.add($(Get-HuduFormattedField -Title 'Management Type' -Value "$($Device.managementAgent)")) + + [System.Collections.Generic.List[PSCustomObject]]$DeviceHardwareFormatted = @() + $DeviceHardwareFormatted.add($(Get-HuduFormattedField -Title 'Serial Number' -Value "$($Device.serialNumber)")) + $DeviceHardwareFormatted.add($(Get-HuduFormattedField -Title 'OS' -Value "$($Device.operatingSystem)")) + $DeviceHardwareFormatted.add($(Get-HuduFormattedField -Title 'OS Versions' -Value "$($Device.osVersion)")) + $DeviceHardwareFormatted.add($(Get-HuduFormattedField -Title 'Chassis' -Value "$($Device.chassisType)")) + $DeviceHardwareFormatted.add($(Get-HuduFormattedField -Title 'Model' -Value "$($Device.model)")) + $DeviceHardwareFormatted.add($(Get-HuduFormattedField -Title 'Manufacturer' -Value "$($Device.manufacturer)")) + $DeviceHardwareFormatted.add($(Get-HuduFormattedField -Title 'Total Storage' -Value "$([math]::Round($Device.totalStorageSpaceInBytes /1024 /1024 /1024, 2))")) + $DeviceHardwareFormatted.add($(Get-HuduFormattedField -Title 'Free Storage' -Value "$([math]::Round($Device.freeStorageSpaceInBytes /1024 /1024 /1024, 2))")) + + [System.Collections.Generic.List[PSCustomObject]]$DeviceEnrollmentFormatted = @() + $DeviceEnrollmentFormatted.add($(Get-HuduFormattedField -Title 'Enrollment Type' -Value "$($Device.deviceEnrollmentType)")) + $DeviceEnrollmentFormatted.add($(Get-HuduFormattedField -Title 'Join Type' -Value "$($Device.joinType)")) + $DeviceEnrollmentFormatted.add($(Get-HuduFormattedField -Title 'Registration State' -Value "$($Device.deviceRegistrationState)")) + $DeviceEnrollmentFormatted.add($(Get-HuduFormattedField -Title 'Autopilot Enrolled' -Value "$($Device.autopilotEnrolled)")) + $DeviceEnrollmentFormatted.add($(Get-HuduFormattedField -Title 'Device Guard Requirements' -Value "$($Device.hardwareinformation.deviceGuardVirtualizationBasedSecurityHardwareRequirementState)")) + $DeviceEnrollmentFormatted.add($(Get-HuduFormattedField -Title 'Virtualistation Based Security' -Value "$($Device.hardwareinformation.deviceGuardVirtualizationBasedSecurityState)")) + $DeviceEnrollmentFormatted.add($(Get-HuduFormattedField -Title 'Credential Guard' -Value "$($Device.hardwareinformation.deviceGuardLocalSystemAuthorityCredentialGuardState)")) + + $DevicePoliciesTable = foreach ($Policy in $DeviceComplianceDetails) { + if ($device.deviceName -in $Policy.DeviceStatuses.deviceDisplayName) { + $Status = $Policy.DeviceStatuses | Where-Object { $_.deviceDisplayName -eq $device.deviceName } + if ($Status.status -ne 'unknown') { + [PSCustomObject]@{ + Name = $Policy.DisplayName + Status = ($Status.status | Select-Object -Unique) -join ', ' + 'Last Report' = "$(Get-Date($Status.lastReportedDateTime[0]) -Format 'yyyy-MM-dd HH:mm:ss')" + 'Grace Expiry' = "$(Get-Date($Status.complianceGracePeriodExpirationDateTime[0]) -Format 'yyyy-MM-dd HH:mm:ss')" + } + } + } + } + $DevicePoliciesFormatted = $DevicePoliciesTable | ConvertTo-Html -Fragment | Out-String + + $DeviceGroupsTable = foreach ($Group in $Groups) { + if ($device.azureADDeviceId -in $Group.members.deviceId) { + [PSCustomObject]@{ + Name = $Group.displayName + } + } + } + $DeviceGroupsFormatted = $DeviceGroupsTable | ConvertTo-Html -Fragment | Out-String + + $DeviceAppsTable = foreach ($App in $DeviceAppInstallDetails) { + if ($device.id -in $App.InstalledAppDetails.deviceId) { + $Status = $App.InstalledAppDetails | Where-Object { $_.deviceId -eq $device.id } + [PSCustomObject]@{ + Name = $App.DisplayName + 'Install Status' = ($Status.installState | Select-Object -Unique ) -join ',' + } + } + } + $DeviceAppsFormatted = $DeviceAppsTable | ConvertTo-Html -Fragment | Out-String + + $DeviceOverviewBlock = Get-HuduFormattedBlock -Heading 'Device Details' -Body ($DeviceOverviewFormatted -join '') + $DeviceHardwareBlock = Get-HuduFormattedBlock -Heading 'Hardware Details' -Body ($DeviceHardwareFormatted -join '') + $DeviceEnrollmentBlock = Get-HuduFormattedBlock -Heading 'Device Enrollment Details' -Body ($DeviceEnrollmentFormatted -join '') + $DevicePolicyBlock = Get-HuduFormattedBlock -Heading 'Compliance Policies' -Body ($DevicePoliciesFormatted -join '') + $DeviceAppsBlock = Get-HuduFormattedBlock -Heading 'App Details' -Body ($DeviceAppsFormatted -join '') + $DeviceGroupsBlock = Get-HuduFormattedBlock -Heading 'Device Groups' -Body ($DeviceGroupsFormatted -join '') + + if ("$($device.serialNumber)" -in $ExcludeSerials) { + $HuduDevice = $HuduDevices | Where-Object { $_.name -eq $device.deviceName -or ($_.cards.integrator_name -eq 'cw_manage' -and $_.cards.data.name -contains $device.deviceName) } + } else { + $HuduDevice = $HuduDevices | Where-Object { $_.primary_serial -eq $device.serialNumber -or ($_.cards.integrator_name -eq 'cw_manage' -and $_.cards.data.serialNumber -eq $device.serialNumber) } + } + + [System.Collections.Generic.List[PSCustomObject]]$DeviceLinksFormatted = @() + $DeviceLinksFormatted.add((Get-HuduLinkBlock -URL "https://endpoint.microsoft.com/$($Tenant.defaultDomainName)/#blade/Microsoft_Intune_Devices/DeviceSettingsBlade/overview/mdmDeviceId/$($Device.id)" -Icon 'fas fa-laptop' -Title 'Endpoint Manager')) + + if ($HuduDevice) { + $DRMMCard = $HuduDevice.cards | Where-Object { $_.integrator_name -eq 'dattormm' } + if ($DRMMCard) { + $DeviceLinksFormatted.add((Get-HuduLinkBlock -URL "$($RMMDeviceURL)$($DRMMCard.data.id)" -Icon 'fas fa-laptop-code' -Title 'Datto RMM')) + $DeviceLinksFormatted.add((Get-HuduLinkBlock -URL "$($RMMRemoteURL)$($DRMMCard.data.id)" -Icon 'fas fa-desktop' -Title 'Datto RMM Remote')) + } + $ManageCard = $HuduDevice.cards | Where-Object { $_.integrator_name -eq 'cw_manage' } + if ($ManageCard) { + $DeviceLinksFormatted.add((Get-HuduLinkBlock -URL $ManageCard.data.managementLink -Icon 'fas fa-laptop-code' -Title 'CW Automate')) + $DeviceLinksFormatted.add((Get-HuduLinkBlock -URL $ManageCard.data.remoteLink -Icon 'fas fa-desktop' -Title 'CW Control')) + } + } + + $DeviceLinksBlock = "
Management Links
$($DeviceLinksFormatted -join '')
" + + $DeviceIntuneDetailshtml = "
$DeviceLinksBlock
$($DeviceOverviewBlock)$($DeviceHardwareBlock)$($DeviceEnrollmentBlock)$($DevicePolicyBlock)$($DeviceAppsBlock)$($DeviceGroupsBlock)
" + + $DeviceAssetFields = @{ + microsoft_365 = $DeviceIntuneDetailshtml + } + $NewHash = Get-StringHash -String $DeviceIntuneDetailshtml + + if ($DeviceLayoutId) { + if ($HuduDevice) { + if (($HuduDevice | Measure-Object).count -eq 1) { + $ExistingAsset = Get-CIPPAzDataTableEntity @HuduAssetCache -Filter "PartitionKey eq 'HuduDevice' and CompanyId eq '$company_id' and RowKey eq '$($HuduDevice.id)'" + $ExistingHash = $ExistingAsset.Hash + + if (!$ExistingAsset -or $ExistingAsset.Hash -ne $NewHash) { + $null = Set-HuduAsset -asset_id $HuduDevice.id -Name $HuduDevice.name -company_id $company_id -asset_layout_id $HuduDevice.asset_layout_id -Fields $DeviceAssetFields -PrimarySerial $Device.serialNumber + $AssetCache = [PSCustomObject]@{ + PartitionKey = 'HuduDevice' + RowKey = [string]$HuduDevice.id + CompanyId = [string]$company_id + Hash = [string]$NewHash + } + Add-CIPPAzDataTableEntity @HuduAssetCache -Entity $AssetCache -Force + + $HuduUser = $People | Where-Object { $_.primary_mail -eq $Device.userPrincipalName -or ($_.cards.integrator_name -eq 'cw_manage' -and $_.cards.data.communicationItems.communicationType -eq 'Email' -and $_.cards.data.communicationItems.value -eq $Device.userPrincipalName) } + + if ($HuduUser) { + $Relation = $HuduRelations | Where-Object { $_.fromable_type -eq 'Asset' -and $_.fromable_id -eq $HuduUser.id -and $_.toable_type -eq 'Asset' -and $_toable_id -eq $HuduDevice.id } + if (-not $Relation) { + try { + $null = New-HuduRelation -FromableType 'Asset' -FromableID $HuduUser.id -ToableType 'Asset' -ToableID $HuduDevice.id -ea stop + } catch {} + } + } + } + } else { + $CompanyResult.Errors.add("Device $($HuduDevice.name): Multiple devices matched on name or serial ($($device.serialNumber -join ', '))") + } + } else { + if ($device.deviceType -in $IntuneDesktopDeviceTypes) { + $DeviceLayoutID = $DesktopsLayout.id + $DeviceCreation = $CreateDevices + } else { + $DeviceLayoutID = $MobilesLayout.id + $DeviceCreation = $CreateMobileDevices + } + if ($DeviceCreation -eq $true) { + $HuduDevice = (New-HuduAsset -Name $device.deviceName -company_id $company_id -asset_layout_id $DeviceLayoutID -Fields $DeviceAssetFields -PrimarySerial $Device.serialNumber).asset + + $AssetCache = [PSCustomObject]@{ + PartitionKey = 'HuduDevice' + RowKey = [string]$HuduDevice.id + CompanyId = [string]$company_id + Hash = [string]$NewHash + } + Add-CIPPAzDataTableEntity @HuduAssetCache -Entity $AssetCache -Force + + $HuduUser = $People | Where-Object { $_.primary_mail -eq $Device.userPrincipalName -or ($_.cards.integrator_name -eq 'cw_manage' -and $_.cards.data.communicationItems.communicationType -eq 'Email' -and $_.cards.data.communicationItems.value -eq $Device.userPrincipalName) } + if ($HuduUser) { + try { + $null = New-HuduRelation -FromableType 'Asset' -FromableID $HuduUser.id -ToableType 'Asset' -ToableID $HuduDevice.id -ea stop + } catch { + # No need to do anything here as its will be when relations already exist. + } + } + } + } + } + } catch { + $CompanyResult.Errors.add("Device $($device.deviceName): A Fatal Error occured while processing the device $_") + } + + } + + + $body = "
+
+

Administrative Portals

+
+
$CustomerLinks
+
+
+
+
+ $detailstable + $licenseHTML +
+
+
+ $RolesHtml +
+
+
+ $licensedUserHTML +
" + + try { + $null = Set-HuduMagicDash -Title "Microsoft 365 - $($Tenant.displayName)" -company_name $TenantMap.IntegrationName -Message "$($licensedUsers.count) Licensed Users" -Icon 'fab fa-microsoft' -Content $body -Shade 'success' + } catch { + $CompanyResult.Errors.add("Company: Failed to add Magic Dash to Company: $_") + } + + try { + if ($importDomains) { + $domainstoimport = $RawDomains + foreach ($imp in $domainstoimport) { + $impdomain = $imp.id + $huduimpdomain = Get-HuduWebsites -Name "https://$impdomain" + if ($($huduimpdomain.id.count) -eq 0) { + if ($monitorDomains) { + $null = New-HuduWebsite -Name "https://$impdomain" -Notes $HuduNotes -Paused 'false' -CompanyId $company_id -DisableDNS 'false' -DisableSSL 'false' -DisableWhois 'false' + } else { + $null = New-HuduWebsite -Name "https://$impdomain" -Notes $HuduNotes -Paused 'true' -CompanyId $company_id -DisableDNS 'true' -DisableSSL 'true' -DisableWhois 'true' + } + + } + } + + } + } catch { + $CompanyResult.Errors.add("Company: Failed to import domain: $_") + Write-LogMessage -tenant $Tenant.defaultDomainName -tenantid $Tenant.customerId -API 'Hudu Sync' -message "Company: Failed to import domain: $_" -level 'Error' + } + Write-LogMessage -tenant $Tenant.defaultDomainName -tenantid $Tenant.customerId -API 'Hudu Sync' -message 'Company: Completed Sync' -level 'Information' + } catch { + $CompanyResult.Errors.add("Company: A fatal error occured: $_") + Write-LogMessage -tenant $Tenant.defaultDomainName -tenantid $Tenant.customerId -API 'Hudu Sync' -message "Company: A fatal error occured: $_" -level 'Error' + } + return $CompanyResult +} diff --git a/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 new file mode 100644 index 000000000000..03c6dddb8fb3 --- /dev/null +++ b/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 @@ -0,0 +1,26 @@ +function Set-HuduMapping { + [CmdletBinding()] + param ( + $CIPPMapping, + $APIName, + $Request + ) + Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'HuduMapping'" | ForEach-Object { + Remove-AzDataTableEntity @CIPPMapping -Entity $_ + } + foreach ($Mapping in ([pscustomobject]$Request.body.mappings).psobject.properties) { + $AddObject = @{ + PartitionKey = 'HuduMapping' + 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' + } + $Result = [pscustomobject]@{'Results' = 'Successfully edited mapping table.' } + + Return $Result +} \ No newline at end of file diff --git a/Modules/CippExtensions/Public/New-CippExtAlert.ps1 b/Modules/CippExtensions/Public/New-CippExtAlert.ps1 index 827347a613d2..54f9faf81258 100644 --- a/Modules/CippExtensions/Public/New-CippExtAlert.ps1 +++ b/Modules/CippExtensions/Public/New-CippExtAlert.ps1 @@ -8,21 +8,22 @@ function New-CippExtAlert { $Table = Get-CIPPTable -TableName Extensionsconfig $Configuration = (Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -Depth 10 $MappingTable = Get-CIPPTable -TableName CippMapping - $MappingFile = (Get-CIPPAzDataTableEntity @MappingTable) + foreach ($ConfigItem in $Configuration.psobject.properties.name) { switch ($ConfigItem) { - "HaloPSA" { + 'HaloPSA' { If ($Configuration.HaloPSA.enabled) { + $MappingFile = Get-CIPPAzDataTableEntity @MappingTable -Filter 'PartitionKey eq HaloMapping' $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 { $_.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 } @@ -30,4 +31,4 @@ function New-CippExtAlert { } } -} \ No newline at end of file +} diff --git a/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 new file mode 100644 index 000000000000..e88f53ceba9c --- /dev/null +++ b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 @@ -0,0 +1,117 @@ +function Get-NinjaOneFieldMapping { + [CmdletBinding()] + param ( + $CIPPMapping + ) + try { + #Get available mappings + $Mappings = [pscustomobject]@{} + + [System.Collections.Generic.List[object]]$CIPPFieldHeaders = @( + [PSCustomObject]@{ + 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]@{ + FieldName = 'TenantSummary' + FieldLabel = 'Microsoft 365 Tenant Summary - Field Used to Display Tenant Summary Information' + FieldType = 'Organization' + Type = 'WYSIWYG' + }, + [PSCustomObject]@{ + FieldName = 'UsersSummary' + FieldLabel = 'Microsoft 365 Users Summary - Field Used to Display User Summary Information' + FieldType = 'Organization' + Type = 'WYSIWYG' + }, + [PSCustomObject]@{ + FieldName = 'DeviceLinks' + FieldLabel = 'Microsoft 365 Device Links - Field Used to Display Links to Microsoft 365 Portals and CIPP' + FieldType = 'Device' + Type = 'WYSIWYG' + }, + [PSCustomObject]@{ + FieldName = 'DeviceSummary' + FieldLabel = 'Microsoft 365 Device Summary - Field Used to Display Device Summary Information' + FieldType = 'Device' + Type = 'WYSIWYG' + }, + [PSCustomObject]@{ + FieldName = 'DeviceCompliance' + FieldLabel = 'Intune Device Compliance Status - Field Used to Monitor Device Compliance' + FieldType = 'Device' + Type = 'TEXT' + } + ) + + $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[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[object]]$NinjaCustomFieldsOrg = @() + } + $Unset = [PSCustomObject]@{ + name = '--- Do not synchronize ---' + value = $null + type = 'unset' + } + + } catch { + [System.Collections.Generic.List[object]]$NinjaCustomFieldsNode = @() + [System.Collections.Generic.List[objecgt]]$NinjaCustomFieldsOrg = @() + } + + $MappingObj = [PSCustomObject]@{ + CIPPFields = $CIPPFields + CIPPFieldHeaders = $CIPPFieldHeaders + IntegrationFields = @($Unset) + @($NinjaCustomFieldsOrg) + @($NinjaCustomFieldsNode) + Mappings = $Mappings + } + + return $MappingObj + +} \ No newline at end of file diff --git a/Modules/CippExtensions/NinjaOne/Get-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneOrgMapping.ps1 similarity index 64% rename from Modules/CippExtensions/NinjaOne/Get-NinjaOneOrgMapping.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneOrgMapping.ps1 index a0274cb0d16f..24c7e6405560 100644 --- a/Modules/CippExtensions/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 + $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/NinjaOne/Get-NinjaOneToken.ps1 b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneToken.ps1 similarity index 99% rename from Modules/CippExtensions/NinjaOne/Get-NinjaOneToken.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneToken.ps1 index d4cb86838ed5..ba293404b5bd 100644 --- a/Modules/CippExtensions/NinjaOne/Get-NinjaOneToken.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneToken.ps1 @@ -17,7 +17,6 @@ function Get-NinjaOneToken { $ClientSecret = $ENV:NinjaClientSecret } - $body = @{ grant_type = 'client_credentials' client_id = $Configuration.ClientId diff --git a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 similarity index 90% rename from Modules/CippExtensions/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 index a8363917efcc..9213a7015b1a 100644 --- a/Modules/CippExtensions/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/NinjaOne/Invoke-NinjaOneDocumentTemplate.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDocumentTemplate.ps1 similarity index 100% rename from Modules/CippExtensions/NinjaOne/Invoke-NinjaOneDocumentTemplate.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDocumentTemplate.ps1 diff --git a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 similarity index 97% rename from Modules/CippExtensions/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 index 1faf7d92c833..ca69e5b10935 100644 --- a/Modules/CippExtensions/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/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 similarity index 93% rename from Modules/CippExtensions/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 index 6ea239b73e36..6b5687d6059f 100644 --- a/Modules/CippExtensions/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/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 similarity index 69% rename from Modules/CippExtensions/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 index 317770c3bb78..c3f05acf1cc3 100644 --- a/Modules/CippExtensions/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/NinjaOne/Invoke-NinjaOneSync.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneSync.ps1 similarity index 91% rename from Modules/CippExtensions/NinjaOne/Invoke-NinjaOneSync.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneSync.ps1 index 5567ddb7c1b8..c6fb732eb30a 100644 --- a/Modules/CippExtensions/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/NinjaOne/Invoke-NinjaOneTenantSync.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 similarity index 96% rename from Modules/CippExtensions/NinjaOne/Invoke-NinjaOneTenantSync.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 index 9828682c6348..9a3b6949b056 100644 --- a/Modules/CippExtensions/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 = '
' + $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/NinjaOne/NinjaOneHelper.ps1 b/Modules/CippExtensions/Public/NinjaOne/NinjaOneHelper.ps1 similarity index 100% rename from Modules/CippExtensions/NinjaOne/NinjaOneHelper.ps1 rename to Modules/CippExtensions/Public/NinjaOne/NinjaOneHelper.ps1 diff --git a/Modules/CippExtensions/NinjaOne/Push-NinjaOneQueue.ps1 b/Modules/CippExtensions/Public/NinjaOne/Push-NinjaOneQueue.ps1 similarity index 100% rename from Modules/CippExtensions/NinjaOne/Push-NinjaOneQueue.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Push-NinjaOneQueue.ps1 diff --git a/Modules/CippExtensions/NinjaOne/Set-NinjaOneFieldMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneFieldMapping.ps1 similarity index 72% rename from Modules/CippExtensions/NinjaOne/Set-NinjaOneFieldMapping.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneFieldMapping.ps1 index 4653d51ed13a..87d243b8cda1 100644 --- a/Modules/CippExtensions/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/NinjaOne/Set-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1 similarity index 71% rename from Modules/CippExtensions/NinjaOne/Set-NinjaOneOrgMapping.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1 index ee09580b94bf..43b1c597e3b0 100644 --- a/Modules/CippExtensions/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/Modules/CippExtensions/Public/New-PwPushLink.ps1 b/Modules/CippExtensions/Public/PwPush/New-PwPushLink.ps1 similarity index 100% rename from Modules/CippExtensions/Public/New-PwPushLink.ps1 rename to Modules/CippExtensions/Public/PwPush/New-PwPushLink.ps1 diff --git a/Modules/CippExtensions/Private/Set-PwPushConfig.ps1 b/Modules/CippExtensions/Public/PwPush/Set-PwPushConfig.ps1 similarity index 100% rename from Modules/CippExtensions/Private/Set-PwPushConfig.ps1 rename to Modules/CippExtensions/Public/PwPush/Set-PwPushConfig.ps1 diff --git a/Modules/HuduAPI/2.4.9/HuduAPI.psd1 b/Modules/HuduAPI/2.4.9/HuduAPI.psd1 new file mode 100644 index 000000000000..c5654110bc01 --- /dev/null +++ b/Modules/HuduAPI/2.4.9/HuduAPI.psd1 @@ -0,0 +1,154 @@ +# +# Module manifest for module 'HuduAPI' +# +# Generated by: Luke Whitelock +# +# Generated on: 06/30/2024 +# + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = '.\HuduAPI.psm1' + +# Version number of this module. +ModuleVersion = '2.4.9' + +# Supported PSEditions +# CompatiblePSEditions = @() + +# ID used to uniquely identify this module +GUID = '4e0a4feb-1658-416b-b854-ab9e913a56de' + +# Author of this module +Author = 'Luke Whitelock' + +# Company or vendor of this module +CompanyName = 'MSPP' + +# Copyright statement for this module +Copyright = '(c) 2021 Luke Whitelock. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'This module provides an interface to the Hudu Rest API further information can be found at https://github.com/lwhitelock/HuduAPI' + +# Minimum version of the PowerShell engine required by this module +PowerShellVersion = '7.0' + +# Name of the PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# ClrVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +# NestedModules = @() + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = 'Get-HuduActivityLogs', 'Get-HuduApiKey', 'Get-HuduAppInfo', + 'Get-HuduArticles', 'Get-HuduAssetLayoutFieldID', + 'Get-HuduAssetLayouts', 'Get-HuduAssets', 'Get-HuduBaseURL', + 'Get-HuduCard', 'Get-HuduCompanies', 'Get-HuduExpirations', + 'Get-HuduFolderMap', 'Get-HuduFolders', 'Get-HuduIntegrationMatchers', + 'Get-HuduMagicDashes', 'Get-HuduObjectByUrl', + 'Get-HuduPasswordFolders', 'Get-HuduPasswords', 'Get-HuduProcesses', + 'Get-HuduPublicPhotos', 'Get-HuduRelations', 'Get-HuduUploads', + 'Get-HuduWebsites', 'Initialize-HuduFolder', + 'Move-HuduAssetsToNewLayout', 'New-HuduAPIKey', 'New-HuduArticle', + 'New-HuduAsset', 'New-HuduAssetLayout', 'New-HuduBaseURL', + 'New-HuduCompany', 'New-HuduCustomHeaders', 'New-HuduFolder', + 'New-HuduPassword', 'New-HuduPublicPhoto', 'New-HuduRelation', + 'New-HuduUpload', 'New-HuduWebsite', 'Remove-HuduAPIKey', + 'Remove-HuduArticle', 'Remove-HuduAsset', 'Remove-HuduBaseURL', + 'Remove-HuduCompany', 'Remove-HuduCustomHeaders', + 'Remove-HuduMagicDash', 'Remove-HuduPassword', 'Remove-HuduRelation', + 'Remove-HuduUpload', 'Remove-HuduWebsite', 'Set-HuduArticle', + 'Set-HuduArticleArchive', 'Set-HuduAsset', 'Set-HuduAssetArchive', + 'Set-HuduAssetLayout', 'Set-HuduCompany', 'Set-HuduCompanyArchive', + 'Set-HuduFolder', 'Set-HuduIntegrationMatcher', 'Set-HuduMagicDash', + 'Set-HuduPassword', 'Set-HuduPasswordArchive', 'Set-HuduWebsite' + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @() + +# Variables to export from this module +VariablesToExport = '*' + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = @() + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + + +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} + diff --git a/Modules/HuduAPI/2.4.9/HuduAPI.psm1 b/Modules/HuduAPI/2.4.9/HuduAPI.psm1 new file mode 100644 index 000000000000..24c908667318 --- /dev/null +++ b/Modules/HuduAPI/2.4.9/HuduAPI.psm1 @@ -0,0 +1,4201 @@ +#Region './Private/ArgumentCompleters/AssetLayoutCompleter.ps1' -1 + +$AssetLayoutCompleter = { + param ( + $CommandName, + $ParamName, + $AssetLayout, + $CommandAst, + $fakeBoundParameters + ) + if (!$script:AssetLayouts) { + Get-HuduAssetLayouts | Out-Null + } + + $AssetLayout = $AssetLayout -replace "'", '' + ($script:AssetLayouts).name | Where-Object { $_ -match "$AssetLayout" } | ForEach-Object { "'$_'" } +} + +Register-ArgumentCompleter -CommandName Get-HuduAssets -ParameterName AssetLayout -ScriptBlock $AssetLayoutCompleter +#EndRegion './Private/ArgumentCompleters/AssetLayoutCompleter.ps1' 18 +#Region './Private/Get-HuduCompanyFolders.ps1' -1 + +function Get-HuduCompanyFolders { + [CmdletBinding()] + Param ( + [PSCustomObject]$FoldersRaw + ) + + $RootFolders = $FoldersRaw | Where-Object { $null -eq $_.parent_folder_id } + $ReturnObject = [PSCustomObject]@{} + foreach ($folder in $RootFolders) { + $SubFolders = Get-HuduSubFolders -id $folder.id -FoldersRaw $FoldersRaw + foreach ($SubFolder in $SubFolders) { + $Folder | Add-Member -MemberType NoteProperty -Name $(Get-HuduFolderCleanName $($SubFolder.PSObject.Properties.name)) -Value $SubFolder.PSObject.Properties.value + } + $ReturnObject | Add-Member -MemberType NoteProperty -Name $(Get-HuduFolderCleanName $($folder.name)) -Value $folder + } + return $ReturnObject +} +#EndRegion './Private/Get-HuduCompanyFolders.ps1' 18 +#Region './Private/Get-HuduFolderCleanName.ps1' -1 + +function Get-HuduFolderCleanName { + [CmdletBinding()] + param( + [string]$Name + ) + + $FieldNames = @('id', 'company_id', 'icon', 'description', 'name', 'parent_folder_id', 'created_at', 'updated_at') + + if ($Name -in $FieldNames) { + Return "fld_$Name" + } else { + Return $Name + } + +} +#EndRegion './Private/Get-HuduFolderCleanName.ps1' 16 +#Region './Private/Get-HuduSubFolders.ps1' -1 + +function Get-HuduSubFolders { + [CmdletBinding()] + Param( + [int]$id, + [PSCustomObject]$FoldersRaw + ) + + $SubFolders = $FoldersRaw | Where-Object { $_.parent_folder_id -eq $id } + $ReturnFolders = [System.Collections.ArrayList]@() + foreach ($Folder in $SubFolders) { + $SubSubFolders = Get-HuduSubFolders -id $Folder.id -FoldersRaw $FoldersRaw + foreach ($AddFolder in $SubSubFolders) { + $null = $folder | Add-Member -MemberType NoteProperty -Name $(Get-HuduFolderCleanName $($AddFolder.PSObject.Properties.name)) -Value $AddFolder.PSObject.Properties.value + } + $ReturnObject = [PSCustomObject]@{ + $(Get-HuduFolderCleanName $($Folder.name)) = $Folder + } + $null = $ReturnFolders.add($ReturnObject) + } + + return $ReturnFolders + +} +#EndRegion './Private/Get-HuduSubFolders.ps1' 24 +#Region './Private/Invoke-HuduRequest.ps1' -1 + +function Invoke-HuduRequest { + <# + .SYNOPSIS + Main Hudu API function + + .DESCRIPTION + Calls Hudu API with token + + .PARAMETER Method + GET,POST,DELETE,PUT,etc + + .PARAMETER Path + Path to API endpoint + + .PARAMETER Params + Hashtable of parameters + + .PARAMETER Body + JSON encoded body string + + .PARAMETER Form + Multipart form data + + .EXAMPLE + Invoke-HuduRequest -Resource '/api/v1/articles' -Method GET + #> + [CmdletBinding()] + Param( + [Parameter()] + [string]$Method = 'GET', + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string]$Resource, + + [Parameter()] + [hashtable]$Params = @{}, + + [Parameter()] + [string]$Body, + + [Parameter()] + [hashtable]$Form + ) + + $HuduAPIKey = Get-HuduApiKey + $HuduBaseURL = Get-HuduBaseURL + + # Assemble parameters + $ParamCollection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) + + # Sort parameters + foreach ($Item in ($Params.GetEnumerator() | Sort-Object -CaseSensitive -Property Key)) { + $ParamCollection.Add($Item.Key, $Item.Value) + } + + # Query string + $Request = $ParamCollection.ToString() + + $Headers = @{ + 'x-api-key' = (New-Object PSCredential 'user', $HuduAPIKey).GetNetworkCredential().Password; + } + + if (($Script:Int_HuduCustomHeaders | Measure-Object).count -gt 0){ + + foreach($Entry in $Int_HuduCustomHeaders.GetEnumerator()) { + $Headers[$Entry.Name] = $Entry.Value + } + } + + $ContentType = 'application/json; charset=utf-8' + + $Uri = '{0}{1}' -f $HuduBaseURL, $Resource + # Make API call URI + if ($Request) { + $UriBuilder = [System.UriBuilder]$Uri + $UriBuilder.Query = $Request + $Uri = $UriBuilder.Uri + } + Write-Verbose ( '{0} [{1}]' -f $Method, $Uri ) + + $RestMethod = @{ + Method = $Method + Uri = $Uri + Headers = $Headers + ContentType = $ContentType + } + + if ($Body) { + $RestMethod.Body = $Body + Write-Verbose $Body + } + + if ($Form) { + $RestMethod.Form = $Form + Write-Verbose ( $Form | Out-String ) + } + + try { + $Results = Invoke-RestMethod @RestMethod + } catch { + if ("$_".trim() -eq 'Retry later' -or "$_".trim() -eq 'The remote server returned an error: (429) Too Many Requests.') { + Write-Information 'Hudu API Rate limited. Waiting 30 Seconds then trying again' + Start-Sleep 30 + $Results = Invoke-HuduRequest @RestMethod + } else { + Write-Error "'$_'" + } + } + + $Results +} +#EndRegion './Private/Invoke-HuduRequest.ps1' 113 +#Region './Private/Invoke-HuduRequestPaginated.ps1' -1 + +function Invoke-HuduRequestPaginated { + <# + .SYNOPSIS + Paginated requests to Hudu API + + .DESCRIPTION + Wraps Invoke-HuduRequest with page sizes + + .PARAMETER HuduRequest + Request to paginate + + .PARAMETER Property + Property name to return (don't specify to return entire response object) + + .PARAMETER PageSize + Number of results to return per page (default 1000) + + #> + [CmdletBinding()] + Param( + [hashtable]$HuduRequest, + [string]$Property, + [int]$PageSize = 1000 + ) + + $i = 1 + do { + $HuduRequest.Params.page = $i + $HuduRequest.Params.page_size = $PageSize + $Response = Invoke-HuduRequest @HuduRequest + $i++ + if ($Property) { + $Response.$Property + } + + else { + $Response + } + } while (($Property -and $Response.$Property.count % $PageSize -eq 0 -and $Response.$Property.count -ne 0) -or (!$Property -and $Response.count % $PageSize -eq 0 -and $Response.count -ne 0)) +} +#EndRegion './Private/Invoke-HuduRequestPaginated.ps1' 41 +#Region './Public/Get-HuduActivityLogs.ps1' -1 + +function Get-HuduActivityLogs { + <# + .SYNOPSIS + Get activity logs for account + + .DESCRIPTION + Calls Hudu API to retrieve activity logs with filters + + .PARAMETER UserId + Filter logs by user_id + + .PARAMETER UserEmail + Filter logs by email address + + .PARAMETER ResourceId + Filter logs by resource id. Must be coupled with resource_type + + .PARAMETER ResourceType + Filter logs by resource type (Asset, AssetPassword, Company, Article, etc.). Must be coupled with resource_id + + .PARAMETER ActionMessage + Filter logs by action + + .PARAMETER StartDate + Filter logs by start date. Converts string to ISO 8601 format + + .PARAMETER EndDate + Filter logs by end date, should be coupled with start date to limit results + + .EXAMPLE + Get-HuduActivityLogs -StartDate 2023-02-01 + + #> + [CmdletBinding()] + Param ( + [Alias('user_id')] + [Int]$UserId = '', + [Alias('user_email')] + [String]$UserEmail = '', + [Alias('resource_id')] + [Int]$ResourceId = '', + [Alias('resource_type')] + [String]$ResourceType = '', + [Alias('action_message')] + [String]$ActionMessage = '', + [Alias('start_date')] + [DateTime]$StartDate, + [Alias('end_date')] + [DateTime]$EndDate + ) + + $Params = @{} + + if ($UserId) { $Params.user_id = $UserId } + if ($UserEmail) { $Params.user_email = $UserEmail } + if ($ResourceId) { $Params.resource_id = $ResourceId } + if ($ResourceType) { $Params.resource_type = $ResourceType } + if ($ActionMessage) { $Params.action_message = $ActionMessage } + if ($StartDate) { + $ISO8601Date = $StartDate.ToString('o'); + $Params.start_date = $ISO8601Date + } + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/activity_logs' + Params = $Params + } + + $AllActivity = Invoke-HuduRequestPaginated -HuduRequest $HuduRequest + + if ($EndDate) { + $AllActivity = $AllActivity | Where-Object { $([DateTime]::Parse($_.created_at)) -le $EndDate } + } + + return $AllActivity +} +#EndRegion './Public/Get-HuduActivityLogs.ps1' 78 +#Region './Public/Get-HuduApiKey.ps1' -1 + +function Get-HuduApiKey { + <# + .SYNOPSIS + Get Hudu API key + + .DESCRIPTION + Returns Hudu API key in securestring format + + .EXAMPLE + Get-HuduApiKey + + #> + [CmdletBinding()] + Param() + if ($null -eq $Int_HuduAPIKey) { + Write-Error 'No API key has been set. Please use New-HuduAPIKey to set it.' + } else { + $Int_HuduAPIKey + } +} +#EndRegion './Public/Get-HuduApiKey.ps1' 21 +#Region './Public/Get-HuduAppInfo.ps1' -1 + +function Get-HuduAppInfo { + <# + .SYNOPSIS + Retrieve information regarding API + + .DESCRIPTION + Calls Hudu API to retrieve version number and date + + .EXAMPLE + Get-HuduAppInfo + + #> + [CmdletBinding()] + Param() + + [version]$script:HuduRequiredVersion = '2.21' + + try { + Invoke-HuduRequest -Resource '/api/v1/api_info' + } catch { + [PSCustomObject]@{ + version = '0.0.0.0' + date = '2000-01-01' + } + } +} +#EndRegion './Public/Get-HuduAppInfo.ps1' 27 +#Region './Public/Get-HuduArticles.ps1' -1 + +function Get-HuduArticles { + <# + .SYNOPSIS + Get Knowledge Base Articles + + .DESCRIPTION + Calls Hudu API to retrieve KB articles by Id or a list + + .PARAMETER Id + Id of the Article + + .PARAMETER CompanyId + Filter by company id + + .PARAMETER Name + Filter by name of article + + .PARAMETER Slug + Filter by slug of article + + .EXAMPLE + Get-HuduArticles -Name 'Article name' + + #> + [CmdletBinding()] + Param ( + [Int]$Id = '', + [Alias('company_id')] + [Int]$CompanyId = '', + [String]$Name = '', + [String]$Slug + ) + + if ($Id) { + Invoke-HuduRequest -Method get -Resource "/api/v1/articles/$Id" + } else { + $Params = @{} + + if ($CompanyId) { $Params.company_id = $CompanyId } + if ($Name) { $Params.name = $Name } + if ($Slug) { $Params.slug = $Slug } + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/articles' + Params = $Params + } + + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest -Property articles + } +} +#EndRegion './Public/Get-HuduArticles.ps1' 52 +#Region './Public/Get-HuduAssetLayoutFieldID.ps1' -1 + +function Get-HuduAssetLayoutFieldID { + <# + .SYNOPSIS + Get Hudu Asset Layout Field ID + + .DESCRIPTION + Retrieves ID for Hudu Asset Layout Fields + + .PARAMETER Name + Name of Field + + .PARAMETER LayoutId + Asset Layout Id + + .EXAMPLE + Get-HuduAssetLayoutFieldID -Name 'Extra Info' -LayoutId 1 + + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + [String]$Name, + [Alias('asset_layout_id')] + [Parameter(Mandatory = $true)] + [Int]$LayoutId + ) + + $Layout = Get-HuduAssetLayouts -LayoutId $LayoutId + + $Fields = [Collections.Generic.List[Object]]($Layout.fields) + $Index = $Fields.FindIndex( { $args[0].label -eq $Name } ) + $Fields[$Index].id +} +#EndRegion './Public/Get-HuduAssetLayoutFieldID.ps1' 34 +#Region './Public/Get-HuduAssetLayouts.ps1' -1 + +function Get-HuduAssetLayouts { + <# + .SYNOPSIS + Get a list of Asset Layouts + + .DESCRIPTION + Call Hudu API to retrieve asset layouts for server + + .PARAMETER Name + Filter by name of Asset Layout + + .PARAMETER LayoutId + Id of Asset Layout + + .PARAMETER Slug + Filter by url slug + + .EXAMPLE + Get-HuduAssetLayouts -Name 'Contacts' + + #> + [CmdletBinding()] + Param ( + [String]$Name, + [Alias('id', 'layout_id')] + [int]$LayoutId, + [String]$Slug + ) + + $HuduRequest = @{ + Resource = '/api/v1/asset_layouts' + Method = 'GET' + } + + if ($LayoutId) { + $HuduRequest.Resource = '{0}/{1}' -f $HuduRequest.Resource, $LayoutId + $AssetLayout = Invoke-HuduRequest @HuduRequest + return $AssetLayout.asset_layout + } else { + $Params = @{} + if ($Name) { $Params.name = $Name } + if ($Slug) { $Params.slug = $Slug } + $HuduRequest.Params = $Params + + $AssetLayouts = Invoke-HuduRequestPaginated -HuduRequest $HuduRequest -Property 'asset_layouts' -PageSize 25 + + if (!$Name -and !$Slug) { + $script:AssetLayouts = $AssetLayouts | Sort-Object -Property name + } + $AssetLayouts + } +} +#EndRegion './Public/Get-HuduAssetLayouts.ps1' 53 +#Region './Public/Get-HuduAssets.ps1' -1 + +function Get-HuduAssets { + <# + .SYNOPSIS + Get a list of Assets + + .DESCRIPTION + Call Hudu API to retrieve Assets + + .PARAMETER Id + Id of requested asset + + .PARAMETER AssetLayoutId + Id of the requested asset layout + + .PARAMETER AssetLayout + Name of the requested asset layout + + .PARAMETER CompanyId + Id of the requested company + + .PARAMETER Name + Filter by name + + .PARAMETER Archived + Show archived results + + .PARAMETER PrimarySerial + Filter by primary serial + + .PARAMETER Slug + Filter by slug + + .EXAMPLE + Get-HuduAssets -AssetLayout 'Contacts' + + #> + [CmdletBinding()] + Param ( + [ValidateRange(1, [int]::MaxValue)] + [Int]$Id = '', + [Alias('asset_layout_id')] + [ValidateRange(1, [int]::MaxValue)] + [Int]$AssetLayoutId = '', + [string]$AssetLayout, + [Alias('company_id')] + [ValidateRange(1, [int]::MaxValue)] + [Int]$CompanyId = '', + [String]$Name = '', + [switch]$Archived, + [Alias('primary_serial')] + [String]$PrimarySerial = '', + [String]$Slug + ) + + if ($AssetLayout) { + if (!$script:AssetLayouts) { Get-HuduAssetLayouts | Out-Null } + $AssetLayoutId = $script:AssetLayouts | Where-Object { $_.name -eq $AssetLayout } | Select-Object -ExpandProperty id + } + + if ($id -and $CompanyId) { + $HuduRequest = @{ + Resource = "/api/v1/companies/$CompanyId/assets/$Id" + Method = 'GET' + } + Invoke-HuduRequest @HuduRequest + } else { + $Params = @{} + if ($CompanyId) { $Params.company_id = $CompanyId } + if ($AssetLayoutId) { $Params.asset_layout_id = $AssetLayoutId } + if ($Name) { $Params.name = $Name } + if ($Archived.IsPresent) { $params.archived = $Archived.IsPresent } + if ($PrimarySerial) { $Params.primary_serial = $PrimarySerial } + if ($Id) { $Params.id = $Id } + if ($Slug) { $Params.slug = $Slug } + + $HuduRequest = @{ + Resource = '/api/v1/assets' + Method = 'GET' + Params = $Params + } + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest -Property assets + } +} +#EndRegion './Public/Get-HuduAssets.ps1' 84 +#Region './Public/Get-HuduBaseURL.ps1' -1 + +function Get-HuduBaseURL { + <# + .SYNOPSIS + Get Hudu Base URL + + .DESCRIPTION + Returns Hudu Base URL + + .EXAMPLE + Get-HuduBaseURL + + #> + [CmdletBinding()] + Param() + if ($null -eq $Int_HuduBaseURL) { + Write-Error 'No Base URL has been set. Please use New-HuduBaseURL to set it.' + } else { + $Int_HuduBaseURL + } +} +#EndRegion './Public/Get-HuduBaseURL.ps1' 21 +#Region './Public/Get-HuduCard.ps1' -1 + +function Get-HuduCard { + <# + .SYNOPSIS + Get Integration Cards + + .DESCRIPTION + Lookup cards with outside integration details + + .PARAMETER IntegrationSlug + Identifier of outside integration + + .PARAMETER IntegrationId + ID in the integration. Must be present, unless integration_identifier is set + + .PARAMETER IntegrationIdentifier + Identifier in the integration (if integration_id is not set) + + .EXAMPLE + Get-HuduCard -IntegrationSlug cw_manage -IntegrationId 1 + + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + [Alias('integration_slug')] + [String]$IntegrationSlug, + + [Alias('integration_id')] + [String]$IntegrationId, + + [Alias('integration_identifier')] + [String]$IntegrationIdentifier + ) + + $Params = @{ + integration_slug = $IntegrationSlug + } + + if ($IntegrationId) { $Params.integration_id = $IntegrationId } + if ($IntegrationIdentifier) { $Params.integration_identifier = $IntegrationIdentifier } + + if (!$IntegrationId -and !$IntegrationIdentifier) { + throw 'IntegrationId or IntegrationIdentifier required' + } + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/cards/lookup' + Params = $Params + } + + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest -Property integrator_cards +} +#EndRegion './Public/Get-HuduCard.ps1' 54 +#Region './Public/Get-HuduCompanies.ps1' -1 + +function Get-HuduCompanies { + <# + .SYNOPSIS + Get a list of companies + + .DESCRIPTION + Call Hudu API to retrieve company list + + .PARAMETER Id + Filter companies by id + + .PARAMETER Name + Filter companies by name + + .PARAMETER PhoneNumber + filter companies by phone number + + .PARAMETER Website + Filter companies by website + + .PARAMETER City + Filter companies by city + + .PARAMETER State + Filter companies by state + + .PARAMETER Search + Filter by search query + + .PARAMETER Slug + Filter by url slug + + .PARAMETER IdInIntegration + Filter companies by id/identifier in PSA/RMM/outside integration + + .EXAMPLE + Get-HuduCompanies -Search 'Vendor' + + #> + [CmdletBinding()] + Param ( + [String]$Name = '', + [Alias('phone_number')] + [String]$PhoneNumber = '', + [String]$Website = '', + [String]$City = '', + [String]$State = '', + [Alias('id_in_integration')] + [Int]$IdInIntegration = '', + [Int]$Id = '', + [string]$Search, + [String]$Slug + ) + + if ($Id) { + $Company = (Invoke-HuduRequest -Method get -Resource "/api/v1/companies/$Id").company + return $Company + } else { + $Params = @{} + if ($Name) { $Params.name = $Name } + if ($PhoneNumber) { $Params.phone_number = $PhoneNumber } + if ($Website) { $Params.website = $Website } + if ($City) { $Params.city = $City } + if ($State) { $Params.state = $State } + if ($IdInIntegration) { $Params.id_in_integration = $IdInIntegration } + if ($Search) { $Params.search = $Search } + if ($Slug) { $Params.slug = $Slug } + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/companies' + Params = $Params + } + + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest -Property 'companies' + } +} +#EndRegion './Public/Get-HuduCompanies.ps1' 78 +#Region './Public/Get-HuduExpirations.ps1' -1 + +function Get-HuduExpirations { + <# + .SYNOPSIS + Get expirations for account + + .DESCRIPTION + Calls Hudu API to retrieve expirations + + .PARAMETER CompanyId + Filter expirations by company_id + + .PARAMETER ExpirationType + Filter expirations by expiration type (undeclared, domain, ssl_certificate, warranty, asset_field, article_expiration) + + .PARAMETER ResourceId + Filter logs by resource id. Must be coupled with resource_type + + .PARAMETER ResourceType + Filter logs by resource type (Asset, AssetPassword, Company, Article, etc.). Must be coupled with resource_id + + .EXAMPLE + Get-HuduExpirations -ExpirationType domain + + #> + [CmdletBinding()] + Param ( + [Alias('company_id')] + [Int]$CompanyId = '', + + [ValidateSet('undeclared', 'domain', 'ssl_certificate', 'warranty', 'asset_field', 'article_expiration')] + [Alias('expiration_type')] + [String]$ExpirationType = '', + + [Alias('resource_id')] + [Int]$ResourceId = '', + + [Alias('resource_type')] + [String]$ResourceType = '' + ) + + $Params = @{} + + if ($CompanyId) { $Params.company_id = $CompanyId } + if ($ExpirationType) { $Params.expiration_type = $ExpirationType } + if ($ResourceType) { $Params.resource_type = $ResourceType } + if ($ResourceId) { $Params.resource_id = $ResourceId } + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/expirations' + Params = $Params + } + + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest +} +#EndRegion './Public/Get-HuduExpirations.ps1' 56 +#Region './Public/Get-HuduFolderMap.ps1' -1 + +function Get-HuduFolderMap { + [CmdletBinding()] + Param ( + [Alias('company_id')] + [Int]$CompanyId = '' + ) + + if ($CompanyId) { + $FoldersRaw = Get-HuduFolders -company_id $CompanyId + $SubFolders = Get-HuduCompanyFolders -FoldersRaw $FoldersRaw + } else { + $FoldersRaw = Get-HuduFolders + $FoldersProcessed = $FoldersRaw | Where-Object { $null -eq $_.company_id } + $SubFolders = Get-HuduCompanyFolders -FoldersRaw $FoldersProcessed + } + + return $SubFolders +} +#EndRegion './Public/Get-HuduFolderMap.ps1' 19 +#Region './Public/Get-HuduFolders.ps1' -1 + +function Get-HuduFolders { + <# + .SYNOPSIS + Get a list of Folders + + .DESCRIPTION + Calls Hudu API to retrieve folders + + .PARAMETER Id + Id of the folder + + .PARAMETER Name + Filter by name + + .PARAMETER CompanyId + Filter by company_id + + .EXAMPLE + Get-HuduFolders + + #> + [CmdletBinding()] + Param ( + [Int]$Id = '', + [String]$Name = '', + [Alias('company_id')] + [Int]$CompanyId = '' + ) + + if ($id) { + $Folder = Invoke-HuduRequest -Method get -Resource "/api/v1/folders/$id" + return $Folder.Folder + } else { + $Params = @{} + + if ($CompanyId) { $Params.company_id = $CompanyId } + if ($Name) { $Params.name = $Name } + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/folders' + Params = $Params + } + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest -Property folders + } +} +#EndRegion './Public/Get-HuduFolders.ps1' 47 +#Region './Public/Get-HuduIntegrationMatchers.ps1' -1 + +function Get-HuduIntegrationMatchers { + <# + .SYNOPSIS + List matchers for an integration + + .DESCRIPTION + Calls Hudu API to get list of integration matching + + .PARAMETER IntegrationId + ID of the integration. Can be found in the URL when editing an integration + + .PARAMETER Matched + Filter on whether the company already been matched + + .PARAMETER SyncId + Filter by ID of the record in the integration. This is used if the id that the integration uses is an integer. + + .PARAMETER Identifier + Filter by Identifier in the integration (if sync_id is not set). This is used if the id that the integration uses is a string. + + .PARAMETER CompanyId + Filter on company id + + .EXAMPLE + Get-HuduIntegrationMatchers -IntegrationId 1 + + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + [int]$IntegrationId, + + [switch]$Matched, + + [int]$SyncId = '', + + [string]$Identifier = '', + + [int]$CompanyId + ) + + $Params = @{ + integration_id = $IntegrationId + } + + if ($Matched.IsPresent) { $Params.matched = 'true' } + if ($CompanyId) { $Params.company_id = $CompanyId } + if ($Identifier) { $Params.identifier = $Identifier } + if ($SyncId) { $Params.sync_id = $SyncId } + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/matchers' + Params = $Params + } + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest -Property 'matchers' +} +#EndRegion './Public/Get-HuduIntegrationMatchers.ps1' 58 +#Region './Public/Get-HuduMagicDashes.ps1' -1 + +function Get-HuduMagicDashes { + <# + .SYNOPSIS + Get all Magic Dash Items + + .DESCRIPTION + Call Hudu API to retrieve Magic Dashes + + .PARAMETER CompanyId + Filter by company id + + .PARAMETER Title + Filter by title + + .EXAMPLE + Get-HuduMagicDashes -Title 'Microsoft 365 - ...' + + #> + Param ( + [Alias('company_id')] + [Int]$CompanyId, + [String]$Title + ) + + $Params = @{} + + if ($CompanyId) { $Params.company_id = $CompanyId } + if ($Title) { $Params.title = $Title } + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/magic_dash' + Params = $Params + } + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest +} +#EndRegion './Public/Get-HuduMagicDashes.ps1' 37 +#Region './Public/Get-HuduObjectByUrl.ps1' -1 + +function Get-HuduObjectByUrl { + <# + .SYNOPSIS + Get Hudu object from URL + + .DESCRIPTION + Calls Hudu API to retrieve object based on URL string + + .PARAMETER Url + Url to retrieve object from + + .EXAMPLE + Get-HuduObject -Url https://your-hudu-server/a/some-asset-1z8z7a + + #> + [CmdletBinding()] + Param ( + [uri]$Url + ) + + if ((Get-HuduBaseURL) -match $Url.Authority) { + $null, $Type, $Slug = $Url.PathAndQuery -split '/' + + $SlugSplat = @{ + Slug = $Slug + } + + switch ($Type) { + 'a' { + # Asset + Get-HuduAssets @SlugSplat + } + 'admin' { + # Admin path + $null, $null, $Type, $Slug = $Url.PathAndQuery -split '/' + $SlugSplat = @{ + Slug = $Slug + } + switch ($Type) { + 'asset_layouts' { + # Asset layouts + Get-HuduAssetLayouts @SlugSplat + } + } + } + 'c' { + # Company + Get-HuduCompanies @SlugSplat + } + 'kba' { + # KB article + Get-HuduArticles @SlugSplat + } + 'passwords' { + # Passwords + Get-HuduPasswords @SlugSplat + } + 'websites' { + # Website + Get-HuduWebsites @SlugSplat + } + default { + Write-Error "Unsupported object type $Type" + } + } + } else { + Write-Error 'Provided URL does not match Hudu Base URL' + } +} +#EndRegion './Public/Get-HuduObjectByUrl.ps1' 70 +#Region './Public/Get-HuduPasswordFolders.ps1' -1 + +function Get-HuduPasswordFolders { + <# + .SYNOPSIS + Get a list of Password Folders + + .DESCRIPTION + Calls Hudu API to retrieve folders + + .PARAMETER Id + Id of the folder + + .PARAMETER Name + Filter by name + + .PARAMETER CompanyId + Filter by company_id + + .EXAMPLE + Get-HuduFolders + + #> + [CmdletBinding()] + Param ( + [Int]$Id = '', + [String]$Name = '', + [String]$Search = '', + [Alias('company_id')] + [Int]$CompanyId = '', + [Int]$page = '', + [Int]$page_size = '' + ) + + if ($id) { + $Folder = Invoke-HuduRequest -Method get -Resource "/api/v1/password_folders/$id" + return $Folder.password_folder + } else { + $Params = @{} + + if ($CompanyId) { $Params.company_id = $CompanyId } + if ($Name) { $Params.name = $Name } + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/password_folders' + Params = $Params + } + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest -Property password_folders + } +} +#EndRegion './Public/Get-HuduPasswordFolders.ps1' 50 +#Region './Public/Get-HuduPasswords.ps1' -1 + +function Get-HuduPasswords { + <# + .SYNOPSIS + Get a list of Passwords + + .DESCRIPTION + Calls Hudu API to list password assets + + .PARAMETER Id + Id of the password + + .PARAMETER CompanyId + Filter by company id + + .PARAMETER Name + Filter by password name + + .PARAMETER Slug + Filter by url slug + + .PARAMETER Search + Filter by search query + + .EXAMPLE + Get-HuduPasswords -CompanyId 1 + + #> + [CmdletBinding()] + Param ( + [Int]$Id, + + [Alias('company_id')] + [Int]$CompanyId, + + [String]$Name, + + [String]$Slug, + + [string]$Search + ) + + if ($Id) { + $Password = Invoke-HuduRequest -Method get -Resource "/api/v1/asset_passwords/$id" + return $Password + } else { + $Params = @{} + if ($CompanyId) { $Params.company_id = $CompanyId } + if ($Name) { $Params.name = $Name } + if ($Slug) { $Params.slug = $Slug } + if ($Search) { $Params.search = $Search } + } + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/asset_passwords' + Params = $Params + } + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest -Property 'asset_passwords' +} +#EndRegion './Public/Get-HuduPasswords.ps1' 60 +#Region './Public/Get-HuduProcesses.ps1' -1 + +function Get-HuduProcesses { + <# + .SYNOPSIS + Get a list of Procedures (Processes) + + .DESCRIPTION + Calls Hudu API to retrieve list of procedures + + .PARAMETER Id + Id of the Procedure + + .PARAMETER CompanyId + Filter by company id + + .PARAMETER Name + Fitler by name of article + + .PARAMETER Slug + Filter by url slug + + .EXAMPLE + Get-HuduProcedures -Name 'Procedure 1' + + #> + [CmdletBinding()] + Param ( + [Int]$Id = '', + [Alias('company_id')] + [Int]$CompanyId = '', + [String]$Name = '', + [String]$Slug + ) + + if ($Id) { + Invoke-HuduRequest -Method get -Resource "/api/v1/procedures/$id" + } else { + $Params = @{} + + if ($CompanyId) { $Params.company_id = $CompanyId } + if ($Name) { $Params.name = $Name } + if ($Slug) { $Params.slug = $Slug } + + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/procedures' + Params = $Params + } + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest -Property 'procedures' + } +} +#EndRegion './Public/Get-HuduProcesses.ps1' 52 +#Region './Public/Get-HuduPublicPhotos.ps1' -1 + +function Get-HuduPublicPhotos { + <# + .SYNOPSIS + Get a list of Public_Photos + + .DESCRIPTION + Calls Hudu API to retrieve public photos + + .EXAMPLE + Get-HuduPublicPhotos + + #> + [CmdletBinding()] + Param() + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/public_photos' + Params = @{} + } + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest -Property 'public_photos' +} +#EndRegion './Public/Get-HuduPublicPhotos.ps1' 23 +#Region './Public/Get-HuduRelations.ps1' -1 + +function Get-HuduRelations { + <# + .SYNOPSIS + Get a list of all relations + + .DESCRIPTION + Calls Hudu API to retrieve object relationsihps + + .EXAMPLE + Get-HuduRelations -CompanyId 1 + + #> + [CmdletBinding()] + Param() + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/relations' + Params = @{} + } + + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest -Property 'relations' +} +#EndRegion './Public/Get-HuduRelations.ps1' 24 +#Region './Public/Get-HuduUploads.ps1' -1 + +function Get-HuduUploads { + <# + .SYNOPSIS + Get a list of uploads + + .DESCRIPTION + Calls Hudu API to retrieve uploads + + .EXAMPLE + Get-HuduUploads + + #> + [CmdletBinding()] + Param( + [Int]$Id + ) + + if ($Id) { + $Upload = Invoke-HuduRequest -Method Get -Resource "/api/v1/uploads/$Id" + } else { + $Upload = Invoke-HuduRequest -Method Get -Resource "/api/v1/uploads" + } + return $Upload +} +#EndRegion './Public/Get-HuduUploads.ps1' 25 +#Region './Public/Get-HuduWebsites.ps1' -1 + +function Get-HuduWebsites { + <# + .SYNOPSIS + Get a list of all websites + + .DESCRIPTION + Calls Hudu API to get websites + + .PARAMETER Name + Filter websites by name + + .PARAMETER Id + ID of website + + .PARAMETER Slug + Filter by url slug + + .PARAMETER Search + Fitler by search query + + .EXAMPLE + Get-HuduWebsites -Search 'domain.com' + + #> + [CmdletBinding()] + Param ( + [String]$Name, + [Alias('website_id')] + [Int]$WebsiteId, + [String]$Slug, + [string]$Search + ) + + if ($WebsiteId) { + Invoke-HuduRequest -Method get -Resource "/api/v1/websites/$($WebsiteId)" + } else { + $Params = @{} + if ($Name) { $Params.name = $Name } + if ($Slug) { $Params.slug = $Slug } + if ($Search) { $Params.search = $Search } + + $HuduRequest = @{ + Method = 'GET' + Resource = '/api/v1/websites' + Params = $Params + } + Invoke-HuduRequestPaginated -HuduRequest $HuduRequest + } +} +#EndRegion './Public/Get-HuduWebsites.ps1' 50 +#Region './Public/Initialize-HuduFolder.ps1' -1 + +function Initialize-HuduFolder { + [CmdletBinding()] + param( + [String[]]$FolderPath, + [Alias('company_id')] + [int]$CompanyId + ) + + if ($CompanyId) { + $FolderMap = Get-HuduFolderMap -company_id $CompanyId + } else { + $FolderMap = Get-HuduFolderMap + } + + $CurrentFolder = $Foldermap + foreach ($Folder in $FolderPath) { + if ($CurrentFolder.$(Get-HuduFolderCleanName $Folder)) { + $CurrentFolder = $CurrentFolder.$(Get-HuduFolderCleanName $Folder) + } else { + $CurrentFolder = (New-HuduFolder -Name $Folder -company_id $CompanyID -parent_folder_id $CurrentFolder.id).folder + } + } + + return $CurrentFolder +} +#EndRegion './Public/Initialize-HuduFolder.ps1' 26 +#Region './Public/Move-HuduAssetsToNewLayout.ps1' -1 + +function Move-HuduAssetsToNewLayout { +<# + .SYNOPSIS + Helper function that uses the Set-HuduAsset function to move an asset between asset layouts. This will leave behind orphan data in the database. + Review the article https://portal.risingtidegroup.net/kb?id=29 for more details. + + .DESCRIPTION + Calls the Hudu API to update an asset by switching its asset_layout_id property to a different asset layout. + This function migrates the asset to the specified new layout while maintaining its fields. Note that this + operation may leave behind orphaned data in the Hudu database, so use it with caution. + + .PARAMETER AssetsToMove + An array of assets to be moved to a new asset layout. Each asset must contain both 'id' and 'fields' properties. + + .PARAMETER NewAssetLayoutID + The ID of the new asset layout to which the assets will be moved. + + .EXAMPLE + $AssetLayout = Get-HuduAssetLayouts -Name "Servers" + $AssetsToUpdate = Get-HuduAssets -AssetLayoutId 9 + Move-HuduAssetsToNewLayout -AssetsToMove $AssetsToUpdate -NewAssetLayoutID $AssetLayout.id + + This example retrieves the asset layout with the name "Servers" and the assets with the layout ID 9, then moves those assets to the new layout. + + .NOTES + Ensure that the new asset layout ID is valid and that the assets to be moved contain the required properties. + Using this function may result in orphaned data in your Hudu database. Review the provided article for more details. +#> + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateScript({ + if ($BadAssets = ($_ | where {(-not $_.id)})) { + $BadAssets + throw "Assets must be an object with an ID" + } + return $true + })] + [array] + $AssetsToMove, + + [Parameter(Mandatory = $true)] + [int] + $NewAssetLayoutID + ) + + Write-Warning "Performing this function will leave behind orphaned data in your Hudu database. Please review https://portal.risingtidegroup.net/kb?id=29" + Read-Host "Press Enter to continue or (CTRL+C) to cancel..." + + $assets = foreach ($AssetToMove in $AssetsToMove) { + if (-not ($AssetToMove.PSObject.Properties.Match('id')) -or -not ($AssetToMove.PSObject.Properties.Match('fields'))) { + Write-Error "Asset does not contain both 'id' and 'fields' properties. Skipping this asset." + continue + } + + if (-not $AssetToMove.fields) { + Write-Warning "Asset ID: $($AssetToMove.id) has no fields. Proceeding with moving the asset." + } + + $assetId = $AssetToMove.id + + if ($PSCmdlet.ShouldProcess("Asset ID: $assetId", "Move to new layout with ID $NewAssetLayoutID")) { + try { + Write-Verbose "Processing Asset ID: $assetId" + + $fields = New-Object -TypeName psobject + foreach ($field in $AssetToMove.fields) { + $fieldName = $field.label.replace(' ', '_').tolower() + $fields | Add-Member -MemberType NoteProperty -Name $fieldName -Value $field.value -Force + } + + (Set-HuduAsset -Id $assetId -AssetLayoutId $NewAssetLayoutID -Fields $fields).asset + + Write-Verbose "Successfully moved Asset ID: $assetId" + } + catch { + Write-Error "Failed to move Asset ID: $assetId. Error: $_" + } + finally { + Remove-Variable -Name fields -ErrorAction SilentlyContinue + } + } + } + return $assets +} +#EndRegion './Public/Move-HuduAssetsToNewLayout.ps1' 87 +#Region './Public/New-HuduAPIKey.ps1' -1 + +function New-HuduAPIKey { + <# + .SYNOPSIS + Set Hudu API Key + + .DESCRIPTION + API keys are required to interact with Hudu + + .PARAMETER ApiKey + The API key + + .EXAMPLE + New-HuduAPIKey -ApiKey abdc1234 + + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Scope = 'Function')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function')] + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $false, ValueFromPipeline = $true)] + [String]$ApiKey + ) + + process { + if ($ApiKey) { + $SecApiKey = ConvertTo-SecureString $ApiKey -AsPlainText -Force + } else { + $SecApiKey = Read-Host -Prompt 'Please enter your Hudu API key, you can obtain it from https://your-hudu-domain/admin/api_keys:' -AsSecureString + } + Set-Variable -Name 'Int_HuduAPIKey' -Value $SecApiKey -Visibility Private -Scope script -Force + + if ($script:Int_HuduBaseURL) { + [version]$version = (Get-HuduAppInfo).version + if ($version -lt $script:HuduRequiredVersion) { + Write-Warning "A connection error occured or Hudu version is below $script:HuduRequiredVersion" + } + } + } +} +#EndRegion './Public/New-HuduAPIKey.ps1' 40 +#Region './Public/New-HuduArticle.ps1' -1 + +function New-HuduArticle { + <# + .SYNOPSIS + Create a Knowledge Base Article + + .DESCRIPTION + Uses Hudu API to create KB articles + + .PARAMETER Name + Name of article + + .PARAMETER Content + Article HTML contents + + .PARAMETER EnableSharing + Create public URL for users to view without being authenticated + + .PARAMETER FolderId + Associate article with folder id + + .PARAMETER CompanyId + Associate article with company id + + .PARAMETER Slug + Manually define slug for Article + + .EXAMPLE + New-HuduArticle -Name "Test" -CompanyId 1 -Content '

    Testing

    ' -EnableSharing -Slug 'this-is-a-test' + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [String]$Name, + + [Parameter(Mandatory = $true)] + [String]$Content, + + [switch]$EnableSharing, + + [Alias('folder_id')] + [Int]$FolderId = '', + + [Alias('company_id')] + [Int]$CompanyId = '', + + [string]$Slug + ) + + $Article = [ordered]@{article = [ordered]@{} } + + $Article.article.add('name', $Name) + $Article.article.add('content', $Content) + + if ($FolderId) { + $Article.article.add('folder_id', $FolderId) + } + + if ($CompanyId) { + $Article.article.add('company_id', $CompanyId) + } + + if ($EnableSharing.IsPresent) { + $Article.article.add('enable_sharing', 'true') + } + + if ($Slug) { + $Article.article.add('slug', $Slug) + } + + $JSON = $Article | ConvertTo-Json -Depth 10 + + if ($PSCmdlet.ShouldProcess($Name)) { + Invoke-HuduRequest -Method post -Resource '/api/v1/articles' -Body $JSON + } +} +#EndRegion './Public/New-HuduArticle.ps1' 77 +#Region './Public/New-HuduAsset.ps1' -1 + +function New-HuduAsset { + <# + .SYNOPSIS + Create an Asset + + .DESCRIPTION + Uses Hudu API to create assets using custom layouts + + .PARAMETER Name + Name of the Asset + + .PARAMETER CompanyId + Company id for asset + + .PARAMETER AssetLayoutId + Asset layout id + + .PARAMETER Fields + Array of custom fields and values + + .PARAMETER PrimarySerial + Asset primary serial number + + .PARAMETER PrimaryMail + Asset primary mail + + .PARAMETER PrimaryModel + Asset primary model + + .PARAMETER PrimaryManufacturer + Asset primary manufacturer + + .PARAMETER Slug + Url identifier + + .EXAMPLE + New-HuduAsset -Name 'Some asset' -CompanyId 1 -Fields @(@{'field_name'='Field Value'}) + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [String]$Name, + + [Alias('company_id')] + [Parameter(Mandatory = $true)] + [Int]$CompanyId, + + [Alias('asset_layout_id')] + [Parameter(Mandatory = $true)] + [Int]$AssetLayoutId, + + [Array]$Fields, + + [Alias('primary_serial')] + [string]$PrimarySerial, + + [Alias('primary_mail')] + [string]$PrimaryMail, + + [Alias('primary_model')] + [string]$PrimaryModel, + + [Alias('primary_manufacturer')] + [string]$PrimaryManufacturer + ) + + $Asset = [ordered]@{asset = [ordered]@{} } + + $Asset.asset.add('name', $Name) + $Asset.asset.add('asset_layout_id', $AssetLayoutId) + + + if ($PrimarySerial) { + $Asset.asset.add('primary_serial', $PrimarySerial) + } + + if ($PrimaryMail) { + $Asset.asset.add('primary_mail', $PrimaryMail) + } + + if ($PrimaryModel) { + $Asset.asset.add('primary_model', $PrimaryModel) + } + + if ($PrimaryManufacturer) { + $Asset.asset.add('primary_manufacturer', $PrimaryManufacturer) + } + + if ($Fields) { + $Asset.asset.add('custom_fields', $Fields) + } + + if ($Slug) { + $Asset.asset.add('slug', $Slug) + } + + $JSON = $Asset | ConvertTo-Json -Depth 10 + + if ($PSCmdlet.ShouldProcess($Name)) { + Invoke-HuduRequest -Method post -Resource "/api/v1/companies/$CompanyId/assets" -Body $JSON + } +} +#EndRegion './Public/New-HuduAsset.ps1' 104 +#Region './Public/New-HuduAssetLayout.ps1' -1 + +function New-HuduAssetLayout { + <# + .SYNOPSIS + Create an Asset Layout + + .DESCRIPTION + Uses Hudu API to create new custom asset layout + + .PARAMETER Name + Name of the layout + + .PARAMETER Icon + FontAwesome Icon class name, example: "fas fa-home" + + .PARAMETER Color + Background color hex code + + .PARAMETER IconColor + Icon color hex code + + .PARAMETER IncludePasswords + Boolean for including passwords + + .PARAMETER IncludePhotos + Boolean for including photos + + .PARAMETER IncludeComments + Boolean for including comments + + .PARAMETER IncludeFiles + Boolean for including files + + .PARAMETER PasswordTypes + List of password types, separated with new line characters + + .PARAMETER Slug + Url identifier + + .PARAMETER Fields + Array of hashtable or custom objects representing layout fields. Most field types only require a label and type. + Valid field types are: Text, RichText, Heading, CheckBox, Website (aka Link), Password (aka ConfidentialText), Number, Date, DropDown, Embed, Email (aka CopyableText), Phone, AssetLink + Field types are Case Sensitive as of Hudu V2.27 due to a known issue with asset type validation. + + .EXAMPLE + New-HuduAssetLayout -Name 'Test asset layout' -Icon 'fas fa-home' -IncludePassword $true + + .EXAMPLE + New-HuduAssetLayout -Name 'Test asset layout' -Icon 'fas fa-home' -IncludePassword $true -Fields @( + @{label = 'Test field'; 'field_type' = 'Text'} + ) + #> + [CmdletBinding(SupportsShouldProcess)] + # This will silence the warning for variables with Password in their name. + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] + Param ( + [Parameter(Mandatory = $true)] + [String]$Name, + + [Parameter(Mandatory = $true)] + [String]$Icon, + + [Parameter(Mandatory = $true)] + [String]$Color, + + [Alias('icon_color')] + [Parameter(Mandatory = $true)] + [String]$IconColor, + + [Alias('include_passwords')] + [bool]$IncludePasswords = '', + + [Alias('include_photos')] + [bool]$IncludePhotos = '', + + [Alias('include_comments')] + [bool]$IncludeComments = '', + + [Alias('include_files')] + [bool]$IncludeFiles = '', + + [Alias('password_types')] + [String]$PasswordTypes = '', + + [Parameter(Mandatory = $true)] + [system.collections.generic.list[hashtable]]$Fields + ) + + foreach ($field in $fields) { + if ($field.show_in_list) { $field.show_in_list = [System.Convert]::ToBoolean($field.show_in_list) } else { $field.remove('show_in_list') } + if ($field.required) { $field.required = [System.Convert]::ToBoolean($field.required) } else { $field.remove('required') } + if ($field.expiration) { $field.expiration = [System.Convert]::ToBoolean($field.expiration) } else { $field.remove('expiration') } + # A bug in versions of Hudu 2.27 and earlier can cause asset layouts to become corrupted if the field type value is not properly cased. + switch ($field.'field_type') { + 'text' { $field.'field_type' = 'Text' } + 'richtext' { $field.'field_type' = 'RichText' } + 'heading' { $field.'field_type' = 'Heading' } + 'checkbox' { $field.'field_type' = 'CheckBox' } + 'number' { $field.'field_type' = 'Number' } + 'date' { $field.'field_type' = 'Date' } + 'dropdown' { $field.'field_type' = 'Dropdown' } + 'embed' { $field.'field_type' = 'Embed' } + 'phone' { $field.'field_type' = 'Phone' } + 'email' { $field.'field_type' = 'Email' } + 'copyabletext' { $field.'field_type' = 'Email' } + 'assettag' { $field.'field_type' = 'AssetTag' } + 'assetlink' { $field.'field_type' = 'AssetTag' } + 'website' { $field.'field_type' = 'Website' } + 'link' { $field.'field_type' = 'Website' } + 'password' { $field.'field_type' = 'Password' } + 'confidentialtext' { $field.'field_type' = 'Password' } + Default { throw "Invalid field type: $($field.'field_type') found in field $($field.name)" } + } + } + + $AssetLayout = [ordered]@{asset_layout = [ordered]@{} } + + $AssetLayout.asset_layout.add('name', $Name) + $AssetLayout.asset_layout.add('icon', $Icon) + $AssetLayout.asset_layout.add('color', $Color) + $AssetLayout.asset_layout.add('icon_color', $IconColor) + $AssetLayout.asset_layout.add('fields', $Fields) + #$AssetLayout.asset_layout.add('active', $Active) + + if ($IncludePasswords) { + $AssetLayout.asset_layout.add('include_passwords', [System.Convert]::ToBoolean($IncludePasswords)) + } + + if ($IncludePhotos) { + $AssetLayout.asset_layout.add('include_photos', [System.Convert]::ToBoolean($IncludePhotos)) + } + + if ($IncludeComments) { + $AssetLayout.asset_layout.add('include_comments', [System.Convert]::ToBoolean($IncludeComments)) + } + + if ($IncludeFiles) { + $AssetLayout.asset_layout.add('include_files', [System.Convert]::ToBoolean($IncludeFiles)) + } + + if ($PasswordTypes) { + $AssetLayout.asset_layout.add('password_types', $PasswordTypes) + } + + if ($Slug) { + $AssetLayout.asset_layout.add('slug', $Slug) + } + + $JSON = $AssetLayout | ConvertTo-Json -Depth 10 + + Write-Verbose $JSON + + if ($PSCmdlet.ShouldProcess($Name)) { + Invoke-HuduRequest -Method post -Resource '/api/v1/asset_layouts' -Body $JSON + } +} +#EndRegion './Public/New-HuduAssetLayout.ps1' 156 +#Region './Public/New-HuduBaseURL.ps1' -1 + +function New-HuduBaseURL { + <# + .SYNOPSIS + Set Hudu Base URL + + .DESCRIPTION + In order to access the Hudu API the Base URL must be set + + .PARAMETER BaseURL + Url with no trailing slash e.g. https://demo.huducloud.com + + .EXAMPLE + New-HuduBaseURL -BaseURL https://demo.huducloud.com + + .NOTES + General notes + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function')] + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $false, + ValueFromPipeline = $true)] + [String] + $BaseURL + ) + process { + if (!$BaseURL) { + $BaseURL = Read-Host -Prompt 'Please enter your Hudu Base URL with no trailing /, for example https://demo.huducloud.com :' + } + + $Protocol = $BaseURL[0..7] -join '' + if ($Protocol -ne 'https://') { + if ($Protocol -like 'http://*') { + Write-Warning "Non HTTPS Base URL was set, rewriting URL to be secure transport only. If connection fails please make sure hostname is correct and HTTPS is enabld." + $BaseURL = $BaseURL.Replace('http://','https://') + } + else { + Write-Warning "No protocol was specified, adding https:// to the beginning of the specified hostname" + $BaseURL = "https://$BaseURL" + } + } + + Set-Variable -Name 'Int_HuduBaseURL' -Value $BaseURL -Visibility Private -Scope script -Force + + if ($script:Int_HuduAPIKey) { + [version]$Version = (Get-HuduAppInfo).version + if ($Version -lt $script:HuduRequiredVersion) { + Write-Warning "A connection error occured or Hudu version is below $script:HuduRequiredVersion" + } + } + } +} +#EndRegion './Public/New-HuduBaseURL.ps1' 53 +#Region './Public/New-HuduCompany.ps1' -1 + +function New-HuduCompany { + <# + .SYNOPSIS + Create a company + + .DESCRIPTION + Uses Hudu API to create a new company + + .PARAMETER Name + Company name + + .PARAMETER Nickname + Company nickname + + .PARAMETER CompanyType + Company type + + .PARAMETER AddressLine1 + Address line 1 + + .PARAMETER AddressLine2 + Address line 2 + + .PARAMETER City + City + + .PARAMETER State + State + + .PARAMETER Zip + Zip + + .PARAMETER CountryName + Country + + .PARAMETER PhoneNumber + Phone number + + .PARAMETER FaxNumber + Fax number + + .PARAMETER Website + Website + + .PARAMETER IdNumber + Company id number + + .PARAMETER ParentCompanyId + Parent company id number + + .PARAMETER Notes + Parameter description + + .PARAMETER Slug + Url identifier + + .EXAMPLE + New-HuduCompany -Name 'Company name' + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [String]$Name, + + [String]$Nickname = '', + + [Alias('company_type')] + [String]$CompanyType = '', + + [Alias('address_line_1')] + [String]$AddressLine1 = '', + + [Alias('address_line_2')] + [String]$AddressLine2 = '', + + [String]$City = '', + + [String]$State = '', + + [Alias('PostalCode', 'PostCode')] + [String]$Zip = '', + + [Alias('country_name')] + [String]$CountryName = '', + + [Alias('phone_number')] + [String]$PhoneNumber = '', + + [Alias('fax_number')] + [String]$FaxNumber = '', + + [String]$Website = '', + + [Alias('id_number')] + [String]$IdNumber = '', + + [Alias('parent_company_id')] + [int]$ParentCompanyId, + + [String]$Notes = '', + + [string]$Slug + ) + + + $Company = [ordered]@{company = [ordered]@{} } + + $Company.company.add('name', $Name) + if (-not ([string]::IsNullOrEmpty($Nickname))) { $Company.company.add('nickname', $Nickname) } + if (-not ([string]::IsNullOrEmpty($Nickname))) { $Company.company.add('company_type', $CompanyType) } + if (-not ([string]::IsNullOrEmpty($AddressLine1))) { $Company.company.add('address_line_1', $AddressLine1) } + if (-not ([string]::IsNullOrEmpty($AddressLine2))) { $Company.company.add('address_line_2', $AddressLine2) } + if (-not ([string]::IsNullOrEmpty($City))) { $Company.company.add('city', $City) } + if (-not ([string]::IsNullOrEmpty($State))) { $Company.company.add('state', $State) } + if (-not ([string]::IsNullOrEmpty($Zip))) { $Company.company.add('zip', $Zip) } + if (-not ([string]::IsNullOrEmpty($CountryName))) { $Company.company.add('country_name', $CountryName) } + if (-not ([string]::IsNullOrEmpty($PhoneNumber))) { $Company.company.add('phone_number', $PhoneNumber) } + if (-not ([string]::IsNullOrEmpty($FaxNumber))) { $Company.company.add('fax_number', $FaxNumber) } + if (-not ([string]::IsNullOrEmpty($Website))) { $Company.company.add('website', $Website) } + if (-not ([string]::IsNullOrEmpty($IdNumber))) { $Company.company.add('id_number', $IdNumber) } + if (-not ([string]::IsNullOrEmpty($ParentCompanyId))) { $Company.company.add('parent_company_id', $ParentCompanyId) } + if (-not ([string]::IsNullOrEmpty($Notes))) { $Company.company.add('notes', $Notes) } + if (-not ([string]::IsNullOrEmpty($Slug))) { $Company.company.add('slug', $Slug) } + + $JSON = $Company | ConvertTo-Json -Depth 10 + Write-Verbose $JSON + + if ($PSCmdlet.ShouldProcess($Name)) { + Invoke-HuduRequest -Method post -Resource '/api/v1/companies' -Body $JSON + } +} +#EndRegion './Public/New-HuduCompany.ps1' 133 +#Region './Public/New-HuduCustomHeaders.ps1' -1 + +function New-HuduCustomHeaders { + <# + .SYNOPSIS + Set Hudu custom headers to be injected into each request + + .DESCRIPTION + There may be times when one might need to use custom headers e.g. Service Tokens for Cloudflare Zero Trust + + .PARAMETER Headers + Hashtable with the Custom Headers that need to be injected into each request + + .EXAMPLE + New-HuduCustomHeaders -Headers @{"CF-Access-Client-Id" = "x"; "CF-Access-Client-Secret" = "y"} + + .NOTES + General notes + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function')] + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true, + ValueFromPipeline = $true)] + [hashtable] + $Headers + ) + process { + if ($Headers.Count -eq 0) { + Write-Host "Empty Custom Header hashtable was provided, no Custom Headers will be set" + return 0 + } + + Set-Variable -Name 'Int_HuduCustomHeaders' -Value $Headers -Visibility Private -Scope script -Force + } +} +#EndRegion './Public/New-HuduCustomHeaders.ps1' 35 +#Region './Public/New-HuduFolder.ps1' -1 + +function New-HuduFolder { + <# + .SYNOPSIS + Create a Folder + + .DESCRIPTION + Uses Hudu API to create a new folder + + .PARAMETER Name + Name of the folder + + .PARAMETER Icon + Folder Icon + + .PARAMETER Description + Folder description + + .PARAMETER ParentFolderId + Parent folder ID + + .PARAMETER CompanyId + Company id + + .EXAMPLE + New-HuduFolder -Name 'Test folder' -CompanyId 1 + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [String]$Name, + [String]$Icon = '', + [String]$Description = '', + [Alias('parent_folder_id')] + [Int]$ParentFolderId = '', + [Alias('company_id')] + [Int]$CompanyId = '' + ) + + $Folder = [ordered]@{folder = [ordered]@{} } + + $Folder.folder.add('name', $Name) + + if ($Icon) { + $Folder.folder.add('icon', $Icon) + } + + if ($Description) { + $Folder.folder.add('description', $Description) + } + + if ($ParentFolderId) { + $Folder.folder.add('parent_folder_id', $ParentFolderId) + } + + if ($CompanyId) { + $Folder.folder.add('company_id', $CompanyId) + } + + $JSON = $Folder | ConvertTo-Json + + if ($PSCmdlet.ShouldProcess($Name)) { + Invoke-HuduRequest -Method post -Resource '/api/v1/folders' -Body $JSON + } +} +#EndRegion './Public/New-HuduFolder.ps1' 66 +#Region './Public/New-HuduPassword.ps1' -1 + +function New-HuduPassword { + <# + .SYNOPSIS + Create a Password + + .DESCRIPTION + Uses Hudu API to create a new password + + .PARAMETER Name + Name of the password + + .PARAMETER CompanyId + Company id + + .PARAMETER PasswordableType + Asset type for the password + + .PARAMETER PasswordableId + Asset id for the password + + .PARAMETER InPortal + Boolean for in portal + + .PARAMETER Password + Password + + .PARAMETER OTPSecret + OTP secret + + .PARAMETER URL + Password URL + + .PARAMETER Username + Username + + .PARAMETER Description + Password description + + .PARAMETER PasswordType + Password type + + .PARAMETER PasswordFolderId + Password folder id + + .PARAMETER Slug + Url identifier + + .EXAMPLE + New-HuduPassword -Name 'Some website password' -Username 'user@domain.com' -Password '12345' + + #> + [CmdletBinding(SupportsShouldProcess)] + # This will silence the warning for variables with Password in their name. + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUsernameAndPasswordParams', '')] + Param ( + [Parameter(Mandatory = $true)] + [String]$Name, + + [Alias('company_id')] + [Parameter(Mandatory = $true)] + [Int]$CompanyId, + + [Alias('passwordable_type')] + [String]$PasswordableType = '', + + [Alias('passwordable_id')] + [int]$PasswordableId = '', + + [Alias('in_portal')] + [Bool]$InPortal = $false, + + [Parameter(Mandatory = $true)] + [String]$Password = '', + + [Alias('otp_secret')] + [string]$OTPSecret = '', + + [String]$URL = '', + + [String]$Username = '', + + [String]$Description = '', + + [Alias('password_type')] + [String]$PasswordType = '', + + [Alias('password_folder_id')] + [int]$PasswordFolderId, + + [string]$Slug + ) + + $AssetPassword = [ordered]@{asset_password = [ordered]@{} } + + $AssetPassword.asset_password.add('name', $Name) + $AssetPassword.asset_password.add('company_id', $CompanyId) + $AssetPassword.asset_password.add('password', $Password) + $AssetPassword.asset_password.add('in_portal', $InPortal) + + if ($PasswordableType) { + $AssetPassword.asset_password.add('passwordable_type', $PasswordableType) + } + if ($PasswordableId) { + $AssetPassword.asset_password.add('passwordable_id', $PasswordableId) + } + + if ($OTPSecret) { + $AssetPassword.asset_password.add('otp_secret', $OTPSecret) + } + + if ($URL) { + $AssetPassword.asset_password.add('url', $URL) + } + + if ($Username) { + $AssetPassword.asset_password.add('username', $Username) + } + + if ($Description) { + $AssetPassword.asset_password.add('description', $Description) + } + + if ($PasswordType) { + $AssetPassword.asset_password.add('password_type', $PasswordType) + } + + if ($PasswordFolderId) { + $AssetPassword.asset_password.add('password_folder_id', $PasswordFolderId) + } + + if ($Slug) { + $AssetPassword.asset_password.add('slug', $Slug) + } + + $JSON = $AssetPassword | ConvertTo-Json -Depth 10 + + if ($PSCmdlet.ShouldProcess($Name)) { + Invoke-HuduRequest -Method post -Resource '/api/v1/asset_passwords' -Body $JSON + } +} +#EndRegion './Public/New-HuduPassword.ps1' 142 +#Region './Public/New-HuduPublicPhoto.ps1' -1 + +function New-HuduPublicPhoto { + <# + .SYNOPSIS + Create a Public Photo + + .DESCRIPTION + Uses Hudu API to upload an image for use in an asset or article + + .PARAMETER FilePath + Path to the image + + .PARAMETER RecordId + Record id to associate with the photo + + .PARAMETER RecordType + Record type to associate with the photo + + .EXAMPLE + New-HuduPublicPhoto -FilePath 'c:\path\to\image.png' -RecordId 1 -RecordType 'asset' + + #> + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(Mandatory)] + [string]$FilePath, + + [Alias('record_id')] + [int]$RecordId, + + [Alias('record_type')] + [string]$RecordType + ) + + $File = Get-Item $FilePath + $form = @{ + photo = $File + } + + if ($RecordId) { $form['record_id'] = $RecordId } + if ($RecordType) { $form['record_type'] = $RecordType } + + if ($PSCmdlet.ShouldProcess($File.FullName)) { + Invoke-HuduRequest -Method POST -Resource '/api/v1/public_photos' -Form $form + } +} +#EndRegion './Public/New-HuduPublicPhoto.ps1' 46 +#Region './Public/New-HuduRelation.ps1' -1 + +function New-HuduRelation { + <# + .SYNOPSIS + Create a Relation + + .DESCRIPTION + Uses Hudu API to create relationships between objects + + .PARAMETER Description + Give a description to the relation so you know why two things are related + + .PARAMETER FromableType + The type of the FROM relation (Asset, Website, Procedure, AssetPassword, Company, Article) + + .PARAMETER FromableID + The ID of the FROM relation + + .PARAMETER ToableType + The type of the TO relation (Asset, Website, Procedure, AssetPassword, Company, Article) + + .PARAMETER ToableID + The ID of the TO relation + + .PARAMETER IsInverse + When a relation is created, it will also create another relation that is the inverse. When this is true, this relation is the inverse. + + .EXAMPLE + An example + + .NOTES + General notes + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [String]$Description, + + [Parameter(Mandatory = $true)] + [ValidateSet('Asset', 'Website', 'Procedure', 'AssetPassword', 'Company', 'Article')] + [Alias('fromable_type')] + [String]$FromableType, + + [Alias('fromable_id')] + [int]$FromableID, + + [Alias('toable_type')] + [String]$ToableType, + + [Alias('toable_id')] + [int]$ToableID, + + [Alias('is_inverse')] + [string]$IsInverse + ) + + $Relation = [ordered]@{relation = [ordered]@{} } + + $Relation.relation.add('fromable_type', $FromableType) + $Relation.relation.add('fromable_id', $FromableID) + $Relation.relation.add('toable_type', $ToableType) + $Relation.relation.add('toable_id', $ToableID) + + if ($Description) { + $Relation.relation.add('description', $Description) + } + + if ($ISInverse) { + $Relation.relation.add('is_inverse', $ISInverse) + } + + $JSON = $Relation | ConvertTo-Json -Depth 100 + + if ($PSCmdlet.ShouldProcess($FromableType)) { + Invoke-HuduRequest -Method post -Resource '/api/v1/relations' -Body $JSON + } +} +#EndRegion './Public/New-HuduRelation.ps1' 76 +#Region './Public/New-HuduUpload.ps1' -1 + +function New-HuduUpload { + <# + .SYNOPSIS + Create a Upload + + .DESCRIPTION + Uses Hudu API to upload a file for use in an asset. RecordType can be of 'asset','website','procedure','assetpassword','comapny','article'. + + .PARAMETER FilePath + Path to the file + + .PARAMETER RecordId + Record id to associate with the Upload + + .PARAMETER RecordType + Record type to associate with the Upload + + .EXAMPLE + New-HuduUpload -FilePath 'c:\path\to\file.png' -RecordId 1 -RecordType 'asset' + + #> + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(Mandatory)] + [string]$FilePath, + + [Parameter(Mandatory)] + [Alias('record_id','recordid')] + [int]$uploadable_id, + + [Parameter(Mandatory)] + [Alias('record_type','recordtype')] + [ValidateSet('Asset', 'Website', 'Procedure', 'AssetPassword', 'Company', 'Article')] + [string]$uploadable_type + ) + + $File = Get-Item $FilePath + + $form = @{ + file = $File + "upload[uploadable_id]" = $uploadable_id + "upload[uploadable_type]" = $uploadable_type + } + + if ($PSCmdlet.ShouldProcess($File.FullName)) { + Invoke-HuduRequest -Method POST -Resource '/api/v1/uploads' -Form $form + } +} +#EndRegion './Public/New-HuduUpload.ps1' 49 +#Region './Public/New-HuduWebsite.ps1' -1 + +function New-HuduWebsite { + <# + .SYNOPSIS + Create a Website + + .DESCRIPTION + Uses Hudu API to create a website + + .PARAMETER Name + Website name (e.g. https://domain.com) + + .PARAMETER Notes + Used to add additional notes to a website + + .PARAMETER Paused + When true, website monitoring is paused + + .PARAMETER CompanyId + Used to associate website with company + + .PARAMETER DisableDNS + When true, dns monitoring is paused. + + .PARAMETER DisableSSL + When true, ssl cert monitoring is paused. + + .PARAMETER DisableWhois + When true, whois monitoring is paused. + + .PARAMETER Slug + Url identifier + + .EXAMPLE + New-HuduWebsite -CompanyId 1 -Name https://domain.com + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [String]$Name, + + [String]$Notes = '', + + [String]$Paused = '', + + [Alias('company_id')] + [Parameter(Mandatory = $true)] + [Int]$CompanyId, + + [Alias('disable_dns')] + [String]$DisableDNS = '', + + [Alias('disable_ssl')] + [String]$DisableSSL = '', + + [Alias('disable_whois')] + [String]$DisableWhois = '', + + [string]$Slug + ) + + $Website = [ordered]@{website = [ordered]@{} } + + $Website.website.add('name', $Name) + + if ($Notes) { + $Website.website.add('notes', $Notes) + } + + if ($Paused) { + $Website.website.add('paused', $Paused) + } + + $Website.website.add('company_id', $CompanyId) + + if ($DisableDNS) { + $Website.website.add('disable_dns', $DisableDNS) + } + + if ($DisableSSL) { + $Website.website.add('disable_ssl', $DisableSSL) + } + + if ($DisableWhois) { + $Website.website.add('disable_whois', $DisableWhois) + } + + if ($Slug) { + $Website.website.add('slug', $Slug) + } + + $JSON = $Website | ConvertTo-Json + + if ($PSCmdlet.ShouldProcess($Name)) { + Invoke-HuduRequest -Method post -Resource '/api/v1/websites' -Body $JSON + } +} +#EndRegion './Public/New-HuduWebsite.ps1' 98 +#Region './Public/Remove-HuduAPIKey.ps1' -1 + +function Remove-HuduAPIKey { + <# + .SYNOPSIS + Remove API key + + .DESCRIPTION + Unsets the variable for the Hudu API Key + + .EXAMPLE + Remove-HuduAPIKey + + #> + [CmdletBinding(SupportsShouldProcess)] + Param() + + if ($PSCmdlet.ShouldProcess('API Key')) { + Remove-Variable -Name 'Int_HuduAPIKey' -Scope script -Force + } +} +#EndRegion './Public/Remove-HuduAPIKey.ps1' 20 +#Region './Public/Remove-HuduArticle.ps1' -1 + +function Remove-HuduArticle { + <# + .SYNOPSIS + Delete a Knowledge Base Article + + .DESCRIPTION + Uses Hudu API to remove a KB article + + .PARAMETER Id + Id of the requested article + + .EXAMPLE + Remove-HuduArticle -Id 1 + + .NOTES + General notes + #> + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] + Param ( + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] + [Int]$Id + ) + process { + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method delete -Resource "/api/v1/articles/$Id" + } + } +} +#EndRegion './Public/Remove-HuduArticle.ps1' 29 +#Region './Public/Remove-HuduAsset.ps1' -1 + +function Remove-HuduAsset { + <# + .SYNOPSIS + Delete an Asset + + .DESCRIPTION + Uses Hudu API to remove an Asset from a company + + .PARAMETER Id + Id of the requested Asset + + .PARAMETER CompanyId + Id of the requested parent Company + + .EXAMPLE + Remove-HuduAsset -CompanyId 1 -Id 1 + + #> + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] + Param ( + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] + [Int]$Id, + [Alias('company_id')] + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] + [Int]$CompanyId + ) + + process { + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method delete -Resource "/api/v1/companies/$CompanyId/assets/$Id" + } + } +} +#EndRegion './Public/Remove-HuduAsset.ps1' 34 +#Region './Public/Remove-HuduBaseURL.ps1' -1 + +function Remove-HuduBaseURL { + <# + .SYNOPSIS + Remove base URL + + .DESCRIPTION + Unsets the Hudu Base URL variable + + .EXAMPLE + Remove-HuduBaseURL + + #> + [CmdletBinding(SupportsShouldProcess)] + Param() + if ($PSCmdlet.ShouldProcess('Base URL')) { + Remove-Variable -Name 'Int_HuduBaseURL' -Scope script -Force + } +} +#EndRegion './Public/Remove-HuduBaseURL.ps1' 19 +#Region './Public/Remove-HuduCompany.ps1' -1 + +function Remove-HuduCompany { + <# + .SYNOPSIS + Delete a Website + + .DESCRIPTION + Uses Hudu API to delete a company + + .PARAMETER Id + Id of the Company to delete + + .EXAMPLE + Remove-HuduCompany -Id 1 + + #> + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] + Param ( + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] + [Int]$Id + ) + + process { + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method delete -Resource "/api/v1/companies/$Id" + } + } +} +#EndRegion './Public/Remove-HuduCompany.ps1' 28 +#Region './Public/Remove-HuduCustomHeaders.ps1' -1 + +function Remove-HuduCustomHeaders { + <# + .SYNOPSIS + Remove Custom Headers that are injected into each request + + .DESCRIPTION + Unsets the Hudu Custom Header variable + + .EXAMPLE + Remove-HuduCustomHeaders + + #> + [CmdletBinding(SupportsShouldProcess)] + Param() + if ($PSCmdlet.ShouldProcess('Custom Headers')) { + Remove-Variable -Name 'Int_HuduCustomHeaders' -Scope script -Force + } +} +#EndRegion './Public/Remove-HuduCustomHeaders.ps1' 19 +#Region './Public/Remove-HuduMagicDash.ps1' -1 + +function Remove-HuduMagicDash { + <# + .SYNOPSIS + Delete a Magic Dash Item + + .DESCRIPTION + Uses Hudu API to remove Magic Dash by Id or Title and Company Name + + .PARAMETER Title + Title of the Magic Dash + + .PARAMETER CompanyName + Company Name + + .PARAMETER Id + Id of the Magic Dash + + .EXAMPLE + Remove-HuduMagicDash -Id 1 + + .EXAMPLE + Remove-HuduMagicDash -Title 'Microsoft 365' -CompanyName 'AcmeCorp' + + #> + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High', DefaultParameterSetName = 'Id')] + Param ( + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true, ParameterSetName = 'TitleCompany')] + [String]$Title, + + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true, ParameterSetName = 'TitleCompany')] + [Alias('company_name')] + [String]$CompanyName, + + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true, ParameterSetName = 'Id')] + [int]$Id + ) + + process { + if ($id) { + if ($PSCmdlet.ShouldProcess($Id)) { + $null = Invoke-HuduRequest -Method delete -Resource "/api/v1/magic_dash/$Id" + } + } else { + $MagicDash = @{} + + $MagicDash.add('title', $Title) + $MagicDash.add('company_name', $CompanyName) + + $JSON = $MagicDash | ConvertTo-Json + + if ($PSCmdlet.ShouldProcess("$Company - $Title")) { + $null = Invoke-HuduRequest -Method delete -Resource '/api/v1/magic_dash' -Body $JSON + } + } + } +} +#EndRegion './Public/Remove-HuduMagicDash.ps1' 57 +#Region './Public/Remove-HuduPassword.ps1' -1 + +function Remove-HuduPassword { + <# + .SYNOPSIS + Delete a Password + + .DESCRIPTION + Uses Hudu API to remove asset password + + .PARAMETER Id + Id of the password + + .EXAMPLE + Remove-HuduPassword -Id 1 + + #> + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] + Param ( + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] + [Int]$Id + ) + process { + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method delete -Resource "/api/v1/asset_passwords/$Id" + } + } +} +#EndRegion './Public/Remove-HuduPassword.ps1' 27 +#Region './Public/Remove-HuduRelation.ps1' -1 + +function Remove-HuduRelation { + <# + .SYNOPSIS + Delete a Relation + + .DESCRIPTION + Uses Hudu API to delete object relationships + + .PARAMETER Id + Id of the requested Relation + + .EXAMPLE + Remove-HuduRelation -Id 1 + + #> + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] + Param ( + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] + [Int]$Id + ) + + process { + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method delete -Resource "/api/v1/relations/$Id" + } + } +} +#EndRegion './Public/Remove-HuduRelation.ps1' 28 +#Region './Public/Remove-HuduUpload.ps1' -1 + +function Remove-HuduUpload { + <# + .SYNOPSIS + Delete an Upload by ID + + .DESCRIPTION + Calls Hudu API to delete uploads by specifying the ID value + + .EXAMPLE + Remove-HuduUpload + + #> + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] + Param( + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] + [Int]$Id + ) + + process { + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method delete -Resource "/api/v1/uploads/$Id" + } + } + +} +#EndRegion './Public/Remove-HuduUpload.ps1' 26 +#Region './Public/Remove-HuduWebsite.ps1' -1 + +function Remove-HuduWebsite { + <# + .SYNOPSIS + Delete a Website + + .DESCRIPTION + Uses Hudu API to delete a website + + .PARAMETER Id + Id of the requested Website + + .EXAMPLE + Remove-HuduWebsite -Id 1 + + #> + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] + Param ( + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] + [Int]$Id + ) + + process { + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method delete -Resource "/api/v1/websites/$Id" + } + } +} +#EndRegion './Public/Remove-HuduWebsite.ps1' 28 +#Region './Public/Set-HuduArticle.ps1' -1 + +function Set-HuduArticle { + <# + .SYNOPSIS + Update a Knowledge Base Article + + .DESCRIPTION + Uses Hudu API to update KB Article + + .PARAMETER Name + Name of the Article + + .PARAMETER Content + Article Content + + .PARAMETER EnableSharing + Set article to public and generate a URL + + .PARAMETER FolderId + Used to associate article with folder + + .PARAMETER CompanyId + Used to associate article with company + + .PARAMETER ArticleId + Id of the requested article + + .PARAMETER Slug + Url identifier + + .EXAMPLE + Set-HuduArticle -ArticleId 1 -Name 'Article Name' -Content '

    New article contents

    ' + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [String]$Name, + + [String]$Content, + [switch]$EnableSharing, + + [Alias('folder_id')] + [Int]$FolderId = '', + + [Alias('company_id')] + [Int]$CompanyId = '', + + [Alias('article_id', 'id')] + [Parameter(Mandatory = $true)] + [Int]$ArticleId, + + [string]$Slug + ) + + $Object = Get-HuduArticles -Id $ArticleId + $Article = [ordered]@{article = $Object.article } + + if ($Name) { + $Article.article.name = $Name + } + + if ($Content) { + $Article.article.content = $Content + } + + if ($FolderId) { + $Article.article.folder_id = $FolderId + } + + if ($CompanyId) { + $Article.article.company_id = $CompanyId + } + + if ($EnableSharing.IsPresent) { + $Article.article.enable_sharing = $true + } + + if ($Slug) { + $Article.article.slug = $Slug + } + + $JSON = $Article | ConvertTo-Json -Depth 10 + + if ($PSCmdlet.ShouldProcess($Name)) { + Invoke-HuduRequest -Method put -Resource "/api/v1/articles/$ArticleId" -Body $JSON + } +} +#EndRegion './Public/Set-HuduArticle.ps1' 87 +#Region './Public/Set-HuduArticleArchive.ps1' -1 + +function Set-HuduArticleArchive { + <# + .SYNOPSIS + Archive/Unarchive a Knowledge Base Article + + .DESCRIPTION + Uses Hudu API to archive or unarchive an article + + .PARAMETER Id + Id of the requested article + + .PARAMETER Archive + Boolean for archive status + + .EXAMPLE + Set-HuduArticleArchive -Id 1 -Archive $true + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [Int]$Id, + [Parameter(Mandatory = $true)] + [Bool]$Archive + ) + + if ($Archive) { + $Action = 'archive' + } else { + $Action = 'unarchive' + } + + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method put -Resource "/api/v1/articles/$Id/$Action" + } +} +#EndRegion './Public/Set-HuduArticleArchive.ps1' 37 +#Region './Public/Set-HuduAsset.ps1' -1 + +function Set-HuduAsset { + <# + .SYNOPSIS + Update an Asset + + .DESCRIPTION + Uses Hudu API to update an Asset + + .PARAMETER Name + Name of the Asset + + .PARAMETER CompanyId + Company id of the Asset + + .PARAMETER AssetLayoutId + Asset layout id + + .PARAMETER Fields + List of fields + + .PARAMETER AssetId + Id of the requested Asset + + .PARAMETER PrimarySerial + Primary serial number + + .PARAMETER PrimaryMail + Primary mail + + .PARAMETER PrimaryModel + Primary model + + .PARAMETER PrimaryManufacturer + Primary manufacturer + + .PARAMETER Slug + Url identifier + + .EXAMPLE + Set-HuduAsset -AssetId 1 -CompanyId 1 -Fields @(@{'field_name'='Field Value'}) + + .NOTES + General notes + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [String]$Name, + + [Alias('company_id')] + [Int]$CompanyId, + + [Alias('asset_layout_id')] + [Int]$AssetLayoutId, + + [Array]$Fields, + + [Alias('asset_id','assetid')] + [Parameter(Mandatory = $true)] + [ValidateRange(1, [int]::MaxValue)] + [Int]$Id, + + [Alias('primary_serial')] + [string]$PrimarySerial, + + [Alias('primary_mail')] + [string]$PrimaryMail, + + [Alias('primary_model')] + [string]$PrimaryModel, + + [Alias('primary_manufacturer')] + [string]$PrimaryManufacturer, + + [string]$Slug + ) + + $Object = Get-HuduAssets -id $Id | Select-Object name,asset_layout_id,company_id,slug,primary_serial,primary_model,primary_mail,id,primary_manufacturer,@{n='custom_fields';e={$_.fields | ForEach-Object {[pscustomobject]@{$_.label.replace(' ','_').tolower()= $_.value}}}} + if ($Object) { + $Asset = [ordered]@{asset = $Object } + $CompanyId = $Object.company_id + + if ($Name) { + $Asset.asset.name = $Name + } + + if ($AssetLayoutId) { + $Asset.asset.asset_layout_id = $AssetLayoutId + } + + if ($PrimarySerial) { + $Asset.asset.primary_serial = $PrimarySerial + } + + if ($PrimaryMail) { + $Asset.asset.primary_mail = $PrimaryMail + } + + if ($PrimaryModel) { + $Asset.asset.primary_model = $PrimaryModel + } + + if ($PrimaryManufacturer) { + $Asset.asset.primary_manufacturer = $PrimaryManufacturer + } + + if ($Fields) { + $Asset.asset.custom_fields = $Fields + } + + if ($Slug) { + $Asset.asset.slug = $Slug + } + + $JSON = $Asset | ConvertTo-Json -Depth 10 + + if ($PSCmdlet.ShouldProcess("ID: $($Asset.id) Name: $($Asset.Name)", "Set Hudu Asset")) { + Invoke-HuduRequest -Method put -Resource "/api/v1/companies/$CompanyId/assets/$Id" -Body $JSON + } + } else { + throw "A valid asset could not be found to update, please double check the ID and try again" + } +} +#EndRegion './Public/Set-HuduAsset.ps1' 123 +#Region './Public/Set-HuduAssetArchive.ps1' -1 + +function Set-HuduAssetArchive { + <# + .SYNOPSIS + Archive/Unarchive an Asset + + .DESCRIPTION + Uses Hudu API to archive or unarchive an asset + + .PARAMETER Id + Id of the requested Asset + + .PARAMETER CompanyId + Id of the requested parent company + + .PARAMETER Archive + Boolean for archive status + + .EXAMPLE + Set-HuduAssetArchive -Id 1 -CompanyId 1 -Archive $true + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [Int]$Id, + [Alias('company_id')] + [Parameter(Mandatory = $true)] + [Int]$CompanyId, + [Parameter(Mandatory = $true)] + [Bool]$Archive + ) + + if ($Archive) { + $Action = 'archive' + } else { + $Action = 'unarchive' + } + + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method put -Resource "/api/v1/companies/$CompanyId/assets/$Id/$Action" + } +} +#EndRegion './Public/Set-HuduAssetArchive.ps1' 43 +#Region './Public/Set-HuduAssetLayout.ps1' -1 + +function Set-HuduAssetLayout { + <# + .SYNOPSIS + Update an Asset Layout + + .DESCRIPTION + Uses Hudu API to update an Asset Layout + + .PARAMETER Id + Id of the requested Asset Layout + + .PARAMETER Name + Name of the Asset Layout + + .PARAMETER Icon + Icon class name, example: "fas fa-home" + + .PARAMETER Color + Hex code for background color, example: #000000 + + .PARAMETER IconColor + Hex code for background color, example: #000000 + + .PARAMETER IncludePasswords + Boolean to include passwords + + .PARAMETER IncludePhotos + Boolean to include photos + + .PARAMETER IncludeComments + Boolean to include comments + + .PARAMETER IncludeFiles + Boolean to include files + + .PARAMETER PasswordTypes + List of password types, separated with new line characters + + .PARAMETER Slug + Url identifier + + .PARAMETER Fields + Array of hashtable or custom objects representing layout fields. Most field types only require a label and type. + Valid field types are: Text, RichText, Heading, CheckBox, Website (aka Link), Password (aka ConfidentialText), Number, Date, DropDown, Embed, Email (aka CopyableText), Phone, AssetLink + Field types are Case Sensitive as of Hudu V2.27 due to a known issue with asset type validation. + + .EXAMPLE + Set-HuduAssetLayout -Id 12 -Name 'Test asset layout' -Icon 'fas fa-home' -IncludePassword $true + + .EXAMPLE + Set-HuduAssetLayout -Id 12 -Fields @( + @{label = 'Test field'; 'field_type' = 'Text'} + ) + #> + [CmdletBinding(SupportsShouldProcess)] + # This will silence the warning for variables with Password in their name. + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] + Param ( + [Parameter(Mandatory = $true)] + [Int]$Id, + + [String]$Name, + + [String]$Icon, + + [String]$Color, + + [Alias('icon_color')] + [String]$IconColor, + + [Alias('include_passwords')] + [bool]$IncludePasswords, + + [Alias('include_photos')] + [bool]$IncludePhotos, + + [Alias('include_comments')] + [bool]$IncludeComments, + + [Alias('include_files')] + [bool]$IncludeFiles, + + [Alias('password_types')] + [String]$PasswordTypes = '', + + [bool]$Active, + + [string]$Slug, + + [array]$Fields + ) + + foreach ($Field in $Fields) { + $Field.show_in_list = [System.Convert]::ToBoolean($Field.show_in_list) + $Field.required = [System.Convert]::ToBoolean($Field.required) + $Field.expiration = [System.Convert]::ToBoolean($Field.expiration) + # A bug in versions of Hudu 2.27 and earlier can cause asset layouts to become corrupted if the field type value is not properly cased. + switch ($field.'field_type') { + 'text' { $field.'field_type' = 'Text' } + 'richtext' { $field.'field_type' = 'RichText' } + 'heading' { $field.'field_type' = 'Heading' } + 'checkbox' { $field.'field_type' = 'CheckBox' } + 'number' { $field.'field_type' = 'Number' } + 'date' { $field.'field_type' = 'Date' } + 'dropdown' { $field.'field_type' = 'Dropdown' } + 'embed' { $field.'field_type' = 'Embed' } + 'phone' { $field.'field_type' = 'Phone' } + ('email' -or 'copyabletext') { $field.'field_type' = 'Email' } + ('assettag' -or 'assetlink') { $field.'field_type' = 'AssetTag' } + ('website' -or 'link') { $field.'field_type' = 'Website' } + ('password' -or 'confidentialtext') { $field.'field_type' = 'Password' } + Default { Write-Error "Invalid field type: $($field.'field_type') found in field $($field.name)"; break } + } + } + $Object = Get-HuduAssetLayouts -id $Id + + $AssetLayout = [ordered]@{asset_layout = $Object } + #$AssetLayout.asset_layout = $Object + + if ($Name) { + $AssetLayout.asset_layout.name = $Name + } + + if ($Icon) { + $AssetLayout.asset_layout.icon = $Icon + } + + if ($Color) { + $AssetLayout.asset_layout.color = $Color + } + + if ($IconColor) { + $AssetLayout.asset_layout.icon_color = $IconColor + } + + if ($Fields) { + $AssetLayout.asset_layout.fields = $Fields + } + + if ($IncludePasswords) { + $AssetLayout.asset_layout.include_passwords = [System.Convert]::ToBoolean($IncludePasswords) + } + + if ($IncludePhotos) { + $AssetLayout.asset_layout.include_photos = [System.Convert]::ToBoolean($IncludePhotos) + } + + if ($IncludeComments) { + $AssetLayout.asset_layout.include_comments = [System.Convert]::ToBoolean($IncludeComments) + } + + if ($IncludeFiles) { + $AssetLayout.asset_layout.include_files = [System.Convert]::ToBoolean($IncludeFiles) + } + + if ($PasswordTypes) { + $AssetLayout.asset_layout.password_types = $PasswordTypes + } + + if ($SidebarFolderID) { + $AssetLayout.asset_layout.sidebar_folder_id = $SidebarFolderID + } + + if ($Slug) { + $AssetLayout.asset_layout.slug = $Slug + } + + if ($Active) { + $AssetLayout.asset_layout.active = [System.Convert]::ToBoolean($Active) + } + + $JSON = $AssetLayout | ConvertTo-Json -Depth 10 + + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method put -Resource "/api/v1/asset_layouts/$Id" -Body $JSON + } +} +#EndRegion './Public/Set-HuduAssetLayout.ps1' 178 +#Region './Public/Set-HuduCompany.ps1' -1 + +function Set-HuduCompany { + <# + .SYNOPSIS + Update a company + + .DESCRIPTION + Uses Hudu API to update a Company + + .PARAMETER Id + Id of the requested company + + .PARAMETER Name + Name of the company + + .PARAMETER Nickname + Nickname of the company + + .PARAMETER CompanyType + Company type + + .PARAMETER AddressLine1 + Address line 1 + + .PARAMETER AddressLine2 + Address line 2 + + .PARAMETER City + City + + .PARAMETER State + State + + .PARAMETER Zip + Zip + + .PARAMETER CountryName + Country name + + .PARAMETER PhoneNumber + Phone number + + .PARAMETER FaxNumber + Fax number + + .PARAMETER Website + Webste + + .PARAMETER IdNumber + Id number + + .PARAMETER ParentCompanyId + Parent company id + + .PARAMETER Notes + Company notes + + .PARAMETER Slug + Url identifier + + .EXAMPLE + Set-HuduCompany -Id 1 -Name 'New company name' + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [Int]$Id, + + [String]$Name, + + [String]$Nickname = '', + + [Alias('company_type')] + [String]$CompanyType = '', + + [Alias('address_line_1')] + [String]$AddressLine1 = '', + + [Alias('address_line_2')] + [String]$AddressLine2 = '', + + [String]$City = '', + + [String]$State = '', + + [Alias('PostalCode', 'PostCode')] + [String]$Zip = '', + + [Alias('country_name')] + [String]$CountryName = '', + + [Alias('phone_number')] + [String]$PhoneNumber = '', + + [Alias('fax_number')] + [String]$FaxNumber = '', + + [String]$Website = '', + + [Alias('id_number')] + [String]$IdNumber = '', + + [Alias('parent_company_id')] + [Int]$ParentCompanyId, + + [String]$Notes = '', + + [string]$Slug + ) + + $Object = Get-HuduCompanies -Id $Id + + $Company = [ordered]@{company = $Object } + + if ($Name) { + $Company.company.name = $Name + } + + if ($Nickname) { + $Company.company.nickname = $Nickname + } + + if ($CompanyType) { + $Company.company.company_type = $CompanyType + } + + if ($AddressLine1) { + $Company.company.address_line_1 = $AddressLine1 + } + + if ($AddressLine2) { + $Company.company.address_line_2 = $AddressLine2 + } + + if ($City) { + $Company.company.city = $City + } + + if ($State) { + $Company.company.state = $State + } + + if ($Zip) { + $Company.company.zip = $Zip + } + + if ($CountryName) { + $Company.company.country_name = $CountryName + } + + if ($PhoneNumber) { + $Company.company.phone_number = $PhoneNumber + } + + if ($FaxNumber) { + $Company.company.fax_number = $FaxNumber + } + + if ($Website) { + $Company.company.website = $Website + } + + if ($IdNumber) { + $Company.company.id_number = $IdNumber + } + + if ($ParentCompanyId) { + $Company.company.parent_company_id = $ParentCompanyId + } + + if ($Notes) { + $Company.company.notes = $Notes + } + + if ($Slug) { + $Company.company.slug = $Slug + } + + $JSON = $Company | ConvertTo-Json -Depth 10 + + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method put -Resource "/api/v1/companies/$Id" -Body $JSON + } +} +#EndRegion './Public/Set-HuduCompany.ps1' 185 +#Region './Public/Set-HuduCompanyArchive.ps1' -1 + +function Set-HuduCompanyArchive { + <# + .SYNOPSIS + Archive/Unarchive a company + + .DESCRIPTION + Uses Hudu API to set archive status on a company + + .PARAMETER Id + Id of the requested company + + .PARAMETER Archive + Boolean for archive status + + .EXAMPLE + Set-HuduCompanyArchive -Id 1 -Archive $true + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [Int]$Id, + [Parameter(Mandatory = $true)] + [Bool]$Archive + ) + + if ($Archive -eq $true) { + $Action = 'archive' + } else { + $Action = 'unarchive' + } + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method put -Resource "/api/v1/companies/$Id/$Action" + } +} +#EndRegion './Public/Set-HuduCompanyArchive.ps1' 36 +#Region './Public/Set-HuduFolder.ps1' -1 + +function Set-HuduFolder { + <# + .SYNOPSIS + Update a Folder + + .DESCRIPTION + Uses Hudu API to update a folder + + .PARAMETER Id + Id of the requested folder + + .PARAMETER Name + Name of the folder + + .PARAMETER Icon + Folder icon + + .PARAMETER Description + Folder description + + .PARAMETER ParentFolderId + Folder parent id + + .PARAMETER CompanyId + Folder company id + + .EXAMPLE + Set-HuduFolder -Id 1 -Name 'New folder name' + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [Int]$Id, + + [Parameter(Mandatory = $true)] + [String]$Name, + + [String]$Icon = '', + + [String]$Description = '', + + [Alias('parent_folder_id')] + [Int]$ParentFolderId = '', + + [Alias('company_id')] + [Int]$CompanyId = '' + ) + + $Folder = [ordered]@{folder = [ordered]@{} } + + $Folder.folder.add('name', $Name) + + if ($icon) { + $Folder.folder.add('icon', $Icon) + } + + if ($Description) { + $Folder.folder.add('description', $Description) + } + + if ($ParentFolderId) { + $Folder.folder.add('parent_folder_id', $ParentFolderId) + } + + if ($CompanyId) { + $Folder.folder.add('company_id', $CompanyId) + } + + $JSON = $Folder | ConvertTo-Json + + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method put -Resource "/api/v1/folders/$Id" -Body $JSON + } +} +#EndRegion './Public/Set-HuduFolder.ps1' 76 +#Region './Public/Set-HuduIntegrationMatcher.ps1' -1 + +function Set-HuduIntegrationMatcher { + <# + .SYNOPSIS + Update a Matcher + + .DESCRIPTION + Uses Hudu API to set integration matchers + + .PARAMETER Id + Id of the requested matcher + + .PARAMETER AcceptSuggestedMatch + Set the Sync Id/Identifier to the suggested one + + .PARAMETER CompanyId + Requested company id to match + + .PARAMETER PotentialCompanyId + Potential company id to match + + .PARAMETER SyncId + Sync id to match + + .PARAMETER Identifier + Identifier to match + + .EXAMPLE + Set-HuduIntegrationMatcher -Id 1 -AcceptSuggestedMatch + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] + [String]$Id, + + [Parameter(ParameterSetName = 'AcceptSuggestedMatch')] + [switch]$AcceptSuggestedMatch, + + [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'SetCompanyId')] + [Alias('company_id')] + [String]$CompanyId, + + [Parameter(ValueFromPipelineByPropertyName = $true)] + [Alias('potential_company_id')] + [String]$PotentialCompanyId, + + [Parameter(ValueFromPipelineByPropertyName = $true)] + [Alias('sync_id')] + [String]$SyncId, + + [Parameter(ValueFromPipelineByPropertyName = $true)] + [String]$Identifier + ) + + process { + $Matcher = [ordered]@{matcher = [ordered]@{} } + + if ($AcceptSuggestedMatch) { + $Matcher.matcher.add('company_id', $PotentialCompanyId) | Out-Null + } else { + $Matcher.matcher.add('company_id', $CompanyId) | Out-Null + } + + if ($PotentialCompanyId) { + $Matcher.matcher.add('potential_company_id', $PotentialCompanyId) | Out-Null + } + if ($SyncId) { + $Matcher.matcher.add('sync_id', $SyncId) | Out-Null + } + if ($Identifier) { + $Matcher.matcher.add('identifier', $identifier) | Out-Null + } + + $JSON = $Matcher | ConvertTo-Json -Depth 10 + + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method put -Resource "/api/v1/matchers/$Id" -Body $JSON + } + } +} +#EndRegion './Public/Set-HuduIntegrationMatcher.ps1' 81 +#Region './Public/Set-HuduMagicDash.ps1' -1 + +function Set-HuduMagicDash { + <# + .SYNOPSIS + Create or Update a Magic Dash Item + + .DESCRIPTION + Magic Dash takes just simple key-pairs. Whether you want to add a new Magic Dash Item, or update one, you can use the same endpoint, so it is really easy! It uses the title, and company_name to match. + + .PARAMETER Title + This is the title. If there is an existing Magic Dash Item with matching title and company_name, then it will match into that item. + + .PARAMETER CompanyName + This is the attribute we use to match to an existing company. If there is an existing Magic Dash Item with matching title and company_name, then it will match into that item. + + .PARAMETER Message + This will be the first content that will be displayed on the Magic Dash Item. + + .PARAMETER Icon + Either fill this in, or image_url. Use a (FontAwesome icon for the header of a Magic Dash Item. Must be in the format of fas fa-circle + + .PARAMETER ImageURL + Either fill this in, or icon. Used in the header of a Magic Dash Item. + + .PARAMETER ContentLink + Either fill this in, or content, or leave both blank. Used to have a link to an external website. + + .PARAMETER Content + Either fill this in, or content_link, or leave both blank. Fill in with HTML (tables, images, videos, etc.) to display more content in your Magic Dash Item. + + .PARAMETER Shade + Use a different color for your Magic Dash Item for different contextual states. Options are to leave it blank, success, or danger + + .EXAMPLE + Set-HuduMagicDash -Title 'Test Dash' -CompanyName 'Test Company' -Message 'This will be displayed first' + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [String]$Title, + + [Alias('company_name')] + [Parameter(Mandatory = $true)] + [String]$CompanyName, + + [Parameter(Mandatory = $true)] + [String]$Message, + + [String]$Icon = '', + + [Alias('image_url')] + [String]$ImageURL = '', + + [Alias('content_link')] + [String]$ContentLink = '', + + [String]$Content = '', + + [String]$Shade = '' + ) + + if ($Icon -and $ImageURL) { + Write-Error ('You can only use one of icon or image URL') + exit 1 + } + + if ($content_link -and $content) { + Write-Error ('You can only use one of content or content_link') + exit 1 + } + + $MagicDash = [ordered]@{} + + if ($Title) { + $MagicDash.add('title', $Title) + } + + if ($CompanyName) { + $MagicDash.add('company_name', $CompanyName) + } + + if ($Message) { + $MagicDash.add('message', $Message) + } + + if ($Icon) { + $MagicDash.add('icon', $Icon) + } + + if ($ImageURL) { + $MagicDash.add('image_url', $ImageURL) + } + + if ($ContentLink) { + $MagicDash.add('content_link', $ContentLink) + } + + if ($Content) { + $MagicDash.add('content', $Content) + } + + if ($Shade) { + $MagicDash.add('shade', $Shade) + } + + $JSON = $MagicDash | ConvertTo-Json + + if ($PSCmdlet.ShouldProcess("$Companyname - $Title")) { + Invoke-HuduRequest -Method post -Resource '/api/v1/magic_dash' -Body $JSON + } +} +#EndRegion './Public/Set-HuduMagicDash.ps1' 112 +#Region './Public/Set-HuduPassword.ps1' -1 + +function Set-HuduPassword { + <# + .SYNOPSIS + Update a Password + + .DESCRIPTION + Uses Hudu API to update a password + + .PARAMETER Id + Id of the requested Password + + .PARAMETER Name + Password name + + .PARAMETER CompanyId + Id of requested company + + .PARAMETER PasswordableType + Type of asset to associate with the password + + .PARAMETER PasswordableId + Id of the asset to associate with the password + + .PARAMETER InPortal + Display password in portal + + .PARAMETER Password + Password + + .PARAMETER OTPSecret + OTP secret + + .PARAMETER URL + Url for the password + + .PARAMETER Username + Username + + .PARAMETER Description + Password description + + .PARAMETER PasswordType + Password type + + .PARAMETER PasswordFolderId + Id of requested password folder + + .PARAMETER Slug + Url identifier + + .EXAMPLE + Set-HuduPassword -Id 1 -CompanyId 1 -Password 'this_is_my_new_password' + + #> + [CmdletBinding(SupportsShouldProcess)] + # This will silence the warning for variables with Password in their name. + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUsernameAndPasswordParams', '')] + Param ( + [Parameter(Mandatory = $true)] + [Int]$Id, + + [String]$Name, + + [Alias('company_id')] + [Int]$CompanyId, + + [Alias('passwordable_type')] + [String]$PasswordableType = '', + + [Alias('passwordable_id')] + [int]$PasswordableId = '', + + [Alias('in_portal')] + [Bool]$InPortal = $false, + [String]$Password = '', + + [Alias('otp_secret')] + [string]$OTPSecret = '', + + [String]$URL = '', + + [String]$Username = '', + + [String]$Description = '', + + [Alias('password_type')] + [String]$PasswordType = '', + + [Alias('password_folder_id')] + [int]$PasswordFolderId, + + [string]$Slug + ) + + $Object = Get-HuduPasswords -Id $Id + $AssetPassword = [ordered]@{asset_password = $Object } + + if ($Name) { + $AssetPassword.asset_password | Add-Member -MemberType NoteProperty -Name name -Force -Value $Name + + } + + if ($CompanyId) { + $AssetPassword.asset_password | Add-Member -MemberType NoteProperty -Name company_id -Force -Value $CompanyId + } + + if ($Password) { + $AssetPassword.asset_password | Add-Member -MemberType NoteProperty -Name password -Force -Value $Password + } + + if ($InPortal) { + $AssetPassword.asset_password | Add-Member -MemberType NoteProperty -Name in_portal -Force -Value $InPortal + } + + + if ($PasswordableType) { + $AssetPassword.asset_password | Add-Member -MemberType NoteProperty -Name passwordable_type -Force -Value $PasswordableType + } + if ($PasswordableId) { + $AssetPassword.asset_password | Add-Member -MemberType NoteProperty -Name passwordable_id -Force -Value $PasswordableId + } + + if ($OTPSecret) { + $AssetPassword.asset_password | Add-Member -MemberType NoteProperty -Name otp_secret -Force -Value $OTPSecret + } + + if ($URL) { + $AssetPassword.asset_password | Add-Member -MemberType NoteProperty -Name url -Force -Value $URL + } + + if ($Username) { + $AssetPassword.asset_password | Add-Member -MemberType NoteProperty -Name username -Force -Value $Username + } + + if ($Description) { + $AssetPassword.asset_password | Add-Member -MemberType NoteProperty -Name description -Force -Value $Description + } + + if ($PasswordType) { + $AssetPassword.asset_password | Add-Member -MemberType NoteProperty -Name password_type -Force -Value $PasswordType + } + + if ($PasswordFolderId) { + $AssetPassword.asset_password | Add-Member -MemberType NoteProperty -Name password_folder_id -Force -Value $PasswordFolderId + } + + if ($Slug) { + $AssetPassword.asset_password | Add-Member -MemberType NoteProperty -Name slug -Force -Value $Slug + } + + $JSON = $AssetPassword | ConvertTo-Json -Depth 10 + + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method put -Resource "/api/v1/asset_passwords/$Id" -Body $JSON + } +} +#EndRegion './Public/Set-HuduPassword.ps1' 158 +#Region './Public/Set-HuduPasswordArchive.ps1' -1 + +function Set-HuduPasswordArchive { + <# + .SYNOPSIS + Archive/Unarchive a Password + + .DESCRIPTION + Uses Hudu API to archive or unarchive a password + + .PARAMETER Id + Id of the requested Password + + .PARAMETER Archive + Boolean of archive status + + .EXAMPLE + Set-HuduPasswordArchive -Archive $true -Id 1 + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] + [Int]$Id, + [Parameter(Mandatory = $true)] + [Bool]$Archive + ) + + process { + if ($Archive) { + $Action = 'archive' + } else { + $Action = 'unarchive' + } + + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method put -Resource "/api/v1/asset_passwords/$Id/$Action" + } + } +} +#EndRegion './Public/Set-HuduPasswordArchive.ps1' 39 +#Region './Public/Set-HuduWebsite.ps1' -1 + +function Set-HuduWebsite { + <# + .SYNOPSIS + Update a Website + + .DESCRIPTION + Uses Hudu API to update a website + + .PARAMETER Id + Id of requested website + + .PARAMETER Name + Website name (e.g. https://example.com) + + .PARAMETER Notes + Website Notes + + .PARAMETER Paused + When true, website monitoring is paused. + + .PARAMETER CompanyId + Used to associate website with company + + .PARAMETER DisableDNS + When true, dns monitoring is paused. + + .PARAMETER DisableSSL + When true, ssl cert monitoring is paused. + + .PARAMETER DisableWhois + When true, whois monitoring is paused. + + .PARAMETER Slug + Url identifier + + .EXAMPLE + Set-HuduWebsite -Id 1 -Paused $true + + #> + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [Int]$Id, + + [Parameter(Mandatory = $true)] + [String]$Name, + + [String]$Notes = '', + + [String]$Paused = '', + + [Alias('company_id')] + [Parameter(Mandatory = $true)] + [Int]$CompanyId, + + [Alias('disable_dns')] + [String]$DisableDNS = '', + + [Alias('disable_ssl')] + [String]$DisableSSL = '', + + [Alias('disable_whois')] + [String]$DisableWhois = '', + + [string]$Slug + ) + + $Website = [ordered]@{website = [ordered]@{} } + + $Website.website.add('name', $Name) + + if ($Notes) { + $Website.website.add('notes', $Notes) + } + + if ($Paused) { + $Website.website.add('paused', $Paused) + } + + $Website.website.add('company_id', $companyid) + + if ($DisableDNS) { + $Website.website.add('disable_dns', $DisableDNS) + } + + if ($DisableSSL) { + $Website.website.add('disable_ssl', $DisableSSL) + } + + if ($DisableWhois) { + $Website.website.add('disable_whois', $DisableWhois) + } + + if ($Slug) { + $Website.website.add('slug', $Slug) + } + + $JSON = $Website | ConvertTo-Json + + if ($PSCmdlet.ShouldProcess($Id)) { + Invoke-HuduRequest -Method put -Resource "/api/v1/websites/$Id" -Body $JSON + } +} +#EndRegion './Public/Set-HuduWebsite.ps1' 104 diff --git a/Modules/HuduAPI/2.4.9/PSGetModuleInfo.xml b/Modules/HuduAPI/2.4.9/PSGetModuleInfo.xml new file mode 100644 index 000000000000..bc673458843c --- /dev/null +++ b/Modules/HuduAPI/2.4.9/PSGetModuleInfo.xml @@ -0,0 +1,248 @@ + + + + Microsoft.PowerShell.Commands.PSRepositoryItemInfo + System.Management.Automation.PSCustomObject + System.Object + + + HuduAPI + 2.4.9 + Module + This module provides an interface to the Hudu Rest API further information can be found at https://github.com/lwhitelock/HuduAPI + Luke Whitelock + + + System.Object[] + System.Array + System.Object + + + mspp + homotechsual + johnduprey + + + (c) 2021 Luke Whitelock. All rights reserved. +
    2024-06-30T03:42:36-04:00
    + + + + + + + + + PSModule + + + + + System.Collections.Hashtable + System.Object + + + + RoleCapability + + + + + + + DscResource + + + + Function + + + + Get-HuduActivityLogs + Get-HuduApiKey + Get-HuduAppInfo + Get-HuduArticles + Get-HuduAssetLayoutFieldID + Get-HuduAssetLayouts + Get-HuduAssets + Get-HuduBaseURL + Get-HuduCard + Get-HuduCompanies + Get-HuduExpirations + Get-HuduFolderMap + Get-HuduFolders + Get-HuduIntegrationMatchers + Get-HuduMagicDashes + Get-HuduObjectByUrl + Get-HuduPasswordFolders + Get-HuduPasswords + Get-HuduProcesses + Get-HuduPublicPhotos + Get-HuduRelations + Get-HuduUploads + Get-HuduWebsites + Initialize-HuduFolder + Move-HuduAssetsToNewLayout + New-HuduAPIKey + New-HuduArticle + New-HuduAsset + New-HuduAssetLayout + New-HuduBaseURL + New-HuduCompany + New-HuduCustomHeaders + New-HuduFolder + New-HuduPassword + New-HuduPublicPhoto + New-HuduRelation + New-HuduUpload + New-HuduWebsite + Remove-HuduAPIKey + Remove-HuduArticle + Remove-HuduAsset + Remove-HuduBaseURL + Remove-HuduCompany + Remove-HuduCustomHeaders + Remove-HuduMagicDash + Remove-HuduPassword + Remove-HuduRelation + Remove-HuduUpload + Remove-HuduWebsite + Set-HuduArticle + Set-HuduArticleArchive + Set-HuduAsset + Set-HuduAssetArchive + Set-HuduAssetLayout + Set-HuduCompany + Set-HuduCompanyArchive + Set-HuduFolder + Set-HuduIntegrationMatcher + Set-HuduMagicDash + Set-HuduPassword + Set-HuduPasswordArchive + Set-HuduWebsite + + + + + Cmdlet + + + + Command + + + + Get-HuduActivityLogs + Get-HuduApiKey + Get-HuduAppInfo + Get-HuduArticles + Get-HuduAssetLayoutFieldID + Get-HuduAssetLayouts + Get-HuduAssets + Get-HuduBaseURL + Get-HuduCard + Get-HuduCompanies + Get-HuduExpirations + Get-HuduFolderMap + Get-HuduFolders + Get-HuduIntegrationMatchers + Get-HuduMagicDashes + Get-HuduObjectByUrl + Get-HuduPasswordFolders + Get-HuduPasswords + Get-HuduProcesses + Get-HuduPublicPhotos + Get-HuduRelations + Get-HuduUploads + Get-HuduWebsites + Initialize-HuduFolder + Move-HuduAssetsToNewLayout + New-HuduAPIKey + New-HuduArticle + New-HuduAsset + New-HuduAssetLayout + New-HuduBaseURL + New-HuduCompany + New-HuduCustomHeaders + New-HuduFolder + New-HuduPassword + New-HuduPublicPhoto + New-HuduRelation + New-HuduUpload + New-HuduWebsite + Remove-HuduAPIKey + Remove-HuduArticle + Remove-HuduAsset + Remove-HuduBaseURL + Remove-HuduCompany + Remove-HuduCustomHeaders + Remove-HuduMagicDash + Remove-HuduPassword + Remove-HuduRelation + Remove-HuduUpload + Remove-HuduWebsite + Set-HuduArticle + Set-HuduArticleArchive + Set-HuduAsset + Set-HuduAssetArchive + Set-HuduAssetLayout + Set-HuduCompany + Set-HuduCompanyArchive + Set-HuduFolder + Set-HuduIntegrationMatcher + Set-HuduMagicDash + Set-HuduPassword + Set-HuduPasswordArchive + Set-HuduWebsite + + + + + Workflow + + + + + + + + + + + https://www.powershellgallery.com/api/v2 + PSGallery + NuGet + + + System.Management.Automation.PSCustomObject + System.Object + + + (c) 2021 Luke Whitelock. All rights reserved. + This module provides an interface to the Hudu Rest API further information can be found at https://github.com/lwhitelock/HuduAPI + False + True + True + 2653 + 1260331 + 24052 + 6/30/2024 3:42:36 AM -04:00 + 6/30/2024 3:42:36 AM -04:00 + 7/1/2024 8:11:32 PM -04:00 + PSModule PSFunction_Get-HuduActivityLogs PSCommand_Get-HuduActivityLogs PSFunction_Get-HuduApiKey PSCommand_Get-HuduApiKey PSFunction_Get-HuduAppInfo PSCommand_Get-HuduAppInfo PSFunction_Get-HuduArticles PSCommand_Get-HuduArticles PSFunction_Get-HuduAssetLayoutFieldID PSCommand_Get-HuduAssetLayoutFieldID PSFunction_Get-HuduAssetLayouts PSCommand_Get-HuduAssetLayouts PSFunction_Get-HuduAssets PSCommand_Get-HuduAssets PSFunction_Get-HuduBaseURL PSCommand_Get-HuduBaseURL PSFunction_Get-HuduCard PSCommand_Get-HuduCard PSFunction_Get-HuduCompanies PSCommand_Get-HuduCompanies PSFunction_Get-HuduExpirations PSCommand_Get-HuduExpirations PSFunction_Get-HuduFolderMap PSCommand_Get-HuduFolderMap PSFunction_Get-HuduFolders PSCommand_Get-HuduFolders PSFunction_Get-HuduIntegrationMatchers PSCommand_Get-HuduIntegrationMatchers PSFunction_Get-HuduMagicDashes PSCommand_Get-HuduMagicDashes PSFunction_Get-HuduObjectByUrl PSCommand_Get-HuduObjectByUrl PSFunction_Get-HuduPasswordFolders PSCommand_Get-HuduPasswordFolders PSFunction_Get-HuduPasswords PSCommand_Get-HuduPasswords PSFunction_Get-HuduProcesses PSCommand_Get-HuduProcesses PSFunction_Get-HuduPublicPhotos PSCommand_Get-HuduPublicPhotos PSFunction_Get-HuduRelations PSCommand_Get-HuduRelations PSFunction_Get-HuduUploads PSCommand_Get-HuduUploads PSFunction_Get-HuduWebsites PSCommand_Get-HuduWebsites PSFunction_Initialize-HuduFolder PSCommand_Initialize-HuduFolder PSFunction_Move-HuduAssetsToNewLayout PSCommand_Move-HuduAssetsToNewLayout PSFunction_New-HuduAPIKey PSCommand_New-HuduAPIKey PSFunction_New-HuduArticle PSCommand_New-HuduArticle PSFunction_New-HuduAsset PSCommand_New-HuduAsset PSFunction_New-HuduAssetLayout PSCommand_New-HuduAssetLayout PSFunction_New-HuduBaseURL PSCommand_New-HuduBaseURL PSFunction_New-HuduCompany PSCommand_New-HuduCompany PSFunction_New-HuduCustomHeaders PSCommand_New-HuduCustomHeaders PSFunction_New-HuduFolder PSCommand_New-HuduFolder PSFunction_New-HuduPassword PSCommand_New-HuduPassword PSFunction_New-HuduPublicPhoto PSCommand_New-HuduPublicPhoto PSFunction_New-HuduRelation PSCommand_New-HuduRelation PSFunction_New-HuduUpload PSCommand_New-HuduUpload PSFunction_New-HuduWebsite PSCommand_New-HuduWebsite PSFunction_Remove-HuduAPIKey PSCommand_Remove-HuduAPIKey PSFunction_Remove-HuduArticle PSCommand_Remove-HuduArticle PSFunction_Remove-HuduAsset PSCommand_Remove-HuduAsset PSFunction_Remove-HuduBaseURL PSCommand_Remove-HuduBaseURL PSFunction_Remove-HuduCompany PSCommand_Remove-HuduCompany PSFunction_Remove-HuduCustomHeaders PSCommand_Remove-HuduCustomHeaders PSFunction_Remove-HuduMagicDash PSCommand_Remove-HuduMagicDash PSFunction_Remove-HuduPassword PSCommand_Remove-HuduPassword PSFunction_Remove-HuduRelation PSCommand_Remove-HuduRelation PSFunction_Remove-HuduUpload PSCommand_Remove-HuduUpload PSFunction_Remove-HuduWebsite PSCommand_Remove-HuduWebsite PSFunction_Set-HuduArticle PSCommand_Set-HuduArticle PSFunction_Set-HuduArticleArchive PSCommand_Set-HuduArticleArchive PSFunction_Set-HuduAsset PSCommand_Set-HuduAsset PSFunction_Set-HuduAssetArchive PSCommand_Set-HuduAssetArchive PSFunction_Set-HuduAssetLayout PSCommand_Set-HuduAssetLayout PSFunction_Set-HuduCompany PSCommand_Set-HuduCompany PSFunction_Set-HuduCompanyArchive PSCommand_Set-HuduCompanyArchive PSFunction_Set-HuduFolder PSCommand_Set-HuduFolder PSFunction_Set-HuduIntegrationMatcher PSCommand_Set-HuduIntegrationMatcher PSFunction_Set-HuduMagicDash PSCommand_Set-HuduMagicDash PSFunction_Set-HuduPassword PSCommand_Set-HuduPassword PSFunction_Set-HuduPasswordArchive PSCommand_Set-HuduPasswordArchive PSFunction_Set-HuduWebsite PSCommand_Set-HuduWebsite PSIncludes_Function + False + 2024-07-01T20:11:32Z + 2.4.9 + Luke Whitelock + false + Module + HuduAPI.nuspec|HuduAPI.psm1|HuduAPI.psd1 + 4e0a4feb-1658-416b-b854-ab9e913a56de + 7.0 + MSPP + + + C:\GitHub\CIPP Workspace\CIPP-API\Modules\HuduAPI\2.4.9 +
    +
    +
    diff --git a/Scheduler_GetWebhooks/run.ps1 b/Scheduler_GetWebhooks/run.ps1 index 3eb4aaae42fd..cef8cfb6d726 100644 --- a/Scheduler_GetWebhooks/run.ps1 +++ b/Scheduler_GetWebhooks/run.ps1 @@ -3,7 +3,7 @@ param($Timer) try { $webhookTable = Get-CIPPTable -tablename webhookTable - $Webhooks = Get-CIPPAzDataTableEntity @webhookTable -Property RowKey + $Webhooks = Get-CIPPAzDataTableEntity @webhookTable -Property PartitionKey, RowKey if (($Webhooks | Measure-Object).Count -eq 0) { Write-Host 'No webhook subscriptions found. Exiting.' return diff --git a/Scheduler_UserTasks/run.ps1 b/Scheduler_UserTasks/run.ps1 index b5d5cdd874ca..950ca9e691bb 100644 --- a/Scheduler_UserTasks/run.ps1 +++ b/Scheduler_UserTasks/run.ps1 @@ -57,6 +57,11 @@ foreach ($task in $tasks) { } } if (($Batch | Measure-Object).Count -gt 0) { + # Create queue entry + $Queue = New-CippQueueEntry -Name 'Scheduled Tasks' -TotalTasks ($Batch | Measure-Object).Count + $QueueId = $Queue.RowKey + $Batch = $Batch | Select-Object *, @{Name = 'QueueId'; Expression = { $QueueId } }, @{Name = 'QueueName'; Expression = { '{0} - {1}' -f $_.TaskInfo.Name, ($_.TaskInfo.Tenant -ne 'AllTenants' ? $_.TaskInfo.Tenant : $_.Parameters.TenantFilter) } } + $InputObject = [PSCustomObject]@{ OrchestratorName = 'UserTaskOrchestrator' Batch = @($Batch) @@ -66,4 +71,4 @@ if (($Batch | Measure-Object).Count -gt 0) { $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 10 -Compress) Write-Host "Started orchestration with ID = '$InstanceId'" -} \ No newline at end of file +} diff --git a/Tools/Initialize-DevEnvironment.ps1 b/Tools/Initialize-DevEnvironment.ps1 index d712e396ad04..8612e74156cf 100644 --- a/Tools/Initialize-DevEnvironment.ps1 +++ b/Tools/Initialize-DevEnvironment.ps1 @@ -12,4 +12,6 @@ ForEach ($Key in $CIPPSettings.PSObject.Properties.Name) { Import-Module "$CippRoot\Modules\AzBobbyTables" Import-Module "$CippRoot\Modules\DNSHealth" Import-Module "$CippRoot\Modules\CippCore" -Get-CIPPAuthentication \ No newline at end of file +Import-Module "$CippRoot\Modules\CippExtensions" + +Get-CIPPAuthentication diff --git a/Tools/Update-StandardsComments.ps1 b/Tools/Update-StandardsComments.ps1 new file mode 100644 index 000000000000..6660c3c5e901 --- /dev/null +++ b/Tools/Update-StandardsComments.ps1 @@ -0,0 +1,110 @@ +<# +.SYNOPSIS + This script updates the comment block in the CIPP standard files. + +.DESCRIPTION + The script reads the standards.json file and updates the comment block in the corresponding CIPP standard files. + It adds or modifies the comment block based on the properties defined in the standards.json file. + This is made to be able to generate the help documentation for the CIPP standards automatically. + +.INPUTS + None. You cannot pipe objects to this script. + +.OUTPUTS + None. The script modifies the CIPP standard files directly. + +.EXAMPLE + Update-StandardsComments.ps1 + + This example runs the script to update the comment block in the CIPP standard files. + + +#> +param ( + [switch]$WhatIf +) + +# Find the paths to the standards.json file based on the current script path +$StandardsJSONPath = Split-Path (Split-Path $PSScriptRoot) +$StandardsJSONPath = Resolve-Path "$StandardsJSONPath\*\src\data\standards.json" +$StandardsInfo = Get-Content -Path $StandardsJSONPath | ConvertFrom-Json -Depth 10 + +foreach ($Standard in $StandardsInfo) { + + # Calculate the standards file name and path + $StandardFileName = $Standard.name -replace 'standards.', 'Invoke-CIPPStandard' + $StandardsFilePath = Resolve-Path "$(Split-Path $PSScriptRoot)\Modules\CIPPCore\Public\Standards\$StandardFileName.ps1" + if (-not (Test-Path $StandardsFilePath)) { + Write-Host "No file found for standard $($Standard.name)" -ForegroundColor Yellow + continue + } + $Content = (Get-Content -Path $StandardsFilePath -Raw).TrimEnd() + "`r`n" + + # Remove random newlines before the param block + $regexPattern = '#>\s*\r?\n\s*\r?\n\s*param' + $Content = $Content -replace $regexPattern, "#>`r`n`r`n param" + + # Regex to match the existing comment block + $Regex = '<#(.|\n)*?\.FUNCTIONALITY\s*Internal(.|\n)*?#>' + + if ($Content -match $Regex) { + $NewComment = [System.Collections.Generic.List[string]]::new() + # Add the initial scatic comments + $NewComment.Add("<#`r`n") + $NewComment.Add(" .FUNCTIONALITY`r`n") + $NewComment.Add(" Internal`r`n") + $NewComment.Add(" .COMPONENT`r`n") + $NewComment.Add(" (APIName) $($Standard.name -replace 'standards.', '')`r`n") + $NewComment.Add(" .SYNOPSIS`r`n") + $NewComment.Add(" (Label) $($Standard.label.ToString())`r`n") + $NewComment.Add(" .DESCRIPTION`r`n") + if ([string]::IsNullOrWhiteSpace($Standard.docsDescription)) { + $NewComment.Add(" (Helptext) $($Standard.helpText.ToString())`r`n") + $NewComment.Add(" (DocsDescription) $($Standard.helpText.ToString())`r`n") + } else { + $NewComment.Add(" (Helptext) $($Standard.helpText.ToString())`r`n") + $NewComment.Add(" (DocsDescription) $($Standard.docsDescription.ToString())`r`n") + } + $NewComment.Add(" .NOTES`r`n") + + # Loop through the rest of the properties of the standard and add them to the NOTES field + foreach ($Property in $Standard.PSObject.Properties) { + switch ($Property.Name) { + 'name' { continue } + 'impactColour' { continue } + 'docsDescription' { continue } + 'helpText' { continue } + 'label' { continue } + Default { + $NewComment.Add(" $($Property.Name.ToUpper())`r`n") + if ($Property.Value -is [System.Object[]]) { + foreach ($Value in $Property.Value) { + $NewComment.Add(" $(ConvertTo-Json -InputObject $Value -Depth 5 -Compress)`r`n") + } + continue + } + $NewComment.Add(" $($Property.Value.ToString())`r`n") + } + } + + } + + # Add header about how to update the comment block with this script + $NewComment.Add(" UPDATECOMMENTBLOCK`r`n") + $NewComment.Add(" Run the Tools\Update-StandardsComments.ps1 script to update this comment block`r`n") + # -Online help link + $NewComment.Add(" .LINK`r`n") + $NewComment.Add(" https://docs.cipp.app/user-documentation/tenant/standards/edit-standards`r`n") + $NewComment.Add(' #>') + + # Write the new comment block to the file + if ($WhatIf.IsPresent) { + Write-Host "Would update $StandardsFilePath with the following comment block:" + $NewComment + } else { + $Content -replace $Regex, $NewComment | Set-Content -Path $StandardsFilePath -Encoding utf8 -NoNewLine + } + } else { + Write-Host "No comment block found in $StandardsFilePath" -ForegroundColor Yellow + } +} diff --git a/openapi.json b/openapi.json index 20a5281ec68b..9deb23f30eee 100644 --- a/openapi.json +++ b/openapi.json @@ -4405,46 +4405,6 @@ } } }, - "/ExecDisableEmailForward": { - "post": { - "description": "ExecDisableEmailForward", - "summary": "ExecDisableEmailForward", - "tags": [ - "POST" - ], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "user", - "in": "body" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" - } - } - } - }, "/ExecEditCalendarPermissions": { "get": { "description": "ExecEditCalendarPermissions", diff --git a/version_latest.txt b/version_latest.txt index d2d714f2a990..288b2cd9a306 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.9.4 \ No newline at end of file +6.0.5