From afa25ec6602f05c1c38e0feed64b5c8b1457323a Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Sun, 23 Jun 2024 22:20:35 +0100 Subject: [PATCH 001/104] Create Invoke-ListGroupSenderAuthentication.ps1 --- .../Invoke-ListGroupSenderAuthentication.ps1 | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroupSenderAuthentication.ps1 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..1eaaf99e0555 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroupSenderAuthentication.ps1 @@ -0,0 +1,41 @@ +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 + + $params = @{ + Identity = $groupid + } + + try { + $Request = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-DistributionGroup' -cmdParams $params -UseSystemMailbox $true + $StatusCode = [HttpStatusCode]::OK + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + $StatusCode = [HttpStatusCode]::Forbidden + $Request = $ErrorMessage + } + + write-host "Group ID is: $($groupid)" + write-host "Tenant Filter is: $($TenantFilter)" + write-host "Search This New: $($Request.RequireSenderAuthenticationEnabled)" + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = $($Request.RequireSenderAuthenticationEnabled) + }) +} \ No newline at end of file From d1a315506145698f95da655a8abd82b8be88d1d5 Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Sun, 23 Jun 2024 22:22:17 +0100 Subject: [PATCH 002/104] Update Invoke-ListGroupSenderAuthentication.ps1 --- .../Groups/Invoke-ListGroupSenderAuthentication.ps1 | 4 ---- 1 file changed, 4 deletions(-) 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 index 1eaaf99e0555..305c8b927564 100644 --- 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 @@ -29,10 +29,6 @@ Function Invoke-ListGroupSenderAuthentication { $Request = $ErrorMessage } - write-host "Group ID is: $($groupid)" - write-host "Tenant Filter is: $($TenantFilter)" - write-host "Search This New: $($Request.RequireSenderAuthenticationEnabled)" - # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode From de594e0d47266f046c678a979ecee12a46d5bd34 Mon Sep 17 00:00:00 2001 From: cipptesting Date: Mon, 24 Jun 2024 19:47:35 -0400 Subject: [PATCH 003/104] Updated Anti-Phishing Policy Standards for more granular configuration --- .../Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 index 17eed3cd2d53..057d13cab8c0 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 @@ -56,10 +56,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 } From 8be3108563ae0fdae6442916d0942d9e658aa81f Mon Sep 17 00:00:00 2001 From: cipptesting Date: Mon, 24 Jun 2024 20:06:43 -0400 Subject: [PATCH 004/104] Fixed Invoke-CIPPStandardAntiPhishPolicy.ps1 --- .../Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 index 057d13cab8c0..4fa25313581d 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 @@ -9,7 +9,7 @@ function Invoke-CIPPStandardAntiPhishPolicy { $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 +23,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' From 764c4c7fba6d08cab2d3a8baa4b5279f9f49211f Mon Sep 17 00:00:00 2001 From: chase-vgo <168204519+chase-vgo@users.noreply.github.com> Date: Tue, 25 Jun 2024 09:15:50 -0500 Subject: [PATCH 005/104] Add Litigation Hold Standard --- ...nvoke-CIPPStandardEnableLitigationHold.ps1 | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 new file mode 100644 index 000000000000..1de0a2315a13 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 @@ -0,0 +1,54 @@ +function Invoke-CIPPStandardEnableLitigationHold { + <# + .FUNCTIONALITY + Internal + #> + param($Tenant, $Settings) + + $MailboxesNoLitHold = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' -cmdparams @{ MailboxPlan = 'ExchangeOnlineEnterprise'; Filter = 'LitigationHoldEnabled -eq "False"'} + + 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 + } +} From 7dd0eb79fae8eeb922a37cecbbcaebcfaba9f5b7 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 25 Jun 2024 20:31:04 -0400 Subject: [PATCH 006/104] Add excluded tenant info to onboarding --- .../Activity Triggers/Push-ExecOnboardTenantQueue.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 { From 46f9dad45d7a4e52259d8526f0a5dae5a9eb35d2 Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Wed, 26 Jun 2024 14:05:35 +0100 Subject: [PATCH 007/104] Update Invoke-ListGroupSenderAuthentication.ps1 --- .../Groups/Invoke-ListGroupSenderAuthentication.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 index 305c8b927564..189dd39468b0 100644 --- 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 @@ -20,6 +20,11 @@ Function Invoke-ListGroupSenderAuthentication { Identity = $groupid } + Write-Host = "This is the group id $groupid" + Write-Host = "This is the tenant filter $TenantFilter" + $GroupType = Invoke-ListGroups -tenantFilter $TenantFilter -GroupID $groupid + Write-Host = "This is the group type $($GroupType.calculatedGroupType)" + try { $Request = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-DistributionGroup' -cmdParams $params -UseSystemMailbox $true $StatusCode = [HttpStatusCode]::OK @@ -32,6 +37,6 @@ Function Invoke-ListGroupSenderAuthentication { # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $($Request.RequireSenderAuthenticationEnabled) + Body = @{ enabled = $Request.RequireSenderAuthenticationEnabled } }) } \ No newline at end of file From 46671ff862d431a6c51cae7b699d1f08316b1d8e Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Thu, 27 Jun 2024 12:13:02 +0100 Subject: [PATCH 008/104] Moved to Set-CIPPForwarding Function Moved ExecEmailForwarding to use Set-CIPPForwarding Removed the need for ExecDisableEmailForward --- .../Invoke-ExecDisableEmailForward.ps1 | 34 ------------------- .../Invoke-ExecEmailForward.ps1 | 7 ++-- .../Public/Invoke-CIPPOffboardingJob.ps1 | 6 +++- .../CIPPCore/Public/Set-CIPPForwarding.ps1 | 18 ++++++---- 4 files changed, 21 insertions(+), 44 deletions(-) delete mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecDisableEmailForward.ps1 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..ad59b4fd71b6 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,10 +16,11 @@ 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" @@ -36,7 +37,7 @@ Function Invoke-ExecEmailForward { elseif ($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" @@ -54,7 +55,7 @@ Function Invoke-ExecEmailForward { elseif ($DisableForwarding -eq 'True') { try { - New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-Mailbox' -cmdParams @{Identity = $Username; ForwardingAddress = $null; ForwardingSMTPAddress = $null; DeliverToMailboxAndForward = $false } + Set-CIPPForwarding -userid $username -username $username -tenantFilter $Tenantfilter -ExecutingUser $ExecutingUser -APIName $APIName -Disable $true Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Disabled Email forwarding for $($username)" -Sev 'Info' -tenant $TenantFilter $results = "Disabled Email Forwarding for $($username)" } catch { diff --git a/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 index e051a2a0144b..9087bd30deff 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 @@ -50,7 +50,11 @@ function Invoke-CIPPOffboardingJob { 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 + 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 $Options.keepCopy -ExecutingUser $ExecutingUser -APIName $APIName + } } { $_.'RemoveLicenses' -eq 'true' } { Remove-CIPPLicense -userid $userid -username $Username -tenantFilter $Tenantfilter -ExecutingUser $ExecutingUser -APIName $APIName diff --git a/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 b/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 index 130ff65ff430..ba995399b49e 100644 --- a/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 @@ -47,15 +47,21 @@ function Set-CIPPForwarding { [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 From 5cd9612a9c8369a37a647312f524c5b0a2f45371 Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Thu, 27 Jun 2024 12:39:06 +0100 Subject: [PATCH 009/104] Edits Edits --- .../Invoke-ExecEmailForward.ps1 | 5 --- openapi.json | 40 ------------------- 2 files changed, 45 deletions(-) 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 ad59b4fd71b6..8b147710e77d 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 @@ -22,10 +22,8 @@ Function Invoke-ExecEmailForward { try { 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 $results = "Forwarding all email for $($username) to $($ForwardingAddress) and keeping a copy" } } catch { @@ -39,10 +37,8 @@ Function Invoke-ExecEmailForward { try { 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 $results = "Forwarding all email for $($username) to $($ForwardingSMTPAddress) and keeping a copy" } } catch { @@ -56,7 +52,6 @@ Function Invoke-ExecEmailForward { elseif ($DisableForwarding -eq 'True') { try { Set-CIPPForwarding -userid $username -username $username -tenantFilter $Tenantfilter -ExecutingUser $ExecutingUser -APIName $APIName -Disable $true - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Disabled Email forwarding for $($username)" -Sev 'Info' -tenant $TenantFilter $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/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", From 00f713d6acde571a6d60f90b77af0c917aa6d2f8 Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Thu, 27 Jun 2024 23:10:19 +0100 Subject: [PATCH 010/104] Update Invoke-ExecMailboxRestore.ps1 Added $Request.Body.input for new Deleted Mailbox Page --- .../HTTP Functions/Email-Exchange/Invoke-ExecMailboxRestore.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 2d3414c36164e1600dce9f1fd27bb4835244497a Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Fri, 28 Jun 2024 08:45:37 +0100 Subject: [PATCH 011/104] Update Invoke-ListMailboxes.ps1 Added WhenSoftDeleted Date for the Deleted Mailbox Page --- Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 index 7a125550426a..9e4fa4731941 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 @@ -20,7 +20,7 @@ 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' $ExoRequest = @{ tenantid = $TenantFilter cmdlet = 'Get-Mailbox' @@ -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' } }, From 0e959bf97de5e3d8274c46062803bf6430df07e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 28 Jun 2024 11:05:58 +0200 Subject: [PATCH 012/104] New UserReportDestinationEmail standard --- ...StandardUserReportDestinationEmail.ps1.ps1 | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1.ps1 diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1.ps1 new file mode 100644 index 000000000000..670da0608743 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1.ps1 @@ -0,0 +1,54 @@ +function Invoke-CIPPStandardUserReportDestinationEmail { + <# + .FUNCTIONALITY + Internal + #> + 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 + } +} From 77f933c2339f14e39990f509fa7e6b6c9a207336 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 28 Jun 2024 12:47:23 +0200 Subject: [PATCH 013/104] cleanup --- .../Email-Exchange/Invoke-ExecEmailForward.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 8b147710e77d..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,14 +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 } + [bool]$KeepCopy = if ($request.body.keepCopy -eq 'true') { $true } else { $false } if ($ForwardingAddress) { try { Set-CIPPForwarding -userid $username -tenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' -Forward $ForwardingAddress -keepCopy $KeepCopy if (-not $request.body.KeepCopy) { $results = "Forwarding all email for $($username) to $($ForwardingAddress) and not keeping a copy" - } elseif ($request.body.KeepCopy) { + } else { $results = "Forwarding all email for $($username) to $($ForwardingAddress) and keeping a copy" } } catch { @@ -33,12 +33,12 @@ Function Invoke-ExecEmailForward { } } - elseif ($ForwardingSMTPAddress) { + if ($ForwardingSMTPAddress) { try { Set-CIPPForwarding -userid $username -tenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' -forwardingSMTPAddress $ForwardingSMTPAddress -keepCopy $KeepCopy if (-not $request.body.KeepCopy) { $results = "Forwarding all email for $($username) to $($ForwardingSMTPAddress) and not keeping a copy" - } elseif ($request.body.KeepCopy) { + } else { $results = "Forwarding all email for $($username) to $($ForwardingSMTPAddress) and keeping a copy" } } catch { @@ -49,7 +49,7 @@ Function Invoke-ExecEmailForward { } - elseif ($DisableForwarding -eq 'True') { + if ($DisableForwarding -eq 'True') { try { Set-CIPPForwarding -userid $username -username $username -tenantFilter $Tenantfilter -ExecutingUser $ExecutingUser -APIName $APIName -Disable $true $results = "Disabled Email Forwarding for $($username)" From 16b4bff3d100ff202bc61f8dc89d917216c95ad3 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 28 Jun 2024 09:45:05 -0400 Subject: [PATCH 014/104] Limit query to contentTypes that are in rules --- .../Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) 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" From 05456010532e848ad3c38f81bbcc97a02aa7574e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 30 Jun 2024 19:56:36 -0400 Subject: [PATCH 015/104] Add OneDrive Pre-Provision --- .../Public/PermissionsTranslator.json | 9 ++++ .../Public/Request-CIPPSPOPersonalSite.ps1 | 51 +++++++++++++++++++ Modules/CIPPCore/Public/SAMManifest.json | 3 +- 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 Modules/CIPPCore/Public/Request-CIPPSPOPersonalSite.ps1 diff --git a/Modules/CIPPCore/Public/PermissionsTranslator.json b/Modules/CIPPCore/Public/PermissionsTranslator.json index a0ba05d3dc02..aa7947e9374d 100644 --- a/Modules/CIPPCore/Public/PermissionsTranslator.json +++ b/Modules/CIPPCore/Public/PermissionsTranslator.json @@ -5295,6 +5295,15 @@ "userConsentDisplayName": "Allows the app to have full control of all site collections on your behalf.", "value": "AllSites.FullControl" }, + { + "description": "Required for Request-SPOPeronalSite", + "displayName": "Manage sharepoint profiles", + "id": "ec4fc4c8-872e-442b-a2a2-d095575807b3", + "Origin": "Delegated (Office 365 SharePoint Online)", + "userConsentDescription": "", + "userConsentDisplayName": "Manage sharepoint profiles", + "value": "AllProfiles.Manage" + }, { "description": "Allows to read the LAPs passwords.", "displayName": "Manage LAPs passwords", diff --git a/Modules/CIPPCore/Public/Request-CIPPSPOPersonalSite.ps1 b/Modules/CIPPCore/Public/Request-CIPPSPOPersonalSite.ps1 new file mode 100644 index 000000000000..44f1764b674c --- /dev/null +++ b/Modules/CIPPCore/Public/Request-CIPPSPOPersonalSite.ps1 @@ -0,0 +1,51 @@ +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 { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not request personal site for $($Users -join ', ')" -Sev 'Error' -tenant $TenantFilter + return "Could not request personal site for $($Users -join ', '). Error: $($_.Exception.Message)" + } +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/SAMManifest.json b/Modules/CIPPCore/Public/SAMManifest.json index 6b1f6429af88..d545a87d25a5 100644 --- a/Modules/CIPPCore/Public/SAMManifest.json +++ b/Modules/CIPPCore/Public/SAMManifest.json @@ -165,7 +165,8 @@ { "resourceAppId": "00000003-0000-0ff1-ce00-000000000000", "resourceAccess": [ - { "id": "56680e0d-d2a3-4ae1-80d8-3c4f2100e3d0", "type": "Scope" } + { "id": "56680e0d-d2a3-4ae1-80d8-3c4f2100e3d0", "type": "Scope" }, + { "id": "ec4fc4c8-872e-442b-a2a2-d095575807b3", "type": "Scope" } ] }, { From fabe2dc3ca11271d3a0013023f90ae89aa017ba9 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 1 Jul 2024 12:01:40 -0400 Subject: [PATCH 016/104] Fix bug with TAP --- Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 index ce9a3c95ef62..ef315d877530 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 @@ -13,7 +13,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 } From 9ca69a759fa0c8dc1cd999756879d00d37a64ec9 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 1 Jul 2024 16:12:09 -0400 Subject: [PATCH 017/104] Add includeerrors for Ninja orgs tenant map --- Modules/CippExtensions/NinjaOne/Get-NinjaOneOrgMapping.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CippExtensions/NinjaOne/Get-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/NinjaOne/Get-NinjaOneOrgMapping.ps1 index a0274cb0d16f..d2d914c89589 100644 --- a/Modules/CippExtensions/NinjaOne/Get-NinjaOneOrgMapping.ps1 +++ b/Modules/CippExtensions/NinjaOne/Get-NinjaOneOrgMapping.ps1 @@ -6,7 +6,7 @@ function Get-NinjaOneOrgMapping { try { #Get available mappings $Mappings = [pscustomobject]@{} - $Tenants = Get-Tenants + $Tenants = Get-Tenants -IncludeErrors $Filter = "PartitionKey eq 'NinjaOrgsMapping'" Get-AzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object { From 2092252507cbd866a9ff386dc0d5ca599531a475 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 1 Jul 2024 16:13:30 -0400 Subject: [PATCH 018/104] Add HuduAPI module --- Modules/HuduAPI/2.4.9/HuduAPI.psd1 | 154 + Modules/HuduAPI/2.4.9/HuduAPI.psm1 | 4201 +++++++++++++++++++++ Modules/HuduAPI/2.4.9/PSGetModuleInfo.xml | 248 ++ 3 files changed, 4603 insertions(+) create mode 100644 Modules/HuduAPI/2.4.9/HuduAPI.psd1 create mode 100644 Modules/HuduAPI/2.4.9/HuduAPI.psm1 create mode 100644 Modules/HuduAPI/2.4.9/PSGetModuleInfo.xml 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 +
+
+
From 2192b11cc46557e647474f4f46ddfc61baca3e98 Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Tue, 2 Jul 2024 17:26:32 +0100 Subject: [PATCH 019/104] Multiple Changes Added Soft Deleted Mailboxes Scripted Alert Added additional select-object to ListMailboxes to be used with the Soft Deleted Mailboxes Alert Added SetAuthMethod function to be used on the new Auth Methods page. --- .../Get-CIPPAlertSoftDeletedMailboxes.ps1 | 28 ++++++++++++++++ .../Administration/Invoke-SetAuthMethod.ps1 | 32 +++++++++++++++++++ .../Entrypoints/Invoke-ListMailboxes.ps1 | 2 +- 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 Modules/CIPPCore/Public/Alerts/Get-CIPPAlertSoftDeletedMailboxes.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-SetAuthMethod.ps1 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/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/Invoke-ListMailboxes.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 index 9e4fa4731941..8dc145b8b53c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 @@ -20,7 +20,7 @@ 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,WhenSoftDeleted' + $Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses,WhenSoftDeleted,IsInactiveMailbox' $ExoRequest = @{ tenantid = $TenantFilter cmdlet = 'Get-Mailbox' From 1d1dd050948c2f8fbc2f796f491a337058465133 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 2 Jul 2024 16:29:12 -0400 Subject: [PATCH 020/104] Hudu extension Organize extension functions --- .../Settings/Invoke-ExecExtensionMapping.ps1 | 9 ++-- .../Settings/Invoke-ExecExtensionTest.ps1 | 6 +++ .../Settings/Invoke-ExecExtensionsConfig.ps1 | 2 +- Modules/CippExtensions/CippExtensions.psm1 | 7 ++- .../Gradient}/Get-GradientToken.ps1 | 0 .../Gradient}/New-GradientAlert.ps1 | 0 .../Gradient}/New-GradientServiceSyncRun.ps1 | 0 .../Halo}/Get-HaloMapping.ps1 | 0 .../Halo}/Get-HaloToken.ps1 | 0 .../Halo}/New-HaloPSATicket.ps1 | 0 .../Halo}/Set-HaloMapping.ps1 | 0 .../Public/Hudu/Connect-HuduAPI.ps1 | 16 +++++++ .../Public/Hudu/Get-HuduMapping.ps1 | 45 +++++++++++++++++++ .../Public/Hudu/Set-HuduMapping.ps1 | 26 +++++++++++ .../NinjaOne/Get-NinjaOneFieldMapping.ps1 | 0 .../NinjaOne/Get-NinjaOneOrgMapping.ps1 | 0 .../NinjaOne/Get-NinjaOneToken.ps1 | 1 - .../NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 | 0 .../Invoke-NinjaOneDocumentTemplate.ps1 | 0 .../Invoke-NinjaOneExtensionScheduler.ps1 | 0 .../NinjaOne/Invoke-NinjaOneOrgMapping.ps1 | 0 .../Invoke-NinjaOneOrgMappingTenant.ps1 | 0 .../NinjaOne/Invoke-NinjaOneSync.ps1 | 0 .../NinjaOne/Invoke-NinjaOneTenantSync.ps1 | 0 .../{ => Public}/NinjaOne/NinjaOneHelper.ps1 | 0 .../NinjaOne/Push-NinjaOneQueue.ps1 | 0 .../NinjaOne/Set-NinjaOneFieldMapping.ps1 | 0 .../NinjaOne/Set-NinjaOneOrgMapping.ps1 | 0 .../Public/{ => PwPush}/New-PwPushLink.ps1 | 0 .../PwPush}/Set-PwPushConfig.ps1 | 0 30 files changed, 103 insertions(+), 9 deletions(-) rename Modules/CippExtensions/{Private => Public/Gradient}/Get-GradientToken.ps1 (100%) rename Modules/CippExtensions/{Private => Public/Gradient}/New-GradientAlert.ps1 (100%) rename Modules/CippExtensions/{Private => Public/Gradient}/New-GradientServiceSyncRun.ps1 (100%) rename Modules/CippExtensions/{Private => Public/Halo}/Get-HaloMapping.ps1 (100%) rename Modules/CippExtensions/{Private => Public/Halo}/Get-HaloToken.ps1 (100%) rename Modules/CippExtensions/{Private => Public/Halo}/New-HaloPSATicket.ps1 (100%) rename Modules/CippExtensions/{Private => Public/Halo}/Set-HaloMapping.ps1 (100%) create mode 100644 Modules/CippExtensions/Public/Hudu/Connect-HuduAPI.ps1 create mode 100644 Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1 create mode 100644 Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 rename Modules/CippExtensions/{ => Public}/NinjaOne/Get-NinjaOneFieldMapping.ps1 (100%) rename Modules/CippExtensions/{ => Public}/NinjaOne/Get-NinjaOneOrgMapping.ps1 (100%) rename Modules/CippExtensions/{ => Public}/NinjaOne/Get-NinjaOneToken.ps1 (99%) rename Modules/CippExtensions/{ => Public}/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 (100%) rename Modules/CippExtensions/{ => Public}/NinjaOne/Invoke-NinjaOneDocumentTemplate.ps1 (100%) rename Modules/CippExtensions/{ => Public}/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 (100%) rename Modules/CippExtensions/{ => Public}/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 (100%) rename Modules/CippExtensions/{ => Public}/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 (100%) rename Modules/CippExtensions/{ => Public}/NinjaOne/Invoke-NinjaOneSync.ps1 (100%) rename Modules/CippExtensions/{ => Public}/NinjaOne/Invoke-NinjaOneTenantSync.ps1 (100%) rename Modules/CippExtensions/{ => Public}/NinjaOne/NinjaOneHelper.ps1 (100%) rename Modules/CippExtensions/{ => Public}/NinjaOne/Push-NinjaOneQueue.ps1 (100%) rename Modules/CippExtensions/{ => Public}/NinjaOne/Set-NinjaOneFieldMapping.ps1 (100%) rename Modules/CippExtensions/{ => Public}/NinjaOne/Set-NinjaOneOrgMapping.ps1 (100%) rename Modules/CippExtensions/Public/{ => PwPush}/New-PwPushLink.ps1 (100%) rename Modules/CippExtensions/{Private => Public/PwPush}/Set-PwPushConfig.ps1 (100%) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 index 77b8e277c780..595bf587ef19 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 @@ -23,14 +23,14 @@ Function Invoke-ExecExtensionMapping { 'Halo' { $body = Get-HaloMapping -CIPPMapping $Table } - 'NinjaOrgs' { $Body = Get-NinjaOneOrgMapping -CIPPMapping $Table } - 'NinjaFields' { $Body = Get-NinjaOneFieldMapping -CIPPMapping $Table - + } + 'Hudu' { + $Body = Get-HuduMapping -CIPPMapping $Table } } } @@ -47,6 +47,9 @@ Function Invoke-ExecExtensionMapping { 'NinjaFields' { $Body = Set-NinjaOneFieldMapping -CIPPMapping $Table -APIName $APIName -Request $Request -TriggerMetadata $TriggerMetadata } + 'Hudu' { + $Body = Set-HuduMapping -CIPPMapping $Table -APIName $APIName -Request $Request + } } } } catch { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 index 78e82e78121a..74b188742a97 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 @@ -46,6 +46,12 @@ Function Invoke-ExecExtensionTest { $Results = [pscustomobject]@{'Results' = 'PWPush is not enabled' } } } + 'Hudu' { + Connect-HuduAPI -configuration $Configuration.Hudu + $Version = Get-HuduAppInfo + Write-Host ($Version | ConvertTo-Json) + $Results = [pscustomobject]@{'Results' = ('Succesfully Connected to Hudu, version: {0}' -f $Version.version) } + } } } 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/Settings/Invoke-ExecExtensionsConfig.ps1 index fd3b8764959d..f40536cebcb4 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 @@ -22,7 +22,7 @@ Function Invoke-ExecExtensionsConfig { $AddedText = $APIConfig.Results } - # Check if NinjaOne URL is set correctly and the intance has at least version 5.6 + # 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 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/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 100% rename from Modules/CippExtensions/Private/Get-HaloMapping.ps1 rename to Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1 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 100% rename from Modules/CippExtensions/Private/Set-HaloMapping.ps1 rename to Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1 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-HuduMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1 new file mode 100644 index 000000000000..cff35483ca7a --- /dev/null +++ b/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1 @@ -0,0 +1,45 @@ +function Get-HuduMapping { + [CmdletBinding()] + param ( + $CIPPMapping + ) + #Get available mappings + $Mappings = [pscustomobject]@{} + + $Filter = "PartitionKey eq 'HuduMapping'" + Get-CIPPAzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object { + $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.HuduCompany)"; value = "$($_.HuduCompanyId)" } + } + $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) + HuduCompanies = @($HuduCompanies) + Mappings = $Mappings + } + + return $MappingObj + +} \ No newline at end of file diff --git a/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 new file mode 100644 index 000000000000..a7ad9f8172b3 --- /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 = 'Mapping' + RowKey = "$($mapping.name)" + 'HuduCompanyId' = "$($mapping.value.value)" + 'HuduCompany' = "$($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/NinjaOne/Get-NinjaOneFieldMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 similarity index 100% rename from Modules/CippExtensions/NinjaOne/Get-NinjaOneFieldMapping.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 diff --git a/Modules/CippExtensions/NinjaOne/Get-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneOrgMapping.ps1 similarity index 100% rename from Modules/CippExtensions/NinjaOne/Get-NinjaOneOrgMapping.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneOrgMapping.ps1 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 100% rename from Modules/CippExtensions/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 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 100% rename from Modules/CippExtensions/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 diff --git a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 similarity index 100% rename from Modules/CippExtensions/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 diff --git a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 similarity index 100% rename from Modules/CippExtensions/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 diff --git a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneSync.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneSync.ps1 similarity index 100% rename from Modules/CippExtensions/NinjaOne/Invoke-NinjaOneSync.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneSync.ps1 diff --git a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneTenantSync.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 similarity index 100% rename from Modules/CippExtensions/NinjaOne/Invoke-NinjaOneTenantSync.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 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 100% rename from Modules/CippExtensions/NinjaOne/Set-NinjaOneFieldMapping.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneFieldMapping.ps1 diff --git a/Modules/CippExtensions/NinjaOne/Set-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1 similarity index 100% rename from Modules/CippExtensions/NinjaOne/Set-NinjaOneOrgMapping.ps1 rename to Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1 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 From ebc91fc5920f0401862500533b4bfea075fdbf05 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 2 Jul 2024 16:29:40 -0400 Subject: [PATCH 021/104] tweak ListMailboxes --- Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 index 9e4fa4731941..ef32158d8d78 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 @@ -24,8 +24,8 @@ Function Invoke-ListMailboxes { $ExoRequest = @{ tenantid = $TenantFilter cmdlet = 'Get-Mailbox' - cmdParams = @{resultsize = 'unlimited' } - Select = $select + cmdParams = @{} + Select = $Select } $AllowedParameters = @( From f229fd42ae7d3558839c1abb49cb5a3a8c6d2b9a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 2 Jul 2024 17:05:02 -0400 Subject: [PATCH 022/104] Extension fix - CIPP-API Remove ResetPassword property from saved settings to prevent rotating secret every save --- .../Settings/Invoke-ExecExtensionsConfig.ps1 | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 index f40536cebcb4..60eed5ba5165 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 @@ -11,21 +11,21 @@ Function Invoke-ExecExtensionsConfig { 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.' $results = try { - if ($Request.body.CIPPAPI.Enabled) { - $APIConfig = New-CIPPAPIConfig -ExecutingUser $request.headers.'x-ms-client-principal' -resetpassword $request.body.CIPPAPI.ResetPassword + if ($Request.Body.CIPPAPI.Enabled) { + $APIConfig = New-CIPPAPIConfig -ExecutingUser $Request.Headers.'x-ms-client-principal' -resetpassword $Request.Body.CIPPAPI.ResetPassword $AddedText = $APIConfig.Results } # Check if NinjaOne URL is set correctly and the instance has at least version 5.6 - if ($request.body.NinjaOne) { + if ($Request.Body.NinjaOne) { try { - [version]$Version = (Invoke-WebRequest -Method GET -Uri "https://$(($request.body.NinjaOne.Instance -replace '/ws','') -replace 'https://','')/app-version.txt" -ea stop).content + [version]$Version = (Invoke-WebRequest -Method GET -Uri "https://$(($Request.Body.NinjaOne.Instance -replace '/ws','') -replace 'https://','')/app-version.txt" -ea stop).content } catch { throw "Failed to connect to NinjaOne check your Instance is set correctly eg 'app.ninjarmmm.com'" } @@ -35,30 +35,31 @@ Function Invoke-ExecExtensionsConfig { } $Table = Get-CIPPTable -TableName Extensionsconfig - foreach ($APIKey in ([pscustomobject]$request.body).psobject.properties.name) { + 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 '') { + 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.' } else { Write-Host 'writing API Key to keyvault, and clearing.' Write-Host "$ENV:WEBSITE_DEPLOYMENT_ID" - if ($request.body.$APIKey.APIKey) { + 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 -String $Request.Body.$APIKey.APIKey -AsPlainText -Force) } } - $request.body.$APIKey.APIKey = 'SentToKeyVault' + $Request.Body.$APIKey.APIKey = 'SentToKeyVault' } + $Request.Body.$APIKey = $Request.Body.$APIKey | Select-Object * -ExcludeProperty ResetPassword } - $body = $request.body | Select-Object * -ExcludeProperty APIKey, Enabled | ConvertTo-Json -Depth 10 -Compress + $body = $Request.Body | Select-Object * -ExcludeProperty APIKey, Enabled | ConvertTo-Json -Depth 10 -Compress $Config = @{ 'PartitionKey' = 'CippExtensions' 'RowKey' = 'Config' From 414e7add7b4c235aca32597292fc14e5296e48d4 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 2 Jul 2024 17:09:38 -0400 Subject: [PATCH 023/104] Update Invoke-ExecExtensionsConfig.ps1 --- .../CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 index 60eed5ba5165..0dbd6f5bd95d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 @@ -7,6 +7,7 @@ Function Invoke-ExecExtensionsConfig { .ROLE CIPP.Extension.ReadWrite #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Scope = 'Function')] [CmdletBinding()] param($Request, $TriggerMetadata) @@ -52,7 +53,7 @@ Function Invoke-ExecExtensionsConfig { } 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' From dc588a28ffcf7382c174e10b4d3a43f89b7edce0 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 2 Jul 2024 17:11:56 -0400 Subject: [PATCH 024/104] Update Invoke-ExecExtensionsConfig.ps1 --- .../CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 index 0dbd6f5bd95d..ebbf5ca9dba4 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 @@ -16,7 +16,7 @@ Function Invoke-ExecExtensionsConfig { #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 @@ -37,12 +37,12 @@ Function Invoke-ExecExtensionsConfig { $Table = Get-CIPPTable -TableName Extensionsconfig foreach ($APIKey in ([pscustomobject]$Request.Body).psobject.properties.name) { - Write-Host "Working on $apikey" + Write-Information "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.' + 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" + 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' From f1aa778b59e82d0772adb7e6bb5a686c346f4d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 3 Jul 2024 00:13:13 +0200 Subject: [PATCH 025/104] Tool script to add standards documentation based on the JSON in the frontend --- Tools/Update-StandardsComments.ps1 | 91 ++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 Tools/Update-StandardsComments.ps1 diff --git a/Tools/Update-StandardsComments.ps1 b/Tools/Update-StandardsComments.ps1 new file mode 100644 index 000000000000..a1b29cf5c39c --- /dev/null +++ b/Tools/Update-StandardsComments.ps1 @@ -0,0 +1,91 @@ +<# +.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" + $Content = Get-Content -Path $StandardsFilePath -Raw + + # 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("<#`n") + $NewComment.Add(" .FUNCTIONALITY`n") + $NewComment.Add(" Internal`n") + $NewComment.Add(" .APINAME`n") + $NewComment.Add(" $($Standard.name -replace 'standards.', '')`n") + + # Loop through the properties of the standard and add them to the comment block + foreach ($Property in $Standard.PSObject.Properties) { + if ($Property.Name -eq 'name') { continue } + if ($Property.Name -eq 'impactColour') { continue } + + # If the property is docsDescription and is empty, use the helpText instead + if ($Property.Name -eq 'docsDescription' -and ([string]::IsNullOrWhiteSpace($Property.Value))) { + $NewComment.Add(" .$('docsDescription'.ToUpper())`n") + $NewComment.Add(" $($Standard.helpText.ToString())`n") + continue + } + + $NewComment.Add(" .$($Property.Name.ToUpper())`n") + # Flatten objects to JSON + if ($Property.Value -is [System.Object[]]) { + foreach ($Value in $Property.Value) { + $NewComment.Add(" $(ConvertTo-Json -InputObject $Value -Depth 5 -Compress)`n") + } + continue + } + $NewComment.Add(" $($Property.Value.ToString())`n") + } + + # Add DOCSDESCRIPTION if it doesn't exist + if ($NewComment -notcontains '.DOCSDESCRIPTION') { + $NewComment.Add(" .DOCSDESCRIPTION`n") + $NewComment.Add(" $($Standard.helpText.ToString())`n") + } + + $NewComment.Add(" #>`n") + + # 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 + } + } else { + Write-Host "No comment block found in $StandardsFilePath" -ForegroundColor Yellow + } +} From 75aded88b3271439d81fa9acbc23ef44fda0a140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 3 Jul 2024 00:17:55 +0200 Subject: [PATCH 026/104] Whoops, copy paste mistake --- ....ps1.ps1 => Invoke-CIPPStandardUserReportDestinationEmail.ps1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Modules/CIPPCore/Public/Standards/{Invoke-CIPPStandardUserReportDestinationEmail.ps1.ps1 => Invoke-CIPPStandardUserReportDestinationEmail.ps1} (100%) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1 similarity index 100% rename from Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1.ps1 rename to Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1 From 5cdb383d08a51d1316caf2d0efd748ed55baf53e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 3 Jul 2024 00:20:43 +0200 Subject: [PATCH 027/104] Add check if file is not found --- Tools/Update-StandardsComments.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tools/Update-StandardsComments.ps1 b/Tools/Update-StandardsComments.ps1 index a1b29cf5c39c..21e5f115dd10 100644 --- a/Tools/Update-StandardsComments.ps1 +++ b/Tools/Update-StandardsComments.ps1 @@ -33,6 +33,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 # Regex to match the existing comment block From 7d854a3d31966deeaa351535ef947eb82ec05299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 3 Jul 2024 00:26:14 +0200 Subject: [PATCH 028/104] Add how to update the comment blocks --- Tools/Update-StandardsComments.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tools/Update-StandardsComments.ps1 b/Tools/Update-StandardsComments.ps1 index 21e5f115dd10..3c47284284c0 100644 --- a/Tools/Update-StandardsComments.ps1 +++ b/Tools/Update-StandardsComments.ps1 @@ -79,7 +79,9 @@ foreach ($Standard in $StandardsInfo) { $NewComment.Add(" .DOCSDESCRIPTION`n") $NewComment.Add(" $($Standard.helpText.ToString())`n") } - + # Add header about how to update the comment block with this script + $NewComment.Add(" .UPDATECOMMENTBLOCK`n") + $NewComment.Add(" Run the Tools\Update-StandardsComments.ps1 script to update this comment block`n") $NewComment.Add(" #>`n") # Write the new comment block to the file From 4d6d080ba5e942bd06e9bb826cc25193ad935e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 3 Jul 2024 00:26:27 +0200 Subject: [PATCH 029/104] Update comment blocks and add standards documentation based on JSON --- ...nvoke-CIPPStandardActivityBasedTimeout.ps1 | 32 +++++++++++ .../Standards/Invoke-CIPPStandardAddDKIM.ps1 | 30 ++++++++++ .../Invoke-CIPPStandardAnonReportDisable.ps1 | 30 ++++++++++ .../Invoke-CIPPStandardAntiPhishPolicy.ps1 | 57 +++++++++++++++++-- .../Invoke-CIPPStandardAtpPolicyForO365.ps1 | 39 +++++++++++-- .../Standards/Invoke-CIPPStandardAuditLog.ps1 | 31 ++++++++++ .../Invoke-CIPPStandardAutoExpandArchive.ps1 | 30 ++++++++++ .../Standards/Invoke-CIPPStandardBookings.ps1 | 31 ++++++++++ .../Standards/Invoke-CIPPStandardBranding.ps1 | 34 +++++++++++ .../Invoke-CIPPStandardCloudMessageRecall.ps1 | 31 ++++++++++ .../Invoke-CIPPStandardDelegateSentItems.ps1 | 30 ++++++++++ ...voke-CIPPStandardDeletedUserRentention.ps1 | 30 ++++++++++ ...PStandardDisableAddShortcutsToOneDrive.ps1 | 30 ++++++++++ ...ndardDisableAdditionalStorageProviders.ps1 | 33 +++++++++++ .../Invoke-CIPPStandardDisableAppCreation.ps1 | 32 +++++++++++ ...nvoke-CIPPStandardDisableBasicAuthSMTP.ps1 | 30 ++++++++++ .../Invoke-CIPPStandardDisableEmail.ps1 | 28 +++++++++ ...StandardDisableExternalCalendarSharing.ps1 | 33 +++++++++++ ...voke-CIPPStandardDisableGuestDirectory.ps1 | 30 ++++++++++ .../Invoke-CIPPStandardDisableGuests.ps1 | 28 +++++++++ ...voke-CIPPStandardDisableM365GroupUsers.ps1 | 30 ++++++++++ ...nvoke-CIPPStandardDisableOutlookAddins.ps1 | 33 +++++++++++ .../Invoke-CIPPStandardDisableReshare.ps1 | 32 +++++++++++ .../Invoke-CIPPStandardDisableSMS.ps1 | 30 ++++++++++ ...-CIPPStandardDisableSecurityGroupUsers.ps1 | 28 +++++++++ ...CIPPStandardDisableSelfServiceLicenses.ps1 | 28 +++++++++ ...IPPStandardDisableSharePointLegacyAuth.ps1 | 32 +++++++++++ ...nvoke-CIPPStandardDisableSharedMailbox.ps1 | 32 +++++++++++ .../Invoke-CIPPStandardDisableTNEF.ps1 | 30 ++++++++++ ...voke-CIPPStandardDisableTenantCreation.ps1 | 32 +++++++++++ ...voke-CIPPStandardDisableUserSiteCreate.ps1 | 30 ++++++++++ .../Invoke-CIPPStandardDisableViva.ps1 | 30 ++++++++++ .../Invoke-CIPPStandardDisableVoice.ps1 | 27 +++++++++ ...oke-CIPPStandardDisablex509Certificate.ps1 | 30 ++++++++++ ...e-CIPPStandardEnableAppConsentRequests.ps1 | 33 +++++++++++ ...voke-CIPPStandardEnableCustomerLockbox.ps1 | 33 +++++++++++ .../Invoke-CIPPStandardEnableFIDO2.ps1 | 30 ++++++++++ ...Invoke-CIPPStandardEnableHardwareOAuth.ps1 | 30 ++++++++++ ...nvoke-CIPPStandardEnableLitigationHold.ps1 | 36 ++++++++++-- .../Invoke-CIPPStandardEnableMailTips.ps1 | 32 +++++++++++ ...voke-CIPPStandardEnableMailboxAuditing.ps1 | 33 +++++++++++ ...voke-CIPPStandardEnableOnlineArchiving.ps1 | 28 +++++++++ .../Invoke-CIPPStandardEnablePronouns.ps1 | 28 +++++++++ .../Invoke-CIPPStandardExcludedfileExt.ps1 | 29 ++++++++++ .../Invoke-CIPPStandardExternalMFATrusted.ps1 | 29 ++++++++++ .../Invoke-CIPPStandardFocusedInbox.ps1 | 31 ++++++++++ ...PStandardGlobalQuarantineNotifications.ps1 | 31 ++++++++++ .../Invoke-CIPPStandardLegacyMFACleanup.ps1 | 28 +++++++++ .../Invoke-CIPPStandardMailContacts.ps1 | 36 ++++++++++++ ...Invoke-CIPPStandardMalwareFilterPolicy.ps1 | 47 +++++++++++++-- .../Invoke-CIPPStandardMessageExpiration.ps1 | 30 ++++++++++ .../Standards/Invoke-CIPPStandardNudgeMFA.ps1 | 32 +++++++++++ .../Invoke-CIPPStandardOauthConsent.ps1 | 33 +++++++++++ .../Invoke-CIPPStandardOauthConsentLowSec.ps1 | 29 ++++++++++ .../Invoke-CIPPStandardOutBoundSpamAlert.ps1 | 33 +++++++++++ ...CIPPStandardPWcompanionAppAllowedState.ps1 | 31 ++++++++++ ...rdPWdisplayAppInformationRequiredState.ps1 | 32 +++++++++++ ...oke-CIPPStandardPasswordExpireDisabled.ps1 | 33 +++++++++++ .../Invoke-CIPPStandardPerUserMFA.ps1 | 28 +++++++++ .../Invoke-CIPPStandardPhishProtection.ps1 | 31 ++++++++++ .../Invoke-CIPPStandardRotateDKIM.ps1 | 30 ++++++++++ ...nvoke-CIPPStandardSafeAttachmentPolicy.ps1 | 45 +++++++++++++-- .../Invoke-CIPPStandardSafeLinksPolicy.ps1 | 43 ++++++++++++-- .../Invoke-CIPPStandardSafeSendersDisable.ps1 | 30 ++++++++++ .../Invoke-CIPPStandardSecurityDefaults.ps1 | 30 ++++++++++ .../Invoke-CIPPStandardSendFromAlias.ps1 | 30 ++++++++++ ...oke-CIPPStandardSendReceiveLimitTenant.ps1 | 30 ++++++++++ .../Invoke-CIPPStandardShortenMeetings.ps1 | 31 ++++++++++ .../Invoke-CIPPStandardSpoofWarn.ps1 | 33 +++++++++++ .../Standards/Invoke-CIPPStandardTAP.ps1 | 31 ++++++++++ ...oke-CIPPStandardTeamsMeetingsByDefault.ps1 | 29 ++++++++++ ...voke-CIPPStandardTenantDefaultTimezone.ps1 | 29 ++++++++++ .../Invoke-CIPPStandardUndoOauth.ps1 | 28 +++++++++ ...CIPPStandardUserReportDestinationEmail.ps1 | 27 ++++++++- .../Invoke-CIPPStandardUserSubmissions.ps1 | 31 ++++++++++ .../Invoke-CIPPStandardallowOAuthTokens.ps1 | 30 ++++++++++ .../Invoke-CIPPStandardallowOTPTokens.ps1 | 30 ++++++++++ .../Invoke-CIPPStandardcalDefault.ps1 | 33 +++++++++++ .../Invoke-CIPPStandarddisableMacSync.ps1 | 28 +++++++++ ...voke-CIPPStandardintuneBrandingProfile.ps1 | 46 +++++++++++++-- .../Invoke-CIPPStandardintuneDeviceReg.ps1 | 29 ++++++++++ ...CIPPStandardintuneDeviceRetirementDays.ps1 | 29 ++++++++++ .../Invoke-CIPPStandardintuneRequireMFA.ps1 | 27 +++++++++ .../Standards/Invoke-CIPPStandardlaps.ps1 | 30 ++++++++++ .../Invoke-CIPPStandardsharingCapability.ps1 | 31 ++++++++++ ...e-CIPPStandardsharingDomainRestriction.ps1 | 39 +++++++++++-- .../Invoke-CIPPStandardunmanagedSync.ps1 | 28 +++++++++ 87 files changed, 2723 insertions(+), 33 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardActivityBasedTimeout.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardActivityBasedTimeout.ps1 index ebf92d21e199..a09e427fbbf8 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardActivityBasedTimeout.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardActivityBasedTimeout.ps1 @@ -2,7 +2,35 @@ function Invoke-CIPPStandardActivityBasedTimeout { <# .FUNCTIONALITY Internal + .APINAME + ActivityBasedTimeout + .CAT + Global Standards + .TAG + "mediumimpact" + "CIS" + "spo_idle_session_timeout" + .HELPTEXT + Enables and sets Idle session timeout for Microsoft 365 to 1 hour. This policy affects most M365 web apps + .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"}]} + .LABEL + Enable Activity based Timeout + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Portal or Graph API + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Enables and sets Idle session timeout for Microsoft 365 to 1 hour. This policy affects most M365 web apps + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) # Input validation @@ -64,3 +92,7 @@ function Invoke-CIPPStandardActivityBasedTimeout { } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 index d4b8bd35166c..5cb6a387ad32 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardAddDKIM { <# .FUNCTIONALITY Internal + .APINAME + AddDKIM + .CAT + Exchange Standards + .TAG + "lowimpact" + "CIS" + .HELPTEXT + Enables DKIM for all domains that currently support it + .ADDEDCOMPONENT + .LABEL + Enables DKIM for all domains that currently support it + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + New-DkimSigningConfig and Set-DkimSigningConfig + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Enables DKIM for all domains that currently support it + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 @@ -81,3 +107,7 @@ function Invoke-CIPPStandardAddDKIM { Add-CIPPBPAField -FieldName 'DKIM' -FieldValue $DKIMState -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAnonReportDisable.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAnonReportDisable.ps1 index 411342e5ab3e..9255be3c1bff 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAnonReportDisable.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAnonReportDisable.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardAnonReportDisable { <# .FUNCTIONALITY Internal + .APINAME + AnonReportDisable + .CAT + Global Standards + .TAG + "lowimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Enable Usernames instead of pseudo anonymised names in reports + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgBetaAdminReportSetting -BodyParameter @{displayConcealedNames = $true} + .RECOMMENDEDBY + .DOCSDESCRIPTION + Shows usernames instead of pseudo anonymised names in reports. This standard is required for reporting to work correctly. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/reportSettings' -tenantid $Tenant -AsApp $true @@ -32,3 +58,7 @@ function Invoke-CIPPStandardAnonReportDisable { Add-CIPPBPAField -FieldName 'AnonReport' -FieldValue $CurrentInfo.displayConcealedNames -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 index f43203659dca..3a245863f65b 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 @@ -1,8 +1,53 @@ function Invoke-CIPPStandardAntiPhishPolicy { - <# - .FUNCTIONALITY - Internal - #> + <# + .FUNCTIONALITY + Internal + .APINAME + AntiPhishPolicy + .CAT + Defender Standards + .TAG + "lowimpact" + "CIS" + "mdo_safeattachments" + "mdo_highconfidencespamaction" + "mdo_highconfidencephishaction" + "mdo_phisspamacation" + "mdo_spam_notifications_only_for_admins" + "mdo_antiphishingpolicies" + .HELPTEXT + This creates a Anti-Phishing policy that automatically enables Mailbox Intelligence and spoofing, optional switches for Mailtips. + .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"}]} + .LABEL + Default Anti-Phishing Policy + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-AntiphishPolicy or New-AntiphishPolicy + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + This creates a Anti-Phishing policy that automatically enables Mailbox Intelligence and spoofing, optional switches for Mailtips. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + + + param($Tenant, $Settings) $PolicyName = 'Default Anti-Phishing Policy' @@ -135,3 +180,7 @@ function Invoke-CIPPStandardAntiPhishPolicy { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 index 4538eef5aed7..3d09454aaaf1 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 @@ -1,8 +1,35 @@ function Invoke-CIPPStandardAtpPolicyForO365 { - <# - .FUNCTIONALITY - Internal - #> + <# + .FUNCTIONALITY + Internal + .APINAME + AtpPolicyForO365 + .CAT + Defender Standards + .TAG + "lowimpact" + "CIS" + .HELPTEXT + This creates a Atp policy that enables Defender for Office 365 for Sharepoint, OneDrive and Microsoft Teams. + .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} + .LABEL + Default Atp Policy For O365 + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-AtpPolicyForO365 + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + This creates a Atp policy that enables Defender for Office 365 for Sharepoint, OneDrive and Microsoft Teams. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + + + param($Tenant, $Settings) $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AtpPolicyForO365' | @@ -46,3 +73,7 @@ function Invoke-CIPPStandardAtpPolicyForO365 { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 index acb6bf9834a1..17d7c440b840 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 @@ -2,7 +2,34 @@ function Invoke-CIPPStandardAuditLog { <# .FUNCTIONALITY Internal + .APINAME + AuditLog + .CAT + Global Standards + .TAG + "lowimpact" + "CIS" + "mip_search_auditlog" + .HELPTEXT + Enables the Unified Audit Log for tracking and auditing activities. Also runs Enable-OrganizationCustomization if necessary. + .ADDEDCOMPONENT + .LABEL + Enable the Unified Audit Log + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Enable-OrganizationCustomization + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Enables the Unified Audit Log for tracking and auditing activities. Also runs Enable-OrganizationCustomization if necessary. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) Write-Host ($Settings | ConvertTo-Json) $AuditLogEnabled = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AdminAuditLogConfig' -Select UnifiedAuditLogIngestionEnabled).UnifiedAuditLogIngestionEnabled @@ -48,3 +75,7 @@ function Invoke-CIPPStandardAuditLog { Add-CIPPBPAField -FieldName 'AuditLog' -FieldValue $AuditLogEnabled -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 index 53d29e442822..432923a068d1 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardAutoExpandArchive { <# .FUNCTIONALITY Internal + .APINAME + AutoExpandArchive + .CAT + Exchange Standards + .TAG + "lowimpact" + .HELPTEXT + Enables auto-expanding archives for the tenant + .DOCSDESCRIPTION + Enables auto-expanding archives for the tenant. Does not enable archives for users. + .ADDEDCOMPONENT + .LABEL + Enable Auto-expanding archives + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-OrganizationConfig -AutoExpandingArchive + .RECOMMENDEDBY + .DOCSDESCRIPTION + Enables auto-expanding archives for the tenant + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentState = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').AutoExpandingArchiveEnabled @@ -36,3 +62,7 @@ function Invoke-CIPPStandardAutoExpandArchive { Add-CIPPBPAField -FieldName 'AutoExpandingArchive' -FieldValue $CurrentState -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1 index 406f0c06bd84..b42cf95556c3 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1 @@ -2,7 +2,34 @@ function Invoke-CIPPStandardBookings { <# .FUNCTIONALITY Internal + .APINAME + Bookings + .CAT + Exchange Standards + .TAG + "mediumimpact" + .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. + .ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.Bookings.state","values":[{"label":"Enabled","value":"true"},{"label":"Disabled","value":"false"}]} + .LABEL + Set Bookings state + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Set-OrganizationConfig -BookingsEnabled + .RECOMMENDEDBY + .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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentState = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').BookingsEnabled @@ -47,3 +74,7 @@ function Invoke-CIPPStandardBookings { } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1 index 2f3841588d28..bd53e1c635e7 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1 @@ -2,8 +2,38 @@ function Invoke-CIPPStandardBranding { <# .FUNCTIONALITY Internal + .APINAME + Branding + .CAT + Global Standards + .TAG + "lowimpact" + .HELPTEXT + Sets the branding for the tenant. This includes the login page, and the Office 365 portal. + .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"} + .LABEL + Set branding for the tenant + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Portal only + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets the branding for the tenant. This includes the login page, and the Office 365 portal. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $TenantId = Get-Tenants | Where-Object -Property defaultDomainName -EQ $Tenant @@ -68,3 +98,7 @@ function Invoke-CIPPStandardBranding { Add-CIPPBPAField -FieldName 'Branding' -FieldValue [bool]$StateIsCorrect -StoreAs bool -Tenant $Tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 index 19c6926d5af4..0c2fcedfcca8 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 @@ -2,7 +2,34 @@ function Invoke-CIPPStandardCloudMessageRecall { <# .FUNCTIONALITY Internal + .APINAME + CloudMessageRecall + .CAT + Exchange Standards + .TAG + "lowimpact" + .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) + .ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.CloudMessageRecall.state","values":[{"label":"Enabled","value":"true"},{"label":"Disabled","value":"false"}]} + .LABEL + Set Cloud Message Recall state + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-OrganizationConfig -MessageRecallEnabled + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets the Cloud Message Recall state for the tenant. This allows users to recall messages from the cloud. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentState = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').MessageRecallEnabled @@ -48,3 +75,7 @@ function Invoke-CIPPStandardCloudMessageRecall { } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 index 81cbdc6e5167..819ba429fa25 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardDelegateSentItems { <# .FUNCTIONALITY Internal + .APINAME + DelegateSentItems + .CAT + Exchange Standards + .TAG + "mediumimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Set mailbox Sent Items delegation (Sent items for shared mailboxes) + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Set-Mailbox + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets emails sent as and on behalf of shared mailboxes to also be stored in the shared mailbox sent items folder + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $Mailboxes = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' -cmdParams @{ RecipientTypeDetails = @('UserMailbox', 'SharedMailbox') } | Where-Object { $_.MessageCopyForSendOnBehalfEnabled -eq $false -or $_.MessageCopyForSentAsEnabled -eq $false } @@ -51,3 +77,7 @@ function Invoke-CIPPStandardDelegateSentItems { Add-CIPPBPAField -FieldName 'DelegateSentItems' -FieldValue $Filtered -StoreAs json -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 index 157bf5fbf690..ae712abebb36 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardDeletedUserRentention { <# .FUNCTIONALITY Internal + .APINAME + DeletedUserRentention + .CAT + SharePoint Standards + .TAG + "lowimpact" + .HELPTEXT + Sets the retention period for deleted users OneDrive to 1 year/365 days + .DOCSDESCRIPTION + When a OneDrive user gets deleted, the personal SharePoint site is saved for 1 year and data can be retrieved from it. + .ADDEDCOMPONENT + .LABEL + Retain a deleted user OneDrive for 1 year + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgBetaAdminSharepointSetting + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets the retention period for deleted users OneDrive to 1 year/365 days + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 } @@ -39,3 +65,7 @@ function Invoke-CIPPStandardDeletedUserRentention { 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..1cfb91402bca 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardDisableAddShortcutsToOneDrive { <# .FUNCTIONALITY Internal + .APINAME + DisableAddShortcutsToOneDrive + .CAT + SharePoint Standards + .TAG + "mediumimpact" + .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. + .DISABLEDFEATURES + + .ADDEDCOMPONENT + .LABEL + Disable Add Shortcuts To OneDrive + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Graph API or Portal + .RECOMMENDEDBY + .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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) If ($Settings.remediate -eq $true) { @@ -95,3 +121,7 @@ function Invoke-CIPPStandardDisableAddShortcutsToOneDrive { Write-LogMessage @log } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 index 6612d7090240..242a4fa64d3c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 @@ -2,7 +2,36 @@ function Invoke-CIPPStandardDisableAdditionalStorageProviders { <# .FUNCTIONALITY Internal + .APINAME + DisableAdditionalStorageProviders + .CAT + Exchange Standards + .TAG + "lowimpact" + "CIS" + "exo_storageproviderrestricted" + .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. + .ADDEDCOMPONENT + .LABEL + Disable additional storage providers in OWA + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Get-OwaMailboxPolicy | Set-OwaMailboxPolicy -AdditionalStorageProvidersEnabled $False + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + 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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $AdditionalStorageProvidersState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OwaMailboxPolicy' -cmdParams @{Identity = 'OwaMailboxPolicy-Default' } @@ -35,3 +64,7 @@ function Invoke-CIPPStandardDisableAdditionalStorageProviders { Add-CIPPBPAField -FieldName 'AdditionalStorageProvidersEnabled' -FieldValue $AdditionalStorageProvidersState.AdditionalStorageProvidersEnabled -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAppCreation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAppCreation.ps1 index 7204971fae4e..624f7d20f1f1 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAppCreation.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAppCreation.ps1 @@ -2,7 +2,35 @@ function Invoke-CIPPStandardDisableAppCreation { <# .FUNCTIONALITY Internal + .APINAME + DisableAppCreation + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + "CIS" + .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. + .ADDEDCOMPONENT + .LABEL + Disable App creation by users + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgPolicyAuthorizationPolicy + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Disables the ability for users to create App registrations in the tenant. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy?$select=defaultUserRolePermissions' -tenantid $Tenant @@ -36,3 +64,7 @@ function Invoke-CIPPStandardDisableAppCreation { Add-CIPPBPAField -FieldName 'UserAppCreationDisabled' -FieldValue $State -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 index ed8a7b256ff7..ded00502d1e0 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardDisableBasicAuthSMTP { <# .FUNCTIONALITY Internal + .APINAME + DisableBasicAuthSMTP + .CAT + Global Standards + .TAG + "mediumimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Disable SMTP Basic Authentication + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Set-TransportConfig -SmtpClientAuthenticationDisabled $true + .RECOMMENDEDBY + .DOCSDESCRIPTION + Disables SMTP AUTH for the organization and all users. This is the default for new tenants. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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) } @@ -68,3 +94,7 @@ function Invoke-CIPPStandardDisableBasicAuthSMTP { } } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableEmail.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableEmail.ps1 index 43eb1f36db3a..09b6fa9ca8d3 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableEmail.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableEmail.ps1 @@ -2,7 +2,31 @@ function Invoke-CIPPStandardDisableEmail { <# .FUNCTIONALITY Internal + .APINAME + DisableEmail + .CAT + Entra (AAD) Standards + .TAG + "highimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Disables Email as an MFA method + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + .RECOMMENDEDBY + .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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 } @@ -27,3 +51,7 @@ function Invoke-CIPPStandardDisableEmail { Add-CIPPBPAField -FieldName 'DisableEmail' -FieldValue $State -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 index 2393e7c3994d..1646b7b36dc1 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 @@ -2,7 +2,36 @@ function Invoke-CIPPStandardDisableExternalCalendarSharing { <# .FUNCTIONALITY Internal + .APINAME + DisableExternalCalendarSharing + .CAT + Exchange Standards + .TAG + "lowimpact" + "CIS" + "exo_individualsharing" + .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. + .ADDEDCOMPONENT + .LABEL + Disable external calendar sharing + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Get-SharingPolicy | Set-SharingPolicy -Enabled $False + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Disables the ability for users to share their calendar with external users. Only for the default policy, so exclusions can be made if needed. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SharingPolicy' | Where-Object { $_.Default -eq $true } @@ -37,3 +66,7 @@ function Invoke-CIPPStandardDisableExternalCalendarSharing { Add-CIPPBPAField -FieldName 'ExternalCalendarSharingDisabled' -FieldValue $CurrentInfo.Enabled -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuestDirectory.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuestDirectory.ps1 index 8fbcb35110d5..62b17aef5b46 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuestDirectory.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuestDirectory.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardDisableGuestDirectory { <# .FUNCTIONALITY Internal + .APINAME + DisableGuestDirectory + .CAT + Global Standards + .TAG + "lowimpact" + .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) + .ADDEDCOMPONENT + .LABEL + Restrict guest user access to directory objects + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-AzureADMSAuthorizationPolicy -GuestUserRoleId '2af84b1e-32c8-42b7-82bc-daa82404023b' + .RECOMMENDEDBY + .DOCSDESCRIPTION + Disables Guest access to enumerate directory objects. This prevents guest users from seeing other users or guests in the directory. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -tenantid $Tenant @@ -36,3 +62,7 @@ function Invoke-CIPPStandardDisableGuestDirectory { Add-CIPPBPAField -FieldName 'DisableGuestDirectory' -FieldValue $CurrentInfo.guestUserRoleId -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 index 7b13fffd147f..e654cd9b5dc7 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 @@ -2,7 +2,31 @@ function Invoke-CIPPStandardDisableGuests { <# .FUNCTIONALITY Internal + .APINAME + DisableGuests + .CAT + Entra (AAD) Standards + .TAG + "mediumimpact" + .HELPTEXT + Blocks login for guest users that have not logged in for 90 days + .ADDEDCOMPONENT + .LABEL + Disable Guest accounts that have not logged on for 90 days + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Graph API + .RECOMMENDEDBY + .DOCSDESCRIPTION + Blocks login for guest users that have not logged in for 90 days + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 } @@ -37,3 +61,7 @@ function Invoke-CIPPStandardDisableGuests { Add-CIPPBPAField -FieldName 'DisableGuests' -FieldValue $filtered -StoreAs json -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 index a173eaba6759..558ce91bf77f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardDisableM365GroupUsers { <# .FUNCTIONALITY Internal + .APINAME + DisableM365GroupUsers + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + .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 + .ADDEDCOMPONENT + .LABEL + Disable M365 Group creation by users + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgBetaDirectorySetting + .RECOMMENDEDBY + .DOCSDESCRIPTION + Restricts M365 group creation to certain admin roles. This disables the ability to create Teams, Sharepoint sites, Planner, etc + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentState = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/settings' -tenantid $tenant) | Where-Object -Property displayname -EQ 'Group.unified' @@ -53,3 +79,7 @@ function Invoke-CIPPStandardDisableM365GroupUsers { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 index 230920781a6a..39b2b91a764e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 @@ -2,7 +2,36 @@ function Invoke-CIPPStandardDisableOutlookAddins { <# .FUNCTIONALITY Internal + .APINAME + DisableOutlookAddins + .CAT + Exchange Standards + .TAG + "mediumimpact" + "CIS" + "exo_outlookaddins" + .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. + .ADDEDCOMPONENT + .LABEL + Disable users from installing add-ins in Outlook + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Get-ManagementRoleAssignment | Remove-ManagementRoleAssignment + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Disables the ability for users to install add-ins in Outlook. This is to prevent users from installing malicious add-ins. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-RoleAssignmentPolicy' | Where-Object { $_.IsDefault -eq $true } @@ -53,3 +82,7 @@ function Invoke-CIPPStandardDisableOutlookAddins { Add-CIPPBPAField -FieldName 'DisabledOutlookAddins' -FieldValue $State -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 index 0893d8fda2d2..26db5c11c88a 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 @@ -2,7 +2,35 @@ function Invoke-CIPPStandardDisableReshare { <# .FUNCTIONALITY Internal + .APINAME + DisableReshare + .CAT + SharePoint Standards + .TAG + "highimpact" + "CIS" + .HELPTEXT + Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access + .DOCSDESCRIPTION + Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access. This is a tenant wide setting and overrules any settings set on the site level + .ADDEDCOMPONENT + .LABEL + Disable Resharing by External Users + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + Update-MgBetaAdminSharepointSetting + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true @@ -34,3 +62,7 @@ function Invoke-CIPPStandardDisableReshare { Add-CIPPBPAField -FieldName 'DisableReshare' -FieldValue $CurrentInfo.isResharingByExternalUsersEnabled -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSMS.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSMS.ps1 index 43dd0198d1b3..30454df4cba6 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSMS.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSMS.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardDisableSMS { <# .FUNCTIONALITY Internal + .APINAME + DisableSMS + .CAT + Entra (AAD) Standards + .TAG + "highimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Disables SMS as an MFA method + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + .RECOMMENDEDBY + .DOCSDESCRIPTION + 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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 } @@ -27,3 +53,7 @@ function Invoke-CIPPStandardDisableSMS { Add-CIPPBPAField -FieldName 'DisableSMS' -FieldValue $State -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSecurityGroupUsers.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSecurityGroupUsers.ps1 index edaf91dfde7d..df6a6d327447 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSecurityGroupUsers.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSecurityGroupUsers.ps1 @@ -2,7 +2,31 @@ function Invoke-CIPPStandardDisableSecurityGroupUsers { <# .FUNCTIONALITY Internal + .APINAME + DisableSecurityGroupUsers + .CAT + Entra (AAD) Standards + .TAG + "mediumimpact" + .HELPTEXT + Completely disables the creation of security groups by users. This also breaks the ability to manage groups themselves, or create Teams + .ADDEDCOMPONENT + .LABEL + Disable Security Group creation by users + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthorizationPolicy + .RECOMMENDEDBY + .DOCSDESCRIPTION + Completely disables the creation of security groups by users. This also breaks the ability to manage groups themselves, or create Teams + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -tenantid $Tenant @@ -35,3 +59,7 @@ function Invoke-CIPPStandardDisableSecurityGroupUsers { Add-CIPPBPAField -FieldName 'DisableSecurityGroupUsers' -FieldValue $CurrentInfo.defaultUserRolePermissions.allowedToCreateSecurityGroups -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 index 6431f6053dc5..147aa0fefe89 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 @@ -2,9 +2,37 @@ function Invoke-CIPPStandardDisableSelfServiceLicenses { <# .FUNCTIONALITY Internal + .APINAME + DisableSelfServiceLicenses + .CAT + Entra (AAD) Standards + .TAG + "mediumimpact" + .HELPTEXT + This standard currently does not function and can be safely disabled + .ADDEDCOMPONENT + .LABEL + Disable Self Service Licensing + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Set-MsolCompanySettings -AllowAdHocSubscriptions $false + .RECOMMENDEDBY + .DOCSDESCRIPTION + This standard currently does not function and can be safely disabled + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) Write-LogMessage -API 'Standards' -tenant $tenant -message 'Self Service Licenses cannot be disabled' -sev Error } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 index 1e109b41a3aa..b562d10dbd3e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 @@ -2,7 +2,35 @@ function Invoke-CIPPStandardDisableSharePointLegacyAuth { <# .FUNCTIONALITY Internal + .APINAME + DisableSharePointLegacyAuth + .CAT + SharePoint Standards + .TAG + "mediumimpact" + "CIS" + .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. + .ADDEDCOMPONENT + .LABEL + Disable legacy basic authentication for SharePoint + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Set-SPOTenant -LegacyAuthProtocolsEnabled $false + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + 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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings?$select=isLegacyAuthProtocolsEnabled' -tenantid $Tenant -AsApp $true @@ -36,3 +64,7 @@ function Invoke-CIPPStandardDisableSharePointLegacyAuth { Add-CIPPBPAField -FieldName 'SharePointLegacyAuthEnabled' -FieldValue $CurrentInfo.isLegacyAuthProtocolsEnabled -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharedMailbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharedMailbox.ps1 index d9d3356eba00..c60ffd274664 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharedMailbox.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharedMailbox.ps1 @@ -2,7 +2,35 @@ function Invoke-CIPPStandardDisableSharedMailbox { <# .FUNCTIONALITY Internal + .APINAME + DisableSharedMailbox + .CAT + Exchange Standards + .TAG + "mediumimpact" + "CIS" + .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. + .ADDEDCOMPONENT + .LABEL + Disable Shared Mailbox AAD accounts + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Get-Mailbox & Update-MgUser + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Blocks login for all accounts that are marked as a shared mailbox. This is Microsoft best practice to prevent direct logons to shared mailboxes. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 }) @@ -37,3 +65,7 @@ function Invoke-CIPPStandardDisableSharedMailbox { Add-CIPPBPAField -FieldName 'DisableSharedMailbox' -FieldValue $SharedMailboxList -StoreAs json -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 index 022f21807864..1db22507ede0 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 @@ -2,8 +2,34 @@ function Invoke-CIPPStandardDisableTNEF { <# .FUNCTIONALITY Internal + .APINAME + DisableTNEF + .CAT + Exchange Standards + .TAG + "lowimpact" + .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) + .ADDEDCOMPONENT + .LABEL + Disable TNEF/winmail.dat + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-RemoteDomain -Identity 'Default' -TNEFEnabled $false + .RECOMMENDEDBY + .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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param ($Tenant, $Settings) $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-RemoteDomain' -cmdParams @{Identity = 'Default' } @@ -37,3 +63,7 @@ function Invoke-CIPPStandardDisableTNEF { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTenantCreation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTenantCreation.ps1 index 97616ca367ea..30eef38a9254 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTenantCreation.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTenantCreation.ps1 @@ -2,7 +2,35 @@ function Invoke-CIPPStandardDisableTenantCreation { <# .FUNCTIONALITY Internal + .APINAME + DisableTenantCreation + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + "CIS" + .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. + .ADDEDCOMPONENT + .LABEL + Disable M365 Tenant creation by users + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgPolicyAuthorizationPolicy + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Restricts creation of M365 tenants to the Global Administrator or Tenant Creator roles. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -tenantid $Tenant $State = $CurrentInfo.defaultUserRolePermissions.allowedToCreateTenants @@ -35,3 +63,7 @@ function Invoke-CIPPStandardDisableTenantCreation { Add-CIPPBPAField -FieldName 'DisableTenantCreation' -FieldValue $State -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 index 97bac09d7668..b47e17c3ccc6 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardDisableUserSiteCreate { <# .FUNCTIONALITY Internal + .APINAME + DisableUserSiteCreate + .CAT + SharePoint Standards + .TAG + "highimpact" + .HELPTEXT + Disables users from creating new SharePoint sites + .DOCSDESCRIPTION + Disables standard users from creating SharePoint sites, also disables the ability to fully create teams + .ADDEDCOMPONENT + .LABEL + Disable site creation by standard users + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + Update-MgAdminSharepointSetting + .RECOMMENDEDBY + .DOCSDESCRIPTION + Disables users from creating new SharePoint sites + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true @@ -36,3 +62,7 @@ function Invoke-CIPPStandardDisableUserSiteCreate { Add-CIPPBPAField -FieldName 'DisableUserSiteCreate' -FieldValue $CurrentInfo.isSiteCreationEnabled -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableViva.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableViva.ps1 index 2a87da3fef09..efc8ef960fa4 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableViva.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableViva.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardDisableViva { <# .FUNCTIONALITY Internal + .APINAME + DisableViva + .CAT + Exchange Standards + .TAG + "lowimpact" + .HELPTEXT + Disables the daily viva reports for all users. + .DOCSDESCRIPTION + Disables the daily viva reports for all users. + .ADDEDCOMPONENT + .LABEL + Disable daily Insight/Viva reports + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-UserBriefingConfig + .RECOMMENDEDBY + .DOCSDESCRIPTION + Disables the daily viva reports for all users. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) try { @@ -45,3 +71,7 @@ function Invoke-CIPPStandardDisableViva { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableVoice.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableVoice.ps1 index 0c064013b444..7d8fc7b30d80 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableVoice.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableVoice.ps1 @@ -2,7 +2,30 @@ function Invoke-CIPPStandardDisableVoice { <# .FUNCTIONALITY Internal + .APINAME + DisableVoice + .CAT + Entra (AAD) Standards + .TAG + "highimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Disables Voice call as an MFA method + .IMPACT + High Impact + .DOCSDESCRIPTION + 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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 } @@ -27,3 +50,7 @@ function Invoke-CIPPStandardDisableVoice { Add-CIPPBPAField -FieldName 'DisableVoice' -FieldValue $State -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisablex509Certificate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisablex509Certificate.ps1 index d59042f1f6c8..b79fa98643c2 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisablex509Certificate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisablex509Certificate.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardDisablex509Certificate { <# .FUNCTIONALITY Internal + .APINAME + Disablex509Certificate + .CAT + Entra (AAD) Standards + .TAG + "highimpact" + .HELPTEXT + This blocks users from using Certificates as an MFA method. + .DOCSDESCRIPTION + This blocks users from using Certificates as an MFA method. + .ADDEDCOMPONENT + .LABEL + Disables Certificates as an MFA method + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + .RECOMMENDEDBY + .DOCSDESCRIPTION + This blocks users from using Certificates as an MFA method. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 } @@ -28,3 +54,7 @@ function Invoke-CIPPStandardDisablex509Certificate { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableAppConsentRequests.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableAppConsentRequests.ps1 index 835d6a9dfe94..4c427bac19d1 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableAppConsentRequests.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableAppConsentRequests.ps1 @@ -2,7 +2,36 @@ function Invoke-CIPPStandardEnableAppConsentRequests { <# .FUNCTIONALITY Internal + .APINAME + EnableAppConsentRequests + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + "CIS" + .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 + .ADDEDCOMPONENT + {"type":"AdminRolesMultiSelect","label":"App Consent Reviewer Roles","name":"standards.EnableAppConsentRequests.ReviewerRoles"} + .LABEL + Enable App consent admin requests + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgPolicyAdminConsentRequestPolicy + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Enables App consent admin requests for the tenant via the GA role. Does not overwrite existing reviewer settings + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/adminConsentRequestPolicy' -tenantid $Tenant @@ -77,3 +106,7 @@ function Invoke-CIPPStandardEnableAppConsentRequests { Add-CIPPBPAField -FieldName 'EnableAppConsentAdminRequests' -FieldValue $CurrentInfo.isEnabled -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1 index b5741d27ac4d..daabff2b8ccc 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1 @@ -2,7 +2,36 @@ function Invoke-CIPPStandardEnableCustomerLockbox { <# .FUNCTIONALITY Internal + .APINAME + EnableCustomerLockbox + .CAT + Global Standards + .TAG + "lowimpact" + "CIS" + "CustomerLockBoxEnabled" + .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. + .ADDEDCOMPONENT + .LABEL + Enable Customer Lockbox + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-OrganizationConfig -CustomerLockBoxEnabled $true + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Enables Customer Lockbox that offers an approval process for Microsoft support to access organization data + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CustomerLockboxStatus = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').CustomerLockboxEnabled @@ -38,3 +67,7 @@ function Invoke-CIPPStandardEnableCustomerLockbox { Add-CIPPBPAField -FieldName 'CustomerLockboxEnabled' -FieldValue $CustomerLockboxStatus -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableFIDO2.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableFIDO2.ps1 index d5d84aa3d8e6..f7111785c042 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableFIDO2.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableFIDO2.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardEnableFIDO2 { <# .FUNCTIONALITY Internal + .APINAME + EnableFIDO2 + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Enable FIDO2 capabilities + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + .RECOMMENDEDBY + .DOCSDESCRIPTION + Enables the FIDO2 authenticationMethod for the tenant + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 } @@ -31,3 +57,7 @@ function Invoke-CIPPStandardEnableFIDO2 { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableHardwareOAuth.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableHardwareOAuth.ps1 index 67b0cf7e7bc4..eaf297caf78e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableHardwareOAuth.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableHardwareOAuth.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardEnableHardwareOAuth { <# .FUNCTIONALITY Internal + .APINAME + EnableHardwareOAuth + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Enable Hardware OAuth tokens + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + .RECOMMENDEDBY + .DOCSDESCRIPTION + Enables the HardwareOath authenticationMethod for the tenant. This allows you to use hardware tokens for generating 6 digit MFA codes. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 } @@ -30,3 +56,7 @@ function Invoke-CIPPStandardEnableHardwareOAuth { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 index 1de0a2315a13..17e735496e88 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 @@ -1,8 +1,32 @@ function Invoke-CIPPStandardEnableLitigationHold { - <# - .FUNCTIONALITY - Internal - #> + <# + .FUNCTIONALITY + Internal + .APINAME + EnableLitigationHold + .CAT + Exchange Standards + .TAG + "lowimpact" + .HELPTEXT + Enables litigation hold for all UserMailboxes with a valid license. + .ADDEDCOMPONENT + .LABEL + Enable Litigation Hold for all users + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-Mailbox -LitigationHoldEnabled $true + .RECOMMENDEDBY + .DOCSDESCRIPTION + Enables litigation hold for all UserMailboxes with a valid license. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + + + param($Tenant, $Settings) $MailboxesNoLitHold = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' -cmdparams @{ MailboxPlan = 'ExchangeOnlineEnterprise'; Filter = 'LitigationHoldEnabled -eq "False"'} @@ -52,3 +76,7 @@ function Invoke-CIPPStandardEnableLitigationHold { 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..1abee9433d86 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 @@ -2,8 +2,36 @@ function Invoke-CIPPStandardEnableMailTips { <# .FUNCTIONALITY Internal + .APINAME + EnableMailTips + .CAT + Exchange Standards + .TAG + "lowimpact" + "CIS" + "exo_mailtipsenabled" + .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 + .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} + .LABEL + Enable all MailTips + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-OrganizationConfig + .RECOMMENDEDBY + "CIS" + .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 + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $MailTipsState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig' | Select-Object MailTipsAllTipsEnabled, MailTipsExternalRecipientsTipsEnabled, MailTipsGroupMetricsEnabled, MailTipsLargeAudienceThreshold $StateIsCorrect = if ($MailTipsState.MailTipsAllTipsEnabled -and $MailTipsState.MailTipsExternalRecipientsTipsEnabled -and $MailTipsState.MailTipsGroupMetricsEnabled -and $MailTipsState.MailTipsLargeAudienceThreshold -eq $Settings.MailTipsLargeAudienceThreshold) { $true } else { $false } @@ -38,3 +66,7 @@ function Invoke-CIPPStandardEnableMailTips { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 index 2374206fc6d0..e085bb128d8a 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 @@ -2,7 +2,36 @@ function Invoke-CIPPStandardEnableMailboxAuditing { <# .FUNCTIONALITY Internal + .APINAME + EnableMailboxAuditing + .CAT + Exchange Standards + .TAG + "lowimpact" + "CIS" + "exo_mailboxaudit" + .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. + .ADDEDCOMPONENT + .LABEL + Enable Mailbox auditing + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-OrganizationConfig -AuditDisabled $false + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + 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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $AuditState = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').AuditDisabled @@ -91,3 +120,7 @@ function Invoke-CIPPStandardEnableMailboxAuditing { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 index 3c3dbd004c45..90a20d59b356 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 @@ -2,7 +2,31 @@ function Invoke-CIPPStandardEnableOnlineArchiving { <# .FUNCTIONALITY Internal + .APINAME + EnableOnlineArchiving + .CAT + Exchange Standards + .TAG + "lowimpact" + .HELPTEXT + Enables the In-Place Online Archive for all UserMailboxes with a valid license. + .ADDEDCOMPONENT + .LABEL + Enable Online Archive for all users + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Enable-Mailbox -Archive $true + .RECOMMENDEDBY + .DOCSDESCRIPTION + Enables the In-Place Online Archive for all UserMailboxes with a valid license. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $MailboxPlans = @( 'ExchangeOnline', 'ExchangeOnlineEnterprise' ) @@ -56,3 +80,7 @@ function Invoke-CIPPStandardEnableOnlineArchiving { Add-CIPPBPAField -FieldName 'EnableOnlineArchiving' -FieldValue $filtered -StoreAs json -Tenant $Tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1 index bd4d6c85e70e..7d20bcee6666 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1 @@ -2,7 +2,31 @@ function Invoke-CIPPStandardEnablePronouns { <# .FUNCTIONALITY Internal + .APINAME + EnablePronouns + .CAT + Global Standards + .TAG + "lowimpact" + .HELPTEXT + Enables the Pronouns feature for the tenant. This allows users to set their pronouns in their profile. + .ADDEDCOMPONENT + .LABEL + Enable Pronouns + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgBetaAdminPeoplePronoun -IsEnabledInOrganization:$true + .RECOMMENDEDBY + .DOCSDESCRIPTION + Enables the Pronouns feature for the tenant. This allows users to set their pronouns in their profile. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param ($Tenant, $Settings) $Uri = 'https://graph.microsoft.com/v1.0/admin/people/pronouns' @@ -47,3 +71,7 @@ function Invoke-CIPPStandardEnablePronouns { Add-CIPPBPAField -FieldName 'PronounsEnabled' -FieldValue $CurrentState.isEnabledInOrganization -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 index 321c13c46ad8..28ab4c8ca495 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 @@ -2,7 +2,32 @@ function Invoke-CIPPStandardExcludedfileExt { <# .FUNCTIONALITY Internal + .APINAME + ExcludedfileExt + .CAT + SharePoint Standards + .TAG + "highimpact" + .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. + .ADDEDCOMPONENT + {"type":"input","name":"standards.ExcludedfileExt.ext","label":"Extensions, Comma separated"} + .LABEL + Exclude File Extensions from Syncing + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + Update-MgAdminSharepointSetting + .RECOMMENDEDBY + .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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true $Exts = ($Settings.ext -replace ' ', '') -split ',' @@ -52,3 +77,7 @@ function Invoke-CIPPStandardExcludedfileExt { Add-CIPPBPAField -FieldName 'ExcludedfileExt' -FieldValue $CurrentInfo.excludedFileExtensionsForSyncApp -StoreAs json -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 index 6fefb63e5b59..619455fdb0e7 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 @@ -2,7 +2,32 @@ function Invoke-CIPPStandardExternalMFATrusted { <# .FUNCTIONALITY Internal + .APINAME + ExternalMFATrusted + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + .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. + .ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.ExternalMFATrusted.state","values":[{"label":"Enabled","value":"true"},{"label":"Disabled","value":"false"}]} + .LABEL + Sets the Cross-tenant access setting to trust external MFA + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyCrossTenantAccessPolicyDefault + .RECOMMENDEDBY + .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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $ExternalMFATrusted = (New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy/default?$select=inboundTrust' -tenantid $Tenant) @@ -48,3 +73,7 @@ function Invoke-CIPPStandardExternalMFATrusted { } } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 index 6dccab45117f..9a9655ac22a4 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 @@ -2,7 +2,34 @@ function Invoke-CIPPStandardFocusedInbox { <# .FUNCTIONALITY Internal + .APINAME + FocusedInbox + .CAT + Exchange Standards + .TAG + "lowimpact" + .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) + .ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.FocusedInbox.state","values":[{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}]} + .LABEL + Set Focused Inbox state + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-OrganizationConfig -FocusedInboxOn $true or $false + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets the default Focused Inbox state for the tenant. This can be overridden by the user. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) # Input validation @@ -45,3 +72,7 @@ function Invoke-CIPPStandardFocusedInbox { Add-CIPPBPAField -FieldName 'FocusedInboxCorrectState' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 index 583a39ecb8f8..a99543882349 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 @@ -2,7 +2,34 @@ function Invoke-CIPPStandardGlobalQuarantineNotifications { <# .FUNCTIONALITY Internal + .APINAME + GlobalQuarantineNotifications + .CAT + Exchange Standards + .TAG + "lowimpact" + .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. + .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"}]} + .LABEL + Set Global Quarantine Notification Interval + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-QuarantinePolicy -EndUserSpamNotificationFrequency + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets the Global Quarantine Notification Interval to the selected value. Determines how often the quarantine notification is sent to users. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param ($Tenant, $Settings) $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-QuarantinePolicy' -cmdParams @{ QuarantinePolicyType = 'GlobalQuarantinePolicy' } @@ -55,3 +82,7 @@ function Invoke-CIPPStandardGlobalQuarantineNotifications { } } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardLegacyMFACleanup.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardLegacyMFACleanup.ps1 index 08f43267469f..282d7ebc4778 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardLegacyMFACleanup.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardLegacyMFACleanup.ps1 @@ -2,8 +2,36 @@ function Invoke-CIPPStandardLegacyMFACleanup { <# .FUNCTIONALITY Internal + .APINAME + LegacyMFACleanup + .CAT + Entra (AAD) Standards + .TAG + "mediumimpact" + .HELPTEXT + This standard currently does not function and can be safely disabled + .ADDEDCOMPONENT + .LABEL + Remove Legacy MFA if SD or CA is active + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Set-MsolUser -StrongAuthenticationRequirements $null + .RECOMMENDEDBY + .DOCSDESCRIPTION + This standard currently does not function and can be safely disabled + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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..724357d3ef55 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 @@ -2,7 +2,39 @@ function Invoke-CIPPStandardMailContacts { <# .FUNCTIONALITY Internal + .APINAME + MailContacts + .CAT + Global Standards + .TAG + "lowimpact" + .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. + .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"} + .LABEL + Set contact e-mails + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-MsolCompanyContactInformation + .RECOMMENDEDBY + .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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 @@ -63,3 +95,7 @@ function Invoke-CIPPStandardMailContacts { Add-CIPPBPAField -FieldName 'MailContacts' -FieldValue $CurrentInfo -StoreAs json -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 index 863110cdac69..79fa04d4c3fc 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 @@ -1,8 +1,43 @@ function Invoke-CIPPStandardMalwareFilterPolicy { - <# - .FUNCTIONALITY - Internal - #> + <# + .FUNCTIONALITY + Internal + .APINAME + MalwareFilterPolicy + .CAT + Defender Standards + .TAG + "lowimpact" + "CIS" + "mdo_zapspam" + "mdo_zapphish" + "mdo_zapmalware" + .HELPTEXT + This creates a Malware filter policy that enables the default File filter and Zero-hour auto purge for malware. + .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"} + .LABEL + Default Malware Filter Policy + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-MalwareFilterPolicy or New-MalwareFilterPolicy + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + This creates a Malware filter policy that enables the default File filter and Zero-hour auto purge for malware. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + + + param($Tenant, $Settings) $PolicyName = 'Default Malware Policy' @@ -119,3 +154,7 @@ function Invoke-CIPPStandardMalwareFilterPolicy { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 index 820486c52ef3..b5012d7aefea 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardMessageExpiration { <# .FUNCTIONALITY Internal + .APINAME + MessageExpiration + .CAT + Exchange Standards + .TAG + "lowimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Lower Transport Message Expiration to 12 hours + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-TransportConfig -MessageExpirationTimeout 12.00:00:00 + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets the transport message configuration to timeout a message at 12 hours. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $MessageExpiration = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-TransportConfig').messageExpiration @@ -34,3 +60,7 @@ function Invoke-CIPPStandardMessageExpiration { Add-CIPPBPAField -FieldName 'messageExpiration' -FieldValue $MessageExpiration -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 index 6d387b212767..116b3d4ed40f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 @@ -2,7 +2,35 @@ function Invoke-CIPPStandardNudgeMFA { <# .FUNCTIONALITY Internal + .APINAME + NudgeMFA + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + .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. + .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} + .LABEL + Sets the state for the request to setup Authenticator + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgPolicyAuthenticationMethodPolicy + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets the state of the registration campaign for the tenant + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy' -tenantid $Tenant @@ -57,3 +85,7 @@ function Invoke-CIPPStandardNudgeMFA { } } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1 index 38d2b41dfce3..036bd6f011d0 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1 @@ -2,7 +2,36 @@ function Invoke-CIPPStandardOauthConsent { <# .FUNCTIONALITY Internal + .APINAME + OauthConsent + .CAT + Entra (AAD) Standards + .TAG + "mediumimpact" + "CIS" + .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. + .ADDEDCOMPONENT + {"type":"input","name":"standards.OauthConsent.AllowedApps","label":"Allowed application IDs, comma separated"} + .LABEL + Require admin consent for applications (Prevent OAuth phishing) + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Update-MgPolicyAuthorizationPolicy + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Disables users from being able to consent to applications, except for those specified in the field below + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 } @@ -46,3 +75,7 @@ function Invoke-CIPPStandardOauthConsent { Add-CIPPBPAField -FieldName 'OauthConsent' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1 index 42814d48cfb6..ba94c4f77843 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1 @@ -2,7 +2,32 @@ function Invoke-CIPPStandardOauthConsentLowSec { <# .FUNCTIONALITY Internal + .APINAME + OauthConsentLowSec + .CAT + Entra (AAD) Standards + .TAG + "mediumimpact" + .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. + .LABEL + Allow users to consent to applications with low security risk (Prevent OAuth phishing. Lower impact, less secure) + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Update-MgPolicyAuthorizationPolicy + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets the default oauth consent level so users can consent to applications that have low risks. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $State = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy' -tenantid $tenant) If ($Settings.remediate -eq $true) { @@ -34,3 +59,7 @@ function Invoke-CIPPStandardOauthConsentLowSec { Add-CIPPBPAField -FieldName 'OauthConsentLowSec' -FieldValue $State.permissionGrantPolicyIdsAssignedToDefaultUserRole -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 index a786a7d044b8..8ab6cab3d30e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 @@ -2,7 +2,36 @@ function Invoke-CIPPStandardOutBoundSpamAlert { <# .FUNCTIONALITY Internal + .APINAME + OutBoundSpamAlert + .CAT + Exchange Standards + .TAG + "lowimpact" + "CIS" + .HELPTEXT + Set the Outbound Spam Alert e-mail address + .DOCSDESCRIPTION + Sets the e-mail address to which outbound spam alerts are sent. + .ADDEDCOMPONENT + {"type":"input","name":"standards.OutBoundSpamAlert.OutboundSpamContact","label":"Outbound spam contact"} + .LABEL + Set Outbound Spam Alert e-mail + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-HostedOutboundSpamFilterPolicy + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Set the Outbound Spam Alert e-mail address + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-HostedOutboundSpamFilterPolicy' -useSystemMailbox $true @@ -35,3 +64,7 @@ function Invoke-CIPPStandardOutBoundSpamAlert { Add-CIPPBPAField -FieldName 'OutboundSpamAlert' -FieldValue $CurrentInfo.NotifyOutboundSpam -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWcompanionAppAllowedState.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWcompanionAppAllowedState.ps1 index c2e3c7a687b7..8148322ca651 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWcompanionAppAllowedState.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWcompanionAppAllowedState.ps1 @@ -2,7 +2,34 @@ function Invoke-CIPPStandardPWcompanionAppAllowedState { <# .FUNCTIONALITY Internal + .APINAME + PWcompanionAppAllowedState + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + .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. + .ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.PWcompanionAppAllowedState.state","values":[{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}]} + .LABEL + Set Authenticator Lite state + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets the state of Authenticator Lite, Authenticator lite is a companion app for passwordless authentication. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $authenticatorFeaturesState = (New-GraphGetRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator' -Type GET) @@ -58,3 +85,7 @@ function Invoke-CIPPStandardPWcompanionAppAllowedState { } } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 index 2f85c01bf859..83b2b276195e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 @@ -2,7 +2,35 @@ function Invoke-CIPPStandardPWdisplayAppInformationRequiredState { <# .FUNCTIONALITY Internal + .APINAME + PWdisplayAppInformationRequiredState + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + "CIS" + .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 + .ADDEDCOMPONENT + .LABEL + Enable Passwordless with Location information and Number Matching + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Enables the MS authenticator app to display information about the app that is requesting authentication. This displays the application name. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 } @@ -27,3 +55,7 @@ function Invoke-CIPPStandardPWdisplayAppInformationRequiredState { Add-CIPPBPAField -FieldName 'PWdisplayAppInformationRequiredState' -FieldValue $State -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPasswordExpireDisabled.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPasswordExpireDisabled.ps1 index 5cf8dac138a1..c4b2bff28cd7 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPasswordExpireDisabled.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPasswordExpireDisabled.ps1 @@ -2,7 +2,36 @@ function Invoke-CIPPStandardPasswordExpireDisabled { <# .FUNCTIONALITY Internal + .APINAME + PasswordExpireDisabled + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + "CIS" + "PWAgePolicyNew" + .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. + .ADDEDCOMPONENT + .LABEL + Do not expire passwords + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgDomain + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Disables the expiration of passwords for the tenant by setting the password expiration policy to never expire for any user. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $GraphRequest = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/domains' -tenantid $Tenant $DomainswithoutPassExpire = $GraphRequest | Where-Object -Property passwordValidityPeriodInDays -NE '2147483647' @@ -43,3 +72,7 @@ function Invoke-CIPPStandardPasswordExpireDisabled { Add-CIPPBPAField -FieldName 'PasswordExpireDisabled' -FieldValue $DomainswithoutPassExpire -StoreAs json -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 index c83204529423..5f08753147f6 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 @@ -2,7 +2,31 @@ function Invoke-CIPPStandardPerUserMFA { <# .FUNCTIONALITY Internal + .APINAME + PerUserMFA + .CAT + Entra (AAD) Standards + .TAG + "highimpact" + .HELPTEXT + Enables per user MFA for all users. + .ADDEDCOMPONENT + .LABEL + Enables per user MFA for all users. + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + Graph API + .RECOMMENDEDBY + .DOCSDESCRIPTION + Enables per user MFA for all users. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 } @@ -39,3 +63,7 @@ function Invoke-CIPPStandardPerUserMFA { Add-CIPPBPAField -FieldName 'LegacyMFAUsers' -FieldValue $UsersWithoutMFA -StoreAs json -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 index 8d0e37ffe6b0..1ed18b3104f5 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 @@ -2,7 +2,34 @@ function Invoke-CIPPStandardPhishProtection { <# .FUNCTIONALITY Internal + .APINAME + PhishProtection + .CAT + Global Standards + .TAG + "lowimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Enable Phishing Protection system via branding CSS + .IMPACT + Low Impact + .DISABLEDFEATURES + + .POWERSHELLEQUIVALENT + Portal only + .RECOMMENDEDBY + "CIPP" + .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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $TenantId = Get-Tenants | Where-Object -Property defaultDomainName -EQ $tenant @@ -56,3 +83,7 @@ function Invoke-CIPPStandardPhishProtection { Add-CIPPBPAField -FieldName 'PhishProtection' -FieldValue $authstate -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 index e149d3aec70d..135d55c06641 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardRotateDKIM { <# .FUNCTIONALITY Internal + .APINAME + RotateDKIM + .CAT + Exchange Standards + .TAG + "lowimpact" + "CIS" + .HELPTEXT + Rotate DKIM keys that are 1024 bit to 2048 bit + .ADDEDCOMPONENT + .LABEL + Rotate DKIM keys that are 1024 bit to 2048 bit + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Rotate-DkimSigningConfig + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Rotate DKIM keys that are 1024 bit to 2048 bit + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $DKIM = (New-ExoRequest -tenantid $tenant -cmdlet 'Get-DkimSigningConfig') | Where-Object { $_.Selector1KeySize -Eq 1024 -and $_.Enabled -eq $true } @@ -36,3 +62,7 @@ function Invoke-CIPPStandardRotateDKIM { Add-CIPPBPAField -FieldName 'DKIM' -FieldValue $DKIM -StoreAs json -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 index bad693702eb5..dc080914f7b8 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 @@ -1,8 +1,41 @@ function Invoke-CIPPStandardSafeAttachmentPolicy { - <# - .FUNCTIONALITY - Internal - #> + <# + .FUNCTIONALITY + Internal + .APINAME + SafeAttachmentPolicy + .CAT + Defender Standards + .TAG + "lowimpact" + "CIS" + "mdo_safedocuments" + "mdo_commonattachmentsfilter" + "mdo_safeattachmentpolicy" + .HELPTEXT + This creates a Safe Attachment policy + .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"} + .LABEL + Default Safe Attachment Policy + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-SafeAttachmentPolicy or New-SafeAttachmentPolicy + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + This creates a Safe Attachment policy + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + + + param($Tenant, $Settings) $PolicyName = 'Default Safe Attachment Policy' @@ -104,3 +137,7 @@ function Invoke-CIPPStandardSafeAttachmentPolicy { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 index 1e68ef5a2475..92220f54e19c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 @@ -1,8 +1,39 @@ function Invoke-CIPPStandardSafeLinksPolicy { - <# - .FUNCTIONALITY - Internal - #> + <# + .FUNCTIONALITY + Internal + .APINAME + SafeLinksPolicy + .CAT + Defender Standards + .TAG + "lowimpact" + "CIS" + "mdo_safelinksforemail" + "mdo_safelinksforOfficeApps" + .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 + .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"} + .LABEL + Default SafeLinks Policy + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-SafeLinksPolicy or New-SafeLinksPolicy + .RECOMMENDEDBY + "CIS" + .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 + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + + + param($Tenant, $Settings) $PolicyName = 'Default SafeLinks Policy' @@ -116,3 +147,7 @@ function Invoke-CIPPStandardSafeLinksPolicy { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 index d70fe30cef99..de39be25829d 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardSafeSendersDisable { <# .FUNCTIONALITY Internal + .APINAME + SafeSendersDisable + .CAT + Exchange Standards + .TAG + "mediumimpact" + .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. + .ADDEDCOMPONENT + .DISABLEDFEATURES + + .LABEL + Remove Safe Senders to prevent SPF bypass + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Set-MailboxJunkEmailConfiguration + .RECOMMENDEDBY + .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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) If ($Settings.remediate -eq $true) { @@ -36,3 +62,7 @@ function Invoke-CIPPStandardSafeSendersDisable { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecurityDefaults.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecurityDefaults.ps1 index 07bc25df5021..d563d8c1fd4e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecurityDefaults.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecurityDefaults.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardSecurityDefaults { <# .FUNCTIONALITY Internal + .APINAME + SecurityDefaults + .CAT + Entra (AAD) Standards + .TAG + "highimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Enable Security Defaults + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + [Read more here](https://www.cyberdrain.com/automating-with-powershell-enabling-secure-defaults-and-sd-explained/) + .RECOMMENDEDBY + .DOCSDESCRIPTION + Enables security defaults for the tenant, for newer tenants this is enabled by default. Do not enable this feature if you use Conditional Access. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $SecureDefaultsState = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -tenantid $tenant) @@ -36,3 +62,7 @@ function Invoke-CIPPStandardSecurityDefaults { Add-CIPPBPAField -FieldName 'SecurityDefaults' -FieldValue $SecureDefaultsState.IsEnabled -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 index ce7d56f76454..14b551316e11 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardSendFromAlias { <# .FUNCTIONALITY Internal + .APINAME + SendFromAlias + .CAT + Exchange Standards + .TAG + "mediumimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Allow users to send from their alias addresses + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Set-Mailbox + .RECOMMENDEDBY + .DOCSDESCRIPTION + Enables the ability for users to send from their alias addresses. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').SendFromAliasEnabled @@ -33,3 +59,7 @@ function Invoke-CIPPStandardSendFromAlias { Add-CIPPBPAField -FieldName 'SendFromAlias' -FieldValue $CurrentInfo -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 index 68c7519f5e25..a9ba445828ed 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardSendReceiveLimitTenant { <# .FUNCTIONALITY Internal + .APINAME + SendReceiveLimitTenant + .CAT + Exchange Standards + .TAG + "lowimpact" + .HELPTEXT + Sets the Send and Receive limits for new users. Valid values are 1MB to 150MB + .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} + .LABEL + Set send/receive size limits + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-MailboxPlan + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets the Send and Receive limits for new users. Valid values are 1MB to 150MB + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) # Input validation @@ -62,3 +88,7 @@ function Invoke-CIPPStandardSendReceiveLimitTenant { Add-CIPPBPAField -FieldName 'SendReceiveLimit' -FieldValue $NotSetCorrectly -StoreAs json -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 index f6605904777f..3fe19fbad3c4 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 @@ -2,7 +2,34 @@ function Invoke-CIPPStandardShortenMeetings { <# .FUNCTIONALITY Internal + .APINAME + ShortenMeetings + .CAT + Exchange Standards + .TAG + "mediumimpact" + .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. + .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} + .LABEL + Set shorten meetings state + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Set-OrganizationConfig -ShortenEventScopeDefault -DefaultMinutesToReduceShortEventsBy -DefaultMinutesToReduceLongEventsBy + .RECOMMENDEDBY + .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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) # Input validation @@ -55,3 +82,7 @@ function Invoke-CIPPStandardShortenMeetings { } } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 index af9e03c6122c..17e4762e44f8 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 @@ -2,7 +2,36 @@ function Invoke-CIPPStandardSpoofWarn { <# .FUNCTIONALITY Internal + .APINAME + SpoofWarn + .CAT + Exchange Standards + .TAG + "lowimpact" + "CIS" + .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) + .ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.SpoofWarn.state","values":[{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}]} + .LABEL + Enable or disable 'external' warning in Outlook + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + et-ExternalInOutlook –Enabled $true or $false + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Adds or removes indicators to e-mail messages received from external senders in Outlook. Works on all Outlook clients/OWA + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-ExternalInOutlook') @@ -45,3 +74,7 @@ function Invoke-CIPPStandardSpoofWarn { } } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 index ef315d877530..fc53152bb426 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTAP.ps1 @@ -2,7 +2,34 @@ function Invoke-CIPPStandardTAP { <# .FUNCTIONALITY Internal + .APINAME + TAP + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + .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. + .ADDEDCOMPONENT + {"type":"Select","label":"Select TAP Lifetime","name":"standards.TAP.config","values":[{"label":"Only Once","value":"true"},{"label":"Multiple Logons","value":"false"}]} + .LABEL + Enable Temporary Access Passwords + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + .RECOMMENDEDBY + .DOCSDESCRIPTION + 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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/TemporaryAccessPass' -tenantid $Tenant @@ -34,3 +61,7 @@ function Invoke-CIPPStandardTAP { } } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 index 615dd6e94237..7945d9e4a2f3 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 @@ -2,7 +2,32 @@ function Invoke-CIPPStandardTeamsMeetingsByDefault { <# .FUNCTIONALITY Internal + .APINAME + TeamsMeetingsByDefault + .CAT + Exchange Standards + .TAG + "lowimpact" + .HELPTEXT + Sets the default state for automatically turning meetings into Teams meetings for the tenant. This can be overridden by the user in Outlook. + .ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.TeamsMeetingsByDefault.state","values":[{"label":"Enabled","value":"true"},{"label":"Disabled","value":"false"}]} + .LABEL + Set Teams Meetings by default state + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-OrganizationConfig -OnlineMeetingsByDefaultEnabled + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets the default state for automatically turning meetings into Teams meetings for the tenant. This can be overridden by the user in Outlook. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentState = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').OnlineMeetingsByDefaultEnabled @@ -45,3 +70,7 @@ function Invoke-CIPPStandardTeamsMeetingsByDefault { } } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 index c2651ae346a9..180b8323ca23 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 @@ -2,8 +2,33 @@ function Invoke-CIPPStandardTenantDefaultTimezone { <# .FUNCTIONALITY Internal + .APINAME + TenantDefaultTimezone + .CAT + SharePoint Standards + .TAG + "lowimpact" + .HELPTEXT + Sets the default timezone for the tenant. This will be used for all new users and sites. + .ADDEDCOMPONENT + {"type":"TimezoneSelect","name":"standards.TenantDefaultTimezone.Timezone","label":"Timezone"} + .LABEL + Set Default Timezone for Tenant + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgBetaAdminSharepointSetting + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets the default timezone for the tenant. This will be used for all new users and sites. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentState = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true @@ -43,3 +68,7 @@ function Invoke-CIPPStandardTenantDefaultTimezone { } } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUndoOauth.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUndoOauth.ps1 index 51cef2225307..f662a8f200f3 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUndoOauth.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUndoOauth.ps1 @@ -2,7 +2,31 @@ function Invoke-CIPPStandardUndoOauth { <# .FUNCTIONALITY Internal + .APINAME + UndoOauth + .CAT + Entra (AAD) Standards + .TAG + "highimpact" + .HELPTEXT + Disables App consent and set to Allow user consent for apps + .ADDEDCOMPONENT + .LABEL + Undo App Consent Standard + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + Update-MgPolicyAuthorizationPolicy + .RECOMMENDEDBY + .DOCSDESCRIPTION + Disables App consent and set to Allow user consent for apps + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 } @@ -36,3 +60,7 @@ function Invoke-CIPPStandardUndoOauth { Add-CIPPBPAField -FieldName 'UndoOauth' -FieldValue $State -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1 index 670da0608743..98466f02cec4 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserReportDestinationEmail.ps1 @@ -1,8 +1,31 @@ -function Invoke-CIPPStandardUserReportDestinationEmail { +function Invoke-CIPPStandardUserReportDestinationEmail { <# .FUNCTIONALITY Internal + .APINAME + UserReportDestinationEmail + .CAT + Exchange Standards + .TAG + "mediumimpact" + .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'. + .ADDEDCOMPONENT + {"type":"input","name":"standards.UserReportDestinationEmail.Email","label":"Destination email address"} + .LABEL + Set the destination email for user reported emails + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + New-ReportSubmissionRule or Set-ReportSubmissionRule + .RECOMMENDEDBY + .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'. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + param($Tenant, $Settings) # Input validation @@ -52,3 +75,5 @@ 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..4e1c15e55651 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 @@ -2,7 +2,34 @@ function Invoke-CIPPStandardUserSubmissions { <# .FUNCTIONALITY Internal + .APINAME + UserSubmissions + .CAT + Exchange Standards + .TAG + "mediumimpact" + .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. + .ADDEDCOMPONENT + {"type":"Select","label":"Select value","name":"standards.UserSubmissions.state","values":[{"label":"Enabled","value":"enable"},{"label":"Disabled","value":"disable"}]} + .LABEL + Set the state of the built-in Report button in Outlook + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + New-ReportSubmissionPolicy or Set-ReportSubmissionPolicy + .RECOMMENDEDBY + .DOCSDESCRIPTION + Set the state of the spam submission button in Outlook + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $Policy = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-ReportSubmissionPolicy' @@ -73,3 +100,7 @@ function Invoke-CIPPStandardUserSubmissions { } } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOAuthTokens.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOAuthTokens.ps1 index a5f43f175998..1c26284c9315 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOAuthTokens.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOAuthTokens.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardallowOAuthTokens { <# .FUNCTIONALITY Internal + .APINAME + allowOAuthTokens + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Enable OTP Software OAuth tokens + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + .RECOMMENDEDBY + .DOCSDESCRIPTION + Allows you to use any software OAuth token generator + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/softwareOath' -tenantid $Tenant @@ -39,3 +65,7 @@ function Invoke-CIPPStandardallowOAuthTokens { } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOTPTokens.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOTPTokens.ps1 index 8459fce1aadc..57dd7c7adb49 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOTPTokens.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardallowOTPTokens.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardallowOTPTokens { <# .FUNCTIONALITY Internal + .APINAME + allowOTPTokens + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Enable OTP via Authenticator + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + .RECOMMENDEDBY + .DOCSDESCRIPTION + Allows you to use MS authenticator OTP token generator + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator' -tenantid $Tenant @@ -27,3 +53,7 @@ function Invoke-CIPPStandardallowOTPTokens { } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 index 491d35ab7eb5..e716d72e8651 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 @@ -2,7 +2,36 @@ function Invoke-CIPPStandardcalDefault { <# .FUNCTIONALITY Internal + .APINAME + calDefault + .CAT + Exchange Standards + .TAG + "lowimpact" + .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) + .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"}]} + .LABEL + Set Sharing Level for Default calendar + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-MailboxFolderPermission + .RECOMMENDEDBY + .DOCSDESCRIPTION + Sets the default sharing level for the default calendar, for all users + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings, $QueueItem) # Input validation @@ -77,3 +106,7 @@ function Invoke-CIPPStandardcalDefault { Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully set default calendar permissions for $SuccessCounter out of $TotalMailboxes mailboxes." -sev Info } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 index b096ade25384..42f8977f066e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 @@ -2,7 +2,31 @@ function Invoke-CIPPStandarddisableMacSync { <# .FUNCTIONALITY Internal + .APINAME + disableMacSync + .CAT + SharePoint Standards + .TAG + "highimpact" + .HELPTEXT + Disables the ability for Mac devices to sync with OneDrive. + .ADDEDCOMPONENT + .LABEL + Do not allow Mac devices to sync using OneDrive + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + Update-MgAdminSharepointSetting + .RECOMMENDEDBY + .DOCSDESCRIPTION + Disables the ability for Mac devices to sync with OneDrive. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true @@ -36,3 +60,7 @@ function Invoke-CIPPStandarddisableMacSync { Add-CIPPBPAField -FieldName 'MacSync' -FieldValue $CurrentInfo.isMacSyncAppEnabled -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 index c049ebb95749..1a15822c6e38 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 @@ -1,8 +1,42 @@ function Invoke-CIPPStandardintuneBrandingProfile { - <# - .FUNCTIONALITY - Internal - #> + <# + .FUNCTIONALITY + Internal + .APINAME + intuneBrandingProfile + .CAT + Intune Standards + .TAG + "lowimpact" + .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. + .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"} + .LABEL + Set Intune Company Portal branding profile + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Graph API + .RECOMMENDEDBY + .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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + + + param($Tenant, $Settings) $CurrentState = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/deviceManagement/intuneBrandingProfiles/c3a59481-1bf2-46ce-94b3-66eec07a8d60/' -tenantid $Tenant -AsApp $true @@ -65,3 +99,7 @@ function Invoke-CIPPStandardintuneBrandingProfile { Add-CIPPBPAField -FieldName 'intuneBrandingProfile' -FieldValue [bool]$StateIsCorrect -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 index e261dbfb859c..9dbcdbba602c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 @@ -2,7 +2,32 @@ function Invoke-CIPPStandardintuneDeviceReg { <# .FUNCTIONALITY Internal + .APINAME + intuneDeviceReg + .CAT + Intune Standards + .TAG + "mediumimpact" + .HELPTEXT + sets the maximum number of devices that can be registered by a user. A value of 0 disables device registration by users + .ADDEDCOMPONENT + {"type":"number","name":"standards.intuneDeviceReg.max","label":"Maximum devices (Enter 2147483647 for unlimited.)"} + .LABEL + Set Maximum Number of Devices per user + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyDeviceRegistrationPolicy + .RECOMMENDEDBY + .DOCSDESCRIPTION + sets the maximum number of devices that can be registered by a user. A value of 0 disables device registration by users + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + 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 } @@ -38,3 +63,7 @@ function Invoke-CIPPStandardintuneDeviceReg { Add-CIPPBPAField -FieldName 'intuneDeviceReg' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 index b150c84e2f0a..1ea419e2639d 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 @@ -2,7 +2,32 @@ function Invoke-CIPPStandardintuneDeviceRetirementDays { <# .FUNCTIONALITY Internal + .APINAME + intuneDeviceRetirementDays + .CAT + Intune Standards + .TAG + "lowimpact" + .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. + .ADDEDCOMPONENT + {"type":"number","name":"standards.intuneDeviceRetirementDays.days","label":"Maximum days (0 equals disabled)"} + .LABEL + Set inactive device retirement days + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Graph API + .RECOMMENDEDBY + .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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/deviceManagement/managedDeviceCleanupSettings' -tenantid $Tenant) @@ -38,3 +63,7 @@ function Invoke-CIPPStandardintuneDeviceRetirementDays { Add-CIPPBPAField -FieldName 'intuneDeviceRetirementDays' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneRequireMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneRequireMFA.ps1 index 9698085e6cb7..79c0d352d1c9 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneRequireMFA.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneRequireMFA.ps1 @@ -2,7 +2,30 @@ function Invoke-CIPPStandardintuneRequireMFA { <# .FUNCTIONALITY Internal + .APINAME + intuneRequireMFA + .CAT + Intune Standards + .TAG + "mediumimpact" + .HELPTEXT + Requires MFA for all users to register devices with Intune. This is useful when not using Conditional Access. + .LABEL + Require Multifactor Authentication to register or join devices with Microsoft Entra + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Update-MgBetaPolicyDeviceRegistrationPolicy + .RECOMMENDEDBY + .DOCSDESCRIPTION + Requires MFA for all users to register devices with Intune. This is useful when not using Conditional Access. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $PreviousSetting = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy' -tenantid $Tenant @@ -37,3 +60,7 @@ function Invoke-CIPPStandardintuneRequireMFA { Add-CIPPBPAField -FieldName 'intuneRequireMFA' -FieldValue $RequireMFA -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardlaps.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardlaps.ps1 index 84f24cffa73d..b159a5d5796f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardlaps.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardlaps.ps1 @@ -2,7 +2,33 @@ function Invoke-CIPPStandardlaps { <# .FUNCTIONALITY Internal + .APINAME + laps + .CAT + Entra (AAD) Standards + .TAG + "lowimpact" + .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. + .ADDEDCOMPONENT + .LABEL + Enable LAPS on the tenant + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Portal or Graph API + .RECOMMENDEDBY + .DOCSDESCRIPTION + 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. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $PreviousSetting = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy' -tenantid $Tenant @@ -36,3 +62,7 @@ function Invoke-CIPPStandardlaps { Add-CIPPBPAField -FieldName 'laps' -FieldValue $PreviousSetting.localAdminPassword.isEnabled -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 index 95dfcec5a26d..1f0f3f7e9f5d 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 @@ -2,7 +2,34 @@ function Invoke-CIPPStandardsharingCapability { <# .FUNCTIONALITY Internal + .APINAME + sharingCapability + .CAT + SharePoint Standards + .TAG + "highimpact" + "CIS" + .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 + .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"}]} + .LABEL + Set Sharing Level for OneDrive and Sharepoint + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + Update-MgBetaAdminSharepointSetting + .RECOMMENDEDBY + "CIS" + .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 + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true @@ -43,3 +70,7 @@ function Invoke-CIPPStandardsharingCapability { } } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 index 9c7e7d11f555..c148a249f8e0 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 @@ -1,8 +1,35 @@ function Invoke-CIPPStandardsharingDomainRestriction { - <# - .FUNCTIONALITY - Internal - #> + <# + .FUNCTIONALITY + Internal + .APINAME + sharingDomainRestriction + .CAT + SharePoint Standards + .TAG + "highimpact" + "CIS" + .HELPTEXT + Restricts sharing to only users with the specified domain. This is useful for organizations that only want to share with their own domain. + .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"} + .LABEL + Restrict sharing to a specific domain + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + Update-MgAdminSharepointSetting + .RECOMMENDEDBY + .DOCSDESCRIPTION + Restricts sharing to only users with the specified domain. This is useful for organizations that only want to share with their own domain. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + + + param($Tenant, $Settings) $CurrentState = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true @@ -61,3 +88,7 @@ function Invoke-CIPPStandardsharingDomainRestriction { Add-CIPPBPAField -FieldName 'sharingDomainRestriction' -FieldValue [bool]$StateIsCorrect -StoreAs bool -Tenant $tenant } } + + + + diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 index 8a234d8eff2a..cc7f13e36fa8 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 @@ -2,7 +2,31 @@ function Invoke-CIPPStandardunmanagedSync { <# .FUNCTIONALITY Internal + .APINAME + unmanagedSync + .CAT + SharePoint Standards + .TAG + "highimpact" + .HELPTEXT + The unmanaged Sync standard has been temporarily disabled and does nothing. + .ADDEDCOMPONENT + .LABEL + Only allow users to sync OneDrive from AAD joined devices + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + Update-MgAdminSharepointSetting + .RECOMMENDEDBY + .DOCSDESCRIPTION + The unmanaged Sync standard has been temporarily disabled and does nothing. + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + + + + param($Tenant, $Settings) $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true @@ -35,3 +59,7 @@ function Invoke-CIPPStandardunmanagedSync { Add-CIPPBPAField -FieldName 'unmanagedSync' -FieldValue $CurrentInfo.isUnmanagedSyncAppForTenantRestricted -StoreAs bool -Tenant $tenant } } + + + + From 1907482c92ca0221f0d790221a64ea504386bb40 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 3 Jul 2024 11:39:56 +0200 Subject: [PATCH 030/104] fix patching issue --- Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 | 2 ++ .../CIPPCore/Public/Standards/Invoke-CIPPStandardAPConfig.ps1 | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 index 07f2646a7243..e685491b262d 100644 --- a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 @@ -59,6 +59,8 @@ function Set-CIPPDefaultAPDeploymentProfile { Write-LogMessage -user $request.headers.'x-ms-client-principal' -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 } 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 From e40a1280a72ee911b141c4df7b84ce759a127e24 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 3 Jul 2024 12:47:19 +0200 Subject: [PATCH 031/104] fixes updates to groups. --- .../Invoke-CIPPStandardGroupTemplate.ps1 | 56 +++++++++++++------ 1 file changed, 38 insertions(+), 18 deletions(-) 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 { From a5f0476e0feefd21935b94159b87ade35945abb3 Mon Sep 17 00:00:00 2001 From: rvdwegen Date: Wed, 3 Jul 2024 22:27:38 +0200 Subject: [PATCH 032/104] Adding self-service license management Standard --- Cache_SAMSetup/SAMManifest.json | 6 ++ Modules/CIPPCore/Public/SAMManifest.json | 6 ++ ...CIPPStandardDisableSelfServiceLicenses.ps1 | 69 +++++++++++++++++-- 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/Cache_SAMSetup/SAMManifest.json b/Cache_SAMSetup/SAMManifest.json index 6b1f6429af88..e2a457b734fb 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": [ diff --git a/Modules/CIPPCore/Public/SAMManifest.json b/Modules/CIPPCore/Public/SAMManifest.json index d545a87d25a5..7316e34c2246 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": [ diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 index 147aa0fefe89..fa8b5cb537e2 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 @@ -9,28 +9,87 @@ function Invoke-CIPPStandardDisableSelfServiceLicenses { .TAG "mediumimpact" .HELPTEXT - This standard currently does not function and can be safely disabled + This standard disables all self service licenses and enables all exclusions .ADDEDCOMPONENT .LABEL Disable Self Service Licensing .IMPACT Medium Impact .POWERSHELLEQUIVALENT - Set-MsolCompanySettings -AllowAdHocSubscriptions $false + Update-MSCommerceProductPolicy -PolicyId AllowSelfServicePurchase -ProductId {productId} -Value "Disabled" .RECOMMENDEDBY .DOCSDESCRIPTION - This standard currently does not function and can be safely disabled + This standard disables all self service licenses and enables all exclusions .UPDATECOMMENTBLOCK Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + param($Tenant, $Settings) + #Write-LogMessage -API 'Standards' -tenant $tenant -message 'Self Service Licenses cannot be disabled' -sev Error + try { + $selfServiceItems = (New-GraphGETRequest -scope "aeb86249-8ea3-49e2-900b-54cc8e308f85/.default" -uri "https://licensing.m365.microsoft.com/v1.0/policies/AllowSelfServicePurchase/products" -tenantid $Tenant).items + #$selfServiceItems = (Invoke-RestMethod -Method GET -Uri "https://licensing.m365.microsoft.com/v1.0/policies/AllowSelfServicePurchase/products" -Headers $header).items + } catch { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to retrieve self service products: $($_.Exception.Message)" -sev Error + throw "Failed to retrieve self service products: $($_.Exception.Message)" + } + if ($settings.remediate) { + if ($settings.exclusions -like "*;*") { + $exclusions = $settings.Exclusions -split(';') + } else { + $exclusions = $settings.Exclusions -split(',') + } - param($Tenant, $Settings) + $selfServiceItems | ForEach-Object { + $body = $null + + if ($_.policyValue -eq "Enabled" -AND ($_.productId -in $exclusions)) { + # Self service is enabled on product and productId is in exclusions, skip + } + if ($_.policyValue -eq "Disabled" -AND ($_.productId -in $exclusions)) { + # Self service is disabled on product and productId is in exclusions, enable + $body = '{ "policyValue": "Enabled" }' + } + if ($_.policyValue -eq "Enabled" -AND ($_.productId -notin $exclusions)) { + # Self service is enabled on product and productId is NOT in exclusions, disable + $body = '{ "policyValue": "Disabled" }' + } + if ($_.policyValue -eq "Disabled" -AND ($_.productId -notin $exclusions)) { + # Self service is disabled on product and productId is NOT in exclusions, skip + } + + try { + if ($body) { + $product = $_ + New-GraphPOSTRequest -scope "aeb86249-8ea3-49e2-900b-54cc8e308f85/.default" -uri "https://licensing.m365.microsoft.com/v1.0/policies/AllowSelfServicePurchase/products/$($product.productId)" -tenantid $Tenant -body $body -type PUT + } + } catch { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set product status for $($product.productId) with body $($body) for reason: $($_.Exception.Message)" -sev Error + #Write-Error "Failed to disable product $($product.productName):$($_.Exception.Message)" + } + } + + if (!$exclusions) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'No exclusions set for self-service licenses, disabled all not excluded licenses for self-service.' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Exclusions present for self-service licenses, disabled all not excluded licenses for self-service.' -sev Info + } + } - Write-LogMessage -API 'Standards' -tenant $tenant -message 'Self Service Licenses cannot be disabled' -sev Error + if ($Settings.alert) { + $selfServiceItemsToAlert = $selfServiceItems | Where-Object { $_.policyValue -eq "Enabled"} + if (!$selfServiceItemsToAlert) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'All self-service licenses are disabled' -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'One or more self-service licenses are enabled' -sev Alert + } + } + if ($Settings.report -eq $true) { + #Add-CIPPBPAField -FieldName '????' -FieldValue "????" -StoreAs bool -Tenant $tenant + } } From 79b05a49d1e2801be23b780a40fbb98ecf05c3ef Mon Sep 17 00:00:00 2001 From: chase-vgo <168204519+chase-vgo@users.noreply.github.com> Date: Wed, 3 Jul 2024 15:40:11 -0500 Subject: [PATCH 033/104] Update Invoke-CIPPStandardEnableLitigationHold.ps1 --- .../Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 index 1de0a2315a13..ba3120814bc8 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 @@ -5,7 +5,7 @@ function Invoke-CIPPStandardEnableLitigationHold { #> param($Tenant, $Settings) - $MailboxesNoLitHold = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' -cmdparams @{ MailboxPlan = 'ExchangeOnlineEnterprise'; Filter = 'LitigationHoldEnabled -eq "False"'} + $MailboxesNoLitHold = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' -cmdparams @{ Filter = 'LitigationHoldEnabled -eq "False"'} | Where-Object {$_.PersistedCapabilities -contains "BPOS_S_DlpAddOn" -or $_.PersistedCapabilities -contains "BPOS_S_Enterprise"} If ($Settings.remediate -eq $true) { From 4c46034861ea48633611e6f74f69dffdcf6db790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 3 Jul 2024 22:45:50 +0200 Subject: [PATCH 034/104] Always said disable even if delete was chosen as the expireaction --- .../Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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..458656041088 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 @@ -156,7 +156,7 @@ Function Invoke-ExecJITAdmin { $DisableTaskBody = @{ TenantFilter = $Request.Body.TenantFilter - Name = "JIT Admin (disable): $Username" + Name = "JIT Admin ($($Request.Body.ExpireAction)): $Username" Command = @{ value = 'Set-CIPPUserJITAdmin' label = 'Set-CIPPUserJITAdmin' @@ -177,7 +177,7 @@ Function Invoke-ExecJITAdmin { ScheduledTime = $Request.Body.EndDate } Add-CIPPScheduledTask -Task $DisableTaskBody -hidden $false - $Results.Add("Scheduling JIT Admin disable task for $Username") + $Results.Add("Scheduling JIT Admin $($Request.Body.ExpireAction) task for $Username") $Body = @{ Results = @($Results) } From 2b721d4ac3c95245ce8848848fa866b01e6674bb Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 3 Jul 2024 17:11:53 -0400 Subject: [PATCH 035/104] Standardize Extension Output and Mappings --- .../Settings/Invoke-ExecExtensionMapping.ps1 | 18 +- .../Settings/Invoke-ExecExtensionSync.ps1 | 8 +- .../Settings/Invoke-ExecExtensionTest.ps1 | 8 +- .../Get-ExtensionMapping.ps1 | 15 + .../Set-ExtensionFieldMapping.ps1 | 24 ++ .../Public/Halo/Get-HaloMapping.ps1 | 26 +- .../Public/Halo/Set-HaloMapping.ps1 | 12 +- .../Public/Hudu/Get-HuduFieldMapping.ps1 | 68 ++++ .../Public/Hudu/Get-HuduMapping.ps1 | 14 +- .../Public/Hudu/Set-HuduMapping.ps1 | 6 +- .../Public/New-CippExtAlert.ps1 | 8 +- .../NinjaOne/Get-NinjaOneFieldMapping.ps1 | 136 ++++---- .../NinjaOne/Get-NinjaOneOrgMapping.ps1 | 21 +- .../NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 | 24 +- .../Invoke-NinjaOneExtensionScheduler.ps1 | 4 +- .../NinjaOne/Invoke-NinjaOneOrgMapping.ps1 | 12 +- .../Invoke-NinjaOneOrgMappingTenant.ps1 | 26 +- .../Public/NinjaOne/Invoke-NinjaOneSync.ps1 | 4 +- .../NinjaOne/Invoke-NinjaOneTenantSync.ps1 | 312 +++++++++--------- .../NinjaOne/Set-NinjaOneFieldMapping.ps1 | 14 +- .../NinjaOne/Set-NinjaOneOrgMapping.ps1 | 12 +- 21 files changed, 460 insertions(+), 312 deletions(-) create mode 100644 Modules/CippExtensions/Public/Extension Functions/Get-ExtensionMapping.ps1 create mode 100644 Modules/CippExtensions/Public/Extension Functions/Set-ExtensionFieldMapping.ps1 create mode 100644 Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 index 595bf587ef19..d8a5bfba10dc 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 @@ -20,36 +20,42 @@ Function Invoke-ExecExtensionMapping { if ($Request.Query.List) { switch ($Request.Query.List) { - 'Halo' { + 'HaloPSA' { $body = Get-HaloMapping -CIPPMapping $Table } - 'NinjaOrgs' { + 'NinjaOne' { $Body = Get-NinjaOneOrgMapping -CIPPMapping $Table } - 'NinjaFields' { + 'NinjaOneFields' { $Body = Get-NinjaOneFieldMapping -CIPPMapping $Table } 'Hudu' { $Body = Get-HuduMapping -CIPPMapping $Table } + 'HuduFields' { + $Body = Get-HuduFieldMapping -CIPPMapping $Table + } } } try { if ($Request.Query.AddMapping) { switch ($Request.Query.AddMapping) { - 'Halo' { + 'HaloPSA' { $body = Set-HaloMapping -CIPPMapping $Table -APIName $APIName -Request $Request } - 'NinjaOrgs' { + 'NinjaOne' { $Body = Set-NinjaOneOrgMapping -CIPPMapping $Table -APIName $APIName -Request $Request } - 'NinjaFields' { + 'NinjaOneFields' { $Body = Set-NinjaOneFieldMapping -CIPPMapping $Table -APIName $APIName -Request $Request -TriggerMetadata $TriggerMetadata } 'Hudu' { $Body = Set-HuduMapping -CIPPMapping $Table -APIName $APIName -Request $Request } + 'HuduFields' { + $Body = Set-ExtensionFieldMapping -CIPPMapping $Table -APIName $APIName -Request $Request -Extension 'Hudu' + } } } } catch { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionSync.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionSync.ps1 index 2f069988996a..9c090c91f094 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionSync.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionSync.ps1 @@ -23,7 +23,7 @@ Function Invoke-ExecExtensionSync { 'Gradient' { If ($Configuration.Gradient.enabled -and $Configuration.Gradient.BillingEnabled) { Push-OutputBinding -Name gradientqueue -Value 'LetsGo' - $Results = [pscustomobject]@{'Results' = 'Succesfully started Gradient Sync' } + $Results = [pscustomobject]@{'Results' = 'Successfully started Gradient Sync' } } } } @@ -40,8 +40,8 @@ Function Invoke-ExecExtensionSync { $Table = Get-CIPPTable -TableName NinjaOneSettings $CIPPMapping = Get-CIPPTable -TableName CippMapping - $Filter = "PartitionKey eq 'NinjaOrgsMapping'" - $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.NinjaOne -and $_.NinjaOne -ne '' } + $Filter = "PartitionKey eq 'NinjaOneMapping'" + $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.IntegrationId -and $_.IntegrationId -ne '' } if ($Request.Query.TenantID) { $Tenant = $TenantsToProcess | Where-Object { $_.RowKey -eq $Request.Query.TenantID } @@ -59,7 +59,7 @@ Function Invoke-ExecExtensionSync { $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress) Write-Host "Started permissions orchestration with ID = '$InstanceId'" - $Results = [pscustomobject]@{'Results' = "NinjaOne Synchronization Queued for $($Tenant.NinjaOneName)" } + $Results = [pscustomobject]@{'Results' = "NinjaOne Synchronization Queued for $($Tenant.IntegrationName)" } } else { $Results = [pscustomobject]@{'Results' = 'Tenant was not found.' } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 index 74b188742a97..e9a6465c4ff0 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionTest.ps1 @@ -27,7 +27,7 @@ Function Invoke-ExecExtensionTest { if ($ExistingIntegrations.Status -ne 'active') { $ActivateRequest = Invoke-RestMethod -Uri 'https://app.usegradient.com/api/vendor-api/organization/status/active' -Method PATCH -Headers $GradientToken } - $Results = [pscustomobject]@{'Results' = 'Succesfully Connected to Gradient' } + $Results = [pscustomobject]@{'Results' = 'Successfully Connected to Gradient' } } 'CIPP-API' { @@ -35,13 +35,13 @@ Function Invoke-ExecExtensionTest { } 'NinjaOne' { $token = Get-NinjaOneToken -configuration $Configuration.NinjaOne - $Results = [pscustomobject]@{'Results' = 'Succesfully Connected to NinjaOne' } + $Results = [pscustomobject]@{'Results' = 'Successfully Connected to NinjaOne' } } 'PWPush' { $Payload = 'This is a test from CIPP' $PasswordLink = New-PwPushLink -Payload $Payload if ($PasswordLink) { - $Results = [pscustomobject]@{'Results' = 'Succesfully generated PWPush'; 'Link' = $PasswordLink } + $Results = [pscustomobject]@{'Results' = 'Successfully generated PWPush'; 'Link' = $PasswordLink } } else { $Results = [pscustomobject]@{'Results' = 'PWPush is not enabled' } } @@ -50,7 +50,7 @@ Function Invoke-ExecExtensionTest { Connect-HuduAPI -configuration $Configuration.Hudu $Version = Get-HuduAppInfo Write-Host ($Version | ConvertTo-Json) - $Results = [pscustomobject]@{'Results' = ('Succesfully Connected to Hudu, version: {0}' -f $Version.version) } + $Results = [pscustomobject]@{'Results' = ('Successfully Connected to Hudu, version: {0}' -f $Version.version) } } } } catch { diff --git a/Modules/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/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/Halo/Get-HaloMapping.ps1 b/Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1 index fcae99cfd5d1..2a8aae7646ef 100644 --- a/Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1 +++ b/Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1 @@ -6,16 +6,28 @@ function Get-HaloMapping { #Get available mappings $Mappings = [pscustomobject]@{} + # Migrate legacy mappings $Filter = "PartitionKey eq 'Mapping'" - Get-CIPPAzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object { - $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.HaloPSAName)"; value = "$($_.HaloPSA)" } + $MigrateRows = Get-CIPPAzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object { + [PSCustomObject]@{ + PartitionKey = 'HaloMapping' + RowKey = $_.RowKey + IntegrationId = $_.HaloPSA + IntegrationName = $_.HaloPSAName + } + Remove-AzDataTableEntity @CIPPMapping -Entity $_ | Out-Null + } + if (($MigrateRows | Measure-Object).Count -gt 0) { + Add-CIPPAzDataTableEntity @CIPPMapping -Entity $MigrateRows -Force } + + $Mappings = Get-ExtensionMapping -Extension 'Halo' + $Tenants = Get-Tenants -IncludeErrors $Table = Get-CIPPTable -TableName Extensionsconfig try { $Configuration = ((Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -ea stop).HaloPSA - $Token = Get-HaloToken -configuration $Configuration $i = 1 $RawHaloClients = do { @@ -32,7 +44,7 @@ function Get-HaloMapping { } Write-LogMessage -Message "Could not get HaloPSA Clients, error: $Message " -Level Error -tenant 'CIPP' -API 'HaloMapping' - $RawHaloClients = @(@{name = "Could not get HaloPSA Clients, error: $Message"; value = '-1' }) + $RawHaloClients = @(@{name = "Could not get HaloPSA Clients, error: $Message"; id = '-1' }) } $HaloClients = $RawHaloClients | ForEach-Object { [PSCustomObject]@{ @@ -41,9 +53,9 @@ function Get-HaloMapping { } } $MappingObj = [PSCustomObject]@{ - Tenants = @($Tenants) - HaloClients = @($HaloClients) - Mappings = $Mappings + Tenants = @($Tenants) + Companies = @($HaloClients) + Mappings = $Mappings } return $MappingObj diff --git a/Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1 b/Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1 index 527bbc94fd22..129b1578ad59 100644 --- a/Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1 +++ b/Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1 @@ -5,20 +5,20 @@ function Set-HaloMapping { $APIName, $Request ) - Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'Mapping'" | ForEach-Object { + Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'HaloMapping'" | ForEach-Object { Remove-AzDataTableEntity @CIPPMapping -Entity $_ } foreach ($Mapping in ([pscustomobject]$Request.body.mappings).psobject.properties) { $AddObject = @{ - PartitionKey = 'Mapping' - RowKey = "$($mapping.name)" - 'HaloPSA' = "$($mapping.value.value)" - 'HaloPSAName' = "$($mapping.value.label)" + PartitionKey = 'HaloMapping' + RowKey = "$($mapping.name)" + IntegrationId = "$($mapping.value.value)" + IntegrationName = "$($mapping.value.label)" } Add-CIPPAzDataTableEntity @CIPPMapping -Entity $AddObject -Force - Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "Added mapping for $($mapping.name)." -Sev 'Info' + Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "Added mapping for $($mapping.name)." -Sev 'Info' } $Result = [pscustomobject]@{'Results' = 'Successfully edited mapping table.' } diff --git a/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 new file mode 100644 index 000000000000..64b4700af62f --- /dev/null +++ b/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 @@ -0,0 +1,68 @@ +function Get-HuduFieldMapping { + [CmdletBinding()] + param ( + $CIPPMapping + ) + + $Mappings = Get-ExtensionMapping -Extension 'HuduFields' + + $CIPPFieldHeaders = @( + [PSCustomObject]@{ + Title = 'Hudu Asset Layouts' + FieldType = 'Layouts' + Description = 'Use the table below to map your Hudu Asset Layouts to the correct CIPP Field' + } + ) + $CIPPFields = @( + [PSCustomObject]@{ + FieldName = 'Users' + FieldLabel = 'Asset Layout for M365 Users' + FieldType = 'Layouts' + } + [PSCustomObject]@{ + FieldName = 'Devices' + FieldLabel = 'Asset Layout for M365 Devices' + FieldType = 'Layouts' + } + [PSCustomObject]@{ + FieldName = 'Licenses' + FieldLabel = 'Asset Layout for M365 Licenses' + FieldType = 'Layouts' + } + ) + + + $Tenants = Get-Tenants -IncludeErrors + $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 Companies, error: $Message " -Level Error -tenant 'CIPP' -API 'HuduMapping' + $HuduCompanies = @(@{name = "Could not get Hudu Companies, 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 + +} \ No newline at end of file diff --git a/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1 index cff35483ca7a..a8d775168cf7 100644 --- a/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1 @@ -3,13 +3,9 @@ function Get-HuduMapping { param ( $CIPPMapping ) - #Get available mappings - $Mappings = [pscustomobject]@{} - $Filter = "PartitionKey eq 'HuduMapping'" - Get-CIPPAzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object { - $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.HuduCompany)"; value = "$($_.HuduCompanyId)" } - } + $Mappings = Get-ExtensionMapping -Extension 'Hudu' + $Tenants = Get-Tenants -IncludeErrors $Table = Get-CIPPTable -TableName Extensionsconfig try { @@ -35,9 +31,9 @@ function Get-HuduMapping { } } $MappingObj = [PSCustomObject]@{ - Tenants = @($Tenants) - HuduCompanies = @($HuduCompanies) - Mappings = $Mappings + Tenants = @($Tenants) + Companies = @($HuduCompanies) + Mappings = $Mappings } return $MappingObj diff --git a/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 index a7ad9f8172b3..03c6dddb8fb3 100644 --- a/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 @@ -10,10 +10,10 @@ function Set-HuduMapping { } foreach ($Mapping in ([pscustomobject]$Request.body.mappings).psobject.properties) { $AddObject = @{ - PartitionKey = 'Mapping' + PartitionKey = 'HuduMapping' RowKey = "$($mapping.name)" - 'HuduCompanyId' = "$($mapping.value.value)" - 'HuduCompany' = "$($mapping.value.label)" + IntegrationId = "$($mapping.value.value)" + IntegrationName = "$($mapping.value.label)" } Add-CIPPAzDataTableEntity @CIPPMapping -Entity $AddObject -Force diff --git a/Modules/CippExtensions/Public/New-CippExtAlert.ps1 b/Modules/CippExtensions/Public/New-CippExtAlert.ps1 index 827347a613d2..21f5acf1923e 100644 --- a/Modules/CippExtensions/Public/New-CippExtAlert.ps1 +++ b/Modules/CippExtensions/Public/New-CippExtAlert.ps1 @@ -11,18 +11,18 @@ function New-CippExtAlert { $MappingFile = (Get-CIPPAzDataTableEntity @MappingTable) foreach ($ConfigItem in $Configuration.psobject.properties.name) { switch ($ConfigItem) { - "HaloPSA" { + 'HaloPSA' { If ($Configuration.HaloPSA.enabled) { $TenantId = (Get-Tenants | Where-Object defaultDomainName -EQ $Alert.TenantId).customerId Write-Host "TenantId: $TenantId" - $MappedId = ($MappingFile | Where-Object RowKey -EQ $TenantId).HaloPSA + $MappedId = ($MappingFile | Where-Object { $_.PartitionKey -eq 'HaloMapping' -and $_.RowKey -eq $TenantId }).IntegrationId Write-Host "MappedId: $MappedId" if (!$mappedId) { $MappedId = 1 } Write-Host "MappedId: $MappedId" - New-HaloPSATicket -Title $Alert.AlertTitle -Description $Alert.AlertText -Client $mappedId + New-HaloPSATicket -Title $Alert.AlertTitle -Description $Alert.AlertText -Client $mappedId } } - "Gradient" { + 'Gradient' { If ($Configuration.Gradient.enabled) { New-GradientAlert -Title $Alert.AlertTitle -Description $Alert.AlertText -Client $Alert.TenantId } diff --git a/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 index 8be773c2d030..e88f53ceba9c 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 @@ -7,93 +7,109 @@ function Get-NinjaOneFieldMapping { #Get available mappings $Mappings = [pscustomobject]@{} - [System.Collections.Generic.List[PSCustomObject]]$CIPPFields = @( + [System.Collections.Generic.List[object]]$CIPPFieldHeaders = @( [PSCustomObject]@{ - InternalName = 'TenantLinks' - Description = 'Microsoft 365 Tenant Links - Field Used to Display Links to Microsoft 365 Portals and CIPP' - Scope = 'Organization' - Type = 'WYSIWYG' + Title = 'NinjaOne Organization Global Custom Field Mapping' + FieldType = 'Organization' + Description = 'Use the table below to map your Organization Field to the correct NinjaOne Field' + } + [PSCustomObject]@{ + Title = 'NinjaOne Device Custom Field Mapping' + FieldType = 'Device' + Description = 'Use the table below to map your Device Field to the correct NinjaOne Field' + } + ) + + [System.Collections.Generic.List[object]]$CIPPFields = @( + [PSCustomObject]@{ + FieldName = 'TenantLinks' + FieldLabel = 'Microsoft 365 Tenant Links - Field Used to Display Links to Microsoft 365 Portals and CIPP' + FieldType = 'Organization' + Type = 'WYSIWYG' }, [PSCustomObject]@{ - InternalName = 'TenantSummary' - Description = 'Microsoft 365 Tenant Summary - Field Used to Display Tenant Summary Information' - Scope = 'Organization' - Type = 'WYSIWYG' + FieldName = 'TenantSummary' + FieldLabel = 'Microsoft 365 Tenant Summary - Field Used to Display Tenant Summary Information' + FieldType = 'Organization' + Type = 'WYSIWYG' }, [PSCustomObject]@{ - InternalName = 'UsersSummary' - Description = 'Microsoft 365 Users Summary - Field Used to Display User Summary Information' - Scope = 'Organization' - Type = 'WYSIWYG' + FieldName = 'UsersSummary' + FieldLabel = 'Microsoft 365 Users Summary - Field Used to Display User Summary Information' + FieldType = 'Organization' + Type = 'WYSIWYG' }, [PSCustomObject]@{ - InternalName = 'DeviceLinks' - Description = 'Microsoft 365 Device Links - Field Used to Display Links to Microsoft 365 Portals and CIPP' - Scope = 'Device' - Type = 'WYSIWYG' + FieldName = 'DeviceLinks' + FieldLabel = 'Microsoft 365 Device Links - Field Used to Display Links to Microsoft 365 Portals and CIPP' + FieldType = 'Device' + Type = 'WYSIWYG' }, [PSCustomObject]@{ - InternalName = 'DeviceSummary' - Description = 'Microsoft 365 Device Summary - Field Used to Display Device Summary Information' - Scope = 'Device' - Type = 'WYSIWYG' + FieldName = 'DeviceSummary' + FieldLabel = 'Microsoft 365 Device Summary - Field Used to Display Device Summary Information' + FieldType = 'Device' + Type = 'WYSIWYG' }, [PSCustomObject]@{ - InternalName = 'DeviceCompliance' - Description = 'Intune Device Compliance Status - Field Used to Monitor Device Compliance' - Scope = 'Device' - Type = 'TEXT' + FieldName = 'DeviceCompliance' + FieldLabel = 'Intune Device Compliance Status - Field Used to Monitor Device Compliance' + FieldType = 'Device' + Type = 'TEXT' } ) - $Filter = "PartitionKey eq 'NinjaFieldMapping'" - Get-AzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object { - $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.NinjaOneName)"; value = "$($_.NinjaOne)" } + $MappingFieldMigrate = Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'NinjaFieldMapping'" | ForEach-Object { + [PSCustomObject]@{ + PartitionKey = 'NinjaOneFieldMapping' + RowKey = $_.RowKey + IntegrationId = $_.NinjaOne + IntegrationName = $_.NinjaOneName + } + Remove-AzDataTableEntity @CIPPMapping -Entity $_ + } + if (($MappingFieldMigrate | Measure-Object).count -gt 0) { + Add-CIPPAzDataTableEntity @CIPPMapping -Entity $MappingFieldMigrate -Force } + $Mappings = Get-ExtensionMapping -Extension 'NinjaOneField' $Table = Get-CIPPTable -TableName Extensionsconfig $Configuration = ((Get-AzDataTableEntity @Table).config | ConvertFrom-Json -ea stop).NinjaOne - - $Token = Get-NinjaOneToken -configuration $Configuration - - $NinjaCustomFieldsNodeRaw = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/device-custom-fields?scopes=node" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -depth 100 - [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsNode = $NinjaCustomFieldsNodeRaw | Where-Object { $_.apiPermission -eq 'READ_WRITE' -and $_.type -in $CIPPFields.Type } | Select-Object @{n = 'name'; e = { $_.label } }, @{n = 'value'; e = { $_.name } }, type - - $NinjaCustomFieldsOrgRaw = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/device-custom-fields?scopes=organization" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -depth 100 - [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsOrg = $NinjaCustomFieldsOrgRaw | Where-Object { $_.apiPermission -eq 'READ_WRITE' -and $_.type -in $CIPPFields.Type } | Select-Object @{n = 'name'; e = { $_.label } }, @{n = 'value'; e = { $_.name } }, type - - if ($Null -eq $NinjaCustomFieldsNode){ - [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsNode = @() + + $NinjaCustomFieldsNodeRaw = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/device-custom-fields?scopes=node" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100 + + [System.Collections.Generic.List[object]]$NinjaCustomFieldsNode = $NinjaCustomFieldsNodeRaw | Where-Object { $_.apiPermission -eq 'READ_WRITE' -and $_.type -in $CIPPFields.Type } | Select-Object @{n = 'name'; e = { $_.label } }, @{n = 'value'; e = { $_.name } }, type, @{n = 'FieldType'; e = { 'Device' } } + + $NinjaCustomFieldsOrgRaw = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/device-custom-fields?scopes=organization" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100 + + [System.Collections.Generic.List[object]]$NinjaCustomFieldsOrg = $NinjaCustomFieldsOrgRaw | Where-Object { $_.apiPermission -eq 'READ_WRITE' -and $_.type -in $CIPPFields.Type } | Select-Object @{n = 'name'; e = { $_.label } }, @{n = 'value'; e = { $_.name } }, type, @{n = 'FieldType'; e = { 'Organization' } } + + if ($Null -eq $NinjaCustomFieldsNode) { + [System.Collections.Generic.List[object]]$NinjaCustomFieldsNode = @() } - - if ($Null -eq $NinjaCustomFieldsOrg){ - [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsOrg = @() + + if ($Null -eq $NinjaCustomFieldsOrg) { + [System.Collections.Generic.List[object]]$NinjaCustomFieldsOrg = @() + } + $Unset = [PSCustomObject]@{ + name = '--- Do not synchronize ---' + value = $null + type = 'unset' } - - } catch { - [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsNode = @() - [System.Collections.Generic.List[PSCustomObject]]$NinjaCustomFieldsOrg = @() - } - $DoNotSync = [PSCustomObject]@{ - name = '--- Do not synchronize ---' - value = $null - type = 'unset' + } catch { + [System.Collections.Generic.List[object]]$NinjaCustomFieldsNode = @() + [System.Collections.Generic.List[objecgt]]$NinjaCustomFieldsOrg = @() } - $NinjaCustomFieldsOrg.Insert(0, $DoNotSync) - $NinjaCustomFieldsNode.Insert(0, $DoNotSync) - - $MappingObj = [PSCustomObject]@{ - CIPPOrgFields = $CIPPFields | Where-Object { $_.Scope -eq 'Organization' } - CIPPNodeFields = @($CIPPFields | Where-Object { $_.Scope -eq 'Device' }) - NinjaOrgFields = @($NinjaCustomFieldsOrg) - NinjaNodeFields = @($NinjaCustomFieldsNode) - Mappings = $Mappings + CIPPFields = $CIPPFields + CIPPFieldHeaders = $CIPPFieldHeaders + IntegrationFields = @($Unset) + @($NinjaCustomFieldsOrg) + @($NinjaCustomFieldsNode) + Mappings = $Mappings } return $MappingObj diff --git a/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneOrgMapping.ps1 index d2d914c89589..24c7e6405560 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneOrgMapping.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneOrgMapping.ps1 @@ -4,14 +4,25 @@ function Get-NinjaOneOrgMapping { $CIPPMapping ) try { - #Get available mappings - $Mappings = [pscustomobject]@{} $Tenants = Get-Tenants -IncludeErrors $Filter = "PartitionKey eq 'NinjaOrgsMapping'" - Get-AzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object { - $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.NinjaOneName)"; value = "$($_.NinjaOne)" } + $MigrateRows = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object { + #$Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.NinjaOneName)"; value = "$($_.NinjaOne)" } + [PSCustomObject]@{ + RowKey = $_.RowKey + IntegrationName = $_.NinjaOneName + IntegrationId = $_.NinjaOne + PartitionKey = 'NinjaOneMapping' + } + Remove-AzDataTableEntity @CIPPMapping -Entity $_ } + + if (($MigrateRows | Measure-Object).Count -gt 0) { + Add-AzDataTableEntity @CIPPMapping -Entity $MigrateRows -Force + } + + $Mappings = Get-ExtensionMapping -Extension 'NinjaOne' #Get Available Tenants #Get available Ninja clients @@ -43,7 +54,7 @@ function Get-NinjaOneOrgMapping { $MappingObj = [PSCustomObject]@{ Tenants = @($Tenants) - NinjaOrgs = @($NinjaOrgs | Sort-Object name) + Companies = @($NinjaOrgs | Sort-Object name) Mappings = $Mappings } diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 index a8363917efcc..9213a7015b1a 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 @@ -8,9 +8,9 @@ function Invoke-NinjaOneDeviceWebhook { Write-LogMessage -user $ExecutingUser -API $APIName -message "Webhook Recieved - Updating NinjaOne Device compliance for $($Data.resourceData.id) in $($Data.tenantId)" -Sev 'Info' -tenant $TenantFilter $MappedFields = [pscustomobject]@{} $CIPPMapping = Get-CIPPTable -TableName CippMapping - $Filter = "PartitionKey eq 'NinjaFieldMapping'" - Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.NinjaOne -and $_.NinjaOne -ne '' } | ForEach-Object { - $MappedFields | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue $($_.NinjaOne) + $Filter = "PartitionKey eq 'NinjaOneFieldMapping'" + Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.IntegrationId -and $_.IntegrationId -ne '' } | ForEach-Object { + $MappedFields | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue $($_.IntegrationId) } if ($MappedFields.DeviceCompliance) { @@ -18,14 +18,14 @@ function Invoke-NinjaOneDeviceWebhook { $M365DeviceID = $Data.resourceData.id $DeviceM365 = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/devices/$($M365DeviceID)" -Tenantid $tenantfilter - + $DeviceFilter = "PartitionKey eq '$($tenantfilter)' and RowKey eq '$($DeviceM365.deviceID)'" $DeviceMapTable = Get-CippTable -tablename 'NinjaOneDeviceMap' $Device = Get-CIPPAzDataTableEntity @DeviceMapTable -Filter $DeviceFilter - + if (($Device | Measure-Object).count -eq 1) { - $Token = Get-NinjaOneToken -configuration $Configuration - + $Token = Get-NinjaOneToken -configuration $Configuration + if ($DeviceM365.isCompliant -eq $True) { $Compliant = 'Compliant' } else { @@ -37,16 +37,16 @@ function Invoke-NinjaOneDeviceWebhook { } | ConvertTo-Json $Null = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/device/$($Device.NinjaOneID)/custom-fields" -Method PATCH -Body $ComplianceBody -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json' - + Write-Host 'Updated NinjaOne Device Compliance' - + } else { Write-LogMessage -API 'NinjaOneSync' -user 'CIPP' -message "$($DeviceM365.displayName) ($($M365DeviceID)) was not matched in Ninja for $($tenantfilter)" -Sev 'Info' } } - + } catch { $Message = if ($_.ErrorDetails.Message) { Get-NormalizedError -Message $_.ErrorDetails.Message @@ -56,7 +56,7 @@ function Invoke-NinjaOneDeviceWebhook { Write-Error "Failed NinjaOne Device Webhook for: $($Data | ConvertTo-Json -Depth 100) Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $Message" Write-LogMessage -API 'NinjaOneSync' -user 'CIPP' -message "Failed NinjaOne Device Webhook Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $Message" -Sev 'Error' } - - + + } \ No newline at end of file diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 index 1faf7d92c833..ca69e5b10935 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 @@ -26,8 +26,8 @@ function Invoke-NinjaOneExtensionScheduler { Write-Host "Current Interval: $CurrentInterval" $CIPPMapping = Get-CIPPTable -TableName CippMapping - $Filter = "PartitionKey eq 'NinjaOrgsMapping'" - $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.NinjaOne -and $_.NinjaOne -ne '' } + $Filter = "PartitionKey eq 'NinjaOneMapping'" + $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.IntegrationId -and $_.IntegrationId -ne '' } if ($Null -eq $LastRunTime -or $LastRunTime -le (Get-Date).addhours(-25) -or $TimeSetting -eq $CurrentInterval) { Write-Host 'Executing' diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 index 6ea239b73e36..6b5687d6059f 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 @@ -9,9 +9,9 @@ function Invoke-NinjaOneOrgMapping { #Get available mappings $Mappings = [pscustomobject]@{} - $Filter = "PartitionKey eq 'NinjaOrgsMapping'" + $Filter = "PartitionKey eq 'NinjaOneMapping'" Get-AzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object { - $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.NinjaOneName)"; value = "$($_.NinjaOne)" } + $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.IntegrationName)"; value = "$($_.IntegrationId)" } } #Get Available Tenants @@ -81,10 +81,10 @@ function Invoke-NinjaOneOrgMapping { $MatchedM365Tenants.add($Tenant) $MatchedNinjaOrgs.add($MatchedOrg) $AddObject = @{ - PartitionKey = 'NinjaOrgsMapping' - RowKey = "$($Tenant.customerId)" - 'NinjaOne' = "$($MatchedOrg.id)" - 'NinjaOneName' = "$($MatchedOrg.name)" + PartitionKey = 'NinjaOneMapping' + RowKey = "$($Tenant.customerId)" + IntegrationId = "$($MatchedOrg.id)" + IntegrationName = "$($MatchedOrg.name)" } Add-AzDataTableEntity @CIPPMapping -Entity $AddObject -Force Write-LogMessage -API 'NinjaOneAutoMap_Queue' -user 'CIPP' -message "Added mapping from Organization name match for $($Tenant.customerId). to $($($MatchedOrg.name))" -Sev 'Info' diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 index 317770c3bb78..c3f05acf1cc3 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneOrgMappingTenant.ps1 @@ -14,7 +14,7 @@ function Invoke-NinjaOneOrgMappingTenant { $TenantFilter = $Tenant.customerId - $M365DevicesRaw = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/managedDevices" -Tenantid $tenantfilter + $M365DevicesRaw = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/managedDevices' -Tenantid $tenantfilter $M365Devices = foreach ($Device in $M365DevicesRaw) { [pscustomobject]@{ @@ -28,10 +28,10 @@ function Invoke-NinjaOneOrgMappingTenant { [System.Collections.Generic.List[PSCustomObject]]$MatchedDevices = @() # Match devices on serial - $DevicesToMatchSerial = $M365Devices | where-object { $null -ne $_.DeviceSerial } + $DevicesToMatchSerial = $M365Devices | Where-Object { $null -ne $_.DeviceSerial } foreach ($SerialMatchDevice in $DevicesToMatchSerial) { - $MatchedDevice = $NinjaDevices | where-object { $_.Serial -eq $SerialMatchDevice.DeviceSerial -or $_.BiosSerialNumber -eq $SerialMatchDevice.DeviceSerial } - if (($MatchedDevice | measure-object).count -eq 1) { + $MatchedDevice = $NinjaDevices | Where-Object { $_.Serial -eq $SerialMatchDevice.DeviceSerial -or $_.BiosSerialNumber -eq $SerialMatchDevice.DeviceSerial } + if (($MatchedDevice | Measure-Object).count -eq 1) { $Match = [pscustomobject]@{ M365 = $SerialMatchDevice Ninja = $MatchedDevice @@ -41,10 +41,10 @@ function Invoke-NinjaOneOrgMappingTenant { } # Try to match on Name - $DevicesToMatchName = $M365Devices | where-object { $_ -notin $MatchedDevices.M365 } + $DevicesToMatchName = $M365Devices | Where-Object { $_ -notin $MatchedDevices.M365 } foreach ($NameMatchDevice in $DevicesToMatchName) { - $MatchedDevice = $NinjaDevices | where-object { $_.SystemName -eq $NameMatchDevice.DeviceName -or $_.DNSName -eq $NameMatchDevice.DeviceName } - if (($MatchedDevice | measure-object).count -eq 1) { + $MatchedDevice = $NinjaDevices | Where-Object { $_.SystemName -eq $NameMatchDevice.DeviceName -or $_.DNSName -eq $NameMatchDevice.DeviceName } + if (($MatchedDevice | Measure-Object).count -eq 1) { $Match = [pscustomobject]@{ M365 = $NameMatchDevice Ninja = $MatchedDevice @@ -56,17 +56,17 @@ function Invoke-NinjaOneOrgMappingTenant { # Match on the Org with the most devices that match if (($MatchedDevices.Ninja.ID | Measure-Object).Count -eq 1) { - $MatchedOrgID = ($MatchedDevices.Ninja | group-object OrgID | sort-object Count -desc)[0].name + $MatchedOrgID = ($MatchedDevices.Ninja | Group-Object OrgID | Sort-Object Count -desc)[0].name $MatchedOrg = $NinjaOrgs | Where-Object { $_.id -eq $MatchedOrgID } $AddObject = @{ - PartitionKey = 'NinjaOrgsMapping' - RowKey = "$($Tenant.customerId)" - 'NinjaOne' = "$($MatchedOrg.id)" - 'NinjaOneName' = "$($MatchedOrg.name)" + PartitionKey = 'NinjaOneMapping' + RowKey = "$($Tenant.customerId)" + IntegrationId = "$($MatchedOrg.id)" + IntegrationName = "$($MatchedOrg.name)" } Add-AzDataTableEntity @CIPPMapping -Entity $AddObject -Force - Write-LogMessage -API 'NinjaOneAutoMap_Queue' -user 'CIPP' -message "Added mapping from Device match for $($Tenant.displayName) to $($($MatchedOrg.name))" -Sev 'Info' + Write-LogMessage -API 'NinjaOneAutoMap_Queue' -user 'CIPP' -message "Added mapping from Device match for $($Tenant.displayName) to $($($MatchedOrg.name))" -Sev 'Info' } diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneSync.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneSync.ps1 index 5567ddb7c1b8..c6fb732eb30a 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneSync.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneSync.ps1 @@ -3,8 +3,8 @@ function Invoke-NinjaOneSync { $Table = Get-CIPPTable -TableName NinjaOneSettings $CIPPMapping = Get-CIPPTable -TableName CippMapping - $Filter = "PartitionKey eq 'NinjaOrgsMapping'" - $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.NinjaOne -and $_.NinjaOne -ne '' } + $Filter = "PartitionKey eq 'NinjaOneMapping'" + $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.IntegrationId -and $_.IntegrationId -ne '' } $Batch = foreach ($Tenant in $TenantsToProcess) { diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 index 9828682c6348..f58cec70b4aa 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 @@ -6,13 +6,13 @@ function Invoke-NinjaOneTenantSync { try { $StartQueueTime = Get-Date Write-Host "$(Get-Date) - Starting NinjaOne Sync" - - # Stagger start + + # Stagger start # Check Global Rate Limiting - $CurrentMap = Get-ExtensionRateLimit -ExtensionName 'NinjaOne' -ExtensionPartitionKey 'NinjaOrgsMapping' -RateLimit 5 -WaitTime 10 + $CurrentMap = Get-ExtensionRateLimit -ExtensionName 'NinjaOne' -ExtensionPartitionKey 'NinjaOneMapping' -RateLimit 5 -WaitTime 10 $StartTime = Get-Date - + # Parse out the Tenant we are processing $MappedTenant = $QueueItem.MappedTenant @@ -21,7 +21,7 @@ function Invoke-NinjaOneTenantSync { $StartDate = try { Get-Date($CurrentItem.lastStartTime) } catch { $Null } $EndDate = try { Get-Date($CurrentItem.lastEndTime) } catch { $Null } - + if (($null -ne $CurrentItem.lastStartTime) -and ($StartDate -gt (Get-Date).AddMinutes(-10)) -and ( $Null -eq $CurrentItem.lastEndTime -or ($StartDate -gt $EndDate))) { Throw "NinjaOne Sync for Tenant $($MappedTenant.RowKey) is still running, please wait 10 minutes and try again." } @@ -40,19 +40,19 @@ function Invoke-NinjaOneTenantSync { $Table = Get-CIPPTable -TableName NinjaOneSettings $NinjaSettings = (Get-CIPPAzDataTableEntity @Table) $CIPPUrl = ($NinjaSettings | Where-Object { $_.RowKey -eq 'CIPPURL' }).SettingValue - - + + $Customer = Get-Tenants | Where-Object { $_.customerId -eq $MappedTenant.RowKey } 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/Public/NinjaOne/Set-NinjaOneFieldMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneFieldMapping.ps1 index 4653d51ed13a..87d243b8cda1 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneFieldMapping.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneFieldMapping.ps1 @@ -6,7 +6,7 @@ function Set-NinjaOneFieldMapping { $Request, $TriggerMetadata ) - + $SettingsTable = Get-CIPPTable -TableName NinjaOneSettings $AddObject = @{ PartitionKey = 'NinjaConfig' @@ -17,15 +17,15 @@ function Set-NinjaOneFieldMapping { foreach ($Mapping in ([pscustomobject]$Request.body.mappings).psobject.properties) { $AddObject = @{ - PartitionKey = 'NinjaFieldMapping' - RowKey = "$($mapping.name)" - 'NinjaOne' = "$($mapping.value.value)" - 'NinjaOneName' = "$($mapping.value.label)" + PartitionKey = 'NinjaOneFieldMapping' + RowKey = "$($mapping.name)" + IntegrationId = "$($mapping.value.value)" + IntegrationName = "$($mapping.value.label)" } Add-AzDataTableEntity @CIPPMapping -Entity $AddObject -Force - Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "Added mapping for $($mapping.name)." -Sev 'Info' + Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "Added mapping for $($mapping.name)." -Sev 'Info' } - $Result = [pscustomobject]@{'Results' = "Successfully edited mapping table." } + $Result = [pscustomobject]@{'Results' = 'Successfully edited mapping table.' } Return $Result } \ No newline at end of file diff --git a/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1 index ee09580b94bf..43b1c597e3b0 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1 @@ -6,18 +6,18 @@ function Set-NinjaOneOrgMapping { $Request ) - Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'NinjaOrgsMapping'" | ForEach-Object { + Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'NinjaOneMapping'" | ForEach-Object { Remove-AzDataTableEntity @CIPPMapping -Entity $_ } foreach ($Mapping in ([pscustomobject]$Request.body.mappings).psobject.properties) { $AddObject = @{ - PartitionKey = 'NinjaOrgsMapping' - RowKey = "$($mapping.name)" - 'NinjaOne' = "$($mapping.value.value)" - 'NinjaOneName' = "$($mapping.value.label)" + PartitionKey = 'NinjaOneMapping' + RowKey = "$($mapping.name)" + IntegrationId = "$($mapping.value.value)" + IntegrationName = "$($mapping.value.label)" } Add-AzDataTableEntity @CIPPMapping -Entity $AddObject -Force - Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "Added mapping for $($mapping.name)." -Sev 'Info' + Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "Added mapping for $($mapping.name)." -Sev 'Info' } $Result = [pscustomobject]@{'Results' = 'Successfully edited mapping table.' } From 9681e66e8e6188953eb77c016cf808cf237dd537 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 3 Jul 2024 17:12:29 -0400 Subject: [PATCH 036/104] Typos --- Durable_BECRun/run.ps1 | 6 +++--- .../CIPP/Settings/Invoke-ExecRestoreBackup.ps1 | 2 +- .../Administration/Invoke-ExecOffboardTenant.ps1 | 12 ++++++------ .../Administration/Invoke-ExecUpdateSecureScore.ps1 | 2 +- Modules/CIPPCore/Public/New-CIPPAPIConfig.ps1 | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Durable_BECRun/run.ps1 b/Durable_BECRun/run.ps1 index 377ca2c5533b..44eecff33d2f 100644 --- a/Durable_BECRun/run.ps1 +++ b/Durable_BECRun/run.ps1 @@ -10,7 +10,7 @@ Write-Host "Working on $UserName" try { $startDate = (Get-Date).AddDays(-7) $endDate = (Get-Date) - $auditLog = (New-ExoRequest -tenantid $Tenantfilter -cmdlet 'Get-AdminAuditLogConfig').UnifiedAuditLogIngestionEnabled + $auditLog = (New-ExoRequest -tenantid $Tenantfilter -cmdlet 'Get-AdminAuditLogConfig').UnifiedAuditLogIngestionEnabled $7dayslog = if ($auditLog -eq $false) { $ExtractResult = 'AuditLog is disabled. Cannot perform full analysis' } else { @@ -40,10 +40,10 @@ try { Write-Host "Retrieved $($logsTenant.count) logs" -ForegroundColor Yellow $logsTenant } while ($LogsTenant.count % 5000 -eq 0 -and $LogsTenant.count -ne 0) - $ExtractResult = 'Succesfully extracted logs from auditlog' + $ExtractResult = 'Successfully extracted logs from auditlog' } Try { - $URI = "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=(userId eq '$SuspectUser')&`$top=1&`$orderby=createdDateTime desc" + $URI = "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=(userId eq '$SuspectUser')&`$top=1&`$orderby=createdDateTime desc" $LastSignIn = New-GraphGetRequest -uri $URI -tenantid $TenantFilter -noPagination $true -verbose | Select-Object @{ Name = 'CreatedDateTime'; Expression = { $(($_.createdDateTime | Out-String) -replace '\r\n') } }, id, @{ Name = 'AppDisplayName'; Expression = { $_.resourceDisplayName } }, diff --git a/Modules/CIPPCore/Public/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/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/New-CIPPAPIConfig.ps1 b/Modules/CIPPCore/Public/New-CIPPAPIConfig.ps1 index 58231273000e..47d111209e0f 100644 --- a/Modules/CIPPCore/Public/New-CIPPAPIConfig.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPAPIConfig.ps1 @@ -51,7 +51,7 @@ function New-CIPPAPIConfig { Write-Host "writing to Azure" $SetAPIAuth = New-GraphPOSTRequest -type "PUT" -uri "https://management.azure.com/subscriptions/$($subscription)/resourceGroups/$ENV:WEBSITE_RESOURCE_GROUP/providers/Microsoft.Web/sites/$ENV:WEBSITE_SITE_NAME/Config/authsettingsV2?api-version=2018-11-01" -scope "https://management.azure.com/.default" -NoAuthCheck $true -body $currentBody $null = Set-AzKeyVaultSecret -VaultName $ENV:WEBSITE_DEPLOYMENT_ID -Name 'CIPPAPIAPP' -SecretValue (ConvertTo-SecureString -String $APIApp.AppID -AsPlainText -Force) - Write-LogMessage -user $ExecutingUser -API $APINAME -tenant 'None '-message "Succesfully setup CIPP-API Access." -Sev "info" + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant 'None '-message "Successfully setup CIPP-API Access." -Sev "info" } return @{ ApplicationID = $APIApp.AppId From 6921c77a026cfdacc5d3730038c2f5981088d2d7 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 3 Jul 2024 18:52:58 -0400 Subject: [PATCH 037/104] Reset graph error count on successful query --- Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1 | 1 + .../Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 index f58cec70b4aa..9a3b6949b056 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 @@ -42,7 +42,7 @@ function Invoke-NinjaOneTenantSync { $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' From d633ad7517b3ea2487d1778b9a8ba7931c6e5115 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 4 Jul 2024 23:44:09 +0200 Subject: [PATCH 038/104] added backups --- .../Scheduler/Invoke-ListScheduledItems.ps1 | 5 ++ .../Alerts/Invoke-ListAlertsQueue.ps1 | 2 +- Modules/CIPPCore/Public/New-CIPPBackup.ps1 | 52 ++++++++----------- .../CIPPCore/Public/New-CIPPBackupTask.ps1 | 48 +++++++++++++++++ 4 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 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/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/New-CIPPBackup.ps1 b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 index 477ea7c2e690..27a2fc9fe629 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 @@ -4,6 +4,7 @@ function New-CIPPBackup { $backupType, $StorageOutput = 'default', $TenantFilter, + $ScheduledBackupValues, $APIName = 'CIPP Backup', $ExecutingUser ) @@ -50,36 +51,27 @@ function New-CIPPBackup { } #If Backup type is ConditionalAccess, create Conditional Access backup. - 'ConditionalAccess' { - $ConditionalAccessPolicyOutput = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $tenantfilter - $AllNamedLocations = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations' -tenantid $tenantfilter - switch ($StorageOutput) { - 'default' { - [PSCustomObject]@{ - ConditionalAccessPolicies = $ConditionalAccessPolicyOutput - NamedLocations = $AllNamedLocations - } - } - 'table' { - #Store output in tablestorage for Recovery - $RowKey = $TenantFilter + '_' + (Get-Date).ToString('yyyy-MM-dd-HHmm') - $entity = [PSCustomObject]@{ - PartitionKey = 'ConditionalAccessBackup' - RowKey = $RowKey - TenantFilter = $TenantFilter - Policies = [string]($ConditionalAccessPolicyOutput | ConvertTo-Json -Compress -Depth 10) - NamedLocations = [string]($AllNamedLocations | ConvertTo-Json -Compress -Depth 10) - } - $Table = Get-CippTable -tablename 'ConditionalAccessBackup' - try { - $Result = Add-CIPPAzDataTableEntity @Table -entity $entity -Force - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Created backup for Conditional Access Policies' -Sev 'Debug' - $Result - } catch { - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup for Conditional Access Policies: $($_.Exception.Message)" -Sev 'Error' - [pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" } - } - } + 'Scheduled' { + #Do a sub switch here based on the ScheduledBackupValues? + #Store output in tablestorage for Recovery + $RowKey = $TenantFilter + '_' + (Get-Date).ToString('yyyy-MM-dd-HHmm') + $entity = @{ + PartitionKey = 'ScheduledBackup' + RowKey = $RowKey + TenantFilter = $TenantFilter + } + foreach ($ScheduledBackup in $ScheduledBackupValues.psobject.Properties.Name) { + $entity[$ScheduledBackup] = New-CIPPBackupTask -Task $ScheduledBackup -TenantFilter $TenantFilter + } + + $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 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)" } } } diff --git a/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 new file mode 100644 index 000000000000..d159c76d9bee --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 @@ -0,0 +1,48 @@ +function New-CIPPBackupTask { + [CmdletBinding()] + param ( + $ScheduledBackup, + $TenantFilter + ) + + $BackupData = switch ($ScheduledBackup) { + 'users' { + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $TenantFilter + } + 'groups' { + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $TenantFilter + } + 'ca' { + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter + } + 'namedlocations' { + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/namedLocations?$top=999' -tenantid $TenantFilter + } + 'authstrengths' { + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/authenticationStrength/policies' -tenantid $TenantFilter + } + 'intuneconfig' { + #alert + } + 'intunecompliance' {} + + 'intuneprotection' {} + + 'CippWebhookAlerts' { + $WebhookTable = Get-CIPPTable -TableName 'WebhookRules' + Get-CIPPAzDataTableEntity @WebhookTable | Where-Object { $TenantFilter -in ($_.Tenants | ConvertFrom-Json).fullvalue.defaultDomainName } + } + 'CippScriptedAlerts' { + $ScheduledTasks = Get-CIPPTable -TableName 'ScheduledTasks' + Get-CIPPAzDataTableEntity @ScheduledTasks | Where-Object { $_.hidden -eq $true -and $_.command -like 'Get-CippAlert*' -and $TenantFilter -in $_.Tenant } + } + 'CippStandards' { + $Table = Get-CippTable -tablename 'standards' + $Filter = "PartitionKey eq 'standards' and RowKey eq '$($TenantFilter)'" + (Get-CIPPAzDataTableEntity @Table -Filter $Filter) + } + + } + return $BackupData +} + From 169cf664421c19f0439424daf653fb9b99083cc9 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 5 Jul 2024 00:11:30 +0200 Subject: [PATCH 039/104] improvements createbackup --- Modules/CIPPCore/Public/New-CIPPBackup.ps1 | 3 ++- .../CIPPCore/Public/New-CIPPBackupTask.ps1 | 20 +++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 index 27a2fc9fe629..21e25cb7813a 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 @@ -60,10 +60,11 @@ function New-CIPPBackup { RowKey = $RowKey TenantFilter = $TenantFilter } + Write-Host "ScheduledBackupValues: $($ScheduledBackupValues | ConvertTo-Json -Compress -Depth 100)" + Write-Host "Scheduled backup value psproperties: $($ScheduledBackupValues.psobject.Properties.Name)" foreach ($ScheduledBackup in $ScheduledBackupValues.psobject.Properties.Name) { $entity[$ScheduledBackup] = New-CIPPBackupTask -Task $ScheduledBackup -TenantFilter $TenantFilter } - $Table = Get-CippTable -tablename 'ScheduledBackup' try { $Result = Add-CIPPAzDataTableEntity @Table -entity $entity -Force diff --git a/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 index d159c76d9bee..3bf2ff778156 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 @@ -1,25 +1,25 @@ function New-CIPPBackupTask { [CmdletBinding()] param ( - $ScheduledBackup, + $Task, $TenantFilter ) - $BackupData = switch ($ScheduledBackup) { + $BackupData = switch ($Task) { 'users' { - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $TenantFilter + $BackupData = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $TenantFilter } 'groups' { - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $TenantFilter + $BackupData = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $TenantFilter } 'ca' { - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter + $BackupData = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter } 'namedlocations' { - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/namedLocations?$top=999' -tenantid $TenantFilter + $BackupData = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/namedLocations?$top=999' -tenantid $TenantFilter } 'authstrengths' { - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/authenticationStrength/policies' -tenantid $TenantFilter + $BackupData = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/authenticationStrength/policies' -tenantid $TenantFilter } 'intuneconfig' { #alert @@ -30,16 +30,16 @@ function New-CIPPBackupTask { 'CippWebhookAlerts' { $WebhookTable = Get-CIPPTable -TableName 'WebhookRules' - Get-CIPPAzDataTableEntity @WebhookTable | Where-Object { $TenantFilter -in ($_.Tenants | ConvertFrom-Json).fullvalue.defaultDomainName } + $BackupData = Get-CIPPAzDataTableEntity @WebhookTable | Where-Object { $TenantFilter -in ($_.Tenants | ConvertFrom-Json).fullvalue.defaultDomainName } } 'CippScriptedAlerts' { $ScheduledTasks = Get-CIPPTable -TableName 'ScheduledTasks' - Get-CIPPAzDataTableEntity @ScheduledTasks | Where-Object { $_.hidden -eq $true -and $_.command -like 'Get-CippAlert*' -and $TenantFilter -in $_.Tenant } + $BackupData = Get-CIPPAzDataTableEntity @ScheduledTasks | Where-Object { $_.hidden -eq $true -and $_.command -like 'Get-CippAlert*' -and $TenantFilter -in $_.Tenant } } 'CippStandards' { $Table = Get-CippTable -tablename 'standards' $Filter = "PartitionKey eq 'standards' and RowKey eq '$($TenantFilter)'" - (Get-CIPPAzDataTableEntity @Table -Filter $Filter) + $BackupData = (Get-CIPPAzDataTableEntity @Table -Filter $Filter) } } From 6a5a37de6ad929c48e3417f76f1c85b33970ce5f Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 5 Jul 2024 12:58:45 +0200 Subject: [PATCH 040/104] fixes external sender --- .../Invoke-ListGroupSenderAuthentication.ps1 | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) 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 index 189dd39468b0..97ca6fe52147 100644 --- 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 @@ -6,8 +6,6 @@ Function Invoke-ListGroupSenderAuthentication { $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.' @@ -15,28 +13,34 @@ Function Invoke-ListGroupSenderAuthentication { $TenantFilter = $Request.Query.TenantFilter $groupid = $Request.query.groupid + $GroupType = $Request.query.Type $params = @{ Identity = $groupid } - Write-Host = "This is the group id $groupid" - Write-Host = "This is the tenant filter $TenantFilter" - $GroupType = Invoke-ListGroups -tenantFilter $TenantFilter -GroupID $groupid - Write-Host = "This is the group type $($GroupType.calculatedGroupType)" - + try { - $Request = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-DistributionGroup' -cmdParams $params -UseSystemMailbox $true - $StatusCode = [HttpStatusCode]::OK + 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 { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - $StatusCode = [HttpStatusCode]::Forbidden - $Request = $ErrorMessage + $state = $true } - # Associate values to output bindings by calling 'Push-OutputBinding'. + # 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 = $StatusCode - Body = @{ enabled = $Request.RequireSenderAuthenticationEnabled } + StatusCode = [HttpStatusCode]::OK + Body = @{ allowedToReceiveExternal = !$state } }) } \ No newline at end of file From 988c08cf139ae929af5a09511b4eea043ec47d70 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 5 Jul 2024 13:11:43 +0200 Subject: [PATCH 041/104] fixes issue if there are no known locations or apps. --- .../Invoke-ListConditionalAccessPolicies.ps1 | 10 ---------- 1 file changed, 10 deletions(-) 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') { From b323911fc4b4c03a6ce68f6e601aa8ad624b01e7 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 5 Jul 2024 08:46:31 -0400 Subject: [PATCH 042/104] Sharepoint functions --- Modules/CIPPCore/Public/Get-CIPPSPOTenant.ps1 | 27 ++++ .../Public/New-CIPPSharepointSite.ps1 | 145 ++++++++++++++++++ Modules/CIPPCore/Public/SAMManifest.json | 13 +- Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 | 88 +++++++++++ 4 files changed, 267 insertions(+), 6 deletions(-) create mode 100644 Modules/CIPPCore/Public/Get-CIPPSPOTenant.ps1 create mode 100644 Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 create mode 100644 Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 diff --git a/Modules/CIPPCore/Public/Get-CIPPSPOTenant.ps1 b/Modules/CIPPCore/Public/Get-CIPPSPOTenant.ps1 new file mode 100644 index 000000000000..80f6e83453aa --- /dev/null +++ b/Modules/CIPPCore/Public/Get-CIPPSPOTenant.ps1 @@ -0,0 +1,27 @@ +function Get-CIPPSPOTenant { + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true)] + [string]$TenantFilter, + [string]$SharepointPrefix + ) + + if (!$SharepointPrefix) { + # get sharepoint admin site + $tenantName = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/root' -asApp $true -tenantid $TenantFilter).id.Split('.')[0] + } else { + $tenantName = $SharepointPrefix + } + $AdminUrl = "https://$($tenantName)-admin.sharepoint.com" + + # Query tenant settings + $XML = @' + +'@ + $AdditionalHeaders = @{ + 'Accept' = 'application/json;odata=verbose' + } + $Results = New-GraphPostRequest -scope "$AdminURL/.default" -tenantid $TenantFilter -Uri "$AdminURL/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' -AddedHeaders $AdditionalHeaders + + $Results | Select-Object -Last 1 *, @{n = 'SharepointPrefix'; e = { $tenantName } }, @{n = 'TenantFilter'; e = { $TenantFilter } } +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 b/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 new file mode 100644 index 000000000000..ccf2e8b81b22 --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 @@ -0,0 +1,145 @@ +function New-CIPPSharepointSite { + <# + .SYNOPSIS + Create a new SharePoint site + + .DESCRIPTION + Create a new SharePoint site using the Modern REST API + + .PARAMETER SiteName + The name of the site + + .PARAMETER SiteDescription + The description of the site + + .PARAMETER SiteOwner + The username of the site owner + + .PARAMETER TemplateName + The template to use for the site. Default is Communication + + .PARAMETER SiteDesign + The design to use for the site. Default is Topic + + .PARAMETER WebTemplateExtensionId + The web template extension ID to use + + .PARAMETER SensitivityLabel + The Purview sensitivity label to apply to the site + + .PARAMETER TenantFilter + The tenant associated with the site + + #> + [CmdletBinding(SupportsShouldProcess = $true)] + Param( + [Parameter(Mandatory = $true)] + [string]$SiteName, + + [Parameter(Mandatory = $true)] + [string]$SiteDescription, + + [Parameter(Mandatory = $true)] + [string]$SiteOwner, + + [Parameter(Mandatory = $false)] + [ValidateSet('Communication', 'Team')] + [string]$TemplateName = 'Communication', + + [Parameter(Mandatory = $false)] + [ValidateSet('Topic', 'Showcase', 'Blank', 'Custom')] + [string]$SiteDesign = 'Showcase', + + [Parameter(Mandatory = $false)] + [ValidatePattern('(\{|\()?[A-Za-z0-9]{4}([A-Za-z0-9]{4}\-?){4}[A-Za-z0-9]{12}(\}|\()?')] + [string]$WebTemplateExtensionId, + + [Parameter(Mandatory = $false)] + [ValidatePattern('(\{|\()?[A-Za-z0-9]{4}([A-Za-z0-9]{4}\-?){4}[A-Za-z0-9]{12}(\}|\()?')] + [string]$SensitivityLabel, + + [string]$Classification, + + [Parameter(Mandatory = $true)] + [string]$TenantFilter + ) + $tenantName = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/root' -asApp $true -tenantid $TenantFilter).id.Split('.')[0] + $AdminUrl = "https://$($tenantName)-admin.sharepoint.com" + $SitePath = $SiteName -replace ' ' -replace '[^A-Za-z0-9-]' + $SiteUrl = "https://$tenantName.sharepoint.com/sites/$SitePath" + + + + + switch ($TemplateName) { + 'Communication' { + $WebTemplate = 'SITEPAGEPUBLISHING#0' + } + 'Team' { + $WebTemplate = 'STS#0' + } + } + + $WebTemplateExtensionId = '00000000-0000-0000-0000-000000000000' + $DefaultSiteDesignIds = @( '96c933ac-3698-44c7-9f4a-5fd17d71af9e', '6142d2a0-63a5-4ba0-aede-d9fefca2c767', 'f6cc5403-0d63-442e-96c0-285923709ffc') + + switch ($SiteDesign) { + 'Topic' { + $SiteDesignId = '96c933ac-3698-44c7-9f4a-5fd17d71af9e' + } + 'Showcase' { + $SiteDesignId = '6142d2a0-63a5-4ba0-aede-d9fefca2c767' + } + 'Blank' { + $SiteDesignId = 'f6cc5403-0d63-442e-96c0-285923709ffc' + } + 'Custom' { + if ($WebTemplateExtensionId -match '^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$') { + if ($WebTemplateExtensionId -notin $DefaultSiteDesignIds) { + $WebTemplateExtensionId = $SiteDesign + $SiteDesignId = '00000000-0000-0000-0000-000000000000' + } else { + $SiteDesignId = $WebTemplateExtensionId + } + } else { + $SiteDesignId = '96c933ac-3698-44c7-9f4a-5fd17d71af9e' + } + } + } + + # Create the request body + $Request = @{ + Title = $SiteName + Url = $SiteUrl + Lcid = 1033 + ShareByEmailEnabled = $false + Description = $SiteDescription + WebTemplate = $WebTemplate + SiteDesignId = $SiteDesignId + Owner = $SiteOwner + WebTemplateExtensionId = $WebTemplateExtensionId + } + + # Set the sensitivity label if provided + if ($SensitivityLabel) { + $Request.SensitivityLabel = $SensitivityLabel + } + if ($Classification) { + $Request.Classification = $Classification + } + + Write-Verbose ($Request | ConvertTo-Json -Compress -Depth 10) + + $body = @{ + request = $Request + } + + # Create the site + if ($PSCmdlet.ShouldProcess($SiteName, 'Create new SharePoint site')) { + $AddedHeaders = @{ + 'accept' = 'application/json;odata.metadata=none' + 'odata-version' = '4.0' + } + New-GraphPostRequest -scope "$AdminUrl/.default" -uri "$AdminUrl/_api/SPSiteManager/create" -Body ($body | ConvertTo-Json -Compress -Depth 10) -tenantid $TenantFilter -ContentType 'application/json' -AddedHeaders $AddedHeaders + } +} diff --git a/Modules/CIPPCore/Public/SAMManifest.json b/Modules/CIPPCore/Public/SAMManifest.json index 7316e34c2246..3d52dfeadcb1 100644 --- a/Modules/CIPPCore/Public/SAMManifest.json +++ b/Modules/CIPPCore/Public/SAMManifest.json @@ -12,11 +12,11 @@ }, "requiredResourceAccess": [ { - "resourceAppId": "aeb86249-8ea3-49e2-900b-54cc8e308f85", - "resourceAccess": [ - { "id": "fc946a4f-bc4d-413b-a090-b2c86113ec4f", "type": "Scope" } - ] - }, + "resourceAppId": "aeb86249-8ea3-49e2-900b-54cc8e308f85", + "resourceAccess": [ + { "id": "fc946a4f-bc4d-413b-a090-b2c86113ec4f", "type": "Scope" } + ] + }, { "resourceAppId": "fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd", "resourceAccess": [ @@ -151,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" } ] }, { diff --git a/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 b/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 new file mode 100644 index 000000000000..383a65a5b854 --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 @@ -0,0 +1,88 @@ +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') { $Properties[$Property] = $Properties[$Property].ToString().ToLower() } + $xml = @" + + $($Properties[$Property]) + +"@ + $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 + } + } +} \ No newline at end of file From 64cd24079b48b68af86c65da42dc312d2bfb2ef8 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 5 Jul 2024 09:54:07 -0400 Subject: [PATCH 043/104] Update Set-CIPPSPOTenant.ps1 --- Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 b/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 index 383a65a5b854..ad6a9b115321 100644 --- a/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 @@ -57,10 +57,14 @@ function Set-CIPPSPOTenant { # Get property type $PropertyType = $Properties[$Property].GetType().Name if ($PropertyType -in $AllowedTypes) { - if ($PropertyType -eq 'Boolean') { $Properties[$Property] = $Properties[$Property].ToString().ToLower() } + if ($PropertyType -eq 'Boolean') { + $PropertyToSet = $Properties[$Property].ToString().ToLower() + } else { + $PropertyToSet = $Properties[$Property] + } $xml = @" - $($Properties[$Property]) + $($PropertyToSet) "@ $SetProperty.Add($xml) @@ -85,4 +89,4 @@ function Set-CIPPSPOTenant { New-GraphPostRequest -scope "$AdminURL/.default" -tenantid $TenantFilter -Uri "$AdminURL/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' -AddedHeaders $AdditionalHeaders } } -} \ No newline at end of file +} From 93f0ebb22a7be64fdedb9f84ea84908359a02b18 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 5 Jul 2024 16:20:57 +0200 Subject: [PATCH 044/104] generate siteid --- .../MEM/Invoke-AddDefenderDeployment.ps1 | 18 +++---- .../Teams-Sharepoint/Invoke-ListSites.ps1 | 22 +++++++-- .../Invoke-AddTenantAllowBlockList.ps1 | 48 ++++++++++--------- 3 files changed, 50 insertions(+), 38 deletions(-) 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/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/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 } }) From ed2378f5708599714c201f9885f976cc88c54710 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 5 Jul 2024 12:57:49 -0400 Subject: [PATCH 045/104] CPV tweaks --- .../Public/Add-CIPPApplicationPermission.ps1 | 3 ++- .../Public/Add-CIPPDelegatedPermission.ps1 | 16 +++++++++++++--- .../CIPPCore/Public/AdditionalPermissions.json | 15 +++++++++++++++ Modules/CIPPCore/Public/SAMManifest.json | 3 +-- 4 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 Modules/CIPPCore/Public/AdditionalPermissions.json diff --git a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 index f0f4c6badf6d..8fb1f23c3537 100644 --- a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 @@ -6,7 +6,8 @@ function Add-CIPPApplicationPermission { $Tenantfilter ) if ($ApplicationId -eq $ENV:ApplicationID -and $Tenantfilter -eq $env:TenantID) { - return @('Cannot modify application permissions for CIPP-SAM on partner tenant') + #return @('Cannot modify application permissions for CIPP-SAM on partner tenant') + $RequiredResourceAccess = 'CIPPDefaults' } Set-Location (Get-Item $PSScriptRoot).FullName if ($RequiredResourceAccess -eq 'CIPPDefaults') { diff --git a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 index 921488c45f08..4ac1639877de 100644 --- a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 @@ -9,11 +9,14 @@ function Add-CIPPDelegatedPermission { Set-Location (Get-Item $PSScriptRoot).FullName if ($ApplicationId -eq $ENV:ApplicationID -and $Tenantfilter -eq $env:TenantID) { - return @('Cannot modify delgated permissions for CIPP-SAM on partner tenant') + #return @('Cannot modify delgated permissions for CIPP-SAM on partner tenant') + $RequiredResourceAccess = 'CIPPDefaults' } if ($RequiredResourceAccess -eq 'CIPPDefaults') { $RequiredResourceAccess = (Get-Content '.\SAMManifest.json' | ConvertFrom-Json).requiredResourceAccess + $AdditionalPermissions = Get-Content '.\AdditionalPermissions.json' | ConvertFrom-Json + $RequiredResourceAccess = $RequiredResourceAccess + ($AdditionalPermissions | Where-Object { $RequiredResourceAccess.resourceAppId -notcontains $_.resourceAppId }) } $Translator = Get-Content '.\PermissionsTranslator.json' | ConvertFrom-Json $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -tenantid $Tenantfilter -skipTokenCache $true @@ -22,10 +25,17 @@ function Add-CIPPDelegatedPermission { $CurrentDelegatedScopes = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals/$($ourSVCPrincipal.id)/oauth2PermissionGrants" -skipTokenCache $true -tenantid $Tenantfilter - foreach ($App in $requiredResourceAccess) { + foreach ($App in $RequiredResourceAccess) { $svcPrincipalId = $ServicePrincipalList | Where-Object -Property AppId -EQ $App.resourceAppId + $AdditionalScopes = ($AdditionalPermissions | Where-Object -Property resourceAppId -EQ $App.resourceAppId).resourceAccess if (!$svcPrincipalId) { continue } - $NewScope = ($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value -join ' ' + if ($AdditionalScopes) { + $NewScope = (($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value + $AdditionalScopes.id | Select-Object -Unique) -join ' ' + Write-Host "NEW SCOPE: $NewScope" + } else { + $NewScope = ($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value -join ' ' + } + $OldScope = ($CurrentDelegatedScopes | Where-Object -Property Resourceid -EQ $svcPrincipalId.id) if (!$OldScope) { diff --git a/Modules/CIPPCore/Public/AdditionalPermissions.json b/Modules/CIPPCore/Public/AdditionalPermissions.json new file mode 100644 index 000000000000..4983c6f5fd03 --- /dev/null +++ b/Modules/CIPPCore/Public/AdditionalPermissions.json @@ -0,0 +1,15 @@ +[ + { + "resourceAppId": "00000003-0000-0ff1-ce00-000000000000", + "resourceAccess": [{ "id": "AllProfiles.Manage", "type": "Scope" }] + }, + { + "resourceAppId": "fb78d390-0c51-40cd-8e17-fdbfab77341b", + "resourceAccess": [ + { "id": "AdminApi.AccessAsUser.All", "type": "Scope" }, + { "id": "FfoPowerShell.AccessAsUser.All", "type": "Scope" }, + { "id": "RemotePowerShell.AccessAsUser.All", "type": "Scope" }, + { "id": "VivaFeatureAccessPolicy.Manage.All", "type": "Scope" } + ] + } +] diff --git a/Modules/CIPPCore/Public/SAMManifest.json b/Modules/CIPPCore/Public/SAMManifest.json index 3d52dfeadcb1..fc75cb8b8644 100644 --- a/Modules/CIPPCore/Public/SAMManifest.json +++ b/Modules/CIPPCore/Public/SAMManifest.json @@ -172,8 +172,7 @@ { "resourceAppId": "00000003-0000-0ff1-ce00-000000000000", "resourceAccess": [ - { "id": "56680e0d-d2a3-4ae1-80d8-3c4f2100e3d0", "type": "Scope" }, - { "id": "ec4fc4c8-872e-442b-a2a2-d095575807b3", "type": "Scope" } + { "id": "56680e0d-d2a3-4ae1-80d8-3c4f2100e3d0", "type": "Scope" } ] }, { From 634f64537789689214c883972c171a059268b475 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 5 Jul 2024 22:18:47 +0200 Subject: [PATCH 046/104] convert to string for backup tasks --- .../CIPP/Core/Invoke-ExecListBackup.ps1 | 5 +++- Modules/CIPPCore/Public/New-CIPPBackup.ps1 | 8 +++---- .../CIPPCore/Public/New-CIPPBackupTask.ps1 | 24 ++++++++++++------- Scheduler_UserTasks/function.json | 2 +- 4 files changed, 25 insertions(+), 14 deletions(-) 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/New-CIPPBackup.ps1 b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 index 21e25cb7813a..f497e7daa823 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 @@ -60,10 +60,10 @@ function New-CIPPBackup { RowKey = $RowKey TenantFilter = $TenantFilter } - Write-Host "ScheduledBackupValues: $($ScheduledBackupValues | ConvertTo-Json -Compress -Depth 100)" - Write-Host "Scheduled backup value psproperties: $($ScheduledBackupValues.psobject.Properties.Name)" - foreach ($ScheduledBackup in $ScheduledBackupValues.psobject.Properties.Name) { - $entity[$ScheduledBackup] = New-CIPPBackupTask -Task $ScheduledBackup -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 { diff --git a/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 index 3bf2ff778156..d06a6ca94c5a 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 @@ -7,19 +7,24 @@ function New-CIPPBackupTask { $BackupData = switch ($Task) { 'users' { - $BackupData = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $TenantFilter + Write-Host "Backup users for $TenantFilter" + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $TenantFilter } 'groups' { - $BackupData = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $TenantFilter + Write-Host "Backup groups for $TenantFilter" + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $TenantFilter } 'ca' { - $BackupData = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter + Write-Host "Backup Conditional Access Policies for $TenantFilter" + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter } 'namedlocations' { - $BackupData = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/namedLocations?$top=999' -tenantid $TenantFilter + Write-Host "Backup Named Locations for $TenantFilter" + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/namedLocations?$top=999' -tenantid $TenantFilter } 'authstrengths' { - $BackupData = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/authenticationStrength/policies' -tenantid $TenantFilter + Write-Host "Backup Authentication Strength Policies for $TenantFilter" + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/authenticationStrength/policies' -tenantid $TenantFilter } 'intuneconfig' { #alert @@ -29,17 +34,20 @@ function New-CIPPBackupTask { 'intuneprotection' {} 'CippWebhookAlerts' { + Write-Host "Backup Webhook Alerts for $TenantFilter" $WebhookTable = Get-CIPPTable -TableName 'WebhookRules' - $BackupData = Get-CIPPAzDataTableEntity @WebhookTable | Where-Object { $TenantFilter -in ($_.Tenants | ConvertFrom-Json).fullvalue.defaultDomainName } + 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' - $BackupData = Get-CIPPAzDataTableEntity @ScheduledTasks | Where-Object { $_.hidden -eq $true -and $_.command -like 'Get-CippAlert*' -and $TenantFilter -in $_.Tenant } + 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)'" - $BackupData = (Get-CIPPAzDataTableEntity @Table -Filter $Filter) + (Get-CIPPAzDataTableEntity @Table -Filter $Filter) } } diff --git a/Scheduler_UserTasks/function.json b/Scheduler_UserTasks/function.json index f7af84092121..017acb166958 100644 --- a/Scheduler_UserTasks/function.json +++ b/Scheduler_UserTasks/function.json @@ -2,7 +2,7 @@ "bindings": [ { "name": "Timer", - "schedule": "0 */15 * * * *", + "schedule": "0 */5 * * * *", "direction": "in", "type": "timerTrigger" }, From ca3b627474e0cdc13438e3f97e4e87700eadecc5 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 5 Jul 2024 22:23:53 +0200 Subject: [PATCH 047/104] fixes backup list --- Modules/CIPPCore/Public/Get-CIPPBackup.ps1 | 1 + 1 file changed, 1 insertion(+) 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)'" From c355a54d572ffb626a52f70f7757bde5128746d4 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 5 Jul 2024 20:14:03 -0400 Subject: [PATCH 048/104] Extension data sync --- .../CIPPCore/Public/Add-CIPPScheduledTask.ps1 | 8 +- .../Push-ExtensionSyncData.ps1 | 6 + .../Register-CippExtensionScheduledTasks.ps1 | 71 +++++++ .../Sync-CippExtensionData.ps1 | 178 ++++++++++++++++++ .../Public/Hudu/Get-HuduFieldMapping.ps1 | 12 +- 5 files changed, 266 insertions(+), 9 deletions(-) create mode 100644 Modules/CippExtensions/Public/Extension Functions/Push-ExtensionSyncData.ps1 create mode 100644 Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 create mode 100644 Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index 952e3a68bc93..7e629e038798 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -2,7 +2,8 @@ function Add-CIPPScheduledTask { [CmdletBinding()] param( [pscustomobject]$Task, - [bool]$Hidden + [bool]$Hidden, + [string]$SyncType = $null ) $Table = Get-CIPPTable -TableName 'ScheduledTasks' @@ -49,10 +50,13 @@ function Add-CIPPScheduledTask { Hidden = [bool]$Hidden Results = 'Planned' } + if ($SyncType) { + $entity.SyncType = $SyncType + } try { Add-CIPPAzDataTableEntity @Table -Entity $entity -Force } catch { return "Could not add task: $($_.Exception.Message)" } return "Successfully added task: $($entity.Name)" -} \ No newline at end of file +} diff --git a/Modules/CippExtensions/Public/Extension Functions/Push-ExtensionSyncData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Push-ExtensionSyncData.ps1 new file mode 100644 index 000000000000..ebee9385b07a --- /dev/null +++ b/Modules/CippExtensions/Public/Extension Functions/Push-ExtensionSyncData.ps1 @@ -0,0 +1,6 @@ +function Push-ExtensionSyncData { + param( + $TenantFilter, + $Extension + ) +} diff --git a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 new file mode 100644 index 000000000000..32aff8ef8ff7 --- /dev/null +++ b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 @@ -0,0 +1,71 @@ +function Register-CIPPExtensionScheduledTasks { + Param( + [switch]$Reschedule + ) + + # get extension configuration and mappings table + $Table = Get-CIPPTable -TableName Extensionsconfig + $Config = ((Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -ea stop) + $MappingsTable = Get-CIPPTable -TableName CippMapping + + # Get existing scheduled usertasks + $ScheduledTasksTable = Get-CIPPTable -TableName ScheduledTasks + $ScheduledTasks = Get-CIPPAzDataTableEntity @ScheduledTasksTable -Filter 'Hidden eq true' | Where-Object { $_.Command -match 'Sync-CippExtensionData' } + $Tenants = Get-Tenants -IncludeErrors + + $Extensions = @('Hudu') + + foreach ($Extension in $Extensions) { + $ExtensionConfig = $Config.$Extension + if ($ExtensionConfig.Enabled -eq $true) { + $Mappings = Get-CIPPAzDataTableEntity @MappingsTable -Filter "PartitionKey eq '$($Extension)Mapping'" + $FieldMapping = Get-CIPPAzDataTableEntity @MappingsTable -Filter "PartitionKey eq '$($Extension)FieldMapping'" + $FieldSync = @{} + $SyncTypes = [System.Collections.Generic.List[string]]::new() + + foreach ($Mapping in $FieldMapping) { + $FieldSync[$Mapping.RowKey] = !([string]::IsNullOrEmpty($Mapping.IntegrationId)) + } + + $SyncTypes.Add('Overview') + + if ($FieldSync.Users) { + $SyncTypes.Add('Users') + $SyncTypes.Add('Mailboxes') + } + if ($FieldSync.Devices) { + $SyncTypes.Add('Devices') + } + + foreach ($Mapping in $Mappings) { + $Tenant = $Tenants | Where-Object { $_.customerId -eq $Mapping.RowKey } + + foreach ($SyncType in $SyncTypes) { + $ExistingTask = $ScheduledTasks | Where-Object { $_.Tenant -eq $Tenant.defaultDomainName -and $_.SyncType -eq $SyncType } + if (!$ExistingTask -or $Reschedule.IsPresent) { + $unixtime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds + $Task = @{ + Name = "Extension Sync - $SyncType" + Command = @{ + value = 'Sync-CippExtensionData' + label = 'Sync-CippExtensionData' + } + Parameters = @{ + TenantFilter = $Tenant.defaultDomainName + SyncType = $SyncType + } + Recurrence = '1d' + ScheduledTime = $unixtime + TenantFilter = $Tenant.defaultDomainName + } + if ($ExistingTask) { + $Task.RowKey = $ExistingTask.RowKey + } + $null = Add-CIPPScheduledTask -Task $Task -hidden $true -SyncType $SyncType + } + } + } + } + } + +} diff --git a/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 new file mode 100644 index 000000000000..7cb1c9017a78 --- /dev/null +++ b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 @@ -0,0 +1,178 @@ +function Sync-CippExtensionData { + <# + .FUNCTIONALITY + Internal + #> + [CmdletBinding()] + param( + $TenantFilter, + $SyncType + ) + + $Table = Get-CIPPTable -TableName ExtensionSync + $Extensions = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq '$($SyncType)'" + $LastSync = $Extensions | Where-Object { $_.RowKey -eq $TenantFilter } + $CacheTable = Get-CIPPTable -tablename 'CacheExtensionSync' + + if (!$LastSync) { + $LastSync = @{ + PartitionKey = $SyncType + RowKey = $TenantFilter + Status = 'Not Synced' + Error = '' + LastSync = 'Never' + } + Add-CIPPAzDataTableEntity @Table -Entity $LastSync + } + + try { + switch ($SyncType) { + 'Overview' { + # Build bulk requests array. + [System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @( + @{ + id = 'TenantDetails' + method = 'GET' + url = '/organization' + }, + @{ + id = 'AllRoles' + method = 'GET' + url = '/directoryRoles?$top=999' + }, + @{ + id = 'Domains' + method = 'GET' + url = '/domains$top=999' + }, + @{ + id = 'Licenses' + method = 'GET' + url = '/subscribedSkus?$top=999' + }, + @{ + id = 'Groups' + method = 'GET' + url = '/groups?$top=999&$select=id,createdDateTime,displayName,description,mail,mailEnabled,mailNickname,resourceProvisioningOptions,securityEnabled,visibility,organizationId,onPremisesSamAccountName,membershipRule,grouptypes,onPremisesSyncEnabled,resourceProvisioningOptions,userPrincipalName' + }, + @{ + id = 'ConditionalAccess' + method = 'GET' + url = '/identity/conditionalAccess/policies' + }, + @{ + id = 'SecureScoreControlProfiles' + method = 'GET' + url = '/security/secureScoreControlProfiles?$top=999' + }, + @{ + id = 'Subscriptions' + method = 'GET' + url = '/directory/subscriptions?$top=999' + } + ) + + $SingleGraphQueries = @(@{ + id = 'SecureScore' + graphRequest = @{ + uri = 'https://graph.microsoft.com/beta/security/secureScores?$top=1' + noPagination = $true + } + }) + } + 'Users' { + [System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @( + @{ + id = 'Users' + method = 'GET' + url = '/users?$top=999&$select=id,accountEnabled,businessPhones,city,createdDateTime,companyName,country,department,displayName,faxNumber,givenName,isResourceAccount,jobTitle,mail,mailNickname,mobilePhone,onPremisesDistinguishedName,officeLocation,onPremisesLastSyncDateTime,otherMails,postalCode,preferredDataLocation,preferredLanguage,proxyAddresses,showInAddressList,state,streetAddress,surname,usageLocation,userPrincipalName,userType,assignedLicenses,onPremisesSyncEnabled' + } + ) + } + 'Devices' { + [System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @( + @{ + id = 'Devices' + method = 'GET' + url = '/deviceManagement/managedDevices?$top=999' + }, + @{ + id = 'DeviceCompliancePolicies' + method = 'GET' + url = '/deviceManagement/deviceCompliancePolicies' + }, + @{ + id = 'DeviceApps' + method = 'GET' + url = '/deviceAppManagement/mobileApps' + } + ) + } + 'Mailboxes' { + $Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses,WhenSoftDeleted,IsInactiveMailbox' + $ExoRequest = @{ + tenantid = $TenantFilter + cmdlet = 'Get-Mailbox' + cmdParams = @{} + Select = $Select + } + $Mailboxes = (New-ExoRequest @ExoRequest) | Select-Object id, ExchangeGuid, ArchiveGuid, WhenSoftDeleted, @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } }, + + @{ Name = 'displayName'; Expression = { $_.'DisplayName' } }, + @{ Name = 'primarySmtpAddress'; Expression = { $_.'PrimarySMTPAddress' } }, + @{ Name = 'recipientType'; Expression = { $_.'RecipientType' } }, + @{ Name = 'recipientTypeDetails'; Expression = { $_.'RecipientTypeDetails' } }, + @{ Name = 'AdditionalEmailAddresses'; Expression = { ($_.'EmailAddresses' | Where-Object { $_ -clike 'smtp:*' }).Replace('smtp:', '') -join ', ' } } + + $Entity = @{ + PartitionKey = $TenantFilter + SyncType = 'Mailboxes' + RowKey = 'Mailboxes' + Data = [string]($Mailboxes | ConvertTo-Json -Depth 10 -Compress) + } + Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force + } + } + + if ($TenantRequests) { + try { + $TenantResults = New-GraphBulkRequest -Requests $TenantRequests -tenantid $TenantFilter + } catch { + Throw "Failed to fetch bulk company data: $_" + } + + if ($SingleGraphQueries) { + foreach ($SingleGraphQuery in $SingleGraphQueries) { + $Request = $SingleGraphQuery.graphRequest + $Data = New-GraphGetRequest @Request -tenantid $TenantFilter + $Entity = @{ + PartitionKey = $TenantFilter + SyncType = $SyncType + RowKey = $SingleGraphQuery.id + Data = [string]($Data | ConvertTo-Json -Depth 10 -Compress) + } + Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force + } + } + + $TenantResults | Select-Object id, body | ForEach-Object { + $Entity = @{ + PartitionKey = $TenantFilter + RowKey = $_.id + SyncType = $SyncType + Data = [string]($_.body.value | ConvertTo-Json -Depth 10 -Compress) + } + Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force + } + } + $LastSync.LastSync = [datetime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ') + $LastSync.Status = 'Completed' + $LastSync.Error = '' + } catch { + $LastSync.Status = 'Failed' + $LastSync.Error = [string](Get-CippException -Exception $_ | ConvertTo-Json -Compress) + throw "Failed to sync data: $($_.Exception.Message)" + } finally { + Add-CIPPAzDataTableEntity @Table -Entity $LastSync -Force + } +} diff --git a/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 index 64b4700af62f..bb12d561104a 100644 --- a/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 @@ -4,13 +4,13 @@ function Get-HuduFieldMapping { $CIPPMapping ) - $Mappings = Get-ExtensionMapping -Extension 'HuduFields' + $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 Field' + 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 = @( @@ -31,8 +31,6 @@ function Get-HuduFieldMapping { } ) - - $Tenants = Get-Tenants -IncludeErrors $Table = Get-CIPPTable -TableName Extensionsconfig try { $Configuration = ((Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -ea stop).Hudu @@ -46,8 +44,8 @@ function Get-HuduFieldMapping { $_.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' }) + 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]@{ @@ -65,4 +63,4 @@ function Get-HuduFieldMapping { return $MappingObj -} \ No newline at end of file +} From 0e429f0a0df10988f61eebd99068dd66e6f55534 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 6 Jul 2024 00:40:22 -0400 Subject: [PATCH 049/104] Update CPV to work with partner tenant --- .../Public/Add-CIPPApplicationPermission.ps1 | 10 +++++----- .../Public/Add-CIPPDelegatedPermission.ps1 | 18 ++++++++++++------ .../CIPPCore/Public/PermissionsTranslator.json | 7 +++++++ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 index 8fb1f23c3537..5ec28d3c2e7a 100644 --- a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 @@ -13,18 +13,18 @@ function Add-CIPPApplicationPermission { 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 @@ -41,7 +41,7 @@ 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 ($Grant | ConvertTo-Json) -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 @@ -49,4 +49,4 @@ function Add-CIPPApplicationPermission { } "Added $counter Application permissions to $($ourSVCPrincipal.displayName)" return $Results -} \ No newline at end of file +} diff --git a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 index 4ac1639877de..0c9fcfb93209 100644 --- a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 @@ -16,22 +16,28 @@ function Add-CIPPDelegatedPermission { 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) { $svcPrincipalId = $ServicePrincipalList | Where-Object -Property AppId -EQ $App.resourceAppId $AdditionalScopes = ($AdditionalPermissions | Where-Object -Property resourceAppId -EQ $App.resourceAppId).resourceAccess if (!$svcPrincipalId) { continue } if ($AdditionalScopes) { - $NewScope = (($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value + $AdditionalScopes.id | Select-Object -Unique) -join ' ' - Write-Host "NEW SCOPE: $NewScope" + $NewScope = (@(($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value) + @($AdditionalScopes.id | Select-Object -Unique)) -join ' ' } else { $NewScope = ($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value -join ' ' } @@ -45,7 +51,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(' ') @@ -56,7 +62,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/PermissionsTranslator.json b/Modules/CIPPCore/Public/PermissionsTranslator.json index aa7947e9374d..f57e0a959812 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 ", From e3bb4d384f597af67a872545653bebc41ec970e9 Mon Sep 17 00:00:00 2001 From: chase-vgo <168204519+chase-vgo@users.noreply.github.com> Date: Sat, 6 Jul 2024 16:48:00 -0500 Subject: [PATCH 050/104] Remove-CIPPCalendarInvites --- .../Public/Remove-CIPPCalendarInvites.ps1 | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1 diff --git a/Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1 b/Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1 new file mode 100644 index 000000000000..22e57c2acff8 --- /dev/null +++ b/Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1 @@ -0,0 +1,21 @@ +function Remove-CIPPCalendarInvites { + [CmdletBinding()] + param( + $userid, + $tenantFilter, + $username, + $APIName = 'Remove Calendar Invites', + $ExecutingUser + ) + + try { + + New-ExoRequest -tenantid $tenantFilter -cmdlet 'Remove-CalendarEvents' -Anchor $username -cmdParams @{Identity = $username; QueryWindowInDays = 730 ; CancelOrganizedMeetings = $true ; Confirm = $false} + Write-LogMessage -user $ExecutingUser -API $APIName -message "Cancelled all calendar invites for $($username)" -Sev 'Info' -tenant $tenantFilter + "Cancelled all calendar invites for $($username)" + + } catch { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not cancel calendar invites for $($username): $($_.Exception.Message)" -Sev 'Error' -tenant $tenantFilter + return "Could not cancel calendar invites for $($username). Error: $($_.Exception.Message)" + } +} From 2aa378818a13e7160699f3e3bb483a872331f7fc Mon Sep 17 00:00:00 2001 From: chase-vgo <168204519+chase-vgo@users.noreply.github.com> Date: Sat, 6 Jul 2024 16:53:30 -0500 Subject: [PATCH 051/104] Added removeCalendarInvites --- Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 index 9087bd30deff..ba5ab6432fb8 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 @@ -72,6 +72,9 @@ function Invoke-CIPPOffboardingJob { { $_.'removeMobile' -eq 'true' } { Remove-CIPPMobileDevice -userid $userid -username $Username -tenantFilter $Tenantfilter -ExecutingUser $ExecutingUser -APIName $APIName } + { $_.'removeCalendarInvites' -eq 'true' } { + Remove-CIPPCalendarInvites -userid $userid -username $Username -tenantFilter $Tenantfilter -ExecutingUser $ExecutingUser -APIName $APIName + } { $_.'removePermissions' } { if ($RunScheduled) { Remove-CIPPMailboxPermissions -PermissionsLevel @('FullAccess', 'SendAs', 'SendOnBehalf') -userid 'AllUsers' -AccessUser $UserName -TenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $ExecutingUser @@ -90,4 +93,4 @@ function Invoke-CIPPOffboardingJob { } return $Return -} \ No newline at end of file +} From fdb9dfc66094b9887e0f09af53b03ce3788e491f Mon Sep 17 00:00:00 2001 From: chase-vgo <168204519+chase-vgo@users.noreply.github.com> Date: Sat, 6 Jul 2024 16:54:23 -0500 Subject: [PATCH 052/104] Roles for modifying calendar invites --- Cache_SAMSetup/SAMManifest.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Cache_SAMSetup/SAMManifest.json b/Cache_SAMSetup/SAMManifest.json index e2a457b734fb..f94959dc3ac6 100644 --- a/Cache_SAMSetup/SAMManifest.json +++ b/Cache_SAMSetup/SAMManifest.json @@ -165,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" } ] }, { From b5019885b80e7ba81e80d5474f2396695717d715 Mon Sep 17 00:00:00 2001 From: chase-vgo <168204519+chase-vgo@users.noreply.github.com> Date: Sat, 6 Jul 2024 16:54:45 -0500 Subject: [PATCH 053/104] Roles for removing calendar invites --- Modules/CIPPCore/Public/SAMManifest.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/SAMManifest.json b/Modules/CIPPCore/Public/SAMManifest.json index fc75cb8b8644..50a03c019af1 100644 --- a/Modules/CIPPCore/Public/SAMManifest.json +++ b/Modules/CIPPCore/Public/SAMManifest.json @@ -166,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" } ] }, { From de6d14e2001a78d36ff8ab9b6d8770ec9fd9e843 Mon Sep 17 00:00:00 2001 From: chase-vgo <168204519+chase-vgo@users.noreply.github.com> Date: Sat, 6 Jul 2024 16:56:25 -0500 Subject: [PATCH 054/104] Roles for removing calendar invites --- Cache_SAMSetup/PermissionsTranslator.json | 38 +++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) 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" } ] From 24a77cb5d7b3f907216e7fd2e9ebb35b802ce748 Mon Sep 17 00:00:00 2001 From: chase-vgo <168204519+chase-vgo@users.noreply.github.com> Date: Sat, 6 Jul 2024 16:56:37 -0500 Subject: [PATCH 055/104] Roles for removing calendar invites --- .../Public/PermissionsTranslator.json | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/Modules/CIPPCore/Public/PermissionsTranslator.json b/Modules/CIPPCore/Public/PermissionsTranslator.json index aa7947e9374d..204fe8532c4f 100644 --- a/Modules/CIPPCore/Public/PermissionsTranslator.json +++ b/Modules/CIPPCore/Public/PermissionsTranslator.json @@ -1004,8 +1004,15 @@ "description": "Allows the app to create, read, update, and delete events of all calendars without a signed-in user.", "displayName": "Read and write calendars in all mailboxes", "id": "ef54d2bf-783f-4e0f-bca1-3210c0444d99", - "origin": "Application", - "value": "Calendars.ReadWrite" + "origin": "Application (Office 365 Exchange Online)", + "value": "Calendars.ReadWrite.All" + }, + { + "description": "Allows the app to create, read, update, and delete user's mailbox settings without a signed-in user. Does not include permission to send mail.", + "displayName": "Read and write all user mailbox settings", + "id": "f9156939-25cd-4ba8-abfe-7fabcf003749", + "origin": "Application (Office 365 Exchange Online)", + "value": "Mailbox.Settings.ReadWrite" }, { "description": "Allows the app to read your organization's user flows, without a signed-in user.", @@ -5286,6 +5293,24 @@ "userConsentDisplayName": "Read Threat and Vulnerability Management vulnerability information", "value": "Exchange.Manage" }, + { + "description": "Allows the app to create, read, update and delete events in all calendars in the organization user has permissions to access. This includes delegate and shared calendars", + "displayName": "Read and write user and shared calendars", + "id": "bbd1ca91-75e0-4814-ad94-9c5dbbae3415", + "Origin": "Delegated (Office 365 Exchange Online)", + "userConsentDescription": "Allows the app to read, update, create and delete events in all calendars in your organization you have permissions to access. This includes delegate and shared calendars", + "userConsentDisplayName": "Read and write to your and shared calendars", + "value": "Calendars.ReadWrite.All" + }, + { + "description": "Allows the app to create, read, update, and delete user's mailbox settings. Does not include permission to send mail.", + "displayName": "Read and write user mailbox settings", + "id": "2e83d72d-8895-4b66-9eea-abb43449ab8b", + "Origin": "Delegated (Office 365 Exchange Online)", + "userConsentDescription": "Allows the app to read, update, create, and delete your mailbox settings.", + "userConsentDisplayName": "Read and write to your mailbox settings", + "value": "MailboxSettings.ReadWrite" + }, { "description": "Allows the app to have full control of all site collections on behalf of the signed-in user.", "displayName": "Manage Sharepoint Online", @@ -5295,15 +5320,6 @@ "userConsentDisplayName": "Allows the app to have full control of all site collections on your behalf.", "value": "AllSites.FullControl" }, - { - "description": "Required for Request-SPOPeronalSite", - "displayName": "Manage sharepoint profiles", - "id": "ec4fc4c8-872e-442b-a2a2-d095575807b3", - "Origin": "Delegated (Office 365 SharePoint Online)", - "userConsentDescription": "", - "userConsentDisplayName": "Manage sharepoint profiles", - "value": "AllProfiles.Manage" - }, { "description": "Allows to read the LAPs passwords.", "displayName": "Manage LAPs passwords", From a76ac093cc85a4aebd502408aa9ec766324f4f58 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sun, 7 Jul 2024 19:49:44 +0200 Subject: [PATCH 056/104] commiting changes to copy app wizard. --- .../Public/Add-CIPPDelegatedPermission.ps1 | 6 +++ .../Push-ExecAddMultiTenantApp.ps1 | 2 +- .../Push-ExecApplicationCopy.ps1 | 13 +++++ .../Invoke-ExecAddMultiTenantApp.ps1 | 15 ++---- .../Public/New-CIPPApplicationCopy.ps1 | 46 +++++++++++++++++ Modules/CIPPCore/Public/New-CIPPBackup.ps1 | 8 +-- .../Invoke-CIPPStandardAppDeploy.ps1 | 50 +++++++++++++++++++ 7 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecApplicationCopy.ps1 create mode 100644 Modules/CIPPCore/Public/New-CIPPApplicationCopy.ps1 create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 diff --git a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 index 4ac1639877de..7701a5ff68cb 100644 --- a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 @@ -3,6 +3,7 @@ function Add-CIPPDelegatedPermission { param( $RequiredResourceAccess, $ApplicationId, + $NoTranslateRequired, $Tenantfilter ) Write-Host 'Adding Delegated Permissions' @@ -33,6 +34,11 @@ function Add-CIPPDelegatedPermission { $NewScope = (($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value + $AdditionalScopes.id | Select-Object -Unique) -join ' ' Write-Host "NEW SCOPE: $NewScope" } else { + if ($NoTranslateRequired) { + $NewScope = $App.resourceAccess | ForEach-Object { $_.id } -join ' ' + } else { + $NewScope = ($Translator | Where-Object { $_.id -in $App.resourceAccess.id }).value -join ' ' + } $NewScope = ($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value -join ' ' } 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/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/New-CIPPApplicationCopy.ps1 b/Modules/CIPPCore/Public/New-CIPPApplicationCopy.ps1 new file mode 100644 index 000000000000..da8b584954f8 --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPApplicationCopy.ps1 @@ -0,0 +1,46 @@ +function New-CIPPApplicationCopy { + [CmdletBinding()] + param( + $App, + $Tenant + ) + $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals?$top=999' -tenantid $env:TenantID -NoAuthCheck $true + try { + $ExistingApp = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/Applications(appId='$($app)')" -tenantid $ENV:tenantid -NoAuthCheck $true + $Type = 'Application' + } catch { + $ExistingApp = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals(appId='$($app)')/oauth2PermissionGrants" -tenantid $ENV:tenantid -NoAuthCheck $true + $ExistingAppRoleAssignments = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals(appId='$($app)')/appRoleAssignments" -tenantid $ENV:tenantid -NoAuthCheck $true + $Type = 'ServicePrincipal' + } + if (!$ExistingApp) { + Write-LogMessage -message "Failed to add $App to tenant. This app does not exist." -tenant $tenant -API 'Application Copy' -sev error + continue + } + if ($Type -eq 'Application') { + $DelegateResourceAccess = $Existingapp.requiredResourceAccess + $ApplicationResourceAccess = $Existingapp.requiredResourceAccess + $NoTranslateRequired = $false + } else { + $DelegateResourceAccess = $ExistingApp | Group-Object -Property resourceId | ForEach-Object { + [pscustomobject]@{ resourceAppId = ($CurrentInfo | Where-Object -Property id -EQ $_.Name).appId; resourceAccess = @($_.Group | ForEach-Object { [pscustomobject]@{ id = $_.scope; type = 'Scope' } } ) + } + } + $ApplicationResourceAccess = $ExistingappRoleAssignments | Group-Object -Property ResourceId | ForEach-Object { + [pscustomobject]@{ resourceAppId = ($CurrentInfo | Where-Object -Property id -EQ $_.Name).appId; resourceAccess = @($_.Group | ForEach-Object { [pscustomobject]@{ id = $_.appRoleId; type = 'Role' } } ) + } + } + $NoTranslateRequired = $true + } + $TenantInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals?$top=999' -tenantid $Tenant -NoAuthCheck $true + + if ($App -Notin $TenantInfo.appId) { + $PostResults = New-GraphPostRequest 'https://graph.microsoft.com/beta/servicePrincipals' -type POST -tenantid $Tenant -body "{ `"appId`": `"$($App)`" }" + Write-LogMessage -message "Added $App as a service principal" -tenant $tenant -API 'Application Copy' -sev Info + } + Add-CIPPApplicationPermission -RequiredResourceAccess $ApplicationResourceAccess -ApplicationId $App -Tenantfilter $Tenant + Add-CIPPDelegatedPermission -RequiredResourceAccess $DelegateResourceAccess -ApplicationId $App -Tenantfilter $Tenant -NoTranslateRequired $NoTranslateRequired + Write-LogMessage -message "Added permissions to $app" -tenant $tenant -API 'Application Copy' -sev Info + + return $Results +} diff --git a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 index f497e7daa823..0893e0e89b15 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 @@ -11,7 +11,7 @@ function New-CIPPBackup { $BackupData = switch ($backupType) { #If backup type is CIPP, create CIPP backup. - 'CIPP' { + 'CIPP' { try { $BackupTables = @( 'bpa' @@ -27,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' @@ -43,7 +43,7 @@ function New-CIPPBackup { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup for CIPP: $($_.Exception.Message)" -Sev 'Error' [pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" } } - + } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup: $($_.Exception.Message)" -Sev 'Error' [pscustomobject]@{'Results' = "Backup Creation failed: $($_.Exception.Message)" } @@ -51,7 +51,7 @@ function New-CIPPBackup { } #If Backup type is ConditionalAccess, create Conditional Access backup. - 'Scheduled' { + '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') diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 new file mode 100644 index 000000000000..c6c2509795ac --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 @@ -0,0 +1,50 @@ +function Invoke-CIPPStandardAppDeploy { + <# + .FUNCTIONALITY + Internal + .APINAME + AppDeploy + .CAT + Entra Standards + .TAG + "lowimpact" + "CIS" + .HELPTEXT + Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access + .DOCSDESCRIPTION + Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access. This is a tenant wide setting and overrules any settings set on the site level + .ADDEDCOMPONENT + .LABEL + Disable Resharing by External Users + .IMPACT + High Impact + .POWERSHELLEQUIVALENT + Update-MgBetaAdminSharepointSetting + .RECOMMENDEDBY + "CIS" + .DOCSDESCRIPTION + Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + param($Tenant, $Settings) + + If ($Settings.remediate -eq $true) { + $AppsToAdd = $Settings.appids -split ',' + foreach ($App In $AppsToAdd) { + try { + New-CIPPApplicationCopy -App $App -Tenant $Tenant + Write-LogMessage -API 'Standards' -tenant $tenant -message "Added $App to $Tenant and update it's permissions" -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to add app $App" -sev Error + } + } + } +} + + + + + From e0ffa585d526962f787ba5834948ea2194d3667a Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 8 Jul 2024 01:40:29 +0200 Subject: [PATCH 057/104] major update to splitting data across tables --- .../Public/Add-CIPPAzDataTableEntity.ps1 | 122 ++++++++++++++---- .../Endpoint/MEM/Invoke-AddIntuneTemplate.ps1 | 77 +---------- .../Invoke-ListIntuneTemplates.ps1 | 4 +- .../Public/Get-CIPPAzDatatableEntity.ps1 | 66 ++++++++-- .../CIPPCore/Public/New-CIPPBackupTask.ps1 | 30 ++++- .../Public/New-CIPPIntuneTemplate.ps1 | 83 ++++++++++++ Scheduler_UserTasks/function.json | 2 +- 7 files changed, 268 insertions(+), 116 deletions(-) create mode 100644 Modules/CIPPCore/Public/New-CIPPIntuneTemplate.ps1 diff --git a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 index befa8155df6c..8c3045f17579 100644 --- a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 @@ -6,45 +6,122 @@ function Add-CIPPAzDataTableEntity { [switch]$Force, [switch]$CreateTableIfNotExists ) - + + $MaxRowSize = 1mb - 100kb + $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 } catch [System.Exception] { if ($_.Exception.ErrorCode -eq 'PropertyValueTooLarge' -or $_.Exception.ErrorCode -eq 'EntityTooLarge') { try { - $MaxSize = 30kb - $largePropertyName = $null + $largePropertyNames = @() + $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 = $largePropertyNames + $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) { + foreach ($largePropertyName in $largePropertyNames) { + $dataString = $SingleEnt[$largePropertyName] + $splitCount = [math]::Ceiling($dataString.Length / $MaxSize) + $splitData = @() + for ($i = 0; $i -lt $splitCount; $i++) { + $start = $i * $MaxSize + $splitData = $splitData + $dataString.Substring($start, [Math]::Min($MaxSize, $dataString.Length - $start)) + } + + $splitPropertyNames = @() + for ($i = 0; $i -lt $splitData.Count; $i++) { + $splitPropertyNames = $splitPropertyNames + "${largePropertyName}_Part$i" + } - $splitPropertyNames = 1..$splitData.Count | ForEach-Object { - "${largePropertyName}_Part$_" + $splitInfo = @{ + OriginalHeader = $largePropertyName + SplitHeaders = $splitPropertyNames + } + $SingleEnt['SplitOverProps'] = ($splitInfo | ConvertTo-Json).ToString() + $SingleEnt.Remove($largePropertyName) + + for ($i = 0; $i -lt $splitData.Count; $i++) { + $SingleEnt[$splitPropertyNames[$i]] = $splitData[$i] + } } + } + + # Check if the entity is still too large + $entitySize = [System.Text.Encoding]::UTF8.GetByteCount($($SingleEnt | ConvertTo-Json)) + if ($entitySize -gt $MaxRowSize) { + $rows = @() + $originalPartitionKey = $SingleEnt.PartitionKey + $originalRowKey = $SingleEnt.RowKey + $entityIndex = 0 + + while ($entitySize -gt $MaxRowSize) { + $newEntity = @{} + $newEntity['PartitionKey'] = $originalPartitionKey + $newEntity['RowKey'] = "$($originalRowKey)-part$entityIndex" + $newEntity['OriginalEntityId'] = $originalRowKey + $newEntity['PartIndex'] = $entityIndex + $entityIndex++ - $splitInfo = @{ - OriginalHeader = $largePropertyName - SplitHeaders = $splitPropertyNames + $propertiesToRemove = @() + 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 = @() + for ($i = 0; $i -lt $splitCount; $i++) { + $start = $i * $MaxSize + $splitData = $splitData + $dataString.Substring($start, [Math]::Min($MaxSize, $dataString.Length - $start)) + } + + $splitPropertyNames = @() + for ($i = 0; $i -lt $splitData.Count; $i++) { + $splitPropertyNames = $splitPropertyNames + "${key}_Part$i" + } + + for ($i = 0; $i -lt $splitData.Count; $i++) { + $newEntity[$splitPropertyNames[$i]] = $splitData[$i] + } + } else { + $newEntity[$key] = $SingleEnt[$key] + } + $propertiesToRemove = $propertiesToRemove + $key + } + } + + foreach ($prop in $propertiesToRemove) { + $SingleEnt.Remove($prop) + } + + $rows = $rows + $newEntity + $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 = $rows + $SingleEnt } + foreach ($row in $rows) { + Write-Host 'Size is larger than 70kb, splitting entity into multiple rows.' + Write-Host "current entity is $($row.RowKey) with $($row.PartitionKey)" + Add-AzDataTableEntity -context $Context -force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $row + } + } else { Add-AzDataTableEntity -context $Context -force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $SingleEnt } @@ -53,7 +130,6 @@ function Add-CIPPAzDataTableEntity { } } else { Write-Host "THE ERROR IS $($_.Exception.ErrorCode)" - throw $_ } } 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/Invoke-ListIntuneTemplates.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntuneTemplates.ps1 index c94431612970..a11384cf8e85 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntuneTemplates.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntuneTemplates.ps1 @@ -31,7 +31,7 @@ Function Invoke-ListIntuneTemplates { $Templates = (Get-CIPPAzDataTableEntity @Table -Filter $Filter).JSON | ConvertFrom-Json if ($Request.query.View) { $Templates = $Templates | ForEach-Object { - $data = $_.RAWJson | ConvertFrom-Json + $data = $_.RAWJson | ConvertFrom-Json -Depth 100 $data | Add-Member -NotePropertyName 'displayName' -NotePropertyValue $_.Displayname -Force $data | Add-Member -NotePropertyName 'description' -NotePropertyValue $_.Description -Force $data | Add-Member -NotePropertyName 'Type' -NotePropertyValue $_.Type -Force @@ -46,7 +46,7 @@ Function Invoke-ListIntuneTemplates { # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = ($Templates | ConvertTo-Json -Depth 10) + Body = ($Templates | ConvertTo-Json -Depth 100) }) } diff --git a/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 b/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 index 4ce96415966d..b72e56a0f477 100644 --- a/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 @@ -7,23 +7,65 @@ function Get-CIPPAzDataTableEntity { $First, $Skip, $Sort, - $Count + $Count ) + $Results = Get-AzDataTableEntity @PSBoundParameters - $Results = $Results | ForEach-Object { - $entity = $_ + $mergedResults = @{} + + foreach ($entity in $Results) { + if ($entity.OriginalEntityId) { + $entityId = $entity.OriginalEntityId + if (-not $mergedResults.ContainsKey($entityId)) { + $mergedResults[$entityId] = @{ + Parts = @() + } + } + $mergedResults[$entityId]['Parts'] = $mergedResults[$entityId]['Parts'] + @($entity) + } else { + $mergedResults[$entity.RowKey] = @{ + Entity = $entity + Parts = @() + } + } + } + + $finalResults = @() + foreach ($entityId in $mergedResults.Keys) { + $entityData = $mergedResults[$entityId] + if ($entityData.Parts.Count -gt 0) { + $fullEntity = [PSCustomObject]@{} + $parts = $entityData.Parts | Sort-Object PartIndex + foreach ($part in $parts) { + foreach ($key in $part.PSObject.Properties.Name) { + if ($key -notin @('OriginalEntityId', 'PartIndex', 'PartitionKey', 'RowKey', 'ETag', '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) + } + } + + foreach ($entity in $finalResults) { if ($entity.SplitOverProps) { $splitInfo = $entity.SplitOverProps | ConvertFrom-Json - $mergedData = -join ($splitInfo.SplitHeaders | ForEach-Object { $entity.$_ }) + $mergedData = [string]::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 - } - else { - $entity + $propsToRemove = $splitInfo.SplitHeaders + 'SplitOverProps' + foreach ($prop in $propsToRemove) { + $entity.PSObject.Properties.Remove($prop) + } } } - - return $Results + + return $finalResults } diff --git a/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 index d06a6ca94c5a..cbd216411068 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 @@ -27,12 +27,32 @@ function New-CIPPBackupTask { New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/authenticationStrength/policies' -tenantid $TenantFilter } 'intuneconfig' { - #alert + $GraphURLS = @("https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations?`$select=id,displayName,lastModifiedDateTime,roleScopeTagIds,microsoft.graph.unsupportedDeviceConfiguration/originalEntityTypeName&`$expand=assignments&top=1000" + 'https://graph.microsoft.com/beta/deviceManagement/windowsDriverUpdateProfiles' + "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations?`$expand=assignments&top=999" + "https://graph.microsoft.com/beta/deviceAppManagement/mobileAppConfigurations?`$expand=assignments&`$filter=microsoft.graph.androidManagedStoreAppConfiguration/appSupportsOemConfig%20eq%20true" + 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' + ) + + $GraphURLS | ForEach-Object { + $URLName = (($_).split('?') | Select-Object -First 1) -replace 'https://graph.microsoft.com/beta/deviceManagement/', '' + New-GraphGetRequest -uri "$($_)" -tenantid $TenantFilter + } | ForEach-Object { + New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName $URLName -ID $_.ID + } + } + 'intunecompliance' { + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies?$top=999' -tenantid $TenantFilter | ForEach-Object { + New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName 'deviceCompliancePolicies' -ID $_.ID + } + } + + 'intuneprotection' { + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceAppManagement/managedAppPolicies?$top=999' -tenantid $TenantFilter | ForEach-Object { + New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName 'managedAppPolicies' -ID $_.ID + } } - 'intunecompliance' {} - 'intuneprotection' {} - 'CippWebhookAlerts' { Write-Host "Backup Webhook Alerts for $TenantFilter" $WebhookTable = Get-CIPPTable -TableName 'WebhookRules' @@ -43,7 +63,7 @@ function New-CIPPBackupTask { $ScheduledTasks = Get-CIPPTable -TableName 'ScheduledTasks' Get-CIPPAzDataTableEntity @ScheduledTasks | Where-Object { $_.hidden -eq $true -and $_.command -like 'Get-CippAlert*' -and $TenantFilter -in $_.Tenant } } - 'CippStandards' { + 'CippStandards' { Write-Host "Backup Standards for $TenantFilter" $Table = Get-CippTable -tablename 'standards' $Filter = "PartitionKey eq 'standards' and RowKey eq '$($TenantFilter)'" 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/Scheduler_UserTasks/function.json b/Scheduler_UserTasks/function.json index 017acb166958..f7af84092121 100644 --- a/Scheduler_UserTasks/function.json +++ b/Scheduler_UserTasks/function.json @@ -2,7 +2,7 @@ "bindings": [ { "name": "Timer", - "schedule": "0 */5 * * * *", + "schedule": "0 */15 * * * *", "direction": "in", "type": "timerTrigger" }, From 8f88e9d16841494ee8f1c2d93c679fb84b9b2f70 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 8 Jul 2024 13:12:20 +0200 Subject: [PATCH 058/104] Improve storing large storage --- Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 | 8 ++++---- Scheduler_UserTasks/function.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 index 8c3045f17579..651f54a42817 100644 --- a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 @@ -7,7 +7,7 @@ function Add-CIPPAzDataTableEntity { [switch]$CreateTableIfNotExists ) - $MaxRowSize = 1mb - 100kb + $MaxRowSize = 500000 - 100 #Maximum size of an entity $MaxSize = 30kb # maximum size of a property value foreach ($SingleEnt in $Entity) { @@ -63,6 +63,7 @@ function Add-CIPPAzDataTableEntity { $entityIndex = 0 while ($entitySize -gt $MaxRowSize) { + Write-Host "Entity size is $entitySize. Splitting entity into multiple parts." $newEntity = @{} $newEntity['PartitionKey'] = $originalPartitionKey $newEntity['RowKey'] = "$($originalRowKey)-part$entityIndex" @@ -117,8 +118,7 @@ function Add-CIPPAzDataTableEntity { } foreach ($row in $rows) { - Write-Host 'Size is larger than 70kb, splitting entity into multiple rows.' - Write-Host "current entity is $($row.RowKey) with $($row.PartitionKey)" + Write-Host "current entity is $($row.RowKey) with $($row.PartitionKey). Our size is $([System.Text.Encoding]::UTF8.GetByteCount($($SingleEnt | ConvertTo-Json)))" Add-AzDataTableEntity -context $Context -force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $row } } else { @@ -129,7 +129,7 @@ function Add-CIPPAzDataTableEntity { throw "Error processing entity: $($_.Exception.Message)." } } else { - Write-Host "THE ERROR IS $($_.Exception.ErrorCode)" + Write-Host "THE ERROR IS $($_.Exception.ErrorCode). The size of the entity is $entitySize." throw $_ } } diff --git a/Scheduler_UserTasks/function.json b/Scheduler_UserTasks/function.json index f7af84092121..017acb166958 100644 --- a/Scheduler_UserTasks/function.json +++ b/Scheduler_UserTasks/function.json @@ -2,7 +2,7 @@ "bindings": [ { "name": "Timer", - "schedule": "0 */15 * * * *", + "schedule": "0 */5 * * * *", "direction": "in", "type": "timerTrigger" }, From 763035db7dd0a222c6ef46cb1def91a2f7fb3ae5 Mon Sep 17 00:00:00 2001 From: Esco Date: Fri, 5 Jul 2024 15:32:46 +0200 Subject: [PATCH 059/104] Added SPAzureB2B Standard --- .../Invoke-CIPPStandardSPAzureB2B.ps1 | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 new file mode 100644 index 000000000000..492d59fa4608 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 @@ -0,0 +1,64 @@ +function Invoke-CIPPStandardSPAzureB2B { + <# + .FUNCTIONALITY + Internal + .APINAME + SPAzureB2B + .CAT + SharePoint Standards + .TAG + "lowimpact" + "CIS" + .HELPTEXT + Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled + .ADDEDCOMPONENT + .LABEL + Enable SharePoint and OneDrive integration with Azure AD B2B + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-SPOTenant -EnableAzureADB2BIntegration $true + .RECOMMENDEDBY + "CIS 3.0" + .DOCSDESCRIPTION + Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + 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 + } +} From 1318d8fc47e7bcba0dd24b4443ab4e047edcc67b Mon Sep 17 00:00:00 2001 From: Esco Date: Fri, 5 Jul 2024 15:55:40 +0200 Subject: [PATCH 060/104] Added SPDirectSharing Standard --- .../Invoke-CIPPStandardSPDirectSharing.ps1 | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 new file mode 100644 index 000000000000..95fb6ffe695e --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 @@ -0,0 +1,64 @@ +function Invoke-CIPPStandardSPDirectSharing { + <# + .FUNCTIONALITY + Internal + .APINAME + SPDirectSharing + .CAT + SharePoint Standards + .TAG + "mediumimpact" + "CIS" + .HELPTEXT + Ensure default link sharing is set to Direct in SharePoint and OneDrive + .ADDEDCOMPONENT + .LABEL + Default sharing to Direct users + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Set-SPOTenant -DefaultSharingLinkType Direct + .RECOMMENDEDBY + "CIS 3.0" + .DOCSDESCRIPTION + Ensure default link sharing is set to Direct in SharePoint and OneDrive + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + 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 + } +} From 10d330b699297c5d64389185031fed7c026d258d Mon Sep 17 00:00:00 2001 From: Esco Date: Mon, 8 Jul 2024 13:38:32 +0200 Subject: [PATCH 061/104] Added SPExternalUserExpiration standard --- ...e-CIPPStandardSPExternalUserExpiration.ps1 | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 new file mode 100644 index 000000000000..581691d4153d --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 @@ -0,0 +1,67 @@ +function Invoke-CIPPStandardSPExternalUserExpiration { + <# + .FUNCTIONALITY + Internal + .APINAME + SPExternalUserExpiration + .CAT + SharePoint Standards + .TAG + "mediumimpact" + "CIS" + .HELPTEXT + Ensure guest access to a site or OneDrive will expire automatically + .ADDEDCOMPONENT + {"type":"number","name":"standards.SPExternalUserExpiration.Days","label":"Days until expiration (Default 60)"} + .LABEL + Set guest access to expire automatically + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Set-SPOTenant -ExternalUserExpireInDays 30 -ExternalUserExpirationRequired $True + .RECOMMENDEDBY + "CIS 3.0" + .DOCSDESCRIPTION + Ensure guest access to a site or OneDrive will expire automatically + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + 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 + } +} From 0393b2bfe5a434c61f70397b8eed1356e73a1f20 Mon Sep 17 00:00:00 2001 From: Esco Date: Mon, 8 Jul 2024 14:02:30 +0200 Subject: [PATCH 062/104] Added SPEmailAttestation standard --- .../Invoke-CIPPStandardSPEmailAttestation.ps1 | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 new file mode 100644 index 000000000000..b70d89fce594 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 @@ -0,0 +1,67 @@ +function Invoke-CIPPStandardSPEmailAttestation { + <# + .FUNCTIONALITY + Internal + .APINAME + SPEmailAttestation + .CAT + SharePoint Standards + .TAG + "mediumimpact" + "CIS" + .HELPTEXT + Ensure reauthentication with verification code is restricted + .ADDEDCOMPONENT + {"type":"number","name":"standards.SPEmailAttestation.Days","label":"Require reauth every X Days (Default 15)"} + .LABEL + Require reauthentication with verification code + .IMPACT + Medium Impact + .POWERSHELLEQUIVALENT + Set-SPOTenant -EmailAttestationRequired $true -EmailAttestationReAuthDays 15 + .RECOMMENDEDBY + "CIS 3.0" + .DOCSDESCRIPTION + Ensure reauthentication with verification code is restricted + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + 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 + } +} From a7da0b1119b79e38fb868dad802a61b1cdca24d2 Mon Sep 17 00:00:00 2001 From: Esco Date: Mon, 8 Jul 2024 14:16:17 +0200 Subject: [PATCH 063/104] Added SPDisallowInfectedFiles standard --- ...ke-CIPPStandardSPDisallowInfectedFiles.ps1 | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 new file mode 100644 index 000000000000..8550e2f3f33f --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 @@ -0,0 +1,64 @@ +function Invoke-CIPPStandardSPDisallowInfectedFiles { + <# + .FUNCTIONALITY + Internal + .APINAME + SPDisallowInfectedFiles + .CAT + SharePoint Standards + .TAG + "lowimpact" + "CIS" + .HELPTEXT + Ensure Office 365 SharePoint infected files are disallowed for download + .ADDEDCOMPONENT + .LABEL + Disallow downloading infected files from SharePoint + .IMPACT + Low Impact + .POWERSHELLEQUIVALENT + Set-SPOTenant -DisallowInfectedFileDownload $true + .RECOMMENDEDBY + "CIS 3.0" + .DOCSDESCRIPTION + Ensure Office 365 SharePoint infected files are disallowed for download + .UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + #> + + 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 + } +} From 68f12478c43bcd3df49e56f75f3a7a1c2c0a6d5f Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 8 Jul 2024 15:13:29 +0200 Subject: [PATCH 064/104] fixed issue with entityid --- .../Public/Add-CIPPAzDataTableEntity.ps1 | 8 +- Modules/CIPPCore/Public/New-CIPPBackup.ps1 | 2 +- Modules/CIPPCore/Public/New-CIPPRestore.ps1 | 17 +++ .../CIPPCore/Public/New-CIPPRestoreTask.ps1 | 127 ++++++++++++++++++ 4 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 Modules/CIPPCore/Public/New-CIPPRestore.ps1 create mode 100644 Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 diff --git a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 index 651f54a42817..797f8846b7c0 100644 --- a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 @@ -66,7 +66,11 @@ function Add-CIPPAzDataTableEntity { Write-Host "Entity size is $entitySize. Splitting entity into multiple parts." $newEntity = @{} $newEntity['PartitionKey'] = $originalPartitionKey - $newEntity['RowKey'] = "$($originalRowKey)-part$entityIndex" + if ($entityIndex -eq 0) { + $newEntity['RowKey'] = $originalRowKey + } else { + $newEntity['RowKey'] = "$($originalRowKey)-part$entityIndex" + } $newEntity['OriginalEntityId'] = $originalRowKey $newEntity['PartIndex'] = $entityIndex $entityIndex++ @@ -118,7 +122,7 @@ function Add-CIPPAzDataTableEntity { } foreach ($row in $rows) { - Write-Host "current entity is $($row.RowKey) with $($row.PartitionKey). Our size is $([System.Text.Encoding]::UTF8.GetByteCount($($SingleEnt | ConvertTo-Json)))" + Write-Host "current entity is $($row.RowKey) with $($row.PartitionKey). Our size is $([System.Text.Encoding]::UTF8.GetByteCount($($row | ConvertTo-Json)))" Add-AzDataTableEntity -context $Context -force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $row } } else { diff --git a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 index 0893e0e89b15..b99530c6de79 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 @@ -68,7 +68,7 @@ function New-CIPPBackup { $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 for Conditional Access Policies' -Sev 'Debug' + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Created backup' -Sev 'Debug' $Result } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create backup for Conditional Access Policies: $($_.Exception.Message)" -Sev 'Error' diff --git a/Modules/CIPPCore/Public/New-CIPPRestore.ps1 b/Modules/CIPPCore/Public/New-CIPPRestore.ps1 new file mode 100644 index 000000000000..399377a02451 --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPRestore.ps1 @@ -0,0 +1,17 @@ +function New-CIPPRestore { + [CmdletBinding()] + param ( + $TenantFilter, + $RestoreValues, + $APIName = 'CIPP Restore', + $ExecutingUser + ) + + Write-Host "Scheduled Restore psproperties: $(([pscustomobject]$ScheduledBackupValues).psobject.Properties)" + Write-LogMessage -user $ExecutingUser -API $APINAME -message 'Restored backup' -Sev 'Debug' + $RestoreData = foreach ($ScheduledBackup in ([pscustomobject]$ScheduledBackupValues).psobject.Properties.Name | Where-Object { $_ -notin 'email', 'webhook', 'psa', 'backup', 'overwrite' }) { + New-CIPPRestoreTask -Task $ScheduledBackup -TenantFilter $TenantFilter -backup $RestoreValues.backup.value -overwrite $RestoreValues.overwrite + } + return $RestoreData +} + diff --git a/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 b/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 new file mode 100644 index 000000000000..64b9c4701cd9 --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 @@ -0,0 +1,127 @@ +function New-CIPPRestoreTask { + [CmdletBinding()] + param ( + $Task, + $TenantFilter, + $backup, + $overwrite + ) + $Table = Get-CippTable -tablename 'ScheduledBackup' + $BackupData = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$backup'" + $RestoreData = switch ($Task) { + 'users' { + Write-Host "Restore users for $TenantFilter" + $currentUsers = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $TenantFilter + $BackupUsers | ForEach-Object { + try { + if ($overwrite) { + $currentUsers | Where-Object { $_.id -eq $_.id } | ForEach-Object { + New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/users/$($_.id)" -tenantid $TenantFilter -body $_ -type PATCH + Write-LogMessage -message "Restored $($_.userprincipalname) from backup" -Sev 'info' + "Restored $($_.userprincipalname) from backup" + } + } else { + if ($currentUsers.id -notin $_.id) { + New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/users' -tenantid $TenantFilter -body $_ -type POST + Write-LogMessage -message "Restored $($_.userprincipalname) from backup" -Sev 'info' + "Restored $($_.userprincipalname) from backup" + + } else { + Write-LogMessage -message "User $($_.userPrincipalName) already exists in tenant $TenantFilter and overwrite is disabled" -Sev 'info' + "User $($_.userPrincipalName) already exists in tenant $TenantFilter and overwrite is disabled" + } + } + } catch { + "Could not restore user $($_.userPrincipalName): $($_.Exception.Message) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore user $($_.userPrincipalName): $($_.Exception.Message) " -Sev 'error' + } + } + } + 'groups' { + Write-Host "Restore groups for $TenantFilter" + $Groups = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $TenantFilter + $BackupGroups | ForEach-Object { + try { + if ($overwrite) { + $currentUsers | Where-Object { $_.id -eq $_.id } | ForEach-Object { + New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/groups/$($_.id)" -tenantid $TenantFilter -body $_ -type PATCH + Write-LogMessage -message "Restored $($_.userprincipalname) from backup" -Sev 'info' + "Restored group $($_.displayName) from backup" + } + } else { + if ($currentUsers.id -notin $_.id) { + New-GraphPOSTRequest -uri 'https://graph.microsoft.com/groups/' -tenantid $TenantFilter -body $_ -type POST + Write-LogMessage -message "Restored $($_.userprincipalname) from backup" -Sev 'info' + "Restored group $($_.displayName) from backup" + + } else { + Write-LogMessage -message "group $($_.group) already exists in tenant $TenantFilter and overwrite is disabled" -Sev 'info' + "group $($_.displayName) already exists in tenant $TenantFilter and overwrite is disabled" + } + } + } catch { + "Could not restore user $($_.userPrincipalName): $($_.Exception.Message) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore user $($_.userPrincipalName): $($_.Exception.Message) " -Sev 'error' + } + } + } + 'ca' { + Write-Host "Backup Conditional Access Policies for $TenantFilter" + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter + } + 'namedlocations' { + Write-Host "Backup Named Locations for $TenantFilter" + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/namedLocations?$top=999' -tenantid $TenantFilter + } + 'authstrengths' { + Write-Host "Backup Authentication Strength Policies for $TenantFilter" + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/authenticationStrength/policies' -tenantid $TenantFilter + } + 'intuneconfig' { + $GraphURLS = @("https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations?`$select=id,displayName,lastModifiedDateTime,roleScopeTagIds,microsoft.graph.unsupportedDeviceConfiguration/originalEntityTypeName&`$expand=assignments&top=1000" + 'https://graph.microsoft.com/beta/deviceManagement/windowsDriverUpdateProfiles' + "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations?`$expand=assignments&top=999" + "https://graph.microsoft.com/beta/deviceAppManagement/mobileAppConfigurations?`$expand=assignments&`$filter=microsoft.graph.androidManagedStoreAppConfiguration/appSupportsOemConfig%20eq%20true" + 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' + ) + + $GraphURLS | ForEach-Object { + $URLName = (($_).split('?') | Select-Object -First 1) -replace 'https://graph.microsoft.com/beta/deviceManagement/', '' + New-GraphGetRequest -uri "$($_)" -tenantid $TenantFilter + } | ForEach-Object { + New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName $URLName -ID $_.ID + } + } + 'intunecompliance' { + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies?$top=999' -tenantid $TenantFilter | ForEach-Object { + New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName 'deviceCompliancePolicies' -ID $_.ID + } + } + + 'intuneprotection' { + New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceAppManagement/managedAppPolicies?$top=999' -tenantid $TenantFilter | ForEach-Object { + New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName 'managedAppPolicies' -ID $_.ID + } + } + + 'CippWebhookAlerts' { + Write-Host "Backup Webhook Alerts for $TenantFilter" + $WebhookTable = Get-CIPPTable -TableName 'WebhookRules' + Get-CIPPAzDataTableEntity @WebhookTable | Where-Object { $TenantFilter -in ($_.Tenants | ConvertFrom-Json).fullvalue.defaultDomainName } + } + 'CippScriptedAlerts' { + Write-Host "Backup Scripted Alerts for $TenantFilter" + $ScheduledTasks = Get-CIPPTable -TableName 'ScheduledTasks' + Get-CIPPAzDataTableEntity @ScheduledTasks | Where-Object { $_.hidden -eq $true -and $_.command -like 'Get-CippAlert*' -and $TenantFilter -in $_.Tenant } + } + 'CippStandards' { + Write-Host "Backup Standards for $TenantFilter" + $Table = Get-CippTable -tablename 'standards' + $Filter = "PartitionKey eq 'standards' and RowKey eq '$($TenantFilter)'" + (Get-CIPPAzDataTableEntity @Table -Filter $Filter) + } + + } + return $RestoreData +} + From 5d41fd39c615ebbfa3fd432e481955035b5d7556 Mon Sep 17 00:00:00 2001 From: Esco Date: Mon, 8 Jul 2024 16:41:04 +0200 Subject: [PATCH 065/104] Create extensions.json --- .vscode/extensions.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .vscode/extensions.json 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" + ] +} From 09931d90224aaa36a8e3bcf70cdc5f7ae01a2235 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 8 Jul 2024 16:52:49 +0200 Subject: [PATCH 066/104] fixesd backup data --- .../Public/Add-CIPPAzDataTableEntity.ps1 | 48 ++++++++++--------- .../Push-ExecScheduledCommand.ps1 | 4 +- .../Public/Get-CIPPAzDatatableEntity.ps1 | 26 ++++++---- 3 files changed, 44 insertions(+), 34 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 index 797f8846b7c0..ffd25c2b50a8 100644 --- a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 @@ -7,57 +7,61 @@ function Add-CIPPAzDataTableEntity { [switch]$CreateTableIfNotExists ) - $MaxRowSize = 500000 - 100 #Maximum size of an entity - $MaxSize = 30kb # maximum size of a property value + $MaxRowSize = 500000 - 100 # Maximum size of an entity + $MaxSize = 30kb # Maximum size of a property value foreach ($SingleEnt in $Entity) { try { - Add-AzDataTableEntity -context $Context -force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $SingleEnt -ErrorAction Stop + Add-AzDataTableEntity -Context $Context -Force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $SingleEnt -ErrorAction Stop } catch [System.Exception] { if ($_.Exception.ErrorCode -eq 'PropertyValueTooLarge' -or $_.Exception.ErrorCode -eq 'EntityTooLarge') { try { - $largePropertyNames = @() + $largePropertyNames = [System.Collections.ArrayList]::new() $entitySize = 0 foreach ($key in $SingleEnt.Keys) { $propertySize = [System.Text.Encoding]::UTF8.GetByteCount($SingleEnt[$key].ToString()) $entitySize = $entitySize + $propertySize if ($propertySize -gt $MaxSize) { - $largePropertyNames = $largePropertyNames + $key + $largePropertyNames.Add($key) } + } if ($largePropertyNames.Count -gt 0) { + $splitInfoList = [System.Collections.ArrayList]@() foreach ($largePropertyName in $largePropertyNames) { $dataString = $SingleEnt[$largePropertyName] $splitCount = [math]::Ceiling($dataString.Length / $MaxSize) - $splitData = @() + $splitData = [System.Collections.ArrayList]@() for ($i = 0; $i -lt $splitCount; $i++) { $start = $i * $MaxSize - $splitData = $splitData + $dataString.Substring($start, [Math]::Min($MaxSize, $dataString.Length - $start)) + $splitData.Add($dataString.Substring($start, [Math]::Min($MaxSize, $dataString.Length - $start))) > $null } - $splitPropertyNames = @() + $splitPropertyNames = [System.Collections.ArrayList]@() for ($i = 0; $i -lt $splitData.Count; $i++) { - $splitPropertyNames = $splitPropertyNames + "${largePropertyName}_Part$i" + $splitPropertyNames.Add("${largePropertyName}_Part$i") > $null } $splitInfo = @{ OriginalHeader = $largePropertyName SplitHeaders = $splitPropertyNames } - $SingleEnt['SplitOverProps'] = ($splitInfo | ConvertTo-Json).ToString() + $splitInfoList.Add($splitInfo) > $null $SingleEnt.Remove($largePropertyName) for ($i = 0; $i -lt $splitData.Count; $i++) { $SingleEnt[$splitPropertyNames[$i]] = $splitData[$i] } } + + $SingleEnt['SplitOverProps'] = ($splitInfoList | ConvertTo-Json).ToString() } # Check if the entity is still too large $entitySize = [System.Text.Encoding]::UTF8.GetByteCount($($SingleEnt | ConvertTo-Json)) if ($entitySize -gt $MaxRowSize) { - $rows = @() + $rows = [System.Collections.ArrayList]@() $originalPartitionKey = $SingleEnt.PartitionKey $originalRowKey = $SingleEnt.RowKey $entityIndex = 0 @@ -75,7 +79,7 @@ function Add-CIPPAzDataTableEntity { $newEntity['PartIndex'] = $entityIndex $entityIndex++ - $propertiesToRemove = @() + $propertiesToRemove = [System.Collections.ArrayList]@() foreach ($key in $SingleEnt.Keys) { $newEntitySize = [System.Text.Encoding]::UTF8.GetByteCount($($newEntity | ConvertTo-Json)) if ($newEntitySize -lt $MaxRowSize) { @@ -83,15 +87,15 @@ function Add-CIPPAzDataTableEntity { if ($propertySize -gt $MaxRowSize) { $dataString = $SingleEnt[$key] $splitCount = [math]::Ceiling($dataString.Length / $MaxSize) - $splitData = @() + $splitData = [System.Collections.ArrayList]@() for ($i = 0; $i -lt $splitCount; $i++) { $start = $i * $MaxSize - $splitData = $splitData + $dataString.Substring($start, [Math]::Min($MaxSize, $dataString.Length - $start)) + $splitData.Add($dataString.Substring($start, [Math]::Min($MaxSize, $dataString.Length - $start))) > $null } - $splitPropertyNames = @() + $splitPropertyNames = [System.Collections.ArrayList]@() for ($i = 0; $i -lt $splitData.Count; $i++) { - $splitPropertyNames = $splitPropertyNames + "${key}_Part$i" + $splitPropertyNames.Add("${key}_Part$i") > $null } for ($i = 0; $i -lt $splitData.Count; $i++) { @@ -100,7 +104,7 @@ function Add-CIPPAzDataTableEntity { } else { $newEntity[$key] = $SingleEnt[$key] } - $propertiesToRemove = $propertiesToRemove + $key + $propertiesToRemove.Add($key) > $null } } @@ -108,7 +112,7 @@ function Add-CIPPAzDataTableEntity { $SingleEnt.Remove($prop) } - $rows = $rows + $newEntity + $rows.Add($newEntity) > $null $entitySize = [System.Text.Encoding]::UTF8.GetByteCount($($SingleEnt | ConvertTo-Json)) } @@ -118,19 +122,19 @@ function Add-CIPPAzDataTableEntity { $SingleEnt['PartIndex'] = $entityIndex $SingleEnt['PartitionKey'] = $originalPartitionKey - $rows = $rows + $SingleEnt + $rows.Add($SingleEnt) > $null } foreach ($row in $rows) { Write-Host "current entity is $($row.RowKey) with $($row.PartitionKey). Our size is $([System.Text.Encoding]::UTF8.GetByteCount($($row | ConvertTo-Json)))" - Add-AzDataTableEntity -context $Context -force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $row + Add-AzDataTableEntity -Context $Context -Force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $row } } else { - Add-AzDataTableEntity -context $Context -force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $SingleEnt + Add-AzDataTableEntity -Context $Context -Force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $SingleEnt } } catch { - throw "Error processing entity: $($_.Exception.Message)." + throw "Error processing entity: $($_.Exception.Message) Linenumner: $($_.InvocationInfo.ScriptLineNumber)" } } else { Write-Host "THE ERROR IS $($_.Exception.ErrorCode). The size of the entity is $entitySize." 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/Get-CIPPAzDatatableEntity.ps1 b/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 index b72e56a0f477..0781e820a125 100644 --- a/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 @@ -13,23 +13,25 @@ function Get-CIPPAzDataTableEntity { $Results = Get-AzDataTableEntity @PSBoundParameters $mergedResults = @{} + # First pass: Collect all parts and complete entities foreach ($entity in $Results) { if ($entity.OriginalEntityId) { $entityId = $entity.OriginalEntityId if (-not $mergedResults.ContainsKey($entityId)) { $mergedResults[$entityId] = @{ - Parts = @() + Parts = New-Object 'System.Collections.ArrayList' } } - $mergedResults[$entityId]['Parts'] = $mergedResults[$entityId]['Parts'] + @($entity) + $mergedResults[$entityId]['Parts'].Add($entity) > $null } else { $mergedResults[$entity.RowKey] = @{ Entity = $entity - Parts = @() + Parts = New-Object 'System.Collections.ArrayList' } } } + # Second pass: Reassemble entities from parts $finalResults = @() foreach ($entityId in $mergedResults.Keys) { $entityData = $mergedResults[$entityId] @@ -38,7 +40,7 @@ function Get-CIPPAzDataTableEntity { $parts = $entityData.Parts | Sort-Object PartIndex foreach ($part in $parts) { foreach ($key in $part.PSObject.Properties.Name) { - if ($key -notin @('OriginalEntityId', 'PartIndex', 'PartitionKey', 'RowKey', 'ETag', 'Timestamp')) { + if ($key -notin @('OriginalEntityId', 'PartIndex', 'PartitionKey', 'RowKey')) { if ($fullEntity.PSObject.Properties[$key]) { $fullEntity | Add-Member -MemberType NoteProperty -Name $key -Value ($fullEntity.$key + $part.$key) -Force } else { @@ -55,15 +57,19 @@ function Get-CIPPAzDataTableEntity { } } + # Third pass: Process split properties and remerge them foreach ($entity in $finalResults) { if ($entity.SplitOverProps) { - $splitInfo = $entity.SplitOverProps | ConvertFrom-Json - $mergedData = [string]::Join('', ($splitInfo.SplitHeaders | ForEach-Object { $entity.$_ })) - $entity | Add-Member -NotePropertyName $splitInfo.OriginalHeader -NotePropertyValue $mergedData -Force - $propsToRemove = $splitInfo.SplitHeaders + 'SplitOverProps' - foreach ($prop in $propsToRemove) { - $entity.PSObject.Properties.Remove($prop) + $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') } } From 6293cf022bd1d7bcde900cc44a92f77e8570af32 Mon Sep 17 00:00:00 2001 From: Esco Date: Mon, 8 Jul 2024 16:21:43 +0200 Subject: [PATCH 067/104] Update Comments --- .../Invoke-CIPPStandardSPAzureB2B.ps1 | 48 +++++++++--------- .../Invoke-CIPPStandardSPDirectSharing.ps1 | 48 +++++++++--------- ...ke-CIPPStandardSPDisallowInfectedFiles.ps1 | 48 +++++++++--------- .../Invoke-CIPPStandardSPEmailAttestation.ps1 | 50 ++++++++++--------- ...e-CIPPStandardSPExternalUserExpiration.ps1 | 50 ++++++++++--------- 5 files changed, 127 insertions(+), 117 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 index 492d59fa4608..940bad58b135 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 @@ -1,29 +1,31 @@ function Invoke-CIPPStandardSPAzureB2B { <# .FUNCTIONALITY - Internal - .APINAME - SPAzureB2B - .CAT - SharePoint Standards - .TAG - "lowimpact" - "CIS" - .HELPTEXT - Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled - .ADDEDCOMPONENT - .LABEL - Enable SharePoint and OneDrive integration with Azure AD B2B - .IMPACT - Low Impact - .POWERSHELLEQUIVALENT - Set-SPOTenant -EnableAzureADB2BIntegration $true - .RECOMMENDEDBY - "CIS 3.0" - .DOCSDESCRIPTION - Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled - .UPDATECOMMENTBLOCK - Run the Tools\Update-StandardsComments.ps1 script to update this comment block + Internal + .COMPONENT + (APIName) SPAzureB2B + .SYNOPSIS + 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 + LABEL + Enable SharePoint and OneDrive integration with Azure AD B2B + 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 #> param($Tenant, $Settings) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 index 95fb6ffe695e..9cfaf3c10317 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 @@ -1,29 +1,31 @@ function Invoke-CIPPStandardSPDirectSharing { <# .FUNCTIONALITY - Internal - .APINAME - SPDirectSharing - .CAT - SharePoint Standards - .TAG - "mediumimpact" - "CIS" - .HELPTEXT - Ensure default link sharing is set to Direct in SharePoint and OneDrive - .ADDEDCOMPONENT - .LABEL - Default sharing to Direct users - .IMPACT - Medium Impact - .POWERSHELLEQUIVALENT - Set-SPOTenant -DefaultSharingLinkType Direct - .RECOMMENDEDBY - "CIS 3.0" - .DOCSDESCRIPTION - Ensure default link sharing is set to Direct in SharePoint and OneDrive - .UPDATECOMMENTBLOCK - Run the Tools\Update-StandardsComments.ps1 script to update this comment block + Internal + .COMPONENT + (APIName) SPDirectSharing + .SYNOPSIS + 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 + LABEL + Default sharing to Direct users + 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 #> param($Tenant, $Settings) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 index 8550e2f3f33f..42f3498e4a3e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 @@ -1,29 +1,31 @@ function Invoke-CIPPStandardSPDisallowInfectedFiles { <# .FUNCTIONALITY - Internal - .APINAME - SPDisallowInfectedFiles - .CAT - SharePoint Standards - .TAG - "lowimpact" - "CIS" - .HELPTEXT - Ensure Office 365 SharePoint infected files are disallowed for download - .ADDEDCOMPONENT - .LABEL - Disallow downloading infected files from SharePoint - .IMPACT - Low Impact - .POWERSHELLEQUIVALENT - Set-SPOTenant -DisallowInfectedFileDownload $true - .RECOMMENDEDBY - "CIS 3.0" - .DOCSDESCRIPTION - Ensure Office 365 SharePoint infected files are disallowed for download - .UPDATECOMMENTBLOCK - Run the Tools\Update-StandardsComments.ps1 script to update this comment block + Internal + .COMPONENT + (APIName) SPDisallowInfectedFiles + .SYNOPSIS + 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 + LABEL + Disallow downloading infected files from SharePoint + 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 #> param($Tenant, $Settings) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 index b70d89fce594..140c65607780 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 @@ -1,30 +1,32 @@ function Invoke-CIPPStandardSPEmailAttestation { <# .FUNCTIONALITY - Internal - .APINAME - SPEmailAttestation - .CAT - SharePoint Standards - .TAG - "mediumimpact" - "CIS" - .HELPTEXT - Ensure reauthentication with verification code is restricted - .ADDEDCOMPONENT - {"type":"number","name":"standards.SPEmailAttestation.Days","label":"Require reauth every X Days (Default 15)"} - .LABEL - Require reauthentication with verification code - .IMPACT - Medium Impact - .POWERSHELLEQUIVALENT - Set-SPOTenant -EmailAttestationRequired $true -EmailAttestationReAuthDays 15 - .RECOMMENDEDBY - "CIS 3.0" - .DOCSDESCRIPTION - Ensure reauthentication with verification code is restricted - .UPDATECOMMENTBLOCK - Run the Tools\Update-StandardsComments.ps1 script to update this comment block + Internal + .COMPONENT + (APIName) SPEmailAttestation + .SYNOPSIS + 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)"} + LABEL + Require reauthentication with verification code + 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 #> param($Tenant, $Settings) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 index 581691d4153d..f05818c0d120 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 @@ -1,30 +1,32 @@ function Invoke-CIPPStandardSPExternalUserExpiration { <# .FUNCTIONALITY - Internal - .APINAME - SPExternalUserExpiration - .CAT - SharePoint Standards - .TAG - "mediumimpact" - "CIS" - .HELPTEXT - Ensure guest access to a site or OneDrive will expire automatically - .ADDEDCOMPONENT - {"type":"number","name":"standards.SPExternalUserExpiration.Days","label":"Days until expiration (Default 60)"} - .LABEL - Set guest access to expire automatically - .IMPACT - Medium Impact - .POWERSHELLEQUIVALENT - Set-SPOTenant -ExternalUserExpireInDays 30 -ExternalUserExpirationRequired $True - .RECOMMENDEDBY - "CIS 3.0" - .DOCSDESCRIPTION - Ensure guest access to a site or OneDrive will expire automatically - .UPDATECOMMENTBLOCK - Run the Tools\Update-StandardsComments.ps1 script to update this comment block + Internal + .COMPONENT + (APIName) SPExternalUserExpiration + .SYNOPSIS + 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)"} + LABEL + Set guest access to expire automatically + 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 #> param($Tenant, $Settings) From f2dc8f590760d59ce0554f6fa6e9936a08d0e3ad Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Jul 2024 15:26:34 -0400 Subject: [PATCH 068/104] Hudu Extension --- .../Public/Add-CIPPAzDataTableEntity.ps1 | 2 +- .../Public/Get-CIPPAzDatatableEntity.ps1 | 2 +- .../Private/Hudu/Get-HuduFormattedBlock.ps1 | 12 + .../Private/Hudu/Get-HuduFormattedField.ps1 | 13 + .../Private/Hudu/Get-HuduLinkBlock.ps1 | 3 + .../Add-HuduAssetLayoutM365Field.ps1 | 28 + .../Get-ExtensionCacheData.ps1 | 14 + .../Push-ExtensionSyncData.ps1 | 12 + .../Register-CippExtensionScheduledTasks.ps1 | 1 + .../Sync-CippExtensionData.ps1 | 80 +- .../Public/Hudu/Get-HuduMapping.ps1 | 2 +- .../Public/Hudu/Invoke-HuduExtensionSync.ps1 | 858 ++++++++++++++++++ 12 files changed, 1018 insertions(+), 9 deletions(-) create mode 100644 Modules/CippExtensions/Private/Hudu/Get-HuduFormattedBlock.ps1 create mode 100644 Modules/CippExtensions/Private/Hudu/Get-HuduFormattedField.ps1 create mode 100644 Modules/CippExtensions/Private/Hudu/Get-HuduLinkBlock.ps1 create mode 100644 Modules/CippExtensions/Public/Extension Functions/Add-HuduAssetLayoutM365Field.ps1 create mode 100644 Modules/CippExtensions/Public/Extension Functions/Get-ExtensionCacheData.ps1 create mode 100644 Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 diff --git a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 index ffd25c2b50a8..ae011505e430 100644 --- a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 @@ -55,7 +55,7 @@ function Add-CIPPAzDataTableEntity { } } - $SingleEnt['SplitOverProps'] = ($splitInfoList | ConvertTo-Json).ToString() + $SingleEnt['SplitOverProps'] = ($splitInfoList | ConvertTo-Json -Compress).ToString() } # Check if the entity is still too large diff --git a/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 b/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 index 0781e820a125..60ee9464ab93 100644 --- a/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 @@ -40,7 +40,7 @@ function Get-CIPPAzDataTableEntity { $parts = $entityData.Parts | Sort-Object PartIndex foreach ($part in $parts) { foreach ($key in $part.PSObject.Properties.Name) { - if ($key -notin @('OriginalEntityId', 'PartIndex', 'PartitionKey', 'RowKey')) { + if ($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 { 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..99cc3d42af27 --- /dev/null +++ b/Modules/CippExtensions/Private/Hudu/Get-HuduLinkBlock.ps1 @@ -0,0 +1,3 @@ +function Get-HuduLinkBlock($URL, $Icon, $Title) { + return "" +} 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/Push-ExtensionSyncData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Push-ExtensionSyncData.ps1 index ebee9385b07a..09966538dbff 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Push-ExtensionSyncData.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Push-ExtensionSyncData.ps1 @@ -3,4 +3,16 @@ function Push-ExtensionSyncData { $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 index 32aff8ef8ff7..9582ff40c00b 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 @@ -28,6 +28,7 @@ function Register-CIPPExtensionScheduledTasks { } $SyncTypes.Add('Overview') + $SyncTypes.Add('Groups') if ($FieldSync.Users) { $SyncTypes.Add('Users') diff --git a/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 index 7cb1c9017a78..07eaec76dd9c 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 @@ -38,7 +38,7 @@ function Sync-CippExtensionData { @{ id = 'AllRoles' method = 'GET' - url = '/directoryRoles?$top=999' + url = '/directoryRoles' }, @{ id = 'Domains' @@ -50,11 +50,6 @@ function Sync-CippExtensionData { method = 'GET' url = '/subscribedSkus?$top=999' }, - @{ - id = 'Groups' - method = 'GET' - url = '/groups?$top=999&$select=id,createdDateTime,displayName,description,mail,mailEnabled,mailNickname,resourceProvisioningOptions,securityEnabled,visibility,organizationId,onPremisesSamAccountName,membershipRule,grouptypes,onPremisesSyncEnabled,resourceProvisioningOptions,userPrincipalName' - }, @{ id = 'ConditionalAccess' method = 'GET' @@ -69,6 +64,16 @@ function Sync-CippExtensionData { id = 'Subscriptions' method = 'GET' url = '/directory/subscriptions?$top=999' + }, + @{ + id = 'OneDriveUsage' + method = 'GET' + url = "reports/getOneDriveUsageAccountDetail(period='D7')?`$format=application/json" + }, + @{ + id = 'MailboxUsage' + method = 'GET' + url = "reports/getMailboxUsageDetail(period='D7')?`$format=application/json" } ) @@ -79,6 +84,15 @@ function Sync-CippExtensionData { noPagination = $true } }) + $AdditionalRequests = @( + @{ + ParentId = 'AllRoles' + graphRequest = @{ + url = '/directoryRoles/{0}/members?$select=id,displayName,userPrincipalName' + method = 'GET' + } + } + ) } 'Users' { [System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @( @@ -89,6 +103,24 @@ function Sync-CippExtensionData { } ) } + '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 = @( @{ @@ -107,6 +139,16 @@ function Sync-CippExtensionData { 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' @@ -155,6 +197,32 @@ function Sync-CippExtensionData { } } + 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 { + [PSCustomObject]@{ + id = $_.id + method = $GraphRequest.method + url = $GraphRequest.url -f $_.id + } + } + #Write-Information ($AdditionalRequestQueries | ConvertTo-Json -Depth 10 -Compress) + $AdditionalResults = New-GraphBulkRequest -Requests $AdditionalRequestQueries -tenantid $TenantFilter + $AdditionalResults | ForEach-Object { + Write-Information ($_ | ConvertTo-Json -Depth 10 -Compress) + $Entity = @{ + PartitionKey = $TenantFilter + SyncType = $SyncType + RowKey = '{0}_{1}' -f $ParentId, $_.id + Data = [string]($_.body.value | ConvertTo-Json -Depth 10 -Compress) + } + Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force + } + } + } + $TenantResults | Select-Object id, body | ForEach-Object { $Entity = @{ PartitionKey = $TenantFilter diff --git a/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1 index a8d775168cf7..7ffbddfa57a0 100644 --- a/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Get-HuduMapping.ps1 @@ -38,4 +38,4 @@ function Get-HuduMapping { return $MappingObj -} \ No newline at end of file +} diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 new file mode 100644 index 000000000000..96cc65cbafbb --- /dev/null +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -0,0 +1,858 @@ +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 } + + 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 + + $null = Add-HuduAssetLayoutM365Field -AssetLayoutId $PeopleLayoutId + $null = Add-HuduAssetLayoutM365Field -AssetLayoutId $DeviceLayoutId + + $importDomains = $false + #$monitorDomains = [System.Convert]::ToBoolean($env:monitorDomains) + $IntuneDesktopDeviceTypes = $env:IntuneDesktopDeviceTypes -split ',' + $ExcludeSerials = $env:ExcludeSerials -split ',' + #$LicenseLookup = Get-LicenseLookup + #$AssignedMap = Get-AssignedMap + #$AssignedNameMap = Get-AssignedNameMap + $EnableCIPP = $true + #$CIPPURL = $env:CIPPURL + + $ExtensionCache = Get-ExtensionCacheData -TenantFilter $Tenant.defaultDomainName + + try { + $company_id = $TenantMap.IntegrationId + + $PeopleLayout = Get-HuduAssetLayouts -Id $PeopleLayoutId + $People = Get-HuduAssets -CompanyId $company_id -AssetLayoutId $PeopleLayout.id + + $DesktopsLayout = Get-HuduAssetLayouts -Id $DeviceLayoutId + $HuduDesktopDevices = Get-HuduAssets -CompanyId $company_id -AssetLayoutId $DesktopsLayout.id + + $HuduRelations = Get-HuduRelations + + $HuduDevices = $HuduDesktopDevices + + $CustomerLinks = "
    +
    +
    +
    +
    +
    +
    +
    " + + + #$Users = Get-BulkResultByID -Results $TenantResults -ID 'Users' + $Users = $ExtensionCache.Users + $licensedUsers = $Users | Where-Object { $null -ne $_.assignedLicenses.skuId } | Sort-Object userPrincipalName + + $CompanyResult.users = ($licensedUsers | Measure-Object).count + + #$AllRoles = Get-BulkResultByID -Results $TenantResults -ID 'AllRoles' + $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 = Get-BulkResultByID -Results $TenantResults -ID 'Licenses' + $Licenses = $ExtensionCache.Licenses + if ($Licenses) { + $pre = "
    +

    Current Licenses

    +
    " + + $post = '
    ' + + $licenseOut = $Licenses | Where-Object { $_.PrepaidUnits.Enabled -gt 0 } | Select-Object @{N = 'License Name'; E = { $($LicenseLookup.$($_.SkuPartNumber)) } }, @{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 = Get-BulkResultByID -Results $TenantResults -ID 'Devices' + $devices = $ExtensionCache.Devices + $CompanyResult.Devices = ($Devices | Measure-Object).count + + #$DeviceCompliancePolicies = Get-BulkResultByID -Results $TenantResults -ID 'DeviceCompliancePolicies' + $DeviceCompliancePolicies = $ExtensionCache.DeviceCompliancePolicies + <#[System.Collections.Generic.List[PSCustomObject]]$PolicyRequestArray = @() + foreach ($CompliancePolicy in $DeviceCompliancePolicies) { + $PolicyRequestArray.add(@{ + id = $CompliancePolicy.id + method = 'GET' + url = "/deviceManagement/deviceCompliancePolicies/$($CompliancePolicy.id)/deviceStatuses" + }) + } + + try { + $PolicyReturn = New-GraphBulkRequest -Headers $AuthHeaders -Requests $PolicyRequestArray -tenantid $TenantFilter + } catch { + $CompanyResult.Errors.add("Company: Unable to fetch Policies $_") + $PolicyReturn = $null + }#> + + $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 = Get-BulkResultByID -Results $TenantResults -ID 'Groups' + $AllGroups = $ExtensionCache.Groups + + <#[System.Collections.Generic.List[PSCustomObject]]$GroupRequestArray = @() + foreach ($Group in $AllGroups) { + $GroupRequestArray.add(@{ + id = $Group.id + method = 'GET' + url = "/groups/$($Group.id)/members" + }) + } + try { + $GroupMembersReturn = New-GraphBulkRequest -Headers $AuthHeaders -Requests $GroupRequestArray -tenantid $TenantFilter + } catch { + $CompanyResult.Errors.add("Company: Unable to fetch Group Membership Details $_") + $GroupMembersReturn = $null + }#> + + $Groups = foreach ($Group in $AllGroups) { + $Members = $ExtensionCache."Groups_$($Result.id)" + [pscustomobject]@{ + ID = $Group.id + DisplayName = $Group.displayName + Members = $Members + } + } + + #$AllConditionalAccessPolicies = Get-BulkResultByID -Results $TenantResults -ID 'ConditionalAccess' + $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 = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getOneDriveUsageAccountDetail(period='D7')" -tenantid $TenantFilter | ConvertFrom-Csv + $OneDriveDetails = $ExtensionCache.OneDriveUsage + } else { + $CompanyResult.Errors.add("Company: Unable to fetch One Drive Details $_") + $OneDriveDetails = $null + } + + + <#try { + $CASFull = New-GraphGetRequest -uri "https://outlook.office365.com/adminapi/beta/$($tenantfilter)/CasMailbox" -Tenantid $tenantfilter -scope ExchangeOnline -noPagination $true + } catch { + $CASFull = $null + $CompanyResult.Errors.add("Company: Unable to fetch CAS Mailbox Details $_") + }#> + + <#try { + $MailboxDetailedFull = New-ExoRequest -TenantID $TenantFilter -cmdlet 'Get-Mailbox' + } catch { + $CompanyResult.Errors.add("Company: Unable to fetch Mailbox Details $_") + $MailboxDetailedFull = $null + }#> + $MailboxDetailedFull = $ExtensionCache.Mailboxes + + + <#try { + $MailboxStatsFull = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/reports/getMailboxUsageDetail(period='D7')" -tenantid $TenantFilter | ConvertFrom-Csv + } catch { + $CompanyResult.Errors.add("Company: Unable to fetch Mailbox Statistic Details $_") + $MailboxStatsFull = $null + }#> + if ($ExtensionCache.MailboxUsage) { + $MailboxStatsFull = $ExtensionCache.MailboxUsage + } else { + $MailboxStatsFull = $null + $CompanyResult.Errors.add('Company: Unable to fetch Mailbox Statistic Details') + } + + 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 { $_.ExternalDirectoryObjectId -eq $User.iD } + $StatsRequest = $MailboxStatsFull | Where-Object { $_.'User Principal Name' -eq $User.UserPrincipalName } + + try { + $PermsRequest = New-GraphGetRequest -uri "https://outlook.office365.com/adminapi/beta/$($tenantfilter)/Mailbox('$($User.ID)')/MailboxPermission" -Tenantid $tenantfilter -scope ExchangeOnline -noPagination $true + } catch { + $PermsRequest = $null + } + + $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.'Storage Used (Byte)' / 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.'Item Count', 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 + try { + "$($LicenseLookup.$SkuPartNumber)" + } catch { + "$SkuPartNumber" + } + }) -join ', ' + + $UserOneDriveDetails = $OneDriveDetails | Where-Object { $_.'Owner Principal Name' -eq $user.UserPrincipalName } + + + + [System.Collections.Generic.List[PSCustomObject]]$OneDriveFormatted = @() + if ($UserOneDriveDetails) { + try { + $OneDriveUsePercent = [math]::Round([float](($UserOneDriveDetails.'Storage Used (Byte)' / $UserOneDriveDetails.'Storage Allocated (Byte)') * 100), 2) + $StorageUsed = [math]::Round($UserOneDriveDetails.'Storage Used (Byte)' / 1024 / 1024 / 1024, 2) + $StorageAllocated = [math]::Round($UserOneDriveDetails.'Storage Allocated (Byte)' / 1024 / 1024 / 1024, 2) + } catch { + $OneDriveUsePercent = 100 + $StorageUsed = 0 + $StorageAllocated = 0 + } + + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Owner Principal Name' -Value "$($UserOneDriveDetails.'Owner Principal Name')")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'One Drive URL' -Value "$($UserOneDriveDetails.'Site URL')")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Is Deleted' -Value "$($UserOneDriveDetails.'Is Deleted')")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Last Activity Date' -Value "$($UserOneDriveDetails.'Last Activity Date')")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'File Count' -Value "$($UserOneDriveDetails.'File Count')")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Active File Count' -Value "$($UserOneDriveDetails.'Active File Count')")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Storage Used (Byte)' -Value "$($UserOneDriveDetails.'Storage Used (Byte)')")) + $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Storage Allocated (Byte)' -Value "$($UserOneDriveDetails.'Storage Allocated (Byte)')")) + $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 = '
      ' + foreach ($Policy in $UserPolicies) { + $UserPoliciesFormatted = $UserPoliciesFormatted + "
    • $($Policy.displayName)
    • " + } + $UserPoliciesFormatted = $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 { $_.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).auth/login/aad?post_login_redirect_uri=$($CIPPURL)identity/administration/users/view?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'far fa-eye' -Title 'CIPP - View User')) + $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL).auth/login/aad?post_login_redirect_uri=$($CIPPURL)identity/administration/users/edit?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'fas fa-user-cog' -Title 'CIPP - Edit User')) + $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL).auth/login/aad?post_login_redirect_uri=$($CIPPURL)identity/administration/ViewBec?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'fas fa-user-secret' -Title 'CIPP - Research Compromise')) + } + + [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 'Azure AD')) + $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://endpoint.microsoft.com/$($Tenant.defaultDomainName)/#blade/Microsoft_AAD_IAM/UserDetailsMenuBlade/Profile/userId/$($User.ID)" -Icon 'fas fa-laptop' -Title 'EPM (User)')) + $UserLinksFormatted.add((Get-HuduLinkBlock -URL "https://endpoint.microsoft.com/$($Tenant.defaultDomainName)/#blade/Microsoft_AAD_IAM/UserDetailsMenuBlade/Devices/userId/$($User.ID)" -Icon 'fas fa-laptop' -Title 'EPM (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)
    " + + $UserAssetFields = @{ + microsoft_365 = $UserBody + email_address = $user.UserPrincipalName + } + + + $HuduUserCount = ($HuduUser | Measure-Object).count + if ($HuduUserCount -eq 1) { + $null = Set-HuduAsset -asset_id $HuduUser.id -Name $HuduUser.name -company_id $company_id -asset_layout_id $PeopleLayout.id -Fields $UserAssetFields + + } 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 + } + } 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 + } + + if ($HuduDevice) { + if (($HuduDevice | Measure-Object).count -eq 1) { + $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 + + $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 + $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: $_") + } + + } catch { + $CompanyResult.Errors.add("Company: A fatal error occured: $_") + } + return $CompanyResult +} From d307b08430841a7fe5b9a319a98f97e988f88432 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Jul 2024 15:26:43 -0400 Subject: [PATCH 069/104] Update Initialize-DevEnvironment.ps1 --- Tools/Initialize-DevEnvironment.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 From ebcf35f31bce23bb073bc7cc09764271b5242c2f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Jul 2024 15:59:37 -0400 Subject: [PATCH 070/104] Create extension specific jobs Add scheduled tasks recurrence fallback --- .../CIPPCore/Public/Add-CIPPScheduledTask.ps1 | 2 +- .../Register-CippExtensionScheduledTasks.ps1 | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index 7e629e038798..ede80f1ca5d5 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -44,7 +44,7 @@ function Add-CIPPScheduledTask { Command = [string]$task.Command.value Parameters = [string]$Parameters ScheduledTime = [string]$task.ScheduledTime - Recurrence = [string]$task.Recurrence.value + Recurrence = [string]$task.Recurrence.value ?? $task.Recurrence PostExecution = [string]$PostExecution AdditionalProperties = [string]$AdditionalProperties Hidden = [bool]$Hidden diff --git a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 index 9582ff40c00b..971976e38a87 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 @@ -65,6 +65,24 @@ function Register-CIPPExtensionScheduledTasks { $null = Add-CIPPScheduledTask -Task $Task -hidden $true -SyncType $SyncType } } + + # push cached data to extension + $in30mins = [int64](([datetime]::UtcNow.AddMinutes(30)) - (Get-Date '1/1/1970')).TotalSeconds + $Task = @{ + Name = "$Extension Extension Sync" + Command = @{ + value = 'Push-CippExtensionData' + label = 'Push-CippExtensionData' + } + Parameters = @{ + TenantFilter = $Tenant.defaultDomainName + Extension = $Extension + } + Recurrence = '1d' + ScheduledTime = $in30mins + TenantFilter = $Tenant.defaultDomainName + } + $null = Add-CIPPScheduledTask -Task $Task -hidden $true -SyncType $Extension } } } From fa7de01f8359b6e182d13efcb64126f982000ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 8 Jul 2024 22:19:00 +0200 Subject: [PATCH 071/104] Use error message --- .../Invoke-CIPPStandardAppDeploy.ps1 | 58 +++++++++---------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 index c6c2509795ac..0b5ba7f47945 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 @@ -1,31 +1,32 @@ function Invoke-CIPPStandardAppDeploy { <# .FUNCTIONALITY - Internal - .APINAME - AppDeploy - .CAT - Entra Standards - .TAG - "lowimpact" - "CIS" - .HELPTEXT - Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access - .DOCSDESCRIPTION - Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access. This is a tenant wide setting and overrules any settings set on the site level - .ADDEDCOMPONENT - .LABEL - Disable Resharing by External Users - .IMPACT - High Impact - .POWERSHELLEQUIVALENT - Update-MgBetaAdminSharepointSetting - .RECOMMENDEDBY - "CIS" - .DOCSDESCRIPTION - Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access - .UPDATECOMMENTBLOCK - Run the Tools\Update-StandardsComments.ps1 script to update this comment block + Internal + .COMPONENT + (APIName) AppDeploy + .SYNOPSIS + 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"} + LABEL + Deploy Application + 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) @@ -38,13 +39,8 @@ function Invoke-CIPPStandardAppDeploy { Write-LogMessage -API 'Standards' -tenant $tenant -message "Added $App to $Tenant and update it's permissions" -sev Info } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to add app $App" -sev Error + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to add app $App. Error: $ErrorMessage" -sev Error } } } } - - - - - From c15dd01ff62e1419b9842eb748f962c30bb87430 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Jul 2024 16:25:24 -0400 Subject: [PATCH 072/104] Extension sync tweaks --- .../Register-CippExtensionScheduledTasks.ps1 | 34 +++++++++++-------- .../Sync-CippExtensionData.ps1 | 29 +++++++++------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 index 971976e38a87..e56fee6cf980 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 @@ -11,6 +11,7 @@ function Register-CIPPExtensionScheduledTasks { # 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') @@ -66,23 +67,26 @@ function Register-CIPPExtensionScheduledTasks { } } - # push cached data to extension - $in30mins = [int64](([datetime]::UtcNow.AddMinutes(30)) - (Get-Date '1/1/1970')).TotalSeconds - $Task = @{ - Name = "$Extension Extension Sync" - Command = @{ - value = 'Push-CippExtensionData' - label = 'Push-CippExtensionData' - } - Parameters = @{ - TenantFilter = $Tenant.defaultDomainName - Extension = $Extension + $ExistingTask = $PushTasks | Where-Object { $_.Tenant -eq $Tenant.defaultDomainName -and $_.SyncType -eq $Extension } + if (!$ExistingTask -or $Reschedule.IsPresent) { + # push cached data to extension + $in30mins = [int64](([datetime]::UtcNow.AddMinutes(30)) - (Get-Date '1/1/1970')).TotalSeconds + $Task = @{ + Name = "$Extension Extension Sync" + Command = @{ + value = 'Push-CippExtensionData' + label = 'Push-CippExtensionData' + } + Parameters = @{ + TenantFilter = $Tenant.defaultDomainName + Extension = $Extension + } + Recurrence = '1d' + ScheduledTime = $in30mins + TenantFilter = $Tenant.defaultDomainName } - Recurrence = '1d' - ScheduledTime = $in30mins - TenantFilter = $Tenant.defaultDomainName + $null = Add-CIPPScheduledTask -Task $Task -hidden $true -SyncType $Extension } - $null = Add-CIPPScheduledTask -Task $Task -hidden $true -SyncType $Extension } } } diff --git a/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 index 07eaec76dd9c..2e27427ecb82 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 @@ -202,23 +202,26 @@ function Sync-CippExtensionData { $ParentId = $AdditionalRequest.ParentId $GraphRequest = $AdditionalRequest.graphRequest.PSObject.Copy() $AdditionalRequestQueries = ($TenantResults | Where-Object { $_.id -eq $ParentId }).body.value | ForEach-Object { - [PSCustomObject]@{ - id = $_.id - method = $GraphRequest.method - url = $GraphRequest.url -f $_.id + if ($_.id) { + [PSCustomObject]@{ + id = $_.id + method = $GraphRequest.method + url = $GraphRequest.url -f $_.id + } } } #Write-Information ($AdditionalRequestQueries | ConvertTo-Json -Depth 10 -Compress) - $AdditionalResults = New-GraphBulkRequest -Requests $AdditionalRequestQueries -tenantid $TenantFilter - $AdditionalResults | ForEach-Object { - Write-Information ($_ | ConvertTo-Json -Depth 10 -Compress) - $Entity = @{ - PartitionKey = $TenantFilter - SyncType = $SyncType - RowKey = '{0}_{1}' -f $ParentId, $_.id - Data = [string]($_.body.value | ConvertTo-Json -Depth 10 -Compress) + if (($AdditionalRequestQueries | Measure-Object).Count -gt 0) { + $AdditionalResults = New-GraphBulkRequest -Requests $AdditionalRequestQueries -tenantid $TenantFilter + $AdditionalResults | ForEach-Object { + $Entity = @{ + PartitionKey = $TenantFilter + SyncType = $SyncType + RowKey = '{0}_{1}' -f $ParentId, $_.id + Data = [string]($_.body.value | ConvertTo-Json -Depth 10 -Compress) + } + Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force } - Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force } } } From 5cc9f6d2c844c77608494e4e998247d953c96c10 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Jul 2024 16:33:08 -0400 Subject: [PATCH 073/104] Fix recurrence --- Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 | 9 ++++++++- .../Register-CippExtensionScheduledTasks.ps1 | 7 +++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index ede80f1ca5d5..79696e0a16a3 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -35,6 +35,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' @@ -44,7 +51,7 @@ function Add-CIPPScheduledTask { Command = [string]$task.Command.value Parameters = [string]$Parameters ScheduledTime = [string]$task.ScheduledTime - Recurrence = [string]$task.Recurrence.value ?? $task.Recurrence + Recurrence = [string]$Recurrence PostExecution = [string]$PostExecution AdditionalProperties = [string]$AdditionalProperties Hidden = [bool]$Hidden diff --git a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 index e56fee6cf980..e9fb5c30987f 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 @@ -67,8 +67,8 @@ function Register-CIPPExtensionScheduledTasks { } } - $ExistingTask = $PushTasks | Where-Object { $_.Tenant -eq $Tenant.defaultDomainName -and $_.SyncType -eq $Extension } - if (!$ExistingTask -or $Reschedule.IsPresent) { + $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 = @{ @@ -85,6 +85,9 @@ function Register-CIPPExtensionScheduledTasks { ScheduledTime = $in30mins TenantFilter = $Tenant.defaultDomainName } + if ($ExistingPushTask) { + $Task.RowKey = $ExistingTask.RowKey + } $null = Add-CIPPScheduledTask -Task $Task -hidden $true -SyncType $Extension } } From 99b0b15a49d03d1aef8b82dd576cb6853810c5e9 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Jul 2024 17:39:22 -0400 Subject: [PATCH 074/104] Add cleanup rename function --- ...yncData.ps1 => Push-CippExtensionData.ps1} | 2 +- .../Register-CippExtensionScheduledTasks.ps1 | 28 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) rename Modules/CippExtensions/Public/Extension Functions/{Push-ExtensionSyncData.ps1 => Push-CippExtensionData.ps1} (93%) diff --git a/Modules/CippExtensions/Public/Extension Functions/Push-ExtensionSyncData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Push-CippExtensionData.ps1 similarity index 93% rename from Modules/CippExtensions/Public/Extension Functions/Push-ExtensionSyncData.ps1 rename to Modules/CippExtensions/Public/Extension Functions/Push-CippExtensionData.ps1 index 09966538dbff..95e74b54838b 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Push-ExtensionSyncData.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Push-CippExtensionData.ps1 @@ -1,4 +1,4 @@ -function Push-ExtensionSyncData { +function Push-CippExtensionData { param( $TenantFilter, $Extension diff --git a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 index e9fb5c30987f..8408ed4b5f94 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 @@ -41,7 +41,10 @@ function Register-CIPPExtensionScheduledTasks { foreach ($Mapping in $Mappings) { $Tenant = $Tenants | Where-Object { $_.customerId -eq $Mapping.RowKey } - + if (!$Tenant) { + Write-Warning "Tenant $($Mapping.RowKey) not found" + continue + } foreach ($SyncType in $SyncTypes) { $ExistingTask = $ScheduledTasks | Where-Object { $_.Tenant -eq $Tenant.defaultDomainName -and $_.SyncType -eq $SyncType } if (!$ExistingTask -or $Reschedule.IsPresent) { @@ -64,6 +67,7 @@ function Register-CIPPExtensionScheduledTasks { $Task.RowKey = $ExistingTask.RowKey } $null = Add-CIPPScheduledTask -Task $Task -hidden $true -SyncType $SyncType + Write-Information "Creating $SyncType task for tenant $($Tenant.defaultDomainName)" } } @@ -89,9 +93,31 @@ function Register-CIPPExtensionScheduledTasks { $Task.RowKey = $ExistingTask.RowKey } $null = Add-CIPPScheduledTask -Task $Task -hidden $true -SyncType $Extension + Write-Information "Creating $Extension task for tenant $($Tenant.defaultDomainName)" } } + } else { + # remove existing scheduled tasks + $ScheduledTasks | 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 + } } } + foreach ($Task in $ScheduledTasks) { + if ($Task.TenantFilter -notin $Tenants.defaultDomainName) { + 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.TenantFilter -notin $Tenants.defaultDomainName) { + 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 + } + } } From 22e95305830907d9eb37dc0f9d0462c72a590b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 8 Jul 2024 23:44:21 +0200 Subject: [PATCH 075/104] Better comment block to support Get-Help --- Tools/Update-StandardsComments.ps1 | 79 +++++++++++++++++------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/Tools/Update-StandardsComments.ps1 b/Tools/Update-StandardsComments.ps1 index 3c47284284c0..a4f6c6a0f082 100644 --- a/Tools/Update-StandardsComments.ps1 +++ b/Tools/Update-StandardsComments.ps1 @@ -18,6 +18,7 @@ This example runs the script to update the comment block in the CIPP standard files. + #> param ( [switch]$WhatIf @@ -37,7 +38,11 @@ foreach ($Standard in $StandardsInfo) { Write-Host "No file found for standard $($Standard.name)" -ForegroundColor Yellow continue } - $Content = Get-Content -Path $StandardsFilePath -Raw + $Content = (Get-Content -Path $StandardsFilePath -Raw).TrimEnd() + + # Remove random newlines before the param block + $regexPattern = '#>\s*\r?\n\s*\r?\n\s*param' + $Content = $Content -replace $regexPattern, "#>`n`n param" # Regex to match the existing comment block $Regex = '<#(.|\n)*?\.FUNCTIONALITY\s*Internal(.|\n)*?#>' @@ -45,51 +50,59 @@ foreach ($Standard in $StandardsInfo) { if ($Content -match $Regex) { $NewComment = [System.Collections.Generic.List[string]]::new() # Add the initial scatic comments - $NewComment.Add("<#`n") - $NewComment.Add(" .FUNCTIONALITY`n") - $NewComment.Add(" Internal`n") - $NewComment.Add(" .APINAME`n") - $NewComment.Add(" $($Standard.name -replace 'standards.', '')`n") + $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 properties of the standard and add them to the comment block + # Loop through the rest of the properties of the standard and add them to the NOTES field foreach ($Property in $Standard.PSObject.Properties) { - if ($Property.Name -eq 'name') { continue } - if ($Property.Name -eq 'impactColour') { continue } - - # If the property is docsDescription and is empty, use the helpText instead - if ($Property.Name -eq 'docsDescription' -and ([string]::IsNullOrWhiteSpace($Property.Value))) { - $NewComment.Add(" .$('docsDescription'.ToUpper())`n") - $NewComment.Add(" $($Standard.helpText.ToString())`n") - continue - } - - $NewComment.Add(" .$($Property.Name.ToUpper())`n") - # Flatten objects to JSON - if ($Property.Value -is [System.Object[]]) { - foreach ($Value in $Property.Value) { - $NewComment.Add(" $(ConvertTo-Json -InputObject $Value -Depth 5 -Compress)`n") + 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") } - continue } - $NewComment.Add(" $($Property.Value.ToString())`n") - } - # Add DOCSDESCRIPTION if it doesn't exist - if ($NewComment -notcontains '.DOCSDESCRIPTION') { - $NewComment.Add(" .DOCSDESCRIPTION`n") - $NewComment.Add(" $($Standard.helpText.ToString())`n") } + # Add header about how to update the comment block with this script - $NewComment.Add(" .UPDATECOMMENTBLOCK`n") - $NewComment.Add(" Run the Tools\Update-StandardsComments.ps1 script to update this comment block`n") - $NewComment.Add(" #>`n") + $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 + $Content -replace $Regex, $NewComment | Set-Content -Path $StandardsFilePath -Encoding utf8 } } else { Write-Host "No comment block found in $StandardsFilePath" -ForegroundColor Yellow From b018e21fd94b6be69e621fb443d12583d9ddcc8a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Jul 2024 18:09:55 -0400 Subject: [PATCH 076/104] Hudu sync tweaks --- .../Public/GraphHelper/Convert-SKUName.ps1 | 12 +++++++-- .../Sync-CippExtensionData.ps1 | 2 +- .../Public/Hudu/Invoke-HuduExtensionSync.ps1 | 26 ++++++++++--------- 3 files changed, 25 insertions(+), 15 deletions(-) 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/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 index 2e27427ecb82..544ba27b1960 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 @@ -48,7 +48,7 @@ function Sync-CippExtensionData { @{ id = 'Licenses' method = 'GET' - url = '/subscribedSkus?$top=999' + url = '/subscribedSkus' }, @{ id = 'ConditionalAccess' diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index 96cc65cbafbb..879fd84d1d89 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -7,7 +7,6 @@ function Invoke-HuduExtensionSync { $Configuration, $TenantFilter ) - Connect-HuduAPI -configuration $Configuration # Get mapping configuration @@ -41,7 +40,11 @@ function Invoke-HuduExtensionSync { #$monitorDomains = [System.Convert]::ToBoolean($env:monitorDomains) $IntuneDesktopDeviceTypes = $env:IntuneDesktopDeviceTypes -split ',' $ExcludeSerials = $env:ExcludeSerials -split ',' - #$LicenseLookup = Get-LicenseLookup + + Set-Location (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent.FullName + $LicTable = Import-Csv Conversiontable.csv + + #$AssignedMap = Get-AssignedMap #$AssignedNameMap = Get-AssignedNameMap $EnableCIPP = $true @@ -162,7 +165,7 @@ function Invoke-HuduExtensionSync { $post = '
    ' - $licenseOut = $Licenses | Where-Object { $_.PrepaidUnits.Enabled -gt 0 } | Select-Object @{N = 'License Name'; E = { $($LicenseLookup.$($_.SkuPartNumber)) } }, @{N = 'Active'; E = { $_.PrepaidUnits.Enabled } }, @{N = 'Consumed'; E = { $_.ConsumedUnits } }, @{N = 'Unused'; E = { $_.PrepaidUnits.Enabled - $_.ConsumedUnits } } + $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 } @@ -434,11 +437,11 @@ function Invoke-HuduExtensionSync { $userLicenses = ($user.AssignedLicenses.SkuID | ForEach-Object { $UserLic = $_ $SkuPartNumber = ($Licenses | Where-Object { $_.SkuId -eq $UserLic }).SkuPartNumber - try { - "$($LicenseLookup.$SkuPartNumber)" - } catch { - "$SkuPartNumber" + $DisplayName = Convert-SKUname -skuName $SkuPartNumber -ConvertTable $LicTable + if (!$DisplayName) { + $DisplayName = $SkuPartNumber } + $DisplayName }) -join ', ' $UserOneDriveDetails = $OneDriveDetails | Where-Object { $_.'Owner Principal Name' -eq $user.UserPrincipalName } @@ -586,7 +589,7 @@ function Invoke-HuduExtensionSync { $UserDevicesDetailsBlock = $null } - $HuduUser = $People | Where-Object { $_.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) } + $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) { @@ -596,18 +599,17 @@ function Invoke-HuduExtensionSync { } [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 'Azure AD')) + $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://endpoint.microsoft.com/$($Tenant.defaultDomainName)/#blade/Microsoft_AAD_IAM/UserDetailsMenuBlade/Profile/userId/$($User.ID)" -Icon 'fas fa-laptop' -Title 'EPM (User)')) - $UserLinksFormatted.add((Get-HuduLinkBlock -URL "https://endpoint.microsoft.com/$($Tenant.defaultDomainName)/#blade/Microsoft_AAD_IAM/UserDetailsMenuBlade/Devices/userId/$($User.ID)" -Icon 'fas fa-laptop' -Title 'EPM (Devices)')) + $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 '')
    " From 3e1ded4bcd715d07742500c77d3acee52e5235f6 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Jul 2024 18:13:37 -0400 Subject: [PATCH 077/104] Add task registration to mapping save --- .../CIPP/Settings/Invoke-ExecExtensionMapping.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 index d8a5bfba10dc..32a21c2119f4 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionMapping.ps1 @@ -52,9 +52,11 @@ Function Invoke-ExecExtensionMapping { } '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 } } } From 3bb72cf1e9aa2338f15703055006cf02438b5f81 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Jul 2024 18:43:35 -0400 Subject: [PATCH 078/104] Fix hudu CIPP url --- .../CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 | 11 +++++++++++ .../Register-CippExtensionScheduledTasks.ps1 | 4 ++-- .../Public/Hudu/Invoke-HuduExtensionSync.ps1 | 13 ++++++++----- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 index ebbf5ca9dba4..699c443d80a7 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 @@ -68,6 +68,17 @@ Function Invoke-ExecExtensionsConfig { } Add-CIPPAzDataTableEntity @Table -Entity $Config -Force | Out-Null + + $CippUri = [System.Uri]$TriggerMetadata.Headers.'x-ms-original-url' + $AddObject = @{ + PartitionKey = 'InstanceProperties' + RowKey = 'CIPPURL' + Value = '{0}://{1}' -f $CippUri.Scheme, $CippUri.Authority + } + $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/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 index 8408ed4b5f94..e964d7c23515 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 @@ -107,14 +107,14 @@ function Register-CIPPExtensionScheduledTasks { } foreach ($Task in $ScheduledTasks) { - if ($Task.TenantFilter -notin $Tenants.defaultDomainName) { + if ($Task.Tenant -notin $Tenants.defaultDomainName) { 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.TenantFilter -notin $Tenants.defaultDomainName) { + if ($Task.Tenant -notin $Tenants.defaultDomainName) { 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/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index 879fd84d1d89..385e23bee2ca 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -44,11 +44,14 @@ function Invoke-HuduExtensionSync { Set-Location (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent.FullName $LicTable = Import-Csv Conversiontable.csv - #$AssignedMap = Get-AssignedMap #$AssignedNameMap = Get-AssignedNameMap + $EnableCIPP = $true - #$CIPPURL = $env:CIPPURL + + $ConfigTable = Get-Cipptable -tablename 'Config' + $Config = Get-CippAzDataTableEntity @ConfigTable -Filter "PartitionKey eq 'InstanceProperties' and RowKey -eq 'CIPPURL'" + $CIPPURL = $Config.Value $ExtensionCache = Get-ExtensionCacheData -TenantFilter $Tenant.defaultDomainName @@ -593,9 +596,9 @@ function Invoke-HuduExtensionSync { [System.Collections.Generic.List[PSCustomObject]]$CIPPLinksFormatted = @() if ($EnableCIPP) { - $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL).auth/login/aad?post_login_redirect_uri=$($CIPPURL)identity/administration/users/view?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'far fa-eye' -Title 'CIPP - View User')) - $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL).auth/login/aad?post_login_redirect_uri=$($CIPPURL)identity/administration/users/edit?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'fas fa-user-cog' -Title 'CIPP - Edit User')) - $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL).auth/login/aad?post_login_redirect_uri=$($CIPPURL)identity/administration/ViewBec?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'fas fa-user-secret' -Title 'CIPP - Research Compromise')) + $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/.auth/login/aad?post_login_redirect_uri=$($CIPPURL)/identity/administration/users/view?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'far fa-eye' -Title 'CIPP - View User')) + $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/.auth/login/aad?post_login_redirect_uri=$($CIPPURL)/identity/administration/users/edit?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'fas fa-user-cog' -Title 'CIPP - Edit User')) + $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/.auth/login/aad?post_login_redirect_uri=$($CIPPURL)/identity/administration/ViewBec?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'fas fa-user-secret' -Title 'CIPP - Research Compromise')) } [System.Collections.Generic.List[PSCustomObject]]$UserLinksFormatted = @() From ff27f5fa6c1b0390c0e992af3ceef4802319870d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Jul 2024 18:49:33 -0400 Subject: [PATCH 079/104] Fix links --- .../Public/Hudu/Invoke-HuduExtensionSync.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index 385e23bee2ca..c3721f99501e 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -50,7 +50,7 @@ function Invoke-HuduExtensionSync { $EnableCIPP = $true $ConfigTable = Get-Cipptable -tablename 'Config' - $Config = Get-CippAzDataTableEntity @ConfigTable -Filter "PartitionKey eq 'InstanceProperties' and RowKey -eq 'CIPPURL'" + $Config = Get-CippAzDataTableEntity @ConfigTable -Filter "PartitionKey eq 'InstanceProperties' and RowKey eq 'CIPPURL'" $CIPPURL = $Config.Value $ExtensionCache = Get-ExtensionCacheData -TenantFilter $Tenant.defaultDomainName @@ -596,9 +596,9 @@ function Invoke-HuduExtensionSync { [System.Collections.Generic.List[PSCustomObject]]$CIPPLinksFormatted = @() if ($EnableCIPP) { - $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/.auth/login/aad?post_login_redirect_uri=$($CIPPURL)/identity/administration/users/view?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'far fa-eye' -Title 'CIPP - View User')) - $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/.auth/login/aad?post_login_redirect_uri=$($CIPPURL)/identity/administration/users/edit?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'fas fa-user-cog' -Title 'CIPP - Edit User')) - $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/.auth/login/aad?post_login_redirect_uri=$($CIPPURL)/identity/administration/ViewBec?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'fas fa-user-secret' -Title 'CIPP - Research Compromise')) + $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/identity/administration/users/view?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'far fa-eye' -Title 'CIPP - View User')) + $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/identity/administration/users/edit?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'fas fa-user-cog' -Title 'CIPP - Edit User')) + $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/identity/administration/ViewBec?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'fas fa-user-secret' -Title 'CIPP - Research Compromise')) } [System.Collections.Generic.List[PSCustomObject]]$UserLinksFormatted = @() From 5885d6952a6975499796c0dc3f6ccd577cfd1070 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Jul 2024 01:58:33 +0200 Subject: [PATCH 080/104] finishing touches backup config --- .../CIPP/Settings/Invoke-ExecRunBackup.ps1 | 2 +- .../Endpoint/MEM/Invoke-AddPolicy.ps1 | 78 +------- .../Conditional/Invoke-AddCATemplate.ps1 | 62 +------ Modules/CIPPCore/Public/New-CIPPBackup.ps1 | 8 +- .../CIPPCore/Public/New-CIPPBackupTask.ps1 | 22 +-- Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 | 10 +- .../CIPPCore/Public/New-CIPPCATemplate.ps1 | 69 ++++++++ .../CIPPCore/Public/New-CIPPRestoreTask.ps1 | 166 +++++++++++------- .../CIPPCore/Public/Set-CIPPIntunePolicy.ps1 | 130 ++++++++++++++ 9 files changed, 329 insertions(+), 218 deletions(-) create mode 100644 Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 create mode 100644 Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 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/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/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/New-CIPPBackup.ps1 b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 index b99530c6de79..722a1f09a18f 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 @@ -69,14 +69,20 @@ function New-CIPPBackup { 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' 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)" } } } } - 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 index cbd216411068..ab953153f0ba 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 @@ -8,7 +8,14 @@ function New-CIPPBackupTask { $BackupData = switch ($Task) { 'users' { Write-Host "Backup users for $TenantFilter" - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $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 { $_.Value -eq $null } | ForEach-Object { + $_.psobject.properties.Remove($_.Name) + } + } + $Users } 'groups' { Write-Host "Backup groups for $TenantFilter" @@ -16,15 +23,10 @@ function New-CIPPBackupTask { } 'ca' { Write-Host "Backup Conditional Access Policies for $TenantFilter" - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter - } - 'namedlocations' { - Write-Host "Backup Named Locations for $TenantFilter" - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/namedLocations?$top=999' -tenantid $TenantFilter - } - 'authstrengths' { - Write-Host "Backup Authentication Strength Policies for $TenantFilter" - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/authenticationStrength/policies' -tenantid $TenantFilter + $Policies = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter + $Policies | ForEach-Object { + New-CIPPCATemplate -TenantFilter $TenantFilter -JSON $_ -ErrorAction SilentlyContinue + } } 'intuneconfig' { $GraphURLS = @("https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations?`$select=id,displayName,lastModifiedDateTime,roleScopeTagIds,microsoft.graph.unsupportedDeviceConfiguration/originalEntityTypeName&`$expand=assignments&top=1000" diff --git a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 index 10f92773ee22..26f9046c4d21 100644 --- a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 @@ -91,11 +91,10 @@ function New-CIPPCAPolicy { 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' - + } 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' [pscustomobject]@{ @@ -138,10 +137,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) { @@ -152,7 +151,7 @@ function New-CIPPCAPolicy { 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' } - } + } } $JsonObj.PSObject.Properties.Remove('LocationInfo') $RawJSON = $JSONObj | ConvertTo-Json -Depth 10 -Compress @@ -177,7 +176,6 @@ function New-CIPPCAPolicy { 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' } diff --git a/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 b/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 new file mode 100644 index 000000000000..ed776e4526b9 --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 @@ -0,0 +1,69 @@ +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 $_ } + (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 -Compress -Depth 100 -InputObject $JSON) + return $JSON +} + diff --git a/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 b/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 index 64b9c4701cd9..8d48960d1899 100644 --- a/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPRestoreTask.ps1 @@ -10,115 +10,155 @@ function New-CIPPRestoreTask { $BackupData = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$backup'" $RestoreData = switch ($Task) { 'users' { - Write-Host "Restore users for $TenantFilter" - $currentUsers = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $TenantFilter + $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) { - $currentUsers | Where-Object { $_.id -eq $_.id } | ForEach-Object { - New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/users/$($_.id)" -tenantid $TenantFilter -body $_ -type PATCH - Write-LogMessage -message "Restored $($_.userprincipalname) from backup" -Sev 'info' - "Restored $($_.userprincipalname) from backup" + 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" } - } else { - if ($currentUsers.id -notin $_.id) { - New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/users' -tenantid $TenantFilter -body $_ -type POST - Write-LogMessage -message "Restored $($_.userprincipalname) from backup" -Sev 'info' - "Restored $($_.userprincipalname) from backup" - + } + 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 $($_.userPrincipalName) already exists in tenant $TenantFilter and overwrite is disabled" -Sev 'info' - "User $($_.userPrincipalName) already exists in tenant $TenantFilter and overwrite is disabled" + 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 { - "Could not restore user $($_.userPrincipalName): $($_.Exception.Message) " - Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore user $($_.userPrincipalName): $($_.Exception.Message) " -Sev 'error' + "Could not restore user $($UPN): $($_.Exception.Message) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore user $($UPN): $($_.Exception.Message) " -Sev 'error' } } } '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) { - $currentUsers | Where-Object { $_.id -eq $_.id } | ForEach-Object { - New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/groups/$($_.id)" -tenantid $TenantFilter -body $_ -type PATCH - Write-LogMessage -message "Restored $($_.userprincipalname) from backup" -Sev 'info' - "Restored group $($_.displayName) from backup" + 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" } - } else { - if ($currentUsers.id -notin $_.id) { - New-GraphPOSTRequest -uri 'https://graph.microsoft.com/groups/' -tenantid $TenantFilter -body $_ -type POST - Write-LogMessage -message "Restored $($_.userprincipalname) from backup" -Sev 'info' - "Restored group $($_.displayName) from backup" - + } + 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 $($_.group) already exists in tenant $TenantFilter and overwrite is disabled" -Sev 'info' - "group $($_.displayName) already exists in tenant $TenantFilter and overwrite is disabled" + 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 { - "Could not restore user $($_.userPrincipalName): $($_.Exception.Message) " - Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore user $($_.userPrincipalName): $($_.Exception.Message) " -Sev 'error' + "Could not restore group $DisplayName $($_.Exception.Message) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore group $DisplayName $($_.Exception.Message) " -Sev 'error' } } } 'ca' { - Write-Host "Backup Conditional Access Policies for $TenantFilter" - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter - } - 'namedlocations' { - Write-Host "Backup Named Locations for $TenantFilter" - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/namedLocations?$top=999' -tenantid $TenantFilter - } - 'authstrengths' { - Write-Host "Backup Authentication Strength Policies for $TenantFilter" - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/authenticationStrength/policies' -tenantid $TenantFilter + 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 { + "Could not restore Conditional Access Policy $DisplayName $($_.Exception.Message) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore Conditional Access Policy $DisplayName $($_.Exception.Message) " -Sev 'error' + } + } } 'intuneconfig' { - $GraphURLS = @("https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations?`$select=id,displayName,lastModifiedDateTime,roleScopeTagIds,microsoft.graph.unsupportedDeviceConfiguration/originalEntityTypeName&`$expand=assignments&top=1000" - 'https://graph.microsoft.com/beta/deviceManagement/windowsDriverUpdateProfiles' - "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations?`$expand=assignments&top=999" - "https://graph.microsoft.com/beta/deviceAppManagement/mobileAppConfigurations?`$expand=assignments&`$filter=microsoft.graph.androidManagedStoreAppConfiguration/appSupportsOemConfig%20eq%20true" - 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' - ) - - $GraphURLS | ForEach-Object { - $URLName = (($_).split('?') | Select-Object -First 1) -replace 'https://graph.microsoft.com/beta/deviceManagement/', '' - New-GraphGetRequest -uri "$($_)" -tenantid $TenantFilter - } | ForEach-Object { - New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName $URLName -ID $_.ID + $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 { + "Could not restore Intune Configuration $DisplayName $($_.Exception.Message) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore Intune Configuration $DisplayName $($_.Exception.Message) " -Sev 'error' + } } + #Convert the manual method to a function } 'intunecompliance' { - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies?$top=999' -tenantid $TenantFilter | ForEach-Object { - New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName 'deviceCompliancePolicies' -ID $_.ID + $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 { + "Could not restore Intune Compliance $DisplayName $($_.Exception.Message) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore Intune Configuration $DisplayName $($_.Exception.Message) " -Sev 'error' + } } + } 'intuneprotection' { - New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceAppManagement/managedAppPolicies?$top=999' -tenantid $TenantFilter | ForEach-Object { - New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName 'managedAppPolicies' -ID $_.ID + $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 { + "Could not restore Intune Protection $DisplayName $($_.Exception.Message) " + Write-LogMessage -user $ExecutingUser -API $APINAME -message "Could not restore Intune Configuration $DisplayName $($_.Exception.Message) " -Sev 'error' + } } + } 'CippWebhookAlerts' { - Write-Host "Backup Webhook Alerts for $TenantFilter" + Write-Host "Restore Webhook Alerts for $TenantFilter" $WebhookTable = Get-CIPPTable -TableName 'WebhookRules' - Get-CIPPAzDataTableEntity @WebhookTable | Where-Object { $TenantFilter -in ($_.Tenants | ConvertFrom-Json).fullvalue.defaultDomainName } + $Backup = $BackupData.CippWebhookAlerts | ConvertFrom-Json + try { + Add-CIPPAzDataTableEntity @WebhookTable -Entity $Backup -Force + } catch { + "Could not restore Webhook Alerts $($_.Exception.Message)" + } } 'CippScriptedAlerts' { - Write-Host "Backup Scripted Alerts for $TenantFilter" + Write-Host "Restore 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 } + $Backup = $BackupData.CippScriptedAlerts | ConvertFrom-Json + try { + Add-CIPPAzDataTableEntity @ScheduledTasks -Entity $Backup -Force + } catch { + "Could not restore Scripted Alerts $($_.Exception.Message) " + } } 'CippStandards' { - Write-Host "Backup Standards for $TenantFilter" + Write-Host "Restore Standards for $TenantFilter" $Table = Get-CippTable -tablename 'standards' - $Filter = "PartitionKey eq 'standards' and RowKey eq '$($TenantFilter)'" - (Get-CIPPAzDataTableEntity @Table -Filter $Filter) + $StandardsBackup = $BackupData.CippStandards | ConvertFrom-Json + try { + Add-CIPPAzDataTableEntity @Table -Entity $StandardsBackup -Force + } catch { + "Could not restore Standards $($_.Exception.Message) " + } } } diff --git a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 new file mode 100644 index 000000000000..45ffd9fad2e1 --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 @@ -0,0 +1,130 @@ +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 { + "Failed to add or set policy for $($tenantFilter) with display name $($Displayname): $($_.Exception.Message)" + Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $($tenantFilter) -message "Failed $($PostType) policy $($Displayname). Error: $($_.Exception.Message)" -Sev 'Error' + continue + } + + return $ReturnValue +} From 1d95b2f76d42c7a07b94f2bc9d6bd035a67c5923 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Jul 2024 20:07:49 -0400 Subject: [PATCH 081/104] Hash contents for reduced api calls --- .../CippExtensions/Private/Get-StringHash.ps1 | 8 +++ .../Public/Hudu/Invoke-HuduExtensionSync.ps1 | 63 ++++++++++++++++--- 2 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 Modules/CippExtensions/Private/Get-StringHash.ps1 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/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index c3721f99501e..d5f352e06679 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -17,6 +17,8 @@ function Invoke-HuduExtensionSync { $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' } @@ -623,15 +625,34 @@ function Invoke-HuduExtensionSync { microsoft_365 = $UserBody email_address = $user.UserPrincipalName } - + $NewHash = Get-StringHash -String $UserBody $HuduUserCount = ($HuduUser | Measure-Object).count if ($HuduUserCount -eq 1) { - $null = Set-HuduAsset -asset_id $HuduUser.id -Name $HuduUser.name -company_id $company_id -asset_layout_id $PeopleLayout.id -Fields $UserAssetFields + $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 ', '))) $_") @@ -763,19 +784,32 @@ function Invoke-HuduExtensionSync { $DeviceAssetFields = @{ microsoft_365 = $DeviceIntuneDetailshtml } + $NewHash = Get-StringHash -String $DeviceIntuneDetailshtml if ($HuduDevice) { if (($HuduDevice | Measure-Object).count -eq 1) { - $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 + $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) } + $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 {} + 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 { @@ -791,6 +825,15 @@ function Invoke-HuduExtensionSync { } 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 { From f030ea92615400fc5b5df65278bbfeef7feb7662 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Jul 2024 23:48:42 -0400 Subject: [PATCH 082/104] Update Invoke-ExecExtensionSync.ps1 --- .../Settings/Invoke-ExecExtensionSync.ps1 | 105 +++++++++--------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionSync.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionSync.ps1 index 9c090c91f094..911ce71d7e14 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionSync.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionSync.ps1 @@ -13,42 +13,60 @@ Function Invoke-ExecExtensionSync { $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' = 'Successfully started Gradient Sync' } + 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)" } + } 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 + 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 + '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) - $CIPPMapping = Get-CIPPTable -TableName CippMapping - $Filter = "PartitionKey eq 'NinjaOneMapping'" - $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.IntegrationId -and $_.IntegrationId -ne '' } + $Results = [pscustomobject]@{'Results' = "NinjaOne Synchronization Queued for $($Tenant.IntegrationName)" } + } else { + $Results = [pscustomobject]@{'Results' = 'Tenant was not found.' } + } - if ($Request.Query.TenantID) { - $Tenant = $TenantsToProcess | Where-Object { $_.RowKey -eq $Request.Query.TenantID } - if (($Tenant | Measure-Object).count -eq 1) { + } else { $Batch = [PSCustomObject]@{ - 'NinjaAction' = 'SyncTenant' - 'MappedTenant' = $Tenant + 'NinjaAction' = 'SyncTenants' 'FunctionName' = 'NinjaOneQueue' } $InputObject = [PSCustomObject]@{ @@ -58,32 +76,17 @@ Function Invoke-ExecExtensionSync { #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" } - $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 } - - - } 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' } } } @@ -92,6 +95,6 @@ Function Invoke-ExecExtensionSync { Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = $Results - }) -clobber + }) } From 3b459e618a6d4064fec5ad89b0b75671d953c8b7 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Jul 2024 10:22:17 +0200 Subject: [PATCH 083/104] fix keepcopy --- .../Public/Invoke-CIPPOffboardingJob.ps1 | 24 +++++++++---------- .../CIPPCore/Public/Set-CIPPForwarding.ps1 | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 index ba5ab6432fb8..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,25 +35,25 @@ 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 '' } { + { $_.'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 $Options.keepCopy -ExecutingUser $ExecutingUser -APIName $APIName + Set-CIPPForwarding -userid $userid -username $username -tenantFilter $Tenantfilter -Forward $Options.forward -KeepCopy [boolean]$Options.keepCopy -ExecutingUser $ExecutingUser -APIName $APIName } } { $_.'RemoveLicenses' -eq 'true' } { @@ -89,7 +89,7 @@ function Invoke-CIPPOffboardingJob { "Removal of permissions queued. This task will run in the background and send it's results to the logbook." } } - + } return $Return diff --git a/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 b/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 index ba995399b49e..d8ebfe422845 100644 --- a/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 @@ -43,7 +43,7 @@ function Set-CIPPForwarding { [string]$ExecutingUser, [string]$APIName = 'Forwarding', [string]$Forward, - [bool]$KeepCopy, + $KeepCopy, [bool]$Disable ) From 7477ccfb2296acd739a67cd0e33df669e3d6c28d Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Jul 2024 12:37:10 +0200 Subject: [PATCH 084/104] fixes minor issues with backup wizard --- .../Applications/Invoke-AddWinGetApp.ps1 | 2 +- .../CIPPCore/Public/New-CIPPBackupTask.ps1 | 33 +++++++++++++++---- .../CIPPCore/Public/New-CIPPCATemplate.ps1 | 16 +++++++++ 3 files changed, 43 insertions(+), 8 deletions(-) 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/New-CIPPBackupTask.ps1 b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 index ab953153f0ba..a82bd4d1ac3a 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 @@ -24,11 +24,17 @@ function New-CIPPBackupTask { 'ca' { Write-Host "Backup Conditional Access Policies for $TenantFilter" $Policies = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/conditionalAccess/policies?$top=999' -tenantid $TenantFilter - $Policies | ForEach-Object { - New-CIPPCATemplate -TenantFilter $TenantFilter -JSON $_ -ErrorAction SilentlyContinue + 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" @@ -36,20 +42,33 @@ function New-CIPPBackupTask { 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' ) - $GraphURLS | ForEach-Object { - $URLName = (($_).split('?') | Select-Object -First 1) -replace 'https://graph.microsoft.com/beta/deviceManagement/', '' - New-GraphGetRequest -uri "$($_)" -tenantid $TenantFilter - } | ForEach-Object { - New-CIPPIntuneTemplate -TenantFilter $TenantFilter -URLName $URLName -ID $_.ID + $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 { + "Failed to create a template of the Intune Configuration Policy with ID: $($Policy.id). Error: $($_.Exception.Message)" + } + } + } 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 } diff --git a/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 b/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 index ed776e4526b9..37577fd35f72 100644 --- a/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 @@ -32,14 +32,22 @@ function New-CIPPCATemplate { 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 $_ + } }) } @@ -51,13 +59,21 @@ function New-CIPPCATemplate { 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 $_ + } }) } From c037f4d8b37e5e9eda67fcd30cb08ad7a658fc8d Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Jul 2024 13:56:58 +0200 Subject: [PATCH 085/104] finished restore settings --- Modules/CIPPCore/Public/New-CIPPRestore.ps1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/New-CIPPRestore.ps1 b/Modules/CIPPCore/Public/New-CIPPRestore.ps1 index 399377a02451..f3dd2ca21cbd 100644 --- a/Modules/CIPPCore/Public/New-CIPPRestore.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPRestore.ps1 @@ -2,14 +2,15 @@ function New-CIPPRestore { [CmdletBinding()] param ( $TenantFilter, + $Type = 'Scheduled', $RestoreValues, $APIName = 'CIPP Restore', $ExecutingUser ) - Write-Host "Scheduled Restore psproperties: $(([pscustomobject]$ScheduledBackupValues).psobject.Properties)" + 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]$ScheduledBackupValues).psobject.Properties.Name | Where-Object { $_ -notin 'email', 'webhook', 'psa', 'backup', 'overwrite' }) { + $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 From e6630a071ae195c6a5c7e6dd1ef8d0fe14ec0af8 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Jul 2024 14:01:31 +0200 Subject: [PATCH 086/104] add adbility to not allow duplicate names --- Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 | 9 +++++++++ .../CIPP/Scheduler/Invoke-AddScheduledItem.ps1 | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index 79696e0a16a3..d1ecc6f090a8 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -3,10 +3,19 @@ function Add-CIPPScheduledTask { param( [pscustomobject]$Task, [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]@{} 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..f5885143a196 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 @@ -14,7 +14,7 @@ Function Invoke-AddScheduledItem { } 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]@{ From a66d8d9ef05b169b2ed65a68745f49e9e40fa1cc Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Jul 2024 14:11:40 +0200 Subject: [PATCH 087/104] fixed extended ignore list --- .../Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 | 8 +------- .../Public/Webhooks/Test-CIPPAuditLogRules.ps1 | 12 +++++++----- 2 files changed, 8 insertions(+), 12 deletions(-) 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 +} From 75cb2ce65ae2d0489776dbe51cba4d8658a14e4d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 9 Jul 2024 09:45:28 -0400 Subject: [PATCH 088/104] Fix cleanup sync tasks and az table function --- .../Public/Add-CIPPAzDataTableEntity.ps1 | 2 +- .../Register-CippExtensionScheduledTasks.ps1 | 10 +++-- .../Sync-CippExtensionData.ps1 | 39 ++++++++++++------- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 index ae011505e430..3e2dafd510ba 100644 --- a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 @@ -14,7 +14,7 @@ function Add-CIPPAzDataTableEntity { try { 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 { $largePropertyNames = [System.Collections.ArrayList]::new() $entitySize = 0 diff --git a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 index e964d7c23515..ba3ba64a9f42 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 @@ -15,7 +15,7 @@ function Register-CIPPExtensionScheduledTasks { $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) { @@ -45,6 +45,7 @@ function Register-CIPPExtensionScheduledTasks { 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) { @@ -98,23 +99,24 @@ function Register-CIPPExtensionScheduledTasks { } } else { # remove existing scheduled tasks - $ScheduledTasks | Where-Object { $_.SyncType -eq $Extension } | ForEach-Object { + $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 $Tenants.defaultDomainName) { + 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 $Tenants.defaultDomainName) { + 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/Sync-CippExtensionData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 index 544ba27b1960..c8bad10b3268 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 @@ -22,7 +22,7 @@ function Sync-CippExtensionData { Error = '' LastSync = 'Never' } - Add-CIPPAzDataTableEntity @Table -Entity $LastSync + $null = Add-CIPPAzDataTableEntity @Table -Entity $LastSync } try { @@ -172,13 +172,14 @@ function Sync-CippExtensionData { RowKey = 'Mailboxes' Data = [string]($Mailboxes | ConvertTo-Json -Depth 10 -Compress) } - Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force + $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 + $TenantResults = New-GraphBulkRequest -Requests @($TenantRequests) -tenantid $TenantFilter } catch { Throw "Failed to fetch bulk company data: $_" } @@ -193,7 +194,7 @@ function Sync-CippExtensionData { RowKey = $SingleGraphQuery.id Data = [string]($Data | ConvertTo-Json -Depth 10 -Compress) } - Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force + $null = Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force } } @@ -210,18 +211,28 @@ function Sync-CippExtensionData { } } } - #Write-Information ($AdditionalRequestQueries | ConvertTo-Json -Depth 10 -Compress) if (($AdditionalRequestQueries | Measure-Object).Count -gt 0) { - $AdditionalResults = New-GraphBulkRequest -Requests $AdditionalRequestQueries -tenantid $TenantFilter - $AdditionalResults | ForEach-Object { - $Entity = @{ - PartitionKey = $TenantFilter - SyncType = $SyncType - RowKey = '{0}_{1}' -f $ParentId, $_.id - Data = [string]($_.body.value | ConvertTo-Json -Depth 10 -Compress) + try { + $AdditionalResults = New-GraphBulkRequest -Requests @($AdditionalRequestQueries) -tenantid $TenantFilter + } catch { + throw $_ + } + if ($AdditionalResults) { + $AdditionalResults | ForEach-Object { + $Entity = @{ + PartitionKey = $TenantFilter + SyncType = $SyncType + RowKey = '{0}_{1}' -f $ParentId, $_.id + Data = [string]($_.body.value | ConvertTo-Json -Depth 10 -Compress) + } + try { + $null = Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force + } catch { + throw $_ + } } - Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force } + } } } @@ -233,7 +244,7 @@ function Sync-CippExtensionData { SyncType = $SyncType Data = [string]($_.body.value | ConvertTo-Json -Depth 10 -Compress) } - Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force + $null = Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force } } $LastSync.LastSync = [datetime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ') From 9bcf9411aef841cff8aca6230d5e8e8b08334de3 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 9 Jul 2024 11:53:35 -0400 Subject: [PATCH 089/104] Extension sync - add mailbox permission/usage/cas --- .../Public/Add-CIPPAzDataTableEntity.ps1 | 6 +- .../Sync-CippExtensionData.ps1 | 79 +++++++++++++++---- .../Public/Hudu/Invoke-HuduExtensionSync.ps1 | 43 ++++++---- 3 files changed, 91 insertions(+), 37 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 index 3e2dafd510ba..6e2e0dd618d5 100644 --- a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 @@ -67,7 +67,7 @@ function Add-CIPPAzDataTableEntity { $entityIndex = 0 while ($entitySize -gt $MaxRowSize) { - Write-Host "Entity size is $entitySize. Splitting entity into multiple parts." + Write-Information "Entity size is $entitySize. Splitting entity into multiple parts." $newEntity = @{} $newEntity['PartitionKey'] = $originalPartitionKey if ($entityIndex -eq 0) { @@ -126,7 +126,7 @@ function Add-CIPPAzDataTableEntity { } foreach ($row in $rows) { - Write-Host "current entity is $($row.RowKey) with $($row.PartitionKey). Our size is $([System.Text.Encoding]::UTF8.GetByteCount($($row | ConvertTo-Json)))" + 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 { @@ -137,7 +137,7 @@ function Add-CIPPAzDataTableEntity { throw "Error processing entity: $($_.Exception.Message) Linenumner: $($_.InvocationInfo.ScriptLineNumber)" } } else { - Write-Host "THE ERROR IS $($_.Exception.ErrorCode). The size of the entity is $entitySize." + Write-Information "THE ERROR IS $($_.Exception.ErrorCode). The size of the entity is $entitySize." throw $_ } } diff --git a/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 index c8bad10b3268..dba30aad0b60 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 @@ -68,12 +68,12 @@ function Sync-CippExtensionData { @{ id = 'OneDriveUsage' method = 'GET' - url = "reports/getOneDriveUsageAccountDetail(period='D7')?`$format=application/json" + url = "reports/getOneDriveUsageAccountDetail(period='D7')?`$format=application%2fjson" }, @{ id = 'MailboxUsage' method = 'GET' - url = "reports/getMailboxUsageDetail(period='D7')?`$format=application/json" + url = "reports/getMailboxUsageDetail(period='D7')?`$format=application%2fjson" } ) @@ -173,6 +173,36 @@ function Sync-CippExtensionData { 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 } } @@ -184,18 +214,21 @@ function Sync-CippExtensionData { Throw "Failed to fetch bulk company data: $_" } - if ($SingleGraphQueries) { - foreach ($SingleGraphQuery in $SingleGraphQueries) { - $Request = $SingleGraphQuery.graphRequest - $Data = New-GraphGetRequest @Request -tenantid $TenantFilter - $Entity = @{ - PartitionKey = $TenantFilter - SyncType = $SyncType - RowKey = $SingleGraphQuery.id - Data = [string]($Data | ConvertTo-Json -Depth 10 -Compress) - } - $null = Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force + $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) { @@ -219,11 +252,17 @@ function Sync-CippExtensionData { } 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]($_.body.value | ConvertTo-Json -Depth 10 -Compress) + Data = [string]($Data | ConvertTo-Json -Depth 10 -Compress) } try { $null = Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force @@ -236,17 +275,23 @@ function Sync-CippExtensionData { } } } + } - $TenantResults | Select-Object id, body | ForEach-Object { + if ($SingleGraphQueries) { + foreach ($SingleGraphQuery in $SingleGraphQueries) { + $Request = $SingleGraphQuery.graphRequest + $Data = New-GraphGetRequest @Request -tenantid $TenantFilter $Entity = @{ PartitionKey = $TenantFilter - RowKey = $_.id SyncType = $SyncType - Data = [string]($_.body.value | ConvertTo-Json -Depth 10 -Compress) + 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 = '' diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index d5f352e06679..09518896bf9b 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -314,7 +314,6 @@ function Invoke-HuduExtensionSync { $OneDriveDetails = $null } - <#try { $CASFull = New-GraphGetRequest -uri "https://outlook.office365.com/adminapi/beta/$($tenantfilter)/CasMailbox" -Tenantid $tenantfilter -scope ExchangeOnline -noPagination $true } catch { @@ -322,6 +321,14 @@ function Invoke-HuduExtensionSync { $CompanyResult.Errors.add("Company: Unable to fetch CAS Mailbox Details $_") }#> + if ($ExtensionCache.CASMailbox) { + $CASFull = $ExtensionCache.CASMailbox + } else { + $CompanyResult.Errors.add('Company: Unable to fetch CAS Mailbox Details') + $CASFull = $null + + } + <#try { $MailboxDetailedFull = New-ExoRequest -TenantID $TenantFilter -cmdlet 'Get-Mailbox' } catch { @@ -344,6 +351,7 @@ function Invoke-HuduExtensionSync { $CompanyResult.Errors.add('Company: Unable to fetch Mailbox Statistic Details') } + $Permissions = $ExtensionCache.MailboxPermissions if ($licensedUsers) { $pre = "

    Licensed Users

    @@ -383,13 +391,14 @@ function Invoke-HuduExtensionSync { $CASRequest = $CASFull | Where-Object { $_.ExternalDirectoryObjectId -eq $User.iD } $MailboxDetailedRequest = $MailboxDetailedFull | Where-Object { $_.ExternalDirectoryObjectId -eq $User.iD } - $StatsRequest = $MailboxStatsFull | Where-Object { $_.'User Principal Name' -eq $User.UserPrincipalName } + $StatsRequest = $MailboxStatsFull | Where-Object { $_.'userPrincipalName' -eq $User.UserPrincipalName } - try { + <#try { $PermsRequest = New-GraphGetRequest -uri "https://outlook.office365.com/adminapi/beta/$($tenantfilter)/Mailbox('$($User.ID)')/MailboxPermission" -Tenantid $tenantfilter -scope ExchangeOnline -noPagination $true } catch { $PermsRequest = $null - } + }#> + $PermsRequest = $Permissions | Where-Object { $_.Identity -eq $User.ID } $ParsedPerms = foreach ($Perm in $PermsRequest) { if ($Perm.User -ne 'NT AUTHORITY\SELF') { @@ -401,7 +410,7 @@ function Invoke-HuduExtensionSync { } try { - $TotalItemSize = [math]::Round($StatsRequest.'Storage Used (Byte)' / 1Gb, 2) + $TotalItemSize = [math]::Round($StatsRequest.storageUsedInBytes / 1Gb, 2) } catch { $TotalItemSize = 0 } @@ -420,7 +429,7 @@ function Invoke-HuduExtensionSync { 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.'Item Count', 2) + ItemCount = [math]::Round($StatsRequest.'itemCount', 2) TotalItemSize = $TotalItemSize } @@ -456,23 +465,23 @@ function Invoke-HuduExtensionSync { [System.Collections.Generic.List[PSCustomObject]]$OneDriveFormatted = @() if ($UserOneDriveDetails) { try { - $OneDriveUsePercent = [math]::Round([float](($UserOneDriveDetails.'Storage Used (Byte)' / $UserOneDriveDetails.'Storage Allocated (Byte)') * 100), 2) - $StorageUsed = [math]::Round($UserOneDriveDetails.'Storage Used (Byte)' / 1024 / 1024 / 1024, 2) - $StorageAllocated = [math]::Round($UserOneDriveDetails.'Storage Allocated (Byte)' / 1024 / 1024 / 1024, 2) + $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.'Owner Principal Name')")) - $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'One Drive URL' -Value "$($UserOneDriveDetails.'Site URL')")) - $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Is Deleted' -Value "$($UserOneDriveDetails.'Is Deleted')")) - $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Last Activity Date' -Value "$($UserOneDriveDetails.'Last Activity Date')")) - $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'File Count' -Value "$($UserOneDriveDetails.'File Count')")) - $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Active File Count' -Value "$($UserOneDriveDetails.'Active File Count')")) - $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Storage Used (Byte)' -Value "$($UserOneDriveDetails.'Storage Used (Byte)')")) - $OneDriveFormatted.add($(Get-HuduFormattedField -Title 'Storage Allocated (Byte)' -Value "$($UserOneDriveDetails.'Storage Allocated (Byte)')")) + $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 = @"
    From e17e01a8a033f0b0386a18c6e05ea8dc326a40d9 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 9 Jul 2024 12:03:39 -0400 Subject: [PATCH 090/104] Remove license asset field map --- Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 index bb12d561104a..7004401fd33d 100644 --- a/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Get-HuduFieldMapping.ps1 @@ -24,11 +24,6 @@ function Get-HuduFieldMapping { FieldLabel = 'Asset Layout for M365 Devices' FieldType = 'Layouts' } - [PSCustomObject]@{ - FieldName = 'Licenses' - FieldLabel = 'Asset Layout for M365 Licenses' - FieldType = 'Layouts' - } ) $Table = Get-CIPPTable -TableName Extensionsconfig From 75cc04bfae53ba030ad8b9cb29aecc2fad7c6e1e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 9 Jul 2024 12:51:33 -0400 Subject: [PATCH 091/104] Add logging --- .../CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 | 3 ++- .../CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 index 699c443d80a7..40479df6de08 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 @@ -69,7 +69,8 @@ Function Invoke-ExecExtensionsConfig { Add-CIPPAzDataTableEntity @Table -Entity $Config -Force | Out-Null - $CippUri = [System.Uri]$TriggerMetadata.Headers.'x-ms-original-url' + $Uri = $TriggerMetadata.Headers.referer ?? $TriggerMetadata.Headers.'x-ms-original-url' + $CippUri = [System.Uri]$Uri $AddObject = @{ PartitionKey = 'InstanceProperties' RowKey = 'CIPPURL' diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index 09518896bf9b..405b48b8d514 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -906,10 +906,12 @@ function Invoke-HuduExtensionSync { } } 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 } From 00b7c74d59dae8eb8d5cdb4d0e957af590d5f19e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 9 Jul 2024 13:00:26 -0400 Subject: [PATCH 092/104] Update Invoke-ExecExtensionsConfig.ps1 --- .../CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 index 40479df6de08..2b9f83d536e5 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 @@ -74,7 +74,8 @@ Function Invoke-ExecExtensionsConfig { $AddObject = @{ PartitionKey = 'InstanceProperties' RowKey = 'CIPPURL' - Value = '{0}://{1}' -f $CippUri.Scheme, $CippUri.Authority + Value = ('{0}://{1}' -f $CippUri.Scheme, $CippUri.Authority) + Original = $Uri } $ConfigTable = Get-CIPPTable -tablename 'Config' Add-AzDataTableEntity @ConfigTable -Entity $AddObject -Force From 26de451743df49b9ea254dd42368f2acac0dbd14 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 9 Jul 2024 13:09:58 -0400 Subject: [PATCH 093/104] Update Invoke-ExecExtensionsConfig.ps1 --- .../CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 index 2b9f83d536e5..3dcfd1bc34db 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 @@ -69,12 +69,10 @@ Function Invoke-ExecExtensionsConfig { Add-CIPPAzDataTableEntity @Table -Entity $Config -Force | Out-Null - $Uri = $TriggerMetadata.Headers.referer ?? $TriggerMetadata.Headers.'x-ms-original-url' - $CippUri = [System.Uri]$Uri $AddObject = @{ PartitionKey = 'InstanceProperties' RowKey = 'CIPPURL' - Value = ('{0}://{1}' -f $CippUri.Scheme, $CippUri.Authority) + Value = ([System.Uri]$TriggerMetadata.Headers.referer).Host Original = $Uri } $ConfigTable = Get-CIPPTable -tablename 'Config' From ba5cd7f4a35c456f5f4037c8f5256d9b4476e4ff Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 9 Jul 2024 13:24:43 -0400 Subject: [PATCH 094/104] Update Invoke-ExecExtensionsConfig.ps1 --- .../CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 index 3dcfd1bc34db..bc19a2b3940a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExtensionsConfig.ps1 @@ -69,12 +69,13 @@ Function Invoke-ExecExtensionsConfig { Add-CIPPAzDataTableEntity @Table -Entity $Config -Force | Out-Null + #Write-Information ($Request.Headers | ConvertTo-Json) $AddObject = @{ PartitionKey = 'InstanceProperties' RowKey = 'CIPPURL' - Value = ([System.Uri]$TriggerMetadata.Headers.referer).Host - Original = $Uri + 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 From 8dd88471f9649a0af03732e0b9cc8f95304e5b63 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 9 Jul 2024 13:27:44 -0400 Subject: [PATCH 095/104] Update Invoke-HuduExtensionSync.ps1 --- Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index 405b48b8d514..79002905bded 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -53,7 +53,7 @@ function Invoke-HuduExtensionSync { $ConfigTable = Get-Cipptable -tablename 'Config' $Config = Get-CippAzDataTableEntity @ConfigTable -Filter "PartitionKey eq 'InstanceProperties' and RowKey eq 'CIPPURL'" - $CIPPURL = $Config.Value + $CIPPURL = 'https://{0}' -f $Config.Value $ExtensionCache = Get-ExtensionCacheData -TenantFilter $Tenant.defaultDomainName From 9db8b8aacd6965466181baf19300c8879d14e0ee Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Jul 2024 19:45:21 +0200 Subject: [PATCH 096/104] temporary fix --- .../Public/GraphHelper/New-GraphBulkRequest.ps1 | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) 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++ From 44567699eaee3df8278c9791739ce0ec8d810ceb Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 9 Jul 2024 14:41:43 -0400 Subject: [PATCH 097/104] Update Sync-CippExtensionData.ps1 --- .../Public/Extension Functions/Sync-CippExtensionData.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 index dba30aad0b60..6a02fdd2c6cb 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Sync-CippExtensionData.ps1 @@ -43,7 +43,7 @@ function Sync-CippExtensionData { @{ id = 'Domains' method = 'GET' - url = '/domains$top=999' + url = '/domains?$top=99' }, @{ id = 'Licenses' From d0de6ab1d2849564cbb0504e0e0753af14b81807 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 9 Jul 2024 14:56:25 -0400 Subject: [PATCH 098/104] Fix CIPP links --- .../CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index 79002905bded..a9830d06ae96 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -607,9 +607,9 @@ function Invoke-HuduExtensionSync { [System.Collections.Generic.List[PSCustomObject]]$CIPPLinksFormatted = @() if ($EnableCIPP) { - $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/identity/administration/users/view?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'far fa-eye' -Title 'CIPP - View User')) - $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/identity/administration/users/edit?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'fas fa-user-cog' -Title 'CIPP - Edit User')) - $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/identity/administration/ViewBec?userId=$($User.id)%26tenantDomain%3D$($Tenant.defaultDomainName)" -Icon 'fas fa-user-secret' -Title 'CIPP - Research Compromise')) + $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 - Research Compromise')) } [System.Collections.Generic.List[PSCustomObject]]$UserLinksFormatted = @() From 5161648957972a263759c9555198553bbde4853e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 9 Jul 2024 15:00:25 -0400 Subject: [PATCH 099/104] Fix onedrive data --- Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index a9830d06ae96..881c002059e6 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -458,7 +458,7 @@ function Invoke-HuduExtensionSync { $DisplayName }) -join ', ' - $UserOneDriveDetails = $OneDriveDetails | Where-Object { $_.'Owner Principal Name' -eq $user.UserPrincipalName } + $UserOneDriveDetails = $OneDriveDetails | Where-Object { $_.ownerPrincipalName -eq $user.UserPrincipalName } From 0ae2de55306ddfbef31c5497a25a1438d0bc7e61 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Jul 2024 21:41:44 +0200 Subject: [PATCH 100/104] removed += for bobby --- .../Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 index ded00502d1e0..ea9a2c14fc1d 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 @@ -9,7 +9,7 @@ function Invoke-CIPPStandardDisableBasicAuthSMTP { .TAG "mediumimpact" .HELPTEXT - Disables SMTP AUTH for the organization and all users. This is the default for new tenants. + 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. .ADDEDCOMPONENT @@ -21,7 +21,7 @@ function Invoke-CIPPStandardDisableBasicAuthSMTP { Set-TransportConfig -SmtpClientAuthenticationDisabled $true .RECOMMENDEDBY .DOCSDESCRIPTION - Disables SMTP AUTH for the organization and all users. This is the default for new tenants. + Disables SMTP AUTH for the organization and all users. This is the default for new tenants. .UPDATECOMMENTBLOCK Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> @@ -61,18 +61,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) { From 14972dd4022e116561f40c567d1a5b3c9d2523de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 9 Jul 2024 21:49:37 +0200 Subject: [PATCH 101/104] Improve DeletedUserRentention standard --- ...voke-CIPPStandardDeletedUserRentention.ps1 | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 index ae712abebb36..4663c7fa9bae 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 @@ -26,44 +26,57 @@ function Invoke-CIPPStandardDeletedUserRentention { Run the Tools\Update-StandardsComments.ps1 script to update this comment block #> + param($Tenant, $Settings) + $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true + 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 + } - 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 } + # Backwards compatibility for pre v5.10.0 + if ($null -eq $Settings.Days) { + $WantedState = 365 + } else { + $WantedState = [int]$Settings.Days + } + + $StateSetCorrectly = if ($CurrentInfo.deletedUserPersonalSiteRetentionPeriodInDays -eq $WantedState) { $true } else { $false } + $RetentionInYears = [int]$Settings.Days / 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 - } } From 7a6e3d78453891e30791962e2d21a66223970816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 9 Jul 2024 21:53:34 +0200 Subject: [PATCH 102/104] Handle pre v5.10.0 --- .../Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 index 4663c7fa9bae..012a8555188e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 @@ -47,7 +47,7 @@ function Invoke-CIPPStandardDeletedUserRentention { } $StateSetCorrectly = if ($CurrentInfo.deletedUserPersonalSiteRetentionPeriodInDays -eq $WantedState) { $true } else { $false } - $RetentionInYears = [int]$Settings.Days / 365 + $RetentionInYears = $WantedState / 365 If ($Settings.remediate -eq $true) { Write-Host 'Time to remediate' From 50eaa5c7a20d0a5ab656e5af1a192c2a954a0e07 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 9 Jul 2024 15:54:33 -0400 Subject: [PATCH 103/104] prettification --- Modules/CippExtensions/Private/Hudu/Get-HuduLinkBlock.ps1 | 2 +- Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CippExtensions/Private/Hudu/Get-HuduLinkBlock.ps1 b/Modules/CippExtensions/Private/Hudu/Get-HuduLinkBlock.ps1 index 99cc3d42af27..5afbaf90de19 100644 --- a/Modules/CippExtensions/Private/Hudu/Get-HuduLinkBlock.ps1 +++ b/Modules/CippExtensions/Private/Hudu/Get-HuduLinkBlock.ps1 @@ -1,3 +1,3 @@ function Get-HuduLinkBlock($URL, $Icon, $Title) { - return "" + return "" } diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index 881c002059e6..80f92e6ad662 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -609,7 +609,7 @@ function Invoke-HuduExtensionSync { 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 - Research Compromise')) + $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 = @() From c0819e8924d7fa18728bac8112225aeb6bd5f56a Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Jul 2024 21:57:20 +0200 Subject: [PATCH 104/104] version up --- version_latest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_latest.txt b/version_latest.txt index d2d714f2a990..09b254e90c61 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.9.4 \ No newline at end of file +6.0.0