diff --git a/Config/openapi.json b/Config/openapi.json index 0456733b43b00..81fb43f4e56a8 100644 --- a/Config/openapi.json +++ b/Config/openapi.json @@ -3186,6 +3186,10 @@ "type": "boolean", "description": "(auto) from CippAddGroupForm.jsx" }, + "disableNesting": { + "type": "boolean", + "description": "Optional. When true, prevents other groups from being added as members. Only applies to Graph-created groups (generic, azurerole, m365, dynamic)." + }, "primDomain": { "allOf": [ { diff --git a/Modules/AzBobbyTables/3.6.0/AzBobbyTables.PS.dll b/Modules/AzBobbyTables/3.6.0/AzBobbyTables.PS.dll deleted file mode 100644 index 0886fa7c42d74..0000000000000 Binary files a/Modules/AzBobbyTables/3.6.0/AzBobbyTables.PS.dll and /dev/null differ diff --git a/Modules/AzBobbyTables/3.6.0/PSGetModuleInfo.xml b/Modules/AzBobbyTables/3.6.0/PSGetModuleInfo.xml deleted file mode 100644 index 3ba1a2251829c..0000000000000 --- a/Modules/AzBobbyTables/3.6.0/PSGetModuleInfo.xml +++ /dev/null @@ -1,159 +0,0 @@ - - - - Microsoft.PowerShell.Commands.PSRepositoryItemInfo - System.Management.Automation.PSCustomObject - System.Object - - - AzBobbyTables - 3.6.0 - Module - A module for handling Azure Table Storage operations by wrapping the Azure Data Tables SDK. - Emanuel Palm - PalmEmanuel - (c) Emanuel Palm. All rights reserved. -
2026-07-01T12:50:12+08:00
- -
2026-07-01T21:42:52.9978294+08:00
- - - - Microsoft.PowerShell.Commands.DisplayHintType - System.Enum - System.ValueType - System.Object - - DateTime - 2 - - -
- - https://github.com/PalmEmanuel/AzBobbyTables/blob/main/LICENSE - https://github.com/PalmEmanuel/AzBobbyTables - - - - System.Object[] - System.Array - System.Object - - - azure - storage - table - cosmos - cosmosdb - data - PSModule - PSEdition_Core - - - - - System.Collections.Hashtable - System.Object - - - - Function - - - - - - - Command - - - - Add-AzDataTableEntity - Clear-AzDataTable - Get-AzDataTable - Get-AzDataTableEntity - Get-AzDataTableSupportedEntityType - Remove-AzDataTableEntity - Update-AzDataTableEntity - New-AzDataTableContext - Remove-AzDataTable - New-AzDataTable - - - - - Cmdlet - - - - Add-AzDataTableEntity - Clear-AzDataTable - Get-AzDataTable - Get-AzDataTableEntity - Get-AzDataTableSupportedEntityType - Remove-AzDataTableEntity - Update-AzDataTableEntity - New-AzDataTableContext - Remove-AzDataTable - New-AzDataTable - - - - - DscResource - - - - RoleCapability - - - - Workflow - - - - - - ## [3.6.0] - 2026-07-01_x000A__x000A_### Added_x000A__x000A_- Added a `-MaxConnectionsPerServer` parameter to `New-AzDataTableContext` to cap the number of concurrent connections per server endpoint on the shared HTTP client pool. Applied process-wide on first use; default is unlimited. ([#133](https://github.com/PalmEmanuel/AzBobbyTables/pull/122))_x000A_- Added a `-MaxRetries` parameter to the table operation cmdlets (`Add-`, `Get-`, `Remove-`, `Update-AzDataTableEntity`, `Clear-`, `Get-`, `New-`, `Remove-AzDataTable`) to retry throttled requests (HTTP 429), waiting for the service's Retry-After hint between attempts. Defaults to `0` (no retries). ([#133](https://github.com/PalmEmanuel/AzBobbyTables/pull/122))_x000A__x000A_### Changed_x000A__x000A_Bumped Microsoft.VisualStudio.Threading from 17.14.15 to 18.7.23 (#132) - - - - - https://www.powershellgallery.com/api/v2 - PSGallery - NuGet - - - System.Management.Automation.PSCustomObject - System.Object - - - (c) Emanuel Palm. All rights reserved. - A module for handling Azure Table Storage operations by wrapping the Azure Data Tables SDK. - False - ## [3.6.0] - 2026-07-01_x000A__x000A_### Added_x000A__x000A_- Added a `-MaxConnectionsPerServer` parameter to `New-AzDataTableContext` to cap the number of concurrent connections per server endpoint on the shared HTTP client pool. Applied process-wide on first use; default is unlimited. ([#133](https://github.com/PalmEmanuel/AzBobbyTables/pull/122))_x000A_- Added a `-MaxRetries` parameter to the table operation cmdlets (`Add-`, `Get-`, `Remove-`, `Update-AzDataTableEntity`, `Clear-`, `Get-`, `New-`, `Remove-AzDataTable`) to retry throttled requests (HTTP 429), waiting for the service's Retry-After hint between attempts. Defaults to `0` (no retries). ([#133](https://github.com/PalmEmanuel/AzBobbyTables/pull/122))_x000A__x000A_### Changed_x000A__x000A_Bumped Microsoft.VisualStudio.Threading from 17.14.15 to 18.7.23 (#132) - True - True - 3 - 93772 - 1969607 - 1/07/2026 12:50:12 PM +08:00 - 1/07/2026 12:50:12 PM +08:00 - 1/07/2026 1:42:19 PM +08:00 - azure storage table cosmos cosmosdb data PSModule PSEdition_Core PSCmdlet_Add-AzDataTableEntity PSCommand_Add-AzDataTableEntity PSCmdlet_Clear-AzDataTable PSCommand_Clear-AzDataTable PSCmdlet_Get-AzDataTable PSCommand_Get-AzDataTable PSCmdlet_Get-AzDataTableEntity PSCommand_Get-AzDataTableEntity PSCmdlet_Get-AzDataTableSupportedEntityType PSCommand_Get-AzDataTableSupportedEntityType PSCmdlet_Remove-AzDataTableEntity PSCommand_Remove-AzDataTableEntity PSCmdlet_Update-AzDataTableEntity PSCommand_Update-AzDataTableEntity PSCmdlet_New-AzDataTableContext PSCommand_New-AzDataTableContext PSCmdlet_Remove-AzDataTable PSCommand_Remove-AzDataTable PSCmdlet_New-AzDataTable PSCommand_New-AzDataTable PSIncludes_Cmdlet - False - 2026-07-01T13:42:19Z - 3.6.0 - Emanuel Palm - false - Module - AzBobbyTables.nuspec|dependencies\System.Memory.Data.dll|dependencies\System.ClientModel.dll|dependencies\System.Interactive.Async.dll|LICENSE|dependencies\AzBobbyTables.Core.dll|dependencies\System.Text.Encodings.Web.dll|dependencies\Microsoft.Bcl.AsyncInterfaces.dll|AzBobbyTables.psd1|dependencies\System.Linq.AsyncEnumerable.dll|dependencies\Microsoft.Win32.Registry.dll|dependencies\System.Numerics.Vectors.dll|CHANGELOG.md|dependencies\System.Linq.Async.dll|dependencies\System.Security.Principal.Windows.dll|dependencies\System.Buffers.dll|AzBobbyTables.PS.dll|dependencies\System.Threading.Tasks.Extensions.dll|dependencies\System.Security.AccessControl.dll|dependencies\Azure.Core.dll|en-US\AzBobbyTables.PS.dll-Help.xml|dependencies\System.Memory.dll|dependencies\System.Diagnostics.DiagnosticSource.dll|dependencies\Microsoft.VisualStudio.Validation.dll|dependencies\Microsoft.Bcl.Memory.dll|dependencies\Microsoft.VisualStudio.Threading.dll|dependencies\Azure.Data.Tables.dll|dependencies\System.Runtime.CompilerServices.Unsafe.dll|dependencies\System.Text.Json.dll - eead4f42-5080-4f83-8901-340c529a5a11 - 7.0 - pipe.how - - - C:\Users\Zac\Documents\PowerShell\Modules\AzBobbyTables\3.6.0 -
-
-
diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/AzBobbyTables.Core.dll b/Modules/AzBobbyTables/3.6.0/dependencies/AzBobbyTables.Core.dll deleted file mode 100644 index 4cb326f0b68db..0000000000000 Binary files a/Modules/AzBobbyTables/3.6.0/dependencies/AzBobbyTables.Core.dll and /dev/null differ diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/Microsoft.Bcl.Memory.dll b/Modules/AzBobbyTables/3.6.0/dependencies/Microsoft.Bcl.Memory.dll deleted file mode 100644 index 8b5a97d8c70eb..0000000000000 Binary files a/Modules/AzBobbyTables/3.6.0/dependencies/Microsoft.Bcl.Memory.dll and /dev/null differ diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Interactive.Async.dll b/Modules/AzBobbyTables/3.6.0/dependencies/System.Interactive.Async.dll deleted file mode 100644 index 0b7806c9d072f..0000000000000 Binary files a/Modules/AzBobbyTables/3.6.0/dependencies/System.Interactive.Async.dll and /dev/null differ diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Linq.Async.dll b/Modules/AzBobbyTables/3.6.0/dependencies/System.Linq.Async.dll deleted file mode 100644 index cfd93e39366fd..0000000000000 Binary files a/Modules/AzBobbyTables/3.6.0/dependencies/System.Linq.Async.dll and /dev/null differ diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Linq.AsyncEnumerable.dll b/Modules/AzBobbyTables/3.6.0/dependencies/System.Linq.AsyncEnumerable.dll deleted file mode 100644 index 88eb4bd5aa174..0000000000000 Binary files a/Modules/AzBobbyTables/3.6.0/dependencies/System.Linq.AsyncEnumerable.dll and /dev/null differ diff --git a/Modules/AzBobbyTables/3.6.2/AzBobbyTables.PS.dll b/Modules/AzBobbyTables/3.6.2/AzBobbyTables.PS.dll new file mode 100644 index 0000000000000..88353e6f18a72 Binary files /dev/null and b/Modules/AzBobbyTables/3.6.2/AzBobbyTables.PS.dll differ diff --git a/Modules/AzBobbyTables/3.6.0/AzBobbyTables.psd1 b/Modules/AzBobbyTables/3.6.2/AzBobbyTables.psd1 similarity index 84% rename from Modules/AzBobbyTables/3.6.0/AzBobbyTables.psd1 rename to Modules/AzBobbyTables/3.6.2/AzBobbyTables.psd1 index b07587f76bff4..24bccef0a9808 100644 --- a/Modules/AzBobbyTables/3.6.0/AzBobbyTables.psd1 +++ b/Modules/AzBobbyTables/3.6.2/AzBobbyTables.psd1 @@ -1,10 +1,10 @@ -@{ +@{ # Script module or binary module file associated with this manifest. RootModule = 'AzBobbyTables.PS.dll' # Version number of this module. -ModuleVersion = '3.6.0' +ModuleVersion = '3.6.2' # Supported PSEditions CompatiblePSEditions = @('Core') @@ -66,11 +66,14 @@ FunctionsToExport = @() # 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 = @( 'Add-AzDataTableEntity' + 'Add-AzDataTableLargeEntity' 'Clear-AzDataTable' 'Get-AzDataTable' 'Get-AzDataTableEntity' + 'Get-AzDataTableLargeEntity' 'Get-AzDataTableSupportedEntityType' 'Remove-AzDataTableEntity' + 'Remove-AzDataTableLargeEntity' 'Update-AzDataTableEntity' 'New-AzDataTableContext' 'Remove-AzDataTable' @@ -110,18 +113,7 @@ PrivateData = @{ # IconUri = '' # ReleaseNotes of this module - ReleaseNotes = '## [3.6.0] - 2026-07-01 - -### Added - -- Added a `-MaxConnectionsPerServer` parameter to `New-AzDataTableContext` to cap the number of concurrent connections per server endpoint on the shared HTTP client pool. Applied process-wide on first use; default is unlimited. ([#133](https://github.com/PalmEmanuel/AzBobbyTables/pull/122)) -- Added a `-MaxRetries` parameter to the table operation cmdlets (`Add-`, `Get-`, `Remove-`, `Update-AzDataTableEntity`, `Clear-`, `Get-`, `New-`, `Remove-AzDataTable`) to retry throttled requests (HTTP 429), waiting for the service''s Retry-After hint between attempts. Defaults to `0` (no retries). ([#133](https://github.com/PalmEmanuel/AzBobbyTables/pull/122)) - -### Changed - -Bumped Microsoft.VisualStudio.Threading from 17.14.15 to 18.7.23 (#132) - -' + # ReleaseNotes = '' # Prerelease string of this module # Prerelease = '' @@ -143,3 +135,5 @@ Bumped Microsoft.VisualStudio.Threading from 17.14.15 to 18.7.23 (#132) # DefaultCommandPrefix = '' } + + diff --git a/Modules/AzBobbyTables/3.6.0/CHANGELOG.md b/Modules/AzBobbyTables/3.6.2/CHANGELOG.md similarity index 76% rename from Modules/AzBobbyTables/3.6.0/CHANGELOG.md rename to Modules/AzBobbyTables/3.6.2/CHANGELOG.md index 1871f2ceeb3b9..f2212ada1a1cd 100644 --- a/Modules/AzBobbyTables/3.6.0/CHANGELOG.md +++ b/Modules/AzBobbyTables/3.6.2/CHANGELOG.md @@ -4,6 +4,27 @@ The format is based on and uses the types of changes according to [Keep a Change ## [Unreleased] +## [3.6.2] - 2026-07-30 + +### Added + +- Added `Add-AzDataTableLargeEntity`, `Get-AzDataTableLargeEntity` and `Remove-AzDataTableLargeEntity` for working with entities that exceed the Azure Table Storage size limits (64 KiB per string property, 1 MiB per entity). Oversized string properties are split into chunk properties recorded in a `SplitOverProps` JSON manifest, and entities that are still too large are distributed over multiple rows marked with `OriginalEntityId` and `PartIndex`; reads reassemble the original entity transparently and removes delete all part rows. The existing entity cmdlets are unaffected. + +## [3.6.1] - 2026-07-29 + +### Changed + +- `Add-`, `Remove-` and `Update-AzDataTableEntity` now collect entities from the pipeline and submit them as batched transactions when the pipeline completes, instead of one transaction per pipeline record. +- `Get-AzDataTableEntity` passes a page-size hint to the service when `-First` is used without `-Sort`, so the service returns a bounded page instead of a full page truncated client-side. +- `-Count` no longer projects full PSObjects in order to count them. +- Entity validation and converter selection now run in a single pass. + +### Removed + +- Dependency on `System.Linq.Async`; queries now run synchronously. + +## [3.6.0] - 2026-07-01 + ### Added - Added a `-MaxConnectionsPerServer` parameter to `New-AzDataTableContext` to cap the number of concurrent connections per server endpoint on the shared HTTP client pool. Applied process-wide on first use; default is unlimited. ([#133](https://github.com/PalmEmanuel/AzBobbyTables/pull/122)) @@ -104,7 +125,9 @@ Bumped Microsoft.VisualStudio.Threading from 17.14.15 to 18.7.23 (#132) ## 3.1.1 - 2023-05-03 -[unreleased]: https://github.com/PalmEmanuel/AzBobbyTables/compare/v3.5.0...HEAD +[unreleased]: https://github.com/PalmEmanuel/AzBobbyTables/compare/v3.6.1...HEAD +[3.6.1]: https://github.com/PalmEmanuel/AzBobbyTables/compare/v3.6.0...v3.6.1 +[3.6.0]: https://github.com/PalmEmanuel/AzBobbyTables/compare/v3.5.0...v3.6.0 [3.5.0]: https://github.com/PalmEmanuel/AzBobbyTables/compare/v3.4.2...v3.5.0 [3.4.2]: https://github.com/PalmEmanuel/AzBobbyTables/compare/v3.4.1...v3.4.2 [3.4.1]: https://github.com/PalmEmanuel/AzBobbyTables/compare/v3.4.0...v3.4.1 @@ -116,3 +139,4 @@ Bumped Microsoft.VisualStudio.Threading from 17.14.15 to 18.7.23 (#132) [3.2.0]: https://github.com/PalmEmanuel/AzBobbyTables/compare/v3.1.3...v3.2.0 [3.1.3]: https://github.com/PalmEmanuel/AzBobbyTables/compare/v3.1.2...v3.1.3 [3.1.2]: https://github.com/PalmEmanuel/AzBobbyTables/compare/d854153aca6c5cce35a123deb86653a0d3289b07...v3.1.2 + diff --git a/Modules/AzBobbyTables/3.6.0/LICENSE b/Modules/AzBobbyTables/3.6.2/LICENSE similarity index 100% rename from Modules/AzBobbyTables/3.6.0/LICENSE rename to Modules/AzBobbyTables/3.6.2/LICENSE diff --git a/Modules/AzBobbyTables/3.6.2/dependencies/AzBobbyTables.Core.dll b/Modules/AzBobbyTables/3.6.2/dependencies/AzBobbyTables.Core.dll new file mode 100644 index 0000000000000..7ddf1c46dfb29 Binary files /dev/null and b/Modules/AzBobbyTables/3.6.2/dependencies/AzBobbyTables.Core.dll differ diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/Azure.Core.dll b/Modules/AzBobbyTables/3.6.2/dependencies/Azure.Core.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/Azure.Core.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/Azure.Core.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/Azure.Data.Tables.dll b/Modules/AzBobbyTables/3.6.2/dependencies/Azure.Data.Tables.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/Azure.Data.Tables.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/Azure.Data.Tables.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/Microsoft.Bcl.AsyncInterfaces.dll b/Modules/AzBobbyTables/3.6.2/dependencies/Microsoft.Bcl.AsyncInterfaces.dll similarity index 83% rename from Modules/AzBobbyTables/3.6.0/dependencies/Microsoft.Bcl.AsyncInterfaces.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/Microsoft.Bcl.AsyncInterfaces.dll index 2867daaf7d5fe..b2851e22156cd 100644 Binary files a/Modules/AzBobbyTables/3.6.0/dependencies/Microsoft.Bcl.AsyncInterfaces.dll and b/Modules/AzBobbyTables/3.6.2/dependencies/Microsoft.Bcl.AsyncInterfaces.dll differ diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/Microsoft.VisualStudio.Threading.dll b/Modules/AzBobbyTables/3.6.2/dependencies/Microsoft.VisualStudio.Threading.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/Microsoft.VisualStudio.Threading.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/Microsoft.VisualStudio.Threading.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/Microsoft.VisualStudio.Validation.dll b/Modules/AzBobbyTables/3.6.2/dependencies/Microsoft.VisualStudio.Validation.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/Microsoft.VisualStudio.Validation.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/Microsoft.VisualStudio.Validation.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/Microsoft.Win32.Registry.dll b/Modules/AzBobbyTables/3.6.2/dependencies/Microsoft.Win32.Registry.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/Microsoft.Win32.Registry.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/Microsoft.Win32.Registry.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Buffers.dll b/Modules/AzBobbyTables/3.6.2/dependencies/System.Buffers.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/System.Buffers.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/System.Buffers.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.ClientModel.dll b/Modules/AzBobbyTables/3.6.2/dependencies/System.ClientModel.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/System.ClientModel.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/System.ClientModel.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Diagnostics.DiagnosticSource.dll b/Modules/AzBobbyTables/3.6.2/dependencies/System.Diagnostics.DiagnosticSource.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/System.Diagnostics.DiagnosticSource.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/System.Diagnostics.DiagnosticSource.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Memory.Data.dll b/Modules/AzBobbyTables/3.6.2/dependencies/System.Memory.Data.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/System.Memory.Data.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/System.Memory.Data.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Memory.dll b/Modules/AzBobbyTables/3.6.2/dependencies/System.Memory.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/System.Memory.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/System.Memory.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Numerics.Vectors.dll b/Modules/AzBobbyTables/3.6.2/dependencies/System.Numerics.Vectors.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/System.Numerics.Vectors.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/System.Numerics.Vectors.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Runtime.CompilerServices.Unsafe.dll b/Modules/AzBobbyTables/3.6.2/dependencies/System.Runtime.CompilerServices.Unsafe.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/System.Runtime.CompilerServices.Unsafe.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/System.Runtime.CompilerServices.Unsafe.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Security.AccessControl.dll b/Modules/AzBobbyTables/3.6.2/dependencies/System.Security.AccessControl.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/System.Security.AccessControl.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/System.Security.AccessControl.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Security.Principal.Windows.dll b/Modules/AzBobbyTables/3.6.2/dependencies/System.Security.Principal.Windows.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/System.Security.Principal.Windows.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/System.Security.Principal.Windows.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Text.Encodings.Web.dll b/Modules/AzBobbyTables/3.6.2/dependencies/System.Text.Encodings.Web.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/System.Text.Encodings.Web.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/System.Text.Encodings.Web.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Text.Json.dll b/Modules/AzBobbyTables/3.6.2/dependencies/System.Text.Json.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/System.Text.Json.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/System.Text.Json.dll diff --git a/Modules/AzBobbyTables/3.6.0/dependencies/System.Threading.Tasks.Extensions.dll b/Modules/AzBobbyTables/3.6.2/dependencies/System.Threading.Tasks.Extensions.dll similarity index 100% rename from Modules/AzBobbyTables/3.6.0/dependencies/System.Threading.Tasks.Extensions.dll rename to Modules/AzBobbyTables/3.6.2/dependencies/System.Threading.Tasks.Extensions.dll diff --git a/Modules/AzBobbyTables/3.6.0/en-US/AzBobbyTables.PS.dll-Help.xml b/Modules/AzBobbyTables/3.6.2/en-US/AzBobbyTables.PS.dll-Help.xml similarity index 70% rename from Modules/AzBobbyTables/3.6.0/en-US/AzBobbyTables.PS.dll-Help.xml rename to Modules/AzBobbyTables/3.6.2/en-US/AzBobbyTables.PS.dll-Help.xml index 66a2240908b39..3ee3d4314d013 100644 --- a/Modules/AzBobbyTables/3.6.0/en-US/AzBobbyTables.PS.dll-Help.xml +++ b/Modules/AzBobbyTables/3.6.2/en-US/AzBobbyTables.PS.dll-Help.xml @@ -280,6 +280,289 @@ PS C:\> Add-AzDataTableEntity -Entity $Users -Context $Context -OperationType + + + Add-AzDataTableLargeEntity + Add + AzDataTableLargeEntity + + Add one or more entities to an Azure Table, transparently splitting entities that exceed the Azure Table Storage size limits. + + + + Add one or more entities to an Azure Table, as an array of either Hashtables, PSObjects, or SortedLists. + Unlike Add-AzDataTableEntity, this cmdlet accepts entities that exceed the Azure Table Storage size limits (64 KiB per string property, 1 MiB per entity) and splits them transparently: + - A string property larger than 32256 characters is stored as multiple chunk properties named `{Property}_Part0`, `{Property}_Part1`, and so on. A JSON manifest in the `SplitOverProps` property records which chunks belong to which original property. + - An entity that is still too large after property splitting is distributed over multiple rows. The first row keeps the original RowKey, additional rows are named `{RowKey}-part1`, `{RowKey}-part2`, and so on. Each row carries an `OriginalEntityId` property with the original RowKey and a `PartIndex` property with the row order. + + Use Get-AzDataTableLargeEntity to read the entities back in their original shape, and Remove-AzDataTableLargeEntity to delete them including all part rows. + Entities in one call are deduplicated by PartitionKey and RowKey, with the last occurrence winning, since the underlying transactions reject multiple operations on the same key. After writing a split entity, leftover part rows from an earlier larger version of the same entity are cleaned up automatically. + Note that when an entity is split over multiple rows, its rows are written as full replacements even with an OperationType of UpsertMerge, since the distribution of properties over rows changes between writes and merging would leave stale values behind. Entities small enough to fit in one row are written with the requested operation type. When the sizes of entities vary between writes across the splitting threshold, prefer UpsertReplace (or Force). + + + + Add-AzDataTableLargeEntity + + Context + + A context object created by New-AzDataTableContext, with authentication information for the table to operate on. + + AzDataTableContext + + AzDataTableContext + + + None + + + CreateTableIfNotExists + + If the table should be created if it does not exist. + + + SwitchParameter + + + False + + + Entity + + The entities to add to the table. + + Object[] + + Object[] + + + None + + + Force + + Overwrites provided entities if they exist. + + + SwitchParameter + + + False + + + MaxRetries + + The number of times to retry the operation when the request is throttled by the service with an HTTP 429 response. Between attempts the module waits for the duration indicated by the service's Retry-After response. Defaults to 0, which disables retries. + + Int32 + + Int32 + + + None + + + + Add-AzDataTableLargeEntity + + Context + + A context object created by New-AzDataTableContext, with authentication information for the table to operate on. + + AzDataTableContext + + AzDataTableContext + + + None + + + CreateTableIfNotExists + + If the table should be created if it does not exist. + + + SwitchParameter + + + False + + + Entity + + The entities to add to the table. + + Object[] + + Object[] + + + None + + + MaxRetries + + The number of times to retry the operation when the request is throttled by the service with an HTTP 429 response. Between attempts the module waits for the duration indicated by the service's Retry-After response. Defaults to 0, which disables retries. + + Int32 + + Int32 + + + None + + + OperationType + + The operation type to perform on the entities. See the Azure SDK documentation for more information: + https://learn.microsoft.com/en-us/dotnet/api/azure.data.tables.tabletransactionactiontype + Entities split over multiple rows are always written as full replacements, see the description. + + + Add + UpsertReplace + UpsertMerge + + String + + String + + + None + + + + + + Context + + A context object created by New-AzDataTableContext, with authentication information for the table to operate on. + + AzDataTableContext + + AzDataTableContext + + + None + + + CreateTableIfNotExists + + If the table should be created if it does not exist. + + SwitchParameter + + SwitchParameter + + + False + + + Entity + + The entities to add to the table. + + Object[] + + Object[] + + + None + + + Force + + Overwrites provided entities if they exist. + + SwitchParameter + + SwitchParameter + + + False + + + MaxRetries + + The number of times to retry the operation when the request is throttled by the service with an HTTP 429 response. Between attempts the module waits for the duration indicated by the service's Retry-After response. Defaults to 0, which disables retries. + + Int32 + + Int32 + + + None + + + OperationType + + The operation type to perform on the entities. See the Azure SDK documentation for more information: + https://learn.microsoft.com/en-us/dotnet/api/azure.data.tables.tabletransactionactiontype + Entities split over multiple rows are always written as full replacements, see the description. + + String + + String + + + None + + + + + + System.Collections.Hashtable[] or System.Management.Automation.PSObject[] or System.Collections.SortedList[] + + + This cmdlet takes either an array of hashtables, psobjects, or sorted lists as input to the Entity parameter, which can also be provided through the pipeline. + + + + + + + System.Object + + + + + + + + + Only string properties are split. A non-string property that individually exceeds the service limits, such as a byte array over 64 KiB, is rejected by the service. + The storage format reserves some naming patterns in tables used with the large-entity cmdlets: property names ending in `_Part{n}` of another property, the property names `SplitOverProps`, `OriginalEntityId` and `PartIndex`, and RowKeys of the form `{OtherRowKey}-part{n}`. Entities using such names can collide with the split representation of other entities. + + + + + -------------------------- Example 1 -------------------------- + PS C:\> $Context = New-AzDataTableContext -TableName $TableName -ConnectionString $ConnectionString +PS C:\> $Report = @{ PartitionKey = 'Reports'; RowKey = 'tenant1'; Data = $LargeJsonString } +PS C:\> Add-AzDataTableLargeEntity -Entity $Report -Context $Context -Force + + Add a report entity whose Data property may exceed the 64 KiB property limit, overwriting any existing version. + + + + -------------------------- Example 2 -------------------------- + PS C:\> $Context = New-AzDataTableContext -TableName $TableName -ConnectionString $ConnectionString +PS C:\> $CacheRows | Add-AzDataTableLargeEntity -Context $Context -Force -CreateTableIfNotExists + + Write a collection of cache rows of arbitrary size from the pipeline, creating the table if needed. + + + + + + Get-AzDataTableLargeEntity + + + + Remove-AzDataTableLargeEntity + + + + Clear-AzDataTable @@ -452,32 +735,302 @@ PS C:\> Clear-AzDataTable $Context Context - A context object created by New-AzDataTableContext, with authentication information for the storage account to operate on. + A context object created by New-AzDataTableContext, with authentication information for the storage account to operate on. + + AzDataTableContext + + AzDataTableContext + + + None + + + Filter + + A string to filter the tables returned. For more information on the filter syntax, see the Azure Table service documentation: + https://learn.microsoft.com/en-us/rest/api/storageservices/Querying-Tables-and-Entities + + String + + String + + + None + + + MaxRetries + + The number of times to retry the operation when the request is throttled by the service with an HTTP 429 response. Between attempts the module waits for the duration indicated by the service's Retry-After response. Defaults to 0, which disables retries. + + Int32 + + Int32 + + + None + + + + + + None + + + + + + + + + + System.String + + + + + + + + + + + + + + -------------------------- Example 1 -------------------------- + PS C:\> Get-AzDataTable -Context $Context + + Gets all table names in the storage account. + + + + -------------------------- Example 2 -------------------------- + PS C:\> Get-AzDataTable -Context $Context -Filter "TableName eq '$MyTableName'" + + Gets the table named `$MyTableName` to see if it exists. + + + + + + + + Get-AzDataTableEntity + Get + AzDataTableEntity + + Get one or more entities from an Azure Table. + + + + Get either all entities from an Azure Table, or those matching a provided OData filter. + Documentation on querying tables and entities: <https://docs.microsoft.com/en-gb/rest/api/storageservices/querying-tables-and-entities> + + + + Get-AzDataTableEntity + + Context + + A context object created by New-AzDataTableContext, with authentication information for the table to operate on. + + AzDataTableContext + + AzDataTableContext + + + None + + + Count + + Specifies to only get the number of matching entities in the table, and not the data itself. + + + SwitchParameter + + + False + + + MaxRetries + + The number of times to retry the operation when the request is throttled by the service with an HTTP 429 response. Between attempts the module waits for the duration indicated by the service's Retry-After response. Defaults to 0, which disables retries. + + Int32 + + Int32 + + + None + + + + Get-AzDataTableEntity + + Context + + A context object created by New-AzDataTableContext, with authentication information for the table to operate on. + + AzDataTableContext + + AzDataTableContext + + + None + + + Filter + + The OData filter to use in the query. Documentation on querying tables and entities: <https://docs.microsoft.com/en-gb/rest/api/storageservices/querying-tables-and-entities> + + String + + String + + + None + + + First + + Gets only the specified number of objects. Enter the number of objects to get. + + Int32 + + Int32 + + + None + + + MaxRetries + + The number of times to retry the operation when the request is throttled by the service with an HTTP 429 response. Between attempts the module waits for the duration indicated by the service's Retry-After response. Defaults to 0, which disables retries. + + Int32 + + Int32 + + + None + + + Property + + One or several names of properties, to specify data to return for the entities. + + String[] + + String[] + + + None + + + Skip + + Ignores the specified number of objects and then gets the remaining objects. Enter the number of objects to skip. + + Int32 + + Int32 + + + None + + + Sort + + Specifies one or several property names that to sort the entities by. If several properties are provided, the entities are sorted in the order that the property names are provided. + Note that using this parameter may slow down the command a lot when working with large data sets! + + String[] + + String[] + + + None + + + + + + Context + + A context object created by New-AzDataTableContext, with authentication information for the table to operate on. + + AzDataTableContext + + AzDataTableContext + + + None + + + Count + + Specifies to only get the number of matching entities in the table, and not the data itself. + + SwitchParameter + + SwitchParameter + + + False + + + Filter + + The OData filter to use in the query. Documentation on querying tables and entities: <https://docs.microsoft.com/en-gb/rest/api/storageservices/querying-tables-and-entities> + + String + + String + + + None + + + First + + Gets only the specified number of objects. Enter the number of objects to get. + + Int32 + + Int32 + + + None + + + MaxRetries + + The number of times to retry the operation when the request is throttled by the service with an HTTP 429 response. Between attempts the module waits for the duration indicated by the service's Retry-After response. Defaults to 0, which disables retries. - AzDataTableContext + Int32 - AzDataTableContext + Int32 None - Filter + Property - A string to filter the tables returned. For more information on the filter syntax, see the Azure Table service documentation: - https://learn.microsoft.com/en-us/rest/api/storageservices/Querying-Tables-and-Entities + One or several names of properties, to specify data to return for the entities. - String + String[] - String + String[] None - MaxRetries + Skip - The number of times to retry the operation when the request is throttled by the service with an HTTP 429 response. Between attempts the module waits for the duration indicated by the service's Retry-After response. Defaults to 0, which disables retries. + Ignores the specified number of objects and then gets the remaining objects. Enter the number of objects to skip. Int32 @@ -486,6 +1039,19 @@ PS C:\> Clear-AzDataTable $Context None + + Sort + + Specifies one or several property names that to sort the entities by. If several properties are provided, the entities are sorted in the order that the property names are provided. + Note that using this parameter may slow down the command a lot when working with large data sets! + + String[] + + String[] + + + None + @@ -500,7 +1066,7 @@ PS C:\> Clear-AzDataTable $Context - System.String + System.Management.Automation.PSObject @@ -515,16 +1081,34 @@ PS C:\> Clear-AzDataTable $Context -------------------------- Example 1 -------------------------- - PS C:\> Get-AzDataTable -Context $Context + PS C:\> $Context = New-AzDataTableContext -TableName $TableName -ConnectionString $ConnectionString +PS C:\> $UserEntity = Get-AzDataTableEntity -Filter "FirstName eq 'Bobby' and LastName eq 'Tables'" -Context $Context - Gets all table names in the storage account. + Get the user "Bobby Tables" from the table using a connection string. -------------------------- Example 2 -------------------------- - PS C:\> Get-AzDataTable -Context $Context -Filter "TableName eq '$MyTableName'" + PS C:\> $Context = New-AzDataTableContext -TableName $TableName -ConnectionString $ConnectionString +PS C:\> $UserCount = Get-AzDataTableEntity -Filter "LastName eq 'Tables'" -Context $Context -Count - Gets the table named `$MyTableName` to see if it exists. + Use the Count parameter to get only the number of users matching the filter, using a connection string. + + + + -------------------------- Example 3 -------------------------- + PS C:\> $Context = New-AzDataTableContext -TableName $TableName -ManagedIdentity -StorageAccountName $Name +PS C:\> $UserEntities = Get-AzDataTableEntity -Sort 'Id','Age' -First 100 -Skip 500 -Context $Context + + Skipping the first 100 entities, get 500 entities sorted by id and age from the table using a managed identity for authorization. + + + + -------------------------- Example 4 -------------------------- + PS C:\> $Context = New-AzDataTableContext -TableName $TableName -SharedAccessSignature $SAS +PS C:\> $UserEntities = Get-AzDataTableEntity -Property 'FirstName','Age' -Context $Context + + Get only the properties "FirstName" and "Age" for all entities found in the table using a shared access signature URL. @@ -532,20 +1116,22 @@ PS C:\> Clear-AzDataTable $Context - Get-AzDataTableEntity + Get-AzDataTableLargeEntity Get - AzDataTableEntity + AzDataTableLargeEntity - Get one or more entities from an Azure Table. + Get one or more entities from an Azure Table, reassembling entities that were split by Add-AzDataTableLargeEntity. - Get either all entities from an Azure Table, or those matching a provided OData filter. - Documentation on querying tables and entities: <https://docs.microsoft.com/en-gb/rest/api/storageservices/querying-tables-and-entities> + Get one or more entities from an Azure Table, reassembling entities that were split across multiple properties or rows because they exceeded the Azure Table Storage size limits. + Rows belonging to one logical entity are recognized by their `OriginalEntityId` property and merged in `PartIndex` order, after which chunked properties recorded in the `SplitOverProps` manifest are joined back into their original single property. Entities that were never split are returned as-is. + If both a plain row and leftover part rows exist for the same RowKey, the plain row wins, so an entity that was rewritten smaller after having been split still reads correctly. + Since split entities span multiple physical rows, use filters that do not separate an entity from its parts; filtering on the PartitionKey level is safe. First, Skip, Sort and Count operate on physical rows, before reassembly. A Property selection that excludes the split markers (OriginalEntityId, PartIndex, SplitOverProps and the chunk properties) prevents reassembly. - Get-AzDataTableEntity + Get-AzDataTableLargeEntity Context @@ -561,7 +1147,7 @@ PS C:\> Clear-AzDataTable $Context Count - Specifies to only get the number of matching entities in the table, and not the data itself. + Specify that the output should only specify the number of entities. Counts physical rows, so each part row of a split entity counts individually. SwitchParameter @@ -583,7 +1169,7 @@ PS C:\> Clear-AzDataTable $Context - Get-AzDataTableEntity + Get-AzDataTableLargeEntity Context @@ -599,7 +1185,7 @@ PS C:\> Clear-AzDataTable $Context Filter - The OData filter to use in the query. Documentation on querying tables and entities: <https://docs.microsoft.com/en-gb/rest/api/storageservices/querying-tables-and-entities> + The OData filter to use in the query. String @@ -611,7 +1197,7 @@ PS C:\> Clear-AzDataTable $Context First - Gets only the specified number of objects. Enter the number of objects to get. + The amount of physical rows to retrieve, counted before split entities are reassembled. Int32 @@ -635,7 +1221,7 @@ PS C:\> Clear-AzDataTable $Context Property - One or several names of properties, to specify data to return for the entities. + The properties to return for the entities. Selecting properties that exclude the split markers prevents reassembly of split entities. String[] @@ -647,7 +1233,7 @@ PS C:\> Clear-AzDataTable $Context Skip - Ignores the specified number of objects and then gets the remaining objects. Enter the number of objects to skip. + The amount of physical rows to skip from the query result, counted before split entities are reassembled. Int32 @@ -659,8 +1245,7 @@ PS C:\> Clear-AzDataTable $Context Sort - Specifies one or several property names that to sort the entities by. If several properties are provided, the entities are sorted in the order that the property names are provided. - Note that using this parameter may slow down the command a lot when working with large data sets! + The names of one or more properties to sort by, in order. Sorting applies to physical rows before reassembly. String[] @@ -687,7 +1272,7 @@ PS C:\> Clear-AzDataTable $Context Count - Specifies to only get the number of matching entities in the table, and not the data itself. + Specify that the output should only specify the number of entities. Counts physical rows, so each part row of a split entity counts individually. SwitchParameter @@ -699,7 +1284,7 @@ PS C:\> Clear-AzDataTable $Context Filter - The OData filter to use in the query. Documentation on querying tables and entities: <https://docs.microsoft.com/en-gb/rest/api/storageservices/querying-tables-and-entities> + The OData filter to use in the query. String @@ -711,7 +1296,7 @@ PS C:\> Clear-AzDataTable $Context First - Gets only the specified number of objects. Enter the number of objects to get. + The amount of physical rows to retrieve, counted before split entities are reassembled. Int32 @@ -735,7 +1320,7 @@ PS C:\> Clear-AzDataTable $Context Property - One or several names of properties, to specify data to return for the entities. + The properties to return for the entities. Selecting properties that exclude the split markers prevents reassembly of split entities. String[] @@ -747,7 +1332,7 @@ PS C:\> Clear-AzDataTable $Context Skip - Ignores the specified number of objects and then gets the remaining objects. Enter the number of objects to skip. + The amount of physical rows to skip from the query result, counted before split entities are reassembled. Int32 @@ -759,8 +1344,7 @@ PS C:\> Clear-AzDataTable $Context Sort - Specifies one or several property names that to sort the entities by. If several properties are provided, the entities are sorted in the order that the property names are provided. - Note that using this parameter may slow down the command a lot when working with large data sets! + The names of one or more properties to sort by, in order. Sorting applies to physical rows before reassembly. String[] @@ -792,44 +1376,29 @@ PS C:\> Clear-AzDataTable $Context - + The ETag and Timestamp of a reassembled entity are taken from its first part row. -------------------------- Example 1 -------------------------- PS C:\> $Context = New-AzDataTableContext -TableName $TableName -ConnectionString $ConnectionString -PS C:\> $UserEntity = Get-AzDataTableEntity -Filter "FirstName eq 'Bobby' and LastName eq 'Tables'" -Context $Context - - Get the user "Bobby Tables" from the table using a connection string. - - - - -------------------------- Example 2 -------------------------- - PS C:\> $Context = New-AzDataTableContext -TableName $TableName -ConnectionString $ConnectionString -PS C:\> $UserCount = Get-AzDataTableEntity -Filter "LastName eq 'Tables'" -Context $Context -Count - - Use the Count parameter to get only the number of users matching the filter, using a connection string. - - - - -------------------------- Example 3 -------------------------- - PS C:\> $Context = New-AzDataTableContext -TableName $TableName -ManagedIdentity -StorageAccountName $Name -PS C:\> $UserEntities = Get-AzDataTableEntity -Sort 'Id','Age' -First 100 -Skip 500 -Context $Context - - Skipping the first 100 entities, get 500 entities sorted by id and age from the table using a managed identity for authorization. - - - - -------------------------- Example 4 -------------------------- - PS C:\> $Context = New-AzDataTableContext -TableName $TableName -SharedAccessSignature $SAS -PS C:\> $UserEntities = Get-AzDataTableEntity -Property 'FirstName','Age' -Context $Context +PS C:\> Get-AzDataTableLargeEntity -Context $Context -Filter "PartitionKey eq 'Reports'" - Get only the properties "FirstName" and "Age" for all entities found in the table using a shared access signature URL. + Get all report entities, transparently reassembling any that were split on write. - + + + Add-AzDataTableLargeEntity + + + + Remove-AzDataTableLargeEntity + + + @@ -1706,6 +2275,168 @@ PS C:\> # OK - The -Force switch overrides ETag validation + + + Remove-AzDataTableLargeEntity + Remove + AzDataTableLargeEntity + + Remove one or more entities from an Azure Table, including any part rows they were split into by Add-AzDataTableLargeEntity. + + + + Remove one or more entities from an Azure Table, based on PartitionKey and RowKey. + In addition to the entity's own row, any part rows that the entity was split into on write (rows whose `OriginalEntityId` matches the entity's RowKey) are looked up and removed as well, so no orphaned parts are left behind. Part rows are removed without ETag validation; ETag validation, when not skipped with Force, applies to the entity's own row. + + + + Remove-AzDataTableLargeEntity + + Context + + A context object created by New-AzDataTableContext, with authentication information for the table to operate on. + + AzDataTableContext + + AzDataTableContext + + + None + + + Entity + + The entities to remove from the table. + + Object[] + + Object[] + + + None + + + Force + + Skips ETag validation and removes entity even if it has changed. + + + SwitchParameter + + + False + + + MaxRetries + + The number of times to retry the operation when the request is throttled by the service with an HTTP 429 response. Between attempts the module waits for the duration indicated by the service's Retry-After response. Defaults to 0, which disables retries. + + Int32 + + Int32 + + + None + + + + + + Context + + A context object created by New-AzDataTableContext, with authentication information for the table to operate on. + + AzDataTableContext + + AzDataTableContext + + + None + + + Entity + + The entities to remove from the table. + + Object[] + + Object[] + + + None + + + Force + + Skips ETag validation and removes entity even if it has changed. + + SwitchParameter + + SwitchParameter + + + False + + + MaxRetries + + The number of times to retry the operation when the request is throttled by the service with an HTTP 429 response. Between attempts the module waits for the duration indicated by the service's Retry-After response. Defaults to 0, which disables retries. + + Int32 + + Int32 + + + None + + + + + + System.Collections.Hashtable[] or System.Management.Automation.PSObject[] or System.Collections.SortedList[] + + + This cmdlet takes either an array of hashtables, psobjects, or sorted lists as input to the Entity parameter, which can also be provided through the pipeline. + + + + + + + System.Object + + + + + + + + + + + + + + -------------------------- Example 1 -------------------------- + PS C:\> $Context = New-AzDataTableContext -TableName $TableName -ConnectionString $ConnectionString +PS C:\> $Entity = Get-AzDataTableLargeEntity -Context $Context -Filter "RowKey eq 'tenant1'" +PS C:\> Remove-AzDataTableLargeEntity -Entity $Entity -Context $Context -Force + + Remove an entity and all rows it was split into. + + + + + + Add-AzDataTableLargeEntity + + + + Get-AzDataTableLargeEntity + + + + Update-AzDataTableEntity diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Applications/Push-UploadApplication.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Applications/Push-UploadApplication.ps1 index 914801bcb29d5..db5b1bbfb36f6 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Applications/Push-UploadApplication.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Applications/Push-UploadApplication.ps1 @@ -18,7 +18,7 @@ function Push-UploadApplication { } $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter if ($AppConfig.tenant -ne 'AllTenants') { - $null = Remove-AzDataTableEntity -Force @Table -Entity $clearRow + $null = Remove-CIPPAzDataTableEntity -Force @Table -Entity $clearRow } else { $Table.Force = $true $null = Add-CIPPAzDataTableEntity @Table -Entity @{ diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 index f06d3a1fae6b3..24d9c31535dfb 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 @@ -14,7 +14,7 @@ function Push-DomainAnalyserTenant { $CleanupCount = ($CleanupRows | Measure-Object).Count if ($CleanupCount -gt 0) { Write-LogMessage -API 'DomainAnalyser' -tenant $Tenant.defaultDomainName -tenantid $Tenant.customerId -message "Cleaning up $CleanupCount domain(s) for excluded tenant" -sev Info - Remove-AzDataTableEntity -Force @DomainTable -Entity $CleanupRows + Remove-CIPPAzDataTableEntity -Force @DomainTable -Entity $CleanupRows } } elseif ($Tenant.GraphErrorCount -gt 50) { return @@ -80,7 +80,7 @@ function Push-DomainAnalyserTenant { $OldDomain = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter if ($OldDomain) { - Remove-AzDataTableEntity -Force @DomainTable -Entity $OldDomain | Out-Null + Remove-CIPPAzDataTableEntity -Force @DomainTable -Entity $OldDomain | Out-Null } $Filter = "PartitionKey eq 'TenantDomains' and RowKey eq '{0}'" -f $TenantDomain.Domain diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Graph Requests/Push-ListGraphRequestQueue.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Graph Requests/Push-ListGraphRequestQueue.ps1 index fb6c7fe15bcc0..de998965ee20d 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Graph Requests/Push-ListGraphRequestQueue.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Graph Requests/Push-ListGraphRequestQueue.ps1 @@ -25,7 +25,7 @@ function Push-ListGraphRequestQueue { Write-Information "Filter: $Filter" $Existing = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey, OriginalEntityId if ($Existing) { - $null = Remove-AzDataTableEntity -Force @Table -Entity $Existing + $null = Remove-CIPPAzDataTableEntity -Force @Table -Entity $Existing } $GraphRequestParams = @{ TenantFilter = $Item.TenantFilter diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Maintenance/Push-TableCleanupTask.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Maintenance/Push-TableCleanupTask.ps1 index 2391968a94f7a..6134363987045 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Maintenance/Push-TableCleanupTask.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Maintenance/Push-TableCleanupTask.ps1 @@ -45,7 +45,7 @@ function Push-TableCleanupTask { if ($Entities) { Write-Information "Removing $($Entities.Count) entities from $($Item.TableName)" try { - Remove-AzDataTableEntity @Table -Entity $Entities -Force + Remove-CIPPAzDataTableEntity @Table -Entity $Entities -Force $RowsRemoved += $Entities.Count if ($DataTableProps.First -and $Entities.Count -lt $DataTableProps.First) { $CleanupCompleted = $true diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenantDownload.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenantDownload.ps1 index e8e0f8b66205f..863f7e814c3ad 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenantDownload.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenantDownload.ps1 @@ -21,7 +21,7 @@ function Push-AuditLogTenantDownload { } # remove legacy webhooks foreach ($Task in $LegacyWebhookTasks) { - Remove-AzDataTableEntity -Force @SchedulerConfig -Entity $Task + Remove-CIPPAzDataTableEntity -Force @SchedulerConfig -Entity $Task } $CIPPURL = $LegacyUrl } else { diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Webhooks/Push-PublicWebhookProcess.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Webhooks/Push-PublicWebhookProcess.ps1 index 0cdc860b7e483..7bf45a4dae814 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Webhooks/Push-PublicWebhookProcess.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Webhooks/Push-PublicWebhookProcess.ps1 @@ -19,6 +19,6 @@ function Push-PublicWebhookProcess { Write-Host "Webhook Exception: $($_.Exception.Message)" } finally { $Entity = $Webhook | Select-Object -Property RowKey, PartitionKey - Remove-AzDataTableEntity -Force @Table -Entity $Entity + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Entity } } diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Webhooks/Push-Schedulerwebhookcreation.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Webhooks/Push-Schedulerwebhookcreation.ps1 index de538961a47a5..db1336bb665c3 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Webhooks/Push-Schedulerwebhookcreation.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Webhooks/Push-Schedulerwebhookcreation.ps1 @@ -20,13 +20,13 @@ function Push-Schedulerwebhookcreation { if ($Webhook) { Write-Information "Found existing webhook for $Tenant - $($Row.webhookType)" if ($Row.tenantid -ne 'AllTenants') { - Remove-AzDataTableEntity -Force @Table -Entity $Row + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Row } if (($Webhook | Measure-Object).Count -gt 1) { $Webhook = $Webhook | Select-Object -First 1 $WebhooksToRemove = $ExistingWebhooks | Where-Object { $_.RowKey -ne $Webhook.RowKey } foreach ($RemoveWebhook in $WebhooksToRemove) { - Remove-AzDataTableEntity -Force @WebhookTable -Entity $RemoveWebhook + Remove-CIPPAzDataTableEntity -Force @WebhookTable -Entity $RemoveWebhook } } } else { @@ -34,7 +34,7 @@ function Push-Schedulerwebhookcreation { try { $NewSub = New-CIPPGraphSubscription -TenantFilter $Tenant -EventType $Row.webhookType -auditLogAPI $true if ($NewSub.Success -and $Row.tenantid -ne 'AllTenants') { - Remove-AzDataTableEntity -Force @Table -Entity $Row + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Row } else { Write-Information "Failed to create webhook for $Tenant - $($Row.webhookType) - $($_.Exception.Message)" } diff --git a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 index 6c2e1ee6f9514..620367566227a 100644 --- a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 @@ -2,6 +2,17 @@ function Add-CIPPAzDataTableEntity { <# .FUNCTIONALITY Internal + .SYNOPSIS + Writes entities of any size to an Azure Table. + .DESCRIPTION + Thin wrapper around Add-AzDataTableLargeEntity (AzBobbyTables >= 3.6.2), which + natively splits entities that exceed the table service size limits across + multiple properties and rows, batches transactions, deduplicates entities by + key and cleans up stale part rows when a split entity shrinks. + + Kept as a wrapper for backward compatibility with existing call sites and to + strip null-valued properties, which the table service cannot store and the + binary module rejects. #> [CmdletBinding(DefaultParameterSetName = 'OperationType')] param( @@ -17,7 +28,6 @@ function Add-CIPPAzDataTableEntity { [string]$OperationType = 'Add' ) - # Validate input parameters if ($null -eq $Context) { throw 'Context parameter cannot be null' } @@ -27,272 +37,57 @@ function Add-CIPPAzDataTableEntity { return } - $Parameters = @{ - Context = $Context - CreateTableIfNotExists = $CreateTableIfNotExists - } - if ($PSCmdlet.ParameterSetName -eq 'Force') { - $Parameters.Force = $Force - } else { - $Parameters.OperationType = $OperationType - } - - $MaxRowSize = 500000 - 100 - $MaxSize = 30kb - $BatchQueue = [System.Collections.Generic.List[object]]::new() - $BatchKeys = [System.Collections.Generic.Dictionary[string,int]]::new() - + $Entities = [System.Collections.Generic.List[object]]::new() foreach ($SingleEnt in @($Entity)) { - try { - # Skip null entities - if ($null -eq $SingleEnt) { - Write-Warning 'Skipping null entity' - continue - } - - if ($null -eq $SingleEnt.PartitionKey -or $null -eq $SingleEnt.RowKey) { - throw 'PartitionKey or RowKey is null' - } + if ($null -eq $SingleEnt) { + Write-Warning 'Skipping null entity' + continue + } - # Ensure entity is not empty - if ($SingleEnt -is [hashtable] -and $SingleEnt.Count -eq 0) { + # Remove null-valued properties before handing the entity to the binary module + if ($SingleEnt -is [hashtable]) { + if ($SingleEnt.Count -eq 0) { Write-Warning 'Skipping empty hashtable entity' continue - } elseif ($SingleEnt -is [PSCustomObject] -and ($SingleEnt.PSObject.Properties | Measure-Object).Count -eq 0) { - Write-Warning 'Skipping empty PSCustomObject entity' - continue } - - # Additional validation for AzBobbyTables compatibility - try { - # Ensure all property values are not null for string properties - if ($SingleEnt -is [hashtable]) { - foreach ($key in @($SingleEnt.Keys)) { - if ($null -eq $SingleEnt[$key]) { - $SingleEnt.Remove($key) - } - } - } elseif ($SingleEnt -is [PSCustomObject]) { - $propsToRemove = [system.Collections.Generic.List[string]]::new() - foreach ($prop in $SingleEnt.PSObject.Properties) { - if ($null -eq $prop.Value) { - $propsToRemove.Add($prop.Name) - } - } - foreach ($propName in $propsToRemove) { - $SingleEnt.PSObject.Properties.Remove($propName) - } + foreach ($key in @($SingleEnt.Keys)) { + if ($null -eq $SingleEnt[$key]) { + $SingleEnt.Remove($key) } - } catch { - Write-Warning "Error during entity validation: $($_.Exception.Message)" } - - # Check entity size - if under MaxSize, batch it for bulk write - $entityBytes = [System.Text.Encoding]::UTF8.GetByteCount($($SingleEnt | ConvertTo-Json -Compress)) - - if ($entityBytes -lt $MaxSize) { - # Small entity - add to batch queue, dedup by PartitionKey+RowKey (last-in wins) - $batchKey = "$($SingleEnt.PartitionKey)|$($SingleEnt.RowKey)" - if ($BatchKeys.ContainsKey($batchKey)) { - $BatchQueue[$BatchKeys[$batchKey]] = $SingleEnt - } else { - $BatchKeys[$batchKey] = $BatchQueue.Count - $BatchQueue.Add($SingleEnt) - } - if ($BatchQueue.Count -ge 100) { - try { - Add-AzDataTableEntity @Parameters -Entity $BatchQueue.ToArray() -ErrorAction Stop - } catch { - # Batch failed - fall back to individual writes - Write-Warning "Batch write failed, falling back to individual writes: $($_.Exception.Message)" - foreach ($batchItem in $BatchQueue) { - Add-AzDataTableEntity @Parameters -Entity $batchItem -ErrorAction Stop - } - } - $BatchQueue.Clear() - $BatchKeys.Clear() - } + } elseif ($SingleEnt -is [PSCustomObject]) { + if (($SingleEnt.PSObject.Properties | Measure-Object).Count -eq 0) { + Write-Warning 'Skipping empty PSCustomObject entity' continue } - - # Large entity - flush any pending batch first, then write individually - if ($BatchQueue.Count -gt 0) { - try { - Add-AzDataTableEntity @Parameters -Entity $BatchQueue.ToArray() -ErrorAction Stop - } catch { - Write-Warning "Batch write failed, falling back to individual writes: $($_.Exception.Message)" - foreach ($batchItem in $BatchQueue) { - Add-AzDataTableEntity @Parameters -Entity $batchItem -ErrorAction Stop - } + $propsToRemove = [System.Collections.Generic.List[string]]::new() + foreach ($prop in $SingleEnt.PSObject.Properties) { + if ($null -eq $prop.Value) { + $propsToRemove.Add($prop.Name) } - $BatchQueue.Clear() - $BatchKeys.Clear() } - - Add-AzDataTableEntity @Parameters -Entity $SingleEnt -ErrorAction Stop - - } catch [System.Exception] { - if ($_.Exception.ErrorCode -in @('PropertyValueTooLarge', 'EntityTooLarge', 'RequestBodyTooLarge')) { - try { - Write-Information 'Entity is too large. Splitting entity into multiple parts.' - - $largePropertyNames = [System.Collections.Generic.List[string]]::new() - $entitySize = 0 - - if ($SingleEnt -is [System.Management.Automation.PSCustomObject]) { - $SingleEnt = $SingleEnt | ConvertTo-Json -Depth 100 -Compress | ConvertFrom-Json -AsHashtable - } - - foreach ($key in $SingleEnt.Keys) { - $propertySize = [System.Text.Encoding]::UTF8.GetByteCount($SingleEnt[$key].ToString()) - $entitySize += $propertySize - if ($propertySize -gt $MaxSize) { - $largePropertyNames.Add($key) - } - } - - if (($largePropertyNames | Measure-Object).Count -gt 0) { - $splitInfoList = [System.Collections.Generic.List[object]]::new() - foreach ($largePropertyName in $largePropertyNames) { - $dataString = $SingleEnt[$largePropertyName] - $splitCount = [math]::Ceiling($dataString.Length / $MaxSize) - $splitData = [System.Collections.Generic.List[object]]::new() - for ($i = 0; $i -lt $splitCount; $i++) { - $start = $i * $MaxSize - $splitData.Add($dataString.Substring($start, [Math]::Min($MaxSize, $dataString.Length - $start))) > $null - } - $splitDataCount = $splitData.Count - $splitPropertyNames = [System.Collections.Generic.List[object]]::new() - for ($i = 0; $i -lt $splitDataCount; $i++) { - $splitPropertyNames.Add("${largePropertyName}_Part$i") - } - - $splitInfo = @{ - OriginalHeader = $largePropertyName - SplitHeaders = $splitPropertyNames - } - $splitInfoList.Add($splitInfo) - $SingleEnt.Remove($largePropertyName) - - for ($i = 0; $i -lt $splitDataCount; $i++) { - $SingleEnt[$splitPropertyNames[$i]] = $splitData[$i] - } - } - $SingleEnt['SplitOverProps'] = ($splitInfoList | ConvertTo-Json -Compress).ToString() - } - - $entitySize = [System.Text.Encoding]::UTF8.GetByteCount($($SingleEnt | ConvertTo-Json -Compress)) - if ($entitySize -gt $MaxRowSize) { - $rows = [System.Collections.Generic.List[object]]::new() - $originalPartitionKey = $SingleEnt.PartitionKey - $originalRowKey = $SingleEnt.RowKey - $entityIndex = 0 - - while ($entitySize -gt $MaxRowSize) { - Write-Information "Entity size is $entitySize. Splitting entity into multiple parts." - $newEntity = @{} - $newEntity['PartitionKey'] = $originalPartitionKey - $newEntity['RowKey'] = if ($entityIndex -eq 0) { $originalRowKey } else { "$($originalRowKey)-part$entityIndex" } - $newEntity['OriginalEntityId'] = $originalRowKey - $newEntity['PartIndex'] = $entityIndex - $entityIndex++ - - $propertiesToRemove = [System.Collections.Generic.List[object]]::new() - foreach ($key in $SingleEnt.Keys) { - if ($key -in @('RowKey', 'PartitionKey')) { continue } - $newEntitySize = [System.Text.Encoding]::UTF8.GetByteCount($($newEntity | ConvertTo-Json -Compress)) - if ($newEntitySize -lt $MaxRowSize) { - $propertySize = [System.Text.Encoding]::UTF8.GetByteCount($SingleEnt[$key].ToString()) - if ($propertySize -gt $MaxRowSize) { - $dataString = $SingleEnt[$key] - $splitCount = [math]::Ceiling($dataString.Length / $MaxSize) - $splitData = [System.Collections.Generic.List[object]]::new() - for ($i = 0; $i -lt $splitCount; $i++) { - $start = $i * $MaxSize - $splitData.Add($dataString.Substring($start, [Math]::Min($MaxSize, $dataString.Length - $start))) > $null - } - - $splitPropertyNames = [System.Collections.Generic.List[object]]::new() - for ($i = 0; $i -lt $splitData.Count; $i++) { - $splitPropertyNames.Add("${key}_Part$i") - } - - for ($i = 0; $i -lt $splitData.Count; $i++) { - $newEntity[$splitPropertyNames[$i]] = $splitData[$i] - } - } else { - $newEntity[$key] = $SingleEnt[$key] - } - $propertiesToRemove.Add($key) - } - } - - foreach ($prop in $propertiesToRemove) { - $SingleEnt.Remove($prop) - } - - $rows.Add($newEntity) - $entitySize = [System.Text.Encoding]::UTF8.GetByteCount($($SingleEnt | ConvertTo-Json -Compress)) - } - - if ($SingleEnt.Count -gt 0) { - $SingleEnt['RowKey'] = "$($originalRowKey)-part$entityIndex" - $SingleEnt['OriginalEntityId'] = $originalRowKey - $SingleEnt['PartIndex'] = $entityIndex - $SingleEnt['PartitionKey'] = $originalPartitionKey - $rows.Add($SingleEnt) - } - - foreach ($row in $rows) { - Write-Information "current entity is $($row.RowKey) with $($row.PartitionKey). Our size is $([System.Text.Encoding]::UTF8.GetByteCount($($row | ConvertTo-Json -Compress)))" - $NewRow = ([PSCustomObject]$row) | Select-Object * -ExcludeProperty Timestamp - Add-AzDataTableEntity @Parameters -Entity $NewRow - } - - } else { - $NewEnt = ([PSCustomObject]$SingleEnt) | Select-Object * -ExcludeProperty Timestamp - Add-AzDataTableEntity @Parameters -Entity $NewEnt - if ($NewEnt.PSObject.Properties['OriginalEntityId'] -eq $null -and $NewEnt.PSObject.Properties['PartIndex'] -eq $null) { - $partIndex = 1 - while ($true) { - $partRowKey = "$($NewEnt.RowKey)-part$partIndex" - try { - Remove-AzDataTableEntity -Context $Context -PartitionKey $NewEnt.PartitionKey -RowKey $partRowKey -ErrorAction Stop - Write-Information "Deleted obsolete part: $partRowKey" - $partIndex++ - } catch { - break - } - } - } - } - - } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-Warning 'AzBobbyTables Error' - throw "Error processing entity: $ErrorMessage Linenumber: $($_.InvocationInfo.ScriptLineNumber)" - } - } else { - try { Write-Information ($_.Exception | ConvertTo-Json) } catch { Write-Information $_.Exception } - Write-Information "THE ERROR IS $($_.Exception.message). The size of the entity is $entitySize." - Write-Information "Parameters are: $($Parameters | ConvertTo-Json -Compress)" - Write-Information $_.InvocationInfo.PositionMessage - throw $_ + foreach ($propName in $propsToRemove) { + $SingleEnt.PSObject.Properties.Remove($propName) } } + + $Entities.Add($SingleEnt) } - # Flush any remaining batched entities - if ($BatchQueue.Count -gt 0) { - try { - Add-AzDataTableEntity @Parameters -Entity $BatchQueue.ToArray() -ErrorAction Stop - } catch { - Write-Warning "Final batch write failed, falling back to individual writes: $($_.Exception.Message)" - foreach ($batchItem in $BatchQueue) { - Add-AzDataTableEntity @Parameters -Entity $batchItem -ErrorAction Stop - } - } - $BatchQueue.Clear() - $BatchKeys.Clear() + if ($Entities.Count -eq 0) { + return } + + $Parameters = @{ + Context = $Context + Entity = $Entities.ToArray() + CreateTableIfNotExists = $CreateTableIfNotExists + } + if ($PSCmdlet.ParameterSetName -eq 'Force') { + $Parameters.Force = $Force + } else { + $Parameters.OperationType = $OperationType + } + + Add-AzDataTableLargeEntity @Parameters -ErrorAction Stop } diff --git a/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 b/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 index f4987a8908232..7f4b79848a91d 100644 --- a/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 @@ -101,7 +101,7 @@ function Add-CIPPDbItem { Write-LogMessage -API 'CIPPDbItem' -tenant $TenantFilter -sev Warning -message "Skipped $Undated $Type row(s) with no readable Timestamp during orphan cleanup — not deleting without positive evidence" } if ($Orphans) { - $null = Remove-AzDataTableEntity @Table -Entity @($Orphans) -Force + $null = Remove-CIPPAzDataTableEntity @Table -Entity @($Orphans) -Force } } } diff --git a/Modules/CIPPCore/Public/AuditLogs/New-CIPPAuditLogSearchResultsCache.ps1 b/Modules/CIPPCore/Public/AuditLogs/New-CIPPAuditLogSearchResultsCache.ps1 index 4a457e98b240b..7f66e81c028f5 100644 --- a/Modules/CIPPCore/Public/AuditLogs/New-CIPPAuditLogSearchResultsCache.ps1 +++ b/Modules/CIPPCore/Public/AuditLogs/New-CIPPAuditLogSearchResultsCache.ps1 @@ -91,7 +91,7 @@ function New-CIPPAuditLogSearchResultsCache { $FailedDownloadsTable = Get-CippTable -TableName 'FailedAuditLogDownloads' $failedEntities = Get-CIPPAzDataTableEntity @FailedDownloadsTable -Filter "PartitionKey eq '$TenantFilter' and SearchId eq '$SearchId'" if ($failedEntities) { - Remove-AzDataTableEntity @FailedDownloadsTable -Entity $failedEntities -Force + Remove-CIPPAzDataTableEntity @FailedDownloadsTable -Entity $failedEntities -Force Write-Information "Removed failed download records for search ID: $SearchId, tenant: $TenantFilter" } } catch { diff --git a/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 b/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 index 3decd89e4d7eb..48666e1bbfb26 100644 --- a/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 @@ -75,6 +75,15 @@ function Initialize-CIPPAuth { } catch { Write-Information "[Auth-Init] SSO redirect URI patch failed (non-fatal): $_" } + + # Admin-consent the SSO app so users aren't prompted at sign-in. Retried every warmup + # until it lands, since the service principal isn't always queryable right after the + # app registration is created. + try { + Update-CIPPSSOPreconsent + } catch { + Write-Information "[Auth-Init] SSO pre-consent failed (non-fatal): $_" + } } # 3. Handle EasyAuth configuration based on current state diff --git a/Modules/CIPPCore/Public/Authentication/Update-CIPPSSOPreconsent.ps1 b/Modules/CIPPCore/Public/Authentication/Update-CIPPSSOPreconsent.ps1 new file mode 100644 index 0000000000000..bbbd94e609550 --- /dev/null +++ b/Modules/CIPPCore/Public/Authentication/Update-CIPPSSOPreconsent.ps1 @@ -0,0 +1,169 @@ +function Update-CIPPSSOPreconsent { + <# + .SYNOPSIS + Grants tenant-wide admin consent to the CIPP-SSO app registration so users are not + prompted to consent when they sign in. + + .DESCRIPTION + Reads the stored SSO AppId from Key Vault (or the DevSecrets table in dev mode) and + ensures an AllPrincipals oauth2PermissionGrant exists for it against Microsoft Graph + covering the delegated scopes New-CIPPSSOApp requests (openid, profile, email). + + Runs from warmup rather than at app-creation time: the service principal is not always + queryable immediately after the app registration is created, so a create-time grant is + racy. Retrying every warmup makes it self-healing at no cost once it has succeeded. + + Best-effort by design. A tenant whose SAM consent predates Directory.ReadWrite.All will + get a 403 here, which is recorded as Preconsented=false and otherwise ignored — sign-in + still works, users just see the consent prompt they see today. + + Only covers the home tenant. A multi-tenant SSO app still prompts users from other + tenants, because the grant can only be written where CIPP holds credentials. + #> + [CmdletBinding()] + param() + + $GraphAppId = '00000003-0000-0000-c000-000000000000' + # Keep in sync with the delegated permissions New-CIPPSSOApp requests. + $RequiredScopes = @('openid', 'profile', 'email') + + $MigrationTable = Get-CIPPTable -TableName 'SSOMigration' + $Existing = $null + try { + $Existing = Get-CIPPAzDataTableEntity @MigrationTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'MigrationConfig'" -ErrorAction SilentlyContinue + } catch { } + + # Merge onto the existing row — Add-CIPPAzDataTableEntity -Force replaces, and this row + # carries the setup state the SSO settings page reads. + $SaveRow = { + param([hashtable]$Updates) + $Row = @{ + PartitionKey = 'SSO' + RowKey = 'MigrationConfig' + } + if ($Existing) { + foreach ($Prop in $Existing.PSObject.Properties) { + if ($Prop.Name -notin @('PartitionKey', 'RowKey', 'Timestamp', 'ETag', 'odata.etag')) { + $Row[$Prop.Name] = $Prop.Value + } + } + } + foreach ($Key in $Updates.Keys) { $Row[$Key] = $Updates[$Key] } + try { + Add-CIPPAzDataTableEntity @MigrationTable -Entity $Row -Force | Out-Null + } catch { + Write-Information "[SSO-Preconsent] Failed to record status: $($_.Exception.Message)" + } + } + + # Resolve the stored SSO AppId + $SSOAppId = $null + if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') { + try { + $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' + $Secret = Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'SSO'" -ErrorAction SilentlyContinue + $SSOAppId = $Secret.SSOAppId + } catch { } + } else { + $VaultName = Get-CippKeyVaultName + if ($VaultName) { + try { + $SSOAppId = Get-CippKeyVaultSecret -VaultName $VaultName -Name 'SSOAppId' -AsPlainText -ErrorAction Stop + } catch { } + } + } + + if (-not $SSOAppId) { + Write-Information '[SSO-Preconsent] No SSO AppId found, skipping pre-consent' + return + } + + # Fast path: already granted for this app. Tracked against the AppId so recreating the + # SSO app re-runs the grant instead of trusting a flag left over from the old one. + if ($Existing.Preconsented -eq 'true' -and $Existing.PreconsentedAppId -eq $SSOAppId) { + Write-Information "[SSO-Preconsent] Already granted for $SSOAppId — skipping" + return + } + + # Looked up separately from the grant work below: a service principal that isn't + # queryable yet is a propagation delay, not a refusal, and shouldn't be recorded as + # Preconsented=false. Graph 404s throw here rather than returning null. + $SsoSp = $null + try { + $SsoSp = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/servicePrincipals(appId='$SSOAppId')?`$select=id" -NoAuthCheck $true -AsApp $true + } catch { + Write-Information "[SSO-Preconsent] Service principal lookup for $SSOAppId failed — will retry next warmup: $($_.Exception.Message)" + return + } + if (-not $SsoSp.id) { + Write-Information "[SSO-Preconsent] Service principal for $SSOAppId not found yet — will retry next warmup" + return + } + + try { + $GraphSp = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/servicePrincipals(appId='$GraphAppId')?`$select=id" -NoAuthCheck $true -AsApp $true + if (-not $GraphSp.id) { + Write-Information '[SSO-Preconsent] Microsoft Graph service principal not found — skipping' + return + } + + $Grants = @(New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/servicePrincipals/$($SsoSp.id)/oauth2PermissionGrants" -NoAuthCheck $true -AsApp $true) + $TenantGrant = $Grants | Where-Object { $_.resourceId -eq $GraphSp.id -and $_.consentType -eq 'AllPrincipals' } | Select-Object -First 1 + + if ($TenantGrant) { + $CurrentScopes = @($TenantGrant.scope -split ' ' | Where-Object { $_ }) + $MissingScopes = @($RequiredScopes | Where-Object { $_ -notin $CurrentScopes }) + + if ($MissingScopes.Count -eq 0) { + Write-Information "[SSO-Preconsent] Admin consent already in place for $SSOAppId" + & $SaveRow @{ + Preconsented = 'true' + PreconsentedAppId = $SSOAppId + PreconsentError = '' + } + return + } + + $MergedScopes = (@($CurrentScopes + $MissingScopes) | Sort-Object -Unique) -join ' ' + $PatchBody = @{ scope = $MergedScopes } | ConvertTo-Json -Compress + New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/oauth2PermissionGrants/$($TenantGrant.id)" -body $PatchBody -type PATCH -NoAuthCheck $true -AsApp $true + Write-Information "[SSO-Preconsent] Added missing scopes to existing grant: $($MissingScopes -join ', ')" + } else { + $CreateBody = @{ + clientId = $SsoSp.id + consentType = 'AllPrincipals' + resourceId = $GraphSp.id + scope = $RequiredScopes -join ' ' + } | ConvertTo-Json -Compress + New-GraphPOSTRequest -uri 'https://graph.microsoft.com/v1.0/oauth2PermissionGrants' -body $CreateBody -type POST -NoAuthCheck $true -AsApp $true + Write-Information "[SSO-Preconsent] Granted admin consent for $SSOAppId ($($RequiredScopes -join ', '))" + } + + $StatusUpdates = @{ + Preconsented = 'true' + PreconsentedAppId = $SSOAppId + PreconsentedAt = (Get-Date).ToUniversalTime().ToString('o') + PreconsentError = '' + } + # A row created here must carry a Status, or the SSO settings page reads it back as + # "not provisioned" and offers to create an app that already exists. + if (-not $Existing -or -not $Existing.Status) { + $StatusUpdates['AppId'] = $SSOAppId + $StatusUpdates['Status'] = if ($env:WEBSITE_AUTH_ENABLED -eq 'True') { 'complete' } else { 'secrets_stored' } + } + & $SaveRow $StatusUpdates + + Write-LogMessage -API 'SSO-Preconsent' -message "Admin consent granted for CIPP-SSO app $SSOAppId" -sev Info + } catch { + # Non-fatal: sign-in still works, users just get the consent prompt. Most likely cause + # is a SAM consent predating Directory.ReadWrite.All, which no retry will fix. + $ErrorMessage = $_.Exception.Message + Write-Information "[SSO-Preconsent] Pre-consent failed for $SSOAppId (non-fatal): $ErrorMessage" + & $SaveRow @{ + Preconsented = 'false' + PreconsentedAppId = $SSOAppId + PreconsentError = $ErrorMessage + } + Write-LogMessage -API 'SSO-Preconsent' -message "Could not pre-consent CIPP-SSO app $SSOAppId — users will see a consent prompt on sign-in: $ErrorMessage" -LogData (Get-CippException -Exception $_) -sev Warning + } +} diff --git a/Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1 b/Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1 index 8e476e57da686..eec2cbf7675f7 100644 --- a/Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1 +++ b/Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1 @@ -379,6 +379,17 @@ function Compare-CIPPIntuneObject { if ($item.id) { $intuneCollectionIndex[$item.id] = $item } } + # Settings Intune generates per tenant. The Defender onboarding blob embeds the tenant's own + # workspace identity, so a template captured in one tenant can never match another - it + # reports drift on every run, remediation cannot resolve it, and the comparison shows a + # friendly option name on one side against a raw identifier on the other. + $tenantSpecificSettings = @( + 'device_vendor_msft_windowsadvancedthreatprotection_onboarding', + 'device_vendor_msft_windowsadvancedthreatprotection_onboarding_fromconnector', + 'device_vendor_msft_windowsadvancedthreatprotection_offboarding', + 'device_vendor_msft_windowsadvancedthreatprotection_offboarding_fromconnector' + ) + # Recursive function to process group setting collections at any depth function Process-GroupSettingChildren { param( @@ -741,6 +752,8 @@ function Compare-CIPPIntuneObject { $settingId = $key.Substring(8) } + if ($settingId -in $tenantSpecificSettings) { continue } + $settingDefinition = $intuneCollectionIndex[$settingId] $refRawValue = if ($refItem) { $refItem.Value } else { $null } diff --git a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreation.ps1 b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreation.ps1 index 250da1d8f6934..f368e083ff43a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreation.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreation.ps1 @@ -46,7 +46,7 @@ function Start-AuditLogSearchCreation { } if ($ExpiredDisabledRows.Count -gt 0) { - Remove-AzDataTableEntity @AuditDisabledTable -Entity $ExpiredDisabledRows -Force | Out-Null + Remove-CIPPAzDataTableEntity @AuditDisabledTable -Entity $ExpiredDisabledRows -Force | Out-Null } # Round time down to nearest minute diff --git a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreationV2.ps1 b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreationV2.ps1 index e579ab736eaee..e31783940a6e6 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreationV2.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreationV2.ps1 @@ -112,7 +112,7 @@ function Start-AuditLogSearchCreationV2 { $CutoffIso = (Get-Date).AddDays(-7).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') $Stale = @(Get-CIPPAzDataTableEntity @Ledger -Filter "Timestamp le datetime'$CutoffIso'" -Property PartitionKey, RowKey) if ($Stale.Count -gt 0) { - Remove-AzDataTableEntity @Ledger -Entity $Stale -Force + Remove-CIPPAzDataTableEntity @Ledger -Entity $Stale -Force Write-Information "AuditLogV2: cleaned $($Stale.Count) stale ledger row(s)" } } catch { diff --git a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPOrchestrator.ps1 b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPOrchestrator.ps1 index b5509bb4efc44..e7fb500555236 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPOrchestrator.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPOrchestrator.ps1 @@ -147,7 +147,7 @@ function Start-CIPPOrchestrator { # Clean up the stored input object after starting the orchestration try { $Entities = Get-AzDataTableEntity @OrchestratorTable -Filter "PartitionKey eq 'Input' and (RowKey eq '$InputObjectGuid' or OriginalEntityId eq '$InputObjectGuid' or OriginalEntityId eq guid'$InputObjectGuid')" -Property PartitionKey, RowKey - Remove-AzDataTableEntity @OrchestratorTable -Entity $Entities -Force + Remove-CIPPAzDataTableEntity @OrchestratorTable -Entity $Entities -Force Write-Information "Cleaned up stored input object: $InputObjectGuid" } catch { Write-Warning "Failed to clean up stored input object $InputObjectGuid : $_" diff --git a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-SchedulerOrchestrator.ps1 b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-SchedulerOrchestrator.ps1 index dd5445f479cc4..ee9e3ef521a5d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-SchedulerOrchestrator.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-SchedulerOrchestrator.ps1 @@ -18,7 +18,7 @@ function Start-SchedulerOrchestrator { if ($Tenant.type -notin $ValidTypes) { if ($Tenant.PartitionKey -eq 'Alert') { Write-Information "Scheduler: removing legacy classic-alert row for '$($Tenant.tenant)'" - Remove-AzDataTableEntity -Force @Table -Entity $Tenant + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Tenant } else { Write-Information "Scheduler: skipping row $($Tenant.PartitionKey)/$($Tenant.RowKey) - no handler for type '$($Tenant.type)'" } diff --git a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-BackupRetentionCleanup.ps1 b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-BackupRetentionCleanup.ps1 index 4b62d62a694d0..7a76402a5b772 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-BackupRetentionCleanup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-BackupRetentionCleanup.ps1 @@ -72,7 +72,7 @@ function Start-BackupRetentionCleanup { } } # Delete blob table entities - Remove-AzDataTableEntity @CIPPBackupTable -Entity $BlobBackups -Force + Remove-CIPPAzDataTableEntity @CIPPBackupTable -Entity $BlobBackups -Force } # Delete table-only backups (no blobs) @@ -82,7 +82,7 @@ function Start-BackupRetentionCleanup { $TableDeletedCount = 0 if ($TableBackups) { - Remove-AzDataTableEntity @CIPPBackupTable -Entity $TableBackups -Force + Remove-CIPPAzDataTableEntity @CIPPBackupTable -Entity $TableBackups -Force $TableDeletedCount = ($TableBackups | Measure-Object).Count } @@ -132,7 +132,7 @@ function Start-BackupRetentionCleanup { } } # Delete blob table entities - Remove-AzDataTableEntity @ScheduledBackupTable -Entity $BlobBackups -Force + Remove-CIPPAzDataTableEntity @ScheduledBackupTable -Entity $BlobBackups -Force } # Delete table-only backups (no blobs) @@ -142,7 +142,7 @@ function Start-BackupRetentionCleanup { $TableDeletedCount = 0 if ($TableBackups) { - Remove-AzDataTableEntity @ScheduledBackupTable -Entity $TableBackups -Force + Remove-CIPPAzDataTableEntity @ScheduledBackupTable -Entity $TableBackups -Force $TableDeletedCount = ($TableBackups | Measure-Object).Count } diff --git a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-LogRetentionCleanup.ps1 b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-LogRetentionCleanup.ps1 index 22719d9a63b4a..66ce86319ba20 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-LogRetentionCleanup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-LogRetentionCleanup.ps1 @@ -70,7 +70,7 @@ function Start-LogRetentionCleanup { if ($OldLogs -and ($OldLogs | Measure-Object).Count -gt 0) { $BatchCount = ($OldLogs | Measure-Object).Count - Remove-AzDataTableEntity @CippLogsTable -Entity $OldLogs -Force + Remove-CIPPAzDataTableEntity @CippLogsTable -Entity $OldLogs -Force $TotalDeletedCount += $BatchCount Write-Host "Batch $BatchNumber`: Deleted $BatchCount log entries" diff --git a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-UserSyncTimer.ps1 b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-UserSyncTimer.ps1 index 89710f3e8bf37..8060d563651be 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-UserSyncTimer.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-UserSyncTimer.ps1 @@ -214,7 +214,7 @@ function Start-UserSyncTimer { Add-CIPPAzDataTableEntity @UsersTable -Entity $Entity -Force } foreach ($Entity in $EntitiesToRemove) { - Remove-AzDataTableEntity -Force @UsersTable -Entity $Entity + Remove-CIPPAzDataTableEntity -Force @UsersTable -Entity $Entity $RemoveCount++ } diff --git a/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 b/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 index da15f05e62a8b..2adbd7f88032e 100644 --- a/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPAzDatatableEntity.ps1 @@ -1,4 +1,17 @@ function Get-CIPPAzDataTableEntity { + <# + .FUNCTIONALITY + Internal + .SYNOPSIS + Gets entities from an Azure Table, reassembling entities that were split for size. + .DESCRIPTION + Thin wrapper around Get-AzDataTableLargeEntity (AzBobbyTables >= 3.6.2), which + natively merges rows that were split across multiple properties or rows because + they exceeded the table service size limits. + + Kept as a wrapper for backward compatibility with existing call sites and to + default MaxRetries to 3 for throttled requests. + #> [CmdletBinding()] param( $Context, @@ -7,99 +20,10 @@ function Get-CIPPAzDataTableEntity { $First, $Skip, $Sort, - $Count, + [switch]$Count, [int]$MaxRetries = 3 ) $PSBoundParameters['MaxRetries'] = $MaxRetries - $Results = Get-AzDataTableEntity @PSBoundParameters - $mergedResults = @{} - $rootEntities = @{} # Keyed by "$PartitionKey|$RowKey" - - foreach ($entity in $Results) { - $partitionKey = $entity.PartitionKey - $rowKey = $entity.RowKey - $hasOriginalId = $entity.PSObject.Properties.Match('OriginalEntityId') -and $entity.OriginalEntityId - - if (-not $mergedResults.ContainsKey($partitionKey)) { - $mergedResults[$partitionKey] = @{} - } - - if (-not $hasOriginalId) { - # It's a standalone root row - $rootEntities["$partitionKey|$rowKey"] = $true - $mergedResults[$partitionKey][$rowKey] = @{ - Entity = $entity - Parts = [System.Collections.Generic.List[object]]::new() - } - continue - } - - # It's a part of something else - $entityId = $entity.OriginalEntityId - - # Check if this entity's target has a "real" base - if ($rootEntities.ContainsKey("$partitionKey|$entityId")) { - # Root row exists → skip merging this part - continue - } - - # Merge it as a part - if (-not $mergedResults[$partitionKey].ContainsKey($entityId)) { - $mergedResults[$partitionKey][$entityId] = @{ - Parts = [System.Collections.Generic.List[object]]::new() - } - } - $mergedResults[$partitionKey][$entityId]['Parts'].Add($entity) - } - - $finalResults = [System.Collections.Generic.List[object]]::new() - foreach ($partitionKey in $mergedResults.Keys) { - foreach ($entityId in $mergedResults[$partitionKey].Keys) { - $entityData = $mergedResults[$partitionKey][$entityId] - if (($entityData.Parts | Measure-Object).Count -gt 0) { - $fullEntity = [PSCustomObject]@{} - $parts = $entityData.Parts | Sort-Object PartIndex - foreach ($part in $parts) { - foreach ($key in $part.PSObject.Properties.Name) { - if ($key -notin @('OriginalEntityId', 'PartIndex', 'PartitionKey', 'RowKey', 'Timestamp')) { - if ($fullEntity.PSObject.Properties[$key]) { - $fullEntity | Add-Member -MemberType NoteProperty -Name $key -Value ($fullEntity.$key + $part.$key) -Force - } else { - $fullEntity | Add-Member -MemberType NoteProperty -Name $key -Value $part.$key - } - } - } - } - $fullEntity | Add-Member -MemberType NoteProperty -Name 'PartitionKey' -Value $parts[0].PartitionKey -Force - $fullEntity | Add-Member -MemberType NoteProperty -Name 'RowKey' -Value $entityId -Force - $fullEntity | Add-Member -MemberType NoteProperty -Name 'Timestamp' -Value $parts[0].Timestamp -Force - $finalResults.Add($fullEntity) - } else { - $FinalResults.Add($entityData.Entity) - } - } - } - - foreach ($entity in $finalResults) { - if ($entity.SplitOverProps) { - try { - $splitInfoList = $entity.SplitOverProps | ConvertFrom-Json -ErrorAction Stop - 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) - } - } - } catch { - Write-Warning "Failed to process SplitOverProps for entity with PartitionKey='$($entity.PartitionKey)' and RowKey='$($entity.RowKey)': $($_.Exception.Message)" - } finally { - $entity.PSObject.Properties.Remove('SplitOverProps') - } - } - } - - return $finalResults + Get-AzDataTableLargeEntity @PSBoundParameters } diff --git a/Modules/CIPPCore/Public/Get-CIPPDrift.ps1 b/Modules/CIPPCore/Public/Get-CIPPDrift.ps1 index e04da1b423117..d2433a81e183a 100644 --- a/Modules/CIPPCore/Public/Get-CIPPDrift.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPDrift.ps1 @@ -571,7 +571,7 @@ function Get-CIPPDrift { if ($StaleDriftEntities) { try { foreach ($StaleEntity in $StaleDriftEntities) { - Remove-AzDataTableEntity @DriftTable -Entity $StaleEntity + Remove-CIPPAzDataTableEntity @DriftTable -Entity $StaleEntity } Write-Information "Removed $(@($StaleDriftEntities).Count) stale drift deviation entries for $TenantFilter" } catch { diff --git a/Modules/CIPPCore/Public/Get-CIPPIntunePolicyName.ps1 b/Modules/CIPPCore/Public/Get-CIPPIntunePolicyName.ps1 new file mode 100644 index 0000000000000..3575872260b3c --- /dev/null +++ b/Modules/CIPPCore/Public/Get-CIPPIntunePolicyName.ps1 @@ -0,0 +1,70 @@ +function Get-CIPPIntunePolicyName { + <# + .SYNOPSIS + Returns the name a template's policy is deployed under in a tenant. + .DESCRIPTION + Set-CIPPIntunePolicy names most policy types from the template's Displayname column, forcing + it onto the payload before sending it to Graph. Catalog and the Windows update profile types + are the exception - they take their name from the payload itself and ignore the column. + + Anything that has to find a deployed policy again has to use the same rule, otherwise a + template whose Displayname was edited after it was captured, or whose payload name contains + a replacement variable, resolves to a name that does not exist in the tenant. The policy is + then reported as missing while it is sitting right there. + .PARAMETER TemplateType + The template Type, e.g. 'Catalog', 'Device', 'deviceCompliancePolicies'. + .PARAMETER RawJSON + The policy payload, as a JSON string or an already parsed object. Pass this after text + replacement has been applied - deployment derives the name from the replaced payload. + .PARAMETER DisplayName + The template's Displayname column. + .EXAMPLE + Get-CIPPIntunePolicyName -TemplateType 'Catalog' -RawJSON $RawJSON -DisplayName $Template.Displayname + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$TemplateType, + $RawJSON, + [string]$DisplayName + ) + + # Types Set-CIPPIntunePolicy names from the payload instead of the Displayname column. + $PayloadNamedTypes = @( + 'Catalog', + 'windowsDriverUpdateProfiles', + 'windowsFeatureUpdateProfiles', + 'windowsQualityUpdatePolicies', + 'windowsQualityUpdateProfiles' + ) + + if ($TemplateType -notin $PayloadNamedTypes) { + return $DisplayName + } + + $Policy = if ($RawJSON -is [string]) { + try { + $RawJSON | ConvertFrom-Json -Depth 100 -ErrorAction Stop + } catch { + # Unparseable payload - fall back to the column rather than returning nothing, so the + # caller still performs a lookup instead of treating the policy as missing outright. + Write-Warning "Could not read the payload for '$DisplayName' to determine its policy name: $($_.Exception.Message)" + $null + } + } else { + $RawJSON + } + + $PayloadName = if ($TemplateType -eq 'Catalog') { + # Settings Catalog policies use 'name'; Set-CIPPIntunePolicy reads that property directly. + $Policy.name + } else { + $Policy.displayName ?? $Policy.name + } + + if ([string]::IsNullOrWhiteSpace($PayloadName)) { + return $DisplayName + } + + return $PayloadName +} diff --git a/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 b/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 index cca9fe00b2a65..5a77f5b246d80 100644 --- a/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPSchemaExtensions.ps1 @@ -65,7 +65,7 @@ function Get-CIPPSchemaExtensions { Write-LogMessage -headers $Headers -message "Updated Schema Extension: $($SchemaDefinition.id)" -API 'Get-CIPPSchemaExtensions' -Sev 'info' -LogData $Body } if ($Patch.status -eq 'Deprecated') { - Remove-AzDataTableEntity @CustomDataTable -Entity $SchemaExtension -Force + Remove-CIPPAzDataTableEntity @CustomDataTable -Entity $SchemaExtension -Force } else { $NewSchema = [string]($Schema | ConvertTo-Json -Depth 5 -Compress) if ($SchemaExtension.JSON -ne $NewSchema) { diff --git a/Modules/CIPPCore/Public/Get-CIPPTimerFunctions.ps1 b/Modules/CIPPCore/Public/Get-CIPPTimerFunctions.ps1 index bafac2b76eae5..9c19320bf7c34 100644 --- a/Modules/CIPPCore/Public/Get-CIPPTimerFunctions.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPTimerFunctions.ps1 @@ -77,7 +77,7 @@ function Get-CIPPTimerFunctions { } $OrchestratorStatus | Where-Object { $_.RowKey -notmatch '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' } | Select-Object ETag, PartitionKey, RowKey | ForEach-Object { - Remove-AzDataTableEntity @Table -Entity $_ -Force + Remove-CIPPAzDataTableEntity @Table -Entity $_ -Force } foreach ($Orchestrator in $Orchestrators) { @@ -173,7 +173,7 @@ function Get-CIPPTimerFunctions { } else { if ($Status) { Write-Warning "Timer function: $($Orchestrator.Command) does not exist" - Remove-AzDataTableEntity @Table -Entity $Status + Remove-CIPPAzDataTableEntity @Table -Entity $Status } } } @@ -181,7 +181,7 @@ function Get-CIPPTimerFunctions { foreach ($StaleStatus in $OrchestratorStatus) { if ($Orchestrators.Id -notcontains $StaleStatus.RowKey) { Write-Warning "Removing stale timer function entry: $($StaleStatus.RowKey)" - Remove-AzDataTableEntity @Table -Entity $StaleStatus + Remove-CIPPAzDataTableEntity @Table -Entity $StaleStatus } } } diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 index 676f9bdb8d04b..3468f3d7df952 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 @@ -82,7 +82,7 @@ function Get-Tenants { } $CurrentTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter "PartitionKey eq 'Tenants' and Excluded eq false and delegatedPrivilegeStatus ne 'directTenant'" $CurrentTenants | Where-Object { $_.customerId -notin $GDAPList.customerId -and $_.customerId -ne $env:TenantID } | ForEach-Object { - Remove-AzDataTableEntity -Force @TenantsTable -Entity $_ + Remove-CIPPAzDataTableEntity -Force @TenantsTable -Entity $_ } } $PartnerModeTable = Get-CippTable -tablename 'tenantMode' diff --git a/Modules/CIPPCore/Public/GraphHelper/Remove-CIPPCache.ps1 b/Modules/CIPPCore/Public/GraphHelper/Remove-CIPPCache.ps1 index 929de5c86ed01..a3756df1a415c 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Remove-CIPPCache.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Remove-CIPPCache.ps1 @@ -11,7 +11,7 @@ function Remove-CIPPCache { $Filter = "PartitionKey eq 'Tenants' and Excluded eq false and delegatedPrivilegeStatus eq 'granularDelegatedAdminPrivileges'" $ClearIncludedTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter $Filter -Property PartitionKey, RowKey if ($ClearIncludedTenants) { - Remove-AzDataTableEntity -Force @TenantsTable -Entity $ClearIncludedTenants + Remove-CIPPAzDataTableEntity -Force @TenantsTable -Entity $ClearIncludedTenants } "Removed $($ClearIncludedTenants.Count) tenants" diff --git a/Modules/CIPPCore/Public/GraphHelper/Remove-SnoozedAlerts.ps1 b/Modules/CIPPCore/Public/GraphHelper/Remove-SnoozedAlerts.ps1 index e0527479ca5cc..8f22ca37d3257 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Remove-SnoozedAlerts.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Remove-SnoozedAlerts.ps1 @@ -64,7 +64,7 @@ function Remove-SnoozedAlerts { if ($RecordsToCleanup.Count -gt 0) { try { foreach ($staleRecord in $RecordsToCleanup) { - Remove-AzDataTableEntity @SnoozeTable -Entity @{ + Remove-CIPPAzDataTableEntity @SnoozeTable -Entity @{ PartitionKey = $staleRecord.PartitionKey RowKey = $staleRecord.RowKey ETag = '*' diff --git a/Modules/CIPPCore/Public/Merge-CIPPIntuneTemplateIdentity.ps1 b/Modules/CIPPCore/Public/Merge-CIPPIntuneTemplateIdentity.ps1 new file mode 100644 index 0000000000000..baf7c9968b1e3 --- /dev/null +++ b/Modules/CIPPCore/Public/Merge-CIPPIntuneTemplateIdentity.ps1 @@ -0,0 +1,65 @@ +function Merge-CIPPIntuneTemplateIdentity { + <# + .SYNOPSIS + Applies the name and description a template will actually be deployed with onto its payload. + .DESCRIPTION + A template stores Displayname and Description as columns alongside RAWJson, not inside it. + Renaming a template, or editing its description, updates the columns and leaves the payload + untouched. Set-CIPPIntunePolicy then writes the columns over the payload for every type that + is not named from its payload, so the tenant ends up with the column values. + + Comparing the stored payload against the tenant therefore reports a difference on exactly + the fields remediation has already brought into line - and remediation can never resolve it, + because it keeps writing the value the comparison does not expect. Run the baseline through + this first so it reflects what deployment sends. + .PARAMETER Policy + The parsed policy payload to adjust. Returned with the identity fields applied. + .PARAMETER TemplateType + The template Type, e.g. 'Catalog', 'Device', 'deviceCompliancePolicies'. + .PARAMETER DisplayName + The template's Displayname column. + .PARAMETER Description + The template's Description column. + .EXAMPLE + $JSONTemplate = Merge-CIPPIntuneTemplateIdentity -Policy $JSONTemplate -TemplateType $TemplateType -DisplayName $displayname -Description $description + #> + [CmdletBinding()] + param( + # An empty payload parses to null. Accept it and hand it straight back, so a malformed + # template surfaces as a comparison failure rather than a parameter binding error. + [Parameter(Mandatory = $true)] + [AllowNull()] + $Policy, + [Parameter(Mandatory = $true)] + [string]$TemplateType, + [string]$DisplayName, + [string]$Description + ) + + if ($null -eq $Policy) { return $Policy } + + # Types where Set-CIPPIntunePolicy forces the Displayname and Description columns onto the + # payload before sending it. Catalog and the update profiles deploy their payload as-is, and + # Admin's RAWJson holds group policy definition values rather than a policy object, so none of + # them get touched here. + $ColumnNamedTypes = @( + 'AppProtection', + 'AppConfiguration', + 'deviceCompliancePolicies', + 'Device' + ) + + if ($TemplateType -notin $ColumnNamedTypes) { + return $Policy + } + + if (-not [string]::IsNullOrWhiteSpace($DisplayName)) { + $null = $Policy | Add-Member -MemberType NoteProperty -Name 'displayName' -Value $DisplayName -Force + } + + # Set unconditionally: deployment always writes the column, so an empty column means the tenant + # policy ends up with an empty description too. + $null = $Policy | Add-Member -MemberType NoteProperty -Name 'description' -Value $Description -Force + + return $Policy +} diff --git a/Modules/CIPPCore/Public/New-CIPPGroup.ps1 b/Modules/CIPPCore/Public/New-CIPPGroup.ps1 index d6913f7f55651..d0129d5d2e069 100644 --- a/Modules/CIPPCore/Public/New-CIPPGroup.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPGroup.ps1 @@ -118,6 +118,10 @@ function New-CIPPGroup { 'isAssignableToRole' = ($NormalizedGroupType -eq 'AzureRole') } + if ($GroupObject.disableNesting -eq $true) { + $BodyParams | Add-Member -NotePropertyName 'disableNesting' -NotePropertyValue $true + } + # Handle dynamic membership if ($GroupObject.membershipRules) { $BodyParams | Add-Member -NotePropertyName 'membershipRule' -NotePropertyValue $GroupObject.membershipRules diff --git a/Modules/CIPPCore/Public/New-CIPPIntuneReportExportJob.ps1 b/Modules/CIPPCore/Public/New-CIPPIntuneReportExportJob.ps1 index 96c85aa1936d7..f642c6950a487 100644 --- a/Modules/CIPPCore/Public/New-CIPPIntuneReportExportJob.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPIntuneReportExportJob.ps1 @@ -56,7 +56,7 @@ function New-CIPPIntuneReportExportJob { $JobsTable = Get-CIPPTable -tablename 'IntuneReportJobs' $Existing = Get-CIPPAzDataTableEntity @JobsTable -Filter "PartitionKey eq '$TenantFilter' and RowKey eq '$ReportName'" if ($Existing) { - Remove-AzDataTableEntity @JobsTable -Entity $Existing -Force -ErrorAction SilentlyContinue + Remove-CIPPAzDataTableEntity @JobsTable -Entity $Existing -Force -ErrorAction SilentlyContinue } Add-CIPPAzDataTableEntity @JobsTable -Entity @{ diff --git a/Modules/CIPPCore/Public/Remove-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Remove-CIPPAzDataTableEntity.ps1 new file mode 100644 index 0000000000000..9e1989641774f --- /dev/null +++ b/Modules/CIPPCore/Public/Remove-CIPPAzDataTableEntity.ps1 @@ -0,0 +1,25 @@ +function Remove-CIPPAzDataTableEntity { + <# + .FUNCTIONALITY + Internal + .SYNOPSIS + Removes entities from an Azure Table, including any part rows they were split into. + .DESCRIPTION + Thin wrapper around Remove-AzDataTableLargeEntity (AzBobbyTables >= 3.6.2), which + also deletes the part rows of entities that were split for size, so removing a + split entity leaves no orphaned rows behind. + + Kept as a wrapper for consistency with the other CIPP table helpers and to + default MaxRetries to 3 for throttled requests. + #> + [CmdletBinding()] + param( + $Context, + $Entity, + [switch]$Force, + [int]$MaxRetries = 3 + ) + + $PSBoundParameters['MaxRetries'] = $MaxRetries + Remove-AzDataTableLargeEntity @PSBoundParameters +} diff --git a/Modules/CIPPCore/Public/Remove-CIPPDbItem.ps1 b/Modules/CIPPCore/Public/Remove-CIPPDbItem.ps1 index fcb2196e4a237..056edfc4a5aac 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPDbItem.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPDbItem.ps1 @@ -43,7 +43,7 @@ function Remove-CIPPDbItem { if ($Entity) { # Remove the entity - Remove-AzDataTableEntity @Table -Entity $Entity -Force + Remove-CIPPAzDataTableEntity @Table -Entity $Entity -Force Write-LogMessage -API 'CIPPDbItem' -tenant $TenantFilter -message "Removed $Type item with ID: $ItemId" -sev Debug # Always decrement count diff --git a/Modules/CIPPCore/Public/Repair-CIPPIntuneTemplateNesting.ps1 b/Modules/CIPPCore/Public/Repair-CIPPIntuneTemplateNesting.ps1 index c4e66e3eb5f70..31dcf674b9d55 100644 --- a/Modules/CIPPCore/Public/Repair-CIPPIntuneTemplateNesting.ps1 +++ b/Modules/CIPPCore/Public/Repair-CIPPIntuneTemplateNesting.ps1 @@ -37,6 +37,17 @@ function Repair-CIPPIntuneTemplateNesting { if ($unwrapped) { Write-Information "Repairing double-nested RAWJson for template '$($Template.Displayname)' ($($Template.GUID))" + # Repair the in-memory copy regardless, so the caller gets usable JSON even if the resave + # below is skipped or fails. + $Template.RAWJson = $currentRawJson + + if ([string]::IsNullOrWhiteSpace($Template.GUID)) { + # Without a GUID there is no RowKey to write back to, and guessing one would create a + # junk row. The in-memory repair above still lets this run succeed. + Write-Warning "Cannot persist the RAWJson repair for template '$($Template.Displayname)' because it has no GUID." + return $Template + } + if (-not $Table) { $Table = Get-CippTable -tablename 'templates' } @@ -49,14 +60,17 @@ function Repair-CIPPIntuneTemplateNesting { GUID = $Template.GUID } | ConvertTo-Json -Depth 10 -Compress + # Merge rather than replace. Package membership lives in its own column on the entity, not + # in the JSON blob, and standards resolve a package by filtering on it. A replace drops + # every column not listed here, which silently removes the template from its package - the + # standard then stops running for it and its last compliance result is frozen for good. Add-CIPPAzDataTableEntity @Table -Entity @{ JSON = "$fixedObject" RowKey = "$($Template.GUID)" GUID = "$($Template.GUID)" PartitionKey = 'IntuneTemplate' - } -Force + } -OperationType UpsertMerge - $Template.RAWJson = $currentRawJson Write-LogMessage -API 'IntuneTemplate' -message "Repaired double-nested RAWJson for template '$($Template.Displayname)' ($($Template.GUID))" -Sev 'Warning' } diff --git a/Modules/CIPPCore/Public/Select-CIPPIntuneAvailableSetting.ps1 b/Modules/CIPPCore/Public/Select-CIPPIntuneAvailableSetting.ps1 new file mode 100644 index 0000000000000..9fa9d9a222fb3 --- /dev/null +++ b/Modules/CIPPCore/Public/Select-CIPPIntuneAvailableSetting.ps1 @@ -0,0 +1,87 @@ +function Select-CIPPIntuneAvailableSetting { + <# + .SYNOPSIS + Reduces a Catalog policy to the settings the tenant actually offers, the way deployment does. + .DESCRIPTION + Endpoint Security policies - Catalog policies carrying a templateReference - expose a + different set of settings per tenant depending on licensing and which features are enabled. + Set-CIPPIntunePolicy drops the settings a tenant does not offer before sending the policy, + so the deployed policy is a subset of the template. + + Comparing the full template against that subset reports the dropped settings as drift on + exactly the tenants that cannot hold them, and remediation can never resolve it. Both the + deploy path and the comparison paths call this so they are looking at the same policy. + + Policies without a templateReference are returned untouched. Setting template lookups are + cached briefly per tenant and template, because a drift run resolves the same Endpoint + Security templates repeatedly. + .PARAMETER Policy + The parsed Catalog policy payload. + .PARAMETER TenantFilter + The tenant to resolve setting availability against. + .EXAMPLE + $Template = Select-CIPPIntuneAvailableSetting -Policy $Template -TenantFilter $TenantFilter + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + $Policy, + [Parameter(Mandatory = $true)] + [string]$TenantFilter + ) + + $TemplateId = $Policy.templateReference.templateId + if (-not $TemplateId) { + return $Policy + } + + if (-not $script:CIPPIntuneSettingTemplateCache) { + $script:CIPPIntuneSettingTemplateCache = @{} + } + + $CacheKey = '{0}|{1}' -f $TenantFilter, $TemplateId + $Cached = $script:CIPPIntuneSettingTemplateCache[$CacheKey] + + if ($Cached -and $Cached.Expires -gt [datetime]::UtcNow) { + $AvailableSettings = $Cached.Settings + } else { + Write-Information "Checking configuration policy template $TemplateId for $($Policy.name)" + $AvailableSettings = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicyTemplates('$TemplateId')/settingTemplates?`$expand=settingDefinitions&`$top=1000" -tenantid $TenantFilter + $script:CIPPIntuneSettingTemplateCache[$CacheKey] = @{ + Settings = $AvailableSettings + Expires = [datetime]::UtcNow.AddMinutes(30) + } + } + + # An empty result means the lookup told us nothing useful. Filtering on it would strip every + # setting, so leave the policy alone. + if (-not $AvailableSettings) { + return $Policy + } + + Write-Information "Available settings for template $TemplateId : $(@($AvailableSettings).Count)" + $FilteredSettings = [System.Collections.Generic.List[psobject]]::new() + + foreach ($setting in $Policy.settings) { + if ($setting.settingInstance.settingInstanceTemplateReference.settingInstanceTemplateId -in $AvailableSettings.settingInstanceTemplate.settingInstanceTemplateId) { + $AvailableSetting = $AvailableSettings | Where-Object { $_.settingInstanceTemplate.settingInstanceTemplateId -eq $setting.settingInstance.settingInstanceTemplateReference.settingInstanceTemplateId } + + if ($AvailableSetting.settingInstanceTemplate.settingInstanceTemplateId -cnotmatch $setting.settingInstance.settingInstanceTemplateReference.settingInstanceTemplateId) { + # update casing + Write-Information "Fixing casing for setting instance template $($AvailableSetting.settingInstanceTemplate.settingInstanceTemplateId)" + $setting.settingInstance.settingInstanceTemplateReference.settingInstanceTemplateId = $AvailableSetting.settingInstanceTemplate.settingInstanceTemplateId + } + + if ($AvailableSetting.settingInstanceTemplate.choiceSettingValueTemplate -cnotmatch $setting.settingInstance.choiceSettingValue.settingValueTemplateReference.settingValueTemplateId) { + # update choice setting value template + Write-Information "Fixing casing for choice setting value template $($AvailableSetting.settingInstanceTemplate.choiceSettingValueTemplate.settingValueTemplateId)" + $setting.settingInstance.choiceSettingValue.settingValueTemplateReference.settingValueTemplateId = $AvailableSetting.settingInstanceTemplate.choiceSettingValueTemplate.settingValueTemplateId + } + + $FilteredSettings.Add($setting) + } + } + + $Policy.settings = $FilteredSettings + return $Policy +} diff --git a/Modules/CIPPCore/Public/Set-CIPPAuditLogUserExclusion.ps1 b/Modules/CIPPCore/Public/Set-CIPPAuditLogUserExclusion.ps1 index c2b6d75d2fea9..24bdb40133794 100644 --- a/Modules/CIPPCore/Public/Set-CIPPAuditLogUserExclusion.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPAuditLogUserExclusion.ps1 @@ -52,7 +52,7 @@ function Set-CIPPAuditLogUserExclusion { if ($ExistingEntries.RowKey -contains $User) { if ($PSCmdlet.ShouldProcess("Removing exclusion for user: $User")) { $Entity = $ExistingEntries | Where-Object { $_.RowKey -eq $User -and $_.PartitionKey -eq $TenantFilter -and $_.Type -eq $Type } - Remove-AzDataTableEntity @AuditLogExclusionsTable -Entity $Entity + Remove-CIPPAzDataTableEntity @AuditLogExclusionsTable -Entity $Entity Write-LogMessage -headers $Headers -API 'Set-CIPPAuditLogUserExclusion' -message "Removed audit log exclusion for user: $User" -Sev 'Info' -tenant $TenantFilter -LogData $Entity "Removed audit log exclusion for user: $User" } diff --git a/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 b/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 index 10be7db62f736..c3a03a7addfa5 100644 --- a/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 @@ -36,7 +36,7 @@ function Set-CIPPGDAPInviteGroups { if ($PSCmdlet.ShouldProcess($Relationship.id, "Remove invite entry for $($Relationship.customer.displayName)")) { Write-LogMessage -API $APINAME -message "Groups mapped for GDAP Relationship: $($Relationship.customer.displayName) - $($Relationship.customer.displayName)" -Sev Info - Remove-AzDataTableEntity -Force @Table -Entity $Invite + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Invite } return $true } else { diff --git a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 index 4244d18ba1437..671dd2a403759 100644 --- a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 @@ -165,7 +165,7 @@ function Set-CIPPIntunePolicy { 'Catalog' { $PlatformType = 'deviceManagement' $TemplateTypeURL = 'configurationPolicies' - $DisplayName = ($RawJSON | ConvertFrom-Json).Name + $DisplayName = Get-CIPPIntunePolicyName -TemplateType 'Catalog' -RawJSON $RawJSON -DisplayName $DisplayName if ($ReusableSettings) { Write-Verbose "Catalog: ReusableSettings count $($ReusableSettings.Count)" Write-Verbose ('Catalog: ReusableSettings detail ' + ($ReusableSettings | ConvertTo-Json -Depth 5 -Compress)) @@ -177,35 +177,10 @@ function Set-CIPPIntunePolicy { $Template = $RawJSON | ConvertFrom-Json if ($Template.templateReference.templateId) { - Write-Information "Checking configuration policy template $($Template.templateReference.templateId) for $($DisplayName)" - # Remove unavailable settings from the template - $AvailableSettings = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicyTemplates('$($Template.templateReference.templateId)')/settingTemplates?`$expand=settingDefinitions&`$top=1000" -tenantid $tenantFilter - - if ($AvailableSettings) { - Write-Information "Available settings for template $($Template.templateReference.templateId): $($AvailableSettings.Count)" - $FilteredSettings = [System.Collections.Generic.List[psobject]]::new() - foreach ($setting in $Template.settings) { - if ($setting.settingInstance.settingInstanceTemplateReference.settingInstanceTemplateId -in $AvailableSettings.settingInstanceTemplate.settingInstanceTemplateId) { - $AvailableSetting = $AvailableSettings | Where-Object { $_.settingInstanceTemplate.settingInstanceTemplateId -eq $setting.settingInstance.settingInstanceTemplateReference.settingInstanceTemplateId } - - if ($AvailableSetting.settingInstanceTemplate.settingInstanceTemplateId -cnotmatch $setting.settingInstance.settingInstanceTemplateReference.settingInstanceTemplateId) { - # update casing - Write-Information "Fixing casing for setting instance template $($AvailableSetting.settingInstanceTemplate.settingInstanceTemplateId)" - $setting.settingInstance.settingInstanceTemplateReference.settingInstanceTemplateId = $AvailableSetting.settingInstanceTemplate.settingInstanceTemplateId - } - - if ($AvailableSetting.settingInstanceTemplate.choiceSettingValueTemplate -cnotmatch $setting.settingInstance.choiceSettingValue.settingValueTemplateReference.settingValueTemplateId) { - # update choice setting value template - Write-Information "Fixing casing for choice setting value template $($AvailableSetting.settingInstanceTemplate.choiceSettingValueTemplate.settingValueTemplateId)" - $setting.settingInstance.choiceSettingValue.settingValueTemplateReference.settingValueTemplateId = $AvailableSetting.settingInstanceTemplate.choiceSettingValueTemplate.settingValueTemplateId - } - - $FilteredSettings.Add($setting) - } - } - $Template.settings = $FilteredSettings - $RawJSON = $Template | ConvertTo-Json -Depth 100 -Compress - } + # Remove settings this tenant does not offer. The comparison paths run the + # baseline through the same helper so they diff against what actually lands. + $Template = Select-CIPPIntuneAvailableSetting -Policy $Template -TenantFilter $TenantFilter + $RawJSON = ConvertTo-Json -InputObject $Template -Depth 100 -Compress } $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/$PlatformType/$TemplateTypeURL" -tenantid $TenantFilter @@ -230,8 +205,7 @@ function Set-CIPPIntunePolicy { 'windowsDriverUpdateProfiles' { $PlatformType = 'deviceManagement' $TemplateTypeURL = 'windowsDriverUpdateProfiles' - $File = ($RawJSON | ConvertFrom-Json) - $DisplayName = $File.displayName ?? $File.Name + $DisplayName = Get-CIPPIntunePolicyName -TemplateType $TemplateType -RawJSON $RawJSON -DisplayName $DisplayName $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/$PlatformType/$TemplateTypeURL" -tenantid $TenantFilter $FuzzyResult = Find-CIPPFuzzyPolicyMatch -DisplayName $DisplayName -ExistingPolicies $CheckExististing -MaxDistance $LevenshteinDistance if ($FuzzyResult) { @@ -254,8 +228,7 @@ function Set-CIPPIntunePolicy { 'windowsFeatureUpdateProfiles' { $PlatformType = 'deviceManagement' $TemplateTypeURL = 'windowsFeatureUpdateProfiles' - $File = ($RawJSON | ConvertFrom-Json) - $DisplayName = $File.displayName ?? $File.Name + $DisplayName = Get-CIPPIntunePolicyName -TemplateType $TemplateType -RawJSON $RawJSON -DisplayName $DisplayName $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/$PlatformType/$TemplateTypeURL" -tenantid $tenantFilter $FuzzyResult = Find-CIPPFuzzyPolicyMatch -DisplayName $DisplayName -ExistingPolicies $CheckExististing -MaxDistance $LevenshteinDistance if ($FuzzyResult) { @@ -279,8 +252,7 @@ function Set-CIPPIntunePolicy { 'windowsQualityUpdatePolicies' { $PlatformType = 'deviceManagement' $TemplateTypeURL = 'windowsQualityUpdatePolicies' - $File = ($RawJSON | ConvertFrom-Json) - $DisplayName = $File.displayName ?? $File.Name + $DisplayName = Get-CIPPIntunePolicyName -TemplateType $TemplateType -RawJSON $RawJSON -DisplayName $DisplayName $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/$PlatformType/$TemplateTypeURL" -tenantid $TenantFilter $FuzzyResult = Find-CIPPFuzzyPolicyMatch -DisplayName $DisplayName -ExistingPolicies $CheckExististing -MaxDistance $LevenshteinDistance if ($FuzzyResult) { @@ -303,8 +275,7 @@ function Set-CIPPIntunePolicy { 'windowsQualityUpdateProfiles' { $PlatformType = 'deviceManagement' $TemplateTypeURL = 'windowsQualityUpdateProfiles' - $File = ($RawJSON | ConvertFrom-Json) - $DisplayName = $File.displayName ?? $File.Name + $DisplayName = Get-CIPPIntunePolicyName -TemplateType $TemplateType -RawJSON $RawJSON -DisplayName $DisplayName $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/$PlatformType/$TemplateTypeURL" -tenantid $TenantFilter $FuzzyResult = Find-CIPPFuzzyPolicyMatch -DisplayName $DisplayName -ExistingPolicies $CheckExististing -MaxDistance $LevenshteinDistance if ($FuzzyResult) { diff --git a/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 b/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 index a7d79797147ff..a3a870aacc0fa 100644 --- a/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 @@ -130,7 +130,7 @@ function Set-CIPPSPOTenant { $SafeTenantFilter = ConvertTo-CIPPODataFilterValue -Value $TenantFilter -Type String $CacheEntity = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'Tenant' and RowKey eq '$SafeTenantFilter'" if ($CacheEntity) { - Remove-AzDataTableEntity @Table -Entity $CacheEntity + Remove-CIPPAzDataTableEntity @Table -Entity $CacheEntity } } } diff --git a/Modules/CIPPCore/Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1 b/Modules/CIPPCore/Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1 index 1585fa8ff11eb..d995d658326bf 100644 --- a/Modules/CIPPCore/Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1 +++ b/Modules/CIPPCore/Public/TenantGroups/Update-CIPPDynamicTenantGroups.ps1 @@ -220,7 +220,7 @@ function Update-CIPPDynamicTenantGroups { $TenantInfo = $AllTenants | Where-Object { $_.customerId -eq $TenantId } $MemberToRemove = $CurrentMembers | Where-Object { $_.customerId -eq $TenantId } if ($MemberToRemove) { - Remove-AzDataTableEntity @MembersTable -Entity $MemberToRemove -Force + Remove-CIPPAzDataTableEntity @MembersTable -Entity $MemberToRemove -Force Write-LogMessage -API 'TenantGroups' -message "Removed tenant '$($TenantInfo.displayName)' from dynamic group '$($Group.Name)'" -sev Info $TotalMembersRemoved++ } diff --git a/Modules/CIPPCore/Public/Test-CIPPGDAPGroupMappings.ps1 b/Modules/CIPPCore/Public/Test-CIPPGDAPGroupMappings.ps1 index ec23d6ba8fb13..e3f7b357491e6 100644 --- a/Modules/CIPPCore/Public/Test-CIPPGDAPGroupMappings.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPGDAPGroupMappings.ps1 @@ -174,7 +174,7 @@ function Test-CIPPGDAPGroupMappings { if ($Correction.OldGroupId -and $Correction.OldGroupId -ne $Mapping.GroupId) { $OldEntity = Get-CIPPAzDataTableEntity @RolesTable -Filter "PartitionKey eq 'Roles' and RowKey eq '$($Correction.OldGroupId)'" if ($OldEntity) { - Remove-AzDataTableEntity -Force @RolesTable -Entity $OldEntity + Remove-CIPPAzDataTableEntity -Force @RolesTable -Entity $OldEntity } } Add-CIPPAzDataTableEntity @RolesTable -Entity @{ diff --git a/Modules/CIPPCore/Public/Test-CIPPRerun.ps1 b/Modules/CIPPCore/Public/Test-CIPPRerun.ps1 index 740056e8e8b03..9ab610c0ace91 100644 --- a/Modules/CIPPCore/Public/Test-CIPPRerun.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPRerun.ps1 @@ -48,14 +48,14 @@ function Test-CIPPRerun { $AllRerunData = Get-CIPPAzDataTableEntity @RerunTable if ($AllRerunData) { Write-Information "Clearing all rerun cache entries for $($Type)_$($API)" - Remove-AzDataTableEntity @RerunTable -Entity $AllRerunData -Force + Remove-CIPPAzDataTableEntity @RerunTable -Entity $AllRerunData -Force } return $false } if ($Clear.IsPresent) { if ($RerunData) { - Remove-AzDataTableEntity @RerunTable -Entity $RerunData + Remove-CIPPAzDataTableEntity @RerunTable -Entity $RerunData } return $false } elseif ($RerunData) { diff --git a/Modules/CIPPCore/Public/Tools/Get-CIPPSchedulerBlockedCommands.ps1 b/Modules/CIPPCore/Public/Tools/Get-CIPPSchedulerBlockedCommands.ps1 index bed47db6b34b0..22ccd764dff3f 100644 --- a/Modules/CIPPCore/Public/Tools/Get-CIPPSchedulerBlockedCommands.ps1 +++ b/Modules/CIPPCore/Public/Tools/Get-CIPPSchedulerBlockedCommands.ps1 @@ -106,7 +106,7 @@ function Get-CIPPSchedulerBlockedCommands { 'Add-CIPPAzDataTableEntity' 'Add-AzDataTableEntity' 'Update-AzDataTableEntity' - 'Remove-AzDataTableEntity' + 'Remove-CIPPAzDataTableEntity' 'Remove-AzDataTable' 'Get-CIPPAzStorageContainer' 'Remove-CIPPAzStorageContainer' diff --git a/Modules/CIPPCore/Public/Tools/Initialize-CIPPExcludedLicenses.ps1 b/Modules/CIPPCore/Public/Tools/Initialize-CIPPExcludedLicenses.ps1 index a7b1c59f95660..60fcf1e0b3ed2 100644 --- a/Modules/CIPPCore/Public/Tools/Initialize-CIPPExcludedLicenses.ps1 +++ b/Modules/CIPPCore/Public/Tools/Initialize-CIPPExcludedLicenses.ps1 @@ -37,7 +37,7 @@ function Initialize-CIPPExcludedLicenses { if ($Force) { $ExistingRows = Get-CIPPAzDataTableEntity @Table foreach ($Row in $ExistingRows) { - Remove-AzDataTableEntity -Force @Table -Entity $Row + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Row } Write-LogMessage -API $APIName -headers $Headers -message 'Cleared existing excluded licenses' -Sev 'Info' } diff --git a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPGraphWebhookRenewal.ps1 b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPGraphWebhookRenewal.ps1 index 0d77bba3c6de9..d3ec63e6146ac 100644 --- a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPGraphWebhookRenewal.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPGraphWebhookRenewal.ps1 @@ -20,7 +20,7 @@ function Invoke-CippGraphWebhookRenewal { $TenantFilter = $UpdateSub.PartitionKey if ($Tenants.defaultDomainName -notcontains $TenantFilter -and $Tenants.customerId -notcontains $TenantFilter) { Write-LogMessage -API 'Renew_Graph_Subscriptions' -message "Removing Subscription Renewal for $($UpdateSub.SubscriptionID) as tenant $TenantFilter is not in the tenant list." -sev 'Warning' -tenant $TenantFilter - Remove-AzDataTableEntity -Force @WebhookTable -Entity $UpdateSub + Remove-CIPPAzDataTableEntity -Force @WebhookTable -Entity $UpdateSub continue } @@ -45,7 +45,7 @@ function Invoke-CippGraphWebhookRenewal { $CreateResult = New-CIPPGraphSubscription -TenantFilter $TenantFilter -TypeofSubscription $TypeofSubscription -BaseURL $BaseURL -Resource $Resource -EventType $EventType -Headers 'GraphSubscriptionRenewal' -Recreate if ($CreateResult -match 'Created Webhook subscription for') { - Remove-AzDataTableEntity -Force @WebhookTable -Entity $UpdateSub + Remove-CIPPAzDataTableEntity -Force @WebhookTable -Entity $UpdateSub } } } catch { diff --git a/Modules/CIPPCore/Public/Webhooks/New-CIPPGraphSubscription.ps1 b/Modules/CIPPCore/Public/Webhooks/New-CIPPGraphSubscription.ps1 index 078ad1eca77bd..ee77cc46c591e 100644 --- a/Modules/CIPPCore/Public/Webhooks/New-CIPPGraphSubscription.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/New-CIPPGraphSubscription.ps1 @@ -157,7 +157,7 @@ function New-CIPPGraphSubscription { # Remove the corresponding table row by SubscriptionID $StaleRow = $ExistingWebhooks | Where-Object { $_.SubscriptionID -eq $Dup.id } foreach ($Row in $StaleRow) { - Remove-AzDataTableEntity @WebhookTable -Entity $Row -Force + Remove-CIPPAzDataTableEntity @WebhookTable -Entity $Row -Force Write-LogMessage -headers $Headers -API $APIName -message "Removed stale webhook table entry (RowKey $($Row.RowKey)) for $($TenantFilter)" -Sev 'Warning' -tenant $TenantFilter } } @@ -165,7 +165,7 @@ function New-CIPPGraphSubscription { # Remove any remaining table rows whose SubscriptionID doesn't match the kept Graph subscription $ExistingWebhooks | Where-Object { $KeptSub -and $_.SubscriptionID -ne $KeptSub.id } | ForEach-Object { try { - Remove-AzDataTableEntity @WebhookTable -Entity $_ -Force + Remove-CIPPAzDataTableEntity @WebhookTable -Entity $_ -Force Write-LogMessage -headers $Headers -API $APIName -message "Removed orphaned webhook table entry (RowKey $($_.RowKey)) for $($TenantFilter)" -Sev 'Warning' -tenant $TenantFilter } catch { # Entity may have already been removed in the duplicate cleanup pass diff --git a/Modules/CIPPCore/Public/Webhooks/Remove-CIPPGraphSubscription.ps1 b/Modules/CIPPCore/Public/Webhooks/Remove-CIPPGraphSubscription.ps1 index 24769a81b3c6f..27a99ec29804a 100644 --- a/Modules/CIPPCore/Public/Webhooks/Remove-CIPPGraphSubscription.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Remove-CIPPGraphSubscription.ps1 @@ -18,7 +18,7 @@ function Remove-CIPPGraphSubscription { $AuditLog = New-GraphPOSTRequest -uri "https://manage.office.com/api/v1.0/$($TenantFilter)/activity/feed/subscriptions/stop?contentType=$($sub.contentType)" -scope 'https://manage.office.com/.default' -tenantid $TenantFilter -type POST -body '{}' -verbose Try { $WebhookRow = Get-CIPPAzDataTableEntity @WebhookTable | Where-Object { $_.PartitionKey -eq $TenantFilter -and $_.Resource -eq $EventType -and $_.version -ne '2' } - $null = Remove-AzDataTableEntity -Force @WebhookTable -Entity $Entity + $null = Remove-CIPPAzDataTableEntity -Force @WebhookTable -Entity $Entity } catch { Write-LogMessage -headers $Headers -API $APIName -message 'Deleted an audit log webhook that was already removed from CIPP' -Sev 'Info' -tenant $TenantFilter @@ -41,11 +41,11 @@ function Remove-CIPPGraphSubscription { } catch { Write-LogMessage -headers $Headers -API $APIName -message "Failed to remove webhook subscription at Microsoft's side: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter } - $null = Remove-AzDataTableEntity -Force @WebhookTable -Entity $Entity + $null = Remove-CIPPAzDataTableEntity -Force @WebhookTable -Entity $Entity } else { $OldID = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscriptions' -tenantid $TenantFilter) | Where-Object { $_.notificationUrl -eq $WebhookRow.WebhookNotificationUrl } $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/subscriptions/$($oldId.ID)" -tenantid $TenantFilter -type DELETE -body {} -Verbose - $null = Remove-AzDataTableEntity -Force @WebhookTable -Entity $Entity + $null = Remove-CIPPAzDataTableEntity -Force @WebhookTable -Entity $Entity } return "Removed webhook subscription to $($WebhookRow.resource) for $($TenantFilter)" } diff --git a/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 b/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 index e85995c2a37f3..135297724eba8 100644 --- a/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 @@ -574,7 +574,7 @@ function Test-CIPPAuditLogRules { }) if ($PendingDeletes.Count -ge $DeleteFlushSize) { try { - $null = Remove-AzDataTableEntity -Force @CacheWebhooksTable -Entity $PendingDeletes.ToArray() + $null = Remove-CIPPAzDataTableEntity -Force @CacheWebhooksTable -Entity $PendingDeletes.ToArray() } catch { Write-Information "Error removing $($PendingDeletes.Count) processed row(s) from cache: $($_.Exception.Message)" } @@ -587,7 +587,7 @@ function Test-CIPPAuditLogRules { if ($PendingDeletes.Count -gt 0) { try { - $null = Remove-AzDataTableEntity -Force @CacheWebhooksTable -Entity $PendingDeletes.ToArray() + $null = Remove-CIPPAzDataTableEntity -Force @CacheWebhooksTable -Entity $PendingDeletes.ToArray() } catch { Write-Information "Error removing $($PendingDeletes.Count) processed row(s) from cache: $($_.Exception.Message)" } @@ -745,7 +745,7 @@ function Test-CIPPAuditLogRules { } if ($RowsToRemove.Count -gt 0) { - Remove-AzDataTableEntity @CacheWebhooksTable -Entity $RowsToRemove -Force + Remove-CIPPAzDataTableEntity @CacheWebhooksTable -Entity $RowsToRemove -Force Write-Information "Removed $($RowsToRemove.Count) processed rows from cache" } } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDetectedApps.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDetectedApps.ps1 index 158b818122cda..397aab88f9029 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDetectedApps.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDetectedApps.ps1 @@ -32,7 +32,7 @@ function Set-CIPPDBCacheDetectedApps { $JobId = $JobRow.JobId if (-not $JobId) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "IntuneReportJobs row missing JobId - removing" -sev Warning - Remove-AzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue + Remove-CIPPAzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue return } @@ -44,14 +44,14 @@ function Set-CIPPDBCacheDetectedApps { } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "$ReportName job $JobId not retrievable: $($ErrorMessage.NormalizedError)" -sev Warning -LogData $ErrorMessage - Remove-AzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue + Remove-CIPPAzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue return } if ($Job.status -eq 'completed') { break } if ($Job.status -eq 'failed') { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "$ReportName job $JobId failed" -sev Error - Remove-AzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue + Remove-CIPPAzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue return } if ([datetime]::UtcNow -ge $Deadline) { @@ -64,7 +64,7 @@ function Set-CIPPDBCacheDetectedApps { if (-not $Job.url) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "$ReportName job $JobId completed but no url returned" -sev Error - Remove-AzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue + Remove-CIPPAzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue return } @@ -124,7 +124,7 @@ function Set-CIPPDBCacheDetectedApps { Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DetectedApps' -Data $DetectedApps -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($DetectedApps.Count) detected apps with devices from export $JobId" -sev Info - Remove-AzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue + Remove-CIPPAzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to cache detected apps: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAppInstallStatus.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAppInstallStatus.ps1 index 8a72b2aaf8116..a9e72c4c088e5 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAppInstallStatus.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAppInstallStatus.ps1 @@ -40,7 +40,7 @@ function Set-CIPPDBCacheIntuneAppInstallStatus { $JobId = $JobRow.JobId if (-not $JobId) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'IntuneReportJobs row missing JobId - removing' -sev Warning - Remove-AzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue + Remove-CIPPAzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue return } @@ -54,14 +54,14 @@ function Set-CIPPDBCacheIntuneAppInstallStatus { } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "$ReportName job $JobId not retrievable: $($ErrorMessage.NormalizedError)" -sev Warning -LogData $ErrorMessage - Remove-AzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue + Remove-CIPPAzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue return } if ($Job.status -eq 'completed') { break } if ($Job.status -eq 'failed') { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "$ReportName job $JobId failed" -sev Error - Remove-AzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue + Remove-CIPPAzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue return } if ([datetime]::UtcNow -ge $Deadline) { @@ -74,7 +74,7 @@ function Set-CIPPDBCacheIntuneAppInstallStatus { if (-not $Job.url) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "$ReportName job $JobId completed but no url returned" -sev Error - Remove-AzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue + Remove-CIPPAzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue return } @@ -123,7 +123,7 @@ function Set-CIPPDBCacheIntuneAppInstallStatus { Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneAppInstallStatusAggregate' -Data $AppStatuses -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($AppStatuses.Count) app install status rows from export $JobId" -sev Info - Remove-AzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue + Remove-CIPPAzDataTableEntity @JobsTable -Entity $JobRow -Force -ErrorAction SilentlyContinue } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to cache app install status: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecAzBobbyTables.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecAzBobbyTables.ps1 index c9e1da70bc2a1..d8a75bcfbb92d 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecAzBobbyTables.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecAzBobbyTables.ps1 @@ -20,7 +20,7 @@ function Invoke-ExecAzBobbyTables { 'Get-CIPPAzDataTableEntity' 'Get-AzDataTable' 'New-AzDataTable' - 'Remove-AzDataTableEntity' + 'Remove-CIPPAzDataTableEntity' 'Remove-AzDataTable' ) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDiagnosticsPresets.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDiagnosticsPresets.ps1 index 9cab483eba992..b7503b35dd12b 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDiagnosticsPresets.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDiagnosticsPresets.ps1 @@ -26,7 +26,7 @@ function Invoke-ExecDiagnosticsPresets { } } - Remove-AzDataTableEntity @Table -Entity @{ + Remove-CIPPAzDataTableEntity @Table -Entity @{ PartitionKey = 'Preset' RowKey = $GUID } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDurableFunctions.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDurableFunctions.ps1 index 7b8b54ee9dc05..f8655fbec78ac 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDurableFunctions.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecDurableFunctions.ps1 @@ -138,11 +138,11 @@ function Invoke-ExecDurableFunctions { if ($Request.Query.PartitionKey) { $HistoryEntities = Get-CIPPAzDataTableEntity @HistoryTable -Filter "PartitionKey eq '$($Request.Query.PartitionKey)'" -Property RowKey, PartitionKey if ($HistoryEntities) { - Remove-AzDataTableEntity -Force @HistoryTable -Entity $HistoryEntities + Remove-CIPPAzDataTableEntity -Force @HistoryTable -Entity $HistoryEntities } $Instance = Get-CIPPAzDataTableEntity @InstancesTable -Filter "PartitionKey eq '$($Request.Query.PartitionKey)'" -Property RowKey, PartitionKey if ($Instance) { - Remove-AzDataTableEntity -Force @InstancesTable -Entity $Instance + Remove-CIPPAzDataTableEntity -Force @InstancesTable -Entity $Instance } $Body = [PSCustomObject]@{ Results = 'Orchestrator {0} purged successfully' -f $Request.Query.PartitionKey diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 index 8edb13057cdee..75f6298da2f6b 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 @@ -40,7 +40,7 @@ function Invoke-ExecPartnerWebhook { # flag a subscription still pointing at a previous CIPP URL. $CurrentHostname = Get-CIPPHostname -Headers $Request.Headers if ($CurrentHostname) { - $Results | Add-Member -MemberType NoteProperty -Name 'expectedWebhookUrl' -Value "https://$CurrentHostname/API/PublicWebhooks?CIPPID=$($env:TenantID)&Type=PartnerCenter" -Force + $Results | Add-Member -MemberType NoteProperty -Name 'expectedWebhookUrl' -Value "https://$CurrentHostname/api/PublicWebhooks?CIPPID=$($env:TenantID)&Type=PartnerCenter" -Force } } 'CreateSubscription' { diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecRemoveSnooze.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecRemoveSnooze.ps1 index 506af62287af0..ccfc01f630cf5 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecRemoveSnooze.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecRemoveSnooze.ps1 @@ -23,7 +23,7 @@ function Invoke-ExecRemoveSnooze { } $SnoozeTable = Get-CIPPTable -tablename 'AlertSnooze' - Remove-AzDataTableEntity @SnoozeTable -Entity @{ + Remove-CIPPAzDataTableEntity @SnoozeTable -Entity @{ PartitionKey = $PartitionKey RowKey = $RowKey ETag = '*' diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecSetCIPPAutoBackup.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecSetCIPPAutoBackup.ps1 index 21335f2b64f1c..02eeb81498069 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecSetCIPPAutoBackup.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecSetCIPPAutoBackup.ps1 @@ -12,7 +12,7 @@ function Invoke-ExecSetCIPPAutoBackup { $Table = Get-CIPPTable -TableName 'ScheduledTasks' $AutomatedCIPPBackupTask = Get-AzDataTableEntity @table -Filter "Name eq 'Automated CIPP Backup'" -Property RowKey, PartitionKey, ETag if ($AutomatedCIPPBackupTask) { - Remove-AzDataTableEntity -Force @Table -Entity $AutomatedCIPPBackupTask | Out-Null + Remove-CIPPAzDataTableEntity -Force @Table -Entity $AutomatedCIPPBackupTask | Out-Null } $TaskBody = [pscustomobject]@{ @@ -32,7 +32,7 @@ function Invoke-ExecSetCIPPAutoBackup { $Table = Get-CIPPTable -TableName 'ScheduledTasks' $AutomatedCIPPBackupTask = Get-AzDataTableEntity @table -Filter "Name eq 'Automated CIPP Backup'" -Property RowKey, PartitionKey, ETag if ($AutomatedCIPPBackupTask) { - Remove-AzDataTableEntity -Force @Table -Entity $AutomatedCIPPBackupTask | Out-Null + Remove-CIPPAzDataTableEntity -Force @Table -Entity $AutomatedCIPPBackupTask | Out-Null $Result = @{ 'Results' = 'Scheduled Task Successfully removed' } } else { $Result = @{ 'Results' = 'No existing scheduled task found to remove' } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 index 331490026b958..a739ec68360c5 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 @@ -20,13 +20,13 @@ function Invoke-RemoveScheduledItem { } try { $Table = Get-CIPPTable -TableName 'ScheduledTasks' - Remove-AzDataTableEntity -Force @Table -Entity $task + Remove-CIPPAzDataTableEntity -Force @Table -Entity $task $DetailTable = Get-CIPPTable -TableName 'ScheduledTaskDetails' $Details = Get-CIPPAzDataTableEntity @DetailTable -Filter "PartitionKey eq '$($RowKey)'" -Property RowKey, PartitionKey, ETag if ($Details) { - Remove-AzDataTableEntity -Force @DetailTable -Entity $Details + Remove-CIPPAzDataTableEntity -Force @DetailTable -Entity $Details } Write-LogMessage -Headers $Headers -API $APIName -message "Task removed: $($task.RowKey)" -Sev 'Info' diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecApiClient.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecApiClient.ps1 index ddbda5d5132b1..383c39af49f72 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecApiClient.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecApiClient.ps1 @@ -336,7 +336,7 @@ function Invoke-ExecApiClient { } Write-Information "Deleting API Client: $ClientId from CIPP" $Client = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$($ClientId)'" -Property RowKey, PartitionKey - Remove-AzDataTableEntity @Table -Entity $Client -Force + Remove-CIPPAzDataTableEntity @Table -Entity $Client -Force Write-LogMessage -headers $Request.Headers -API 'ExecApiClient' -message "Deleted API client $ClientId" -Sev 'Info' $Body = @{ Results = "API client $ClientId deleted" } } else { diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAppServiceDomains.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAppServiceDomains.ps1 index 2931bae489bd8..f6e627b6f5ad2 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAppServiceDomains.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecAppServiceDomains.ps1 @@ -15,8 +15,10 @@ function Invoke-ExecAppServiceDomains { Actions (passed as Query.Action or Body.Action): List - Site metadata (default hostname, inbound IP, verification id) plus every hostname binding and any App Service Managed Certificate that matches. - CheckDns - Live DoH lookup of the two records a custom domain needs (ownership TXT - at asuid. and the CNAME/A alias). Powers wizard step 1 + resume. + CheckDns - Live DoH lookup of the records a custom domain needs. A matching CNAME + alias verifies on its own; the ownership TXT at asuid. is only + required for apex/A, wildcard or proxied aliases — and a stale TXT + (wrong value) is flagged for removal. Powers wizard step 1 + resume. AddBinding - Create the hostname binding (wizard step 2). Azure re-validates ownership. AddCertificate - Create an App Service Managed Certificate and enable the SNI SSL binding (wizard step 3). Safe to re-run — reuses an existing cert if present. @@ -52,8 +54,9 @@ function Invoke-ExecAppServiceDomains { } # Work out which DNS records a given custom hostname needs. Azure accepts either a CNAME (to the - # app's default hostname) or an A record (to the inbound IP) for the alias itself, plus a TXT - # ownership record at asuid.. Wildcards verify ownership at the parent domain. + # app's default hostname) or an A record (to the inbound IP) for the alias itself. A matching + # CNAME proves ownership by itself; the TXT record at asuid. is only needed when it can't + # (apex/A aliases, proxied CNAMEs, and wildcards — which verify ownership at the parent domain). function Get-DomainRecordPlan { param( [string]$Hostname, @@ -172,15 +175,21 @@ function Invoke-ExecAppServiceDomains { -InboundIp $SiteObj.properties.inboundIpAddress ` -VerificationId $SiteObj.properties.customDomainVerificationId - # Ownership: TXT at asuid. must contain the verification id. + # Ownership TXT at asuid.. A matching CNAME alias proves ownership by itself, + # so the TXT record is only REQUIRED when Azure cannot see such a CNAME: apex/A + # aliases, wildcards, and proxied records (e.g. Cloudflare orange-cloud). A TXT + # record with the WRONG value is worse than none — Azure hard-fails the binding on + # a mismatched asuid even when the CNAME is correct, so surface it for removal. $TxtValues = Resolve-DohRecord -Name $Plan.AsuidHost -Type 'TXT' - $OwnershipVerified = $TxtValues -contains $Plan.VerificationId + $AsuidState = if ($TxtValues.Count -eq 0) { 'Absent' } elseif ($TxtValues -contains $Plan.VerificationId) { 'Match' } else { 'Mismatch' } + $StaleAsuid = ($AsuidState -eq 'Mismatch') # Alias: accept a CNAME to the default hostname OR an A record to the inbound IP. # Wildcards can't be resolved directly, so ownership alone gates them here — Azure # validates the wildcard alias when the binding is created. $AliasVerified = $false $AliasDetail = $null + $CnameMatch = $null if ($Plan.IsWildcard) { $AliasVerified = $true $AliasDetail = 'Wildcard alias is validated by Azure when the binding is created.' @@ -201,31 +210,47 @@ function Invoke-ExecAppServiceDomains { } } + # TXT required whenever a matching CNAME can't vouch for the hostname. + $OwnershipRequired = [bool]($Plan.IsWildcard -or -not $CnameMatch) + $OwnershipVerified = $OwnershipRequired ? ($AsuidState -eq 'Match') : (-not $StaleAsuid) + # Proceed when a clean CNAME verified the domain, or the TXT record proves + # ownership (covers apex/A, wildcard and proxied aliases — Azure then makes the + # final call at binding time, matching the previous wizard behavior). + $CanProceed = ($CnameMatch -and -not $StaleAsuid) -or ($AsuidState -eq 'Match') + + $Records = [System.Collections.Generic.List[object]]::new() + if ($OwnershipRequired -or $StaleAsuid) { + $Records.Add([pscustomobject]@{ + Purpose = 'Ownership' + Type = 'TXT' + Host = $Plan.AsuidHost + Value = $Plan.VerificationId + Verified = ($AsuidState -eq 'Match') + }) + } + $Records.Add([pscustomobject]@{ + Purpose = 'Alias' + Type = $Plan.RecommendedType + Host = $Plan.IsApex ? '@' : $HostName + Value = $Plan.IsApex ? $Plan.ARecordTarget : $Plan.CnameTarget + Verified = $AliasVerified + }) + $Body = @{ Results = @{ Hostname = $HostName RecommendedType = $Plan.RecommendedType IsWildcard = $Plan.IsWildcard OwnershipVerified = $OwnershipVerified + OwnershipRequired = $OwnershipRequired + AsuidState = $AsuidState + StaleAsuid = $StaleAsuid + AsuidHost = $Plan.AsuidHost AliasVerified = $AliasVerified AllVerified = ($OwnershipVerified -and $AliasVerified) + CanProceed = [bool]$CanProceed AliasDetail = $AliasDetail - Records = @( - [pscustomobject]@{ - Purpose = 'Ownership' - Type = 'TXT' - Host = $Plan.AsuidHost - Value = $Plan.VerificationId - Verified = $OwnershipVerified - } - [pscustomobject]@{ - Purpose = 'Alias' - Type = $Plan.RecommendedType - Host = $Plan.IsApex ? '@' : $HostName - Value = $Plan.IsApex ? $Plan.ARecordTarget : $Plan.CnameTarget - Verified = $AliasVerified - } - ) + Records = @($Records) } } } @@ -239,8 +264,10 @@ function Invoke-ExecAppServiceDomains { $Site = Get-AppServiceSiteInfo $ArmBase = Get-SiteArmBase -Site $Site - # Azure enforces domain-ownership validation (asuid TXT + alias) during this PUT and - # returns a descriptive error if the records aren't in place yet. + # Azure enforces domain-ownership validation during this PUT: a matching CNAME + # suffices on its own; the asuid TXT is the fallback for apex/A, wildcard and + # proxied aliases. A TXT record with the WRONG value hard-fails validation even + # when the CNAME is correct, so append removal guidance to that error. $BindingUri = "$($ArmBase)/hostNameBindings/$HostName`?api-version=$ApiVersion" $BindingBody = @{ properties = @{ @@ -248,7 +275,16 @@ function Invoke-ExecAppServiceDomains { hostNameType = 'Verified' } } - New-CIPPAzRestRequest -Uri $BindingUri -Method PUT -Body $BindingBody -ContentType 'application/json' | Out-Null + try { + New-CIPPAzRestRequest -Uri $BindingUri -Method PUT -Body $BindingBody -ContentType 'application/json' | Out-Null + } catch { + $BindingError = $_.Exception.Message + if ($BindingError -match 'TXT record|asuid|CanonicalName') { + $AsuidHint = $HostName.StartsWith('*.') ? "asuid.$($HostName.Substring(2))" : "asuid.$HostName" + throw "$BindingError — If a TXT record named '$AsuidHint' already exists with a different value, remove or update it: a stale domain-verification record blocks validation even when the CNAME/A alias is correct." + } + throw + } Write-LogMessage -API $APIName -headers $Headers -message "Added custom domain binding '$HostName' to $($Site.SiteName)" -sev Info $Body = @{ Results = "Custom domain '$HostName' bound to the App Service. You can now enable a managed certificate." } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecBrandingSettings.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecBrandingSettings.ps1 index ddc4ac991934e..40e26cdc7486f 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecBrandingSettings.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecBrandingSettings.ps1 @@ -14,8 +14,12 @@ Function Invoke-ExecBrandingSettings { try { $Table = Get-CIPPTable -TableName Config - $Filter = "PartitionKey eq 'BrandingSettings' and RowKey eq 'BrandingSettings'" - $BrandingConfig = Get-CIPPAzDataTableEntity @Table -Filter $Filter + # Partition-scoped, not RowKey-scoped: a logo large enough to exceed the 1 MiB entity limit is + # stored across 'BrandingSettings-partN' rows, and reassembly needs every part row in the + # result set. Filtering on RowKey returns only the root row, so the split manifest is missing + # and the logo comes back as $null. + $Filter = "PartitionKey eq 'BrandingSettings'" + $BrandingConfig = Get-CIPPAzDataTableEntity @Table -Filter $Filter | Where-Object { $_.RowKey -eq 'BrandingSettings' } if (-not $BrandingConfig) { $BrandingConfig = @{ diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCIPPUsers.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCIPPUsers.ps1 index 2e1ca1931df5c..d7b75396f4cd2 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCIPPUsers.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCIPPUsers.ps1 @@ -108,7 +108,7 @@ function Invoke-ExecCIPPUsers { # Remove any case-variant duplicate rows now merged into the canonical row foreach ($Existing in $MatchingEntities) { if ($Existing.RowKey -cne $UPN) { - Remove-AzDataTableEntity -Force @Table -Entity $Existing + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Existing } } @@ -163,7 +163,7 @@ function Invoke-ExecCIPPUsers { } foreach ($Existing in $MatchingEntities) { - Remove-AzDataTableEntity -Force @Table -Entity $Existing + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Existing } try { [Craft.Services.AuthBridge]::InvalidateUsers() } catch {} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCippReplacemap.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCippReplacemap.ps1 index 17e84e919a898..0010097ef3050 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCippReplacemap.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCippReplacemap.ps1 @@ -68,7 +68,7 @@ function Invoke-ExecCippReplacemap { $VariableEntity = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq '$customerId' and RowKey eq '$VariableName'" if ($VariableEntity) { - Remove-AzDataTableEntity @Table -Entity $VariableEntity -Force + Remove-CIPPAzDataTableEntity @Table -Entity $VariableEntity -Force $Body = @{ Results = "Variable '$VariableName' deleted successfully" } } else { $Body = @{ Results = "Variable '$VariableName' not found" } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomData.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomData.ps1 index 61c8fac9ad443..09574746fa1f1 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomData.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomData.ps1 @@ -95,7 +95,7 @@ function Invoke-ExecCustomData { # Delete the schema extension entity - Remove-AzDataTableEntity @CustomDataTable -Entity $SchemaEntity + Remove-CIPPAzDataTableEntity @CustomDataTable -Entity $SchemaEntity $Body = @{ Results = @{ @@ -326,7 +326,7 @@ function Invoke-ExecCustomData { $ExtensionEntity = Get-CIPPAzDataTableEntity @CustomDataTable -Filter "PartitionKey eq 'DirectoryExtension' and RowKey eq '$ExtensionName'" # Remove the extension from the custom data table if ($ExtensionEntity) { - Remove-AzDataTableEntity @CustomDataTable -Entity $ExtensionEntity + Remove-CIPPAzDataTableEntity @CustomDataTable -Entity $ExtensionEntity } } catch { Write-Warning "Failed to delete directory extension from custom data table: $($_.Exception.Message)" @@ -433,7 +433,7 @@ function Invoke-ExecCustomData { } # Delete the mapping entity - Remove-AzDataTableEntity @CustomDataMappingsTable -Entity $MappingEntity + Remove-CIPPAzDataTableEntity @CustomDataMappingsTable -Entity $MappingEntity Register-CIPPExtensionScheduledTasks $Body = @{ Results = @{ diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 index a8e120844c4f2..37609dc2f7ba1 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 @@ -73,7 +73,7 @@ function Invoke-ExecCustomRole { # Remove IP ranges if none provided or role is superadmin $ExistingIPRange = Get-CIPPAzDataTableEntity @AccessIPRangeTable -Filter "RowKey eq '$($Request.Body.RoleName.ToLower())'" if ($ExistingIPRange) { - Remove-AzDataTableEntity -Force @AccessIPRangeTable -Entity $ExistingIPRange + Remove-CIPPAzDataTableEntity -Force @AccessIPRangeTable -Entity $ExistingIPRange if ($Request.Body.RoleName -ne 'superadmin') { $Results.Add("IP ranges removed from '$($Request.Body.RoleName)' role.") } @@ -92,7 +92,7 @@ function Invoke-ExecCustomRole { } else { $AccessRoleGroup = Get-CIPPAzDataTableEntity @AccessRoleGroupTable -Filter "RowKey eq '$($Request.Body.RoleName)'" if ($AccessRoleGroup) { - Remove-AzDataTableEntity -Force @AccessRoleGroupTable -Entity $AccessRoleGroup + Remove-CIPPAzDataTableEntity -Force @AccessRoleGroupTable -Entity $AccessRoleGroup $Results.Add("Security group '$($AccessRoleGroup.GroupName)' removed from the '$($Request.Body.RoleName)' role.") Write-LogMessage -headers $Request.Headers -API 'ExecCustomRole' -message "Security group '$($AccessRoleGroup.GroupName)' removed from the '$($Request.Body.RoleName)' role." -Sev 'Info' } @@ -153,14 +153,14 @@ function Invoke-ExecCustomRole { 'Delete' { Write-Information "Deleting custom role $($Request.Body.RoleName)" $Role = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$($Request.Body.RoleName)'" -Property RowKey, PartitionKey - Remove-AzDataTableEntity -Force @Table -Entity $Role + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Role $AccessRoleGroup = Get-CIPPAzDataTableEntity @AccessRoleGroupTable -Filter "PartitionKey eq 'AccessRoleGroups' and RowKey eq '$($Request.Body.RoleName)'" if ($AccessRoleGroup) { - Remove-AzDataTableEntity -Force @AccessRoleGroupTable -Entity $AccessRoleGroup + Remove-CIPPAzDataTableEntity -Force @AccessRoleGroupTable -Entity $AccessRoleGroup } $AccessIPRange = Get-CIPPAzDataTableEntity @AccessIPRangeTable -Filter "PartitionKey eq 'AccessIPRanges' and RowKey eq '$($Request.Body.RoleName)'" if ($AccessIPRange) { - Remove-AzDataTableEntity -Force @AccessIPRangeTable -Entity $AccessIPRange + Remove-CIPPAzDataTableEntity -Force @AccessIPRangeTable -Entity $AccessIPRange } $Body = @{Results = 'Custom role deleted' } Write-LogMessage -headers $Request.Headers -API 'ExecCustomRole' -message "Deleted custom role $($Request.Body.RoleName)" -Sev 'Info' diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1 index b21924d486a7c..bd4f4db30f65e 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1 @@ -94,7 +94,7 @@ function Invoke-ExecDnsConfig { 'RemoveDomain' { $Filter = "RowKey eq '{0}'" -f $Domain $DomainRow = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @DomainTable -Entity $DomainRow + Remove-CIPPAzDataTableEntity -Force @DomainTable -Entity $DomainRow Write-LogMessage -API $APIName -tenant 'Global' -headers $Headers -message "Removed Domain - $Domain " -Sev 'Info' $body = [pscustomobject]@{ 'Results' = "Domain removed - $Domain" } } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExcludeLicenses.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExcludeLicenses.ps1 index 98b1b07ab8cad..d16812bfe683c 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExcludeLicenses.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExcludeLicenses.ps1 @@ -66,7 +66,7 @@ function Invoke-ExecExcludeLicenses { 'RemoveExclusion' { $Filter = "RowKey eq '{0}' and PartitionKey eq 'License'" -f $GUID $Entity = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $Entity + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Entity $Result = "Success. Removed $DisplayName($GUID) from the excluded licenses list." Write-LogMessage -API $APIName -headers $Headers -message $Result -Sev 'Info' diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecPartnerMode.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecPartnerMode.ps1 index 9c2b9ea49dc56..a7b6783ad57dc 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecPartnerMode.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecPartnerMode.ps1 @@ -22,7 +22,7 @@ function Invoke-ExecPartnerMode { $Tenant = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'Tenants' and RowKey eq '$($env:TenantID)'" -Property RowKey, PartitionKey, customerId, displayName if ($Tenant) { try { - Remove-AzDataTableEntity -Force @Table -Entity $Tenant + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Tenant } catch { } } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRemoveTenant.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRemoveTenant.ps1 index 52381ad800535..c0d710475bc79 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRemoveTenant.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRemoveTenant.ps1 @@ -16,7 +16,7 @@ function Invoke-ExecRemoveTenant { $Tenant = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'Tenants' and RowKey eq '$($Request.Body.TenantID)'" -Property RowKey, PartitionKey, customerId, displayName if ($Tenant) { try { - Remove-AzDataTableEntity -Force @Table -Entity $Tenant + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Tenant $Body = @{Results = "$($Tenant.displayName) ($($Tenant.customerId)) deleted from CIPP. Note: This does not remove the GDAP relationship, see the Tenant Offboarding wizard to perform that action." } $StatusCode = [HttpStatusCode]::OK } catch { diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 index 0a19eeb588041..3974edd519321 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 @@ -78,7 +78,7 @@ function Invoke-ExecSAMAppPermissions { $Table = Get-CIPPTable -TableName 'AppPermissions' $Existing = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'CIPP-SAM' and RowKey eq 'CIPP-SAM'" if ($Existing) { - $null = Remove-AzDataTableEntity @Table -Entity $Existing -Force + $null = Remove-CIPPAzDataTableEntity @Table -Entity $Existing -Force } $Body = @{ 'Results' = 'Permissions reset to CIPP defaults.' diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTenantGroup.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTenantGroup.ps1 index 76a392e6b4a5a..b4387430ff53e 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTenantGroup.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTenantGroup.ps1 @@ -119,7 +119,7 @@ function Invoke-ExecTenantGroup { if ($CurrentMembers -and $null -ne $members) { foreach ($CurrentMember in $CurrentMembers) { if ($members.value -notcontains $CurrentMember.customerId) { - Remove-AzDataTableEntity @MembersTable -Entity $CurrentMember -Force + Remove-CIPPAzDataTableEntity @MembersTable -Entity $CurrentMember -Force $Removes.Add('Removed member {0}' -f $CurrentMember.customerId) } } @@ -150,7 +150,7 @@ function Invoke-ExecTenantGroup { # Delete group $GroupEntity = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'TenantGroup' and RowKey eq '$groupId'" if ($GroupEntity) { - Remove-AzDataTableEntity @Table -Entity $GroupEntity -Force + Remove-CIPPAzDataTableEntity @Table -Entity $GroupEntity -Force $Body = @{ Results = "Group '$($GroupEntity.Name)' deleted successfully" } } else { $Body = @{ Results = "Group '$groupId' not found" } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecWebhookSubscriptions.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecWebhookSubscriptions.ps1 index baa30069dc302..692239ac87796 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecWebhookSubscriptions.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecWebhookSubscriptions.ps1 @@ -19,7 +19,7 @@ function Invoke-ExecWebhookSubscriptions { $Webhook = Get-AzDataTableEntity @Table -Filter "RowKey eq '$SafeWebhookId'" -Property PartitionKey, RowKey if ($Webhook) { Remove-CIPPGraphSubscription -TenantFilter $Webhook.PartitionKey -CIPPID $Webhook.RowKey - Remove-AzDataTableEntity -Force @Table -Entity $Webhook + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Webhook return ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = @{ Results = "Deleted subscription $($Webhook.RowKey) for $($Webhook.PartitionKey)" } @@ -53,7 +53,7 @@ function Invoke-ExecWebhookSubscriptions { return } Remove-CIPPGraphSubscription @Unsubscribe - Remove-AzDataTableEntity -Force @Table -Entity $Webhook + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Webhook return ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = @{ Results = "Unsubscribed from $($Webhook.Resource) for $($Webhook.PartitionKey)" } @@ -76,7 +76,7 @@ function Invoke-ExecWebhookSubscriptions { # get row from table if exists and remove $Webhook = Get-AzDataTableEntity @Table -Filter "WebhookNotificationUrl eq 'https://graph.microsoft.com/beta/subscriptions/$($_.id)'" -Property PartitionKey, RowKey, ETag if ($Webhook) { - $null = Remove-AzDataTableEntity -Force @Table -Entity $Webhook + $null = Remove-CIPPAzDataTableEntity -Force @Table -Entity $Webhook } } } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSSOSetup.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSSOSetup.ps1 index fc035264316f6..dd352df45bb46 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSSOSetup.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSSOSetup.ps1 @@ -72,20 +72,26 @@ function Invoke-ExecSSOSetup { $MigrationError = if ($MigrationMatches) { $Migration.LastError } else { '' } $MigrationCanRepair = $MigrationMatches -and ($Migration.Status -in @('error', 'app_created', 'appid_stored')) + # Null (rather than false) when warmup has never attempted the grant, so + # the UI can tell "not yet tried" apart from "tried and refused". + $Preconsented = if ([string]::IsNullOrWhiteSpace($Migration.Preconsented)) { $null } else { $Migration.Preconsented -eq 'true' } + $Body = @{ Results = @{ - configured = $true - status = $MigrationStatus - appId = $ClientId - multiTenant = $IsMultiTenant - tenantId = $IssuerTenantId - issuer = $Issuer - audiences = $AllowedAudiences - allowedApps = $AllowedApps - excludedPaths = $ExcludedPaths - easyAuthActive = $true - lastError = $MigrationError - canRepair = [bool]$MigrationCanRepair + configured = $true + status = $MigrationStatus + appId = $ClientId + multiTenant = $IsMultiTenant + tenantId = $IssuerTenantId + issuer = $Issuer + audiences = $AllowedAudiences + allowedApps = $AllowedApps + excludedPaths = $ExcludedPaths + easyAuthActive = $true + lastError = $MigrationError + canRepair = [bool]$MigrationCanRepair + preconsented = $Preconsented + preconsentError = $Migration.PreconsentError } } } else { @@ -94,15 +100,17 @@ function Invoke-ExecSSOSetup { if ($Migration) { $Body = @{ Results = @{ - configured = $true - status = $Migration.Status - appId = $Migration.AppId - multiTenant = [bool]($Migration.MultiTenant -eq 'true' -or $Migration.MultiTenant -eq 'True') - createdAt = $Migration.CreatedAt - lastChecked = $Migration.LastChecked - lastError = $Migration.LastError - easyAuthActive = $false - canRepair = [bool]($Migration.AppId -and ($Migration.Status -in @('error', 'app_created', 'appid_stored'))) + configured = $true + status = $Migration.Status + appId = $Migration.AppId + multiTenant = [bool]($Migration.MultiTenant -eq 'true' -or $Migration.MultiTenant -eq 'True') + createdAt = $Migration.CreatedAt + lastChecked = $Migration.LastChecked + lastError = $Migration.LastError + easyAuthActive = $false + canRepair = [bool]($Migration.AppId -and ($Migration.Status -in @('error', 'app_created', 'appid_stored'))) + preconsented = if ([string]::IsNullOrWhiteSpace($Migration.Preconsented)) { $null } else { $Migration.Preconsented -eq 'true' } + preconsentError = $Migration.PreconsentError } } } else { @@ -121,14 +129,16 @@ function Invoke-ExecSSOSetup { if ($Migration) { $Body = @{ Results = @{ - configured = $true - status = $Migration.Status - appId = $Migration.AppId - multiTenant = [bool]($Migration.MultiTenant -eq 'true' -or $Migration.MultiTenant -eq 'True') - createdAt = $Migration.CreatedAt - lastChecked = $Migration.LastChecked - lastError = $Migration.LastError - canRepair = [bool]($Migration.AppId -and ($Migration.Status -in @('error', 'app_created', 'appid_stored'))) + configured = $true + status = $Migration.Status + appId = $Migration.AppId + multiTenant = [bool]($Migration.MultiTenant -eq 'true' -or $Migration.MultiTenant -eq 'True') + createdAt = $Migration.CreatedAt + lastChecked = $Migration.LastChecked + lastError = $Migration.LastError + canRepair = [bool]($Migration.AppId -and ($Migration.Status -in @('error', 'app_created', 'appid_stored'))) + preconsented = if ([string]::IsNullOrWhiteSpace($Migration.Preconsented)) { $null } else { $Migration.Preconsented -eq 'true' } + preconsentError = $Migration.PreconsentError } } } else { @@ -347,7 +357,7 @@ function Invoke-ExecSSOSetup { $PreviousAppId = $Existing.AppId if ($Existing) { - Remove-AzDataTableEntity @MigrationTable -Entity $Existing -Force | Out-Null + Remove-CIPPAzDataTableEntity @MigrationTable -Entity $Existing -Force | Out-Null Write-LogMessage -API $APIName -headers $Headers -message "SSO migration record cleared (previous AppId: $PreviousAppId). Use Create to provision a new app." -sev Info } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Contacts/Invoke-RemoveContactTemplates.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Contacts/Invoke-RemoveContactTemplates.ps1 index d041baef7ddf9..cbcafcaa7d703 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Contacts/Invoke-RemoveContactTemplates.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Contacts/Invoke-RemoveContactTemplates.ps1 @@ -18,7 +18,7 @@ function Invoke-RemoveContactTemplates { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type Guid $Filter = "PartitionKey eq 'ContactTemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed Contact Template with ID $ID." Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-RemoveConnectionfilterTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-RemoveConnectionfilterTemplate.ps1 index 77b04992d67de..b95166053a740 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-RemoveConnectionfilterTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-RemoveConnectionfilterTemplate.ps1 @@ -18,7 +18,7 @@ Function Invoke-RemoveConnectionfilterTemplate { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type Guid $Filter = "PartitionKey eq 'ConnectionfilterTemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed Connection Filter template with ID $($ID)" Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-RemoveSpamfilterTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-RemoveSpamfilterTemplate.ps1 index 531b7e6e591ba..ef2f062498197 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-RemoveSpamfilterTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-RemoveSpamfilterTemplate.ps1 @@ -18,7 +18,7 @@ Function Invoke-RemoveSpamfilterTemplate { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type Guid $Filter = "PartitionKey eq 'SpamfilterTemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed Spamfilter template with ID $ID" Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-RemoveTenantAllowBlockListTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-RemoveTenantAllowBlockListTemplate.ps1 index 5636743deb3f8..fd4be24f76e19 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-RemoveTenantAllowBlockListTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-RemoveTenantAllowBlockListTemplate.ps1 @@ -18,7 +18,7 @@ Function Invoke-RemoveTenantAllowBlockListTemplate { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type Guid $Filter = "PartitionKey eq 'TenantAllowBlockListTemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed Tenant Allow/Block List Template with ID $ID." Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev Info $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-RemoveExConnectorTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-RemoveExConnectorTemplate.ps1 index 78634814ab85c..db10764ffb5fa 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-RemoveExConnectorTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-RemoveExConnectorTemplate.ps1 @@ -18,7 +18,7 @@ Function Invoke-RemoveExConnectorTemplate { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type Guid $Filter = "PartitionKey eq 'ExConnectorTemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed Exchange Connector Template with ID $ID." Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-RemoveTransportRuleTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-RemoveTransportRuleTemplate.ps1 index ae2e30b8a057a..069d6deb7f522 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-RemoveTransportRuleTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-RemoveTransportRuleTemplate.ps1 @@ -18,7 +18,7 @@ function Invoke-RemoveTransportRuleTemplate { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type String $Filter = "PartitionKey eq 'TransportTemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed Transport Rule Template with ID $ID." Write-LogMessage -Headers $User -API $APINAME -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-RemoveAppTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-RemoveAppTemplate.ps1 index 87a752135fcbc..23bab28a6f0f1 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-RemoveAppTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-RemoveAppTemplate.ps1 @@ -20,7 +20,7 @@ function Invoke-RemoveAppTemplate { $Filter = "PartitionKey eq 'AppTemplate' and RowKey eq '$SafeID'" $Entity = Get-CIPPAzDataTableEntity @Table -Filter $Filter if ($Entity) { - Remove-AzDataTableEntity @Table -Entity $Entity + Remove-CIPPAzDataTableEntity @Table -Entity $Entity $Result = 'Successfully removed app template' Write-LogMessage -headers $Headers -API $APIName -message "Removed app template $ID" -Sev 'Info' } else { diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-RemoveQueuedApp.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-RemoveQueuedApp.ps1 index 557f3bc8ff4b9..82e296c6218d3 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-RemoveQueuedApp.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-RemoveQueuedApp.ps1 @@ -17,7 +17,7 @@ function Invoke-RemoveQueuedApp { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type Guid $Filter = "PartitionKey eq 'apps' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Message = "Removed application queue for $ID." Write-LogMessage -Headers $Request.Headers -API $APIName -message $Message -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ExecCompareIntunePolicy.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ExecCompareIntunePolicy.ps1 index 8ee45df653588..dfd9bda52396b 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ExecCompareIntunePolicy.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ExecCompareIntunePolicy.ps1 @@ -42,7 +42,11 @@ function Invoke-ExecCompareIntunePolicy { [Parameter(Mandatory = $true)] [string]$TemplateGuid, [string]$TenantFilter, - [string]$Label + [string]$Label, + # Set when the caller only needs the template's identity and type in order to find + # the tenant's own copy. Skips the reusable settings sync, which writes to the + # tenant and is the other source's job to run. + [switch]$SkipReusableSync ) $Table = Get-CippTable -tablename 'templates' @@ -57,21 +61,48 @@ function Invoke-ExecCompareIntunePolicy { $RawJSON = $JSONData.RAWJson if ($TenantFilter) { - try { - $ReusableSync = Sync-CIPPReusablePolicySettings -TemplateInfo $JSONData -Tenant $TenantFilter -ErrorAction Stop - if ($ReusableSync.RawJSON) { - $RawJSON = $ReusableSync.RawJSON + if (-not $SkipReusableSync) { + try { + $ReusableSync = Sync-CIPPReusablePolicySettings -TemplateInfo $JSONData -Tenant $TenantFilter -ErrorAction Stop + if ($ReusableSync.RawJSON) { + $RawJSON = $ReusableSync.RawJSON + } + } catch { + Write-Warning "$Label : Failed to sync reusable policy settings - $($_.Exception.Message)" } - } catch { - Write-Warning "$Label : Failed to sync reusable policy settings - $($_.Exception.Message)" } $RawJSON = Get-CIPPTextReplacement -Text $RawJSON -TenantFilter $TenantFilter -EscapeForJson } + $TemplateType = Get-CIPPIntuneTemplateType -Type $JSONData.Type -RawJson $RawJSON + $Object = $RawJSON | ConvertFrom-Json -Depth 100 + + if ($TenantFilter -and $TemplateType) { + # The same preparation the IntuneTemplate standard applies, so both agree on what + # the baseline is: identity comes from the template's columns for the types + # deployment writes them onto, and a Catalog policy loses the settings this tenant + # cannot hold, because deployment drops those before sending it. + $Object = Merge-CIPPIntuneTemplateIdentity -Policy $Object -TemplateType $TemplateType -DisplayName $JSONData.Displayname -Description $JSONData.Description + if ($TemplateType -eq 'Catalog') { + try { + $Object = Select-CIPPIntuneAvailableSetting -Policy $Object -TenantFilter $TenantFilter + } catch { + Write-Warning "$Label : Could not resolve available settings, comparing against the full template - $($_.Exception.Message)" + } + } + } + return @{ - Object = $RawJSON | ConvertFrom-Json -Depth 100 - TemplateType = Get-CIPPIntuneTemplateType -Type $JSONData.Type -RawJson $RawJSON + Object = $Object + TemplateType = $TemplateType DisplayName = $JSONData.Displayname + # The name this template's policy is deployed under, which is not always the + # Displayname - Catalog and the update profiles are named from their payload. + PolicyName = if ($TemplateType) { + Get-CIPPIntunePolicyName -TemplateType $TemplateType -RawJSON $RawJSON -DisplayName $JSONData.Displayname + } else { + $JSONData.Displayname + } } } @@ -135,15 +166,18 @@ function Invoke-ExecCompareIntunePolicy { throw "$Label : templateGuid and tenantFilter are required for tenantPolicyByTemplate sources" } - $Template = Get-ComparisonTemplate -TemplateGuid $Source.templateGuid -Label $Label + # Resolved for the tenant, because the name a policy is deployed under can depend on + # tenant variables in the payload. The reusable settings sync is skipped - it writes + # to the tenant, and the baseline source already runs it. + $Template = Get-ComparisonTemplate -TemplateGuid $Source.templateGuid -TenantFilter $Source.tenantFilter -SkipReusableSync -Label $Label if (-not $Template.TemplateType) { throw "$Label : Template '$($Template.DisplayName)' has no policy type and none could be inferred. Re-import the template to fix this." } - $Policy = Get-CIPPIntunePolicy -TemplateType $Template.TemplateType -DisplayName $Template.DisplayName -tenantFilter $Source.tenantFilter -Headers $Headers -APINAME $APIName + $Policy = Get-CIPPIntunePolicy -TemplateType $Template.TemplateType -DisplayName $Template.PolicyName -tenantFilter $Source.tenantFilter -Headers $Headers -APINAME $APIName $MatchType = 'exact' - $MatchedName = $Template.DisplayName + $MatchedName = $Template.PolicyName # Without this the comparison would report the policy as missing while remediation # would happily overwrite an existing, similarly named one. Mirrors the candidate @@ -154,7 +188,7 @@ function Invoke-ExecCompareIntunePolicy { $AllPolicies = @(Get-CIPPIntunePolicy -TemplateType $Template.TemplateType -tenantFilter $Source.tenantFilter -Headers $Headers -APINAME $APIName) $FuzzyParams = @{ - DisplayName = $Template.DisplayName + DisplayName = $Template.PolicyName ExistingPolicies = $AllPolicies MaxDistance = $MaxDistance } @@ -186,10 +220,10 @@ function Invoke-ExecCompareIntunePolicy { return @{ Object = $null Missing = $true - MissingName = $Template.DisplayName + MissingName = $Template.PolicyName FuzzyDistance = $MaxDistance TemplateType = $Template.TemplateType - Label = "$($Template.DisplayName) (not deployed to $($Source.tenantFilter))" + Label = "$($Template.PolicyName) (not deployed to $($Source.tenantFilter))" RawData = $null } } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ExecRemoveCippCveException.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ExecRemoveCippCveException.ps1 index a263d81176ac0..3b02d8bbe598b 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ExecRemoveCippCveException.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ExecRemoveCippCveException.ps1 @@ -55,7 +55,7 @@ function Invoke-ExecRemoveCippCveException { $RemovedCount = 0 foreach ($Entity in $EntitiesToRemove) { - Remove-AzDataTableEntity @CveExceptionsTable -Entity $Entity -Force + Remove-CIPPAzDataTableEntity @CveExceptionsTable -Entity $Entity -Force $RemovedCount++ } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-RemoveAssignmentFilterTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-RemoveAssignmentFilterTemplate.ps1 index b45105a4fb5d5..ccab5a1289605 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-RemoveAssignmentFilterTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-RemoveAssignmentFilterTemplate.ps1 @@ -21,7 +21,7 @@ Function Invoke-RemoveAssignmentFilterTemplate { $Filter = "PartitionKey eq 'AssignmentFilterTemplate' and RowKey eq '$SafeID'" Write-Host $Filter $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed Assignment Filter Template with ID $ID" Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-RemoveIntuneReusableSettingTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-RemoveIntuneReusableSettingTemplate.ps1 index 65083c8acfce6..ebf61faf6d338 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-RemoveIntuneReusableSettingTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-RemoveIntuneReusableSettingTemplate.ps1 @@ -20,7 +20,7 @@ function Invoke-RemoveIntuneReusableSettingTemplate { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type Guid $Filter = "PartitionKey eq 'IntuneReusableSettingTemplate' and RowKey eq '$SafeID'" $Row = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $Row + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Row $Result = "Removed Intune reusable setting template with ID $ID" Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Info' diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-RemoveIntuneTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-RemoveIntuneTemplate.ps1 index 86b03e0d32bb2..52e9847dcd30c 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-RemoveIntuneTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-RemoveIntuneTemplate.ps1 @@ -20,7 +20,7 @@ function Invoke-RemoveIntuneTemplate { $Filter = "PartitionKey eq 'IntuneTemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey if ($ClearRow) { - Remove-AzDataTableEntity @Table -Entity $clearRow -Force + Remove-CIPPAzDataTableEntity @Table -Entity $clearRow -Force $Result = "Removed Intune Template with ID $ID." } else { $Result = "The template with ID $ID has already been deleted." diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-RemoveGroupTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-RemoveGroupTemplate.ps1 index c61006b1b0b19..8d26a0d722738 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-RemoveGroupTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-RemoveGroupTemplate.ps1 @@ -21,7 +21,7 @@ Function Invoke-RemoveGroupTemplate { $Filter = "PartitionKey eq 'GroupTemplate' and RowKey eq '$SafeID'" Write-Host $Filter $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed Group Template with ID $ID" Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSettings.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSettings.ps1 index 01282d8552c0c..570a1ef4d1188 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSettings.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSettings.ps1 @@ -104,7 +104,9 @@ function Invoke-ListUserSettings { #Get branding settings if ($UserSettings) { $brandingTable = Get-CippTable -tablename 'Config' - $BrandingSettings = Get-CIPPAzDataTableEntity @brandingTable -Filter "PartitionKey eq 'BrandingSettings' and RowKey eq 'BrandingSettings'" + # Partition-scoped, not RowKey-scoped: a logo over the 1 MiB entity limit is split across + # 'BrandingSettings-partN' rows and reassembly needs all of them in the result set. + $BrandingSettings = Get-CIPPAzDataTableEntity @brandingTable -Filter "PartitionKey eq 'BrandingSettings'" | Where-Object { $_.RowKey -eq 'BrandingSettings' } if ($BrandingSettings) { $UserSettings | Add-Member -MemberType NoteProperty -Name 'customBranding' -Value $BrandingSettings -Force | Out-Null } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveJITAdminTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveJITAdminTemplate.ps1 index 86ee71eee2931..bb6843e016351 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveJITAdminTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveJITAdminTemplate.ps1 @@ -24,7 +24,7 @@ function Invoke-RemoveJITAdminTemplate { $Template = Get-CIPPAzDataTableEntity @Table -Filter $Filter if ($Template) { - Remove-AzDataTableEntity @Table -Entity $Template + Remove-CIPPAzDataTableEntity @Table -Entity $Template $Result = "Successfully deleted JIT Admin Template with ID: $ID" Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveUserDefaultTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveUserDefaultTemplate.ps1 index ef053e4c72af5..61f9495301eaa 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveUserDefaultTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveUserDefaultTemplate.ps1 @@ -19,7 +19,7 @@ function Invoke-RemoveUserDefaultTemplate { $Template = Get-CIPPAzDataTableEntity @Table -Filter $Filter if ($Template) { - Remove-AzDataTableEntity @Table -Entity $Template + Remove-CIPPAzDataTableEntity @Table -Entity $Template $Result = "Successfully deleted User Default Template with ID: $ID" Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-DeleteTestReport.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-DeleteTestReport.ps1 index ed502f467c1e0..6f82effad9a89 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-DeleteTestReport.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-DeleteTestReport.ps1 @@ -15,7 +15,7 @@ function Invoke-DeleteTestReport { $ReportId = $Request.Body.ReportId $Table = Get-CippTable -tablename 'CippReportTemplates' $ExistingReport = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$ReportId'" - Remove-AzDataTableEntity @Table -Entity $ExistingReport + Remove-CIPPAzDataTableEntity @Table -Entity $ExistingReport $Body = [PSCustomObject]@{ Results = 'Successfully deleted custom report' diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-RemoveWebhookAlert.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-RemoveWebhookAlert.ps1 index be8e5cdaa6399..764a0d1524379 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-RemoveWebhookAlert.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-RemoveWebhookAlert.ps1 @@ -17,7 +17,7 @@ Function Invoke-RemoveWebhookAlert { Write-Host "The webhook count is $($WebhookRow.count)" if ($WebhookRow.count -gt 1) { $Entity = $WebhookRow | Where-Object -Property RowKey -EQ $Request.query.ID - Remove-AzDataTableEntity -Force @WebhookTable -Entity $Entity | Out-Null + Remove-CIPPAzDataTableEntity -Force @WebhookTable -Entity $Entity | Out-Null $Results = "Removed Alert Rule for $($Request.query.TenantFilter)" } else { if ($Request.query.TenantFilter -eq 'AllTenants') { @@ -29,7 +29,7 @@ Function Invoke-RemoveWebhookAlert { RowKey = 'AllTenantsWebhookCreation' PartitionKey = 'webhookcreation' } - Remove-AzDataTableEntity -Force @Table -Entity $CompleteObject -ErrorAction SilentlyContinue | Out-Null + Remove-CIPPAzDataTableEntity -Force @Table -Entity $CompleteObject -ErrorAction SilentlyContinue | Out-Null } catch { Write-LogMessage -headers $Request.Headers -API $APIName -message "Failed to remove webhook for AllTenants. $($_.Exception.Message)" -Sev 'Error' } @@ -40,7 +40,7 @@ Function Invoke-RemoveWebhookAlert { $Results = foreach ($Tenant in $Tenants) { Remove-CIPPGraphSubscription -TenantFilter $Tenant -Type 'AuditLog' $Entity = $WebhookRow | Where-Object -Property RowKey -EQ $Request.query.ID - Remove-AzDataTableEntity -Force @WebhookTable -Entity $Entity | Out-Null + Remove-CIPPAzDataTableEntity -Force @WebhookTable -Entity $Entity | Out-Null "Removed Alert Rule for $($Request.query.TenantFilter)" } } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-DLP/Invoke-RemoveDlpCompliancePolicyTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-DLP/Invoke-RemoveDlpCompliancePolicyTemplate.ps1 index 9504141526e5b..44265d265ff23 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-DLP/Invoke-RemoveDlpCompliancePolicyTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-DLP/Invoke-RemoveDlpCompliancePolicyTemplate.ps1 @@ -17,7 +17,7 @@ Function Invoke-RemoveDlpCompliancePolicyTemplate { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type Guid $Filter = "PartitionKey eq 'DlpCompliancePolicyTemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed DLP Compliance Policy template with ID $ID" Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-Retention/Invoke-RemoveRetentionCompliancePolicyTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-Retention/Invoke-RemoveRetentionCompliancePolicyTemplate.ps1 index 69d7b60eca4e2..b4549ff14af2e 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-Retention/Invoke-RemoveRetentionCompliancePolicyTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-Retention/Invoke-RemoveRetentionCompliancePolicyTemplate.ps1 @@ -17,7 +17,7 @@ Function Invoke-RemoveRetentionCompliancePolicyTemplate { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type Guid $Filter = "PartitionKey eq 'RetentionCompliancePolicyTemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed Retention Compliance Policy template with ID $ID" Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-SIT/Invoke-RemoveSensitiveInfoTypeTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-SIT/Invoke-RemoveSensitiveInfoTypeTemplate.ps1 index 23c88a9572a6c..152a966f512d0 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-SIT/Invoke-RemoveSensitiveInfoTypeTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-SIT/Invoke-RemoveSensitiveInfoTypeTemplate.ps1 @@ -17,7 +17,7 @@ Function Invoke-RemoveSensitiveInfoTypeTemplate { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type Guid $Filter = "PartitionKey eq 'SensitiveInfoTypeTemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed Sensitive Information Type template with ID $ID" Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-SensitivityLabel/Invoke-RemoveSensitivityLabelTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-SensitivityLabel/Invoke-RemoveSensitivityLabelTemplate.ps1 index c8084265db52d..1f1ce54e462cb 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-SensitivityLabel/Invoke-RemoveSensitivityLabelTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-SensitivityLabel/Invoke-RemoveSensitivityLabelTemplate.ps1 @@ -17,7 +17,7 @@ Function Invoke-RemoveSensitivityLabelTemplate { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type Guid $Filter = "PartitionKey eq 'SensitivityLabelTemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed Sensitivity Label template with ID $ID" Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-RemoveSafeLinksPolicyTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-RemoveSafeLinksPolicyTemplate.ps1 index d411a5d152a8e..7725f52cb25e5 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-RemoveSafeLinksPolicyTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Safe-Links-Policy/Invoke-RemoveSafeLinksPolicyTemplate.ps1 @@ -16,7 +16,7 @@ function Invoke-RemoveSafeLinksPolicyTemplate { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type String $Filter = "PartitionKey eq 'SafeLinksTemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed SafeLinks Policy Template with ID $ID." Write-LogMessage -Headers $User -API $APINAME -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSharePointTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSharePointTemplate.ps1 index 8f24d1ee7dc81..d437123d0c81b 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSharePointTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSharePointTemplate.ps1 @@ -94,7 +94,7 @@ function Invoke-ExecSharePointTemplate { if ($Template) { $TemplateName = ($Template.JSON | ConvertFrom-Json).templateName - $null = Remove-AzDataTableEntity @Table -Entity $Template -Force + $null = Remove-CIPPAzDataTableEntity @Table -Entity $Template -Force $Body = @{ 'Results' = "Successfully deleted template '$TemplateName'" } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-RemoveQueuedAlert.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-RemoveQueuedAlert.ps1 index af7a7cfd9a348..7e1624f2f3b4f 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-RemoveQueuedAlert.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-RemoveQueuedAlert.ps1 @@ -26,7 +26,7 @@ Function Invoke-RemoveQueuedAlert { try { $Filter = "RowKey eq '{0}'" -f $ID $Alert = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $Alert + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Alert $Result = "Successfully removed alert $ID from queue" Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppApprovalTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppApprovalTemplate.ps1 index 75c24fa64b0b1..3440b114303ee 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppApprovalTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppApprovalTemplate.ps1 @@ -91,7 +91,7 @@ function Invoke-ExecAppApprovalTemplate { $TemplateName = $TemplateData.TemplateName # Remove the template - $null = Remove-AzDataTableEntity @Table -Entity $Template -Force + $null = Remove-CIPPAzDataTableEntity @Table -Entity $Template -Force $Body = @{ 'Results' = "Successfully deleted template '$TemplateName'" diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppPermissionTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppPermissionTemplate.ps1 index 3cdc003452d46..509d201eea98b 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppPermissionTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAppPermissionTemplate.ps1 @@ -51,7 +51,7 @@ function Invoke-ExecAppPermissionTemplate { $TemplateName = $Template.TemplateName if ($TemplateId) { - $null = Remove-AzDataTableEntity @Table -Entity $Template -Force + $null = Remove-CIPPAzDataTableEntity @Table -Entity $Template -Force $Body = @{ 'Results' = "Successfully deleted template '$TemplateName'" } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOffboardTenant.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOffboardTenant.ps1 index c84b15000f513..219140ee5c638 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOffboardTenant.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOffboardTenant.ps1 @@ -114,7 +114,7 @@ function Invoke-ExecOffboardTenant { if ($DomainEntries) { $DomainCount = ($DomainEntries | Measure-Object).Count foreach ($Domain in $DomainEntries) { - Remove-AzDataTableEntity @DomainTable -Entity $Domain + Remove-CIPPAzDataTableEntity @DomainTable -Entity $Domain } $Results.Add("Successfully removed $DomainCount Domain Analyser entries") Write-LogMessage -headers $Headers -API $APIName -message "Removed $DomainCount Domain Analyser entries" -Sev 'Info' -tenant $TenantFilter @@ -204,7 +204,7 @@ function Invoke-ExecOffboardTenant { $TenantsTable = Get-CippTable -tablename 'Tenants' $TenantRow = Get-CIPPAzDataTableEntity @TenantsTable -Filter "PartitionKey eq 'Tenants' and RowKey eq '$TenantId'" -Property RowKey, PartitionKey, customerId, displayName if ($TenantRow) { - Remove-AzDataTableEntity -Force @TenantsTable -Entity $TenantRow + Remove-CIPPAzDataTableEntity -Force @TenantsTable -Entity $TenantRow $Results.Add("$($Tenant.displayName) ($TenantId) has been deleted from CIPP") Write-LogMessage -headers $Headers -API $APIName -message "Tenant $($Tenant.displayName) ($TenantId) deleted from CIPP" -Sev 'Info' -tenant $TenantFilter } else { diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOnboardTenant.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOnboardTenant.ps1 index ce92f7230b803..78199c7cc1e65 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOnboardTenant.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ExecOnboardTenant.ps1 @@ -20,7 +20,7 @@ function Invoke-ExecOnboardTenant { if ($Request.Body.Cancel -eq $true) { $TenantOnboarding = Get-CIPPAzDataTableEntity @OnboardTable -Filter "RowKey eq '$SafeId'" if ($TenantOnboarding) { - Remove-AzDataTableEntity -Force @OnboardTable -Entity $TenantOnboarding + Remove-CIPPAzDataTableEntity -Force @OnboardTable -Entity $TenantOnboarding $Results = @{'Results' = 'Onboarding job canceled' } $StatusCode = [HttpStatusCode]::OK } else { diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-EditTenant.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-EditTenant.ps1 index b98479d24bc06..ac9a6c15e7591 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-EditTenant.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-EditTenant.ps1 @@ -28,7 +28,7 @@ function Invoke-EditTenant { if (!$tenantAlias) { if ($AliasEntity) { Write-Host 'Removing alias' - Remove-AzDataTableEntity @PropertiesTable -Entity $AliasEntity + Remove-CIPPAzDataTableEntity @PropertiesTable -Entity $AliasEntity $null = Get-Tenants -TenantFilter $customerId -TriggerRefresh } } else { @@ -70,7 +70,7 @@ function Invoke-EditTenant { foreach ($Group in $CurrentGroupMemberships) { if ($StaticGroupIds -contains $Group.GroupId -and $tenantGroups.GroupId -notcontains $Group.GroupId) { $GroupName = ($StaticGroups | Where-Object { $_.RowKey -eq $Group.GroupId }).Name - Remove-AzDataTableEntity @GroupMembersTable -Entity $Group + Remove-CIPPAzDataTableEntity @GroupMembersTable -Entity $Group Write-LogMessage -headers $Headers -API $APINAME -tenant $Tenant.defaultDomainName -TenantId $Tenant.customerId -message "Removed tenant from group '$GroupName'" -Sev 'Info' } } @@ -87,7 +87,7 @@ function Invoke-EditTenant { customerId = $Tenant.customerId } Add-CIPPAzDataTableEntity @GroupMembersTable -Entity $NewEntry -Force - Remove-AzDataTableEntity @GroupMembersTable -Entity $Entry + Remove-CIPPAzDataTableEntity @GroupMembersTable -Entity $Entry } catch { Write-Host "Error migrating entry: $($_.Exception.Message)" } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-EditTenantOffboardingDefaults.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-EditTenantOffboardingDefaults.ps1 index 35f90390af225..aebd8f12364f4 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-EditTenantOffboardingDefaults.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-EditTenantOffboardingDefaults.ps1 @@ -53,7 +53,7 @@ function Invoke-EditTenantOffboardingDefaults { $toRemove = $existingDefaults | Where-Object { $_.PartitionKey -in $partitionKeys } if ($toRemove) { foreach ($Entity in $toRemove) { - Remove-AzDataTableEntity @PropertiesTable -Entity $Entity + Remove-CIPPAzDataTableEntity @PropertiesTable -Entity $Entity } Write-LogMessage -headers $Headers -tenant $customerId -API $APIName -message "Removed tenant offboarding defaults for partition keys: $($partitionKeys -join ', ')" -Sev 'Info' } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-RemoveTenantCapabilitiesCache.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-RemoveTenantCapabilitiesCache.ps1 index 8c0d5174553cf..52db5565027ea 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-RemoveTenantCapabilitiesCache.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-RemoveTenantCapabilitiesCache.ps1 @@ -34,7 +34,7 @@ function Invoke-RemoveTenantCapabilitiesCache { if ($CacheEntry) { # Remove the cache entry - Remove-AzDataTableEntity -Force @Table -Entity $CacheEntry + Remove-CIPPAzDataTableEntity -Force @Table -Entity $CacheEntry Write-LogMessage -Headers $Headers -API $APIName -message "Removed capabilities cache for tenant $DefaultDomainName." -Sev 'Info' $body = [pscustomobject]@{'Results' = "Successfully removed capabilities cache for tenant $DefaultDomainName" } $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-RemoveCATemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-RemoveCATemplate.ps1 index 8105dd4b29897..b16db984f01f0 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-RemoveCATemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-RemoveCATemplate.ps1 @@ -19,7 +19,7 @@ function Invoke-RemoveCATemplate { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type String $Filter = "PartitionKey eq 'CATemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed Conditional Access Template with ID $ID" Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecDeleteGDAPRoleMapping.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecDeleteGDAPRoleMapping.ps1 index 1c94da4480f21..2d061f3bdcba9 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecDeleteGDAPRoleMapping.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecDeleteGDAPRoleMapping.ps1 @@ -18,7 +18,7 @@ Function Invoke-ExecDeleteGDAPRoleMapping { try { $Filter = "PartitionKey eq 'Roles' and RowKey eq '{0}'" -f $GroupId $Entity = Get-CIPPAzDataTableEntity @Table -Filter $Filter - Remove-AzDataTableEntity -Force @Table -Entity $Entity + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Entity $Results = [pscustomobject]@{'Results' = 'Success. GDAP relationship mapping deleted' } Write-LogMessage -headers $Headers -API $APIName -message "GDAP relationship mapping deleted for $($GroupId)" -Sev 'Info' diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 index d5472f996b01f..865ca6e6921ff 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 @@ -145,7 +145,7 @@ function Invoke-ExecGDAPInvite { 'Delete' { $Invite = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'invite' and RowKey eq '$InviteId'" if ($Invite) { - Remove-AzDataTableEntity @Table -Entity $Invite + Remove-CIPPAzDataTableEntity @Table -Entity $Invite $Message = 'Invite deleted' } else { $Message = 'Invite not found' diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPRoleTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPRoleTemplate.ps1 index 8a9560bd2ccc5..abb2b84eabdb7 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPRoleTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPRoleTemplate.ps1 @@ -53,7 +53,7 @@ function Invoke-ExecGDAPRoleTemplate { # If the template ID is being changed, delete the old one and create a new one if ($OriginalRowKey -ne $NewRowKey) { - Remove-AzDataTableEntity -Force @Table -Entity $Template + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Template Add-CIPPGDAPRoleTemplate -TemplateId $NewRowKey -RoleMappings $RoleMappings -Overwrite Write-LogMessage -headers $Headers -API $APIName -message "Renamed GDAP template from '$OriginalRowKey' to '$NewRowKey' and updated role mappings" -Sev 'Info' $Body = @{ @@ -78,7 +78,7 @@ function Invoke-ExecGDAPRoleTemplate { $RowKey = $Request.Body.TemplateId $Template = $Templates | Where-Object -Property RowKey -EQ $RowKey if ($Template) { - Remove-AzDataTableEntity -Force @Table -Entity $Template + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Template Write-LogMessage -headers $Headers -API $APIName -message "Deleted GDAP role template '$RowKey'" -Sev 'Info' $Body = @{ Results = "Deleted template $RowKey" diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecShadowAISanction.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecShadowAISanction.ps1 index 8c5f84c6d1e0a..7f75cfbe0fced 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecShadowAISanction.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecShadowAISanction.ps1 @@ -40,7 +40,7 @@ function Invoke-ExecShadowAISanction { $EscapedRowKey = $RowKey -replace "'", "''" $Entity = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq '$EscapedTenant' and RowKey eq '$EscapedRowKey'" if ($Entity) { - Remove-AzDataTableEntity @Table -Entity $Entity -Force + Remove-CIPPAzDataTableEntity @Table -Entity $Entity -Force } Write-LogMessage -headers $Request.Headers -API 'ExecShadowAISanction' -tenant $TenantFilter -message "Removed company sanctioned status from AI tool '$Tool'" -Sev 'Info' "Removed company sanctioned status from '$Tool'. Its catalog risk level applies again." diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecStandardConvert.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecStandardConvert.ps1 index 1b74e88cbfb1a..e69cf05ed25e4 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecStandardConvert.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecStandardConvert.ps1 @@ -222,7 +222,7 @@ function Invoke-ExecStandardConvert { $Table = Get-CippTable -tablename 'standards' $OldStdsTableItems = Get-CIPPAzDataTableEntity @Table -Filter $Filter try { - Remove-AzDataTableEntity @Table -Entity $OldStdsTableItems -Force + Remove-CIPPAzDataTableEntity @Table -Entity $OldStdsTableItems -Force } catch { #donothing } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1 index 152e87c5515f9..4ea38cfbe1fd6 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1 @@ -62,7 +62,7 @@ function Invoke-ExecUpdateDriftDeviation { $Filter = "PartitionKey eq '$TenantFilter'" $ExistingDeviations = Get-CIPPAzDataTableEntity @Table -Filter $Filter foreach ($Deviation in $ExistingDeviations) { - Remove-AzDataTableEntity @Table -Entity $Deviation + Remove-CIPPAzDataTableEntity @Table -Entity $Deviation } $Results = @([PSCustomObject]@{ success = $true diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveBPATemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveBPATemplate.ps1 index 9551c305ae1d7..d655fd884bc6d 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveBPATemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveBPATemplate.ps1 @@ -19,7 +19,7 @@ function Invoke-RemoveBPATemplate { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type String $Filter = "PartitionKey eq 'BPATemplate' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow $Result = "Removed BPA Template with ID $ID" Write-LogMessage -Headers $Headers -API $APINAME -message $Result -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveStandard.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveStandard.ps1 index 6581376261f6f..b3a6ca42f2ab7 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveStandard.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveStandard.ps1 @@ -19,7 +19,7 @@ Function Invoke-RemoveStandard { $SafeID = ConvertTo-CIPPODataFilterValue -Value $ID -Type String $Filter = "PartitionKey eq 'standards' and RowKey eq '$SafeID'" $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @Table -Entity $ClearRow + Remove-CIPPAzDataTableEntity -Force @Table -Entity $ClearRow Write-LogMessage -Headers $Headers -API $APIName -message "Removed standards for $ID." -Sev 'Info' $body = [pscustomobject]@{'Results' = 'Successfully removed standards deployment' } $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveStandardTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveStandardTemplate.ps1 index 8302317f89a53..2adff9cbefb48 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveStandardTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveStandardTemplate.ps1 @@ -25,14 +25,14 @@ function Invoke-RemoveStandardTemplate { $TemplateName = '' } $Entities = Get-AzDataTableEntity @Table -Filter $Filter - Remove-AzDataTableEntity -Force @Table -Entity $Entities + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Entities # Remove any drift remediation scheduled tasks associated with this template $ScheduledTasksTable = Get-CIPPTable -TableName 'ScheduledTasks' $SafeTag = ConvertTo-CIPPODataFilterValue -Value "DriftRemediation_$SafeID" $DriftTasks = Get-CIPPAzDataTableEntity @ScheduledTasksTable -Filter "PartitionKey eq 'ScheduledTask' and Tag eq '$SafeTag'" foreach ($DriftTask in $DriftTasks) { - Remove-AzDataTableEntity -Force @ScheduledTasksTable -Entity $DriftTask + Remove-CIPPAzDataTableEntity -Force @ScheduledTasksTable -Entity $DriftTask Write-LogMessage -Headers $Headers -API $APIName -message "Removed drift remediation scheduled task: $($DriftTask.Name)" -Sev Info } $StandardsReportsTable = Get-CIPPTable -TableName 'CippStandardsReports' @@ -44,7 +44,7 @@ function Invoke-RemoveStandardTemplate { foreach ($Row in $Rows) { $OrphanedReports.Add($Row) } } if ($OrphanedReports.Count -gt 0) { - Remove-AzDataTableEntity -Force @StandardsReportsTable -Entity @($OrphanedReports) + Remove-CIPPAzDataTableEntity -Force @StandardsReportsTable -Entity @($OrphanedReports) Write-LogMessage -Headers $Headers -API $APIName -message "Removed $($OrphanedReports.Count) orphaned standards comparison row(s) for template id: $($ID)" -Sev Info } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 index 1f0b6019c30f7..4a8eee7d65ea9 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 @@ -88,7 +88,7 @@ function Invoke-ExecGraphExplorerPreset { $Entity = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$Id'" if ($Entity.Owner -eq $Username ) { if ($Action -eq 'Delete') { - Remove-AzDataTableEntity -Force @Table -Entity $Entity + Remove-CIPPAzDataTableEntity -Force @Table -Entity $Entity } elseif ($Action -eq 'Save') { Add-CIPPAzDataTableEntity @Table -Entity $Preset -Force } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Custom-Scripts/Invoke-AddCustomScript.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Custom-Scripts/Invoke-AddCustomScript.ps1 index ce30603163f64..292dc54aec9ba 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Custom-Scripts/Invoke-AddCustomScript.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Custom-Scripts/Invoke-AddCustomScript.ps1 @@ -1,7 +1,7 @@ function Invoke-AddCustomScript { <# .FUNCTIONALITY - Entrypoint + Entrypoint, AnyTenant .ROLE CIPP.Tests.ReadWrite #> @@ -94,7 +94,7 @@ function Invoke-AddCustomScript { $NewerVersions = $ExistingScripts | Where-Object { $_.Version -gt $RestoreToVersion } foreach ($script in $NewerVersions) { - Remove-AzDataTableEntity @Table -Entity $script + Remove-CIPPAzDataTableEntity @Table -Entity $script } Write-LogMessage -API $APIName -headers $Headers -message "Restored custom script: $($TargetScript.ScriptName) to version $RestoreToVersion (Deleted $($NewerVersions.Count) newer version(s))" -sev 'Info' diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Custom-Scripts/Invoke-ListCustomScripts.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Custom-Scripts/Invoke-ListCustomScripts.ps1 index feb77b8077d41..ef80a2a72fc21 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Custom-Scripts/Invoke-ListCustomScripts.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Custom-Scripts/Invoke-ListCustomScripts.ps1 @@ -1,7 +1,7 @@ function Invoke-ListCustomScripts { <# .FUNCTIONALITY - Entrypoint + Entrypoint, AnyTenant .ROLE CIPP.Tests.Read .DESCRIPTION diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Custom-Scripts/Invoke-RemoveCustomScript.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Custom-Scripts/Invoke-RemoveCustomScript.ps1 index ee289833074a8..c64b1b10d3104 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Custom-Scripts/Invoke-RemoveCustomScript.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Custom-Scripts/Invoke-RemoveCustomScript.ps1 @@ -33,7 +33,7 @@ function Invoke-RemoveCustomScript { # Delete all versions foreach ($script in $Scripts) { - Remove-AzDataTableEntity @Table -Entity $script + Remove-CIPPAzDataTableEntity @Table -Entity $script } # Delete matching test result rows for this custom script across tenants @@ -42,7 +42,7 @@ function Invoke-RemoveCustomScript { $TestResultsFilter = "RowKey eq '{0}'" -f $CustomTestId $RelatedTestResults = @(Get-CIPPAzDataTableEntity @TestResultsTable -Filter $TestResultsFilter) foreach ($ResultRow in $RelatedTestResults) { - Remove-AzDataTableEntity @TestResultsTable -Entity $ResultRow + Remove-CIPPAzDataTableEntity @TestResultsTable -Entity $ResultRow } # Remove this custom test from any custom report templates that include it diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/GitHub/Invoke-ExecCommunityRepo.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/GitHub/Invoke-ExecCommunityRepo.ps1 index 3ff65c2cab27d..04d814532d6f6 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/GitHub/Invoke-ExecCommunityRepo.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/GitHub/Invoke-ExecCommunityRepo.ps1 @@ -97,7 +97,7 @@ function Invoke-ExecCommunityRepo { 'Delete' { if ($RepoEntity) { $Delete = $RepoEntity | Select-Object PartitionKey, RowKey, ETag - Remove-AzDataTableEntity @Table -Entity $Delete + Remove-CIPPAzDataTableEntity @Table -Entity $Delete } $Results = @{ resultText = "Repository $($RepoEntity.Name) deleted" diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Invoke-ExecGenerateReportBuilderReport.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Invoke-ExecGenerateReportBuilderReport.ps1 index 3dcc82865ead5..6e076663583e7 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Invoke-ExecGenerateReportBuilderReport.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Invoke-ExecGenerateReportBuilderReport.ps1 @@ -22,7 +22,7 @@ function Invoke-ExecGenerateReportBuilderReport { $ReportTable = Get-CippTable -tablename 'ReportBuilderReports' $ExistingEntity = Get-CIPPAzDataTableEntity @ReportTable -Filter "RowKey eq '$($Body.ReportGUID)'" if ($ExistingEntity) { - Remove-AzDataTableEntity @ReportTable -Entity $ExistingEntity + Remove-CIPPAzDataTableEntity @ReportTable -Entity $ExistingEntity Write-LogMessage -headers $Headers -API $APIName -message "Deleted generated report '$($Body.ReportGUID)'" -Sev 'Info' $Result = @{ Results = 'Successfully deleted generated report' } } else { diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Invoke-ExecReportBuilderTemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Invoke-ExecReportBuilderTemplate.ps1 index 58896ff8b5a60..751f3ce846da2 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Invoke-ExecReportBuilderTemplate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tools/Invoke-ExecReportBuilderTemplate.ps1 @@ -55,7 +55,7 @@ function Invoke-ExecReportBuilderTemplate { $ExistingEntity = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'ReportBuilderTemplate' and RowKey eq '$($Body.GUID)'" if ($ExistingEntity) { - Remove-AzDataTableEntity @Table -Entity $ExistingEntity + Remove-CIPPAzDataTableEntity @Table -Entity $ExistingEntity Write-LogMessage -headers $Headers -API $APIName -message "Deleted report builder template '$($Body.GUID)'" -Sev 'Info' $Result = @{ Results = 'Successfully deleted report builder template' } } else { diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 index fa01079680b95..f678f9554c17b 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 @@ -73,7 +73,6 @@ function Invoke-CIPPStandardIntuneTemplate { $displayname = $Template.Displayname $description = $Template.Description - $RawJSON = $rawJsonFromTemplate $TemplateType = $Template.Type # Fallback: infer type from RAWJson content when stored template has no Type @@ -87,27 +86,65 @@ function Invoke-CIPPStandardIntuneTemplate { } } + # Resolve tenant variables before anything reads the payload. Deployment replaces them first and + # derives the policy name from the result, so looking up the unreplaced text searches for a name + # that only ever existed in the template. + $RawJSON = Get-CIPPTextReplacement -Text $rawJsonFromTemplate -TenantFilter $Tenant -EscapeForJson + + # Catalog and the Windows update profile types are deployed under the name in their payload + # rather than the template's Displayname, so find them under the name they were created with. + $PolicyName = Get-CIPPIntunePolicyName -TemplateType $TemplateType -RawJSON $RawJSON -DisplayName $displayname + $AssignmentsMatch = $null try { - $ExistingPolicy = Get-CIPPIntunePolicy -tenantFilter $Tenant -DisplayName $displayname -TemplateType $TemplateType - if ($ExistingPolicy -and $Settings.verifyAssignments -eq $true) { + $ExistingPolicy = Get-CIPPIntunePolicy -tenantFilter $Tenant -DisplayName $PolicyName -TemplateType $TemplateType -APIName 'IntuneTemplate' + } catch { + # Graph failing is not the same thing as the policy being absent. Recording it as absent + # writes a non-compliant result that survives every later run until the standard next + # succeeds, and tells remediation to create a policy that is probably already there. Keep + # the previous result and surface the real error instead. + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not read Intune policy '$PolicyName' ($TemplateType) while checking template '$displayname'. Keeping the previous compliance result. Error: $($_.Exception.Message)" -sev 'Error' + return $true + } + + if ($ExistingPolicy -and $Settings.verifyAssignments -eq $true) { + try { Write-Information "Verifying assignments for tenant $Tenant" $ExistingAssignments = Get-CIPPIntunePolicyAssignments -PolicyId $ExistingPolicy.id -TemplateType $TemplateType -TenantFilter $Tenant -ExistingPolicy $ExistingPolicy $AssignmentsMatch = Compare-CIPPIntuneAssignments -ExistingAssignments $ExistingAssignments -ExpectedAssignTo $Settings.AssignTo -ExpectedCustomGroup $Settings.customGroup -ExpectedExcludeGroup $Settings.excludeGroup -ExpectedAssignmentFilter $Settings.assignmentFilter -ExpectedAssignmentFilterType $Settings.assignmentFilterType -TenantFilter $Tenant Write-Information "AssignmentsMatch for tenant $($Tenant): $AssignmentsMatch" + } catch { + # The policy itself read back fine, so still report on its configuration rather than + # discarding the whole check because the assignment lookup failed. + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not verify assignments for Intune policy '$PolicyName'. Error: $($_.Exception.Message)" -sev 'Error' + $AssignmentsMatch = $null } - } catch { - $ExistingPolicy = $null } - Write-Information "[IntuneTemplate][$Tenant] GetPolicy '$displayname' ($TemplateType): $([int]($sw.Elapsed - $lap).TotalMilliseconds)ms" + Write-Information "[IntuneTemplate][$Tenant] GetPolicy '$PolicyName' ($TemplateType): $([int]($sw.Elapsed - $lap).TotalMilliseconds)ms" $lap = $sw.Elapsed if ($ExistingPolicy) { try { - $RawJSON = Get-CIPPTextReplacement -Text $RawJSON -TenantFilter $Tenant -EscapeForJson $JSONExistingPolicy = $ExistingPolicy.cippconfiguration | ConvertFrom-Json $JSONTemplate = $RawJSON | ConvertFrom-Json + + # Compare against what deployment actually sends, not what the payload was captured + # with. Remediation writes the template's Displayname and Description columns over the + # payload for most types, so a renamed or re-described template otherwise shows a + # difference on the very fields remediation has already brought into line. + $JSONTemplate = Merge-CIPPIntuneTemplateIdentity -Policy $JSONTemplate -TemplateType $TemplateType -DisplayName $displayname -Description $description + + if ($TemplateType -eq 'Catalog') { + try { + $JSONTemplate = Select-CIPPIntuneAvailableSetting -Policy $JSONTemplate -TenantFilter $Tenant + } catch { + # Fall back to the full template. Over-reporting drift is recoverable; silently + # dropping settings from the baseline would hide real drift. + Write-Information "[IntuneTemplate][$Tenant] Could not resolve available settings for '$PolicyName', comparing against the full template: $($_.Exception.Message)" + } + } + $Compare = Compare-CIPPIntuneObject -ReferenceObject $JSONTemplate -DifferenceObject $JSONExistingPolicy -compareType $TemplateType -ErrorAction SilentlyContinue } catch { Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to compare Intune Template $displayname against the existing policy: $($_.Exception.Message)" -sev 'Error' @@ -119,9 +156,12 @@ function Invoke-CIPPStandardIntuneTemplate { Write-Information "[IntuneTemplate][$Tenant] Compare '$displayname': $([int]($sw.Elapsed - $lap).TotalMilliseconds)ms" $lap = $sw.Elapsed } else { + # Name the policy that was searched for. When a template is deployed under a different name + # than it is looked up under, "does not exist" on its own sends people hunting for a policy + # that is sitting in the tenant under the name in this message. $compare = [pscustomobject]@{ MatchFailed = $true - Difference = 'This policy does not exist in Intune.' + Difference = "No policy named '$PolicyName' exists in Intune." } } $CompareResult = [PSCustomObject]@{ diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO101.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO101.md index 47d0249a06897..2e42e618656a9 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO101.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO101.md @@ -14,7 +14,7 @@ Set-MalwareFilterPolicy -Identity "Default" -EnableFileFilter $true ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.10.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo101v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.10.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo101v1) - [Configure anti-malware policies](https://learn.microsoft.com/microsoft-365/security/office-365-security/anti-malware-protection-configure) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO102.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO102.md index 438d6636be83b..6e7965ddee11f 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO102.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO102.md @@ -16,7 +16,7 @@ Set-MalwareFilterPolicy -Identity "Default" -Action DeleteMessage ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.10.2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo102v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.10.2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo102v1) - [Configure anti-malware policies](https://learn.microsoft.com/microsoft-365/security/office-365-security/anti-malware-protection-configure) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO103.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO103.md index 17f11b64670f1..eb568d55459d0 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO103.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO103.md @@ -14,7 +14,7 @@ Set-MalwareFilterPolicy -Identity "Default" -ZapEnabled $true ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.10.3](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo103v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.10.3](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo103v1) - [Zero-hour auto purge (ZAP) in Microsoft Defender for Office 365](https://learn.microsoft.com/microsoft-365/security/office-365-security/zero-hour-auto-purge) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO11.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO11.md index be33a98245e38..4a0cfaa5b0959 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO11.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO11.md @@ -15,7 +15,7 @@ Get-RemoteDomain | Set-RemoteDomain -AutoForwardEnabled $false ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.1.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo11v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.1.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo11v1) - [Configure remote domain settings](https://learn.microsoft.com/exchange/mail-flow-best-practices/remote-domains/remote-domains) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO111.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO111.md index c4979f85630ec..e913ebc01f049 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO111.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO111.md @@ -15,7 +15,7 @@ Enable-ATPProtectionPolicyRule -Identity "Standard Preset Security Policy" ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.11.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo111v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.11.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo111v1) - [Preset security policies](https://learn.microsoft.com/microsoft-365/security/office-365-security/preset-security-policies) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO112.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO112.md index 6ef65e55b9348..d6913db3dc923 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO112.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO112.md @@ -19,7 +19,7 @@ Set-AntiPhishPolicy -Identity "Standard Preset Security Policy" ` ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.11.2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo112v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.11.2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo112v1) - [Safety tips in email messages](https://learn.microsoft.com/microsoft-365/security/office-365-security/anti-phishing-protection-about#safety-tips-in-email-messages) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO113.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO113.md index 0a5894c4c237d..8a1a52662a572 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO113.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO113.md @@ -17,7 +17,7 @@ Set-AntiPhishPolicy -Identity "Standard Preset Security Policy" ` ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.11.3](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo113v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.11.3](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo113v1) - [Mailbox intelligence in anti-phishing policies](https://learn.microsoft.com/microsoft-365/security/office-365-security/anti-phishing-policies-about#impersonation-settings-in-anti-phishing-policies-in-microsoft-defender-for-office-365) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO121.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO121.md index 4af85199efc50..0c5fce81e8906 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO121.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO121.md @@ -16,7 +16,7 @@ Remove-TenantAllowBlockListItems -ListType Sender -Ids ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.12.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo121v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.12.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo121v1) - [Manage the Tenant Allow/Block List](https://learn.microsoft.com/microsoft-365/security/office-365-security/tenant-allow-block-list-about) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO122.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO122.md index 32352dc39224a..ef227ba26c09c 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO122.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO122.md @@ -15,7 +15,7 @@ Set-HostedContentFilterPolicy -Identity "Default" -EnableSafeList $false ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.12.2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo122v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.12.2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo122v1) - [Configure anti-spam policies](https://learn.microsoft.com/microsoft-365/security/office-365-security/anti-spam-policies-configure) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO131.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO131.md index 26ea16f7bbb2d..26ca77df998af 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO131.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO131.md @@ -12,7 +12,7 @@ Set-OrganizationConfig -AuditDisabled $false ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.13.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo131v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.13.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo131v1) - [Manage mailbox auditing](https://learn.microsoft.com/purview/audit-mailboxes) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO141.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO141.md index 8b7ff1d730252..960f78135b274 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO141.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO141.md @@ -14,7 +14,7 @@ Set-HostedContentFilterPolicy -Identity "Default" -HighConfidenceSpamAction Quar ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.14.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo141v2) +- [CISA ScubaGear EXO Baseline - MS.EXO.14.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo141v2) - [Configure anti-spam policies](https://learn.microsoft.com/microsoft-365/security/office-365-security/anti-spam-policies-configure) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO142.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO142.md index ec3b2ce5e871d..59a577f4dbb90 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO142.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO142.md @@ -16,7 +16,7 @@ Set-HostedContentFilterPolicy -Identity "Default" -SpamAction Quarantine ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.14.2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo142v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.14.2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo142v1) - [Configure anti-spam policies](https://learn.microsoft.com/microsoft-365/security/office-365-security/anti-spam-policies-configure) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO143.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO143.md index 4958c0a1627e0..5136d4f22ffe9 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO143.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO143.md @@ -15,7 +15,7 @@ Set-HostedContentFilterPolicy -Identity "Default" -AllowedSenders @() -AllowedSe ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.14.3](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo143v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.14.3](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo143v1) - [Configure allowed and blocked senders](https://learn.microsoft.com/microsoft-365/security/office-365-security/create-safe-sender-lists-in-office-365) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO151.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO151.md index 5fbbde6b6bca3..1f7ef6d4a94aa 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO151.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO151.md @@ -14,7 +14,7 @@ Set-SafeLinksPolicy -Identity "Default" -EnableSafeLinksForEmail $true ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.15.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo151v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.15.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo151v1) - [Set up Safe Links policies](https://learn.microsoft.com/microsoft-365/security/office-365-security/safe-links-policies-configure) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO152.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO152.md index db04d60a9e240..8365dc32fff81 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO152.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO152.md @@ -15,7 +15,7 @@ Set-SafeLinksPolicy -Identity "Default" -ScanUrls $true ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.15.2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo152v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.15.2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo152v1) - [Set up Safe Links policies](https://learn.microsoft.com/microsoft-365/security/office-365-security/safe-links-policies-configure) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO153.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO153.md index f8c99f40da390..4f5e28359d59e 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO153.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO153.md @@ -14,7 +14,7 @@ Set-SafeLinksPolicy -Identity "Default" -TrackUserClicks $false ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.15.3](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo153v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.15.3](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo153v1) - [Set up Safe Links policies](https://learn.microsoft.com/microsoft-365/security/office-365-security/safe-links-policies-configure) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO171.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO171.md index 677b8a8372483..84fc4a4b350bb 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO171.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO171.md @@ -12,7 +12,7 @@ Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.17.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo171v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.17.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo171v1) - [Turn audit log search on or off](https://learn.microsoft.com/purview/audit-log-enable-disable) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO173.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO173.md index 32dcbc6e2f79b..ad92e0823119e 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO173.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO173.md @@ -15,7 +15,7 @@ Set-AdminAuditLogConfig -AdminAuditLogEnabled $true ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.17.3](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo173v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.17.3](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo173v1) - [Audit log retention policies](https://learn.microsoft.com/purview/audit-log-retention-policies) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO31.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO31.md index 4cb5dc494993d..8c391cfccf2a7 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO31.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO31.md @@ -20,7 +20,7 @@ Set-DkimSigningConfig -Identity "contoso.com" -Enabled $true ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.3.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo31v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.3.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo31v1) - [Use DKIM to validate outbound email](https://learn.microsoft.com/microsoft-365/security/office-365-security/email-authentication-dkim-configure) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO51.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO51.md index 4df1e83a5c569..1af71e9bf32a3 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO51.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO51.md @@ -16,7 +16,7 @@ Set-CASMailbox -Identity user@domain.com -SmtpClientAuthenticationDisabled $true ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.5.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo51v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.5.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo51v1) - [Disable SMTP AUTH](https://learn.microsoft.com/exchange/clients-and-mobile-in-exchange-online/authenticated-client-smtp-submission) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO61.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO61.md index 94c2efba55231..2d6f3f0502901 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO61.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO61.md @@ -13,7 +13,7 @@ Set-SharingPolicy -Identity "Default Sharing Policy" -Domains @{Remove="*:Contac ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.6.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo61v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.6.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo61v1) - [Sharing policies in Exchange Online](https://learn.microsoft.com/exchange/sharing/sharing-policies/sharing-policies) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO62.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO62.md index 22e7bfad43107..48ab67354f2a0 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO62.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO62.md @@ -17,7 +17,7 @@ Set-SharingPolicy -Identity "Default Sharing Policy" -Domains @{Add="partner.com ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.6.2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo62v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.6.2](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo62v1) - [Sharing policies in Exchange Online](https://learn.microsoft.com/exchange/sharing/sharing-policies/sharing-policies) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO71.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO71.md index 1bda7cbc9a3d4..d97b50b541663 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO71.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO71.md @@ -13,7 +13,7 @@ Set-ExternalInOutlook -Enabled $true ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.7.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo71v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.7.1](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo71v1) - [External sender warnings](https://learn.microsoft.com/microsoft-365/security/office-365-security/external-email-forwarding) diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO95.md b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO95.md index 1b8d550197e91..cad20ad71995c 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO95.md +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO95.md @@ -15,7 +15,7 @@ Set-MalwareFilterPolicy -Identity "Default" -EnableFileFilter $true -FileTypes @ ``` **Links:** -- [CISA SCubaGear EXO Baseline - MS.EXO.9.5](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo95v1) +- [CISA ScubaGear EXO Baseline - MS.EXO.9.5](https://github.com/cisagov/ScubaGear/blob/main/PowerShell/ScubaGear/baselines/exo.md#msexo95v1) - [Configure anti-malware policies](https://learn.microsoft.com/microsoft-365/security/office-365-security/anti-malware-protection-configure) diff --git a/Modules/CIPPTests/Public/Tests/CISA/report.json b/Modules/CIPPTests/Public/Tests/CISA/report.json index 75ac2ab1bb699..b0eb43d2b5b3e 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/report.json +++ b/Modules/CIPPTests/Public/Tests/CISA/report.json @@ -1,6 +1,6 @@ { - "name": "CISA SCubaGear Tests for Exchange Online", - "description": "Security configuration assessment tests based on CISA's Secure Cloud Business Applications (SCubaGear) project for Microsoft Exchange Online. These tests validate compliance with federal security baselines.", + "name": "CISA ScubaGear Tests for Exchange Online", + "description": "Security configuration assessment tests based on CISA's Secure Cloud Business Applications (ScubaGear) project for Microsoft Exchange Online. These tests validate compliance with federal security baselines.", "version": "1.0", "source": "https://github.com/cisagov/ScubaGear", "category": "CISA Security Baselines", diff --git a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 index 247088aaed815..e514464ceac84 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Register-CippExtensionScheduledTasks.ps1 @@ -22,7 +22,7 @@ function Register-CIPPExtensionScheduledTasks { foreach ($Task in $ScheduledTasks) { Write-Information "Removing legacy task: $($Task.Name) for tenant $($Task.Tenant)" $Entity = $Task | Select-Object -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @ScheduledTasksTable -Entity $Entity + Remove-CIPPAzDataTableEntity -Force @ScheduledTasksTable -Entity $Entity } $ScheduledTasks = @() # Clear the list since we removed them all @@ -143,13 +143,13 @@ function Register-CIPPExtensionScheduledTasks { $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 -Force @ScheduledTasksTable -Entity $Entity + Remove-CIPPAzDataTableEntity -Force @ScheduledTasksTable -Entity $Entity } if ($Extension -eq 'Sherweb') { $SherwebMigTasks | ForEach-Object { Write-Information "Extension Disabled: Cleaning up scheduled task $($_.Name) for tenant $($_.Tenant)" $Entity = $_ | Select-Object -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @ScheduledTasksTable -Entity $Entity + Remove-CIPPAzDataTableEntity -Force @ScheduledTasksTable -Entity $Entity } $SherwebMigTasks = @() # Clear the list since we removed them all } @@ -161,21 +161,21 @@ function Register-CIPPExtensionScheduledTasks { 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 -Force @ScheduledTasksTable -Entity $Entity + Remove-CIPPAzDataTableEntity -Force @ScheduledTasksTable -Entity $Entity } } foreach ($Task in $PushTasks) { if ($Task.Tenant -notin $MappedTenants) { Write-Information "Tenant Removed: Cleaning up scheduled task $($Task.Name) for tenant $($Task.TenantFilter)" $Entity = $Task | Select-Object -Property PartitionKey, RowKey - Remove-AzDataTableEntity -Force @ScheduledTasksTable -Entity $Entity + Remove-CIPPAzDataTableEntity -Force @ScheduledTasksTable -Entity $Entity } } foreach ($Task in $SherwebMigTasks) { 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 -Force @ScheduledTasksTable -Entity $Entity + Remove-CIPPAzDataTableEntity -Force @ScheduledTasksTable -Entity $Entity } } } diff --git a/Modules/CippExtensions/Public/Extension Functions/Remove-ExtensionAPIKey.ps1 b/Modules/CippExtensions/Public/Extension Functions/Remove-ExtensionAPIKey.ps1 index 804dd8e69994d..1dfb46fcb6d0b 100644 --- a/Modules/CippExtensions/Public/Extension Functions/Remove-ExtensionAPIKey.ps1 +++ b/Modules/CippExtensions/Public/Extension Functions/Remove-ExtensionAPIKey.ps1 @@ -13,7 +13,7 @@ function Remove-ExtensionAPIKey { $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' $DevSecretRows = Get-AzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq '$Extension'" if ($DevSecretRows) { - Remove-AzDataTableEntity @DevSecretsTable -Entity @($DevSecretRows) -Force -ErrorAction Stop + Remove-CIPPAzDataTableEntity @DevSecretsTable -Entity @($DevSecretRows) -Force -ErrorAction Stop Write-Information "Deleted $(@($DevSecretRows).Count) DevSecrets row(s) for '$Extension'." } else { Write-Information "No existing DevSecrets row found for '$Extension' to delete." diff --git a/Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1 b/Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1 index 8f1b6d982f746..2bd9fe338efe2 100644 --- a/Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1 +++ b/Modules/CippExtensions/Public/Halo/Get-HaloMapping.ps1 @@ -15,7 +15,7 @@ function Get-HaloMapping { IntegrationId = $_.HaloPSA IntegrationName = $_.HaloPSAName } - Remove-AzDataTableEntity -Force @CIPPMapping -Entity $_ | Out-Null + Remove-CIPPAzDataTableEntity -Force @CIPPMapping -Entity $_ | Out-Null } if (($MigrateRows | Measure-Object).Count -gt 0) { Add-CIPPAzDataTableEntity @CIPPMapping -Entity $MigrateRows -Force diff --git a/Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1 b/Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1 index 48d75df2af63a..b1f0390096785 100644 --- a/Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1 +++ b/Modules/CippExtensions/Public/Halo/Set-HaloMapping.ps1 @@ -6,7 +6,7 @@ function Set-HaloMapping { $Request ) Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'HaloMapping'" | ForEach-Object { - Remove-AzDataTableEntity -Force @CIPPMapping -Entity $_ + Remove-CIPPAzDataTableEntity -Force @CIPPMapping -Entity $_ } foreach ($Mapping in $Request.Body) { if ($Mapping.TenantId) { diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index c0ad986868841..9e56257c549d3 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -157,7 +157,7 @@ function Invoke-HuduExtensionSync { $ExistingRelationRows = Get-CIPPAzDataTableEntity @HuduRelationsCache -Filter "PartitionKey eq 'HuduRelation'" if ($ExistingRelationRows) { - Remove-AzDataTableEntity @HuduRelationsCache -Entity $ExistingRelationRows -Force + Remove-CIPPAzDataTableEntity @HuduRelationsCache -Entity $ExistingRelationRows -Force } $RelationEntities = foreach ($Relation in $HuduRelations) { diff --git a/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 b/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 index 667ce95ad8695..07a8516aaf8dc 100644 --- a/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Set-HuduMapping.ps1 @@ -6,7 +6,7 @@ function Set-HuduMapping { $Request ) Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'HuduMapping'" | ForEach-Object { - Remove-AzDataTableEntity -Force @CIPPMapping -Entity $_ + Remove-CIPPAzDataTableEntity -Force @CIPPMapping -Entity $_ } foreach ($Mapping in $Request.Body) { $AddObject = @{ diff --git a/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 index c82202da52009..464b7d790407b 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Get-NinjaOneFieldMapping.ps1 @@ -73,7 +73,7 @@ function Get-NinjaOneFieldMapping { IntegrationId = $_.NinjaOne IntegrationName = $_.NinjaOneName } - Remove-AzDataTableEntity -Force @CIPPMapping -Entity $_ + Remove-CIPPAzDataTableEntity -Force @CIPPMapping -Entity $_ } if (($MappingFieldMigrate | Measure-Object).count -gt 0) { Add-CIPPAzDataTableEntity @CIPPMapping -Entity $MappingFieldMigrate -Force diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 index e415d4cf580c1..ab3d64cecf705 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 @@ -749,13 +749,13 @@ function Invoke-NinjaOneTenantSync { [System.Collections.Generic.List[PSCustomObject]]$StaleParsedUsers = Get-CIPPAzDataTableEntity @UsersTable -Filter $UsersFilter if (($StaleParsedUsers | Measure-Object).count -gt 0) { - Remove-AzDataTableEntity -Force @UsersTable -Entity ($StaleParsedUsers | Select-Object PartitionKey, RowKey) + Remove-CIPPAzDataTableEntity -Force @UsersTable -Entity ($StaleParsedUsers | Select-Object PartitionKey, RowKey) } [System.Collections.Generic.List[PSCustomObject]]$ParsedUsers = @() [System.Collections.Generic.List[PSCustomObject]]$StaleUserUpdates = Get-CIPPAzDataTableEntity @UsersUpdateTable -Filter $UsersFilter if (($StaleUserUpdates | Measure-Object).count -gt 0) { - Remove-AzDataTableEntity -Force @UsersUpdateTable -Entity ($StaleUserUpdates | Select-Object PartitionKey, RowKey) + Remove-CIPPAzDataTableEntity -Force @UsersUpdateTable -Entity ($StaleUserUpdates | Select-Object PartitionKey, RowKey) } [System.Collections.Generic.List[PSCustomObject]]$UsersMap = Get-CIPPAzDataTableEntity @UsersMapTable -Filter $UsersFilter @@ -1210,7 +1210,7 @@ function Invoke-NinjaOneTenantSync { if (($NinjaUserCreation | Measure-Object).count -ge 100) { Write-Information '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 -Force @UsersUpdateTable -Entity $NinjaUserCreation + Remove-CIPPAzDataTableEntity -Force @UsersUpdateTable -Entity $NinjaUserCreation [System.Collections.Generic.List[PSCustomObject]]$NinjaUserCreation = @() } } catch { @@ -1223,7 +1223,7 @@ function Invoke-NinjaOneTenantSync { if (($NinjaUserUpdates | Measure-Object).count -ge 100) { Write-Information 'Updating NinjaOne Users' [System.Collections.Generic.List[PSCustomObject]]$UpdatedUsers = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserUpdates.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 - Remove-AzDataTableEntity -Force @UsersUpdateTable -Entity $NinjaUserUpdates + Remove-CIPPAzDataTableEntity -Force @UsersUpdateTable -Entity $NinjaUserUpdates [System.Collections.Generic.List[PSCustomObject]]$NinjaUserUpdates = @() } } catch { @@ -1287,7 +1287,7 @@ function Invoke-NinjaOneTenantSync { if (($NinjaUserCreation | Measure-Object).count -ge 1) { Write-Information '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 -Force @UsersUpdateTable -Entity $NinjaUserCreation + Remove-CIPPAzDataTableEntity -Force @UsersUpdateTable -Entity $NinjaUserCreation } } catch { @@ -1300,7 +1300,7 @@ function Invoke-NinjaOneTenantSync { if (($NinjaUserUpdates | Measure-Object).count -ge 1) { Write-Information 'Updating NinjaOne Users' [System.Collections.Generic.List[PSCustomObject]]$UpdatedUsers = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserUpdates.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 - Remove-AzDataTableEntity -Force @UsersUpdateTable -Entity $NinjaUserUpdates + Remove-CIPPAzDataTableEntity -Force @UsersUpdateTable -Entity $NinjaUserUpdates } } catch { $ErrorMessage = Get-CippException -Exception $_ @@ -2281,12 +2281,12 @@ function Invoke-NinjaOneTenantSync { Write-Information 'Cleaning Users Cache' if (($ParsedUsers | Measure-Object).count -gt 0) { - Remove-AzDataTableEntity -Force @UsersTable -Entity ($ParsedUsers | Select-Object PartitionKey, RowKey) + Remove-CIPPAzDataTableEntity -Force @UsersTable -Entity ($ParsedUsers | Select-Object PartitionKey, RowKey) } Write-Information 'Cleaning Device Cache' if (($ParsedDevices | Measure-Object).count -gt 0) { - Remove-AzDataTableEntity -Force @DeviceTable -Entity ($ParsedDevices | Select-Object PartitionKey, RowKey) + Remove-CIPPAzDataTableEntity -Force @DeviceTable -Entity ($ParsedDevices | Select-Object PartitionKey, RowKey) } Write-Information "Total Fetch Time: $((New-TimeSpan -Start $StartTime -End $FetchEnd).TotalSeconds)" diff --git a/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1 index ab2e745c8417a..07665f1029ac0 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Set-NinjaOneOrgMapping.ps1 @@ -7,7 +7,7 @@ function Set-NinjaOneOrgMapping { ) Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'NinjaOneMapping'" | ForEach-Object { - Remove-AzDataTableEntity -Force @CIPPMapping -Entity $_ + Remove-CIPPAzDataTableEntity -Force @CIPPMapping -Entity $_ } foreach ($Mapping in $Request.Body) { if ($Mapping.TenantId) { diff --git a/Modules/CippExtensions/Public/Sherweb/Set-SherwebMapping.ps1 b/Modules/CippExtensions/Public/Sherweb/Set-SherwebMapping.ps1 index 0fe51015c4b43..98397cefa53a5 100644 --- a/Modules/CippExtensions/Public/Sherweb/Set-SherwebMapping.ps1 +++ b/Modules/CippExtensions/Public/Sherweb/Set-SherwebMapping.ps1 @@ -6,7 +6,7 @@ function Set-SherwebMapping { $Request ) Get-CIPPAzDataTableEntity @CIPPMapping -Filter "PartitionKey eq 'SherwebMapping'" | ForEach-Object { - Remove-AzDataTableEntity -Force @CIPPMapping -Entity $_ + Remove-CIPPAzDataTableEntity -Force @CIPPMapping -Entity $_ } foreach ($Mapping in $Request.Body) { Write-Host "Adding mapping for $($mapping.IntegrationId)" diff --git a/Tests/Endpoint/Invoke-RemoveIntuneReusableSettingTemplate.Tests.ps1 b/Tests/Endpoint/Invoke-RemoveIntuneReusableSettingTemplate.Tests.ps1 index 0e12a26d892d2..f14a64f983f53 100644 --- a/Tests/Endpoint/Invoke-RemoveIntuneReusableSettingTemplate.Tests.ps1 +++ b/Tests/Endpoint/Invoke-RemoveIntuneReusableSettingTemplate.Tests.ps1 @@ -15,7 +15,7 @@ BeforeAll { function Get-CippTable { param($tablename) @{} } function Get-CIPPAzDataTableEntity { param($Filter, $Property) return [pscustomobject]@{ PartitionKey = 'IntuneReusableSettingTemplate'; RowKey = 'template-x' } } - function Remove-AzDataTableEntity { param([switch]$Force, $Entity) $script:lastRemoved = $Entity; $script:lastForce = $Force } + function Remove-CIPPAzDataTableEntity { param([switch]$Force, $Entity) $script:lastRemoved = $Entity; $script:lastForce = $Force } function Write-LogMessage { param($Headers, $API, $message, $sev, $LogData) $script:logs += $message } function Get-CippException { param($Exception) [pscustomobject]@{ NormalizedError = $Exception } } # The ID is sanitised for OData before the table lookup; stub it to pass the value through. diff --git a/Tests/Private/Get-CIPPIntunePolicyName.Tests.ps1 b/Tests/Private/Get-CIPPIntunePolicyName.Tests.ps1 new file mode 100644 index 0000000000000..af16c6cfff25a --- /dev/null +++ b/Tests/Private/Get-CIPPIntunePolicyName.Tests.ps1 @@ -0,0 +1,89 @@ +# Pester tests for Get-CIPPIntunePolicyName +# Covers the split between types named from the Displayname column and types named from their +# payload, which is what decides whether a deployed policy can be found again. + +BeforeAll { + $RepoRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSCommandPath)) + . (Join-Path $RepoRoot 'Modules/CIPPCore/Public/Get-CIPPIntunePolicyName.ps1') +} + +Describe 'Get-CIPPIntunePolicyName' { + + Context 'types named from the Displayname column' { + It 'returns the Displayname for even when the payload says otherwise' -ForEach @( + @{ Type = 'Device' } + @{ Type = 'deviceCompliancePolicies' } + @{ Type = 'AppProtection' } + @{ Type = 'AppConfiguration' } + @{ Type = 'Admin' } + ) { + $RawJSON = '{"displayName":"Name from payload","name":"Other name"}' + Get-CIPPIntunePolicyName -TemplateType $Type -RawJSON $RawJSON -DisplayName 'Name from column' | + Should -Be 'Name from column' + } + } + + Context 'Catalog' { + It 'uses the payload name, because that is what deployment sends' { + $RawJSON = '{"name":"Baseline - Defender","description":"x"}' + Get-CIPPIntunePolicyName -TemplateType 'Catalog' -RawJSON $RawJSON -DisplayName 'Renamed in CIPP' | + Should -Be 'Baseline - Defender' + } + + It 'ignores displayName on a Catalog payload' { + $RawJSON = '{"name":"Correct","displayName":"Wrong"}' + Get-CIPPIntunePolicyName -TemplateType 'Catalog' -RawJSON $RawJSON -DisplayName 'Column' | + Should -Be 'Correct' + } + + It 'falls back to the column when the payload has no name' { + Get-CIPPIntunePolicyName -TemplateType 'Catalog' -RawJSON '{"description":"x"}' -DisplayName 'Column' | + Should -Be 'Column' + } + + It 'resolves the replaced name when text replacement has already run' { + # Deployment replaces tenant variables before reading the name, so a lookup on the + # unreplaced payload would search for a policy that never existed. + $RawJSON = '{"name":"Contoso - Outlook configuration"}' + Get-CIPPIntunePolicyName -TemplateType 'Catalog' -RawJSON $RawJSON -DisplayName '%tenantname% - Outlook configuration' | + Should -Be 'Contoso - Outlook configuration' + } + } + + Context 'Windows update profile types' { + It 'uses the payload displayName for ' -ForEach @( + @{ Type = 'windowsDriverUpdateProfiles' } + @{ Type = 'windowsFeatureUpdateProfiles' } + @{ Type = 'windowsQualityUpdatePolicies' } + @{ Type = 'windowsQualityUpdateProfiles' } + ) { + $RawJSON = '{"displayName":"Payload name"}' + Get-CIPPIntunePolicyName -TemplateType $Type -RawJSON $RawJSON -DisplayName 'Column name' | + Should -Be 'Payload name' + } + + It 'falls back to the payload name property when displayName is absent' { + Get-CIPPIntunePolicyName -TemplateType 'windowsDriverUpdateProfiles' -RawJSON '{"name":"From name"}' -DisplayName 'Column' | + Should -Be 'From name' + } + } + + Context 'input handling' { + It 'accepts an already parsed payload' { + $Policy = [PSCustomObject]@{ name = 'Parsed policy' } + Get-CIPPIntunePolicyName -TemplateType 'Catalog' -RawJSON $Policy -DisplayName 'Column' | + Should -Be 'Parsed policy' + } + + It 'falls back to the column when the payload cannot be parsed' { + # Returning nothing here would make every lookup miss and report the policy as absent. + Get-CIPPIntunePolicyName -TemplateType 'Catalog' -RawJSON 'not json' -DisplayName 'Column' -WarningAction SilentlyContinue | + Should -Be 'Column' + } + + It 'falls back to the column when the payload name is whitespace' { + Get-CIPPIntunePolicyName -TemplateType 'Catalog' -RawJSON '{"name":" "}' -DisplayName 'Column' | + Should -Be 'Column' + } + } +} diff --git a/Tests/Private/Merge-CIPPIntuneTemplateIdentity.Tests.ps1 b/Tests/Private/Merge-CIPPIntuneTemplateIdentity.Tests.ps1 new file mode 100644 index 0000000000000..ff92fba0f730d --- /dev/null +++ b/Tests/Private/Merge-CIPPIntuneTemplateIdentity.Tests.ps1 @@ -0,0 +1,90 @@ +# Pester tests for Merge-CIPPIntuneTemplateIdentity +# Guards the case that makes a template permanently non-compliant: remediation writes the +# template's Displayname/Description columns onto the policy, so the baseline has to carry those +# same values or the comparison reports a difference remediation has already resolved. + +BeforeAll { + $RepoRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSCommandPath)) + . (Join-Path $RepoRoot 'Modules/CIPPCore/Public/Merge-CIPPIntuneTemplateIdentity.ps1') +} + +Describe 'Merge-CIPPIntuneTemplateIdentity' { + + Context 'types deployment writes the columns onto' { + It 'overwrites a stale payload description for ' -ForEach @( + @{ Type = 'Device' } + @{ Type = 'deviceCompliancePolicies' } + @{ Type = 'AppProtection' } + @{ Type = 'AppConfiguration' } + ) { + $Policy = [PSCustomObject]@{ + displayName = 'Old name' + description = 'Compliance Policy for Defender for Endpoint' + } + + $Result = Merge-CIPPIntuneTemplateIdentity -Policy $Policy -TemplateType $Type -DisplayName 'New name' -Description 'Edited in CIPP' + + $Result.displayName | Should -Be 'New name' + $Result.description | Should -Be 'Edited in CIPP' + } + + It 'adds the identity fields when the payload has none' { + $Policy = [PSCustomObject]@{ someSetting = $true } + + $Result = Merge-CIPPIntuneTemplateIdentity -Policy $Policy -TemplateType 'Device' -DisplayName 'Name' -Description 'Desc' + + $Result.displayName | Should -Be 'Name' + $Result.description | Should -Be 'Desc' + $Result.someSetting | Should -BeTrue + } + + It 'clears the description when the column is empty, mirroring deployment' { + $Policy = [PSCustomObject]@{ displayName = 'Name'; description = 'Left over' } + + $Result = Merge-CIPPIntuneTemplateIdentity -Policy $Policy -TemplateType 'Device' -DisplayName 'Name' -Description '' + + $Result.description | Should -BeNullOrEmpty + } + + It 'leaves the payload name alone when the column is empty' { + $Policy = [PSCustomObject]@{ displayName = 'Payload name' } + + $Result = Merge-CIPPIntuneTemplateIdentity -Policy $Policy -TemplateType 'Device' -DisplayName '' -Description 'Desc' + + $Result.displayName | Should -Be 'Payload name' + } + } + + Context 'types deployment sends as-is' { + It 'does not touch , which is named and described by its payload' -ForEach @( + @{ Type = 'Catalog' } + @{ Type = 'windowsDriverUpdateProfiles' } + @{ Type = 'windowsFeatureUpdateProfiles' } + @{ Type = 'windowsQualityUpdatePolicies' } + @{ Type = 'windowsQualityUpdateProfiles' } + ) { + $Policy = [PSCustomObject]@{ name = 'Payload name'; description = 'Payload description' } + + $Result = Merge-CIPPIntuneTemplateIdentity -Policy $Policy -TemplateType $Type -DisplayName 'Column' -Description 'Column description' + + $Result.name | Should -Be 'Payload name' + $Result.description | Should -Be 'Payload description' + } + + It 'leaves Admin alone, whose payload holds definition values rather than a policy' { + $Policy = [PSCustomObject]@{ added = @(); updated = @(); deletedIds = @() } + + $Result = Merge-CIPPIntuneTemplateIdentity -Policy $Policy -TemplateType 'Admin' -DisplayName 'Column' -Description 'Column description' + + $Result.PSObject.Properties.Name | Should -Not -Contain 'displayName' + $Result.PSObject.Properties.Name | Should -Not -Contain 'description' + } + } + + Context 'input handling' { + It 'returns null unchanged' { + Merge-CIPPIntuneTemplateIdentity -Policy $null -TemplateType 'Device' -DisplayName 'Name' -Description 'Desc' | + Should -BeNullOrEmpty + } + } +} diff --git a/Tests/Private/Update-CIPPSSOPreconsent.Tests.ps1 b/Tests/Private/Update-CIPPSSOPreconsent.Tests.ps1 new file mode 100644 index 0000000000000..0ac0f4fe9c5c4 --- /dev/null +++ b/Tests/Private/Update-CIPPSSOPreconsent.Tests.ps1 @@ -0,0 +1,220 @@ +# Pester tests for Update-CIPPSSOPreconsent +# Verifies the CIPP-SSO app gets a tenant-wide (AllPrincipals) Graph consent grant during warmup, +# that an already-granted app costs no Graph calls, and that every failure path is soft - warmup +# must never be broken by a tenant that refuses the grant. + +BeforeAll { + $RepoRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSCommandPath)) + $FunctionPath = Join-Path $RepoRoot 'Modules/CIPPCore/Public/Authentication/Update-CIPPSSOPreconsent.ps1' + + # Minimal stubs so Mock has commands to replace during tests + function Get-CIPPTable { param($TableName) } + function Get-CIPPAzDataTableEntity { param($Context, $Filter) } + function Add-CIPPAzDataTableEntity { param($Context, $Entity, [switch]$Force) } + function Get-CippKeyVaultName { } + function Get-CippKeyVaultSecret { param($VaultName, $Name, [switch]$AsPlainText) } + function New-GraphGetRequest { param($uri, $NoAuthCheck, $AsApp) } + function New-GraphPOSTRequest { param($uri, $body, $type, $NoAuthCheck, $AsApp) } + function Write-LogMessage { param($API, $message, $LogData, $sev) } + function Get-CippException { param($Exception) } + + . $FunctionPath +} + +Describe 'Update-CIPPSSOPreconsent' { + BeforeEach { + $script:SsoAppId = '22222222-2222-2222-2222-222222222222' + $script:SsoSpId = 'sp-sso-object-id' + $script:GraphSpId = 'sp-graph-object-id' + $script:GraphAppId = '00000003-0000-0000-c000-000000000000' + + $script:OriginalStorage = $env:AzureWebJobsStorage + $script:OriginalNonLocal = $env:NonLocalHostAzurite + $script:OriginalAuthEnabled = $env:WEBSITE_AUTH_ENABLED + + # Hosted (Key Vault) path by default + $env:AzureWebJobsStorage = 'DefaultEndpointsProtocol=https;AccountName=stub' + $env:NonLocalHostAzurite = $null + + $script:SavedEntity = $null + + Mock -CommandName Get-CIPPTable -MockWith { @{ Context = 'stub-table' } } + Mock -CommandName Get-CippKeyVaultName -MockWith { 'stub-vault' } + Mock -CommandName Get-CippKeyVaultSecret -MockWith { $script:SsoAppId } + Mock -CommandName Write-LogMessage -MockWith { } + Mock -CommandName Get-CippException -MockWith { @{ NormalizedError = 'stub' } } + Mock -CommandName Add-CIPPAzDataTableEntity -MockWith { $script:SavedEntity = $Entity } + Mock -CommandName New-GraphPOSTRequest -MockWith { } + + # No pre-existing migration row unless a test sets one + Mock -CommandName Get-CIPPAzDataTableEntity -MockWith { $null } + + Mock -CommandName New-GraphGetRequest -MockWith { + if ($uri -match [regex]::Escape("appId='$script:SsoAppId'")) { return [PSCustomObject]@{ id = $script:SsoSpId } } + if ($uri -match [regex]::Escape("appId='$script:GraphAppId'")) { return [PSCustomObject]@{ id = $script:GraphSpId } } + return @() + } + } + + AfterEach { + $env:AzureWebJobsStorage = $script:OriginalStorage + $env:NonLocalHostAzurite = $script:OriginalNonLocal + $env:WEBSITE_AUTH_ENABLED = $script:OriginalAuthEnabled + } + + Context 'When no SSO app is provisioned' { + It 'returns without touching Graph' { + Mock -CommandName Get-CippKeyVaultSecret -MockWith { $null } + + Update-CIPPSSOPreconsent + + Should -Invoke -CommandName New-GraphGetRequest -Times 0 -Exactly + Should -Invoke -CommandName New-GraphPOSTRequest -Times 0 -Exactly + } + } + + Context 'When consent has already been granted for this app' { + It 'skips Graph entirely' { + Mock -CommandName Get-CIPPAzDataTableEntity -MockWith { + [PSCustomObject]@{ Preconsented = 'true'; PreconsentedAppId = $script:SsoAppId; Status = 'complete' } + } + + Update-CIPPSSOPreconsent + + Should -Invoke -CommandName New-GraphGetRequest -Times 0 -Exactly + } + + It 'still runs when the stored grant belongs to a previous app registration' { + Mock -CommandName Get-CIPPAzDataTableEntity -MockWith { + [PSCustomObject]@{ Preconsented = 'true'; PreconsentedAppId = 'an-older-app-id'; Status = 'complete' } + } + + Update-CIPPSSOPreconsent + + Should -Invoke -CommandName New-GraphPOSTRequest -Times 1 -Exactly + } + } + + Context 'When no grant exists yet' { + It 'creates an AllPrincipals grant for the OIDC scopes' { + Update-CIPPSSOPreconsent + + Should -Invoke -CommandName New-GraphPOSTRequest -Times 1 -Exactly -ParameterFilter { + $type -eq 'POST' -and + $uri -eq 'https://graph.microsoft.com/v1.0/oauth2PermissionGrants' -and + ($body | ConvertFrom-Json).consentType -eq 'AllPrincipals' -and + ($body | ConvertFrom-Json).clientId -eq $script:SsoSpId -and + ($body | ConvertFrom-Json).resourceId -eq $script:GraphSpId -and + ($body | ConvertFrom-Json).scope -eq 'openid profile email' + } + } + + It 'records Preconsented true against the app id' { + Update-CIPPSSOPreconsent + + $script:SavedEntity.Preconsented | Should -Be 'true' + $script:SavedEntity.PreconsentedAppId | Should -Be $script:SsoAppId + } + + It 'stamps a Status on a row that has none so the settings page still reads as provisioned' { + $env:WEBSITE_AUTH_ENABLED = 'True' + + Update-CIPPSSOPreconsent + + $script:SavedEntity.Status | Should -Be 'complete' + $script:SavedEntity.AppId | Should -Be $script:SsoAppId + } + + It 'leaves an existing Status untouched' { + Mock -CommandName Get-CIPPAzDataTableEntity -MockWith { + [PSCustomObject]@{ AppId = $script:SsoAppId; Status = 'secrets_stored' } + } + + Update-CIPPSSOPreconsent + + $script:SavedEntity.Status | Should -Be 'secrets_stored' + } + } + + Context 'When a grant already exists' { + It 'does nothing when it already covers every required scope' { + Mock -CommandName New-GraphGetRequest -MockWith { + if ($uri -match [regex]::Escape("appId='$script:SsoAppId'")) { return [PSCustomObject]@{ id = $script:SsoSpId } } + if ($uri -match [regex]::Escape("appId='$script:GraphAppId'")) { return [PSCustomObject]@{ id = $script:GraphSpId } } + return @([PSCustomObject]@{ + id = 'grant-1' + resourceId = $script:GraphSpId + consentType = 'AllPrincipals' + scope = 'email openid profile User.Read' + }) + } + + Update-CIPPSSOPreconsent + + Should -Invoke -CommandName New-GraphPOSTRequest -Times 0 -Exactly + $script:SavedEntity.Preconsented | Should -Be 'true' + } + + It 'patches in missing scopes without dropping the ones already consented' { + Mock -CommandName New-GraphGetRequest -MockWith { + if ($uri -match [regex]::Escape("appId='$script:SsoAppId'")) { return [PSCustomObject]@{ id = $script:SsoSpId } } + if ($uri -match [regex]::Escape("appId='$script:GraphAppId'")) { return [PSCustomObject]@{ id = $script:GraphSpId } } + return @([PSCustomObject]@{ + id = 'grant-1' + resourceId = $script:GraphSpId + consentType = 'AllPrincipals' + scope = 'openid User.Read' + }) + } + + Update-CIPPSSOPreconsent + + Should -Invoke -CommandName New-GraphPOSTRequest -Times 1 -Exactly -ParameterFilter { + $type -eq 'PATCH' -and + $uri -eq 'https://graph.microsoft.com/v1.0/oauth2PermissionGrants/grant-1' -and + ($body | ConvertFrom-Json).scope -eq 'email openid profile User.Read' + } + } + + It 'ignores per-user grants and creates the tenant-wide one' { + Mock -CommandName New-GraphGetRequest -MockWith { + if ($uri -match [regex]::Escape("appId='$script:SsoAppId'")) { return [PSCustomObject]@{ id = $script:SsoSpId } } + if ($uri -match [regex]::Escape("appId='$script:GraphAppId'")) { return [PSCustomObject]@{ id = $script:GraphSpId } } + return @([PSCustomObject]@{ + id = 'grant-user' + resourceId = $script:GraphSpId + consentType = 'Principal' + scope = 'openid profile email' + }) + } + + Update-CIPPSSOPreconsent + + Should -Invoke -CommandName New-GraphPOSTRequest -Times 1 -Exactly -ParameterFilter { $type -eq 'POST' } + } + } + + Context 'When the tenant refuses the grant' { + It 'does not throw and records Preconsented false with the reason' { + Mock -CommandName New-GraphPOSTRequest -MockWith { throw 'Insufficient privileges to complete the operation.' } + + { Update-CIPPSSOPreconsent } | Should -Not -Throw + + $script:SavedEntity.Preconsented | Should -Be 'false' + $script:SavedEntity.PreconsentError | Should -Match 'Insufficient privileges' + } + } + + Context 'When the service principal has not propagated yet' { + It 'returns quietly without recording a failure' { + Mock -CommandName New-GraphGetRequest -MockWith { + if ($uri -match [regex]::Escape("appId='$script:SsoAppId'")) { throw 'Resource not found.' } + return @() + } + + { Update-CIPPSSOPreconsent } | Should -Not -Throw + + Should -Invoke -CommandName Add-CIPPAzDataTableEntity -Times 0 -Exactly + } + } +} diff --git a/Tests/Reports/Get-CIPPDrift.Tests.ps1 b/Tests/Reports/Get-CIPPDrift.Tests.ps1 index b69b241a59f33..b09798461fa12 100644 --- a/Tests/Reports/Get-CIPPDrift.Tests.ps1 +++ b/Tests/Reports/Get-CIPPDrift.Tests.ps1 @@ -25,7 +25,7 @@ BeforeAll { function Get-CIPPTenantAlignment { param($TenantFilter, $TemplateId) } function New-GraphBulkRequest { param($Requests, $tenantid, $asapp) } function Add-CIPPAzDataTableEntity { param($Entity, [switch]$Force, $TableName) } - function Remove-AzDataTableEntity { param($Entity, $TableName) } + function Remove-CIPPAzDataTableEntity { param($Entity, $TableName) } # Builds an IntuneTemplate row the way the templates table stores it: JSON is a wrapper object # (Displayname/Description/Type/RAWJson) where RAWJson is itself a JSON string of the captured @@ -148,7 +148,7 @@ Describe 'Get-CIPPDrift - Intune extra-policy matching (#6347 and Settings Catal param($Entity, [switch]$Force, $TableName) foreach ($e in @($Entity)) { $script:AddedDriftEntities.Add($e) } } - Mock -CommandName Remove-AzDataTableEntity -MockWith { + Mock -CommandName Remove-CIPPAzDataTableEntity -MockWith { param($Entity, $TableName) $script:RemovedDriftEntities.Add($Entity) } @@ -329,7 +329,7 @@ Describe 'Get-CIPPDrift - Conditional Access extra-policy matching' { } } Mock -CommandName Add-CIPPAzDataTableEntity -MockWith { param($Entity, [switch]$Force, $TableName) } - Mock -CommandName Remove-AzDataTableEntity -MockWith { param($Entity, $TableName) } + Mock -CommandName Remove-CIPPAzDataTableEntity -MockWith { param($Entity, $TableName) } } It 'does not report a deviation when displayName matches exactly' { @@ -378,7 +378,7 @@ Describe 'Get-CIPPDrift - standards deviation display name resolution' { } Mock -CommandName New-GraphBulkRequest -MockWith { @() } Mock -CommandName Add-CIPPAzDataTableEntity -MockWith { param($Entity, [switch]$Force, $TableName) } - Mock -CommandName Remove-AzDataTableEntity -MockWith { param($Entity, $TableName) } + Mock -CommandName Remove-CIPPAzDataTableEntity -MockWith { param($Entity, $TableName) } } It 'resolves the Intune template display name and description for standards.IntuneTemplate deviations' { @@ -476,7 +476,7 @@ Describe 'Get-CIPPDrift - stale drift entity pruning' { Mock -CommandName Get-CippTable -MockWith { param($tablename) @{ TableName = $tablename } } Mock -CommandName New-GraphBulkRequest -MockWith { @() } Mock -CommandName Add-CIPPAzDataTableEntity -MockWith { param($Entity, [switch]$Force, $TableName) } - Mock -CommandName Remove-AzDataTableEntity -MockWith { + Mock -CommandName Remove-CIPPAzDataTableEntity -MockWith { param($Entity, $TableName) $script:RemovedDriftEntities.Add($Entity) } diff --git a/Tests/Webhooks/Test-CIPPAuditLogRules.Tests.ps1 b/Tests/Webhooks/Test-CIPPAuditLogRules.Tests.ps1 index 2e2f81f212583..707ef2a6d3b3e 100644 --- a/Tests/Webhooks/Test-CIPPAuditLogRules.Tests.ps1 +++ b/Tests/Webhooks/Test-CIPPAuditLogRules.Tests.ps1 @@ -14,7 +14,7 @@ BeforeAll { function Get-CIPPAzDataTableEntity { param($TableName, $Context, $Filter, $Property, $First) } function Get-AzDataTableEntity { param($TableName, $Context, $Filter, $Property, $First) } function Add-CIPPAzDataTableEntity { param($TableName, $Context, $Entity, [switch]$Force, $OperationType) } - function Remove-AzDataTableEntity { param($TableName, $Context, $Entity, [switch]$Force) } + function Remove-CIPPAzDataTableEntity { param($TableName, $Context, $Entity, [switch]$Force) } function Expand-CIPPTenantGroups { param($TenantFilter) } function Test-CIPPConditionFilter { param($Condition) } function Invoke-CippWebhookProcessing { param($Data, $CIPPURL, $TenantFilter, $AlertComment) } @@ -134,7 +134,7 @@ Describe 'Test-CIPPAuditLogRules record shaping' { }) } - Mock -CommandName Remove-AzDataTableEntity -MockWith { + Mock -CommandName Remove-CIPPAzDataTableEntity -MockWith { param($TableName, $Context, $Entity, [switch]$Force) foreach ($e in @($Entity)) { $script:RemovedRows.Add($e) } } @@ -144,7 +144,7 @@ Describe 'Test-CIPPAuditLogRules record shaping' { Mock -CommandName Test-CIPPConditionFilter -MockWith { '$_.Operation -eq ''Set-Mailbox''' } Mock -CommandName Invoke-CippWebhookProcessing -MockWith { } Mock -CommandName Add-CIPPAzDataTableEntity -MockWith { } - # Remove-AzDataTableEntity is mocked above with a capturing body; a second mock here + # Remove-CIPPAzDataTableEntity is mocked above with a capturing body; a second mock here # would win and silently capture nothing. Mock -CommandName Get-CIPPGeoIPLocationBatch -MockWith { @{} } Mock -CommandName Write-LogMessage -MockWith { } @@ -312,7 +312,7 @@ Describe 'Test-CIPPAuditLogRules record shaping' { $null = Test-CIPPAuditLogRules -TenantFilter 'contoso.com' -Rows $rows # Under the flush size, so one flush after the loop plus the end-of-run sweep. - Should -Invoke Remove-AzDataTableEntity -Times 2 -Exactly + Should -Invoke Remove-CIPPAzDataTableEntity -Times 2 -Exactly @($script:RemovedRows).RowKey | Should -Contain 'rec-1' @($script:RemovedRows).RowKey | Should -Contain 'rec-5' } @@ -329,7 +329,7 @@ Describe 'Test-CIPPAuditLogRules record shaping' { # 60 records at a flush size of 25: two mid-loop flushes, a remainder flush, # and the sweep - not 60 individual calls. - Should -Invoke Remove-AzDataTableEntity -Times 4 -Exactly + Should -Invoke Remove-CIPPAzDataTableEntity -Times 4 -Exactly @($script:RemovedRows).RowKey.Count | Should -Be 120 # 60 flushed + 60 swept } diff --git a/version_latest.txt b/version_latest.txt index a04a9079b9283..180a8192f393d 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -10.7.4 \ No newline at end of file +10.7.5 \ No newline at end of file