diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 000000000000..d193e2e23923
Binary files /dev/null and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
index ec6b5ec8902e..073300712d7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,7 @@ yarn.lock
# Cursor IDE
.cursor/rules
+
+# Ignore all root PowerShell files except profile.ps1
+/*.ps1
+!/profile.ps1
diff --git a/CIPP-Permissions.json b/CIPP-Permissions.json
deleted file mode 100644
index ad1e52cb5388..000000000000
--- a/CIPP-Permissions.json
+++ /dev/null
@@ -1,814 +0,0 @@
-[
- {
- "AppId": "aeb86249-8ea3-49e2-900b-54cc8e308f85",
- "DisplayName": "M365 License Manager",
- "DelegatedPermissions": [
- {
- "Id": "fc946a4f-bc4d-413b-a090-b2c86113ec4f",
- "Name": "LicenseManager.AccessAsUser",
- "Description": "Allows the application to impersonate the signed-in user when communicating with the M365 License Manager service."
- }
- ],
- "ApplicationPermissions": []
- },
- {
- "AppId": "00000003-0000-0000-c000-000000000000",
- "DisplayName": "Microsoft Graph",
- "DelegatedPermissions": [
- {
- "Id": "bdfbf15f-ee85-4955-8675-146e8e5296b5",
- "Name": "Application.ReadWrite.All",
- "Description": "Allows the app to create, read, update and delete applications and service principals on your behalf. Does not allow management of consent grants."
- },
- {
- "Id": "84bccea3-f856-4a8a-967b-dbe0a3d53a64",
- "Name": "AppRoleAssignment.ReadWrite.All",
- "Description": "Allows the app to manage permission grants for application permissions to any API (including Microsoft Graph) and application assignments for any app, on your behalf."
- },
- {
- "Id": "e4c9e354-4dc5-45b8-9e7c-e1393b0b1a20",
- "Name": "AuditLog.Read.All",
- "Description": "Allows the app to read and query your audit log activities, on your behalf."
- },
- {
- "Id": "b27a61ec-b99c-4d6a-b126-c4375d08ae30",
- "Name": "BitlockerKey.Read.All",
- "Description": "Allows the app to read BitLocker keys for your owned devices. Allows read of the recovery key."
- },
- {
- "Id": "101147cf-4178-4455-9d58-02b5c164e759",
- "Name": "Channel.Create",
- "Description": "Create channels in any team, on your behalf."
- },
- {
- "Id": "cc83893a-e232-4723-b5af-bd0b01bcfe65",
- "Name": "Channel.Delete.All",
- "Description": "Delete channels in any team, on your behalf."
- },
- {
- "Id": "9d8982ae-4365-4f57-95e9-d6032a4c0b87",
- "Name": "Channel.ReadBasic.All",
- "Description": "Read channel names and channel descriptions, on your behalf."
- },
- {
- "Id": "2eadaff8-0bce-4198-a6b9-2cfc35a30075",
- "Name": "ChannelMember.Read.All",
- "Description": "Read the members of channels, on your behalf."
- },
- {
- "Id": "0c3e411a-ce45-4cd1-8f30-f99a3efa7b11",
- "Name": "ChannelMember.ReadWrite.All",
- "Description": "Add and remove members from channels, on your behalf. Also allows changing a member's role, for example from owner to non-owner."
- },
- {
- "Id": "2b61aa8a-6d36-4b2f-ac7b-f29867937c53",
- "Name": "ChannelMessage.Edit",
- "Description": "Allows the app to edit channel messages in Microsoft Teams, on your behalf."
- },
- {
- "Id": "767156cb-16ae-4d10-8f8b-41b657c8c8c8",
- "Name": "ChannelMessage.Read.All",
- "Description": "Allows the app to read a channel's messages in Microsoft Teams, on your behalf."
- },
- {
- "Id": "ebf0f66e-9fb1-49e4-a278-222f76911cf4",
- "Name": "ChannelMessage.Send",
- "Description": "Allows the app to send channel messages in Microsoft Teams, on your behalf."
- },
- {
- "Id": "233e0cf1-dd62-48bc-b65b-b38fe87fcf8e",
- "Name": "ChannelSettings.Read.All",
- "Description": "Read all channel names, channel descriptions, and channel settings, on your behalf."
- },
- {
- "Id": "d649fb7c-72b4-4eec-b2b4-b15acf79e378",
- "Name": "ChannelSettings.ReadWrite.All",
- "Description": "Read and write the names, descriptions, and settings of all channels, on your behalf."
- },
- {
- "Id": "f3bfad56-966e-4590-a536-82ecf548ac1e",
- "Name": "ConsentRequest.Read.All",
- "Description": "Allows the app to read consent requests and approvals, on your behalf."
- },
- {
- "Id": "885f682f-a990-4bad-a642-36736a74b0c7",
- "Name": "DelegatedAdminRelationship.ReadWrite.All",
- "Description": "Allows the app to manage (create-update-terminate) Delegated Admin relationships with customers and role assignments to security groups for active Delegated Admin relationships on your behalf."
- },
- {
- "Id": "41ce6ca6-6826-4807-84f1-1c82854f7ee5",
- "Name": "DelegatedPermissionGrant.ReadWrite.All",
- "Description": "Allows the app to manage permission grants for delegated permissions exposed by any API (including Microsoft Graph), on your behalf."
- },
- {
- "Id": "bac3b9c2-b516-4ef4-bd3b-c2ef73d8d804",
- "Name": "Device.Command",
- "Description": "Allows the app to launch another app or communicate with another app on a device that you own."
- },
- {
- "Id": "11d4cd79-5ba5-460f-803f-e22c8ab85ccd",
- "Name": "Device.Read",
- "Description": "Allows the app to see your list of devices."
- },
- {
- "Id": "951183d1-1a61-466f-a6d1-1fde911bfd95",
- "Name": "Device.Read.All",
- "Description": "Allows the app to read devices' configuration information on your behalf."
- },
- {
- "Id": "280b3b69-0437-44b1-bc20-3b2fca1ee3e9",
- "Name": "DeviceLocalCredential.Read.All",
- "Description": "Allows the app to read device local credential properties including passwords, on your behalf."
- },
- {
- "Id": "7b3f05d5-f68c-4b8d-8c59-a2ecd12f24af",
- "Name": "DeviceManagementApps.ReadWrite.All",
- "Description": "Allows the app to read and write the properties, group assignments and status of apps, app configurations and app protection policies managed by Microsoft Intune."
- },
- {
- "Id": "0883f392-0a7a-443d-8c76-16a6d39c7b63",
- "Name": "DeviceManagementConfiguration.ReadWrite.All",
- "Description": "Allows the app to read and write properties of Microsoft Intune-managed device configuration and device compliance policies and their assignment to groups."
- },
- {
- "Id": "3404d2bf-2b13-457e-a330-c24615765193",
- "Name": "DeviceManagementManagedDevices.PrivilegedOperations.All",
- "Description": "Allows the app to perform remote high impact actions such as wiping the device or resetting the passcode on devices managed by Microsoft Intune."
- },
- {
- "Id": "44642bfe-8385-4adc-8fc6-fe3cb2c375c3",
- "Name": "DeviceManagementManagedDevices.ReadWrite.All",
- "Description": "Allows the app to read and write the properties of devices managed by Microsoft Intune. Does not allow high impact operations such as remote wipe and password reset on the device’s owner."
- },
- {
- "Id": "0c5e8a55-87a6-4556-93ab-adc52c4d862d",
- "Name": "DeviceManagementRBAC.ReadWrite.All",
- "Description": "Allows the app to read and write the properties relating to the Microsoft Intune Role-Based Access Control (RBAC) settings."
- },
- {
- "Id": "662ed50a-ac44-4eef-ad86-62eed9be2a29",
- "Name": "DeviceManagementServiceConfig.ReadWrite.All",
- "Description": "Allows the app to read and write Microsoft Intune service properties including device enrollment and third party service connection configuration."
- },
- {
- "Id": "0e263e50-5827-48a4-b97c-d940288653c7",
- "Name": "Directory.AccessAsUser.All",
- "Description": "Allows the app to have the same access to information in your work or school directory as you do."
- },
- {
- "Id": "c5366453-9fb0-48a5-a156-24f0c49a4b84",
- "Name": "Directory.ReadWrite.All",
- "Description": "Allows the app to read and write data in your organization's directory, such as other users, groups. It does not allow the app to delete users or groups, or reset user passwords."
- },
- {
- "Id": "2f9ee017-59c1-4f1d-9472-bd5529a7b311",
- "Name": "Domain.Read.All",
- "Description": "Allows the app to read all domain properties on your behalf."
- },
- {
- "Id": "4e46008b-f24c-477d-8fff-7bb4ec7aafe0",
- "Name": "Group.ReadWrite.All",
- "Description": "Allows the app to create groups and read all group properties and memberships on your behalf. Additionally allows the app to manage your groups and to update group content for groups you are a member of."
- },
- {
- "Id": "f81125ac-d3b7-4573-a3b2-7099cc39df9e",
- "Name": "GroupMember.ReadWrite.All",
- "Description": "Allows the app to list groups, read basic properties, read and update the membership of your groups. Group properties and owners cannot be updated and groups cannot be deleted."
- },
- {
- "Id": "9e4862a5-b68f-479e-848a-4e07e25c9916",
- "Name": "IdentityRiskEvent.ReadWrite.All",
- "Description": "Allows the app to read and update identity risk event information for all users in your organization on your behalf. Update operations include confirming risk event detections. "
- },
- {
- "Id": "bb6f654c-d7fd-4ae3-85c3-fc380934f515",
- "Name": "IdentityRiskyServicePrincipal.ReadWrite.All",
- "Description": "Allows the app to read and update identity risky service principal information for all service principals in your organization, on your behalf. Update operations include dismissing risky service principals."
- },
- {
- "Id": "e0a7cdbb-08b0-4697-8264-0069786e9674",
- "Name": "IdentityRiskyUser.ReadWrite.All",
- "Description": "Allows the app to read and update identity risky user information for all users in your organization on your behalf. Update operations include dismissing risky users."
- },
- {
- "Id": "e383f46e-2787-4529-855e-0e479a3ffac0",
- "Name": "Mail.Send",
- "Description": "Allows the app to send mail as you."
- },
- {
- "Id": "a367ab51-6b49-43bf-a716-a1fb06d2a174",
- "Name": "Mail.Send.Shared",
- "Description": "Allows the app to send mail as you or on-behalf of someone else."
- },
- {
- "Id": "818c620a-27a9-40bd-a6a5-d96f7d610b4b",
- "Name": "MailboxSettings.ReadWrite",
- "Description": "Allows the app to read, update, create, and delete your mailbox settings."
- },
- {
- "Id": "f6a3db3e-f7e8-4ed2-a414-557c8c9830be",
- "Name": "Member.Read.Hidden",
- "Description": "Allows the app to read the memberships of hidden groups or administrative units on your behalf, for those hidden groups or adminstrative units that you have access to."
- },
- {
- "Id": "7427e0e9-2fba-42fe-b0c0-848c9e6a8182",
- "Name": "offline_access",
- "Description": "Allows the app to see and update the data you gave it access to, even when you are not currently using the app. This does not give the app any additional permissions."
- },
- {
- "Id": "37f7f235-527c-4136-accd-4a02d197296e",
- "Name": "openid",
- "Description": "Allows you to sign in to the app with your work or school account and allows the app to read your basic profile information."
- },
- {
- "Id": "46ca0847-7e6b-426e-9775-ea810a948356",
- "Name": "Organization.ReadWrite.All",
- "Description": "Allows the app to read and write the organization and related resources, on your behalf. Related resources include things like subscribed skus and tenant branding information."
- },
- {
- "Id": "346c19ff-3fb2-4e81-87a0-bac9e33990c1",
- "Name": "OrgSettings-Forms.ReadWrite.All",
- "Description": "Allows the app to read and write organization-wide Microsoft Forms settings on your behalf."
- },
- {
- "Id": "e67e6727-c080-415e-b521-e3f35d5248e9",
- "Name": "PeopleSettings.ReadWrite.All",
- "Description": "Allows the application to read and write tenant-wide people settings on your behalf."
- },
- {
- "Id": "4c06a06a-098a-4063-868e-5dfee3827264",
- "Name": "Place.ReadWrite.All",
- "Description": "Allows the app to manage organization places (conference rooms and room lists) for calendar events and other applications, on your behalf."
- },
- {
- "Id": "572fea84-0151-49b2-9301-11cb16974376",
- "Name": "Policy.Read.All",
- "Description": "Allows the app to read your organization's policies on your behalf."
- },
- {
- "Id": "b27add92-efb2-4f16-84f5-8108ba77985c",
- "Name": "Policy.ReadWrite.ApplicationConfiguration",
- "Description": "Allows the app to read and write your organization's application configuration policies on your behalf. This includes policies such as activityBasedTimeoutPolicy, claimsMappingPolicy, homeRealmDiscoveryPolicy, tokenIssuancePolicy and tokenLifetimePolicy."
- },
- {
- "Id": "edb72de9-4252-4d03-a925-451deef99db7",
- "Name": "Policy.ReadWrite.AuthenticationFlows",
- "Description": "Allows the app to read and write the authentication flow policies for your tenant, on your behalf."
- },
- {
- "Id": "7e823077-d88e-468f-a337-e18f1f0e6c7c",
- "Name": "Policy.ReadWrite.AuthenticationMethod",
- "Description": "Allows the app to read and write the authentication method policies for your tenant, on your behalf."
- },
- {
- "Id": "edd3c878-b384-41fd-95ad-e7407dd775be",
- "Name": "Policy.ReadWrite.Authorization",
- "Description": "Allows the app to read and write your organization's authorization policy on your behalf. For example, authorization policies can control some of the permissions that the out-of-the-box user role has by default."
- },
- {
- "Id": "ad902697-1014-4ef5-81ef-2b4301988e8c",
- "Name": "Policy.ReadWrite.ConditionalAccess",
- "Description": "Allows the app to read and write your organization's conditional access policies on your behalf."
- },
- {
- "Id": "4d135e65-66b8-41a8-9f8b-081452c91774",
- "Name": "Policy.ReadWrite.ConsentRequest",
- "Description": "Allows the app to read and write your organization's consent request policy on your behalf."
- },
- {
- "Id": "40b534c3-9552-4550-901b-23879c90bcf9",
- "Name": "Policy.ReadWrite.DeviceConfiguration",
- "Description": "Allows the app to read and write your organization's device configuration policies on your behalf. For example, device registration policy can limit initial provisioning controls using quota restrictions, additional authentication and authorization checks."
- },
- {
- "Id": "a8ead177-1889-4546-9387-f25e658e2a79",
- "Name": "Policy.ReadWrite.MobilityManagement",
- "Description": "Allows the app to read and write your organization's mobility management policies on your behalf. For example, a mobility management policy can set the enrollment scope for a given mobility management application."
- },
- {
- "Id": "1d89d70c-dcac-4248-b214-903c457af83a",
- "Name": "PrivilegedAccess.Read.AzureResources",
- "Description": "Allows the app to read time-based assignment and just-in-time elevation of Azure resources (like your subscriptions, resource groups, storage, compute) on your behalf."
- },
- {
- "Id": "a84a9652-ffd3-496e-a991-22ba5529156a",
- "Name": "PrivilegedAccess.ReadWrite.AzureResources",
- "Description": "Allows the app to request and manage time-based assignment and just-in-time elevation of user privileges to manage your Azure resources (like your subscriptions, resource groups, storage, compute) on your behalf."
- },
- {
- "Id": "14dad69e-099b-42c9-810b-d002981feec1",
- "Name": "profile",
- "Description": "Allows the app to see your basic profile (e.g., name, picture, user name, email address)"
- },
- {
- "Id": "02e97553-ed7b-43d0-ab3c-f8bace0d040c",
- "Name": "Reports.Read.All",
- "Description": "Allows an app to read all service usage reports on your behalf. Services that provide usage reports include Office 365 and Azure Active Directory."
- },
- {
- "Id": "b955410e-7715-4a88-a940-dfd551018df3",
- "Name": "ReportSettings.ReadWrite.All",
- "Description": "Allows the app to read and update admin report settings, such as whether to display concealed information in reports, on your behalf."
- },
- {
- "Id": "d01b97e9-cbc0-49fe-810a-750afd5527a3",
- "Name": "RoleManagement.ReadWrite.Directory",
- "Description": "Allows the app to read and manage the role-based access control (RBAC) settings for your company's directory, on your behalf. This includes instantiating directory roles and managing directory role membership, and reading directory role templates, directory roles and memberships."
- },
- {
- "Id": "dc38509c-b87d-4da0-bd92-6bec988bac4a",
- "Name": "SecurityActions.ReadWrite.All",
- "Description": "Allows the app to read and update security actions, on your behalf."
- },
- {
- "Id": "6aedf524-7e1c-45a7-bd76-ded8cab8d0fc",
- "Name": "SecurityEvents.ReadWrite.All",
- "Description": "Allows the app to read your organization’s security events on your behalf. Also allows you to update editable properties in security events."
- },
- {
- "Id": "128ca929-1a19-45e6-a3b8-435ec44a36ba",
- "Name": "SecurityIncident.ReadWrite.All",
- "Description": "Allows the app to read and write to all security incidents that you have access to."
- },
- {
- "Id": "55896846-df78-47a7-aa94-8d3d4442ca7f",
- "Name": "ServiceHealth.Read.All",
- "Description": "Allows the app to read your tenant's service health information on your behalf.Health information may include service issues or service health overviews."
- },
- {
- "Id": "eda39fa6-f8cf-4c3c-a909-432c683e4c9b",
- "Name": "ServiceMessage.Read.All",
- "Description": "Allows the app to read your tenant's service announcement messages on your behalf. Messages may include information about new or changed features."
- },
- {
- "Id": "aa07f155-3612-49b8-a147-6c590df35536",
- "Name": "SharePointTenantSettings.ReadWrite.All",
- "Description": "Allows the application to read and change the tenant-level settings of SharePoint and OneDrive on your behalf."
- },
- {
- "Id": "89fe6a52-be36-487e-b7d8-d061c450a026",
- "Name": "Sites.ReadWrite.All",
- "Description": "Allow the application to edit or delete documents and list items in all site collections on your behalf."
- },
- {
- "Id": "7825d5d6-6049-4ce7-bdf6-3b8d53f4bcd0",
- "Name": "Team.Create",
- "Description": "Allows the app to create teams on your behalf. "
- },
- {
- "Id": "485be79e-c497-4b35-9400-0e3fa7f2a5d4",
- "Name": "Team.ReadBasic.All",
- "Description": "Read the names and descriptions of teams, on your behalf."
- },
- {
- "Id": "4a06efd2-f825-4e34-813e-82a57b03d1ee",
- "Name": "TeamMember.ReadWrite.All",
- "Description": "Add and remove members from teams, on your behalf. Also allows changing a member's role, for example from owner to non-owner."
- },
- {
- "Id": "2104a4db-3a2f-4ea0-9dba-143d457dc666",
- "Name": "TeamMember.ReadWriteNonOwnerRole.All",
- "Description": "Add and remove members from all teams, on your behalf. Does not allow adding or removing a member with the owner role. Additionally, does not allow the app to elevate an existing member to the owner role."
- },
- {
- "Id": "0e755559-83fb-4b44-91d0-4cc721b9323e",
- "Name": "TeamsActivity.Read",
- "Description": "Allows the app to read your teamwork activity feed."
- },
- {
- "Id": "48638b3c-ad68-4383-8ac4-e6880ee6ca57",
- "Name": "TeamSettings.Read.All",
- "Description": "Read all teams' settings, on your behalf."
- },
- {
- "Id": "39d65650-9d3e-4223-80db-a335590d027e",
- "Name": "TeamSettings.ReadWrite.All",
- "Description": "Read and change all teams' settings, on your behalf."
- },
- {
- "Id": "a9ff19c2-f369-4a95-9a25-ba9d460efc8e",
- "Name": "TeamsTab.Create",
- "Description": "Allows the app to create tabs in any team in Microsoft Teams, on your behalf. This does not grant the ability to read, modify or delete tabs after they are created, or give access to the content inside the tabs."
- },
- {
- "Id": "b98bfd41-87c6-45cc-b104-e2de4f0dafb9",
- "Name": "TeamsTab.ReadWrite.All",
- "Description": "Read and write tabs in any team in Microsoft Teams, on your behalf. This does not give access to the content inside the tabs."
- },
- {
- "Id": "cac97e40-6730-457d-ad8d-4852fddab7ad",
- "Name": "ThreatAssessment.ReadWrite.All",
- "Description": "Allows an app to read your organization's threat assessment requests on your behalf. Also allows the app to create new requests to assess threats received by your organization on your behalf."
- },
- {
- "Id": "73e75199-7c3e-41bb-9357-167164dbb415",
- "Name": "UnifiedGroupMember.Read.AsGuest",
- "Description": "Allows the app to read basic unified group properties, memberships and owners of the group you are a member of."
- },
- {
- "Id": "637d7bec-b31e-4deb-acc9-24275642a2c9",
- "Name": "User.ManageIdentities.All",
- "Description": "Allows the app to read, update and delete identities that are associated with a user's account that you have access to. This controls the identities users can sign-in with."
- },
- {
- "Id": "204e0828-b5ca-4ad8-b9f3-f32a958e7cc4",
- "Name": "User.ReadWrite.All",
- "Description": "Allows the app to read and write the full set of profile properties, reports, and managers of other users in your organization, on your behalf."
- },
- {
- "Id": "aec28ec7-4d02-4e8c-b864-50163aea77eb",
- "Name": "UserAuthenticationMethod.Read.All",
- "Description": "Allows the app to read authentication methods of all users you have access to in your organization. Authentication methods include things like a user’s phone numbers and Authenticator app settings. This does not allow the app to see secret information like passwords, or to sign-in or otherwise use the authentication methods."
- },
- {
- "Id": "48971fc1-70d7-4245-af77-0beb29b53ee2",
- "Name": "UserAuthenticationMethod.ReadWrite",
- "Description": "Allows the app to read and write your authentication methods, including phone numbers and Authenticator app settings.This does not allow the app to see secret information like your passwords, or to sign-in or otherwise use your authentication methods."
- },
- {
- "Id": "424b07a8-1209-4d17-9fe4-9018a93a1024",
- "Name": "TeamsTelephoneNumber.ReadWrite.All",
- "Description": "Allows the app to read and modify your tenant's acquired telephone number details on behalf of the signed-in admin user. Acquired telephone numbers may include attributes related to assigned object, emergency location, network site, etc."
- },
- {
- "Id": "b7887744-6746-4312-813d-72daeaee7e2d",
- "Name": "UserAuthenticationMethod.ReadWrite.All",
- "Description": "Allows the app to read and write authentication methods of all users you have access to in your organization. Authentication methods include things like a user’s phone numbers and Authenticator app settings. This does not allow the app to see secret information like passwords, or to sign-in or otherwise use the authentication methods."
- }
- ],
- "ApplicationPermissions": [
- {
- "Id": "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9",
- "Name": "Application.ReadWrite.All",
- "Description": "Allows the app to create, read, update and delete applications and service principals without a signed-in user. Does not allow management of consent grants."
- },
- {
- "Id": "b0afded3-3588-46d8-8b3d-9842eff778da",
- "Name": "AuditLog.Read.All",
- "Description": "Allows the app to read and query your audit log activities, without a signed-in user."
- },
- {
- "Id": "5e1e9171-754d-478c-812c-f1755a9a4c2d",
- "Name": "AuditLogsQuery.Read.All",
- "Description": "Allows the app to read and query audit logs from all services."
- },
- {
- "Id": "f3a65bd4-b703-46df-8f7e-0174fea562aa",
- "Name": "Channel.Create",
- "Description": "Create channels in any team, without a signed-in user."
- },
- {
- "Id": "59a6b24b-4225-4393-8165-ebaec5f55d7a",
- "Name": "Channel.ReadBasic.All",
- "Description": "Read all channel names and channel descriptions, without a signed-in user."
- },
- {
- "Id": "3b55498e-47ec-484f-8136-9013221c06a9",
- "Name": "ChannelMember.Read.All",
- "Description": "Read the members of all channels, without a signed-in user."
- },
- {
- "Id": "35930dcf-aceb-4bd1-b99a-8ffed403c974",
- "Name": "ChannelMember.ReadWrite.All",
- "Description": "Add and remove members from all channels, without a signed-in user. Also allows changing a member's role, for example from owner to non-owner."
- },
- {
- "Id": "cac88765-0581-4025-9725-5ebc13f729ee",
- "Name": "CrossTenantInformation.ReadBasic.All",
- "Description": "Allows the application to obtain basic tenant information about another target tenant within the Azure AD ecosystem without a signed-in user."
- },
- {
- "Id": "1138cb37-bd11-4084-a2b7-9f71582aeddb",
- "Name": "Device.ReadWrite.All",
- "Description": "Allows the app to read and write all device properties without a signed in user. Does not allow device creation, device deletion or update of device alternative security identifiers."
- },
- {
- "Id": "78145de6-330d-4800-a6ce-494ff2d33d07",
- "Name": "DeviceManagementApps.ReadWrite.All",
- "Description": "Allows the app to read and write the properties, group assignments and status of apps, app configurations and app protection policies managed by Microsoft Intune, without a signed-in user."
- },
- {
- "Id": "9241abd9-d0e6-425a-bd4f-47ba86e767a4",
- "Name": "DeviceManagementConfiguration.ReadWrite.All",
- "Description": "Allows the app to read and write properties of Microsoft Intune-managed device configuration and device compliance policies and their assignment to groups, without a signed-in user."
- },
- {
- "Id": "5b07b0dd-2377-4e44-a38d-703f09a0dc3c",
- "Name": "DeviceManagementManagedDevices.PrivilegedOperations.All",
- "Description": "Allows the app to perform remote high impact actions such as wiping the device or resetting the passcode on devices managed by Microsoft Intune, without a signed-in user."
- },
- {
- "Id": "2f51be20-0bb4-4fed-bf7b-db946066c75e",
- "Name": "DeviceManagementManagedDevices.Read.All",
- "Description": "Allows the app to read the properties of devices managed by Microsoft Intune, without a signed-in user."
- },
- {
- "Id": "243333ab-4d21-40cb-a475-36241daa0842",
- "Name": "DeviceManagementManagedDevices.ReadWrite.All",
- "Description": "Allows the app to read and write the properties of devices managed by Microsoft Intune, without a signed-in user. Does not allow high impact operations such as remote wipe and password reset on the device’s owner"
- },
- {
- "Id": "58ca0d9a-1575-47e1-a3cb-007ef2e4583b",
- "Name": "DeviceManagementRBAC.Read.All",
- "Description": "Allows the app to read the properties relating to the Microsoft Intune Role-Based Access Control (RBAC) settings, without a signed-in user."
- },
- {
- "Id": "e330c4f0-4170-414e-a55a-2f022ec2b57b",
- "Name": "DeviceManagementRBAC.ReadWrite.All",
- "Description": "Allows the app to read and write the properties relating to the Microsoft Intune Role-Based Access Control (RBAC) settings, without a signed-in user."
- },
- {
- "Id": "9255e99d-faf5-445e-bbf7-cb71482737c4",
- "Name": "DeviceManagementScripts.ReadWrite.All",
- "Description": "Allows the app to read and write Microsoft Intune device compliance scripts, device management scripts, device shell scripts, device custom attribute shell scripts and device health scripts, without a signed-in user."
- },
- {
- "Id": "06a5fe6d-c49d-46a7-b082-56b1b14103c7",
- "Name": "DeviceManagementServiceConfig.Read.All",
- "Description": "Allows the app to read Microsoft Intune service properties including device enrollment and third party service connection configuration, without a signed-in user."
- },
- {
- "Id": "5ac13192-7ace-4fcf-b828-1a26f28068ee",
- "Name": "DeviceManagementServiceConfig.ReadWrite.All",
- "Description": "Allows the app to read and write Microsoft Intune service properties including device enrollment and third party service connection configuration, without a signed-in user."
- },
- {
- "Id": "7ab1d382-f21e-4acd-a863-ba3e13f7da61",
- "Name": "Directory.Read.All",
- "Description": "Allows the app to read data in your organization's directory, such as users, groups and apps, without a signed-in user."
- },
- {
- "Id": "19dbc75e-c2e2-444c-a770-ec69d8559fc7",
- "Name": "Directory.ReadWrite.All",
- "Description": "Allows the app to read and write data in your organization's directory, such as users, and groups, without a signed-in user. Does not allow user or group deletion."
- },
- {
- "Id": "dbb9058a-0e50-45d7-ae91-66909b5d4664",
- "Name": "Domain.Read.All",
- "Description": "Allows the app to read all domain properties without a signed-in user."
- },
- {
- "Id": "75359482-378d-4052-8f01-80520e7db3cd",
- "Name": "Files.ReadWrite.All",
- "Description": "Allows the app to read, create, update and delete all files in all site collections without a signed in user."
- },
- {
- "Id": "bf7b1a76-6e77-406b-b258-bf5c7720e98f",
- "Name": "Group.Create",
- "Description": "Allows the app to create groups without a signed-in user."
- },
- {
- "Id": "5b567255-7703-4780-807c-7be8301ae99b",
- "Name": "Group.Read.All",
- "Description": "Allows the app to read group properties and memberships, and read conversations for all groups, without a signed-in user."
- },
- {
- "Id": "62a82d76-70ea-41e2-9197-370581804d09",
- "Name": "Group.ReadWrite.All",
- "Description": "Allows the app to create groups, read all group properties and memberships, update group properties and memberships, and delete groups. Also allows the app to read and write conversations. All of these operations can be performed by the app without a signed-in user."
- },
- {
- "Id": "dbaae8cf-10b5-4b86-a4a1-f871c94c6695",
- "Name": "GroupMember.ReadWrite.All",
- "Description": "Allows the app to list groups, read basic properties, read and update the membership of the groups this app has access to without a signed-in user. Group properties and owners cannot be updated and groups cannot be deleted."
- },
- {
- "Id": "19da66cb-0fb0-4390-b071-ebc76a349482",
- "Name": "InformationProtectionPolicy.Read.All",
- "Description": "Allows an app to read published sensitivity labels and label policy settings for the entire organization or a specific user, without a signed in user."
- },
- {
- "Id": "6931bccd-447a-43d1-b442-00a195474933",
- "Name": "MailboxSettings.ReadWrite",
- "Description": "Allows the app to create, read, update, and delete user's mailbox settings without a signed-in user. Does not include permission to send mail."
- },
- {
- "Id": "292d869f-3427-49a8-9dab-8c70152b74e9",
- "Name": "Organization.ReadWrite.All",
- "Description": "Allows the app to read and write the organization and related resources, without a signed-in user. Related resources include things like subscribed skus and tenant branding information."
- },
- {
- "Id": "2cb92fee-97a3-4034-8702-24a6f5d0d1e9",
- "Name": "OrgSettings-Forms.ReadWrite.All",
- "Description": "Allows the app to read and write organization-wide Microsoft Forms settings, without a signed-in user."
- },
- {
- "Id": "b6890674-9dd5-4e42-bb15-5af07f541ae1",
- "Name": "PeopleSettings.ReadWrite.All",
- "Description": "Allows the application to read and write tenant-wide people settings without a signed-in user."
- },
- {
- "Id": "913b9306-0ce1-42b8-9137-6a7df690a760",
- "Name": "Place.Read.All",
- "Description": "Allows the app to read company places (conference rooms and room lists) for calendar events and other applications, without a signed-in user."
- },
- {
- "Id": "246dd0d5-5bd0-4def-940b-0421030a5b68",
- "Name": "Policy.Read.All",
- "Description": "Allows the app to read all your organization's policies without a signed in user."
- },
- {
- "Id": "be74164b-cff1-491c-8741-e671cb536e13",
- "Name": "Policy.ReadWrite.ApplicationConfiguration",
- "Description": "Allows the app to read and write your organization's application configuration policies, without a signed-in user. This includes policies such as activityBasedTimeoutPolicy, claimsMappingPolicy, homeRealmDiscoveryPolicy, tokenIssuancePolicy and tokenLifetimePolicy."
- },
- {
- "Id": "25f85f3c-f66c-4205-8cd5-de92dd7f0cec",
- "Name": "Policy.ReadWrite.AuthenticationFlows",
- "Description": "Allows the app to read and write all authentication flow policies for the tenant, without a signed-in user."
- },
- {
- "Id": "29c18626-4985-4dcd-85c0-193eef327366",
- "Name": "Policy.ReadWrite.AuthenticationMethod",
- "Description": "Allows the app to read and write all authentication method policies for the tenant, without a signed-in user. "
- },
- {
- "Id": "01c0a623-fc9b-48e9-b794-0756f8e8f067",
- "Name": "Policy.ReadWrite.ConditionalAccess",
- "Description": "Allows the app to read and write your organization's conditional access policies, without a signed-in user."
- },
- {
- "Id": "999f8c63-0a38-4f1b-91fd-ed1947bdd1a9",
- "Name": "Policy.ReadWrite.ConsentRequest",
- "Description": "Allows the app to read and write your organization's consent requests policy without a signed-in user."
- },
- {
- "Id": "338163d7-f101-4c92-94ba-ca46fe52447c",
- "Name": "Policy.ReadWrite.CrossTenantAccess",
- "Description": "Allows the app to read and write your organization's cross tenant access policies without a signed-in user."
- },
- {
- "Id": "2f6817f8-7b12-4f0f-bc18-eeaf60705a9e",
- "Name": "PrivilegedAccess.ReadWrite.AzureADGroup",
- "Description": "Allows the app to request and manage time-based assignment and just-in-time elevation (including scheduled elevation) of Azure AD groups in your organization, without a signed-in user."
- },
- {
- "Id": "230c1aed-a721-4c5d-9cb4-a90514e508ef",
- "Name": "Reports.Read.All",
- "Description": "Allows an app to read all service usage reports without a signed-in user. Services that provide usage reports include Office 365 and Azure Active Directory."
- },
- {
- "Id": "2a60023f-3219-47ad-baa4-40e17cd02a1d",
- "Name": "ReportSettings.ReadWrite.All",
- "Description": "Allows the app to read and update all admin report settings, such as whether to display concealed information in reports, without a signed-in user."
- },
- {
- "Id": "025d3225-3f02-4882-b4c0-cd5b541a4e80",
- "Name": "RoleManagement.ReadWrite.Exchange",
- "Description": "Allows the app to read and manage the role-based access control (RBAC) settings for your organization's Exchange Online service, without a signed-in user. This includes reading, creating, updating, and deleting Exchange management role definitions, role groups, role group membership, role assignments, management scopes, and role assignment policies."
- },
- {
- "Id": "04c55753-2244-4c25-87fc-704ab82a4f69",
- "Name": "SecurityAnalyzedMessage.ReadWrite.All",
- "Description": "Read email metadata and security detection details, and execute remediation actions like deleting an email, without a signed-in user."
- },
- {
- "Id": "bf394140-e372-4bf9-a898-299cfc7564e5",
- "Name": "SecurityEvents.Read.All",
- "Description": "Allows the app to read your organization’s security events without a signed-in user."
- },
- {
- "Id": "45cc0394-e837-488b-a098-1918f48d186c",
- "Name": "SecurityIncident.Read.All",
- "Description": "Allows the app to read all security incidents, without a signed-in user."
- },
- {
- "Id": "34bf0e97-1971-4929-b999-9e2442d941d7",
- "Name": "SecurityIncident.ReadWrite.All",
- "Description": "Allows the app to read and write to all security incidents, without a signed-in user."
- },
- {
- "Id": "19b94e34-907c-4f43-bde9-38b1909ed408",
- "Name": "SharePointTenantSettings.ReadWrite.All",
- "Description": "Allows the application to read and change the tenant-level settings of SharePoint and OneDrive, without a signed-in user."
- },
- {
- "Id": "a82116e5-55eb-4c41-a434-62fe8a61c773",
- "Name": "Sites.FullControl.All",
- "Description": "Allows the app to have full control of all site collections without a signed in user."
- },
- {
- "Id": "0121dc95-1b9f-4aed-8bac-58c5ac466691",
- "Name": "TeamMember.ReadWrite.All",
- "Description": "Add and remove members from all teams, without a signed-in user. Also allows changing a team member's role, for example from owner to non-owner."
- },
- {
- "Id": "4437522e-9a86-4a41-a7da-e380edd4a97d",
- "Name": "TeamMember.ReadWriteNonOwnerRole.All",
- "Description": "Add and remove members from all teams, without a signed-in user. Does not allow adding or removing a member with the owner role. Additionally, does not allow the app to elevate an existing member to the owner role."
- },
- {
- "Id": "741f803b-c850-494e-b5df-cde7c675a1ca",
- "Name": "User.ReadWrite.All",
- "Description": "Allows the app to read and update user profiles without a signed in user."
- },
- {
- "Id": "0a42382f-155c-4eb1-9bdc-21548ccaa387",
- "Name": "TeamsTelephoneNumber.ReadWrite.All",
- "Description": "Allows the app to read your tenant's acquired telephone number details, without a signed-in user. Acquired telephone numbers may include attributes related to assigned object, emergency location, network site, etc."
- },
- {
- "Id": "50483e42-d915-4231-9639-7fdb7fd190e5",
- "Name": "UserAuthenticationMethod.ReadWrite.All",
- "Description": "Allows the application to read and write authentication methods of all users in your organization, without a signed-in user. Authentication methods include things like a user’s phone numbers and Authenticator app settings. This does not allow the app to see secret information like passwords, or to sign-in or otherwise use the authentication methods"
- }
- ]
- },
- {
- "AppId": "fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd",
- "DisplayName": "Microsoft Partner Center",
- "DelegatedPermissions": [
- {
- "Id": "1cebfa2a-fb4d-419e-b5f9-839b4383e05a",
- "Name": "user_impersonation",
- "Description": "Allow the application to access Partner Center on your behalf"
- }
- ],
- "ApplicationPermissions": []
- },
- {
- "AppId": "00000002-0000-0ff1-ce00-000000000000",
- "DisplayName": "Office 365 Exchange Online",
- "DelegatedPermissions": [
- {
- "Id": "ab4f2b77-0b06-4fc1-a9de-02113fc2ab7c",
- "Name": "Exchange.Manage",
- "Description": "Allows the app to manage your organization's Exchange environment, such as mailboxes, groups, and other configuration objects. To enable management actions, an admin must assign you the appropriate roles."
- },
- {
- "Id": "bbd1ca91-75e0-4814-ad94-9c5dbbae3415",
- "Name": "Calendars.ReadWrite.All",
- "Description": "Allows the app to read, update, create and delete events in all calendars in your organization you have permissions to access. This includes delegate and shared calendars. "
- },
- {
- "Id": "2e83d72d-8895-4b66-9eea-abb43449ab8b",
- "Name": "MailboxSettings.ReadWrite",
- "Description": "Allows the app to read, update, create, and delete your mailbox settings."
- }
- ],
- "ApplicationPermissions": [
- {
- "Id": "dc50a0fb-09a3-484d-be87-e023b12c6440",
- "Name": "Exchange.ManageAsApp",
- "Description": "Allows the app to manage the organization's Exchange environment without any user interaction. This includes mailboxes, groups, and other configuration objects. To enable management actions, an admin must assign the appropriate roles directly to the app."
- },
- {
- "Id": "ef54d2bf-783f-4e0f-bca1-3210c0444d99",
- "Name": "Calendars.ReadWrite.All",
- "Description": "Allows the app to create, read, update, and delete events of all calendars without a signed-in user."
- },
- {
- "Id": "f9156939-25cd-4ba8-abfe-7fabcf003749",
- "Name": "MailboxSettings.ReadWrite",
- "Description": "Allows the app to create, read, update, and delete user's mailbox settings without a signed-in user. Does not include permission to send mail."
- }
- ]
- },
- {
- "AppId": "00000003-0000-0ff1-ce00-000000000000",
- "DisplayName": "Office 365 SharePoint Online",
- "DelegatedPermissions": [
- {
- "Id": "56680e0d-d2a3-4ae1-80d8-3c4f2100e3d0",
- "Name": "AllSites.FullControl",
- "Description": "Allows the app to have full control of all site collections on your behalf."
- },
- {
- "Id": "AllProfiles.Manage",
- "Name": "AllProfiles.Manage",
- "Description": "Manually added"
- }
- ],
- "ApplicationPermissions": []
- },
- {
- "AppId": "48ac35b8-9aa8-4d74-927d-1f4a14a0b239",
- "DisplayName": "Skype and Teams Tenant Admin API",
- "DelegatedPermissions": [
- {
- "Id": "e60370c1-e451-437e-aa6e-d76df38e5f15",
- "Name": "user_impersonation",
- "Description": "Access Microsoft Teams and Skype for Business data based on the user's role membership"
- }
- ],
- "ApplicationPermissions": []
- },
- {
- "AppId": "fc780465-2017-40d4-a0c5-307022471b92",
- "DisplayName": "WindowsDefenderATP",
- "DelegatedPermissions": [
- {
- "Id": "63a677ce-818c-4409-9d12-5c6d2e2a6bfe",
- "Name": "Vulnerability.Read",
- "Description": "Allows the app to read Threat and Vulnerability Management vulnerability information on behalf of the signed-in user"
- }
- ],
- "ApplicationPermissions": [
- {
- "Id": "41269fc5-d04d-4bfd-bce7-43a51cea049a",
- "Name": "Vulnerability.Read.All",
- "Description": "Allows the app to read any Threat and Vulnerability Management vulnerability information"
- }
- ]
- }
-]
diff --git a/Config/ExcludeSkuList.JSON b/Config/ExcludeSkuList.JSON
index ec0dfb32d4a9..7409a30962dd 100644
--- a/Config/ExcludeSkuList.JSON
+++ b/Config/ExcludeSkuList.JSON
@@ -5,15 +5,15 @@
},
{
"GUID": "f30db892-07e9-47e9-837c-80727f46fd3d",
- "Product_Display_Name": "MICROSOFT FLOW FREE"
+ "Product_Display_Name": "Microsoft Power Automate Free"
},
{
"GUID": "16ddbbfc-09ea-4de2-b1d7-312db6112d70",
- "Product_Display_Name": "MICROSOFT TEAMS (FREE)"
+ "Product_Display_Name": "Microsoft Teams (Free)"
},
{
"GUID": "a403ebcc-fae0-4ca2-8c8c-7a907fd6c235",
- "Product_Display_Name": "Power BI (free)"
+ "Product_Display_Name": "Microsoft Fabric (Free)"
},
{
"GUID": "61e6bd70-fbdb-4deb-82ea-912842f39431",
@@ -25,7 +25,7 @@
},
{
"GUID": "338148b6-1b11-4102-afb9-f92b6cdc0f8d",
- "Product_Display_Name": "DYNAMICS 365 P1 TRIAL FOR INFORMATION WORKERS"
+ "Product_Display_Name": "Dynamics 365 P1 Tria for Information Workers"
},
{
"GUID": "fcecd1f9-a91e-488d-a918-a96cdb6ce2b0",
@@ -41,19 +41,19 @@
},
{
"GUID": "606b54a9-78d8-4298-ad8b-df6ef4481c80",
- "Product_Display_Name": "Power Virtual Agents Viral Trial"
+ "Product_Display_Name": "Microsoft Copilot Studio Viral Trial"
},
{
"GUID": "1f2f344a-700d-42c9-9427-5cea1d5d7ba6",
- "Product_Display_Name": "MICROSOFT STREAM"
+ "Product_Display_Name": "Microsoft Stream"
},
{
"GUID": "6470687e-a428-4b7a-bef2-8a291ad947c9",
- "Product_Display_Name": "WINDOWS STORE FOR BUSINESS"
+ "Product_Display_Name": "Windows Store for Business"
},
{
"GUID": "710779e8-3d4a-4c88-adb9-386c958d1fdf",
- "Product_Display_Name": "MICROSOFT TEAMS EXPLORATORY"
+ "Product_Display_Name": "Microsoft Teams Exploratory"
},
{
"GUID": "8c4ce438-32a7-4ac5-91a6-e22ae08d9c8b",
@@ -94,5 +94,9 @@
{
"GUID": "99049c9c-6011-4908-bf17-15f496e6519d",
"Product_Display_Name": "Office 365 Extra File Storage"
+ },
+ {
+ "GUID": "47794cd0-f0e5-45c5-9033-2eb6b5fc84e0",
+ "Product_Display_Name": "Communications Credits"
}
]
diff --git a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1
index ddcbd3447e9e..c7963cc5a196 100644
--- a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1
+++ b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1
@@ -7,12 +7,9 @@ function Add-CIPPApplicationPermission {
$TenantFilter
)
if ($ApplicationId -eq $env:ApplicationID -and $TenantFilter -eq $env:TenantID) {
- #return @('Cannot modify application permissions for CIPP-SAM on partner tenant')
$RequiredResourceAccess = 'CIPPDefaults'
}
- Set-Location (Get-Item $PSScriptRoot).FullName
if ($RequiredResourceAccess -eq 'CIPPDefaults') {
- #$RequiredResourceAccess = (Get-Content '.\SAMManifest.json' | ConvertFrom-Json).requiredResourceAccess
$Permissions = Get-CippSamPermissions -NoDiff
$RequiredResourceAccess = [System.Collections.Generic.List[object]]::new()
@@ -59,13 +56,18 @@ function Add-CIPPApplicationPermission {
}
}
+ Write-Information "Adding application permissions to application $ApplicationId in tenant $TenantFilter"
- $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -skipTokenCache $true -tenantid $TenantFilter -NoAuthCheck $true
+ $ServicePrincipalList = [System.Collections.Generic.List[object]]::new()
+ $SPList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -skipTokenCache $true -tenantid $TenantFilter -NoAuthCheck $true
+ foreach ($SP in $SPList) { $ServicePrincipalList.Add($SP) }
$ourSVCPrincipal = $ServicePrincipalList | Where-Object -Property AppId -EQ $ApplicationId
if (!$ourSVCPrincipal) {
#Our Service Principal isn't available yet. We do a sleep and reexecute after 3 seconds.
Start-Sleep -Seconds 5
- $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -skipTokenCache $true -tenantid $TenantFilter -NoAuthCheck $true
+ $ServicePrincipalList.Clear()
+ $SPList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -skipTokenCache $true -tenantid $TenantFilter -NoAuthCheck $true
+ foreach ($SP in $SPList) { $ServicePrincipalList.Add($SP) }
$ourSVCPrincipal = $ServicePrincipalList | Where-Object -Property AppId -EQ $ApplicationId
}
@@ -73,19 +75,53 @@ function Add-CIPPApplicationPermission {
$CurrentRoles = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals/$($ourSVCPrincipal.id)/appRoleAssignments" -tenantid $TenantFilter -skipTokenCache $true -NoAuthCheck $true
- $Grants = foreach ($App in $RequiredResourceAccess) {
+ # Collect missing service principals and prepare bulk request
+ $MissingServicePrincipals = [System.Collections.Generic.List[object]]::new()
+ $AppIdToRequestId = @{}
+ $requestId = 1
+
+ foreach ($App in $RequiredResourceAccess) {
$svcPrincipalId = $ServicePrincipalList | Where-Object -Property AppId -EQ $App.resourceAppId
if (!$svcPrincipalId) {
- try {
- $Body = @{
- appId = $App.resourceAppId
- } | ConvertTo-Json -Compress
- $svcPrincipalId = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/servicePrincipals' -tenantid $TenantFilter -body $Body -type POST
- } catch {
- $Results.add("Failed to create service principal for $($App.resourceAppId): $(Get-NormalizedError -message $_.Exception.Message)")
- continue
+ $Body = @{
+ appId = $App.resourceAppId
+ }
+ $MissingServicePrincipals.Add(@{
+ id = $requestId.ToString()
+ method = 'POST'
+ url = '/servicePrincipals'
+ headers = @{
+ 'Content-Type' = 'application/json'
+ }
+ body = $Body
+ })
+ $AppIdToRequestId[$App.resourceAppId] = $requestId.ToString()
+ $requestId++
+ }
+ }
+
+ # Create missing service principals in bulk
+ if ($MissingServicePrincipals.Count -gt 0) {
+ try {
+ $BulkResults = New-GraphBulkRequest -Requests $MissingServicePrincipals -tenantid $TenantFilter -NoAuthCheck $true
+ foreach ($Result in $BulkResults) {
+ if ($Result.status -eq 201) {
+ $ServicePrincipalList.Add($Result.body)
+ } else {
+ $AppId = ($MissingServicePrincipals | Where-Object { $_.id -eq $Result.id }).body.appId
+ $Results.add("Failed to create service principal for $($AppId): $($Result.body.error.message)")
+ }
}
+ } catch {
+ $Results.add("Failed to create service principals in bulk: $(Get-NormalizedError -message $_.Exception.Message)")
}
+ }
+
+ # Build grants list
+ $Grants = foreach ($App in $RequiredResourceAccess) {
+ $svcPrincipalId = $ServicePrincipalList | Where-Object -Property AppId -EQ $App.resourceAppId
+ if (!$svcPrincipalId) { continue }
+
foreach ($SingleResource in $App.ResourceAccess | Where-Object -Property Type -EQ 'Role') {
if ($SingleResource.id -in $CurrentRoles.appRoleId) { continue }
[pscustomobject]@{
@@ -95,14 +131,37 @@ function Add-CIPPApplicationPermission {
}
}
}
+
+ # Apply grants in bulk
$counter = 0
- foreach ($Grant in $Grants) {
+ if ($Grants.Count -gt 0) {
+ $GrantRequests = [System.Collections.Generic.List[object]]::new()
+ $requestId = 1
+ foreach ($Grant in $Grants) {
+ $GrantRequests.Add(@{
+ id = $requestId.ToString()
+ method = 'POST'
+ url = "/servicePrincipals/$($ourSVCPrincipal.id)/appRoleAssignedTo"
+ headers = @{
+ 'Content-Type' = 'application/json'
+ }
+ body = $Grant
+ })
+ $requestId++
+ }
+
try {
- $SettingsRequest = New-GraphPOSTRequest -body (ConvertTo-Json -InputObject $Grant -Depth 5) -uri "https://graph.microsoft.com/beta/servicePrincipals/$($ourSVCPrincipal.id)/appRoleAssignedTo" -tenantid $TenantFilter -type POST -NoAuthCheck $true
- $counter++
+ $BulkResults = New-GraphBulkRequest -Requests $GrantRequests -tenantid $TenantFilter -NoAuthCheck $true
+ foreach ($Result in $BulkResults) {
+ if ($Result.status -eq 201) {
+ $counter++
+ } else {
+ $GrantRequest = $GrantRequests | Where-Object { $_.id -eq $Result.id }
+ $Results.add("Failed to grant $($GrantRequest.body.appRoleId) to $($GrantRequest.body.resourceId): $($Result.body.error.message)")
+ }
+ }
} catch {
- $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
- $Results.add("Failed to grant $($Grant.appRoleId) to $($Grant.resourceId): $ErrorMessage")
+ $Results.add("Failed to grant permissions in bulk: $(Get-NormalizedError -message $_.Exception.Message)")
}
}
"Added $counter Application permissions to $($ourSVCPrincipal.displayName)"
diff --git a/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 b/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1
index bd9c4d733eff..7b96b16bc4cb 100644
--- a/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1
+++ b/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1
@@ -4,7 +4,7 @@ function Add-CIPPDbItem {
Add items to the CIPP Reporting database
.DESCRIPTION
- Adds items to the CippReportingDB table with support for bulk inserts and count mode
+ Adds items to the CippReportingDB table with support for bulk inserts, count mode, and pipeline streaming
.PARAMETER TenantFilter
The tenant domain or GUID (used as partition key)
@@ -12,15 +12,22 @@ function Add-CIPPDbItem {
.PARAMETER Type
The type of data being stored (used in row key)
- .PARAMETER Data
- Array of items to add to the database
+ .PARAMETER InputObject
+ Items to add to the database. Accepts pipeline input for memory-efficient streaming.
+ Alias: Data (for backward compatibility)
.PARAMETER Count
- If specified, stores a single row with count of each object property as separate properties
+ If specified, stores a single row with count of items processed
+
+ .PARAMETER AddCount
+ If specified, automatically records the total count after processing all items
.EXAMPLE
Add-CIPPDbItem -TenantFilter 'contoso.onmicrosoft.com' -Type 'Groups' -Data $GroupsData
+ .EXAMPLE
+ New-GraphGetRequest -uri '...' | Add-CIPPDbItem -TenantFilter 'contoso.onmicrosoft.com' -Type 'Users' -AddCount
+
.EXAMPLE
Add-CIPPDbItem -TenantFilter 'contoso.onmicrosoft.com' -Type 'Groups' -Data $GroupsData -Count
#>
@@ -32,92 +39,167 @@ function Add-CIPPDbItem {
[Parameter(Mandatory = $true)]
[string]$Type,
- [Parameter(Mandatory = $true)]
+ [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
+ [Alias('Data')]
[AllowEmptyCollection()]
- [array]$Data,
+ $InputObject,
+
+ [Parameter(Mandatory = $false)]
+ [switch]$Count,
[Parameter(Mandatory = $false)]
- [switch]$Count
+ [switch]$AddCount
)
- try {
+ begin {
+ # Initialize pipeline processing with state hashtable for nested function access
$Table = Get-CippTable -tablename 'CippReportingDB'
+ $BatchAccumulator = [System.Collections.Generic.List[hashtable]]::new(500)
+ $State = @{
+ TotalProcessed = 0
+ BatchNumber = 0
+ }
# Helper function to format RowKey values by removing disallowed characters
function Format-RowKey {
param([string]$RowKey)
-
- # Remove disallowed characters: / \ # ? and control characters (U+0000 to U+001F and U+007F to U+009F)
$sanitized = $RowKey -replace '[/\\#?]', '_' -replace '[\u0000-\u001F\u007F-\u009F]', ''
-
return $sanitized
}
- if ($Count) {
- $Entity = @{
- PartitionKey = $TenantFilter
- RowKey = Format-RowKey "$Type-Count"
- DataCount = [int]$Data.Count
- }
+ # Function to flush current batch
+ function Invoke-FlushBatch {
+ param($State)
+ if ($BatchAccumulator.Count -eq 0) { return }
+
+ $State.BatchNumber++
+ $batchSize = $BatchAccumulator.Count
+ $MemoryBeforeGC = [System.GC]::GetTotalMemory($false)
+ $flushStart = Get-Date
+
+ try {
+ # Entities are already in the accumulator, just write them
+ $writeStart = Get-Date
+ Add-CIPPAzDataTableEntity @Table -Entity $BatchAccumulator.ToArray() -Force | Out-Null
+ $writeEnd = Get-Date
+ $writeDuration = [math]::Round(($writeEnd - $writeStart).TotalSeconds, 2)
+ $State.TotalProcessed += $batchSize
+
+ } finally {
+ # Clear and GC
+ $gcStart = Get-Date
+ $BatchAccumulator.Clear()
+
+ # Single GC pass is sufficient - aggressive GC was causing slowdown
+ [System.GC]::Collect()
- Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force | Out-Null
+ $flushEnd = Get-Date
+ $gcDuration = [math]::Round(($flushEnd - $gcStart).TotalSeconds, 2)
+ $flushDuration = [math]::Round(($flushEnd - $flushStart).TotalSeconds, 2)
+ $MemoryAfterGC = [System.GC]::GetTotalMemory($false)
+ $FreedMB = [math]::Round(($MemoryBeforeGC - $MemoryAfterGC) / 1MB, 2)
+ $CurrentMemoryMB = [math]::Round($MemoryAfterGC / 1MB, 2)
+ #Write-Debug "Batch $($State.BatchNumber): ${flushDuration}s total (write: ${writeDuration}s, gc: ${gcDuration}s) | Processed: $($State.TotalProcessed) | Memory: ${CurrentMemoryMB}MB | Freed: ${FreedMB}MB"
+ }
+ }
- } else {
- #Get the existing type entries and nuke them. This ensures we don't have stale data.
+ if (-not $Count.IsPresent) {
+ # Delete existing entries for this type
$Filter = "PartitionKey eq '{0}' and RowKey ge '{1}-' and RowKey lt '{1}0'" -f $TenantFilter, $Type
- $ExistingEntities = Get-CIPPAzDataTableEntity @Table -Filter $Filter
+ $ExistingEntities = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey, ETag
if ($ExistingEntities) {
Remove-AzDataTableEntity @Table -Entity $ExistingEntities -Force | Out-Null
}
+ $AllocatedMemoryMB = [math]::Round([System.GC]::GetTotalMemory($false) / 1MB, 2)
+ #Write-Debug "Starting $Type import for $TenantFilter | Allocated Memory: ${AllocatedMemoryMB}MB | Batch Size: 500"
+ }
+ }
- # Calculate batch size based on available memory
- $AvailableMemory = [System.GC]::GetTotalMemory($false)
- $AvailableMemoryMB = [math]::Round($AvailableMemory / 1MB, 2)
+ process {
+ # Process each item from pipeline
+ if ($null -eq $InputObject) { return }
- # Estimate item size from first item (with fallback)
- $EstimatedItemSizeBytes = 1KB # Default assumption
- if ($Data.Count -gt 0) {
- $SampleJson = $Data[0] | ConvertTo-Json -Depth 10 -Compress
- $EstimatedItemSizeBytes = [System.Text.Encoding]::UTF8.GetByteCount($SampleJson)
- }
+ # If Count mode and InputObject is an integer, use it directly as count
+ if ($Count.IsPresent -and $InputObject -is [int]) {
+ $State.TotalProcessed = $InputObject
+ return
+ }
- # Use 25% of available memory for batch processing, with min/max bounds
- $TargetBatchMemoryMB = [Math]::Max(50, $AvailableMemoryMB * 0.25)
- $CalculatedBatchSize = [Math]::Floor(($TargetBatchMemoryMB * 1MB) / $EstimatedItemSizeBytes)
- # Reduce max to 500 to prevent OOM with large datasets
- $BatchSize = [Math]::Max(100, [Math]::Min(500, $CalculatedBatchSize))
-
- $TotalCount = $Data.Count
- $ProcessedCount = 0
- Write-Information "Adding $TotalCount items of type $Type to CIPP Reporting DB for tenant $TenantFilter | Available Memory: ${AvailableMemoryMB}MB | Target Memory: ${TargetBatchMemoryMB}MB | Calculated: $CalculatedBatchSize | Batch Size: $BatchSize (est. item size: $([math]::Round($EstimatedItemSizeBytes/1KB, 2))KB)"
- for ($i = 0; $i -lt $TotalCount; $i += $BatchSize) {
- $BatchEnd = [Math]::Min($i + $BatchSize, $TotalCount)
- $Batch = $Data[$i..($BatchEnd - 1)]
-
- $Entities = foreach ($Item in $Batch) {
- $ItemId = $Item.id ?? $Item.ExternalDirectoryObjectId ?? $Item.Identity ?? $Item.skuId
- @{
- PartitionKey = $TenantFilter
- RowKey = Format-RowKey "$Type-$ItemId"
- Data = [string]($Item | ConvertTo-Json -Depth 10 -Compress)
- Type = $Type
- }
- }
+ # Handle both single items and arrays (for backward compatibility)
+ $ItemsToProcess = if ($InputObject -is [array]) {
+ $InputObject
+ } else {
+ @($InputObject)
+ }
- Add-CIPPAzDataTableEntity @Table -Entity $Entities -Force | Out-Null
- $ProcessedCount += $Batch.Count
+ # If Count mode, just count items without processing
+ if ($Count.IsPresent) {
+ $itemCount = if ($ItemsToProcess -is [array]) { $ItemsToProcess.Count } else { 1 }
+ $State.TotalProcessed += $itemCount
+ return
+ }
- # Clear batch variables to free memory
- $Entities = $null
- $Batch = $null
- [System.GC]::Collect()
+ foreach ($Item in $ItemsToProcess) {
+ if ($null -eq $Item) { continue }
+
+ # Convert to entity
+ $ItemId = $Item.ExternalDirectoryObjectId ?? $Item.id ?? $Item.Identity ?? $Item.skuId
+ $Entity = @{
+ PartitionKey = $TenantFilter
+ RowKey = Format-RowKey "$Type-$ItemId"
+ Data = [string]($Item | ConvertTo-Json -Depth 10 -Compress)
+ Type = $Type
}
+ $BatchAccumulator.Add($Entity)
+
+ # Flush when batch reaches 500 items
+ if ($BatchAccumulator.Count -ge 500) {
+ Invoke-FlushBatch -State $State
+ }
}
- Write-LogMessage -API 'CIPPDbItem' -tenant $TenantFilter -message "Added $($Data.Count) items of type $Type$(if ($Count) { ' (count mode)' })" -sev Debug
+ }
+
+ end {
+ try {
+ # Flush any remaining items in final partial batch
+ if ($BatchAccumulator.Count -gt 0) {
+ Invoke-FlushBatch -State $State
+ }
+
+ if ($Count.IsPresent) {
+ # Store count record
+ $Entity = @{
+ PartitionKey = $TenantFilter
+ RowKey = Format-RowKey "$Type-Count"
+ DataCount = [int]$State.TotalProcessed
+ }
+ Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force | Out-Null
+ }
- } catch {
- Write-LogMessage -API 'CIPPDbItem' -tenant $TenantFilter -message "Failed to add items of type $Type : $($_.Exception.Message)" -sev Error -LogData (Get-CippException -Exception $_)
- throw
+ Write-LogMessage -API 'CIPPDbItem' -tenant $TenantFilter `
+ -message "Added $($State.TotalProcessed) items of type $Type$(if ($Count.IsPresent) { ' (count mode)' })" -sev Debug
+
+ } catch {
+ Write-LogMessage -API 'CIPPDbItem' -tenant $TenantFilter `
+ -message "Failed to add items of type $Type : $($_.Exception.Message)" -sev Error `
+ -LogData (Get-CippException -Exception $_)
+ #Write-Debug "[Add-CIPPDbItem] $TenantFilter - $(Get-CippException -Exception $_ | ConvertTo-Json -Depth 5 -Compress)"
+ throw
+ } finally {
+ # Record count if AddCount was specified
+ if ($AddCount.IsPresent -and $State.TotalProcessed -gt 0) {
+ try {
+ Add-CIPPDbItem -TenantFilter $TenantFilter -Type $Type -InputObject $State.TotalProcessed -Count
+ } catch {
+ Write-LogMessage -API 'CIPPDbItem' -tenant $TenantFilter `
+ -message "Failed to record count for $Type : $($_.Exception.Message)" -sev Warning
+ }
+ }
+
+ # Final cleanup
+ $BatchAccumulator = $null
+ [System.GC]::Collect()
+ }
}
}
diff --git a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1
index 2202e25147f9..26ece527aa48 100644
--- a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1
+++ b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1
@@ -8,7 +8,6 @@ function Add-CIPPDelegatedPermission {
$TenantFilter
)
Write-Host 'Adding Delegated Permissions'
- Set-Location (Get-Item $PSScriptRoot).FullName
if ($ApplicationId -eq $env:ApplicationID -and $TenantFilter -eq $env:TenantID) {
#return @('Cannot modify delgated permissions for CIPP-SAM on partner tenant')
diff --git a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertInactiveLicensedUsers.ps1 b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertInactiveLicensedUsers.ps1
index 964e60d5a375..09288d3fee13 100644
--- a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertInactiveLicensedUsers.ps1
+++ b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertInactiveLicensedUsers.ps1
@@ -15,10 +15,26 @@ function Get-CIPPAlertInactiveLicensedUsers {
try {
try {
- $Lookup = (Get-Date).AddDays(-90).ToUniversalTime()
+ $inactiveDays = 90
+ $excludeDisabled = $false
+ if ($InputValue -is [hashtable] -or $InputValue -is [pscustomobject]) {
+ $excludeDisabled = [bool]$InputValue.ExcludeDisabled
+ if ($null -ne $InputValue.DaysSinceLastLogin -and $InputValue.DaysSinceLastLogin -ne '') {
+ $parsedDays = 0
+ if ([int]::TryParse($InputValue.DaysSinceLastLogin.ToString(), [ref]$parsedDays) -and $parsedDays -gt 0) {
+ $inactiveDays = $parsedDays
+ }
+ }
+ } elseif ($InputValue -eq $true) {
+ # Backwards compatibility: legacy single-input boolean means exclude disabled users
+ $excludeDisabled = $true
+ }
+
+ $Lookup = (Get-Date).AddDays(-$inactiveDays).ToUniversalTime()
+ Write-Host "Checking for users inactive since $Lookup (excluding disabled: $excludeDisabled)"
# Build base filter - cannot filter assignedLicenses server-side
- $BaseFilter = if ($InputValue -eq $true) { 'accountEnabled eq true' } else { '' }
+ $BaseFilter = if ($excludeDisabled) { 'accountEnabled eq true' } else { '' }
$Uri = if ($BaseFilter) {
"https://graph.microsoft.com/beta/users?`$filter=$BaseFilter&`$select=id,UserPrincipalName,signInActivity,mail,userType,accountEnabled,assignedLicenses"
diff --git a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertSmtpAuthSuccess.ps1 b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertSmtpAuthSuccess.ps1
index 8edc398ff3cc..c7c8e57f4ea6 100644
--- a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertSmtpAuthSuccess.ps1
+++ b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertSmtpAuthSuccess.ps1
@@ -13,7 +13,7 @@ function Get-CIPPAlertSmtpAuthSuccess {
try {
# Graph API endpoint for sign-ins
- $uri = "https://graph.microsoft.com/v1.0/auditLogs/signIns?`$filter=clientAppUsed eq 'SMTP' and status/errorCode eq 0"
+ $uri = "https://graph.microsoft.com/v1.0/auditLogs/signIns?`$filter=clientAppUsed eq 'Authenticated SMTP' and status/errorCode eq 0"
# Call Graph API for the given tenant
$SignIns = New-GraphGetRequest -uri $uri -tenantid $TenantFilter
diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1
index ed24471113d9..8643b34e1084 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1
@@ -13,7 +13,7 @@ function Push-DomainAnalyserDomain {
$Filter = "PartitionKey eq 'Domains' and RowKey eq 'Domains'"
$Config = Get-CIPPAzDataTableEntity @ConfigTable -Filter $Filter
- $ValidResolvers = @('Google', 'CloudFlare', 'Quad9')
+ $ValidResolvers = @('Google', 'CloudFlare')
if ($ValidResolvers -contains $Config.Resolver) {
$Resolver = $Config.Resolver
} else {
diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Mailbox Permissions/Push-StoreMailboxPermissions.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Mailbox Permissions/Push-StoreMailboxPermissions.ps1
index c7b22a576847..42a4f93d60dc 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Mailbox Permissions/Push-StoreMailboxPermissions.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Mailbox Permissions/Push-StoreMailboxPermissions.ps1
@@ -44,21 +44,15 @@ function Push-StoreMailboxPermissions {
# Results are grouped by cmdlet name due to ReturnWithCommand
if ($ActualResult['Get-MailboxPermission']) {
Write-Information "Adding $($ActualResult['Get-MailboxPermission'].Count) mailbox permissions"
- foreach ($perm in $ActualResult['Get-MailboxPermission']) {
- $AllMailboxPermissions.Add($perm)
- }
+ $AllMailboxPermissions.AddRange($ActualResult['Get-MailboxPermission'])
}
if ($ActualResult['Get-RecipientPermission']) {
Write-Information "Adding $($ActualResult['Get-RecipientPermission'].Count) recipient permissions"
- foreach ($perm in $ActualResult['Get-RecipientPermission']) {
- $AllRecipientPermissions.Add($perm)
- }
+ $AllRecipientPermissions.AddRange($ActualResult['Get-RecipientPermission'])
}
if ($ActualResult['Get-MailboxFolderPermission']) {
Write-Information "Adding $($ActualResult['Get-MailboxFolderPermission'].Count) calendar permissions"
- foreach ($perm in $ActualResult['Get-MailboxFolderPermission']) {
- $AllCalendarPermissions.Add($perm)
- }
+ $AllCalendarPermissions.AddRange($ActualResult['Get-MailboxFolderPermission'])
}
} else {
Write-Information "Skipping non-hashtable result: $($ActualResult.GetType().Name)"
@@ -67,47 +61,28 @@ function Push-StoreMailboxPermissions {
# Combine all permissions (mailbox and recipient) into a single collection
$AllPermissions = [System.Collections.Generic.List[object]]::new()
- foreach ($perm in $AllMailboxPermissions) {
- $AllPermissions.Add($perm)
- }
- foreach ($perm in $AllRecipientPermissions) {
- $AllPermissions.Add($perm)
- }
+ $AllPermissions.AddRange($AllMailboxPermissions)
+ $AllPermissions.AddRange($AllRecipientPermissions)
Write-Information "Aggregated $($AllPermissions.Count) total permissions ($($AllMailboxPermissions.Count) mailbox + $($AllRecipientPermissions.Count) recipient)"
Write-Information "Aggregated $($AllCalendarPermissions.Count) calendar permissions"
# Store all permissions together as MailboxPermissions
if ($AllPermissions.Count -gt 0) {
- Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'MailboxPermissions' -Data $AllPermissions.ToArray()
- Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'MailboxPermissions' -Data @{ Count = $AllPermissions.Count } -Count
+ $AllPermissions | Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'MailboxPermissions' -AddCount
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($AllPermissions.Count) mailbox permission records" -sev Info
} else {
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'No mailbox permissions found to cache' -sev Info
}
- # Clear to free memory before processing calendar permissions
- $AllMailboxPermissions.Clear()
- $AllRecipientPermissions.Clear()
- $AllPermissions.Clear()
- $AllMailboxPermissions = $null
- $AllRecipientPermissions = $null
- $AllPermissions = $null
-
# Store calendar permissions separately
if ($AllCalendarPermissions.Count -gt 0) {
- Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CalendarPermissions' -Data $AllCalendarPermissions.ToArray()
- Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CalendarPermissions' -Data @{ Count = $AllCalendarPermissions.Count } -Count
+ $AllCalendarPermissions | Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CalendarPermissions' -AddCount
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($AllCalendarPermissions.Count) calendar permission records" -sev Info
} else {
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'No calendar permissions found to cache' -sev Info
}
- # Final cleanup
- $AllCalendarPermissions.Clear()
- $AllCalendarPermissions = $null
- [System.GC]::Collect()
-
return
} catch {
diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1
index f7cd1a3dc2a0..23ca76c1adeb 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1
@@ -412,6 +412,7 @@ function Push-ExecOnboardTenantQueue {
$TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress)
Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop
Write-LogMessage -API 'Onboarding' -message "Tenant onboarding succeeded for $($Relationship.customer.displayName)" -Sev 'Info'
+ Write-LogMessage -API 'NewTenant' -message "New tenant onboarded: $($Relationship.customer.displayName) ($($Relationship.customer.id))" -Sev 'Info'
} else {
$Logs.Add([PSCustomObject]@{ Date = (Get-Date).ToUniversalTime(); Log = 'API Test failed: {0}' -f $ApiError })
$OnboardingSteps.Step5.Status = 'failed'
diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandardsList.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandardsList.ps1
index 9cd477e3b68a..01acec8da950 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandardsList.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandardsList.ps1
@@ -170,7 +170,8 @@ function Push-CIPPStandardsList {
# Remove if both unchanged
if (-not $PolicyChanged -and -not $StandardTemplateChanged) {
-x [void]$ComputedStandards.Remove($Key)
+ Write-Host "NO INTUNE CHANGE: Filtering out $key for $($TenantFilter)"
+ [void]$ComputedStandards.Remove($Key)
}
}
} catch {
diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Tests/Invoke-CIPPTestsRun.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Tests/Invoke-CIPPDBTestsRun.ps1
similarity index 68%
rename from Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Tests/Invoke-CIPPTestsRun.ps1
rename to Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Tests/Invoke-CIPPDBTestsRun.ps1
index 7c30210d90a3..e4ecb119bdcb 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Tests/Invoke-CIPPTestsRun.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Tests/Invoke-CIPPDBTestsRun.ps1
@@ -25,16 +25,6 @@ function Invoke-CIPPDBTestsRun {
return $true
}
try {
- $AllTests = Get-Command -Name 'Invoke-CippTest*' -Module CIPPCore | Select-Object -ExpandProperty Name | ForEach-Object {
- $_ -replace '^Invoke-CippTest', ''
- }
-
- if ($AllTests.Count -eq 0) {
- Write-LogMessage -API 'Tests' -message 'No test functions found.' -sev Error
- return
- }
-
- Write-Information "Found $($AllTests.Count) test functions to run"
$AllTenantsList = if ($TenantFilter -eq 'allTenants') {
$DbCounts = Get-CIPPDbItem -CountsOnly -TenantFilter 'allTenants'
$TenantsWithData = $DbCounts | Where-Object { $_.Count -gt 0 } | Select-Object -ExpandProperty PartitionKey -Unique
@@ -55,31 +45,31 @@ function Invoke-CIPPDBTestsRun {
return
}
- # Build batch: all tests for all tenants
+ # Build batch of per-tenant list activities
+ # Each activity will start its own orchestrator for that tenant's tests
$Batch = foreach ($Tenant in $AllTenantsList) {
- foreach ($Test in $AllTests) {
- @{
- FunctionName = 'CIPPTest'
- TenantFilter = $Tenant
- TestId = $Test
- }
+ @{
+ FunctionName = 'CIPPTestsList'
+ TenantFilter = $Tenant
}
}
- Write-Information "Built batch of $($Batch.Count) test activities ($($AllTests.Count) tests x $($AllTenantsList.Count) tenants)"
+ Write-Information "Built batch of $($Batch.Count) tenant test list activities"
+ # Start orchestrator to dispatch per-tenant test orchestrators
$InputObject = [PSCustomObject]@{
- OrchestratorName = 'TestsRun'
+ OrchestratorName = 'TestsList'
Batch = @($Batch)
SkipLog = $true
}
+ Write-Information "InputObject: $($InputObject | ConvertTo-Json -Depth 5 -Compress)"
$InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress)
- Write-Information "Started tests orchestration with ID = '$InstanceId'"
+ Write-Information "Started tests list orchestration with ID = '$InstanceId'"
return @{
InstanceId = $InstanceId
- Message = "Tests orchestration started: $($AllTests.Count) tests for $($AllTenantsList.Count) tenants"
+ Message = "Tests orchestration started: $($AllTenantsList.Count) tenant orchestrators will be created"
}
} catch {
$ErrorMessage = Get-CippException -Exception $_
diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTest.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTest.ps1
index 844c8ff80695..5b2124a4dea0 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTest.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTest.ps1
@@ -17,7 +17,7 @@ function Push-CIPPTest {
if (-not (Get-Command $FunctionName -ErrorAction SilentlyContinue)) {
Write-LogMessage -API 'Tests' -tenant $TenantFilter -message "Test function not found: $FunctionName" -sev Error
- return
+ return @{ testRun = $false }
}
Write-Information "Executing $FunctionName for $TenantFilter"
@@ -28,5 +28,6 @@ function Push-CIPPTest {
} catch {
$ErrorMessage = Get-CippException -Exception $_
Write-LogMessage -API 'Tests' -tenant $TenantFilter -message "Failed to run test $TestId $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
+ return @{ testRun = $false }
}
}
diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTestsList.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTestsList.ps1
new file mode 100644
index 000000000000..dc5f57c87f4a
--- /dev/null
+++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTestsList.ps1
@@ -0,0 +1,67 @@
+function Push-CIPPTestsList {
+ <#
+ .FUNCTIONALITY
+ Entrypoint
+ #>
+ param($Item)
+
+ $TenantFilter = $Item.TenantFilter
+
+ try {
+ Write-Information "Building test list for tenant: $TenantFilter"
+
+ # Get all test functions
+ $AllTests = Get-Command -Name 'Invoke-CippTest*' -Module CIPPCore | Select-Object -ExpandProperty Name | ForEach-Object {
+ $_ -replace '^Invoke-CippTest', ''
+ }
+
+ if ($AllTests.Count -eq 0) {
+ Write-Information 'No test functions found'
+ return @()
+ }
+
+ # Check if tenant has data
+ $DbCounts = Get-CIPPDbItem -TenantFilter $TenantFilter -CountsOnly
+ if (($DbCounts | Measure-Object -Property DataCount -Sum).Sum -eq 0) {
+ Write-Information "Tenant $TenantFilter has no data in database. Skipping tests."
+ return @()
+ }
+
+ # Build test batch for this tenant
+ $TestBatch = foreach ($Test in $AllTests) {
+ [PSCustomObject]@{
+ FunctionName = 'CIPPTest'
+ TenantFilter = $TenantFilter
+ TestId = $Test
+ }
+ }
+
+ Write-Information "Built $($TestBatch.Count) test activities for tenant $TenantFilter"
+
+ # Start orchestrator for this tenant's tests
+ $InputObject = [PSCustomObject]@{
+ OrchestratorName = "TestsRun_$TenantFilter"
+ Batch = @($TestBatch)
+ SkipLog = $true
+ }
+
+ $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress)
+ Write-Information "Started tests orchestrator for tenant $TenantFilter with ID = '$InstanceId'"
+
+ return @{
+ Success = $true
+ Tenant = $TenantFilter
+ InstanceId = $InstanceId
+ TestCount = $TestBatch.Count
+ }
+
+ } catch {
+ $ErrorMessage = Get-CippException -Exception $_
+ Write-LogMessage -API 'Tests' -tenant $TenantFilter -message "Failed to start tests for tenant: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
+ return @{
+ Success = $false
+ Tenant = $TenantFilter
+ Error = $ErrorMessage.NormalizedError
+ }
+ }
+}
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecCPVRefresh.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecCPVRefresh.ps1
index 5459d1fab375..f8d2f9355c10 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecCPVRefresh.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecCPVRefresh.ps1
@@ -16,8 +16,10 @@ function Invoke-ExecCPVRefresh {
return @{
StatusCode = [System.Net.HttpStatusCode]::OK
Body = @{
- Results = 'CPV Refresh has been triggered'
- InstanceId = $InstanceId
+ Results = 'CPV Refresh has been triggered'
+ Metadata = @{
+ InstanceId = $InstanceId
+ }
}
}
}
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1
index dc3a91b75e05..b4d91f8bffa6 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCustomRole.ps1
@@ -178,7 +178,7 @@ function Invoke-ExecCustomRole {
}
}
default {
- $Body = Get-CIPPAzDataTableEntity @Table
+ $Body = Get-CIPPAzDataTableEntity @Table | Sort-Object -Property RowKey
$EntraRoleGroups = Get-CIPPAzDataTableEntity @AccessRoleGroupTable
$AccessIPRanges = Get-CIPPAzDataTableEntity @AccessIPRangeTable
if (!$Body) {
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1
index 46d80f061f29..6ad0becf4381 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1
@@ -1,4 +1,4 @@
-Function Invoke-ExecDnsConfig {
+function Invoke-ExecDnsConfig {
<#
.FUNCTIONALITY
Entrypoint
@@ -13,7 +13,6 @@ Function Invoke-ExecDnsConfig {
$ValidResolvers = @(
'Google'
'Cloudflare'
- 'Quad9'
)
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecAddTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecAddTenant.ps1
index 5d9b59c9616a..f1345a069ea6 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecAddTenant.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecAddTenant.ps1
@@ -31,13 +31,35 @@ function Invoke-ExecAddTenant {
} else {
# Create new tenant entry
try {
- # Get tenant information from Microsoft Graph
+ # Get tenant information from Microsoft Graph using bulk request
$headers = @{ Authorization = "Bearer $($request.body.accessToken)" }
- $Organization = (Invoke-RestMethod -Uri 'https://graph.microsoft.com/v1.0/organization' -Headers $headers -Method GET -ContentType 'application/json' -ErrorAction Stop).value
- $displayName = $Organization.displayName
- $Domains = (Invoke-RestMethod -Uri 'https://graph.microsoft.com/v1.0/domains?$top=999' -Headers $headers -Method GET -ContentType 'application/json' -ErrorAction Stop).value
- $defaultDomainName = ($Domains | Where-Object { $_.isDefault -eq $true }).id
- $initialDomainName = ($Domains | Where-Object { $_.isInitial -eq $true }).id
+
+ $BulkRequests = @(
+ @{
+ id = 'organization'
+ method = 'GET'
+ url = '/organization?$select=id,displayName'
+ }
+ @{
+ id = 'domains'
+ method = 'GET'
+ url = '/domains?$top=999'
+ }
+ )
+
+ $BulkBody = @{
+ requests = $BulkRequests
+ } | ConvertTo-Json -Depth 10
+
+ $BulkResponse = Invoke-RestMethod -Uri 'https://graph.microsoft.com/v1.0/$batch' -Headers $headers -Method POST -Body $BulkBody -ContentType 'application/json' -ErrorAction Stop
+
+ # Parse bulk response
+ $OrgResponse = ($BulkResponse.responses | Where-Object { $_.id -eq 'organization' }).body.value
+ $DomainsResponse = ($BulkResponse.responses | Where-Object { $_.id -eq 'domains' }).body.value
+
+ $displayName = $OrgResponse.displayName
+ $defaultDomainName = ($DomainsResponse | Where-Object { $_.isDefault -eq $true }).id
+ $initialDomainName = ($DomainsResponse | Where-Object { $_.isInitial -eq $true }).id
} catch {
Write-LogMessage -API 'Add-Tenant' -message "Failed to get information for tenant $tenantId - $($_.Exception.Message)" -Sev 'Critical'
throw "Failed to get information for tenant $tenantId. Make sure the tenant is properly authenticated."
@@ -64,8 +86,29 @@ function Invoke-ExecAddTenant {
# Add tenant to table
Add-CIPPAzDataTableEntity @TenantsTable -Entity $NewTenant -Force | Out-Null
- $Results = @{'message' = "Successfully added tenant $displayName ($defaultDomainName) to the tenant list with Direct Tenant status."; 'severity' = 'success' }
- Write-LogMessage -tenant $defaultDomainName -tenantid $tenantId -API 'Add-Tenant' -message "Added tenant $displayName ($defaultDomainName) with Direct Tenant status." -Sev 'Info'
+ $Results = @{'message' = "Successfully added tenant $displayName ($defaultDomainName) to the tenant list with Direct Tenant status. Permission refresh queued, the tenant will be available shortly."; 'severity' = 'success' }
+ Write-LogMessage -tenant $defaultDomainName -tenantid $tenantId -API 'NewTenant' -message "Added tenant $displayName ($defaultDomainName) with Direct Tenant status." -Sev 'Info'
+
+ # Trigger CPV refresh to push remaining permissions to this specific tenant
+ try {
+ $Queue = New-CippQueueEntry -Name "Update Permissions - $displayName" -TotalTasks 1
+ $TenantBatch = @([PSCustomObject]@{
+ defaultDomainName = $defaultDomainName
+ customerId = $tenantId
+ displayName = $displayName
+ FunctionName = 'UpdatePermissionsQueue'
+ QueueId = $Queue.RowKey
+ })
+ $InputObject = [PSCustomObject]@{
+ OrchestratorName = 'UpdatePermissionsOrchestrator'
+ Batch = @($TenantBatch)
+ }
+ Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress)
+ Write-Information "Started permissions update orchestrator for $displayName"
+ } catch {
+ Write-Warning "Failed to start permissions orchestrator: $($_.Exception.Message)"
+ }
+
}
} catch {
$Results = @{'message' = "Failed to add tenant: $($_.Exception.Message)"; 'state' = 'error'; 'severity' = 'error' }
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecTokenExchange.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecTokenExchange.ps1
index 6fd1a97bf94a..c53b7f1690b7 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecTokenExchange.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecTokenExchange.ps1
@@ -1,4 +1,4 @@
-Function Invoke-ExecTokenExchange {
+function Invoke-ExecTokenExchange {
<#
.FUNCTIONALITY
Entrypoint,AnyTenant
@@ -32,24 +32,29 @@ Function Invoke-ExecTokenExchange {
# Make sure we get the latest authentication
$auth = Get-CIPPAuthentication
- if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') {
+ # Check if environment variable is already set and not the placeholder value
+ if ($auth -and $env:ApplicationSecret -and $env:ApplicationSecret -ne 'AppSecret') {
+ $ClientSecret = $env:ApplicationSecret
+ Write-LogMessage -API $APIName -message 'Using client secret from environment variable' -Sev 'Debug'
+ } elseif ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') {
$DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets'
$Secret = Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'Secret' and RowKey eq 'Secret'"
$ClientSecret = $Secret.applicationsecret
- Write-LogMessage -API $APIName -message 'Retrieved client secret from development secrets' -Sev 'Info'
+ Write-LogMessage -API $APIName -message 'Retrieved client secret from development secrets' -Sev 'Debug'
} else {
try {
$ClientSecret = (Get-CippKeyVaultSecret -VaultName $kv -Name 'applicationsecret' -AsPlainText)
- Write-LogMessage -API $APIName -message 'Retrieved client secret from key vault' -Sev 'Info'
+ Write-LogMessage -API $APIName -message 'Retrieved client secret from key vault' -Sev 'Debug'
} catch {
Write-LogMessage -API $APIName -message "Failed to retrieve client secret: $($_.Exception.Message)" -Sev 'Error'
throw "Failed to retrieve client secret: $($_.Exception.Message)"
}
}
- if (!$ClientSecret) {
- Write-LogMessage -API $APIName -message 'Client secret is empty or null' -Sev 'Error'
- throw 'Client secret is empty or null'
+ # Check if client secret is still the default placeholder value from ARM template
+ if (!$ClientSecret -or $ClientSecret -eq 'AppSecret') {
+ Write-LogMessage -API $APIName -message 'Client secret is not configured' -Sev 'Error'
+ throw 'Application secret has not been configured. Please complete the setup process first.'
}
# Convert token request to form data and add client secret
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecUpdateRefreshToken.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecUpdateRefreshToken.ps1
index 6a6dfae29ccc..8725c2c87de1 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecUpdateRefreshToken.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecUpdateRefreshToken.ps1
@@ -21,27 +21,54 @@ function Invoke-ExecUpdateRefreshToken {
if ($env:TenantID -eq $Request.body.tenantId) {
$Secret | Add-Member -MemberType NoteProperty -Name 'RefreshToken' -Value $Request.body.refreshtoken -Force
+ # Set environment variable to make it immediately available
+ Set-Item -Path env:RefreshToken -Value $Request.body.refreshtoken -Force
} else {
Write-Host "$($env:TenantID) does not match $($Request.body.tenantId)"
$name = $Request.body.tenantId -replace '-', '_'
$secret | Add-Member -MemberType NoteProperty -Name $name -Value $Request.body.refreshtoken -Force
+ # Set environment variable to make it immediately available
+ Set-Item -Path env:$name -Value $Request.body.refreshtoken -Force
}
Add-CIPPAzDataTableEntity @DevSecretsTable -Entity $Secret -Force
} else {
if ($env:TenantID -eq $Request.body.tenantId) {
Set-CippKeyVaultSecret -VaultName $kv -Name 'RefreshToken' -SecretValue (ConvertTo-SecureString -String $Request.body.refreshtoken -AsPlainText -Force)
+ # Set environment variable to make it immediately available
+ Set-Item -Path env:RefreshToken -Value $Request.body.refreshtoken -Force
+
+ # Trigger CPV refresh for partner tenant only
+ try {
+ $Queue = New-CippQueueEntry -Name 'Update Permissions - Partner Tenant' -TotalTasks 1
+ $TenantBatch = @([PSCustomObject]@{
+ defaultDomainName = 'PartnerTenant'
+ customerId = $env:TenantID
+ displayName = '*Partner Tenant'
+ FunctionName = 'UpdatePermissionsQueue'
+ QueueId = $Queue.RowKey
+ })
+ $InputObject = [PSCustomObject]@{
+ OrchestratorName = 'UpdatePermissionsOrchestrator'
+ Batch = @($TenantBatch)
+ }
+ Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress)
+ Write-Information 'Started permissions update orchestrator for Partner Tenant'
+ } catch {
+ Write-Warning "Failed to start permissions orchestrator: $($_.Exception.Message)"
+ }
} else {
Write-Host "$($env:TenantID) does not match $($Request.body.tenantId) - we're adding a new secret for the tenant."
$name = $Request.body.tenantId
try {
Set-CippKeyVaultSecret -VaultName $kv -Name $name -SecretValue (ConvertTo-SecureString -String $Request.body.refreshtoken -AsPlainText -Force)
+ # Set environment variable to make it immediately available
+ Set-Item -Path env:$name -Value $Request.body.refreshtoken -Force
} catch {
Write-Host "Failed to set secret $name in KeyVault. $($_.Exception.Message)"
throw $_
}
}
}
- $InstanceId = Start-UpdatePermissionsOrchestrator #start the CPV refresh immediately while wizard still runs.
if ($request.body.tenantId -eq $env:TenantID) {
$TenantName = 'your partner tenant'
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-EditRoomMailbox.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-EditRoomMailbox.ps1
index 7b976f0b9861..652ca7868e3b 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-EditRoomMailbox.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-EditRoomMailbox.ps1
@@ -59,7 +59,8 @@ Function Invoke-EditRoomMailbox {
$CalendarProperties = @(
'AllowConflicts', 'AllowRecurringMeetings', 'BookingWindowInDays',
'MaximumDurationInMinutes', 'ProcessExternalMeetingMessages', 'EnforceCapacity',
- 'ForwardRequestsToDelegates', 'ScheduleOnlyDuringWorkHours ', 'AutomateProcessing'
+ 'ForwardRequestsToDelegates', 'ScheduleOnlyDuringWorkHours ', 'AutomateProcessing',
+ 'AddOrganizerToSubject', 'DeleteSubject', 'RemoveCanceledMeetings'
)
foreach ($prop in $CalendarProperties) {
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-ListRooms.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-ListRooms.ps1
index 4218cd7a19ba..d8756f9b6a6e 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-ListRooms.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-ListRooms.ps1
@@ -84,6 +84,9 @@ Function Invoke-ListRooms {
ForwardRequestsToDelegates = $CalendarProperties.ForwardRequestsToDelegates
ScheduleOnlyDuringWorkHours = $CalendarProperties.ScheduleOnlyDuringWorkHours
AutomateProcessing = $CalendarProperties.AutomateProcessing
+ AddOrganizerToSubject = $CalendarProperties.AddOrganizerToSubject
+ DeleteSubject = $CalendarProperties.DeleteSubject
+ RemoveCanceledMeetings = $CalendarProperties.RemoveCanceledMeetings
# Calendar Configuration Properties
WorkDays = if ([string]::IsNullOrWhiteSpace($CalendarConfigurationProperties.WorkDays)) { $null } else { $CalendarConfigurationProperties.WorkDays }
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1
index af575369f8f4..43289dc9cd39 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1
@@ -8,8 +8,6 @@ function Invoke-PublicWebhooks {
param($Request, $TriggerMetadata)
$Headers = $Request.Headers
-
- Set-Location (Get-Item $PSScriptRoot).Parent.FullName
$WebhookTable = Get-CIPPTable -TableName webhookTable
$WebhookIncoming = Get-CIPPTable -TableName WebhookIncoming
$Webhooks = Get-CIPPAzDataTableEntity @WebhookTable
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecNamedLocation.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecNamedLocation.ps1
index 95bfae0262f2..1a7c6b021b23 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecNamedLocation.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecNamedLocation.ps1
@@ -11,28 +11,25 @@ function Invoke-ExecNamedLocation {
$APIName = $Request.Params.CIPPEndpoint
$Headers = $Request.Headers
-
-
# Interact with query parameters or the body of the request.
$TenantFilter = $Request.Body.tenantFilter ?? $Request.Query.tenantFilter
$NamedLocationId = $Request.Body.namedLocationId ?? $Request.Query.namedLocationId
$Change = $Request.Body.change ?? $Request.Query.change
- $Content = $Request.Body.input ?? $Request.Query.input
- if ($content.value) { $content = $content.value }
+ $Content = $Request.Body.input.value ?? $Request.Query.input.value
try {
- $results = Set-CIPPNamedLocation -NamedLocationId $NamedLocationId -TenantFilter $TenantFilter -Change $Change -Content $Content -Headers $Headers
+ $Results = Set-CIPPNamedLocation -NamedLocationId $NamedLocationId -TenantFilter $TenantFilter -Change $Change -Content $Content -Headers $Headers
$StatusCode = [HttpStatusCode]::OK
} catch {
$ErrorMessage = Get-CippException -Exception $_
Write-LogMessage -headers $Headers -API $APIName -message "Failed to edit named location: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage
- $results = "Failed to edit named location. Error: $($ErrorMessage.NormalizedError)"
+ $Results = "Failed to edit named location. Error: $($ErrorMessage.NormalizedError)"
$StatusCode = [HttpStatusCode]::InternalServerError
}
return ([HttpResponseContext]@{
StatusCode = $StatusCode
- Body = @{'Results' = @($results) }
+ Body = @{'Results' = @($Results) }
})
}
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListDomainHealth.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListDomainHealth.ps1
index 6930f2cce975..8a87edb67943 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListDomainHealth.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListDomainHealth.ps1
@@ -16,7 +16,7 @@ function Invoke-ListDomainHealth {
$Filter = "PartitionKey eq 'Domains' and RowKey eq 'Domains'"
$Config = Get-CIPPAzDataTableEntity @ConfigTable -Filter $Filter
- $ValidResolvers = @('Google', 'CloudFlare', 'Quad9')
+ $ValidResolvers = @('Google', 'CloudFlare')
if ($ValidResolvers -contains $Config.Resolver) {
$Resolver = $Config.Resolver
} else {
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tools/GitHub/Invoke-ListCommunityRepos.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tools/GitHub/Invoke-ListCommunityRepos.ps1
index 79256990242f..8c33f1ed2d2a 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tools/GitHub/Invoke-ListCommunityRepos.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tools/GitHub/Invoke-ListCommunityRepos.ps1
@@ -24,7 +24,7 @@ function Invoke-ListCommunityRepos {
if (!$Request.Query.WriteAccess) {
$CIPPRoot = (Get-Item (Get-Module -Name CIPPCore).ModuleBase).Parent.Parent.FullName
- $CommunityRepos = Join-Path -Path $CIPPRoot -ChildPath 'CommunityRepos.json'
+ $CommunityRepos = Join-Path -Path $CIPPRoot -ChildPath 'Resources\CommunityRepos.json'
$DefaultCommunityRepos = Get-Content -Path $CommunityRepos -Raw | ConvertFrom-Json
$DefaultsMissing = $false
diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecListAppId.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecListAppId.ps1
index e3c8a7f62611..6c0581ad0c8e 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecListAppId.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecListAppId.ps1
@@ -1,4 +1,4 @@
-Function Invoke-ExecListAppId {
+function Invoke-ExecListAppId {
<#
.FUNCTIONALITY
Entrypoint
@@ -28,14 +28,83 @@ Function Invoke-ExecListAppId {
$env:TenantID = (Get-CippException -Exception $_)
}
}
+
+ # Get organization info and authenticated user using bulk request
+ $AuthenticatedUserDisplayName = $null
+ $AuthenticatedUserPrincipalName = $null
+ $OrgInfo = $null
+ try {
+ $BulkRequests = @(
+ @{
+ id = 'organization'
+ url = '/organization?$select=displayName,partnerTenantType'
+ method = 'GET'
+ },
+ @{
+ id = 'me'
+ url = '/me?$select=displayName,userPrincipalName'
+ method = 'GET'
+ }
+ @{
+ id = 'application'
+ url = "/applications(appId='$($env:ApplicationID)')?`$select=id,web"
+ method = 'GET'
+ }
+ )
+
+ $BulkResponse = New-GraphBulkRequest -Requests $BulkRequests -tenantid $env:TenantID -NoAuthCheck $true
+ $OrgResponse = $BulkResponse | Where-Object { $_.id -eq 'organization' }
+ $MeResponse = $BulkResponse | Where-Object { $_.id -eq 'me' }
+ $AppResponse = $BulkResponse | Where-Object { $_.id -eq 'application' }
+ if ($MeResponse.body) {
+ $AuthenticatedUserDisplayName = $MeResponse.body.displayName
+ $AuthenticatedUserPrincipalName = $MeResponse.body.userPrincipalName
+ }
+ if ($OrgResponse.body.value -and $OrgResponse.body.value.Count -gt 0) {
+ $OrgInfo = $OrgResponse.body.value[0]
+ }
+
+ if ($AppResponse.body) {
+ $AppWeb = $AppResponse.body.web
+ if ($AppWeb.redirectUris) {
+ # construct new redirect uri with current
+ $URL = ($Request.headers.'x-ms-original-url').split('/api') | Select-Object -First 1
+ $NewRedirectUri = "$($URL)/authredirect"
+ if ($AppWeb.redirectUris -notcontains $NewRedirectUri) {
+ try {
+ $RedirectUris = [system.collections.generic.list[string]]::new()
+ $AppWeb.redirectUris | ForEach-Object { $RedirectUris.Add($_) }
+ $RedirectUris.Add($NewRedirectUri)
+ $AppUpdateBody = @{
+ web = @{
+ redirectUris = $RedirectUris
+ }
+ } | ConvertTo-Json -Depth 10
+ Invoke-GraphRequest -Method PATCH -Url "https://graph.microsoft.com/v1.0/applications/$($AppResponse.body.id)" -Body $AppUpdateBody -tenantid $env:TenantID -NoAuthCheck $true
+ Write-LogMessage -message "Updated redirect URIs for application $($env:ApplicationID) to include $NewRedirectUri" -Sev 'Info'
+ } catch {
+ Write-LogMessage -message "Failed to update redirect URIs for application $($env:ApplicationID)" -LogData (Get-CippException -Exception $_) -Sev 'Warning'
+ }
+ }
+ }
+ }
+ } catch {
+ Write-LogMessage -message 'Failed to retrieve organization info and authenticated user' -LogData (Get-CippException -Exception $_) -Sev 'Warning'
+ }
+
$Results = @{
- applicationId = $env:ApplicationID
- tenantId = $env:TenantID
- refreshUrl = "https://login.microsoftonline.com/$env:TenantID/oauth2/v2.0/authorize?client_id=$env:ApplicationID&response_type=code&redirect_uri=$ResponseURL&response_mode=query&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default+offline_access+profile+openid&state=1&prompt=select_account"
+ applicationId = $env:ApplicationID
+ tenantId = $env:TenantID
+ orgName = $OrgInfo.displayName
+ authenticatedUserDisplayName = $AuthenticatedUserDisplayName
+ authenticatedUserPrincipalName = $AuthenticatedUserPrincipalName
+ isPartnerTenant = !!$OrgInfo.partnerTenantType
+ partnerTenantType = $OrgInfo.partnerTenantType
+ refreshUrl = "https://login.microsoftonline.com/$env:TenantID/oauth2/v2.0/authorize?client_id=$env:ApplicationID&response_type=code&redirect_uri=$ResponseURL&response_mode=query&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default+offline_access+profile+openid&state=1&prompt=select_account"
}
return [HttpResponseContext]@{
- StatusCode = [HttpStatusCode]::OK
- Body = $Results
- }
+ StatusCode = [HttpStatusCode]::OK
+ Body = $Results
+ }
}
diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUniversalSearchV2.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUniversalSearchV2.ps1
new file mode 100644
index 000000000000..25cb9e964f4c
--- /dev/null
+++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUniversalSearchV2.ps1
@@ -0,0 +1,22 @@
+function Invoke-ExecUniversalSearchV2 {
+ <#
+ .FUNCTIONALITY
+ Entrypoint,AnyTenant
+ .ROLE
+ CIPP.Core.Read
+ #>
+ [CmdletBinding()]
+ param($Request, $TriggerMetadata)
+
+ $TenantFilter = $Request.Query.tenantFilter
+ $SearchTerms = $Request.Query.searchTerms
+ $Limit = if ($Request.Query.limit) { [int]$Request.Query.limit } else { 10 }
+
+ $Results = Search-CIPPDbData -TenantFilter $TenantFilter -SearchTerms $SearchTerms -Types 'Users' -Limit $Limit
+
+ return [HttpResponseContext]@{
+ StatusCode = [HttpStatusCode]::OK
+ Body = @($Results)
+ }
+
+}
diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListExtensionsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListExtensionsConfig.ps1
index 45faae5f8224..eb94ced2c322 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListExtensionsConfig.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListExtensionsConfig.ps1
@@ -10,7 +10,7 @@ function Invoke-ListExtensionsConfig {
$Table = Get-CIPPTable -TableName Extensionsconfig
try {
$Config = (Get-CIPPAzDataTableEntity @Table).config
- if (Test-Json -Json $Config) {
+ if (Test-Json -Json $Config -ErrorAction SilentlyContinue) {
$Body = $Config | ConvertFrom-Json -Depth 10 -ErrorAction Stop
if ($Body.HaloPSA.TicketType -and !$Body.HaloPSA.TicketType.value) {
# translate ticket type to autocomplete format
diff --git a/Modules/CIPPCore/Public/Get-CIPPAuthentication.ps1 b/Modules/CIPPCore/Public/Get-CIPPAuthentication.ps1
index 1aecba454413..cc70d3e0a5b5 100644
--- a/Modules/CIPPCore/Public/Get-CIPPAuthentication.ps1
+++ b/Modules/CIPPCore/Public/Get-CIPPAuthentication.ps1
@@ -2,7 +2,8 @@
function Get-CIPPAuthentication {
[CmdletBinding()]
param (
- $APIName = 'Get Keyvault Authentication'
+ $APIName = 'Get Keyvault Authentication',
+ [switch]$Force
)
$Variables = @('ApplicationID', 'ApplicationSecret', 'TenantID', 'RefreshToken')
@@ -19,35 +20,8 @@ function Get-CIPPAuthentication {
}
}
Write-Host "Got secrets from dev storage. ApplicationID: $env:ApplicationID"
- #Get list of tenants that have 'directTenant' set to true
- #get directtenants directly from table, avoid get-tenants due to performance issues
- $TenantsTable = Get-CippTable -tablename 'Tenants'
- $Filter = "PartitionKey eq 'Tenants' and delegatedPrivilegeStatus eq 'directTenant'"
- $tenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter $Filter
- if ($tenants) {
- $tenants | ForEach-Object {
- $secretname = $_.customerId -replace '-', '_'
- if ($secret.$secretname) {
- $name = $_.customerId
- Set-Item -Path env:$name -Value $secret.$secretname -Force
- }
- }
- }
} else {
$keyvaultname = ($env:WEBSITE_DEPLOYMENT_ID -split '-')[0]
- #Get list of tenants that have 'directTenant' set to true
- $TenantsTable = Get-CippTable -tablename 'Tenants'
- $Filter = "PartitionKey eq 'Tenants' and delegatedPrivilegeStatus eq 'directTenant'"
- $tenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter $Filter
- if ($tenants) {
- $tenants | ForEach-Object {
- $name = $_.customerId
- $secret = Get-CippKeyVaultSecret -VaultName $keyvaultname -Name $name -AsPlainText -ErrorAction Stop
- if ($secret) {
- Set-Item -Path env:$name -Value $secret -Force
- }
- }
- }
$Variables | ForEach-Object {
Set-Item -Path env:$_ -Value (Get-CippKeyVaultSecret -VaultName $keyvaultname -Name $_ -AsPlainText -ErrorAction Stop) -Force
}
diff --git a/Modules/CIPPCore/Public/Get-CIPPTimerFunctions.ps1 b/Modules/CIPPCore/Public/Get-CIPPTimerFunctions.ps1
index d60ca8ed40cf..529cd3f3aa84 100644
--- a/Modules/CIPPCore/Public/Get-CIPPTimerFunctions.ps1
+++ b/Modules/CIPPCore/Public/Get-CIPPTimerFunctions.ps1
@@ -40,7 +40,7 @@ function Get-CIPPTimerFunctions {
}
$CIPPRoot = (Get-Item $CIPPCoreModuleRoot).Parent.Parent
- $CippTimers = Get-Content -Path $CIPPRoot\CIPPTimers.json
+ $CippTimers = Get-Content -Path $CIPPRoot\Resources\CIPPTimers.json
if ($ListAllTasks) {
$Orchestrators = $CippTimers | ConvertFrom-Json | Sort-Object -Property Priority
diff --git a/Modules/CIPPCore/Public/Get-CippKeyVaultSecret.ps1 b/Modules/CIPPCore/Public/Get-CippKeyVaultSecret.ps1
index 6de9dc5c25a6..6455e07781b0 100644
--- a/Modules/CIPPCore/Public/Get-CippKeyVaultSecret.ps1
+++ b/Modules/CIPPCore/Public/Get-CippKeyVaultSecret.ps1
@@ -40,18 +40,36 @@ function Get-CippKeyVaultSecret {
if ($env:WEBSITE_DEPLOYMENT_ID) {
$VaultName = ($env:WEBSITE_DEPLOYMENT_ID -split '-')[0]
} else {
- throw "VaultName not provided and WEBSITE_DEPLOYMENT_ID environment variable not set"
+ throw 'VaultName not provided and WEBSITE_DEPLOYMENT_ID environment variable not set'
}
}
# Get access token for Key Vault
- $token = Get-CIPPAzIdentityToken -ResourceUrl "https://vault.azure.net"
+ $token = Get-CIPPAzIdentityToken -ResourceUrl 'https://vault.azure.net'
- # Call Key Vault REST API
+ # Call Key Vault REST API with retry logic
$uri = "https://$VaultName.vault.azure.net/secrets/$Name`?api-version=7.4"
- $response = Invoke-RestMethod -Uri $uri -Headers @{
- Authorization = "Bearer $token"
- } -Method Get -ErrorAction Stop
+ $maxRetries = 3
+ $retryDelay = 2
+ $response = $null
+
+ for ($i = 0; $i -lt $maxRetries; $i++) {
+ try {
+ $response = Invoke-RestMethod -Uri $uri -Headers @{
+ Authorization = "Bearer $token"
+ } -Method Get -ErrorAction Stop
+ break
+ } catch {
+ $lastError = $_
+ if ($i -lt ($maxRetries - 1)) {
+ Start-Sleep -Seconds $retryDelay
+ $retryDelay *= 2 # Exponential backoff
+ } else {
+ Write-Error "Failed to retrieve secret '$Name' from vault '$VaultName' after $maxRetries attempts: $($_.Exception.Message)"
+ throw
+ }
+ }
+ }
# Return based on AsPlainText switch
if ($AsPlainText) {
@@ -60,12 +78,12 @@ function Get-CippKeyVaultSecret {
# Return object similar to Get-AzKeyVaultSecret for compatibility
return @{
SecretValue = ($response.value | ConvertTo-SecureString -AsPlainText -Force)
- Name = $Name
- VaultName = $VaultName
+ Name = $Name
+ VaultName = $VaultName
}
}
} catch {
- Write-Error "Failed to retrieve secret '$Name' from vault '$VaultName': $($_.Exception.Message)"
+ # Error already handled in retry loop, just rethrow
throw
}
}
diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-GraphToken.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-GraphToken.ps1
index 327052774f61..1548ebe869fc 100644
--- a/Modules/CIPPCore/Public/GraphHelper/Get-GraphToken.ps1
+++ b/Modules/CIPPCore/Public/GraphHelper/Get-GraphToken.ps1
@@ -24,6 +24,34 @@ function Get-GraphToken($tenantid, $scope, $AsApp, $AppID, $AppSecret, $refreshT
if ($tenantid -ne $env:TenantID -and $clientType.delegatedPrivilegeStatus -eq 'directTenant') {
Write-Host "Using direct tenant refresh token for $($clientType.customerId)"
$ClientRefreshToken = Get-Item -Path "env:\$($clientType.customerId)" -ErrorAction SilentlyContinue
+
+ if ($null -eq $ClientRefreshToken) {
+ # Lazy load the refresh token from Key Vault only when needed
+ Write-Host "Fetching refresh token for direct tenant $($clientType.customerId) from Key Vault"
+ try {
+ if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') {
+ # Development environment - get from table storage
+ $Table = Get-CIPPTable -tablename 'DevSecrets'
+ $Secret = Get-AzDataTableEntity @Table -Filter "PartitionKey eq 'Secret' and RowKey eq 'Secret'"
+ $secretname = $clientType.customerId -replace '-', '_'
+ if ($Secret.$secretname) {
+ Set-Item -Path "env:\$($clientType.customerId)" -Value $Secret.$secretname -Force
+ $ClientRefreshToken = Get-Item -Path "env:\$($clientType.customerId)" -ErrorAction SilentlyContinue
+ }
+ } else {
+ # Production environment - get from Key Vault
+ $keyvaultname = ($env:WEBSITE_DEPLOYMENT_ID -split '-')[0]
+ $secret = Get-CippKeyVaultSecret -VaultName $keyvaultname -Name $clientType.customerId -AsPlainText -ErrorAction Stop
+ if ($secret) {
+ Set-Item -Path "env:\$($clientType.customerId)" -Value $secret -Force
+ $ClientRefreshToken = Get-Item -Path "env:\$($clientType.customerId)" -ErrorAction SilentlyContinue
+ }
+ }
+ } catch {
+ Write-Host "Failed to retrieve refresh token for direct tenant $($clientType.customerId): $($_.Exception.Message)"
+ }
+ }
+
$refreshToken = $ClientRefreshToken.Value
}
diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1
index 75b05c0fc184..13398c7fddee 100644
--- a/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1
+++ b/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1
@@ -49,7 +49,13 @@ function Get-NormalizedError {
'*AADSTS50177' { 'AADSTS50177: The user you have used for your Secure Application Model is a guest in this tenant, or your are using GDAP and have not added the user to the correct group. Please delete the guest user to gain access to this tenant' }
'*invalid or malformed*' { 'The request is malformed. Have you finished the Setup Wizard' }
'*Windows Store repository apps feature is not supported for this tenant*' { 'This tenant does not have WinGet support available' }
- '*AADSTS650051*' { 'The application does not exist yet. Try again in 30 seconds.' }
+ '*AADSTS650051*' {
+ if ($Message -like '*service principal name is already present*') {
+ 'The application service principal already exists in this tenant. This is expected and not an error.'
+ } else {
+ 'The application does not exist yet. Try again in 30 seconds.'
+ }
+ }
'*AppLifecycle_2210*' { 'Failed to call Intune APIs: Does the tenant have a license available?' }
'*One or more added object references already exist for the following modified properties:*' { 'This user is already a member of this group.' }
'*Microsoft.Exchange.Management.Tasks.MemberAlreadyExistsException*' { 'This user is already a member of this group.' }
diff --git a/Modules/CIPPCore/Public/GraphHelper/New-passwordString.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-passwordString.ps1
index c8393455c326..89545efd1229 100644
--- a/Modules/CIPPCore/Public/GraphHelper/New-passwordString.ps1
+++ b/Modules/CIPPCore/Public/GraphHelper/New-passwordString.ps1
@@ -10,7 +10,7 @@ function New-passwordString {
$SettingsTable = Get-CippTable -tablename 'Settings'
$PasswordType = (Get-CIPPAzDataTableEntity @SettingsTable).passwordType
if ($PasswordType -eq 'Correct-Battery-Horse') {
- $Words = Get-Content .\words.txt
+ $Words = Get-Content .\Resources\words.txt
(Get-Random -InputObject $words -Count 4) -join '-'
} else {
# Generate a complex password with a maximum of 100 tries
diff --git a/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 b/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1
index 67340eaf3ab5..7ca6e0d74716 100644
--- a/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1
+++ b/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1
@@ -13,7 +13,7 @@ function New-CIPPAlertTemplate {
$AlertComment
)
$Appname = '[{"Application Name":"ACOM Azure Website","Application IDs":"23523755-3a2b-41ca-9315-f81f3f566a95"},{"Application Name":"AEM-DualAuth","Application IDs":"69893ee3-dd10-4b1c-832d-4870354be3d8"},{"Application Name":"ASM Campaign Servicing","Application IDs":"0cb7b9ec-5336-483b-bc31-b15b5788de71"},{"Application Name":"Azure Advanced Threat Protection","Application IDs":"7b7531ad-5926-4f2d-8a1d-38495ad33e17"},{"Application Name":"Azure Data Lake","Application IDs":"e9f49c6b-5ce5-44c8-925d-015017e9f7ad"},{"Application Name":"Azure Lab Services Portal","Application IDs":"835b2a73-6e10-4aa5-a979-21dfda45231c"},{"Application Name":"Azure Portal","Application IDs":"c44b4083-3bb0-49c1-b47d-974e53cbdf3c"},{"Application Name":"AzureSupportCenter","Application IDs":"37182072-3c9c-4f6a-a4b3-b3f91cacffce"},{"Application Name":"Bing","Application IDs":"9ea1ad79-fdb6-4f9a-8bc3-2b70f96e34c7"},{"Application Name":"CPIM Service","Application IDs":"bb2a2e3a-c5e7-4f0a-88e0-8e01fd3fc1f4"},{"Application Name":"CRM Power BI Integration","Application IDs":"e64aa8bc-8eb4-40e2-898b-cf261a25954f"},{"Application Name":"Dataverse","Application IDs":"00000007-0000-0000-c000-000000000000"},{"Application Name":"Enterprise Roaming and Backup","Application IDs":"60c8bde5-3167-4f92-8fdb-059f6176dc0f"},{"Application Name":"IAM Supportability","Application IDs":"a57aca87-cbc0-4f3c-8b9e-dc095fdc8978"},{"Application Name":"IrisSelectionFrontDoor","Application IDs":"16aeb910-ce68-41d1-9ac3-9e1673ac9575"},{"Application Name":"MCAPI Authorization Prod","Application IDs":"d73f4b35-55c9-48c7-8b10-651f6f2acb2e"},{"Application Name":"Media Analysis and Transformation Service","Application IDs":"944f0bd1-117b-4b1c-af26-804ed95e767e
0cd196ee-71bf-4fd6-a57c-b491ffd4fb1e"},{"Application Name":"Microsoft 365 Support Service","Application IDs":"ee272b19-4411-433f-8f28-5c13cb6fd407"},{"Application Name":"Microsoft App Access Panel","Application IDs":"0000000c-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Approval Management","Application IDs":"65d91a3d-ab74-42e6-8a2f-0add61688c74
38049638-cc2c-4cde-abe4-4479d721ed44"},{"Application Name":"Microsoft Authentication Broker","Application IDs":"29d9ed98-a469-4536-ade2-f981bc1d605e"},{"Application Name":"Microsoft Azure CLI","Application IDs":"04b07795-8ddb-461a-bbee-02f9e1bf7b46"},{"Application Name":"Microsoft Azure PowerShell","Application IDs":"1950a258-227b-4e31-a9cf-717495945fc2"},{"Application Name":"Microsoft Bing Search","Application IDs":"cf36b471-5b44-428c-9ce7-313bf84528de"},{"Application Name":"Microsoft Bing Search for Microsoft Edge","Application IDs":"2d7f3606-b07d-41d1-b9d2-0d0c9296a6e8"},{"Application Name":"Microsoft Bing Default Search Engine","Application IDs":"1786c5ed-9644-47b2-8aa0-7201292175b6"},{"Application Name":"Microsoft Defender for Cloud Apps","Application IDs":"3090ab82-f1c1-4cdf-af2c-5d7a6f3e2cc7"},{"Application Name":"Microsoft Docs","Application IDs":"18fbca16-2224-45f6-85b0-f7bf2b39b3f3"},{"Application Name":"Microsoft Dynamics ERP","Application IDs":"00000015-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Edge Insider Addons Prod","Application IDs":"6253bca8-faf2-4587-8f2f-b056d80998a7"},{"Application Name":"Microsoft Exchange Online Protection","Application IDs":"00000007-0000-0ff1-ce00-000000000000"},{"Application Name":"Microsoft Forms","Application IDs":"c9a559d2-7aab-4f13-a6ed-e7e9c52aec87"},{"Application Name":"Microsoft Graph","Application IDs":"00000003-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Intune Web Company Portal","Application IDs":"74bcdadc-2fdc-4bb3-8459-76d06952a0e9"},{"Application Name":"Microsoft Intune Windows Agent","Application IDs":"fc0f3af4-6835-4174-b806-f7db311fd2f3"},{"Application Name":"Microsoft Learn","Application IDs":"18fbca16-2224-45f6-85b0-f7bf2b39b3f3"},{"Application Name":"Microsoft Office","Application IDs":"d3590ed6-52b3-4102-aeff-aad2292ab01c"},{"Application Name":"Microsoft Office 365 Portal","Application IDs":"00000006-0000-0ff1-ce00-000000000000"},{"Application Name":"Microsoft Office Web Apps Service","Application IDs":"67e3df25-268a-4324-a550-0de1c7f97287"},{"Application Name":"Microsoft Online Syndication Partner Portal","Application IDs":"d176f6e7-38e5-40c9-8a78-3998aab820e7"},{"Application Name":"Microsoft password reset service","Application IDs":"93625bc8-bfe2-437a-97e0-3d0060024faa"},{"Application Name":"Microsoft Power BI","Application IDs":"871c010f-5e61-4fb1-83ac-98610a7e9110"},{"Application Name":"Microsoft Storefronts","Application IDs":"28b567f6-162c-4f54-99a0-6887f387bbcc"},{"Application Name":"Microsoft Stream Portal","Application IDs":"cf53fce8-def6-4aeb-8d30-b158e7b1cf83"},{"Application Name":"Microsoft Substrate Management","Application IDs":"98db8bd6-0cc0-4e67-9de5-f187f1cd1b41"},{"Application Name":"Microsoft Support","Application IDs":"fdf9885b-dd37-42bf-82e5-c3129ef5a302"},{"Application Name":"Microsoft Teams","Application IDs":"1fec8e78-bce4-4aaf-ab1b-5451cc387264"},{"Application Name":"Microsoft Teams Services","Application IDs":"cc15fd57-2c6c-4117-a88c-83b1d56b4bbe"},{"Application Name":"Microsoft Teams Web Client","Application IDs":"5e3ce6c0-2b1f-4285-8d4b-75ee78787346"},{"Application Name":"Microsoft Whiteboard Services","Application IDs":"95de633a-083e-42f5-b444-a4295d8e9314"},{"Application Name":"O365 Suite UX","Application IDs":"4345a7b9-9a63-4910-a426-35363201d503"},{"Application Name":"Office 365 Exchange Online","Application IDs":"00000002-0000-0ff1-ce00-000000000000"},{"Application Name":"Office 365 Management","Application IDs":"00b41c95-dab0-4487-9791-b9d2c32c80f2"},{"Application Name":"Office 365 Search Service","Application IDs":"66a88757-258c-4c72-893c-3e8bed4d6899"},{"Application Name":"Office 365 SharePoint Online","Application IDs":"00000003-0000-0ff1-ce00-000000000000"},{"Application Name":"Office Delve","Application IDs":"94c63fef-13a3-47bc-8074-75af8c65887a"},{"Application Name":"Office Online Add-in SSO","Application IDs":"93d53678-613d-4013-afc1-62e9e444a0a5"},{"Application Name":"Office Online Client AAD- Augmentation Loop","Application IDs":"2abdc806-e091-4495-9b10-b04d93c3f040"},{"Application Name":"Office Online Client AAD- Loki","Application IDs":"b23dd4db-9142-4734-867f-3577f640ad0c"},{"Application Name":"Office Online Client AAD- Maker","Application IDs":"17d5e35f-655b-4fb0-8ae6-86356e9a49f5"},{"Application Name":"Office Online Client MSA- Loki","Application IDs":"b6e69c34-5f1f-4c34-8cdf-7fea120b8670"},{"Application Name":"Office Online Core SSO","Application IDs":"243c63a3-247d-41c5-9d83-7788c43f1c43"},{"Application Name":"Office Online Search","Application IDs":"a9b49b65-0a12-430b-9540-c80b3332c127"},{"Application Name":"Office.com","Application IDs":"4b233688-031c-404b-9a80-a4f3f2351f90"},{"Application Name":"Office365 Shell WCSS-Client","Application IDs":"89bee1f7-5e6e-4d8a-9f3d-ecd601259da7"},{"Application Name":"OfficeClientService","Application IDs":"0f698dd4-f011-4d23-a33e-b36416dcb1e6"},{"Application Name":"OfficeHome","Application IDs":"4765445b-32c6-49b0-83e6-1d93765276ca"},{"Application Name":"OfficeShredderWacClient","Application IDs":"4d5c2d63-cf83-4365-853c-925fd1a64357"},{"Application Name":"OMSOctopiPROD","Application IDs":"62256cef-54c0-4cb4-bcac-4c67989bdc40"},{"Application Name":"OneDrive SyncEngine","Application IDs":"ab9b8c07-8f02-4f72-87fa-80105867a763"},{"Application Name":"OneNote","Application IDs":"2d4d3d8e-2be3-4bef-9f87-7875a61c29de"},{"Application Name":"Outlook Mobile","Application IDs":"27922004-5251-4030-b22d-91ecd9a37ea4"},{"Application Name":"Partner Customer Delegated Admin Offline Processor","Application IDs":"a3475900-ccec-4a69-98f5-a65cd5dc5306"},{"Application Name":"Password Breach Authenticator","Application IDs":"bdd48c81-3a58-4ea9-849c-ebea7f6b6360"},{"Application Name":"Power BI Service","Application IDs":"00000009-0000-0000-c000-000000000000"},{"Application Name":"SharedWithMe","Application IDs":"ffcb16e8-f789-467c-8ce9-f826a080d987"},{"Application Name":"SharePoint Online Web Client Extensibility","Application IDs":"08e18876-6177-487e-b8b5-cf950c1e598c"},{"Application Name":"Signup","Application IDs":"b4bddae8-ab25-483e-8670-df09b9f1d0ea"},{"Application Name":"Skype for Business Online","Application IDs":"00000004-0000-0ff1-ce00-000000000000"},{"Application Name":"Sway","Application IDs":"905fcf26-4eb7-48a0-9ff0-8dcc7194b5ba"},{"Application Name":"Universal Store Native Client","Application IDs":"268761a2-03f3-40df-8a8b-c3db24145b6b"},{"Application Name":"Vortex [wsfed enabled]","Application IDs":"5572c4c0-d078-44ce-b81c-6cbf8d3ed39e"},{"Application Name":"Windows Azure Active Directory","Application IDs":"00000002-0000-0000-c000-000000000000"},{"Application Name":"Windows Azure Service Management API","Application IDs":"797f4846-ba00-4fd7-ba43-dac1f8f63013"},{"Application Name":"WindowsDefenderATP Portal","Application IDs":"a3b79187-70b2-4139-83f9-6016c58cd27b"},{"Application Name":"Windows Search","Application IDs":"26a7ee05-5602-4d76-a7ba-eae8b7b67941"},{"Application Name":"Windows Spotlight","Application IDs":"1b3c667f-cde3-4090-b60b-3d2abd0117f0"},{"Application Name":"Windows Store for Business","Application IDs":"45a330b1-b1ec-4cc1-9161-9f03992aa49f"},{"Application Name":"Yammer","Application IDs":"00000005-0000-0ff1-ce00-000000000000"},{"Application Name":"Yammer Web","Application IDs":"c1c74fed-04c9-4704-80dc-9f79a2e515cb"},{"Application Name":"Yammer Web Embed","Application IDs":"e1ef36fd-b883-4dbf-97f0-9ece4b576fc6"}]' | ConvertFrom-Json | Where-Object -Property 'Application IDs' -EQ $data.applicationId
- $HTMLTemplate = Get-Content 'TemplateEmail.html' -Raw | Out-String
+ $HTMLTemplate = Get-Content 'Resources\TemplateEmail.html' -Raw | Out-String
$Title = ''
$IntroText = ''
$ButtonUrl = ''
diff --git a/Modules/CIPPCore/Public/Search-CIPPDbData.ps1 b/Modules/CIPPCore/Public/Search-CIPPDbData.ps1
index 1ef40a996260..a782821fa989 100644
--- a/Modules/CIPPCore/Public/Search-CIPPDbData.ps1
+++ b/Modules/CIPPCore/Public/Search-CIPPDbData.ps1
@@ -27,6 +27,9 @@ function Search-CIPPDbData {
.PARAMETER MaxResultsPerType
Maximum number of results to return per type. Default is unlimited (0)
+ .PARAMETER Limit
+ Maximum total number of results to return across all types. Default is unlimited (0)
+
.EXAMPLE
Search-CIPPDbData -TenantFilter 'contoso.onmicrosoft.com' -SearchTerms 'john.doe' -Types 'Users', 'Groups'
@@ -63,7 +66,10 @@ function Search-CIPPDbData {
[switch]$MatchAll,
[Parameter(Mandatory = $false)]
- [int]$MaxResultsPerType = 0
+ [int]$MaxResultsPerType = 0,
+
+ [Parameter(Mandatory = $false)]
+ [int]$Limit = 0
)
try {
@@ -91,9 +97,9 @@ function Search-CIPPDbData {
}
# Process each data type
- foreach ($Type in $Types) {
+ :typeLoop foreach ($Type in $Types) {
Write-Verbose "Searching type: $Type"
- $TypeResults = [System.Collections.Generic.List[object]]::new()
+ $TypeResultCount = 0
# Search across all tenants
foreach ($Tenant in $TenantsToSearch) {
@@ -145,10 +151,18 @@ function Search-CIPPDbData {
Timestamp = $Item.Timestamp
}
$Results.Add($ResultItem)
+ $TypeResultCount++
+
+ # Check total limit first
+ if ($Limit -gt 0 -and $Results.Count -ge $Limit) {
+ Write-Verbose "Reached total limit of $Limit results"
+ break typeLoop
+ }
# Check max results per type
- if ($MaxResultsPerType -gt 0 -and $Results.Count -ge $MaxResultsPerType) {
- break
+ if ($MaxResultsPerType -gt 0 -and $TypeResultCount -ge $MaxResultsPerType) {
+ Write-Verbose "Reached max results per type ($MaxResultsPerType) for type '$Type'"
+ continue typeLoop
}
} catch {
Write-Verbose "Failed to parse JSON for $($Item.RowKey): $($_.Exception.Message)"
diff --git a/Modules/CIPPCore/Public/Set-CIPPDBCacheCASMailboxes.ps1 b/Modules/CIPPCore/Public/Set-CIPPDBCacheCASMailboxes.ps1
index 283b056f9ba6..02c12125f6e4 100644
--- a/Modules/CIPPCore/Public/Set-CIPPDBCacheCASMailboxes.ps1
+++ b/Modules/CIPPCore/Public/Set-CIPPDBCacheCASMailboxes.ps1
@@ -15,20 +15,9 @@ function Set-CIPPDBCacheCASMailboxes {
try {
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching CAS mailboxes' -sev Debug
- # Use Generic List for better memory efficiency with large datasets
- $CASMailboxList = [System.Collections.Generic.List[PSObject]]::new()
- $CASMailboxesResponse = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-CasMailbox'
- foreach ($Mailbox in $CASMailboxesResponse) {
- $CASMailboxList.Add($Mailbox)
- }
-
- Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CASMailbox' -Data $CASMailboxList.ToArray()
- Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CASMailbox' -Data @{ Count = $CASMailboxList.Count } -Count
-
- $CASMailboxesResponse = $null
- $CASMailboxList.Clear()
- $CASMailboxList = $null
- [System.GC]::Collect()
+ # Stream CAS mailboxes directly to batch processor
+ New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-CasMailbox' |
+ Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CASMailbox' -AddCount
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached CAS mailboxes successfully' -sev Debug
diff --git a/Modules/CIPPCore/Public/Set-CIPPDBCacheMailboxes.ps1 b/Modules/CIPPCore/Public/Set-CIPPDBCacheMailboxes.ps1
index 8c78abbdab4b..27fb33c5cbc6 100644
--- a/Modules/CIPPCore/Public/Set-CIPPDBCacheMailboxes.ps1
+++ b/Modules/CIPPCore/Public/Set-CIPPDBCacheMailboxes.ps1
@@ -44,13 +44,8 @@ function Set-CIPPDBCacheMailboxes {
MessageCopyForSentAsEnabled))
}
- $Mailboxes = $MailboxList.ToArray()
- $RawMailboxes = $null
- $MailboxList.Clear()
- $MailboxList = $null
+ $Mailboxes | Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Mailboxes' -AddCount
- Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Mailboxes' -Data $Mailboxes
- Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Mailboxes' -Data $Mailboxes -Count
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($Mailboxes.Count) mailboxes successfully" -sev Debug
# Start orchestrator to cache mailbox permissions in batches
diff --git a/Modules/CIPPCore/Public/Set-CIPPDBCacheUsers.ps1 b/Modules/CIPPCore/Public/Set-CIPPDBCacheUsers.ps1
index 3355fbdd9f33..72f498142664 100644
--- a/Modules/CIPPCore/Public/Set-CIPPDBCacheUsers.ps1
+++ b/Modules/CIPPCore/Public/Set-CIPPDBCacheUsers.ps1
@@ -15,23 +15,13 @@ function Set-CIPPDBCacheUsers {
try {
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching users' -sev Debug
- $Users = [System.Collections.Generic.List[PSObject]]::new()
- $UsersResponse = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $TenantFilter
- foreach ($User in $UsersResponse) {
- $Users.Add($User)
- }
-
- Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Users' -Data $Users.ToArray()
- Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Users' -Data @{ Count = $Users.Count } -Count
-
- $Users.Clear()
- $Users = $null
- $UsersResponse = $null
- [System.GC]::Collect()
+ # Stream users directly from Graph API to batch processor
+ New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $TenantFilter |
+ Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Users' -AddCount
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached users successfully' -sev Debug
} catch {
- Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to cache users: $($_.Exception.Message)" -sev Error -LogData (Get-CippException -Exception $_)
+ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to cache users: $($_.Exception.Message)" -sev Error
}
}
diff --git a/Modules/CIPPCore/Public/Set-CIPPDbCacheTestData.ps1 b/Modules/CIPPCore/Public/Set-CIPPDbCacheTestData.ps1
new file mode 100644
index 000000000000..5a5f6b2094f1
--- /dev/null
+++ b/Modules/CIPPCore/Public/Set-CIPPDbCacheTestData.ps1
@@ -0,0 +1,84 @@
+function Set-CIPPDbCacheTestData {
+ <#
+ .SYNOPSIS
+ Generates test data for cache performance testing
+
+ .DESCRIPTION
+ Creates 50,000 test objects with ~3KB of data each to test streaming performance
+
+ .PARAMETER TenantFilter
+ The tenant to use for test data
+
+ .PARAMETER Count
+ Number of test objects to generate (default: 50000)
+ #>
+ [CmdletBinding()]
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$TenantFilter,
+
+ [Parameter(Mandatory = $false)]
+ [int]$Count = 50000
+ )
+
+ try {
+ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Generating $Count test objects" -sev Debug
+
+ # Generate sample data to reach ~3KB per object
+ $sampleText = @'
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
+Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+'@ * 10 # Repeat to get ~3KB
+
+ $startTime = Get-Date
+
+ Write-Information "[Set-CIPPDbCacheTestData] Starting generation of $Count test objects for tenant $TenantFilter"
+ # Stream test objects directly to batch processor
+ 1..$Count | ForEach-Object {
+ [PSCustomObject]@{
+ id = [guid]::NewGuid().ToString()
+ displayName = "Test User $_"
+ userPrincipalName = "testuser$_@$TenantFilter"
+ mail = "testuser$_@$TenantFilter"
+ givenName = 'Test'
+ surname = "User $_"
+ jobTitle = 'Test Engineer'
+ department = 'Testing Department'
+ officeLocation = "Test Office $_"
+ mobilePhone = "+1-555-000-$($_.ToString().PadLeft(4, '0'))"
+ businessPhones = @("+1-555-001-$($_.ToString().PadLeft(4, '0'))")
+ accountEnabled = $true
+ createdDateTime = (Get-Date).ToString('o')
+ lastSignInDateTime = (Get-Date).AddDays(-1).ToString('o')
+ description = $sampleText
+ companyName = 'Test Company'
+ country = 'United States'
+ city = 'Test City'
+ state = 'Test State'
+ postalCode = '12345'
+ streetAddress = '123 Test Street'
+ proxyAddresses = @("SMTP:testuser$_@$TenantFilter", "smtp:alias$_@$TenantFilter")
+ assignedLicenses = @(
+ @{ skuId = [guid]::NewGuid().ToString(); disabledPlans = @() }
+ )
+ customAttribute1 = 'Custom Value 1'
+ customAttribute2 = 'Custom Value 2'
+ customAttribute3 = 'Custom Value 3'
+ additionalData = $sampleText
+ }
+ } | Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'TestData' -AddCount
+
+ $endTime = Get-Date
+ $duration = ($endTime - $startTime).TotalSeconds
+ $objectsPerSecond = [math]::Round($Count / $duration, 2)
+
+ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter `
+ -message "Generated $Count test objects in $duration seconds ($objectsPerSecond objects/sec)" -sev Debug
+
+ } catch {
+ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter `
+ -message "Failed to generate test data: $($_.Exception.Message)" -sev Error
+ }
+}
diff --git a/Modules/CIPPCore/Public/Set-CIPPNamedLocation.ps1 b/Modules/CIPPCore/Public/Set-CIPPNamedLocation.ps1
index 888622741f86..3e23d70b8edf 100644
--- a/Modules/CIPPCore/Public/Set-CIPPNamedLocation.ps1
+++ b/Modules/CIPPCore/Public/Set-CIPPNamedLocation.ps1
@@ -24,12 +24,14 @@ function Set-CIPPNamedLocation {
$ActionDescription = "Adding location $Content to named location"
}
'removeIp' {
- $NamedLocations.ipRanges = @($NamedLocations.ipRanges | Where-Object -Property cidrAddress -NE $Content)
- $ActionDescription = "Removing IP $Content from named location"
+ $IpsToRemove = @($Content)
+ $NamedLocations.ipRanges = @($NamedLocations.ipRanges | Where-Object { $_.cidrAddress -notin $IpsToRemove })
+ $ActionDescription = "Removing IP(s) $($IpsToRemove -join ', ') from named location"
}
'removeLocation' {
- $NamedLocations.countriesAndRegions = @($NamedLocations.countriesAndRegions | Where-Object { $_ -NE $Content })
- $ActionDescription = "Removing location $Content from named location"
+ $LocationsToRemove = @($Content)
+ $NamedLocations.countriesAndRegions = @($NamedLocations.countriesAndRegions | Where-Object { $_ -notin $LocationsToRemove })
+ $ActionDescription = "Removing location(s) $($LocationsToRemove -join ', ') from named location"
}
'rename' {
$NamedLocations.displayName = $Content
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1
index fbaf8ce3251a..e4831bb8f50e 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1
@@ -37,7 +37,6 @@ function Invoke-CIPPStandardAddDKIM {
$TestResult = Test-CIPPStandardLicense -StandardName 'AddDKIM' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -61,10 +60,10 @@ function Invoke-CIPPStandardAddDKIM {
# Check for errors in the batch results. Cannot continue if there are errors.
$ErrorCounter = 0
$ErrorMessages = [System.Collections.Generic.List[string]]::new()
- $BatchResults | ForEach-Object {
- if ($_.error) {
+ foreach ($Result in $BatchResults) {
+ if ($Result.error) {
$ErrorCounter++
- $ErrorMessage = Get-NormalizedError -Message $_.error
+ $ErrorMessage = Get-NormalizedError -Message $Result.error
$ErrorMessages.Add($ErrorMessage)
}
}
@@ -89,8 +88,8 @@ function Invoke-CIPPStandardAddDKIM {
'*.teams-sbc.dk'
)
- $AllDomains = ($BatchResults | Where-Object { $_.DomainName }).DomainName | ForEach-Object {
- $Domain = $_
+ $AllDomains = foreach ($DomainName in ($BatchResults | Where-Object { $_.DomainName }).DomainName) {
+ $Domain = $DomainName
foreach ($ExclusionDomain in $ExclusionDomains) {
if ($Domain -like $ExclusionDomain) {
$Domain = $null
@@ -98,8 +97,8 @@ function Invoke-CIPPStandardAddDKIM {
}
if ($null -ne $Domain) { $Domain }
}
- $DKIM = $BatchResults | Where-Object { $_.Domain } | Select-Object Domain, Enabled, Status | ForEach-Object {
- $Domain = $_
+ $DKIM = foreach ($DkimConfig in ($BatchResults | Where-Object { $_.Domain } | Select-Object Domain, Enabled, Status)) {
+ $Domain = $DkimConfig
foreach ($ExclusionDomain in $ExclusionDomains) {
if ($Domain.Domain -like $ExclusionDomain) {
$Domain = $null
@@ -132,37 +131,37 @@ function Invoke-CIPPStandardAddDKIM {
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Trying to enable DKIM for:$($NewDomains -join ', ' ) $($SetDomains.Domain -join ', ')" -sev Info
# New-domains
- $Request = $NewDomains | ForEach-Object {
+ $Request = foreach ($Domain in $NewDomains) {
@{
CmdletInput = @{
CmdletName = 'New-DkimSigningConfig'
- Parameters = @{ KeySize = 2048; DomainName = $_; Enabled = $true }
+ Parameters = @{ KeySize = 2048; DomainName = $Domain; Enabled = $true }
}
}
}
if ($null -ne $Request) { $BatchResults = New-ExoBulkRequest -tenantid $Tenant -cmdletArray @($Request) -useSystemMailbox $true }
- $BatchResults | ForEach-Object {
- if ($_.error) {
+ foreach ($Result in $BatchResults) {
+ if ($Result.error) {
$ErrorCounter ++
- $ErrorMessage = Get-NormalizedError -Message $_.error
+ $ErrorMessage = Get-NormalizedError -Message $Result.error
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to enable DKIM. Error: $ErrorMessage" -sev Error
}
}
# Set-domains
- $Request = $SetDomains | ForEach-Object {
+ $Request = foreach ($Domain in $SetDomains) {
@{
CmdletInput = @{
CmdletName = 'Set-DkimSigningConfig'
- Parameters = @{ Identity = $_.Domain; Enabled = $true }
+ Parameters = @{ Identity = $Domain.Domain; Enabled = $true }
}
}
}
if ($null -ne $Request) { $BatchResults = New-ExoBulkRequest -tenantid $Tenant -cmdletArray @($Request) -useSystemMailbox $true }
- $BatchResults | ForEach-Object {
- if ($_.error) {
+ foreach ($Result in $BatchResults) {
+ if ($Result.error) {
$ErrorCounter ++
- $ErrorMessage = Get-NormalizedError -Message $_.error
+ $ErrorMessage = Get-NormalizedError -Message $Result.error
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set DKIM. Error: $ErrorMessage" -sev Error
}
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDMARCToMOERA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDMARCToMOERA.ps1
index 288053375351..63fa43bc6ece 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDMARCToMOERA.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDMARCToMOERA.ps1
@@ -49,17 +49,17 @@ function Invoke-CIPPStandardAddDMARCToMOERA {
try {
$Domains = New-GraphGetRequest -scope 'https://admin.microsoft.com/.default' -TenantID $Tenant -Uri 'https://admin.microsoft.com/admin/api/Domains/List' | Where-Object -Property Name -Like '*.onmicrosoft.com'
- $CurrentInfo = $Domains | ForEach-Object {
+ $CurrentInfo = foreach ($Domain in $Domains) {
# Get current DNS records that matches _dmarc hostname and TXT type
- $RecordsResponse = New-GraphGetRequest -scope 'https://admin.microsoft.com/.default' -TenantID $Tenant -Uri "https://admin.microsoft.com/admin/api/Domains/Records?domainName=$($_.Name)"
+ $RecordsResponse = New-GraphGetRequest -scope 'https://admin.microsoft.com/.default' -TenantID $Tenant -Uri "https://admin.microsoft.com/admin/api/Domains/Records?domainName=$($Domain.Name)"
$AllRecords = $RecordsResponse | Select-Object -ExpandProperty DnsRecords
$CurrentRecords = $AllRecords | Where-Object { $_.HostName -eq '_dmarc' -and $_.Type -eq 'TXT' }
- Write-Information "Found $($CurrentRecords.count) DMARC records for domain $($_.Name)"
+ Write-Information "Found $($CurrentRecords.count) DMARC records for domain $($Domain.Name)"
if ($CurrentRecords.count -eq 0) {
#record not found, return a model with Match set to false
[PSCustomObject]@{
- DomainName = $_.Name
+ DomainName = $Domain.Name
Match = $false
CurrentRecord = $null
}
@@ -76,13 +76,13 @@ function Invoke-CIPPStandardAddDMARCToMOERA {
# Compare the current record with the expected record model
if (!(Compare-Object -ReferenceObject $RecordModel -DifferenceObject $CurrentRecordModel -Property HostName, TtlValue, Type, Value)) {
[PSCustomObject]@{
- DomainName = $_.Name
+ DomainName = $Domain.Name
Match = $true
CurrentRecord = $CurrentRecord
}
} else {
[PSCustomObject]@{
- DomainName = $_.Name
+ DomainName = $Domain.Name
Match = $false
CurrentRecord = $CurrentRecord
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1
index 79f02f28e15f..253c88731a29 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1
@@ -55,16 +55,23 @@ function Invoke-CIPPStandardAntiPhishPolicy {
$TestResult = Test-CIPPStandardLicense -StandardName 'AntiPhishPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AntiPhishPolicy'
- $ServicePlans = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus?$select=servicePlans' -tenantid $Tenant
- $ServicePlans = $ServicePlans.servicePlans.servicePlanName
- $MDOLicensed = $ServicePlans -contains "ATP_ENTERPRISE"
+ $TenantCapabilities = Get-CIPPTenantCapabilities -TenantFilter $Tenant
+ $MDOLicensed = $TenantCapabilities.ATP_ENTERPRISE -eq $true
Write-Information "MDOLicensed: $MDOLicensed"
+ # Single data retrieval for Get-AntiPhishRule (used twice) with error handling
+ try {
+ $AllAntiPhishRule = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AntiPhishRule'
+ } catch {
+ $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
+ Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the AntiPhishRule state for $Tenant. Error: $ErrorMessage" -Sev Error
+ return
+ }
+
# Use custom name if provided, otherwise use default for backward compatibility
$PolicyName = if ($Settings.name) { $Settings.name } else { 'CIPP Default Anti-Phishing Policy' }
$PolicyList = @($PolicyName, 'CIPP Default Anti-Phishing Policy','Default Anti-Phishing Policy')
@@ -79,7 +86,7 @@ function Invoke-CIPPStandardAntiPhishPolicy {
# Derive rule name from policy name, but check for old names for backward compatibility
$DesiredRuleName = "$PolicyName Rule"
$RuleList = @($DesiredRuleName, 'CIPP Default Anti-Phishing Rule','CIPP Default Anti-Phishing Policy')
- $ExistingRule = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AntiPhishRule' | Where-Object -Property Name -In $RuleList | Select-Object -First 1
+ $ExistingRule = $AllAntiPhishRule | Where-Object -Property Name -In $RuleList | Select-Object -First 1
if ($null -eq $ExistingRule.Name) {
# No existing rule - use the derived name
$RuleName = $DesiredRuleName
@@ -166,7 +173,7 @@ function Invoke-CIPPStandardAntiPhishPolicy {
$AcceptedDomains = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AcceptedDomain'
- $RuleState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AntiPhishRule' |
+ $RuleState = $AllAntiPhishRule |
Where-Object -Property Name -EQ $RuleName |
Select-Object Name, AntiPhishPolicy, Priority, RecipientDomainIs
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiSpamSafeList.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiSpamSafeList.ps1
index 614a1a7f036c..e959f26818ea 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiSpamSafeList.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiSpamSafeList.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardAntiSpamSafeList {
$TestResult = Test-CIPPStandardLicense -StandardName 'AntiSpamSafeList' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AntiSpamSafeList'
@@ -69,7 +68,6 @@ function Invoke-CIPPStandardAntiSpamSafeList {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
if ($StateIsCorrect -eq $false) {
try {
$null = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-HostedConnectionFilterPolicy' -cmdParams @{
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1
index c01aade26629..318e751bfd2e 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1
@@ -36,7 +36,7 @@ function Invoke-CIPPStandardAppDeploy {
Write-Information "Running AppDeploy standard for tenant $($Tenant)."
$AppsToAdd = $Settings.appids -split ','
- $AppExists = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/servicePrincipals?$top=999' -tenantid $Tenant
+ $AppExists = New-CIPPDbRequest -TenantFilter $Tenant -Type 'ServicePrincipals'
$Mode = $Settings.mode ?? 'copy'
$ExpectedValue = [PSCustomObject]@{ state = 'Configured correctly' }
@@ -271,6 +271,13 @@ function Invoke-CIPPStandardAppDeploy {
}
}
}
+
+ # Refresh service principals cache after remediation
+ try {
+ Set-CIPPDBCacheServicePrincipals -TenantFilter $Tenant
+ } catch {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to refresh service principals cache after remediation: $($_.Exception.Message)" -sev Warning
+ }
}
if ($Settings.alert) {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAssignmentFilterTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAssignmentFilterTemplate.ps1
index 8d371752daa4..c0147bddaa93 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAssignmentFilterTemplate.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAssignmentFilterTemplate.ps1
@@ -31,8 +31,14 @@ function Invoke-CIPPStandardAssignmentFilterTemplate {
#>
param($Tenant, $Settings)
+ $TestResult = Test-CIPPStandardLicense -StandardName 'AssignmentFilterTemplate' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1')
+
+ if ($TestResult -eq $false) {
+ return $true
+ } #we're done.
+
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AssignmentFilterTemplate'
- $existingFilters = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/assignmentFilters' -tenantid $tenant
+ $existingFilters = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/assignmentFilters?$select=id,displayName,description,platform,rule,assignmentFilterManagementType' -tenantid $tenant
$Settings.assignmentFilterTemplate ? ($Settings | Add-Member -NotePropertyName 'TemplateList' -NotePropertyValue $Settings.assignmentFilterTemplate) : $null
@@ -50,7 +56,6 @@ function Invoke-CIPPStandardAssignmentFilterTemplate {
$CurrentValue = if ($MissingFilters.Count -eq 0) { [PSCustomObject]@{'state' = 'Configured correctly' } } else { [PSCustomObject]@{'MissingFilters' = @($MissingFilters) } }
if ($Settings.remediate -eq $true) {
- Write-Host "Settings: $($Settings.TemplateList | ConvertTo-Json)"
foreach ($Template in $AssignmentFilterTemplates) {
Write-Information "Processing template: $($Template.displayName)"
try {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1
index 3bf5fd8ba0d7..3cf2a1536f67 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1
@@ -36,7 +36,6 @@ function Invoke-CIPPStandardAtpPolicyForO365 {
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AtpPolicyForO365'
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
try {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1
index 4d1c5a4d1b68..21d7ff92f9fd 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1
@@ -38,12 +38,10 @@ function Invoke-CIPPStandardAuditLog {
$TestResult = Test-CIPPStandardLicense -StandardName 'AuditLog' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AuditLog'
- Write-Host ($Settings | ConvertTo-Json)
$AuditLogEnabled = [bool](New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AdminAuditLogConfig' -Select UnifiedAuditLogIngestionEnabled).UnifiedAuditLogIngestionEnabled
$CurrentValue = [PSCustomObject]@{
@@ -54,8 +52,6 @@ function Invoke-CIPPStandardAuditLog {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
$DehydratedTenant = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig' -Select IsDehydrated).IsDehydrated
if ($DehydratedTenant -eq $true) {
try {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuthMethodsSettings.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuthMethodsSettings.ps1
index cd0332008211..c9f92324fb17 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuthMethodsSettings.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuthMethodsSettings.ps1
@@ -54,8 +54,6 @@ function Invoke-CIPPStandardAuthMethodsSettings {
$ValidStates = @('default', 'enabled', 'disabled')
if (($Settings.remediate -eq $true -or $Settings.alert -eq $true) -and
($ReportSuspiciousActivityState -notin $ValidStates -or $SystemCredentialState -notin $ValidStates)) {
- Write-Host "ReportSuspiciousActivity: $($ReportSuspiciousActivityState)"
- Write-Host "SystemCredential: $($SystemCredentialState)"
Write-LogMessage -API 'Standards' -tenant $tenant -message 'AuthMethodsPolicy: Invalid state parameter set' -sev Error
return
}
@@ -75,7 +73,6 @@ function Invoke-CIPPStandardAuthMethodsSettings {
$StateSetCorrectly = $ReportSuspiciousActivityCorrect -and $SystemCredentialCorrect
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
if ($StateSetCorrectly -eq $false) {
try {
$body = [PSCustomObject]@{
@@ -85,7 +82,6 @@ function Invoke-CIPPStandardAuthMethodsSettings {
$body.reportSuspiciousActivitySettings.state = $ReportSuspiciousActivityState
$body.systemCredentialPreferences.state = $SystemCredentialState
- Write-Host "Body: $($body | ConvertTo-Json -Depth 10 -Compress)"
# Update settings
$null = New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy' -AsApp $true -Type PATCH -Body ($body | ConvertTo-Json -Depth 10 -Compress) -ContentType 'application/json'
Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully configured authentication methods policy settings: Report Suspicious Activity ($ReportSuspiciousActivityState), System Credential Preferences ($SystemCredentialState)" -sev Info
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoAddProxy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoAddProxy.ps1
index 43b3a943ecf3..5549e79b9bc1 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoAddProxy.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoAddProxy.ps1
@@ -104,11 +104,10 @@ function Invoke-CIPPStandardAutoAddProxy {
}
}
$BatchResults = New-ExoBulkRequest -tenantid $Tenant -cmdletArray @($bulkRequest)
- $BatchResults | ForEach-Object {
- if ($_.error) {
- $ErrorMessage = Get-CippException -Exception $_.error
- Write-Host "Failed to apply new email policy to $($_.target) Error: $($ErrorMessage.NormalizedError)"
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to apply proxy address to $($_.error.target) Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
+ foreach ($Result in $BatchResults) {
+ if ($Result.error) {
+ $ErrorMessage = Get-CippException -Exception $Result.error
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to apply proxy address to $($Result.error.target) Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
}
}
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoArchive.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoArchive.ps1
index 971c4bf55e56..e8c0b7d6be5a 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoArchive.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoArchive.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardAutoArchive {
$TestResult = Test-CIPPStandardLicense -StandardName 'AutoArchive' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
}
@@ -65,8 +64,6 @@ function Invoke-CIPPStandardAutoArchive {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($CorrectState) {
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Auto-archiving threshold is already set to $CurrentState%." -Sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoArchiveMailbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoArchiveMailbox.ps1
index ab6d73d2d135..ef649fcedfbb 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoArchiveMailbox.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoArchiveMailbox.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardAutoArchiveMailbox {
$TestResult = Test-CIPPStandardLicense -StandardName 'AutoArchiveMailbox' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
}
@@ -65,8 +64,6 @@ function Invoke-CIPPStandardAutoArchiveMailbox {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($CorrectState) {
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Auto enable archive mailbox is already set to $StateValue." -Sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1
index 72b4552da9bb..40dc7014d49e 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1
@@ -33,7 +33,6 @@ function Invoke-CIPPStandardAutoExpandArchive {
$TestResult = Test-CIPPStandardLicense -StandardName 'AutoExpandArchive' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AutoExpandArchive'
@@ -54,8 +53,6 @@ function Invoke-CIPPStandardAutoExpandArchive {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($CurrentState) {
Write-LogMessage -API 'Standards' -tenant $tenant -message 'Auto Expanding Archive is already enabled.' -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotProfile.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotProfile.ps1
index 98cda9ca282b..508e56635b2e 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotProfile.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotProfile.ps1
@@ -45,7 +45,6 @@ function Invoke-CIPPStandardAutopilotProfile {
# Get the current configuration
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
try {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotStatusPage.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotStatusPage.ps1
index 17ac191280b5..1fcf83165723 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotStatusPage.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotStatusPage.ps1
@@ -43,7 +43,6 @@ function Invoke-CIPPStandardAutopilotStatusPage {
# Get current Autopilot enrollment status page configuration
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
try {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1
index 608aadf0e58c..95cb110b2076 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardBookings {
$TestResult = Test-CIPPStandardLicense -StandardName 'Bookings' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'Bookings'
@@ -72,7 +71,6 @@ function Invoke-CIPPStandardBookings {
return
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
if ($StateIsCorrect -eq $false) {
try {
$null = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-OrganizationConfig' -cmdParams @{ BookingsEnabled = $WantedState } -useSystemMailbox $true
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1
index 467279f5838d..a0efe5f4ea5b 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1
@@ -36,6 +36,13 @@ function Invoke-CIPPStandardBranding {
#>
param($Tenant, $Settings)
+
+ $TestResult = Test-CIPPStandardLicense -StandardName 'Branding' -TenantFilter $Tenant -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2')
+
+ if ($TestResult -eq $false) {
+ return $true
+ } #we're done.
+
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'Branding'
$TenantId = Get-Tenants | Where-Object -Property defaultDomainName -EQ $Tenant
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1
index 48feb07b2c45..28b2adefcbe5 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardCloudMessageRecall {
$TestResult = Test-CIPPStandardLicense -StandardName 'CloudMessageRecall' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'CloudMessageRecall'
@@ -74,7 +73,6 @@ function Invoke-CIPPStandardCloudMessageRecall {
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
if ($StateIsCorrect -eq $false) {
try {
$null = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-OrganizationConfig' -cmdParams @{ MessageRecallEnabled = $WantedState } -useSystemMailbox $true
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1
index 95765f0dd839..07da79a1fc70 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1
@@ -39,10 +39,9 @@ function Invoke-CIPPStandardConditionalAccessTemplate {
$TestP2 = Test-CIPPStandardLicense -StandardName 'ConditionalAccessTemplate_p2' -TenantFilter $Tenant -RequiredCapabilities @('AAD_PREMIUM_P2') -SkipLog
if ($TestResult -eq $false) {
#writing to each item that the license is not present.
- $settings.TemplateList | ForEach-Object {
- Set-CIPPStandardsCompareField -FieldName "standards.ConditionalAccessTemplate.$($_.value)" -FieldValue 'This tenant does not have the required license for this standard.' -Tenant $Tenant
+ foreach ($Template in $settings.TemplateList) {
+ Set-CIPPStandardsCompareField -FieldName "standards.ConditionalAccessTemplate.$($Template.value)" -FieldValue 'This tenant does not have the required license for this standard.' -Tenant $Tenant
}
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1
index 1eb6f3370fcd..9ff4c6c9ebcd 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1
@@ -31,7 +31,13 @@ function Invoke-CIPPStandardCustomBannedPasswordList {
#>
param($Tenant, $Settings)
- Write-Host "All params received: $Tenant, $tenant, $($Settings | ConvertTo-Json -Depth 10 -Compress)"
+
+ $TestResult = Test-CIPPStandardLicense -StandardName 'CustomBannedPasswordList' -TenantFilter $Tenant -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2')
+
+ if ($TestResult -eq $false) {
+ return $true
+ } #we're done.
+
$PasswordRuleTemplateId = '5cf42378-d67d-4f36-ba46-e8b86229381d'
# Parse and validate banned words from input
$BannedWordsInput = $Settings.BannedWords
@@ -43,12 +49,6 @@ function Invoke-CIPPStandardCustomBannedPasswordList {
# Split input by commas, newlines, or semicolons and clean up
$BannedWordsList = $BannedWordsInput -split '[,;\r\n]+' | ForEach-Object { ($_.Trim()) } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique
- # Validate word count
- if ($BannedWordsList.Count -gt 1000) {
- Write-LogMessage -API 'Standards' -tenant $tenant -message "CustomBannedPasswordList: Too many banned words provided ($($BannedWordsList.Count)). Maximum allowed is 1000." -sev Error
- return
- }
-
# Validate word length (4-16 characters), remove duplicates and invalid words
$ValidBannedWordsList = [System.Collections.Generic.List[string]]::new()
$InvalidWords = [System.Collections.Generic.List[string]]::new()
@@ -67,6 +67,12 @@ function Invoke-CIPPStandardCustomBannedPasswordList {
Write-LogMessage -API 'Standards' -tenant $tenant -message "CustomBannedPasswordList: Invalid words found in input (must be 4-16 characters). Please remove the following words: $($InvalidWords -join ', ')" -sev Warning
}
+ # Validate word count after filtering
+ if ($BannedWordsList.Count -gt 1000) {
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "CustomBannedPasswordList: Too many valid banned words ($($BannedWordsList.Count)). Maximum allowed is 1000." -sev Error
+ return
+ }
+
# Get existing directory settings for password rules
try {
$ExistingSettings = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/settings' -tenantid $Tenant | Where-Object { $_.templateId -eq $PasswordRuleTemplateId }
@@ -77,10 +83,7 @@ function Invoke-CIPPStandardCustomBannedPasswordList {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate Custom Banned Password List'
-
if ($null -eq $ExistingSettings) {
- Write-Host 'No existing Custom Banned Password List found, creating new one'
# Create new directory setting with default values if it doesn't exist
try {
$Body = @{
@@ -121,7 +124,6 @@ function Invoke-CIPPStandardCustomBannedPasswordList {
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to create Custom Banned Password List: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
}
} else {
- Write-Host 'Existing Custom Banned Password List found, updating it'
# Update existing directory setting
try {
# Get the current passwords and check if all the new words are already in the list
@@ -131,13 +133,11 @@ function Invoke-CIPPStandardCustomBannedPasswordList {
# Check if the new words are already in the list
$NewBannedWords = $BannedWordsList | Where-Object { $CurrentBannedWords -notcontains $_ }
if ($NewBannedWords.Count -eq 0 -and ($ExistingSettings.values | Where-Object { $_.name -eq 'EnableBannedPasswordCheck' }).value -eq 'True') {
- Write-Host 'No new words to add'
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Custom Banned Password List is already configured with $($CurrentBannedWords.Count) words." -sev Info
} else {
- Write-Host "$($NewBannedWords.Count) new words to add"
$AllBannedWords = [System.Collections.Generic.List[string]]::new()
- $NewBannedWords | ForEach-Object { $AllBannedWords.Add($_) }
- $CurrentBannedWords | ForEach-Object { $AllBannedWords.Add($_) }
+ foreach ($Word in $NewBannedWords) { $AllBannedWords.Add($Word) }
+ foreach ($Word in $CurrentBannedWords) { $AllBannedWords.Add($Word) }
$AllBannedWords = $AllBannedWords | Select-Object -Unique -First 1000 | Where-Object { $_ -ne $null }
$Body = @{
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultPlatformRestrictions.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultPlatformRestrictions.ps1
index 23945c2b628b..349cb5b4aea3 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultPlatformRestrictions.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultPlatformRestrictions.ps1
@@ -44,7 +44,6 @@ function Invoke-CIPPStandardDefaultPlatformRestrictions {
$TestResult = Test-CIPPStandardLicense -StandardName 'DefaultPlatformRestrictions' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1
index d3397e85af50..684f768cd70a 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1
@@ -41,7 +41,6 @@ function Invoke-CIPPStandardDefaultSharingLink {
# Determine the desired sharing link type (default to Internal if not specified)
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
$DesiredSharingLinkType = $Settings.SharingLinkType.value ?? 'Internal'
@@ -84,7 +83,6 @@ function Invoke-CIPPStandardDefaultSharingLink {
# Check if the current state matches the desired configuration
$StateIsCorrect = ($CurrentState.DefaultSharingLinkType -eq $DesiredSharingLinkTypeValue) -and ($CurrentState.DefaultLinkPermission -eq 1)
- Write-Host "currentstate: $($CurrentState.DefaultSharingLinkType), $($CurrentState.DefaultLinkPermission). Desired: $DesiredSharingLinkTypeValue, 1"
if ($Settings.remediate -eq $true) {
if ($StateIsCorrect -eq $true) {
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Default sharing link settings are already configured correctly (Type: $DesiredSharingLinkType, Permission: View)" -Sev Info
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1
index ac95dcd2b1fd..561f1420bbf1 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardDelegateSentItems {
$TestResult = Test-CIPPStandardLicense -StandardName 'DelegateSentItems' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
#$Rerun -Type Standard -Tenant $Tenant -API 'DelegateSentItems' -Settings $Settings
@@ -62,26 +61,22 @@ function Invoke-CIPPStandardDelegateSentItems {
state = 'Configured correctly'
}
- Write-Host "Mailboxes: $($Mailboxes.Count)"
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($Mailboxes) {
try {
- $Request = $Mailboxes | ForEach-Object {
+ $Request = foreach ($Mailbox in $Mailboxes) {
@{
CmdletInput = @{
CmdletName = 'Set-Mailbox'
- Parameters = @{Identity = $_.UserPrincipalName ; MessageCopyForSendOnBehalfEnabled = $true; MessageCopyForSentAsEnabled = $true }
+ Parameters = @{Identity = $Mailbox.UserPrincipalName ; MessageCopyForSendOnBehalfEnabled = $true; MessageCopyForSentAsEnabled = $true }
}
}
}
$BatchResults = New-ExoBulkRequest -tenantid $Tenant -cmdletArray @($Request)
- $BatchResults | ForEach-Object {
- if ($_.error) {
- $ErrorMessage = Get-CippException -Exception $_.error
- Write-Host "Failed to apply Delegate Sent Items Style to $($_.target) Error: $($ErrorMessage.NormalizedError)"
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to apply Delegate Sent Items Style to $($_.error.target) Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
+ foreach ($Result in $BatchResults) {
+ if ($Result.error) {
+ $ErrorMessage = Get-CippException -Exception $Result.error
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to apply Delegate Sent Items Style to $($Result.error.target) Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
}
}
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Delegate Sent Items Style applied for $($Mailboxes.Count - $BatchResults.Error.Count) mailboxes" -sev Info
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1
index ccb08c900fe3..8a6ed59c11f8 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardDeletedUserRentention {
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DeletedUserRetention'
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -78,8 +77,6 @@ function Invoke-CIPPStandardDeletedUserRentention {
$StateSetCorrectly = if ($CurrentInfo.deletedUserPersonalSiteRetentionPeriodInDays -eq $WantedState) { $true } else { $false }
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($StateSetCorrectly -eq $false) {
try {
$body = [PSCustomObject]@{
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployContactTemplates.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployContactTemplates.ps1
index 067feb5c567d..f92a0742d070 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployContactTemplates.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployContactTemplates.ps1
@@ -37,7 +37,6 @@ function Invoke-CIPPStandardDeployContactTemplates {
$TestResult = Test-CIPPStandardLicense -StandardName 'DeployContactTemplates' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -254,8 +253,10 @@ function Invoke-CIPPStandardDeployContactTemplates {
if ($ProcessedCount -gt 0) {
Write-LogMessage -API $APIName -tenant $Tenant -message "DeployContactTemplate: Successfully processed $ProcessedCount contacts" -sev Info
- # Wait for contacts to propagate before updating additional fields
- Start-Sleep -Seconds 1
+ # Wait for contacts to propagate before updating additional fields (only needed for small batches)
+ if ($ProcessedCount -le 3) {
+ Start-Sleep -Seconds 1
+ }
# Second pass: Update contacts with additional fields (only if needed)
$UpdateFailures = 0
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployMailContact.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployMailContact.ps1
index 8dc3df011a96..0bc606b7adbf 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployMailContact.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployMailContact.ps1
@@ -38,7 +38,6 @@ function Invoke-CIPPStandardDeployMailContact {
$TestResult = Test-CIPPStandardLicense -StandardName 'DeployMailContact' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1
index 5b43e27dbee0..b27abf52a5df 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardDisableAddShortcutsToOneDrive {
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableAddShortcutsToOneDrive'
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -77,8 +76,6 @@ function Invoke-CIPPStandardDisableAddShortcutsToOneDrive {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($StateIsCorrect -eq $false) {
try {
$CurrentState | Set-CIPPSPOTenant -Properties @{DisableAddToOneDrive = $WantedState }
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1
index 5a83612fc3b9..42e28232c593 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1
@@ -36,7 +36,6 @@ function Invoke-CIPPStandardDisableAdditionalStorageProviders {
$TestResult = Test-CIPPStandardLicense -StandardName 'DisableAdditionalStorageProviders' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableAdditionalStorageProviders'
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1
index d648b2d8589b..6c54c0b78c1b 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1
@@ -37,7 +37,6 @@ function Invoke-CIPPStandardDisableBasicAuthSMTP {
$TestResult = Test-CIPPStandardLicense -StandardName 'DisableBasicAuthSMTP' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableBasicAuthSMTP'
@@ -54,8 +53,6 @@ function Invoke-CIPPStandardDisableBasicAuthSMTP {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($CurrentInfo.SmtpClientAuthenticationDisabled -and $SMTPusers.Count -eq 0) {
Write-LogMessage -API 'Standards' -tenant $tenant -message 'SMTP Basic Authentication for tenant and all users is already disabled' -sev Info
} else {
@@ -68,14 +65,26 @@ function Invoke-CIPPStandardDisableBasicAuthSMTP {
Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable SMTP Basic Authentication. Error: $ErrorMessage" -sev Error
}
- # Disable SMTP Basic Authentication for all users
- $SMTPusers | ForEach-Object {
- try {
- New-ExoRequest -tenantid $Tenant -cmdlet 'Set-CASMailbox' -cmdParams @{ Identity = $_.Guid; SmtpClientAuthenticationDisabled = $null } -UseSystemMailbox $true
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Disabled SMTP Basic Authentication for $($_.DisplayName), $($_.PrimarySmtpAddress)" -sev Info
- } catch {
- $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable SMTP Basic Authentication for $($_.DisplayName), $($_.PrimarySmtpAddress). Error: $ErrorMessage" -sev Error
+ # Disable SMTP Basic Authentication for all users using bulk request
+ if ($SMTPusers.Count -gt 0) {
+ $BulkRequest = foreach ($User in $SMTPusers) {
+ @{
+ CmdletInput = @{
+ CmdletName = 'Set-CASMailbox'
+ Parameters = @{ Identity = $User.Guid; SmtpClientAuthenticationDisabled = $null }
+ }
+ }
+ }
+ $BatchResults = New-ExoBulkRequest -tenantid $Tenant -cmdletArray @($BulkRequest) -useSystemMailbox $true
+ foreach ($Result in $BatchResults) {
+ if ($Result.error) {
+ $ErrorMessage = Get-NormalizedError -Message $Result.error
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable SMTP Basic Authentication for $($Result.target). Error: $ErrorMessage" -sev Error
+ }
+ }
+ $SuccessCount = ($BatchResults | Where-Object { -not $_.error }).Count
+ if ($SuccessCount -gt 0) {
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Disabled SMTP Basic Authentication for $SuccessCount users" -sev Info
}
}
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1
index ff49eefefeef..62cce48dcb8f 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1
@@ -37,7 +37,6 @@ function Invoke-CIPPStandardDisableExchangeOnlinePowerShell {
$TestResult = Test-CIPPStandardLicense -StandardName 'DisableExchangeOnlinePowerShell' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableExchangeOnlinePowerShell'
@@ -58,22 +57,21 @@ function Invoke-CIPPStandardDisableExchangeOnlinePowerShell {
if ($PowerShellEnabledCount -gt 0) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Started disabling Exchange Online PowerShell for $PowerShellEnabledCount users." -sev Info
- $Request = $UsersWithPowerShell | ForEach-Object {
+ $Request = foreach ($User in $UsersWithPowerShell) {
@{
CmdletInput = @{
CmdletName = 'Set-User'
- Parameters = @{Identity = $_.Guid; RemotePowerShellEnabled = $false }
+ Parameters = @{Identity = $User.Guid; RemotePowerShellEnabled = $false }
}
}
}
$BatchResults = New-ExoBulkRequest -tenantid $tenant -cmdletArray @($Request)
$SuccessCount = 0
- $BatchResults | ForEach-Object {
- if ($_.error) {
- $ErrorMessage = Get-NormalizedError -Message $_.error
- Write-Host "Failed to disable Exchange Online PowerShell for $($_.target). Error: $ErrorMessage"
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to disable Exchange Online PowerShell for $($_.target). Error: $ErrorMessage" -sev Error
+ foreach ($Result in $BatchResults) {
+ if ($Result.error) {
+ $ErrorMessage = Get-NormalizedError -Message $Result.error
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to disable Exchange Online PowerShell for $($Result.target). Error: $ErrorMessage" -sev Error
} else {
$SuccessCount++
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1
index d8bf42551e92..480bf6a37c6c 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1
@@ -36,7 +36,6 @@ function Invoke-CIPPStandardDisableExternalCalendarSharing {
$TestResult = Test-CIPPStandardLicense -StandardName 'DisableExternalCalendarSharing' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -51,15 +50,25 @@ function Invoke-CIPPStandardDisableExternalCalendarSharing {
if ($Settings.remediate -eq $true) {
if ($CurrentInfo.Enabled) {
- $CurrentInfo | ForEach-Object {
- try {
- New-ExoRequest -tenantid $Tenant -cmdlet 'Set-SharingPolicy' -cmdParams @{ Identity = $_.Id ; Enabled = $false } -UseSystemMailbox $true
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully disabled external calendar sharing for the policy $($_.Name)" -sev Info
- } catch {
- $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable external calendar sharing for the policy $($_.Name). Error: $ErrorMessage" -sev Error
+ $BulkRequest = foreach ($Policy in $CurrentInfo) {
+ @{
+ CmdletInput = @{
+ CmdletName = 'Set-SharingPolicy'
+ Parameters = @{ Identity = $Policy.Id ; Enabled = $false }
+ }
}
}
+ $BatchResults = New-ExoBulkRequest -tenantid $Tenant -cmdletArray @($BulkRequest) -useSystemMailbox $true
+ foreach ($Result in $BatchResults) {
+ if ($Result.error) {
+ $ErrorMessage = Get-NormalizedError -Message $Result.error
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable external calendar sharing. Error: $ErrorMessage" -sev Error
+ }
+ }
+ $SuccessCount = ($BatchResults | Where-Object { -not $_.error }).Count
+ if ($SuccessCount -gt 0) {
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully disabled external calendar sharing for $SuccessCount policies" -sev Info
+ }
} else {
Write-LogMessage -API 'Standards' -tenant $tenant -message 'External calendar sharing is already disabled' -sev Info
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1
index de4789652b77..baa729c653b2 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1
@@ -37,47 +37,79 @@ function Invoke-CIPPStandardDisableGuests {
if ($TestResult -eq $false) {
#writing to each item that the license is not present.
- $settings.TemplateList | ForEach-Object {
+ foreach ($Template in $settings.TemplateList) {
Set-CIPPStandardsCompareField -FieldName 'standards.DisableGuests' -FieldValue 'This tenant does not have the required license for this standard.' -Tenant $Tenant
}
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
$checkDays = if ($Settings.days) { $Settings.days } else { 90 } # Default to 90 days if not set. Pre v8.5.0 compatibility
$Days = (Get-Date).AddDays(-$checkDays).ToUniversalTime()
- $Lookup = $Days.ToString('o')
$AuditLookup = (Get-Date).AddDays(-7).ToUniversalTime().ToString('o')
try {
- $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$filter=createdDateTime le $Lookup and userType eq 'Guest' and accountEnabled eq true &`$select=id,UserPrincipalName,signInActivity,mail,userType,accountEnabled,createdDateTime,externalUserState" -scope 'https://graph.microsoft.com/.default' -tenantid $Tenant |
- Where-Object { $_.signInActivity.lastSuccessfulSignInDateTime -le $Days }
+ $AllUsers = New-CIPPDbRequest -TenantFilter $Tenant -Type 'Users'
+
+ $GraphRequest = $AllUsers | Where-Object {
+ $_.userType -eq 'Guest' -and
+ $_.accountEnabled -eq $true -and
+ ($null -ne $_.createdDateTime -and [DateTime]$_.createdDateTime -le $Days) -and
+ ($null -eq $_.signInActivity -or $null -eq $_.signInActivity.lastSuccessfulSignInDateTime -or [DateTime]$_.signInActivity.lastSuccessfulSignInDateTime -le $Days)
+ }
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the DisableGuests state for $Tenant. Error: $ErrorMessage" -Sev Error
return
}
- $RecentlyReactivatedUsers = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/auditLogs/directoryAudits?`$filter=activityDisplayName eq 'Enable account' and activityDateTime ge $AuditLookup" -scope 'https://graph.microsoft.com/.default' -tenantid $Tenant |
- ForEach-Object { $_.targetResources[0].id } | Select-Object -Unique)
+ $AuditResults = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/auditLogs/directoryAudits?`$filter=activityDisplayName eq 'Enable account' and activityDateTime ge $AuditLookup" -scope 'https://graph.microsoft.com/.default' -tenantid $Tenant
+ $RecentlyReactivatedUsers = @(foreach ($AuditEntry in $AuditResults) { $AuditEntry.targetResources[0].id }) | Select-Object -Unique
$GraphRequest = $GraphRequest | Where-Object { -not ($RecentlyReactivatedUsers -contains $_.id) }
if ($Settings.remediate -eq $true) {
if ($GraphRequest.Count -gt 0) {
- foreach ($guest in $GraphRequest) {
- try {
- $null = New-GraphPostRequest -type Patch -tenantid $tenant -uri "https://graph.microsoft.com/beta/users/$($guest.id)" -body '{"accountEnabled":"false"}'
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Disabling guest $($guest.UserPrincipalName) ($($guest.id)). Last sign-in: $($guest.signInActivity.lastSuccessfulSignInDateTime)" -sev Info
- } catch {
- $ErrorMessage = Get-CippException -Exception $_
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable guest $($guest.UserPrincipalName) ($($guest.id)): $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
+ $int = 0
+ $BulkRequests = foreach ($guest in $GraphRequest) {
+ @{
+ id = $int++
+ method = 'PATCH'
+ url = "users/$($guest.id)"
+ body = @{ accountEnabled = $false }
+ 'headers' = @{
+ 'Content-Type' = 'application/json'
+ }
+ }
+ }
+
+ try {
+ $BulkResults = New-GraphBulkRequest -tenantid $tenant -Requests @($BulkRequests)
+
+ for ($i = 0; $i -lt $BulkResults.Count; $i++) {
+ $result = $BulkResults[$i]
+ $guest = $GraphRequest[$i]
+
+ if ($result.status -eq 200 -or $result.status -eq 204) {
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Disabled guest $($guest.UserPrincipalName) ($($guest.id)). Last sign-in: $($guest.signInActivity.lastSuccessfulSignInDateTime)" -sev Info
+ } else {
+ $errorMsg = if ($result.body.error.message) { $result.body.error.message } else { "Unknown error (Status: $($result.status))" }
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable guest $($guest.UserPrincipalName) ($($guest.id)): $errorMsg" -sev Error
+ }
}
+ } catch {
+ $ErrorMessage = Get-CippException -Exception $_
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to process bulk disable guests request: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
+ }
+
+ # Refresh user cache after remediation
+ try {
+ Set-CIPPDBCacheUsers -TenantFilter $Tenant
+ } catch {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to refresh user cache after remediation: $($_.Exception.Message)" -sev Warning
}
} else {
Write-LogMessage -API 'Standards' -tenant $tenant -message "No guests accounts with a login longer than $checkDays days ago." -sev Info
}
-
}
if ($Settings.alert -eq $true) {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1
index 4919d502721f..79075e249008 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardDisableM365GroupUsers {
$TestResult = Test-CIPPStandardLicense -StandardName 'DisableM365GroupUsers' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1
index 42c955f5186f..5def2a8ca32d 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1
@@ -38,7 +38,6 @@ function Invoke-CIPPStandardDisableOutlookAddins {
$TestResult = Test-CIPPStandardLicense -StandardName 'DisableOutlookAddins' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -63,8 +62,9 @@ function Invoke-CIPPStandardDisableOutlookAddins {
foreach ($Role in $RolesToRemove) {
try {
- New-ExoRequest -tenantid $Tenant -cmdlet 'Get-ManagementRoleAssignment' -cmdParams @{ RoleAssignee = $CurrentInfo.Identity; Role = $Role } | ForEach-Object {
- New-ExoRequest -tenantid $Tenant -cmdlet 'Remove-ManagementRoleAssignment' -cmdParams @{ Identity = $_.Guid; Confirm = $false } -UseSystemMailbox $true
+ $RoleAssignments = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-ManagementRoleAssignment' -cmdParams @{ RoleAssignee = $CurrentInfo.Identity; Role = $Role }
+ foreach ($Assignment in $RoleAssignments) {
+ New-ExoRequest -tenantid $Tenant -cmdlet 'Remove-ManagementRoleAssignment' -cmdParams @{ Identity = $Assignment.Guid; Confirm = $false } -UseSystemMailbox $true
Write-LogMessage -API 'Standards' -tenant $tenant -message "Disabled Outlook add-in role: $Role" -sev Debug
}
} catch {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1
index 3e29a5d81a3b..6f2fd8f23a03 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1
@@ -38,7 +38,6 @@ function Invoke-CIPPStandardDisableReshare {
$TestResult = Test-CIPPStandardLicense -StandardName 'DisableReshare' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableResourceMailbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableResourceMailbox.ps1
index ca70a1330275..a15bcae71e8a 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableResourceMailbox.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableResourceMailbox.ps1
@@ -36,14 +36,18 @@ function Invoke-CIPPStandardDisableResourceMailbox {
$TestResult = Test-CIPPStandardLicense -StandardName 'DisableResourceMailbox' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
# Get all users that are able to be
try {
- $UserList = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999&$filter=accountEnabled eq true and onPremisesSyncEnabled ne true and assignedLicenses/$count eq 0&$count=true' -Tenantid $Tenant -ComplexFilter |
- Where-Object { $_.userType -eq 'Member' }
+ $AllUsers = New-CIPPDbRequest -TenantFilter $Tenant -Type 'Users'
+ $UserList = $AllUsers | Where-Object {
+ $_.accountEnabled -eq $true -and
+ $_.onPremisesSyncEnabled -ne $true -and
+ ($null -eq $_.assignedLicenses -or $_.assignedLicenses.Count -eq 0) -and
+ $_.userType -eq 'Member'
+ }
$ResourceMailboxList = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' -cmdParams @{ Filter = "RecipientTypeDetails -eq 'RoomMailbox' -or RecipientTypeDetails -eq 'EquipmentMailbox'" } -Select 'UserPrincipalName,DisplayName,RecipientTypeDetails,ExternalDirectoryObjectId' |
Where-Object { $_.ExternalDirectoryObjectId -in $UserList.id }
} catch {
@@ -53,19 +57,44 @@ function Invoke-CIPPStandardDisableResourceMailbox {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
+ if ($ResourceMailboxList.Count -gt 0) {
+ $int = 0
+ $BulkRequests = foreach ($Mailbox in $ResourceMailboxList) {
+ @{
+ id = $int++
+ method = 'PATCH'
+ url = "users/$($Mailbox.ExternalDirectoryObjectId)"
+ body = @{ accountEnabled = $false }
+ 'headers' = @{
+ 'Content-Type' = 'application/json'
+ }
+ }
+ }
+ try {
+ $BulkResults = New-GraphBulkRequest -tenantid $Tenant -Requests @($BulkRequests)
- if ($ResourceMailboxList) {
- Write-Host "Resource Mailboxes to disable: $($ResourceMailboxList.Count)"
- $ResourceMailboxList | ForEach-Object {
- try {
- New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/users/$($_.ExternalDirectoryObjectId)" -type PATCH -body '{"accountEnabled":"false"}' -tenantid $Tenant
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Entra account for $($_.RecipientTypeDetails), $($_.DisplayName), $($_.UserPrincipalName) disabled." -sev Info
- } catch {
- $ErrorMessage = Get-CippException -Exception $_
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to disable Entra account for $($_.RecipientTypeDetails), $($_.DisplayName), $($_.UserPrincipalName). Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
+ for ($i = 0; $i -lt $BulkResults.Count; $i++) {
+ $result = $BulkResults[$i]
+ $Mailbox = $ResourceMailboxList[$i]
+
+ if ($result.status -eq 200 -or $result.status -eq 204) {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Entra account for $($Mailbox.RecipientTypeDetails), $($Mailbox.DisplayName), $($Mailbox.UserPrincipalName) disabled." -sev Info
+ } else {
+ $errorMsg = if ($result.body.error.message) { $result.body.error.message } else { "Unknown error (Status: $($result.status))" }
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to disable Entra account for $($Mailbox.RecipientTypeDetails), $($Mailbox.DisplayName), $($Mailbox.UserPrincipalName): $errorMsg" -sev Error
+ }
}
+ } catch {
+ $ErrorMessage = Get-CippException -Exception $_
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to process bulk disable resource mailboxes request: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
+ }
+
+ # Refresh user cache after remediation
+ try {
+ Set-CIPPDBCacheUsers -TenantFilter $Tenant
+ } catch {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to refresh user cache after remediation: $($_.Exception.Message)" -sev Warning
}
} else {
Write-LogMessage -API 'Standards' -tenant $Tenant -message 'All Entra accounts for resource mailboxes are already disabled.' -sev Info
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1
index 41dc31365e43..f06f7e0091f0 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSelfServiceLicenses.ps1
@@ -52,32 +52,31 @@ function Invoke-CIPPStandardDisableSelfServiceLicenses {
$exclusions = $settings.Exclusions -split (',')
}
- $selfServiceItems | ForEach-Object {
+ foreach ($Item in $selfServiceItems) {
$body = $null
- if ($_.policyValue -eq 'Enabled' -AND ($_.productId -in $exclusions)) {
+ if ($Item.policyValue -eq 'Enabled' -AND ($Item.productId -in $exclusions)) {
# Self service is enabled on product and productId is in exclusions, skip
}
- if ($_.policyValue -eq 'Disabled' -AND ($_.productId -in $exclusions)) {
+ if ($Item.policyValue -eq 'Disabled' -AND ($Item.productId -in $exclusions)) {
# Self service is disabled on product and productId is in exclusions, enable
$body = '{ "policyValue": "Enabled" }'
}
- if ($_.policyValue -eq 'Enabled' -AND ($_.productId -notin $exclusions)) {
+ if ($Item.policyValue -eq 'Enabled' -AND ($Item.productId -notin $exclusions)) {
# Self service is enabled on product and productId is NOT in exclusions, disable
$body = '{ "policyValue": "Disabled" }'
}
- if ($_.policyValue -eq 'Disabled' -AND ($_.productId -notin $exclusions)) {
+ if ($Item.policyValue -eq 'Disabled' -AND ($Item.productId -notin $exclusions)) {
# Self service is disabled on product and productId is NOT in exclusions, skip
}
try {
if ($body) {
- $product = $_
- New-GraphPOSTRequest -scope 'aeb86249-8ea3-49e2-900b-54cc8e308f85/.default' -uri "https://licensing.m365.microsoft.com/v1.0/policies/AllowSelfServicePurchase/products/$($product.productId)" -tenantid $Tenant -body $body -type PUT
+ New-GraphPOSTRequest -scope 'aeb86249-8ea3-49e2-900b-54cc8e308f85/.default' -uri "https://licensing.m365.microsoft.com/v1.0/policies/AllowSelfServicePurchase/products/$($Item.productId)" -tenantid $Tenant -body $body -type PUT
}
} catch {
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set product status for $($product.productId) with body $($body) for reason: $($_.Exception.Message)" -sev Error
- #Write-Error "Failed to disable product $($product.productName):$($_.Exception.Message)"
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set product status for $($Item.productId) with body $($body) for reason: $($_.Exception.Message)" -sev Error
+ #Write-Error "Failed to disable product $($Item.productName):$($_.Exception.Message)"
}
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1
index 88b113d8d554..4c09192f756f 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1
@@ -40,7 +40,6 @@ function Invoke-CIPPStandardDisableSharePointLegacyAuth {
$TestResult = Test-CIPPStandardLicense -StandardName 'DisableSharePointLegacyAuth' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharedMailbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharedMailbox.ps1
index 01b501aec319..1f1202c0a164 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharedMailbox.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharedMailbox.ps1
@@ -37,7 +37,11 @@ function Invoke-CIPPStandardDisableSharedMailbox {
param($Tenant, $Settings)
try {
- $UserList = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999&$filter=accountEnabled eq true and onPremisesSyncEnabled ne true&$count=true' -Tenantid $Tenant -ComplexFilter
+ $AllUsers = New-CIPPDbRequest -TenantFilter $Tenant -Type 'Users'
+ $UserList = $AllUsers | Where-Object {
+ $_.accountEnabled -eq $true -and
+ $_.onPremisesSyncEnabled -ne $true
+ }
$SharedMailboxList = (New-GraphGetRequest -uri "https://outlook.office365.com/adminapi/beta/$($Tenant)/Mailbox" -Tenantid $Tenant -scope ExchangeOnline | Where-Object { $_.RecipientTypeDetails -eq 'SharedMailbox' -or $_.RecipientTypeDetails -eq 'SchedulingMailbox' -and $_.UserPrincipalName -in $UserList.UserPrincipalName })
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
@@ -46,16 +50,44 @@ function Invoke-CIPPStandardDisableSharedMailbox {
}
if ($Settings.remediate -eq $true) {
+ if ($SharedMailboxList.Count -gt 0) {
+ $int = 0
+ $BulkRequests = foreach ($Mailbox in $SharedMailboxList) {
+ @{
+ id = $int++
+ method = 'PATCH'
+ url = "users/$($Mailbox.ObjectKey)"
+ body = @{ accountEnabled = $false }
+ 'headers' = @{
+ 'Content-Type' = 'application/json'
+ }
+ }
+ }
- if ($SharedMailboxList) {
- $SharedMailboxList | ForEach-Object {
- try {
- New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/users/$($_.ObjectKey)" -type PATCH -body '{"accountEnabled":"false"}' -tenantid $Tenant
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Entra account for shared mailbox $($_.DisplayName) disabled." -sev Info
- } catch {
- $ErrorMessage = Get-CippException -Exception $_
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to disable Entra account for shared mailbox. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
+ try {
+ $BulkResults = New-GraphBulkRequest -tenantid $Tenant -Requests @($BulkRequests)
+
+ for ($i = 0; $i -lt $BulkResults.Count; $i++) {
+ $result = $BulkResults[$i]
+ $Mailbox = $SharedMailboxList[$i]
+
+ if ($result.status -eq 200 -or $result.status -eq 204) {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Entra account for shared mailbox $($Mailbox.DisplayName) ($($Mailbox.ObjectKey)) disabled." -sev Info
+ } else {
+ $errorMsg = if ($result.body.error.message) { $result.body.error.message } else { "Unknown error (Status: $($result.status))" }
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to disable Entra account for shared mailbox $($Mailbox.DisplayName) ($($Mailbox.ObjectKey)): $errorMsg" -sev Error
+ }
}
+ } catch {
+ $ErrorMessage = Get-CippException -Exception $_
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to process bulk disable shared mailboxes request: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
+ }
+
+ # Refresh user cache after remediation
+ try {
+ Set-CIPPDBCacheUsers -TenantFilter $Tenant
+ } catch {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to refresh user cache after remediation: $($_.Exception.Message)" -sev Warning
}
} else {
Write-LogMessage -API 'Standards' -tenant $Tenant -message 'All Entra accounts for shared mailboxes are already disabled.' -sev Info
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1
index e82a30d9de1a..7656fb4ebfa1 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardDisableTNEF {
$TestResult = Test-CIPPStandardLicense -StandardName 'DisableTNEF' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -47,8 +46,6 @@ function Invoke-CIPPStandardDisableTNEF {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($CurrentState.TNEFEnabled -ne $false) {
try {
New-ExoRequest -tenantid $Tenant -cmdlet 'Set-RemoteDomain' -cmdParams @{Identity = 'Default'; TNEFEnabled = $false } -useSystemMailbox $true
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTenantCreation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTenantCreation.ps1
index c68199c54554..688e76667111 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTenantCreation.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTenantCreation.ps1
@@ -45,7 +45,6 @@ function Invoke-CIPPStandardDisableTenantCreation {
$StateIsCorrect = ($CurrentState.defaultUserRolePermissions.allowedToCreateTenants -eq $false)
if ($Settings.remediate -eq $true) {
- Write-Host "Time to remediate DisableTenantCreation standard for tenant $Tenant"
if ($StateIsCorrect -eq $true) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Users are already disabled from creating tenants.' -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1
index 2eeda69ca0f1..2d0ab46cd39d 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1
@@ -33,7 +33,6 @@ function Invoke-CIPPStandardDisableUserSiteCreate {
$TestResult = Test-CIPPStandardLicense -StandardName 'DisableUserSiteCreate' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableViva.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableViva.ps1
index 4ae2623e8e1b..e32c4b4a2d5c 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableViva.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableViva.ps1
@@ -41,8 +41,6 @@ function Invoke-CIPPStandardDisableViva {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($CurrentSetting.isEnabledInOrganization -eq $false) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Viva is already disabled.' -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODirectSend.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODirectSend.ps1
index 81bfe45e8006..1fa581d7c933 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODirectSend.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODirectSend.ps1
@@ -54,8 +54,6 @@ function Invoke-CIPPStandardEXODirectSend {
# Remediate if needed
if ($Settings.remediate -eq $true) {
-
- Write-Host 'Time to remediate'
if ($StateIsCorrect -eq $true) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Direct Send is already set to $DesiredStateName." -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1
index f39c7de785f5..8c636b8201ae 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1
@@ -40,7 +40,6 @@ function Invoke-CIPPStandardEXODisableAutoForwarding {
$TestResult = Test-CIPPStandardLicense -StandardName 'EXODisableAutoForwarding' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXOOutboundSpamLimits.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXOOutboundSpamLimits.ps1
index 537bb0133407..6c7fa531bbc0 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXOOutboundSpamLimits.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXOOutboundSpamLimits.ps1
@@ -40,7 +40,6 @@ function Invoke-CIPPStandardEXOOutboundSpamLimits {
$TestResult = Test-CIPPStandardLicense -StandardName 'EXOOutboundSpamLimits' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -87,7 +86,6 @@ function Invoke-CIPPStandardEXOOutboundSpamLimits {
# Remediation
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
if ($StateIsCorrect -eq $false) {
try {
$null = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-HostedOutboundSpamFilterPolicy' -cmdParams @{
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1
index f790392524ba..1446e6695b9f 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1
@@ -36,7 +36,6 @@ function Invoke-CIPPStandardEnableCustomerLockbox {
$TestResult = Test-CIPPStandardLicense -StandardName 'EnableCustomerLockbox' -TenantFilter $Tenant -RequiredCapabilities @('CustomerLockbox')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -49,7 +48,6 @@ function Invoke-CIPPStandardEnableCustomerLockbox {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
try {
if ($CustomerLockboxStatus) {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1
index 0b7f49ac521e..35f68f3350d9 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardEnableLitigationHold {
$TestResult = Test-CIPPStandardLicense -StandardName 'EnableLitigationHold' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -52,11 +51,11 @@ function Invoke-CIPPStandardEnableLitigationHold {
Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Litigation Hold already enabled for all accounts' -sev Info
} else {
try {
- $Request = $MailboxesNoLitHold | ForEach-Object {
+ $Request = foreach ($Mailbox in $MailboxesNoLitHold) {
$params = @{
CmdletInput = @{
CmdletName = 'Set-Mailbox'
- Parameters = @{ Identity = $_.UserPrincipalName; LitigationHoldEnabled = $true }
+ Parameters = @{ Identity = $Mailbox.UserPrincipalName; LitigationHoldEnabled = $true }
}
}
if ($null -ne $Settings.days) {
@@ -67,11 +66,10 @@ function Invoke-CIPPStandardEnableLitigationHold {
$BatchResults = New-ExoBulkRequest -tenantid $Tenant -cmdletArray @($Request)
- $BatchResults | ForEach-Object {
- if ($_.error) {
- $ErrorMessage = Get-NormalizedError -Message $_.error
- Write-Host "Failed to Enable Litigation Hold for $($_.Target). Error: $ErrorMessage"
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to Enable Litigation Hold for $($_.Target). Error: $ErrorMessage" -sev Error
+ foreach ($Result in $BatchResults) {
+ if ($Result.error) {
+ $ErrorMessage = Get-NormalizedError -Message $Result.error
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to Enable Litigation Hold for $($Result.Target). Error: $ErrorMessage" -sev Error
}
}
} catch {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1
index c76f81dd6e3f..bfa1cc10f477 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1
@@ -38,7 +38,6 @@ function Invoke-CIPPStandardEnableMailTips {
$TestResult = Test-CIPPStandardLicense -StandardName 'EnableMailTips' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1
index 5bfab677b1d9..6f63e7477bbd 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1
@@ -42,7 +42,6 @@ function Invoke-CIPPStandardEnableMailboxAuditing {
$TestResult = Test-CIPPStandardLicense -StandardName 'EnableMailboxAuditing' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -96,22 +95,21 @@ function Invoke-CIPPStandardEnableMailboxAuditing {
# Disable audit bypass for all mailboxes that have it enabled
- $BypassMailboxes = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MailboxAuditBypassAssociation' -select 'GUID, AuditBypassEnabled, Name' -useSystemMailbox $true | Where-Object { $_.AuditBypassEnabled -eq $true }
- $Request = $BypassMailboxes | ForEach-Object {
+ #$BypassMailboxes = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MailboxAuditBypassAssociation' -select 'GUID, AuditBypassEnabled, Name' -useSystemMailbox $true | Where-Object { $_.AuditBypassEnabled -eq $true }
+ $Request = foreach ($Mailbox in $BypassMailboxes) {
@{
CmdletInput = @{
CmdletName = 'Set-MailboxAuditBypassAssociation'
- Parameters = @{Identity = $_.Guid; AuditBypassEnabled = $false }
+ Parameters = @{Identity = $Mailbox.Guid; AuditBypassEnabled = $false }
}
}
}
$BatchResults = New-ExoBulkRequest -tenantid $tenant -cmdletArray @($Request)
- $BatchResults | ForEach-Object {
- if ($_.error) {
- $ErrorMessage = Get-NormalizedError -Message $_.error
- Write-Host "Failed to disable mailbox audit bypass for $($_.target). Error: $ErrorMessage"
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable mailbox audit bypass for $($_.target). Error: $ErrorMessage" -sev Error
+ foreach ($Result in $BatchResults) {
+ if ($Result.error) {
+ $ErrorMessage = Get-NormalizedError -Message $Result.error
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable mailbox audit bypass for $($Result.target). Error: $ErrorMessage" -sev Error
}
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableNamePronunciation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableNamePronunciation.ps1
index 21d676aa2253..eeee70c6fd6f 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableNamePronunciation.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableNamePronunciation.ps1
@@ -38,11 +38,8 @@ function Invoke-CIPPStandardEnableNamePronunciation {
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not get CurrentState for Name Pronunciation. Error: $($ErrorMessage.NormalizedError)" -sev Error
return
}
- Write-Host $CurrentState
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($CurrentState.isEnabledInOrganization -eq $true) {
Write-LogMessage -API 'Standards' -tenant $tenant -message 'Name Pronunciation is already enabled.' -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1
index b69034289ed8..152f5847d3e0 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1
@@ -35,14 +35,12 @@ function Invoke-CIPPStandardEnableOnlineArchiving {
$TestResult = Test-CIPPStandardLicense -StandardName 'EnableOnlineArchiving' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
$MailboxPlans = @( 'ExchangeOnline', 'ExchangeOnlineEnterprise' )
- $MailboxesNoArchive = $MailboxPlans | ForEach-Object {
- New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' -cmdParams @{ MailboxPlan = $_; Filter = 'ArchiveGuid -Eq "00000000-0000-0000-0000-000000000000" -AND RecipientTypeDetails -Eq "UserMailbox"' }
- Write-Host "Getting mailboxes without Online Archiving for plan $_"
+ $MailboxesNoArchive = foreach ($Plan in $MailboxPlans) {
+ New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' -cmdParams @{ MailboxPlan = $Plan; Filter = 'ArchiveGuid -Eq "00000000-0000-0000-0000-000000000000" -AND RecipientTypeDetails -Eq "UserMailbox"' }
}
if ($Settings.remediate -eq $true) {
@@ -51,21 +49,20 @@ function Invoke-CIPPStandardEnableOnlineArchiving {
Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Online Archiving already enabled for all accounts' -sev Info
} else {
try {
- $Request = $MailboxesNoArchive | ForEach-Object {
+ $Request = foreach ($Mailbox in $MailboxesNoArchive) {
@{
CmdletInput = @{
CmdletName = 'Enable-Mailbox'
- Parameters = @{ Identity = $_.UserPrincipalName; Archive = $true }
+ Parameters = @{ Identity = $Mailbox.UserPrincipalName; Archive = $true }
}
}
}
$BatchResults = New-ExoBulkRequest -tenantid $tenant -cmdletArray @($Request)
- $BatchResults | ForEach-Object {
- if ($_.error) {
- $ErrorMessage = Get-NormalizedError -Message $_.error
- Write-Host "Failed to Enable Online Archiving for $($_.Target). Error: $ErrorMessage"
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to Enable Online Archiving for $($_.Target). Error: $ErrorMessage" -sev Error
+ foreach ($Result in $BatchResults) {
+ if ($Result.error) {
+ $ErrorMessage = Get-NormalizedError -Message $Result.error
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to Enable Online Archiving for $($Result.Target). Error: $ErrorMessage" -sev Error
}
}
} catch {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1
index 1288147b8448..e57dc64edd9f 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnablePronouns.ps1
@@ -41,8 +41,6 @@ function Invoke-CIPPStandardEnablePronouns {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($CurrentState.isEnabledInOrganization -eq $true) {
Write-LogMessage -API 'Standards' -tenant $tenant -message 'Pronouns are already enabled.' -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration.ps1
index 5115ee7463b8..80643e0d9937 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration.ps1
@@ -45,7 +45,6 @@ function Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration {
$TestResult = Test-CIPPStandardLicense -StandardName 'EnrollmentWindowsHelloForBusinessConfiguration' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExchangeConnectorTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExchangeConnectorTemplate.ps1
index e40f07aabcc4..53d5b0db49ca 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExchangeConnectorTemplate.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExchangeConnectorTemplate.ps1
@@ -31,7 +31,6 @@ function Invoke-CIPPStandardExchangeConnectorTemplate {
$TestResult = Test-CIPPStandardLicense -StandardName 'ExConnector' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1
index 8271da894f31..5482feac8fa9 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardExcludedfileExt {
$TestResult = Test-CIPPStandardLicense -StandardName 'ExcludedfileExt' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -56,16 +55,11 @@ function Invoke-CIPPStandardExcludedfileExt {
}
}
- Write-Host "MissingExclusions: $($MissingExclusions)"
-
-
if ($Settings.remediate -eq $true) {
# If the number of extensions in the settings does not match the number of extensions in the current settings, we need to update the settings
$MissingExclusions = if ($Exts.Count -ne $CurrentInfo.excludedFileExtensionsForSyncApp.Count) { $true } else { $MissingExclusions }
if ($MissingExclusions) {
- Write-Host "CurrentInfo.excludedFileExtensionsForSyncApp: $($CurrentInfo.excludedFileExtensionsForSyncApp)"
- Write-Host "Exts: $($Exts)"
try {
$body = ConvertTo-Json -InputObject @{ excludedFileExtensionsForSyncApp = @($Exts) }
$null = New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -AsApp $true -Type patch -Body $body -ContentType 'application/json'
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1
index f4d03b8edaeb..858e214b9bdd 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1
@@ -52,8 +52,6 @@ function Invoke-CIPPStandardExternalMFATrusted {
}
if ($Settings.remediate -eq $true) {
-
- Write-Host 'Remediate External MFA Trusted'
if ($ExternalMFATrusted.inboundTrust.isMfaAccepted -eq $WantedState ) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message "External MFA Trusted is already $StateMessage." -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1
index 9c60877dd511..1eaeb003af7a 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardFocusedInbox {
$TestResult = Test-CIPPStandardLicense -StandardName 'FocusedInbox' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -59,8 +58,6 @@ function Invoke-CIPPStandardFocusedInbox {
$StateIsCorrect = if ($CurrentState -eq $WantedState) { $true } else { $false }
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($StateIsCorrect -eq $true) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Focused Inbox is already set to $state." -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFormsPhishingProtection.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFormsPhishingProtection.ps1
index 957c2b2d7059..8531ad4f56ee 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFormsPhishingProtection.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFormsPhishingProtection.ps1
@@ -48,8 +48,6 @@ function Invoke-CIPPStandardFormsPhishingProtection {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate Forms phishing protection'
-
# Check if phishing protection is already enabled
if ($CurrentState -eq $true) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Forms internal phishing protection is already enabled.' -sev Info
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1
index e7a3b9f246ea..549409230064 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1
@@ -31,7 +31,6 @@ function Invoke-CIPPStandardGlobalQuarantineNotifications {
$TestResult = Test-CIPPStandardLicense -StandardName 'GlobalQuarantineNotifications' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -68,8 +67,6 @@ function Invoke-CIPPStandardGlobalQuarantineNotifications {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($CurrentState.EndUserSpamNotificationFrequency -eq $WantedState) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Global Quarantine Notifications are already set to the desired value of $WantedState" -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1
index a7539fe40fd1..ed52d13b0b4b 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1
@@ -31,7 +31,7 @@ function Invoke-CIPPStandardGroupTemplate {
#>
param($Tenant, $Settings)
- $existingGroups = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $tenant
+ $existingGroups = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999&$select=id,displayName,description,membershipRule' -tenantid $tenant
$TestResult = Test-CIPPStandardLicense -StandardName 'GroupTemplate' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') -SkipLog
@@ -48,8 +48,6 @@ function Invoke-CIPPStandardGroupTemplate {
if ($Settings.remediate -eq $true) {
#Because the list name changed from TemplateList to groupTemplate by someone :@, we'll need to set it back to TemplateList
-
- Write-Host "Settings: $($Settings.TemplateList | ConvertTo-Json)"
foreach ($Template in $GroupTemplates) {
Write-Information "Processing template: $($Template.displayName)"
try {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1
index 06ab67ebb055..8d785d6de7d7 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardIntuneComplianceSettings {
$TestResult = Test-CIPPStandardLicense -StandardName 'IntuneComplianceSettings' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1
index f3d7ccca4a0c..a47738ecaee2 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1
@@ -36,170 +36,117 @@ function Invoke-CIPPStandardIntuneTemplate {
https://docs.cipp.app/user-documentation/tenant/standards/list-standards
#>
param($Tenant, $Settings)
- $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneTemplate_general' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1')
-
- if ($TestResult -eq $false) {
- #writing to each item that the license is not present.
- $settings.TemplateList | ForEach-Object {
- Set-CIPPStandardsCompareField -FieldName "standards.IntuneTemplate.$($_.value)" -FieldValue 'This tenant does not have the required license for this standard.' -Tenant $Tenant
- }
- Write-Host "We're exiting as the correct license is not present for this standard."
- return $true
- } #we're done.
+ Write-Host 'INTUNETEMPLATERUN'
$Table = Get-CippTable -tablename 'templates'
$Filter = "PartitionKey eq 'IntuneTemplate'"
- $Request = @{body = $null }
- Write-Host "IntuneTemplate: Starting process. Settings are: $($Settings | ConvertTo-Json -Compress)"
- $CompareList = foreach ($Template in $Settings) {
- Write-Host "IntuneTemplate: $($Template.TemplateList.value) - Trying to find template"
- $Request.body = (Get-CIPPAzDataTableEntity @Table -Filter $Filter | Where-Object -Property RowKey -Like "$($Template.TemplateList.value)*").JSON | ConvertFrom-Json -ErrorAction SilentlyContinue
- if ($null -eq $Request.body) {
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to find template $($Template.TemplateList.value). Has this Intune Template been deleted?" -sev 'Error'
- continue
- }
- Write-Host "IntuneTemplate: $($Template.TemplateList.value) - Got template."
- $displayname = $request.body.Displayname
- $description = $request.body.Description
- $RawJSON = $Request.body.RawJSON
+ $Template = (Get-CIPPAzDataTableEntity @Table -Filter $Filter | Where-Object -Property RowKey -Like "$($Settings.TemplateList.value)*").JSON | ConvertFrom-Json -ErrorAction SilentlyContinue
+ if ($null -eq $Template) {
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to find template $($Settings.TemplateList.value). Has this Intune Template been deleted?" -sev 'Error'
+ return $true
+ }
+
+ $displayname = $Template.Displayname
+ $description = $Template.Description
+ $RawJSON = $Template.RawJSON
+ $TemplateType = $Template.Type
+
+ try {
+ $ExistingPolicy = Get-CIPPIntunePolicy -tenantFilter $Tenant -DisplayName $displayname -TemplateType $TemplateType
+ } catch {
+ $ExistingPolicy = $null
+ }
+
+ if ($ExistingPolicy) {
try {
- Write-Host "IntuneTemplate: $($Template.TemplateList.value) - Grabbing existing Policy"
- $ExistingPolicy = Get-CIPPIntunePolicy -tenantFilter $Tenant -DisplayName $displayname -TemplateType $Request.body.Type
+ $RawJSON = Get-CIPPTextReplacement -Text $RawJSON -TenantFilter $Tenant
+ $JSONExistingPolicy = $ExistingPolicy.cippconfiguration | ConvertFrom-Json
+ $JSONTemplate = $RawJSON | ConvertFrom-Json
+ #This might be a slow one.
+ $Compare = Compare-CIPPIntuneObject -ReferenceObject $JSONTemplate -DifferenceObject $JSONExistingPolicy -compareType $TemplateType -ErrorAction SilentlyContinue
} catch {
- Write-Host "IntuneTemplate: $($Template.TemplateList.value) - Failed to get existing."
- }
- if ($ExistingPolicy) {
- try {
- Write-Host "IntuneTemplate: $($Template.TemplateList.value) - Found existing policy."
- $RawJSON = Get-CIPPTextReplacement -Text $RawJSON -TenantFilter $Tenant
- Write-Host "IntuneTemplate: $($Template.TemplateList.value) - Grabbing JSON existing."
- $JSONExistingPolicy = $ExistingPolicy.cippconfiguration | ConvertFrom-Json
- Write-Host "IntuneTemplate: $($Template.TemplateList.value) - Got existing JSON. Converting RawJSON to Template"
- $JSONTemplate = $RawJSON | ConvertFrom-Json
- Write-Host "IntuneTemplate: $($Template.TemplateList.value) - Converted RawJSON to Template."
- Write-Host "IntuneTemplate: $($Template.TemplateList.value) - Comparing JSON."
- $Compare = Compare-CIPPIntuneObject -ReferenceObject $JSONTemplate -DifferenceObject $JSONExistingPolicy -compareType $Request.body.Type -ErrorAction SilentlyContinue
- } catch {
- Write-Host "The compare failed. The error was: $($_.Exception.Message)"
- }
- Write-Host "IntuneTemplate: $($Template.TemplateList.value) - Compared JSON: $($Compare | ConvertTo-Json -Compress)"
- } else {
- Write-Host "IntuneTemplate: $($Template.TemplateList.value) - No existing policy found."
- $compare = [pscustomobject]@{
- MatchFailed = $true
- Difference = 'This policy does not exist in Intune.'
- }
}
- if ($Compare) {
- Write-Host "IntuneTemplate: $($Template.TemplateList.value) - Compare found differences."
- [PSCustomObject]@{
- MatchFailed = $true
- displayname = $displayname
- description = $description
- compare = $Compare
- rawJSON = $RawJSON
- body = $Request.body
- assignTo = $Template.AssignTo
- excludeGroup = $Template.excludeGroup
- remediate = $Template.remediate
- alert = $Template.alert
- report = $Template.report
- existingPolicyId = $ExistingPolicy.id
- templateId = $Template.TemplateList.value
- customGroup = $Template.customGroup
- assignmentFilter = $Template.assignmentFilter
- assignmentFilterType = $Template.assignmentFilterType
- }
- } else {
- Write-Host "IntuneTemplate: $($Template.TemplateList.value) - No differences found."
- [PSCustomObject]@{
- MatchFailed = $false
- displayname = $displayname
- description = $description
- compare = $false
- rawJSON = $RawJSON
- body = $Request.body
- assignTo = $Template.AssignTo
- excludeGroup = $Template.excludeGroup
- remediate = $Template.remediate
- alert = $Template.alert
- report = $Template.report
- existingPolicyId = $ExistingPolicy.id
- templateId = $Template.TemplateList.value
- customGroup = $Template.customGroup
- assignmentFilter = $Template.assignmentFilter
- assignmentFilterType = $Template.assignmentFilterType
- }
+ } else {
+ $compare = [pscustomobject]@{
+ MatchFailed = $true
+ Difference = 'This policy does not exist in Intune.'
}
}
+ $CompareResult = [PSCustomObject]@{
+ MatchFailed = [bool]$Compare
+ displayname = $displayname
+ description = $description
+ compare = $Compare
+ rawJSON = $RawJSON
+ templateType = $TemplateType
+ assignTo = $Settings.AssignTo
+ excludeGroup = $Settings.excludeGroup
+ remediate = $Settings.remediate
+ alert = $Settings.alert
+ report = $Settings.report
+ existingPolicyId = $ExistingPolicy.id
+ templateId = $Settings.TemplateList.value
+ customGroup = $Settings.customGroup
+ assignmentFilter = $Settings.assignmentFilter
+ assignmentFilterType = $Settings.assignmentFilterType
+ }
- if ($true -in $Settings.remediate) {
- Write-Host 'starting template deploy'
- foreach ($TemplateFile in $CompareList | Where-Object -Property remediate -EQ $true) {
- Write-Host "working on template deploy: $($TemplateFile.displayname)"
- try {
- $TemplateFile.customGroup ? ($TemplateFile.AssignTo = $TemplateFile.customGroup) : $null
-
- $PolicyParams = @{
- TemplateType = $TemplateFile.body.Type
- Description = $TemplateFile.description
- DisplayName = $TemplateFile.displayname
- RawJSON = $templateFile.rawJSON
- AssignTo = $TemplateFile.AssignTo
- ExcludeGroup = $TemplateFile.excludeGroup
- tenantFilter = $Tenant
- }
+ if ($Settings.remediate) {
+ try {
+ $CompareResult.customGroup ? ($CompareResult.AssignTo = $CompareResult.customGroup) : $null
- # Add assignment filter if specified
- if ($TemplateFile.assignmentFilter) {
- $PolicyParams.AssignmentFilterName = $TemplateFile.assignmentFilter
- $PolicyParams.AssignmentFilterType = $TemplateFile.assignmentFilterType ?? 'include'
- }
+ $PolicyParams = @{
+ TemplateType = $CompareResult.templateType
+ Description = $CompareResult.description
+ DisplayName = $CompareResult.displayname
+ RawJSON = $CompareResult.rawJSON
+ AssignTo = $CompareResult.AssignTo
+ ExcludeGroup = $CompareResult.excludeGroup
+ tenantFilter = $Tenant
+ }
- Set-CIPPIntunePolicy @PolicyParams
- } catch {
- $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to create or update Intune Template $($TemplateFile.displayname), Error: $ErrorMessage" -sev 'Error'
+ # Add assignment filter if specified
+ if ($CompareResult.assignmentFilter) {
+ $PolicyParams.AssignmentFilterName = $CompareResult.assignmentFilter
+ $PolicyParams.AssignmentFilterType = $CompareResult.assignmentFilterType ?? 'include'
}
- }
+ Set-CIPPIntunePolicy @PolicyParams
+ } catch {
+ $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to create or update Intune Template $($CompareResult.displayname), Error: $ErrorMessage" -sev 'Error'
+ }
}
- if ($true -in $Settings.alert) {
- foreach ($Template in $CompareList | Where-Object -Property alert -EQ $true) {
- Write-Host "working on template alert: $($Template.displayname)"
- $AlertObj = $Template | Select-Object -Property displayname, description, compare, assignTo, excludeGroup, existingPolicyId
- if ($Template.compare) {
- Write-StandardsAlert -message "Template $($Template.displayname) does not match the expected configuration." -object $AlertObj -tenant $Tenant -standardName 'IntuneTemplate' -standardId $Settings.templateId
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Template $($Template.displayname) does not match the expected configuration. We've generated an alert" -sev info
+ if ($Settings.alert) {
+ $AlertObj = $CompareResult | Select-Object -Property displayname, description, compare, assignTo, excludeGroup, existingPolicyId
+ if ($CompareResult.compare) {
+ Write-StandardsAlert -message "Template $($CompareResult.displayname) does not match the expected configuration." -object $AlertObj -tenant $Tenant -standardName 'IntuneTemplate' -standardId $Settings.templateId
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Template $($CompareResult.displayname) does not match the expected configuration. We've generated an alert" -sev info
+ } else {
+ if ($CompareResult.ExistingPolicyId) {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Template $($CompareResult.displayname) has the correct configuration." -sev Info
} else {
- if ($Template.ExistingPolicyId) {
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Template $($Template.displayname) has the correct configuration." -sev Info
- } else {
- Write-StandardsAlert -message "Template $($Template.displayname) is missing." -object $AlertObj -tenant $Tenant -standardName 'IntuneTemplate' -standardId $Settings.templateId
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Template $($Template.displayname) is missing." -sev info
- }
+ Write-StandardsAlert -message "Template $($CompareResult.displayname) is missing." -object $AlertObj -tenant $Tenant -standardName 'IntuneTemplate' -standardId $Settings.templateId
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Template $($CompareResult.displayname) is missing." -sev info
}
}
}
- if ($true -in $Settings.report) {
- foreach ($Template in $CompareList | Where-Object { $_.report -eq $true -or $_.remediate -eq $true }) {
- Write-Host "working on template report: $($Template.displayname)"
- $id = $Template.templateId
+ if ($Settings.report -or $Settings.remediate) {
+ $id = $CompareResult.templateId
- $CurrentValue = @{
- displayName = $Template.displayname
- description = $Template.description
- isCompliant = if ($Template.compare) { $false } else { $true }
- }
- $ExpectedValue = @{
- displayName = $Template.displayname
- description = $Template.description
- isCompliant = $true
- }
- Set-CIPPStandardsCompareField -FieldName "standards.IntuneTemplate.$id" -CurrentValue $CurrentValue -ExpectedValue $ExpectedValue -TenantFilter $Tenant
+ $CurrentValue = @{
+ displayName = $CompareResult.displayname
+ description = $CompareResult.description
+ isCompliant = if ($CompareResult.compare) { $false } else { $true }
+ }
+ $ExpectedValue = @{
+ displayName = $CompareResult.displayname
+ description = $CompareResult.description
+ isCompliant = $true
}
+ Set-CIPPStandardsCompareField -FieldName "standards.IntuneTemplate.$id" -CurrentValue $CurrentValue -ExpectedValue $ExpectedValue -TenantFilter $Tenant
#Add-CIPPBPAField -FieldName "policy-$id" -FieldValue $Compare -StoreAs bool -Tenant $tenant
}
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneWindowsDiagnostic.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneWindowsDiagnostic.ps1
new file mode 100644
index 000000000000..edea119a83d2
--- /dev/null
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneWindowsDiagnostic.ps1
@@ -0,0 +1,102 @@
+Function Invoke-CIPPStandardIntuneWindowsDiagnostic {
+ <#
+ .FUNCTIONALITY
+ Internal
+ .COMPONENT
+ (APIName) IntuneWindowsDiagnostic
+ .SYNOPSIS
+ (Label) Set Intune Windows diagnostic data settings
+ .DESCRIPTION
+ (Helptext) **Some features require Windows E3 or equivalent licenses** Configures Windows diagnostic data settings for Intune. Enables features like Windows update reports, device readiness reports, and driver update reports. More information can be found in [Microsoft's documentation.](https://go.microsoft.com/fwlink/?linkid=2204384)
+ (DocsDescription) Enables Windows diagnostic data in processor configuration for your Intune tenant. This setting is required for several Intune features including Windows feature update device readiness reports, compatibility risk reports, driver update reports, and update policy alerts. When enabled, your organization becomes the controller of Windows diagnostic data collected from managed devices, allowing Intune to use this data for reporting and update management features. More information can be found in [Microsoft's documentation.](https://go.microsoft.com/fwlink/?linkid=2204384)
+ .NOTES
+ CAT
+ Intune Standards
+ TAG
+ EXECUTIVETEXT
+ Enables access to Windows Update reporting and compatibility analysis features in Intune by allowing the use of Windows diagnostic data. This unlocks important capabilities like device readiness reports for feature updates, driver update reports, and proactive alerts for update failures, helping IT teams plan and monitor Windows updates more effectively across the organization.
+ ADDEDCOMPONENT
+ {"type":"switch","name":"standards.IntuneWindowsDiagnostic.areDataProcessorServiceForWindowsFeaturesEnabled","label":"Enable Windows data","defaultValue":false}
+ {"type":"switch","name":"standards.IntuneWindowsDiagnostic.hasValidWindowsLicense","label":"Confirm ownership of the required Windows E3 or equivalent licenses (Enables Windows update app and driver compatibility reports)","defaultValue":false}
+ IMPACT
+ Low Impact
+ ADDEDDATE
+ 2026-01-27
+ POWERSHELLEQUIVALENT
+ Graph API
+ RECOMMENDEDBY
+ UPDATECOMMENTBLOCK
+ Run the Tools\Update-StandardsComments.ps1 script to update this comment block
+ .LINK
+ https://docs.cipp.app/user-documentation/tenant/standards/list-standards
+ #>
+ [CmdletBinding()]
+ param($Tenant, $Settings)
+
+ $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneWindowsDiagnostic' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1')
+
+ if ($TestResult -eq $false) {
+ return $true
+ }
+
+ # Example diagnostic logic for Intune Windows devices
+ try {
+
+ $CurrentInfo = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/dataProcessorServiceForWindowsFeaturesOnboarding" -tenantid $Tenant
+ $CurrentValue = $CurrentInfo | Select-Object -Property areDataProcessorServiceForWindowsFeaturesEnabled, hasValidWindowsLicense
+
+ $StateIsCorrect = ($CurrentInfo.areDataProcessorServiceForWindowsFeaturesEnabled -eq $Settings.areDataProcessorServiceForWindowsFeaturesEnabled) -and ($CurrentInfo.hasValidWindowsLicense -eq $Settings.hasValidWindowsLicense)
+ } catch {
+ $ErrorMessage = Get-CippException -Exception $_
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to retrieve Windows diagnostic data settings for Intune." -Sev 'Error' -LogData $ErrorMessage
+ throw "Failed to retrieve current Windows diagnostic data settings for Intune. Error: $($ErrorMessage)"
+ }
+
+ if ($Settings.remediate -eq $true) {
+ if ($StateIsCorrect -eq $true) {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Windows Diagnostic for Intune is already in the desired state." -sev Info
+ }
+ else {
+ $Body = [pscustomobject]@{
+ value = @{
+ areDataProcessorServiceForWindowsFeaturesEnabled = $Settings.areDataProcessorServiceForWindowsFeaturesEnabled
+ hasValidWindowsLicense = $Settings.hasValidWindowsLicense
+ }
+ } | ConvertTo-Json -Depth 10 -Compress
+
+ try {
+ $null = New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/deviceManagement/dataProcessorServiceForWindowsFeaturesOnboarding' -Type PATCH -Body $body -ContentType 'application/json' -AsApp $true
+ $CurrentInfo.areDataProcessorServiceForWindowsFeaturesEnabled = $Settings.areDataProcessorServiceForWindowsFeaturesEnabled
+ $CurrentInfo.hasValidWindowsLicense = $Settings.hasValidWindowsLicense
+ $StateIsCorrect = $true
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully updated Windows Diagnostic settings for Intune." -sev Info
+ } catch {
+ $ErrorMessage = Get-CippException -Exception $_
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to update Windows Diagnostic settings for Intune. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
+ }
+ }
+ }
+
+ if ($Settings.alert -eq $true) {
+ if ($StateIsCorrect -eq $true) {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Windows Diagnostic for Intune is in the desired state." -sev Info
+ } else {
+ Write-StandardsAlert -message "Windows Diagnostic for Intune is not in the desired state." -object $CurrentValue -tenant $Tenant -standardName 'IntuneWindowsDiagnostic' -standardId $Settings.standardId
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Windows Diagnostic for Intune is not in the desired state." -sev Info
+ }
+
+ }
+
+ if ($Settings.report -eq $true) {
+ $CurrentValue = @{
+ areDataProcessorServiceForWindowsFeaturesEnabled = $CurrentInfo.areDataProcessorServiceForWindowsFeaturesEnabled
+ hasValidWindowsLicense = $CurrentInfo.hasValidWindowsLicense
+ }
+ $ExpectedValue = @{
+ areDataProcessorServiceForWindowsFeaturesEnabled = $Settings.areDataProcessorServiceForWindowsFeaturesEnabled
+ hasValidWindowsLicense = $Settings.hasValidWindowsLicense
+ }
+ Set-CIPPStandardsCompareField -FieldName 'standards.IntuneWindowsDiagnostic' -CurrentValue $CurrentValue -ExpectedValue $ExpectedValue -TenantFilter $Tenant
+ Add-CIPPBPAField -FieldName 'IntuneWindowsDiagnostic' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant
+ }
+}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMDMEnrollmentDuringRegistration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMDMEnrollmentDuringRegistration.ps1
index 250aaa273045..373be8b68738 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMDMEnrollmentDuringRegistration.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMDMEnrollmentDuringRegistration.ps1
@@ -34,7 +34,6 @@
$TestResult = Test-CIPPStandardLicense -StandardName 'MDMEnrollmentDuringRegistration' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMDMScope.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMDMScope.ps1
index e01b2616abe1..7a02236ff7e9 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMDMScope.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMDMScope.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardMDMScope {
$TestResult = Test-CIPPStandardLicense -StandardName 'MDMScope' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1
index d45e345f556c..e804e605ce61 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailContacts.ps1
@@ -63,7 +63,6 @@ function Invoke-CIPPStandardMailContacts {
{ $Contacts.TechContact } { $body | Add-Member -NotePropertyName technicalNotificationMails -NotePropertyValue @($Contacts.TechContact) -ErrorAction SilentlyContinue }
{ $Contacts.GeneralContact } { $body | Add-Member -NotePropertyName privacyProfile -NotePropertyValue @{contactEmail = $Contacts.GeneralContact } }
}
- Write-Host (ConvertTo-Json -InputObject $body)
New-GraphPostRequest -tenantid $tenant -Uri "https://graph.microsoft.com/v1.0/organization/$($TenantID.id)" -asApp $true -Type patch -Body (ConvertTo-Json -InputObject $body) -ContentType 'application/json'
Write-LogMessage -API 'Standards' -tenant $tenant -message 'Contact emails set.' -sev Info
} catch {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1
index a796462e2881..ae62b19cd6eb 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardMailboxRecipientLimits {
$TestResult = Test-CIPPStandardLicense -StandardName 'MailboxRecipientLimits' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -82,11 +81,9 @@ function Invoke-CIPPStandardMailboxRecipientLimits {
if ($null -ne $Mailboxes -and @($Mailboxes).Count -gt 0) {
# Process mailboxes and categorize them based on their plan limits
- $MailboxResults = @($Mailboxes) | ForEach-Object {
-
- $Mailbox = $_
+ $MailboxResults = foreach ($Mailbox in @($Mailboxes)) {
if ($Mailbox.UserPrincipalName -like 'DiscoverySearchMailbox*' -or $Mailbox.UserPrincipalName -like 'SystemMailbox*') {
- return
+ continue
}
# Safe hashtable lookup - check if MailboxPlanId exists and is not null
$Plan = $null
@@ -128,12 +125,13 @@ function Invoke-CIPPStandardMailboxRecipientLimits {
# Separate mailboxes into their respective categories only if we have results
$MailboxesToUpdate = $MailboxResults | Where-Object { $_.Type -eq 'ToUpdate' } | Select-Object -ExpandProperty Mailbox
- $MailboxesWithPlanIssues = $MailboxResults | Where-Object { $_.Type -eq 'PlanIssue' } | ForEach-Object {
+ $PlanIssueResults = $MailboxResults | Where-Object { $_.Type -eq 'PlanIssue' }
+ $MailboxesWithPlanIssues = foreach ($Issue in $PlanIssueResults) {
[PSCustomObject]@{
- Identity = $_.Mailbox.Identity
- CurrentLimit = $_.CurrentLimit
- PlanLimit = $_.PlanLimit
- PlanName = $_.PlanName
+ Identity = $Issue.Mailbox.Identity
+ CurrentLimit = $Issue.CurrentLimit
+ PlanLimit = $Issue.PlanLimit
+ PlanName = $Issue.PlanName
}
}
}
@@ -150,12 +148,12 @@ function Invoke-CIPPStandardMailboxRecipientLimits {
if ($MailboxesToUpdate.Count -gt 0) {
try {
# Create detailed log data for audit trail
- $MailboxChanges = $MailboxesToUpdate | ForEach-Object {
- $CurrentLimit = if ($_.RecipientLimits -eq 'Unlimited') { 'Unlimited' } else { $_.RecipientLimits }
+ $MailboxChanges = foreach ($Mailbox in $MailboxesToUpdate) {
+ $CurrentLimit = if ($Mailbox.RecipientLimits -eq 'Unlimited') { 'Unlimited' } else { $Mailbox.RecipientLimits }
@{
- Identity = $_.Identity
- DisplayName = $_.DisplayName
- PrimarySmtpAddress = $_.PrimarySmtpAddress
+ Identity = $Mailbox.Identity
+ DisplayName = $Mailbox.DisplayName
+ PrimarySmtpAddress = $Mailbox.PrimarySmtpAddress
CurrentLimit = $CurrentLimit
NewLimit = $Settings.RecipientLimit
}
@@ -164,12 +162,12 @@ function Invoke-CIPPStandardMailboxRecipientLimits {
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Updating recipient limits to $($Settings.RecipientLimit) for $($MailboxesToUpdate.Count) mailboxes" -sev Info -LogData $MailboxChanges
# Create batch requests for mailbox updates
- $UpdateRequests = $MailboxesToUpdate | ForEach-Object {
+ $UpdateRequests = foreach ($Mailbox in $MailboxesToUpdate) {
@{
CmdletInput = @{
CmdletName = 'Set-Mailbox'
Parameters = @{
- Identity = $_.Identity
+ Identity = $Mailbox.Identity
RecipientLimits = $Settings.RecipientLimit
}
}
@@ -205,12 +203,12 @@ function Invoke-CIPPStandardMailboxRecipientLimits {
# Add mailboxes that need updating
if ($MailboxesToUpdate.Count -gt 0) {
- $AlertData.MailboxesToUpdate = $MailboxesToUpdate | ForEach-Object {
- $CurrentLimit = if ($_.RecipientLimits -eq 'Unlimited') { 'Unlimited' } else { $_.RecipientLimits }
+ $AlertData.MailboxesToUpdate = foreach ($Mailbox in $MailboxesToUpdate) {
+ $CurrentLimit = if ($Mailbox.RecipientLimits -eq 'Unlimited') { 'Unlimited' } else { $Mailbox.RecipientLimits }
@{
- Identity = $_.Identity
- DisplayName = $_.DisplayName
- PrimarySmtpAddress = $_.PrimarySmtpAddress
+ Identity = $Mailbox.Identity
+ DisplayName = $Mailbox.DisplayName
+ PrimarySmtpAddress = $Mailbox.PrimarySmtpAddress
CurrentLimit = $CurrentLimit
RequiredLimit = $Settings.RecipientLimit
}
@@ -223,12 +221,12 @@ function Invoke-CIPPStandardMailboxRecipientLimits {
# Add mailboxes with plan issues
if ($MailboxesWithPlanIssues.Count -gt 0) {
- $AlertData.MailboxesWithPlanIssues = $MailboxesWithPlanIssues | ForEach-Object {
+ $AlertData.MailboxesWithPlanIssues = foreach ($Issue in $MailboxesWithPlanIssues) {
@{
- Identity = $_.Identity
- CurrentLimit = $_.CurrentLimit
- PlanLimit = $_.PlanLimit
- PlanName = $_.PlanName
+ Identity = $Issue.Identity
+ CurrentLimit = $Issue.CurrentLimit
+ PlanLimit = $Issue.PlanLimit
+ PlanName = $Issue.PlanName
RequestedLimit = $Settings.RecipientLimit
}
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1
index 640db0445b43..f7c72a9f8675 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1
@@ -46,14 +46,29 @@ function Invoke-CIPPStandardMalwareFilterPolicy {
$TestResult = Test-CIPPStandardLicense -StandardName 'MalwareFilterPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
+ try {
+ $AllMalwareFilterPolicies = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MalwareFilterPolicy'
+ } catch {
+ $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
+ Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the MalwareFilterPolicy state for $Tenant. Error: $ErrorMessage" -Sev Error
+ return
+ }
+
+ try {
+ $AllMalwareFilterRules = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MalwareFilterRule'
+ } catch {
+ $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
+ Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the Get-MalwareFilterRule state for $Tenant. Error: $ErrorMessage" -Sev Error
+ return
+ }
+
# Use custom name if provided, otherwise use default for backward compatibility
$PolicyName = if ($Settings.name) { $Settings.name } else { 'CIPP Default Malware Policy' }
$PolicyList = @($PolicyName, 'CIPP Default Malware Policy', 'Default Malware Policy')
- $ExistingPolicy = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MalwareFilterPolicy' | Where-Object -Property Name -In $PolicyList | Select-Object -First 1
+ $ExistingPolicy = $AllMalwareFilterPolicies | Where-Object -Property Name -In $PolicyList | Select-Object -First 1
if ($null -eq $ExistingPolicy.Name) {
# No existing policy - use the configured/default name
$PolicyName = if ($Settings.name) { $Settings.name } else { 'CIPP Default Malware Policy' }
@@ -64,7 +79,7 @@ function Invoke-CIPPStandardMalwareFilterPolicy {
# Derive rule name from policy name, but check for old names for backward compatibility
$DesiredRuleName = "$PolicyName Rule"
$RuleList = @($DesiredRuleName, 'CIPP Default Malware Rule', 'CIPP Default Malware Policy')
- $ExistingRule = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MalwareFilterRule' | Where-Object -Property Name -In $RuleList | Select-Object -First 1
+ $ExistingRule = $AllMalwareFilterRules | Where-Object -Property Name -In $RuleList | Select-Object -First 1
if ($null -eq $ExistingRule.Name) {
# No existing rule - use the derived name
$RuleName = $DesiredRuleName
@@ -73,15 +88,9 @@ function Invoke-CIPPStandardMalwareFilterPolicy {
$RuleName = $ExistingRule.Name
}
- try {
- $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MalwareFilterPolicy' |
- Where-Object -Property Name -EQ $PolicyName |
- Select-Object Name, EnableFileFilter, FileTypeAction, FileTypes, ZapEnabled, QuarantineTag, EnableInternalSenderAdminNotifications, InternalSenderAdminAddress, EnableExternalSenderAdminNotifications, ExternalSenderAdminAddress
- } catch {
- $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
- Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the MalwareFilterPolicy state for $Tenant. Error: $ErrorMessage" -Sev Error
- return
- }
+ $CurrentState = $AllMalwareFilterPolicies |
+ Where-Object -Property Name -EQ $PolicyName |
+ Select-Object Name, EnableFileFilter, FileTypeAction, FileTypes, ZapEnabled, QuarantineTag, EnableInternalSenderAdminNotifications, InternalSenderAdminAddress, EnableExternalSenderAdminNotifications, ExternalSenderAdminAddress
$DefaultFileTypes = @('ace', 'ani', 'apk', 'app', 'appx', 'arj', 'bat', 'cab', 'cmd', 'com', 'deb', 'dex', 'dll', 'docm', 'elf', 'exe', 'hta', 'img', 'iso', 'jar', 'jnlp', 'kext', 'lha', 'lib', 'library', 'lnk', 'lzh', 'macho', 'msc', 'msi', 'msix', 'msp', 'mst', 'pif', 'ppa', 'ppam', 'reg', 'rev', 'scf', 'scr', 'sct', 'sys', 'uif', 'vb', 'vbe', 'vbs', 'vxd', 'wsc', 'wsf', 'wsh', 'xll', 'xz', 'z')
@@ -106,7 +115,7 @@ function Invoke-CIPPStandardMalwareFilterPolicy {
$AcceptedDomains = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AcceptedDomain'
- $RuleState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MalwareFilterRule' |
+ $RuleState = $AllMalwareFilterRules |
Where-Object -Property Name -EQ $RuleName |
Select-Object Name, MalwareFilterPolicy, Priority, RecipientDomainIs
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1
index 489366c54f0a..07dc9be99dc0 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1
@@ -31,7 +31,6 @@ function Invoke-CIPPStandardMessageExpiration {
$TestResult = Test-CIPPStandardLicense -StandardName 'MessageExpiration' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -44,7 +43,6 @@ function Invoke-CIPPStandardMessageExpiration {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
if ($MessageExpiration -ne '12:00:00') {
try {
New-ExoRequest -tenantid $Tenant -cmdlet 'Set-TransportConfig' -cmdParams @{MessageExpiration = '12:00:00' }
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1
index 169ba51d7586..93b3fe130970 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1
@@ -32,7 +32,7 @@ function Invoke-CIPPStandardNudgeMFA {
#>
param($Tenant, $Settings)
- Write-Host "NudgeMFA: $($Settings | ConvertTo-Json -Compress)"
+
# Get state value using null-coalescing operator
$State = $Settings.state.value ?? $Settings.state
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOWAAttachmentRestrictions.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOWAAttachmentRestrictions.ps1
index 614df8aca6fe..9a2f38181609 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOWAAttachmentRestrictions.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOWAAttachmentRestrictions.ps1
@@ -39,7 +39,6 @@ function Invoke-CIPPStandardOWAAttachmentRestrictions {
$TestResult = Test-CIPPStandardLicense -StandardName 'OWAAttachmentRestrictions' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1
index adc90a5a342d..bf86be5f502e 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsent.ps1
@@ -69,7 +69,6 @@ function Invoke-CIPPStandardOauthConsent {
foreach ($AllowedApp in $AllowedAppIdsForTenant) {
if ($AllowedApp -and ($AllowedApp -notin $ExistingAppIds)) {
- Write-Host "Adding missing approved app: $AllowedApp"
New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/permissionGrantPolicies/cipp-consent-policy/includes' -Type POST -Body ('{"permissionType": "delegated","clientApplicationIds": ["' + $AllowedApp + '"]}') -ContentType 'application/json'
New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/permissionGrantPolicies/cipp-consent-policy/includes' -Type POST -Body ('{ "permissionType": "Application", "clientApplicationIds": ["' + $AllowedApp + '"] }') -ContentType 'application/json'
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1
index 2ba180c1a0ca..2bce9422a74b 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1
@@ -76,19 +76,19 @@ function Invoke-CIPPStandardOauthConsentLowSec {
Write-LogMessage -API 'Standards' -tenant $tenant -message 'All permissions for Application Consent already assigned.' -sev Info
} else {
try {
- $missingPermissions | ForEach-Object {
+ foreach ($Permission in $missingPermissions) {
$GraphParam = @{
tenantid = $tenant
Uri = "https://graph.microsoft.com/beta/servicePrincipals(appId='00000003-0000-0000-c000-000000000000')/delegatedPermissionClassifications"
Type = 'POST'
Body = @{
- permissionName = $_
+ permissionName = $Permission
classification = 'low'
} | ConvertTo-Json
ContentType = 'application/json'
}
$null = New-GraphPostRequest @GraphParam
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Permission $_ has been added to low Application Consent" -sev Info
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Permission $Permission has been added to low Application Consent" -sev Info
}
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1
index 746cb2632c46..0ae5d1e92eab 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardOutBoundSpamAlert {
$TestResult = Test-CIPPStandardLicense -StandardName 'OutBoundSpamAlert' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWcompanionAppAllowedState.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWcompanionAppAllowedState.ps1
index 474de9cf7e44..656e69bd5367 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWcompanionAppAllowedState.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPWcompanionAppAllowedState.ps1
@@ -54,8 +54,6 @@ function Invoke-CIPPStandardPWcompanionAppAllowedState {
}
If ($Settings.remediate -eq $true) {
- Write-Host "Remediating PWcompanionAppAllowedState for tenant $Tenant to $WantedState"
-
if ($AuthStateCorrect -eq $true) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message "companionAppAllowedState is already set to the desired state of $WantedState." -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPasswordExpireDisabled.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPasswordExpireDisabled.ps1
index 403f80236b2e..2caaa9e00f7f 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPasswordExpireDisabled.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPasswordExpireDisabled.ps1
@@ -49,19 +49,18 @@ function Invoke-CIPPStandardPasswordExpireDisabled {
if ($Settings.remediate -eq $true) {
if ($DomainsWithoutPassExpire) {
- $DomainsWithoutPassExpire | ForEach-Object {
+ foreach ($Domain in $DomainsWithoutPassExpire) {
try {
- if ( $null -eq $_.passwordNotificationWindowInDays ) {
+ if ( $null -eq $Domain.passwordNotificationWindowInDays ) {
$Body = '{"passwordValidityPeriodInDays": 2147483647, "passwordNotificationWindowInDays": 14 }'
- Write-Host "PasswordNotificationWindowInDays is null for $($_.id). Setting to the default of 14 days."
} else {
$Body = '{"passwordValidityPeriodInDays": 2147483647 }'
}
- New-GraphPostRequest -type Patch -tenantid $Tenant -uri "https://graph.microsoft.com/v1.0/domains/$($_.id)" -body $Body
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Disabled Password Expiration for $($_.id)." -sev Info
+ New-GraphPostRequest -type Patch -tenantid $Tenant -uri "https://graph.microsoft.com/v1.0/domains/$($Domain.id)" -body $Body
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Disabled Password Expiration for $($Domain.id)." -sev Info
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable Password Expiration for $($_.id). Error: $ErrorMessage" -sev Error
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to disable Password Expiration for $($Domain.id). Error: $ErrorMessage" -sev Error
}
}
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1
index e4ea97dc4389..98d5a2fcb762 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1
@@ -41,7 +41,12 @@ function Invoke-CIPPStandardPerUserMFA {
param($Tenant, $Settings)
try {
- $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$top=999&`$select=userPrincipalName,displayName,accountEnabled,perUserMfaState&`$filter=userType eq 'Member' and accountEnabled eq true and displayName ne 'On-Premises Directory Synchronization Service Account'&`$count=true" -tenantid $Tenant -ComplexFilter
+ $AllUsers = New-CIPPDbRequest -TenantFilter $Tenant -Type 'Users'
+ $GraphRequest = $AllUsers | Where-Object {
+ $_.userType -eq 'Member' -and
+ $_.accountEnabled -eq $true -and
+ $_.displayName -ne 'On-Premises Directory Synchronization Service Account'
+ }
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the PerUserMFA state for $Tenant. Error: $ErrorMessage" -Sev Error
@@ -58,6 +63,13 @@ function Invoke-CIPPStandardPerUserMFA {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to enforce MFA for all users: $ErrorMessage" -sev Error
}
+
+ # Refresh user cache after remediation
+ try {
+ Set-CIPPDBCacheUsers -TenantFilter $Tenant
+ } catch {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to refresh user cache after remediation: $($_.Exception.Message)" -sev Warning
+ }
}
}
if ($Settings.alert -eq $true) {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1
index fdf203641250..8f79c8d5a850 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1
@@ -34,6 +34,12 @@ function Invoke-CIPPStandardPhishProtection {
param($Tenant, $Settings)
+ $TestResult = Test-CIPPStandardLicense -StandardName 'PhishProtection' -TenantFilter $Tenant -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2')
+
+ if ($TestResult -eq $false) {
+ return $true
+ } #we're done.
+
$TenantId = Get-Tenants | Where-Object -Property defaultDomainName -EQ $tenant
$Table = Get-CIPPTable -TableName Config
@@ -74,11 +80,9 @@ function Invoke-CIPPStandardPhishProtection {
}
}
if ($currentBody -like "*$CSS*") {
- Write-Host 'Logon Screen Phishing Protection system already active'
Write-LogMessage -API 'Standards' -tenant $tenant -message 'Logon Screen Phishing Protection system already active' -sev Info
} else {
$currentBody = $currentBody + $CSS
- Write-Host 'Creating Logon Screen Phishing Protection System'
New-GraphPostRequest -tenantid $tenant -Uri "https://graph.microsoft.com/beta/organization/$($TenantId.customerId)/branding/localizations/0/customCSS" -ContentType 'text/css' -asApp $true -Type PUT -Body $currentBody
Write-LogMessage -API 'Standards' -tenant $tenant -message 'Enabled Logon Screen Phishing Protection system' -sev Info
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishSimSpoofIntelligence.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishSimSpoofIntelligence.ps1
index 8374e9803f06..e2de19d4fd2e 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishSimSpoofIntelligence.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishSimSpoofIntelligence.ps1
@@ -33,7 +33,6 @@ function Invoke-CIPPStandardPhishSimSpoofIntelligence {
$TestResult = Test-CIPPStandardLicense -StandardName 'PhishSimSpoofIntelligence' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
# Fetch current Phishing Simulations Spoof Intelligence domains and ensure it is correctly configured
@@ -71,7 +70,6 @@ function Invoke-CIPPStandardPhishSimSpoofIntelligence {
if ($Settings.RemoveExtraDomains -eq $true) {
# Prepare removal requests
if ($RemoveDomain.Count -gt 0) {
- Write-Host "Removing $($RemoveDomain.Count) domains from Spoof Intelligence"
$BulkRequests.Add(@{
CmdletInput = @{
CmdletName = 'Remove-TenantAllowBlockListSpoofItems'
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishingSimulations.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishingSimulations.ps1
index e5d64514d13c..978cd750c317 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishingSimulations.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishingSimulations.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardPhishingSimulations {
$TestResult = Test-CIPPStandardLicense -StandardName 'PhishingSimulations' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
$PolicyName = 'CIPPPhishSim'
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardProfilePhotos.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardProfilePhotos.ps1
index c802bc152a03..a7b462a40fdb 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardProfilePhotos.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardProfilePhotos.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardProfilePhotos {
$TestResult = Test-CIPPStandardLicense -StandardName 'ProfilePhotos' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -75,20 +74,15 @@ function Invoke-CIPPStandardProfilePhotos {
$CurrentStatesCorrect = $GraphStateCorrect -eq $true -and $OWAStateCorrect -eq $true
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($CurrentStatesCorrect -eq $false) {
- Write-Host 'Settings are not correct'
try {
if ($StateValue -eq 'enabled') {
- Write-Host 'Enabling'
# Enable photo updates
$null = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-OwaMailboxPolicy' -cmdParams @{Identity = $CurrentOWAState.Identity; SetPhotoEnabled = $true } -useSystemMailbox $true
$null = New-GraphPostRequest -uri $Uri -tenant $Tenant -type DELETE -AsApp $true
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Set Profile photo settings to $StateValue" -sev Info
} else {
- Write-Host 'Disabling'
# Disable photo updates
$null = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-OwaMailboxPolicy' -cmdParams @{Identity = $CurrentOWAState.Identity; SetPhotoEnabled = $false } -useSystemMailbox $true
@@ -108,7 +102,6 @@ function Invoke-CIPPStandardProfilePhotos {
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set profile photo settings to $StateValue. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
}
} else {
- Write-Host 'Settings are correct'
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Profile photo settings are already set to the desired state: $StateValue" -sev Info
}
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1
index 8e22526e6b29..2638eae4e1de 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1
@@ -34,15 +34,12 @@ function Invoke-CIPPStandardQuarantineRequestAlert {
$TestResult = Test-CIPPStandardLicense -StandardName 'QuarantineRequestAlert' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
$PolicyName = 'CIPP User requested to release a quarantined message'
try {
- $CurrentState = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-ProtectionAlert' -Compliance |
- Where-Object { $_.Name -eq $PolicyName } |
- Select-Object -Property *
+ $CurrentState = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-ProtectionAlert' -Compliance | Where-Object { $_.Name -eq $PolicyName }
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the QuarantineRequestAlert state for $Tenant. Error: $ErrorMessage" -Sev Error
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineTemplate.ps1
index a89ce3829c0a..c490d80e062d 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineTemplate.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineTemplate.ps1
@@ -45,7 +45,6 @@ function Invoke-CIPPStandardQuarantineTemplate {
$TestResult = Test-CIPPStandardLicense -StandardName 'QuarantineTemplate' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRestrictThirdPartyStorageServices.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRestrictThirdPartyStorageServices.ps1
index d140c767a1da..517045ae0169 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRestrictThirdPartyStorageServices.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRestrictThirdPartyStorageServices.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardRestrictThirdPartyStorageServices {
$TestResult = Test-CIPPStandardLicense -StandardName 'ThirdPartyStorageServicesRestricted' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -51,8 +50,6 @@ function Invoke-CIPPStandardRestrictThirdPartyStorageServices {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate third-party storage services restriction'
-
# Check if service principal is already disabled
if ($CurrentState.accountEnabled -eq $false) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Third-party storage services are already restricted (service principal is disabled).' -sev Info
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRetentionPolicyTag.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRetentionPolicyTag.ps1
index cb992130b094..f8e204371c98 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRetentionPolicyTag.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRetentionPolicyTag.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardRetentionPolicyTag {
$TestResult = Test-CIPPStandardLicense -StandardName 'RetentionPolicyTag' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -61,8 +60,6 @@ function Invoke-CIPPStandardRetentionPolicyTag {
($PolicyState.RetentionPolicyTagLinks -contains $PolicyName)
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($StateIsCorrect -eq $true) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Retention policy tag already correctly configured' -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1
index 9e9e96d44c85..11759ab03809 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1
@@ -36,7 +36,6 @@ function Invoke-CIPPStandardRotateDKIM {
$TestResult = Test-CIPPStandardLicense -StandardName 'RotateDKIM' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -51,10 +50,10 @@ function Invoke-CIPPStandardRotateDKIM {
if ($Settings.remediate -eq $true) {
if ($DKIM) {
- $DKIM | ForEach-Object {
+ foreach ($DkimConfig in $DKIM) {
try {
- (New-ExoRequest -tenantid $tenant -cmdlet 'Rotate-DkimSigningConfig' -cmdParams @{ KeySize = 2048; Identity = $_.Identity } -useSystemMailbox $true)
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Rotated DKIM for $($_.Identity)" -sev Info
+ (New-ExoRequest -tenantid $tenant -cmdlet 'Rotate-DkimSigningConfig' -cmdParams @{ KeySize = 2048; Identity = $DkimConfig.Identity } -useSystemMailbox $true)
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Rotated DKIM for $($DkimConfig.Identity)" -sev Info
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to rotate DKIM Error: $ErrorMessage" -sev Error
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1
index 6a4d323ed5a1..c6ef5d3a2862 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardSPAzureB2B {
$TestResult = Test-CIPPStandardLicense -StandardName 'SPAzureB2B' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1
index a2ac80b497d4..f8f894c18a3d 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardSPDirectSharing {
$TestResult = Test-CIPPStandardLicense -StandardName 'SPDirectSharing' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1
index 5612ce7c034e..0ca4672a15bb 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1
@@ -32,13 +32,11 @@ function Invoke-CIPPStandardSPDisableLegacyWorkflows {
$TestResult = Test-CIPPStandardLicense -StandardName 'SPDisableLegacyWorkflows' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
try {
- $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant |
- Select-Object -Property *
+ $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the SPDisableLegacyWorkflows state for $Tenant. Error: $ErrorMessage" -Sev Error
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1
index 650647f6a0e0..969c329052ce 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1
@@ -38,7 +38,6 @@ function Invoke-CIPPStandardSPDisallowInfectedFiles {
$TestResult = Test-CIPPStandardLicense -StandardName 'SPDisallowInfectedFiles' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU','ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1
index 965b5bf90d5c..154451f56a8a 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1
@@ -38,7 +38,6 @@ function Invoke-CIPPStandardSPEmailAttestation {
$TestResult = Test-CIPPStandardLicense -StandardName 'SPEmailAttestation' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1
index f5595d9263f1..8070afb1b523 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1
@@ -37,7 +37,6 @@ function Invoke-CIPPStandardSPExternalUserExpiration {
$TestResult = Test-CIPPStandardLicense -StandardName 'SPExternalUserExpiration' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPSyncButtonState.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPSyncButtonState.ps1
index 0c39478cd85b..24d175063862 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPSyncButtonState.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPSyncButtonState.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardSPSyncButtonState {
$TestResult = Test-CIPPStandardLicense -StandardName 'SPSyncButtonState' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -59,8 +58,6 @@ function Invoke-CIPPStandardSPSyncButtonState {
$HumanReadableState = if ($WantedState -eq $true) { 'disabled' } else { 'enabled' }
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($StateIsCorrect -eq $false) {
try {
$CurrentState | Set-CIPPSPOTenant -Properties @{HideSyncButtonOnDocLib = $WantedState }
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1
index f23855cbbeee..79c6d309ee85 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1
@@ -42,19 +42,26 @@ function Invoke-CIPPStandardSafeAttachmentPolicy {
$TestResult = Test-CIPPStandardLicense -StandardName 'SafeAttachmentPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
- $ServicePlans = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus?$select=servicePlans' -tenantid $Tenant
- $ServicePlans = $ServicePlans.servicePlans.servicePlanName
- $MDOLicensed = $ServicePlans -contains 'ATP_ENTERPRISE'
+ $TenantCapabilities = Get-CIPPTenantCapabilities -TenantFilter $Tenant
+ $MDOLicensed = $TenantCapabilities.ATP_ENTERPRISE -eq $true
if ($MDOLicensed) {
+ # Cache all Safe Attachment Policies to avoid duplicate API calls
+ try {
+ $AllSafeAttachmentPolicies = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SafeAttachmentPolicy'
+ } catch {
+ $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
+ Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the SafeAttachmentPolicy state for $Tenant. Error: $ErrorMessage" -Sev Error
+ return
+ }
+
# Use custom name if provided, otherwise use default for backward compatibility
$PolicyName = if ($Settings.name) { $Settings.name } else { 'CIPP Default Safe Attachment Policy' }
$PolicyList = @($PolicyName, 'CIPP Default Safe Attachment Policy', 'Default Safe Attachment Policy')
- $ExistingPolicy = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SafeAttachmentPolicy' | Where-Object -Property Name -In $PolicyList | Select-Object -First 1
+ $ExistingPolicy = $AllSafeAttachmentPolicies | Where-Object -Property Name -In $PolicyList | Select-Object -First 1
if ($null -eq $ExistingPolicy.Name) {
# No existing policy - use the configured/default name
$PolicyName = if ($Settings.name) { $Settings.name } else { 'CIPP Default Safe Attachment Policy' }
@@ -74,15 +81,9 @@ function Invoke-CIPPStandardSafeAttachmentPolicy {
$RuleName = $ExistingRule.Name
}
- try {
- $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SafeAttachmentPolicy' |
+ $CurrentState = $AllSafeAttachmentPolicies |
Where-Object -Property Name -EQ $PolicyName |
Select-Object Name, Enable, Action, QuarantineTag, Redirect, RedirectAddress
- } catch {
- $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
- Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the SafeAttachmentPolicy state for $Tenant. Error: $ErrorMessage" -Sev Error
- return
- }
$StateIsCorrect = ($CurrentState.Name -eq $PolicyName) -and
($CurrentState.Enable -eq $true) -and
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1
index bd7fadbfbbb4..d4db33c46fd8 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1
@@ -41,19 +41,33 @@ function Invoke-CIPPStandardSafeLinksPolicy {
$TestResult = Test-CIPPStandardLicense -StandardName 'SafeLinksPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
- $ServicePlans = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus?$select=servicePlans' -tenantid $Tenant
- $ServicePlans = $ServicePlans.servicePlans.servicePlanName
- $MDOLicensed = $ServicePlans -contains 'ATP_ENTERPRISE'
+ $TenantCapabilities = Get-CIPPTenantCapabilities -TenantFilter $Tenant
+ $MDOLicensed = $TenantCapabilities.ATP_ENTERPRISE -eq $true
if ($MDOLicensed) {
+ # Single data retrieval calls with error handling
+ try {
+ $AllSafeLinksPolicy = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SafeLinksPolicy'
+ } catch {
+ $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
+ Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the SafeLinksPolicy state for $Tenant. Error: $ErrorMessage" -Sev Error
+ return
+ }
+ try {
+ $AllSafeLinksRule = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SafeLinksRule'
+ } catch {
+ $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
+ Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the SafeLinksRule state for $Tenant. Error: $ErrorMessage" -Sev Error
+ return
+ }
+
# Use custom name if provided, otherwise use default for backward compatibility
$PolicyName = if ($Settings.name) { $Settings.name } else { 'CIPP Default SafeLinks Policy' }
$PolicyList = @($PolicyName, 'CIPP Default SafeLinks Policy', 'Default SafeLinks Policy')
- $ExistingPolicy = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SafeLinksPolicy' | Where-Object -Property Name -In $PolicyList | Select-Object -First 1
+ $ExistingPolicy = $AllSafeLinksPolicy | Where-Object -Property Name -In $PolicyList | Select-Object -First 1
if ($null -eq $ExistingPolicy.Name) {
# No existing policy - use the configured/default name
$PolicyName = if ($Settings.name) { $Settings.name } else { 'CIPP Default SafeLinks Policy' }
@@ -64,7 +78,7 @@ function Invoke-CIPPStandardSafeLinksPolicy {
# Derive rule name from policy name, but check for old names for backward compatibility
$DesiredRuleName = "$PolicyName Rule"
$RuleList = @($DesiredRuleName, 'CIPP Default SafeLinks Rule', 'CIPP Default SafeLinks Policy')
- $ExistingRule = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SafeLinksRule' | Where-Object -Property Name -In $RuleList | Select-Object -First 1
+ $ExistingRule = $AllSafeLinksRule | Where-Object -Property Name -In $RuleList | Select-Object -First 1
if ($null -eq $ExistingRule.Name) {
# No existing rule - use the derived name
$RuleName = $DesiredRuleName
@@ -73,15 +87,9 @@ function Invoke-CIPPStandardSafeLinksPolicy {
$RuleName = $ExistingRule.Name
}
- try {
- $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SafeLinksPolicy' |
- Where-Object -Property Name -EQ $PolicyName |
- Select-Object Name, EnableSafeLinksForEmail, EnableSafeLinksForTeams, EnableSafeLinksForOffice, TrackClicks, AllowClickThrough, ScanUrls, EnableForInternalSenders, DeliverMessageAfterScan, DisableUrlRewrite, EnableOrganizationBranding, DoNotRewriteUrls
- } catch {
- $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
- Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the SafeLinksPolicy state for $Tenant. Error: $ErrorMessage" -Sev Error
- return
- }
+ $CurrentState = $AllSafeLinksPolicy |
+ Where-Object -Property Name -EQ $PolicyName |
+ Select-Object Name, EnableSafeLinksForEmail, EnableSafeLinksForTeams, EnableSafeLinksForOffice, TrackClicks, AllowClickThrough, ScanUrls, EnableForInternalSenders, DeliverMessageAfterScan, DisableUrlRewrite, EnableOrganizationBranding, DoNotRewriteUrls
$StateIsCorrect = ($CurrentState.Name -eq $PolicyName) -and
($CurrentState.EnableSafeLinksForEmail -eq $true) -and
@@ -98,7 +106,7 @@ function Invoke-CIPPStandardSafeLinksPolicy {
$AcceptedDomains = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AcceptedDomain'
- $RuleState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SafeLinksRule' |
+ $RuleState = $AllSafeLinksRule |
Where-Object -Property Name -EQ $RuleName |
Select-Object Name, SafeLinksPolicy, Priority, RecipientDomainIs
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1
index f3a91c2a43e6..800bd87a62ef 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardSafeLinksTemplatePolicy {
$TestResult = Test-CIPPStandardLicense -StandardName 'SafeLinksTemplatePolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -69,9 +68,8 @@ function Invoke-CIPPStandardSafeLinksTemplatePolicy {
function Test-MDOLicense {
param($Tenant, $Settings)
- $ServicePlans = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus?$select=servicePlans' -tenantid $Tenant
- $ServicePlans = $ServicePlans.servicePlans.servicePlanName
- $MDOLicensed = $ServicePlans -contains 'ATP_ENTERPRISE'
+ $TenantCapabilities = Get-CIPPTenantCapabilities -TenantFilter $Tenant
+ $MDOLicensed = $TenantCapabilities.ATP_ENTERPRISE -eq $true
if (-not $MDOLicensed) {
$Message = 'Tenant does not have Microsoft Defender for Office 365 license'
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1
index b879e3250c8d..cd172bd96070 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1
@@ -36,19 +36,18 @@ function Invoke-CIPPStandardSafeSendersDisable {
$TestResult = Test-CIPPStandardLicense -StandardName 'SafeSendersDisable' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
if ($Settings.remediate -eq $true) {
try {
$Mailboxes = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' -select 'UserPrincipalName'
- $Request = $Mailboxes | ForEach-Object {
+ $Request = foreach ($Mailbox in $Mailboxes) {
@{
CmdletInput = @{
CmdletName = 'Set-MailboxJunkEmailConfiguration'
Parameters = @{
- Identity = $_.UserPrincipalName
+ Identity = $Mailbox.UserPrincipalName
TrustedRecipientsAndDomains = $null
}
}
@@ -56,11 +55,10 @@ function Invoke-CIPPStandardSafeSendersDisable {
}
$BatchResults = New-ExoBulkRequest -tenantid $tenant -cmdletArray @($Request)
- $BatchResults | ForEach-Object {
- if ($_.error) {
- $ErrorMessage = Get-NormalizedError -Message $_.error
- Write-Host "Failed to Disable SafeSenders for $($_.target). Error: $ErrorMessage"
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to Disable SafeSenders for $($_.target). Error: $ErrorMessage" -sev Error
+ foreach ($Result in $BatchResults) {
+ if ($Result.error) {
+ $ErrorMessage = Get-NormalizedError -Message $Result.error
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to Disable SafeSenders for $($Result.target). Error: $ErrorMessage" -sev Error
}
}
Write-LogMessage -API 'Standards' -tenant $tenant -message 'Safe Senders disabled' -sev Info
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecureScoreRemediation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecureScoreRemediation.ps1
index 4534d08d73ef..d96f9702e39e 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecureScoreRemediation.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecureScoreRemediation.ps1
@@ -36,7 +36,7 @@ function Invoke-CIPPStandardSecureScoreRemediation {
# Get current secure score controls
try {
- $CurrentControls = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/security/secureScoreControlProfiles' -tenantid $Tenant
+ $CurrentControls = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/security/secureScoreControlProfiles?$top=999' -tenantid $Tenant
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not retrieve Secure Score controls for $Tenant. Error: $ErrorMessage" -sev Error
@@ -95,39 +95,66 @@ function Invoke-CIPPStandardSecureScoreRemediation {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Processing Secure Score control updates'
+ $ControlsNeedingUpdate = [System.Collections.Generic.List[object]]::new()
foreach ($Control in $ControlsToUpdate) {
# Skip if this is a Defender control (starts with scid_)
if ($Control.ControlName -match '^scid_') {
+ Write-Host 'scid'
Write-LogMessage -API 'Standards' -tenant $tenant -message "Skipping Defender control $($Control.ControlName) - cannot be updated via this API" -sev Info
continue
}
- # Build the request body
- $Body = @{
- state = $Control.State
- comment = $Control.Reason
- vendorInformation = @{
- vendor = 'Microsoft'
- provider = 'SecureScore'
+ $CurrentControl = $CurrentControls | Where-Object { $_.id -eq $Control.ControlName }
+
+ # Check if already in desired state
+ if ($CurrentControl.state -eq $Control.State) {
+ Write-Host 'Already in state'
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Control $($Control.ControlName) is already in state $($Control.State)" -sev Info
+ } else {
+ $ControlsNeedingUpdate.Add($Control)
+ }
+ }
+
+ # Build bulk requests for all controls that need updating
+ if ($ControlsNeedingUpdate.Count -gt 0) {
+ $int = 1
+ $BulkRequests = foreach ($Control in $ControlsNeedingUpdate) {
+ @{
+ id = $int++
+ method = 'PATCH'
+ url = "security/secureScoreControlProfiles/$($Control.ControlName)"
+ body = @{
+ state = $Control.State
+ comment = $Control.Reason
+ vendorInformation = @{
+ vendor = 'Microsoft'
+ provider = 'SecureScore'
+ }
+ }
+ headers = @{
+ 'Content-Type' = 'application/json'
+ }
}
}
try {
- $CurrentControl = $CurrentControls | Where-Object { $_.id -eq $Control.ControlName }
-
- # Check if already in desired state
- if ($CurrentControl.state -eq $Control.State) {
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Control $($Control.ControlName) is already in state $($Control.State)" -sev Info
- } else {
- # Update the control
- $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/security/secureScoreControlProfiles/$($Control.ControlName)" -tenantid $Tenant -type PATCH -Body (ConvertTo-Json -InputObject $Body -Compress)
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully set control $($Control.ControlName) to $($Control.State)" -sev Info
+ $BulkResults = New-GraphBulkRequest -tenantid $Tenant -Requests @($BulkRequests)
+
+ for ($i = 0; $i -lt $BulkResults.Count; $i++) {
+ $result = $BulkResults[$i]
+ $Control = $ControlsNeedingUpdate[$i]
+
+ if ($result.status -eq 200 -or $result.status -eq 204) {
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully set control $($Control.ControlName) to $($Control.State)" -sev Info
+ } else {
+ $errorMsg = if ($result.body.error.message) { $result.body.error.message } else { "Unknown error (Status: $($result.status))" }
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set control $($Control.ControlName) to $($Control.State). Error: $errorMsg" -sev Error
+ }
}
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
- Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set control $($Control.ControlName) to $($Control.State). Error: $ErrorMessage" -sev Error
+ Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to update secure score controls in bulk. Error: $ErrorMessage" -sev Error
}
}
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecurityDefaults.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecurityDefaults.ps1
index bc5c73b053b5..e774f32756e7 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecurityDefaults.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSecurityDefaults.ps1
@@ -44,7 +44,6 @@ function Invoke-CIPPStandardSecurityDefaults {
if ($SecureDefaultsState.IsEnabled -ne $true) {
try {
- Write-Host "Secure Defaults is disabled. Enabling for $tenant" -ForegroundColor Yellow
$body = '{ "isEnabled": true }'
$null = New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -Type patch -Body $body -ContentType 'application/json'
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1
index 4f2d624577b9..8dc6a4af027b 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardSendFromAlias {
$TestResult = Test-CIPPStandardLicense -StandardName 'SendFromAlias' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1
index be9fe0909549..0911e5d2634d 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardSendReceiveLimitTenant {
$TestResult = Test-CIPPStandardLicense -StandardName 'SendReceiveLimitTenant' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -78,10 +77,7 @@ function Invoke-CIPPStandardSendReceiveLimitTenant {
}
if ($Settings.remediate -eq $true) {
- Write-Host "Time to remediate. Our Settings are $($Settings.SendLimit)MB and $($Settings.ReceiveLimit)MB"
-
if ($NotSetCorrectly.Count -gt 0) {
- Write-Host "Found $($NotSetCorrectly.Count) Mailbox Plans that are not set correctly. Setting them to $($Settings.SendLimit)MB and $($Settings.ReceiveLimit)MB"
try {
foreach ($MailboxPlan in $NotSetCorrectly) {
New-ExoRequest -tenantid $Tenant -cmdlet 'Set-MailboxPlan' -cmdParams @{Identity = $MailboxPlan.GUID; MaxSendSize = $MaxSendSize; MaxReceiveSize = $MaxReceiveSize } -useSystemMailbox $true
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSharePointMassDeletionAlert.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSharePointMassDeletionAlert.ps1
index 4891f990cf02..682d24b2c0c2 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSharePointMassDeletionAlert.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSharePointMassDeletionAlert.ps1
@@ -36,7 +36,6 @@ function Invoke-CIPPStandardSharePointMassDeletionAlert {
$TestResult = Test-CIPPStandardLicense -StandardName 'DeletedUserRentention' -TenantFilter $Tenant -RequiredCapabilities @('RMS_S_PREMIUM2')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -44,8 +43,7 @@ function Invoke-CIPPStandardSharePointMassDeletionAlert {
try {
$CurrentState = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-ProtectionAlert' -Compliance |
- Where-Object { $_.Name -eq $PolicyName } |
- Select-Object -Property *
+ Where-Object { $_.Name -eq $PolicyName }
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the sharingCapability state for $Tenant. Error: $ErrorMessage" -Sev Error
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1
index b05f3abb32db..64b420cfa7c3 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1
@@ -36,10 +36,9 @@ function Invoke-CIPPStandardShortenMeetings {
$TestResult = Test-CIPPStandardLicense -StandardName 'ShortenMeetings' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
- Write-Host "ShortenMeetings: $($Settings | ConvertTo-Json -Compress)"
+
# Get state value using null-coalescing operator
$scopeDefault = $Settings.ShortenEventScopeDefault.value ? $Settings.ShortenEventScopeDefault.value : $Settings.ShortenEventScopeDefault
@@ -57,8 +56,6 @@ function Invoke-CIPPStandardShortenMeetings {
$CurrentState.DefaultMinutesToReduceLongEventsBy -eq $Settings.DefaultMinutesToReduceLongEventsBy) { $true } else { $false }
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
-
if ($CorrectState -eq $true) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Shorten meetings settings are already in the correct state. ' -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1
index 32a9cb7f22b1..37dd46c5c4a4 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1
@@ -55,7 +55,6 @@ function Invoke-CIPPStandardSpamFilterPolicy {
$TestResult = Test-CIPPStandardLicense -StandardName 'SpamFilterPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -64,8 +63,7 @@ function Invoke-CIPPStandardSpamFilterPolicy {
try {
$CurrentState = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-HostedContentFilterPolicy' |
- Where-Object -Property Name -EQ $PolicyName |
- Select-Object -Property *
+ Where-Object -Property Name -EQ $PolicyName
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the SpamFilterPolicy state for $Tenant. Error: $ErrorMessage" -Sev Error
@@ -136,8 +134,7 @@ function Invoke-CIPPStandardSpamFilterPolicy {
$AcceptedDomains = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-AcceptedDomain'
$RuleState = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-HostedContentFilterRule' |
- Where-Object -Property Name -EQ $PolicyName |
- Select-Object -Property *
+ Where-Object -Property Name -EQ $PolicyName
$RuleStateIsCorrect = ($RuleState.Name -eq $PolicyName) -and
($RuleState.HostedContentFilterPolicy -eq $PolicyName) -and
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1
index fc1989311104..3da3cec7fe99 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1
@@ -38,7 +38,6 @@ function Invoke-CIPPStandardSpoofWarn {
$TestResult = Test-CIPPStandardLicense -StandardName 'SpoofWarn' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -60,16 +59,12 @@ function Invoke-CIPPStandardSpoofWarn {
$AllowListCorrect = $true
if ($AllowListAdd -eq $null -or $AllowListAdd.Count -eq 0) {
- Write-Host 'No AllowList entries provided, skipping AllowList check.'
$AllowListAdd = @{'@odata.type' = '#Exchange.GenericHashTable'; Add = @() }
} else {
$AllowListAddEntries = foreach ($entry in $AllowListAdd) {
if ($CurrentInfo.AllowList -notcontains $entry) {
$AllowListCorrect = $false
- Write-Host "AllowList entry $entry not found in current AllowList"
$entry
- } else {
- Write-Host "AllowList entry $entry found in current AllowList."
}
}
$AllowListAdd = @{'@odata.type' = '#Exchange.GenericHashTable'; Add = $AllowListAddEntries }
@@ -86,7 +81,6 @@ function Invoke-CIPPStandardSpoofWarn {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate!'
$status = if ($Settings.enable -and $Settings.disable) {
# Handle pre standards v2.0 legacy settings when this was 2 separate standards
Write-LogMessage -API 'Standards' -tenant $Tenant -message 'You cannot both enable and disable the Spoof Warnings setting' -sev Error
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardStaleEntraDevices.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardStaleEntraDevices.ps1
index 88deaae2c234..bc202c1360ad 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardStaleEntraDevices.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardStaleEntraDevices.ps1
@@ -41,12 +41,11 @@ function Invoke-CIPPStandardStaleEntraDevices {
# Get all Entra devices
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
try {
- $AllDevices = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/devices' -tenantid $Tenant | Where-Object { $null -ne $_.approximateLastSignInDateTime }
+ $AllDevices = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/devices?$select=id,displayName,approximateLastSignInDateTime,accountEnabled,enrollmentProfileName,operatingSystem,managementType,profileType' -tenantid $Tenant | Where-Object { $null -ne $_.approximateLastSignInDateTime }
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the StaleEntraDevices state for $Tenant. Error: $ErrorMessage" -Sev Error
@@ -57,8 +56,6 @@ function Invoke-CIPPStandardStaleEntraDevices {
$StaleDevices = $AllDevices | Where-Object { $_.approximateLastSignInDateTime -lt $Date }
if ($Settings.remediate -eq $true) {
-
- Write-Host 'Remediation not implemented yet'
# TODO: Implement remediation. For others in the future that want to try this:
# Good MS guide on what to watch out for https://learn.microsoft.com/en-us/entra/identity/devices/manage-stale-devices#clean-up-stale-devices
# https://learn.microsoft.com/en-us/graph/api/device-list?view=graph-rest-beta&tabs=http
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsChatProtection.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsChatProtection.ps1
index 06bd0ed34f07..6c21fe2f03c4 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsChatProtection.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsChatProtection.ps1
@@ -36,7 +36,6 @@ function Invoke-CIPPStandardTeamsChatProtection {
$TestResult = Test-CIPPStandardLicense -StandardName 'TeamsChatProtection' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1
index 6cf019c7353e..df9b629223f3 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1
@@ -36,7 +36,6 @@ function Invoke-CIPPStandardTeamsEmailIntegration {
$TestResult = Test-CIPPStandardLicense -StandardName 'TeamsEmailIntegration' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEnrollUser.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEnrollUser.ps1
index ac908df686cb..bc64d8317237 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEnrollUser.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEnrollUser.ps1
@@ -36,7 +36,6 @@ function Invoke-CIPPStandardTeamsEnrollUser {
# Get EnrollUserOverride value using null-coalescing operator
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
$enrollUserOverride = $Settings.EnrollUserOverride.value ?? $Settings.EnrollUserOverride
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1
index 3b09e9240683..78d77b893356 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardTeamsExternalAccessPolicy {
$TestResult = Test-CIPPStandardLicense -StandardName 'TeamsExternalAccessPolicy' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalChatWithAnyone.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalChatWithAnyone.ps1
index 659a46c5d73a..2a83ae8a4a76 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalChatWithAnyone.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalChatWithAnyone.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardTeamsExternalChatWithAnyone {
$TestResult = Test-CIPPStandardLicense -StandardName 'TeamsExternalChatWithAnyone' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1
index a24252704227..296160fd73fb 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1
@@ -40,7 +40,6 @@ function Invoke-CIPPStandardTeamsExternalFileSharing {
$TestResult = Test-CIPPStandardLicense -StandardName 'TeamsExternalFileSharing' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1
index ee526b9037bb..2ac7b7ae4898 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1
@@ -36,7 +36,6 @@ function Invoke-CIPPStandardTeamsFederationConfiguration {
$TestResult = Test-CIPPStandardLicense -StandardName 'TeamsFederationConfiguration' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1','Teams_Room_Standard')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1
index 163e626da422..7c30eb607332 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1
@@ -44,7 +44,6 @@ function Invoke-CIPPStandardTeamsGlobalMeetingPolicy {
$TestResult = Test-CIPPStandardLicense -StandardName 'TeamsGlobalMeetingPolicy' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1
index 3c5c3d6336f9..14ea444c35b1 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardTeamsGuestAccess {
$TestResult = Test-CIPPStandardLicense -StandardName 'TeamsGuestAccess' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1
index 9004c3de2b30..9738625eb568 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1
@@ -36,7 +36,6 @@ function Invoke-CIPPStandardTeamsMeetingRecordingExpiration {
# Input validation
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
$ExpirationDays = try { [int64]$Settings.ExpirationDays } catch { Write-Warning "Invalid ExpirationDays value provided: $($Settings.ExpirationDays)"; return }
@@ -56,7 +55,6 @@ function Invoke-CIPPStandardTeamsMeetingRecordingExpiration {
$StateIsCorrect = if ($CurrentExpirationDays -eq $ExpirationDays) { $true } else { $false }
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
if ($StateIsCorrect -eq $true) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Teams Meeting Recording Expiration Policy already set to $ExpirationDays days." -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingVerification.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingVerification.ps1
index 119e3a0af58a..c9ac9f3a5db9 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingVerification.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingVerification.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardTeamsMeetingVerification {
$TestResult = Test-CIPPStandardLicense -StandardName 'TeamsMeetingVerification' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1
index 38cb28d5efae..187764549419 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardTeamsMeetingsByDefault {
$TestResult = Test-CIPPStandardLicense -StandardName 'TeamsMeetingsByDefault' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -59,7 +58,6 @@ function Invoke-CIPPStandardTeamsMeetingsByDefault {
}
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate'
if ($StateIsCorrect -eq $false) {
try {
$null = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-OrganizationConfig' -cmdParams @{ OnlineMeetingsByDefaultEnabled = $WantedState } -useSystemMailbox $true
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMessagingPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMessagingPolicy.ps1
index d2c938e26366..27aa5e7a4f8f 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMessagingPolicy.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMessagingPolicy.ps1
@@ -42,7 +42,6 @@ function Invoke-CIPPStandardTeamsMessagingPolicy {
$TestResult = Test-CIPPStandardLicense -StandardName 'TeamsMessagingPolicy' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1
index 265ff6116e17..7463f6e7e773 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardTenantDefaultTimezone {
$TestResult = Test-CIPPStandardLicense -StandardName 'TenantDefaultTimezone' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1
index d129d05ab124..1387dc113b7d 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1
@@ -31,16 +31,13 @@ function Invoke-CIPPStandardTransportRuleTemplate {
$TestResult = Test-CIPPStandardLicense -StandardName 'TransportRuleTemplate' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
$existingRules = New-ExoRequest -ErrorAction SilentlyContinue -tenantid $Tenant -cmdlet 'Get-TransportRule' -useSystemMailbox $true
if ($Settings.remediate -eq $true) {
- Write-Host "Settings: $($Settings | ConvertTo-Json)"
$Settings.transportRuleTemplate ? ($Settings | Add-Member -NotePropertyName 'TemplateList' -NotePropertyValue $Settings.transportRuleTemplate) : $null
foreach ($Template in $Settings.TemplateList) {
- Write-Host "working on $($Template.value)"
$Table = Get-CippTable -tablename 'templates'
$Filter = "PartitionKey eq 'TransportTemplate' and RowKey eq '$($Template.value)'"
$RequestParams = (Get-AzDataTableEntity @Table -Filter $Filter).JSON | ConvertFrom-Json
@@ -48,7 +45,6 @@ function Invoke-CIPPStandardTransportRuleTemplate {
try {
if ($Existing) {
- Write-Host 'Found existing'
if ($Settings.overwrite) {
$RequestParams | Add-Member -NotePropertyValue $RequestParams.name -NotePropertyName Identity
$GraphRequest = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-TransportRule' -cmdParams ($RequestParams | Select-Object -Property * -ExcludeProperty GUID, Comments, HasSenderOverride, ExceptIfHasSenderOverride, ExceptIfMessageContainsDataClassifications, MessageContainsDataClassifications, UseLegacyRegex) -useSystemMailbox $true
@@ -57,7 +53,6 @@ function Invoke-CIPPStandardTransportRuleTemplate {
Write-LogMessage -API 'Standards' -tenant $tenant -message "Skipping transport rule for $tenant as it already exists" -sev 'Info'
}
} else {
- Write-Host 'Creating new'
$GraphRequest = New-ExoRequest -tenantid $Tenant -cmdlet 'New-TransportRule' -cmdParams ($RequestParams | Select-Object -Property * -ExcludeProperty GUID, Comments, HasSenderOverride, ExceptIfHasSenderOverride, ExceptIfMessageContainsDataClassifications, MessageContainsDataClassifications, UseLegacyRegex) -useSystemMailbox $true
Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully created transport rule for $tenant" -sev 'Info'
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTwoClickEmailProtection.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTwoClickEmailProtection.ps1
index 3368eaa5be6c..cd23ae592974 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTwoClickEmailProtection.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTwoClickEmailProtection.ps1
@@ -34,7 +34,6 @@ function Invoke-CIPPStandardTwoClickEmailProtection {
$TestResult = Test-CIPPStandardLicense -StandardName 'TwoClickEmailProtection' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -59,8 +58,6 @@ function Invoke-CIPPStandardTwoClickEmailProtection {
$StateIsCorrect = $CurrentState -eq $WantedState ? $true : $false
if ($Settings.remediate -eq $true) {
- Write-Host 'Time to remediate two-click email protection'
-
if ($StateIsCorrect -eq $true) {
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Two-click email protection is already set to $State." -sev Info
} else {
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserPreferredLanguage.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserPreferredLanguage.ps1
index c124c14d352e..08c7dfd53772 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserPreferredLanguage.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserPreferredLanguage.ps1
@@ -33,7 +33,12 @@ function Invoke-CIPPStandardUserPreferredLanguage {
$preferredLanguage = $Settings.preferredLanguage.value
try {
- $IncorrectUsers = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$top=999&`$select=userPrincipalName,displayName,preferredLanguage,userType,onPremisesSyncEnabled&`$filter=preferredLanguage ne '$preferredLanguage' and userType eq 'Member' and onPremisesSyncEnabled ne true&`$count=true" -tenantid $Tenant -ComplexFilter
+ $AllUsers = New-CIPPDbRequest -TenantFilter $Tenant -Type 'Users'
+ $IncorrectUsers = $AllUsers | Where-Object {
+ ($null -eq $_.preferredLanguage -or $_.preferredLanguage -ne $preferredLanguage) -and
+ $_.userType -eq 'Member' -and
+ $_.onPremisesSyncEnabled -ne $true
+ }
} catch {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the UserPreferredLanguage state for $Tenant. Error: $ErrorMessage" -Sev Error
@@ -61,6 +66,13 @@ function Invoke-CIPPStandardUserPreferredLanguage {
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set preferred language to $preferredLanguage for all users." -sev Error -LogData $ErrorMessage
}
+
+ # Refresh user cache after remediation
+ try {
+ Set-CIPPDBCacheUsers -TenantFilter $Tenant
+ } catch {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to refresh user cache after remediation: $($_.Exception.Message)" -sev Warning
+ }
}
}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1
index d0653e4d2c8f..265959e7ce6a 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardUserSubmissions {
$TestResult = Test-CIPPStandardLicense -StandardName 'UserSubmissions' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1
index 193addccbdc1..2edce2a10d35 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1
@@ -37,7 +37,6 @@ function Invoke-CIPPStandardcalDefault {
$TestResult = Test-CIPPStandardLicense -StandardName 'calDefault' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -51,69 +50,65 @@ function Invoke-CIPPStandardcalDefault {
}
if ($Settings.remediate -eq $true) {
- $Mailboxes = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' | Sort-Object UserPrincipalName
- $TotalMailboxes = $Mailboxes.Count
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Started setting default calendar permissions for $($TotalMailboxes) mailboxes." -sev Info
-
- # Retrieve the last run status
- $LastRunTable = Get-CIPPTable -Table StandardsLastRun
- $Filter = "RowKey eq 'calDefaults' and PartitionKey eq '{0}'" -f $tenant
- $LastRun = Get-CIPPAzDataTableEntity @LastRunTable -Filter $Filter
-
- $startIndex = 0
- if ($LastRun -and $LastRun.processedMailboxes -lt $LastRun.totalMailboxes ) {
- $startIndex = $LastRun.processedMailboxes
- }
+ try {
+ # Get calendar permissions from cache - this contains the calendar Identity we need
+ $CalendarPermissions = New-CIPPDbRequest -TenantFilter $Tenant -Type 'CalendarPermissions'
- $SuccessCounter = if ($startIndex -eq 0) { 0 } else { [int64]$LastRun.currentSuccessCount }
- $processedMailboxes = $startIndex
- $Mailboxes = $Mailboxes[$startIndex..($TotalMailboxes - 1)]
- Write-Host "CalDefaults Starting at index $startIndex"
- Write-Host "CalDefaults success counter starting at $SuccessCounter"
- Write-Host "CalDefaults Processing $($Mailboxes.Count) mailboxes"
- $Mailboxes | ForEach-Object {
- $Mailbox = $_
- try {
- New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MailboxFolderStatistics' -cmdParams @{identity = $Mailbox.UserPrincipalName; FolderScope = 'Calendar' } -Anchor $Mailbox.UserPrincipalName | Where-Object { $_.FolderType -eq 'Calendar' } |
- ForEach-Object {
- try {
- New-ExoRequest -tenantid $Tenant -cmdlet 'Set-MailboxFolderPermission' -cmdParams @{Identity = "$($Mailbox.UserPrincipalName):$($_.FolderId)"; User = 'Default'; AccessRights = $permissionLevel } -Anchor $Mailbox.UserPrincipalName
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Set default folder permission for $($Mailbox.UserPrincipalName):\$($_.Name) to $permissionLevel" -sev Debug
- $SuccessCounter++
- } catch {
- $ErrorMessage = Get-CippException -Exception $_
- Write-Host "Setting cal failed: $ErrorMessage"
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not set default calendar permissions for $($Mailbox.UserPrincipalName). Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
- }
+ if (-not $CalendarPermissions) {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message 'No cached calendar permissions found. Please ensure the mailbox cache has been populated.' -sev Error
+ return
+ }
+
+ # Filter to only Default user permissions that don't match target level
+ $DefaultPermissions = $CalendarPermissions | Where-Object { $_.User -eq 'Default' }
+ $NeedsUpdate = $DefaultPermissions | Where-Object {
+ $currentRights = if ($_.AccessRights -is [array]) { $_.AccessRights -join ',' } else { $_.AccessRights }
+ $currentRights -ne $permissionLevel
+ }
+
+ $TotalCalendars = $DefaultPermissions.Count
+ $CalendarsToUpdate = $NeedsUpdate.Count
+
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Found $TotalCalendars calendars. $CalendarsToUpdate need permission update to $permissionLevel." -sev Info
+
+ if ($CalendarsToUpdate -eq 0) {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message 'All calendars already have the correct default permission level.' -sev Info
+ return
+ }
+
+ # Set permissions for each calendar that needs updating
+ $SuccessCounter = 0
+ $ErrorCounter = 0
+
+ foreach ($Calendar in $NeedsUpdate) {
+ try {
+ New-ExoRequest -tenantid $Tenant -cmdlet 'Set-MailboxFolderPermission' -cmdParams @{
+ Identity = $Calendar.Identity
+ User = 'Default'
+ AccessRights = $permissionLevel
}
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Set default calendar permission for $($Calendar.Identity) to $permissionLevel" -sev Debug
+ $SuccessCounter++
} catch {
+ $ErrorCounter++
$ErrorMessage = Get-CippException -Exception $_
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not set default calendar permissions for $($Mailbox.UserPrincipalName). Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
- }
- $processedMailboxes++
- if ($processedMailboxes % 25 -eq 0) {
- $LastRun = @{
- RowKey = 'calDefaults'
- PartitionKey = $Tenant
- totalMailboxes = $TotalMailboxes
- processedMailboxes = $processedMailboxes
- currentSuccessCount = $SuccessCounter
- }
- Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force
- Write-Host "Processed $processedMailboxes mailboxes"
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set calendar permission for $($Calendar.Identity): $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
}
}
- $LastRun = @{
- RowKey = 'calDefaults'
- PartitionKey = $Tenant
- totalMailboxes = $TotalMailboxes
- processedMailboxes = $processedMailboxes
- currentSuccessCount = $SuccessCounter
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully set default calendar permissions for $SuccessCounter calendars. $ErrorCounter failed." -sev Info
+
+ # Refresh calendar permissions cache after remediation
+ try {
+ Set-CIPPDBCacheMailboxes -TenantFilter $Tenant
+ } catch {
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to refresh mailbox cache after remediation: $($_.Exception.Message)" -sev Warning
}
- Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force
- Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully set default calendar permissions for $SuccessCounter out of $TotalMailboxes mailboxes." -sev Info
+ } catch {
+ $ErrorMessage = Get-CippException -Exception $_
+ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not set default calendar permissions. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
}
-
}
+
+}
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1
index df8b04ecfdeb..47f420c9ac3f 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1
@@ -33,7 +33,6 @@ function Invoke-CIPPStandarddisableMacSync {
$TestResult = Test-CIPPStandardLicense -StandardName 'disableMacSync' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1
index cd36588ae4ad..66ed49f285ec 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1
@@ -43,7 +43,6 @@ function Invoke-CIPPStandardintuneBrandingProfile {
$TestResult = Test-CIPPStandardLicense -StandardName 'intuneBrandingProfile' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1
index 24948cf10bf7..aa31d08e889b 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardintuneDeviceReg {
$TestResult = Test-CIPPStandardLicense -StandardName 'intuneDeviceReg' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1
index b980c034ddd0..6441d9ff09bc 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1
@@ -35,7 +35,6 @@ function Invoke-CIPPStandardintuneDeviceRetirementDays {
$TestResult = Test-CIPPStandardLicense -StandardName 'intuneDeviceRetirementDays' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1
index 054066c38646..f19d3dba95b4 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1
@@ -39,7 +39,6 @@ function Invoke-CIPPStandardsharingCapability {
$TestResult = Test-CIPPStandardLicense -StandardName 'sharingCapability' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -67,10 +66,8 @@ function Invoke-CIPPStandardsharingCapability {
if ($Settings.remediate -eq $true) {
if ($CurrentInfo.sharingCapability -eq $level) {
- Write-Host "Sharing level is already set to $level"
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Sharing level is already set to $level" -sev Info
} else {
- Write-Host "Setting sharing level to $level from $($CurrentInfo.sharingCapability)"
try {
$body = @{
sharingCapability = $level
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1
index 6aae7a5b8dcd..f0bd6cff3c37 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1
@@ -38,7 +38,6 @@ function Invoke-CIPPStandardsharingDomainRestriction {
$TestResult = Test-CIPPStandardLicense -StandardName 'sharingDomainRestriction' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
@@ -65,7 +64,6 @@ function Invoke-CIPPStandardsharingDomainRestriction {
($mode -eq 'blockList' -and ([string[]]($CurrentBlockedDomains | Sort-Object) -join ',') -eq ([string[]]($SelectedDomains | Sort-Object) -join ','))
)
}
- Write-Host "StateIsCorrect: $StateIsCorrect"
if ($Settings.remediate -eq $true) {
if ($StateIsCorrect -eq $true) {
@@ -89,8 +87,6 @@ function Invoke-CIPPStandardsharingDomainRestriction {
body = ($Body | ConvertTo-Json)
}
- Write-Host ($cmdParams | ConvertTo-Json -Depth 5)
-
try {
$null = New-GraphPostRequest @cmdParams
Write-LogMessage -API 'Standards' -tenant $tenant -message 'Successfully updated Sharing Domain Restriction settings' -sev Info
diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1
index 18b75b21b643..fdf2bd41931f 100644
--- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1
+++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1
@@ -38,7 +38,6 @@ function Invoke-CIPPStandardunmanagedSync {
$TestResult = Test-CIPPStandardLicense -StandardName 'unmanagedSync' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1')
if ($TestResult -eq $false) {
- Write-Host "We're exiting as the correct license is not present for this standard."
return $true
} #we're done.
diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1
index e95cdb074f8d..9843f4c7de09 100644
--- a/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1
+++ b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1
@@ -25,7 +25,6 @@ function Test-CIPPAccessPermissions {
}
$Success = $true
try {
- Set-Location (Get-Item $PSScriptRoot).FullName
$null = Get-CIPPAuthentication
$GraphToken = Get-GraphToken -returnRefresh $true -SkipCache $true
if ($GraphToken) {
diff --git a/Modules/CippEntrypoints/CippEntrypoints.psm1 b/Modules/CippEntrypoints/CippEntrypoints.psm1
index cda294332f1a..1f521af36c66 100644
--- a/Modules/CippEntrypoints/CippEntrypoints.psm1
+++ b/Modules/CippEntrypoints/CippEntrypoints.psm1
@@ -39,7 +39,6 @@ function Receive-CippHttpTrigger {
# Convert the request to a PSCustomObject because the httpContext is case sensitive since 7.3
$Request = $Request | ConvertTo-Json -Depth 100 | ConvertFrom-Json
- Set-Location (Get-Item $PSScriptRoot).Parent.Parent.FullName
if ($Request.Params.CIPPEndpoint -eq '$batch') {
# Implement batch processing in the style of graph api $batch
@@ -291,7 +290,6 @@ function Receive-CippActivityTrigger {
Write-Warning "Hey Boo, the activity function is running. Here's some info: $($Item | ConvertTo-Json -Depth 10 -Compress)"
try {
$Output = $null
- Set-Location (Get-Item $PSScriptRoot).Parent.Parent.FullName
$metric = @{
Kind = 'CIPPCommandStart'
InvocationId = "$($ExecutionContext.InvocationId)"
diff --git a/Modules/CippExtensions/Public/Gradient/New-GradientServiceSyncRun.ps1 b/Modules/CippExtensions/Public/Gradient/New-GradientServiceSyncRun.ps1
index cb85b1b60c74..b3e32933987b 100644
--- a/Modules/CippExtensions/Public/Gradient/New-GradientServiceSyncRun.ps1
+++ b/Modules/CippExtensions/Public/Gradient/New-GradientServiceSyncRun.ps1
@@ -27,8 +27,8 @@ function New-GradientServiceSyncRun {
}
- Set-Location (Get-Item $PSScriptRoot).Parent.FullName
- $ConvertTable = Import-Csv ConversionTable.csv
+ Set-Location (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent.FullName
+ $ConvertTable = Import-Csv Resources\ConversionTable.csv
$Table = Get-CIPPTable -TableName cachelicenses
$LicenseTable = Get-CIPPTable -TableName ExcludedLicenses
$ExcludedSkuList = Get-CIPPAzDataTableEntity @LicenseTable
diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1
index 19b1e8c13def..61b68e9e3b02 100644
--- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1
+++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1
@@ -34,7 +34,7 @@ function Invoke-HuduExtensionSync {
# Import license mapping
Set-Location (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent.FullName
- $LicTable = Import-Csv ConversionTable.csv
+ $LicTable = Import-Csv Resources\ConversionTable.csv
$CompanyResult.Logs.Add('Starting Hudu Extension Sync')
diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1
index 7d2b1a4b9aab..ca67d3b360e3 100644
--- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1
+++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1
@@ -1846,9 +1846,6 @@ function Invoke-NinjaOneTenantSync {
### CIPP Applied Standards Cards
Write-Information 'Applied Standards'
- $ModuleBase = Get-Module CIPPExtensions | Select-Object -ExpandProperty ModuleBase
- $CIPPRoot = (Get-Item $ModuleBase).Parent.Parent.FullName
- Set-Location $CIPPRoot
try {
$StandardsDefinitions = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/KelvinTegelaar/CIPP/refs/heads/main/src/data/standards.json'
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/BarracudaESS.json b/Modules/DNSHealth/1.1.0/MailProviders/BarracudaESS.json
deleted file mode 100644
index cc7349d1435d..000000000000
--- a/Modules/DNSHealth/1.1.0/MailProviders/BarracudaESS.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "Name": "Barracuda Email Security Service",
- "_MxComment": "https://campus.barracuda.com/product/essentials/doc/86545480/step-2-configure-office-365-for-inbound-and-outbound-mail/",
- "MxMatch": "ess(?.[a-z]{2})?.barracudanetworks.com",
- "_SpfComment": "https://campus.barracuda.com/product/essentials/doc/73702276/sender-policy-framework-for-outbound-mail",
- "SpfInclude": "spf.ess{0}.barracudanetworks.com",
- "SpfReplace": ["Country"],
- "_DkimComment": "No configuration found",
- "Selectors": [""]
-}
\ No newline at end of file
diff --git a/Modules/DNSHealth/1.1.0/DNSHealth.psd1 b/Modules/DNSHealth/1.1.2/DNSHealth.psd1
similarity index 99%
rename from Modules/DNSHealth/1.1.0/DNSHealth.psd1
rename to Modules/DNSHealth/1.1.2/DNSHealth.psd1
index 651cc2720db7..b6f33b295966 100644
--- a/Modules/DNSHealth/1.1.0/DNSHealth.psd1
+++ b/Modules/DNSHealth/1.1.2/DNSHealth.psd1
@@ -12,7 +12,7 @@
RootModule = 'DNSHealth.psm1'
# Version number of this module.
- ModuleVersion = '1.1.0'
+ ModuleVersion = '1.1.2'
# Supported PSEditions
# CompatiblePSEditions = @()
diff --git a/Modules/DNSHealth/1.1.0/DNSHealth.psm1 b/Modules/DNSHealth/1.1.2/DNSHealth.psm1
similarity index 98%
rename from Modules/DNSHealth/1.1.0/DNSHealth.psm1
rename to Modules/DNSHealth/1.1.2/DNSHealth.psm1
index cbefac309733..26e008d67c06 100644
--- a/Modules/DNSHealth/1.1.0/DNSHealth.psm1
+++ b/Modules/DNSHealth/1.1.2/DNSHealth.psm1
@@ -1155,13 +1155,13 @@ function Read-MXRecord {
elseif ($Result.Status -ne 0 -or -not ($Result.Answer)) {
if ($Result.Status -eq 3) {
$ValidationFails.Add($NoMxValidation) | Out-Null
- $MXResults.MailProvider = Get-Content "$PSScriptRoot\MailProviders\Null.json" | ConvertFrom-Json
+ $MXResults.MailProvider = Get-Content "$($MyInvocation.MyCommand.Module.ModuleBase)\MailProviders\Null.json" | ConvertFrom-Json
$MXResults.Selectors = $MXRecords.MailProvider.Selectors
}
else {
$ValidationFails.Add($NoMxValidation) | Out-Null
- $MXResults.MailProvider = Get-Content "$PSScriptRoot\MailProviders\Null.json" | ConvertFrom-Json
+ $MXResults.MailProvider = Get-Content "$($MyInvocation.MyCommand.Module.ModuleBase)\MailProviders\Null.json" | ConvertFrom-Json
$MXResults.Selectors = $MXRecords.MailProvider.Selectors
}
$MXRecords = $null
@@ -1183,17 +1183,17 @@ function Read-MXRecord {
$MXRecords = $MXRecords | Sort-Object -Property Priority
# Attempt to identify mail provider based on MX record
- if (Test-Path "$PSScriptRoot\MailProviders") {
+ if (Test-Path "$($MyInvocation.MyCommand.Module.ModuleBase)\MailProviders") {
$ReservedVariables = @{
'DomainNameDashNotation' = $Domain -replace '\.', '-'
}
if ($MXRecords.Hostname -eq '') {
$ValidationFails.Add($NoMxValidation) | Out-Null
- $MXResults.MailProvider = Get-Content "$PSScriptRoot\MailProviders\Null.json" | ConvertFrom-Json
+ $MXResults.MailProvider = Get-Content "$($MyInvocation.MyCommand.Module.ModuleBase)\MailProviders\Null.json" | ConvertFrom-Json
}
else {
- $ProviderList = Get-ChildItem "$PSScriptRoot\MailProviders" -Exclude '_template.json' | ForEach-Object {
+ $ProviderList = Get-ChildItem "$($MyInvocation.MyCommand.Module.ModuleBase)\MailProviders" -Exclude '_template.json' | ForEach-Object {
try { Get-Content $_ | ConvertFrom-Json -ErrorAction Stop }
catch { Write-Verbose $_.Exception.Message }
}
@@ -1345,7 +1345,7 @@ function Read-SpfRecord {
Author: John Duprey
#>
[CmdletBinding(DefaultParameterSetName = 'Lookup')]
- Param(
+ param(
[Parameter(Mandatory = $true, ParameterSetName = 'Lookup')]
[Parameter(ParameterSetName = 'Manual')]
[string]$Domain,
@@ -1774,6 +1774,30 @@ function Read-SpfRecord {
$ValidationPasses.Add('The SPF record ends with a hard fail qualifier (-all). This is best practice and will instruct recipients to discard unauthorized senders.') | Out-Null
}
+ elseif ($AllMechanism -eq '~all') {
+ # Check DMARC policy for soft fail
+ $DmarcRejectPolicy = $false
+ try {
+ $DmarcPolicy = Read-DmarcPolicy -Domain $Domain -ErrorAction Stop
+ if ($DmarcPolicy.Policy -eq 'reject' -and ($DmarcPolicy.Percent -eq 100 -or $null -eq $DmarcPolicy.Percent)) {
+ $DmarcRejectPolicy = $true
+ }
+ } catch {
+ Write-Verbose "Unable to read DMARC policy: $($_.Exception.Message)"
+ }
+
+ if ($DmarcRejectPolicy) {
+ $ValidationPasses.Add('The SPF record ends with a soft fail qualifier (~all). With DMARC p=reject at 100%, this is acceptable as DMARC will enforce rejection.') | Out-Null
+ } else {
+ $ValidationFails.Add('The SPF record should end in -all to prevent spamming.') | Out-Null
+ $Recommendations.Add([PSCustomObject]@{
+ Message = "Replace '~all' with '-all' to make a SPF failure result in a hard fail."
+ Match = '~all'
+ Replace = '-all'
+ }) | Out-Null
+ }
+ }
+
elseif ($Record -ne '') {
$ValidationFails.Add('The SPF record should end in -all to prevent spamming.') | Out-Null
$Recommendations.Add([PSCustomObject]@{
@@ -1865,7 +1889,7 @@ function Read-SpfRecord {
# Output SpfResults object
$SpfResults
}
-#EndRegion './Public/Records/Read-SPFRecord.ps1' 553
+#EndRegion './Public/Records/Read-SPFRecord.ps1' 577
#Region './Public/Records/Read-TlsRptRecord.ps1' -1
function Read-TlsRptRecord {
@@ -2269,7 +2293,7 @@ function Resolve-DnsHttpsQuery {
if (!$Results) { throw 'Exception querying resolver {0}: {1}' -f $Resolver.Resolver, $Exception.Exception.Message }
if ($RecordType -eq 'txt' -and $Results.Answer) {
- if ($Resolver -eq 'Cloudflare' -or $Resolver -eq 'Quad9') {
+ if ($Resolver -eq 'Cloudflare') {
$Results.Answer | ForEach-Object {
$_.data = $_.data -replace '" "' -replace '"', ''
}
@@ -2284,9 +2308,9 @@ function Resolve-DnsHttpsQuery {
function Set-DnsResolver {
[CmdletBinding(SupportsShouldProcess)]
- Param(
+ param(
[Parameter()]
- [ValidateSet('Google', 'Cloudflare', 'Quad9')]
+ [ValidateSet('Google', 'Cloudflare')]
[string]$Resolver = 'Google'
)
@@ -2306,17 +2330,10 @@ function Set-DnsResolver {
QueryTemplate = '{0}?name={1}&type={2}'
}
}
- 'Quad9' {
- [PSCustomObject]@{
- Resolver = $Resolver
- BaseUri = 'https://dns.quad9.net:5053/dns-query'
- QueryTemplate = '{0}?name={1}&type={2}'
- }
- }
}
}
}
-#EndRegion './Public/Resolver/Set-DnsResolver.ps1' 35
+#EndRegion './Public/Resolver/Set-DnsResolver.ps1' 28
#Region './Public/Tests/Test-DNSSEC.ps1' -1
function Test-DNSSEC {
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/AppRiver.json b/Modules/DNSHealth/1.1.2/MailProviders/AppRiver.json
similarity index 100%
rename from Modules/DNSHealth/1.1.0/MailProviders/AppRiver.json
rename to Modules/DNSHealth/1.1.2/MailProviders/AppRiver.json
diff --git a/Modules/DNSHealth/1.1.2/MailProviders/BarracudaESS.json b/Modules/DNSHealth/1.1.2/MailProviders/BarracudaESS.json
new file mode 100644
index 000000000000..febadc8c686a
--- /dev/null
+++ b/Modules/DNSHealth/1.1.2/MailProviders/BarracudaESS.json
@@ -0,0 +1,10 @@
+{
+ "Name": "Barracuda Email Gateway Defense",
+ "_MxComment": "https://campus.barracuda.com/product/emailgatewaydefense/doc/167976430/step-2-configure-microsoft-365-for-inbound-and-outbound-mail",
+ "MxMatch": "ess(?.[a-z]{2})?.barracudanetworks.com",
+ "_SpfComment": "https://campus.barracuda.com/product/emailgatewaydefense/doc/167976840/sender-policy-framework-for-outbound-mail",
+ "SpfInclude": "spf.ess{0}.barracudanetworks.com",
+ "SpfReplace": ["Country"],
+ "_DkimComment": "No configuration found",
+ "Selectors": [""]
+}
\ No newline at end of file
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/Google.json b/Modules/DNSHealth/1.1.2/MailProviders/Google.json
similarity index 100%
rename from Modules/DNSHealth/1.1.0/MailProviders/Google.json
rename to Modules/DNSHealth/1.1.2/MailProviders/Google.json
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/HornetSecurity.json b/Modules/DNSHealth/1.1.2/MailProviders/HornetSecurity.json
similarity index 100%
rename from Modules/DNSHealth/1.1.0/MailProviders/HornetSecurity.json
rename to Modules/DNSHealth/1.1.2/MailProviders/HornetSecurity.json
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/Intermedia.json b/Modules/DNSHealth/1.1.2/MailProviders/Intermedia.json
similarity index 100%
rename from Modules/DNSHealth/1.1.0/MailProviders/Intermedia.json
rename to Modules/DNSHealth/1.1.2/MailProviders/Intermedia.json
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/Microsoft365.json b/Modules/DNSHealth/1.1.2/MailProviders/Microsoft365.json
similarity index 88%
rename from Modules/DNSHealth/1.1.0/MailProviders/Microsoft365.json
rename to Modules/DNSHealth/1.1.2/MailProviders/Microsoft365.json
index 35fb2c66b9df..13d02769ff7f 100644
--- a/Modules/DNSHealth/1.1.0/MailProviders/Microsoft365.json
+++ b/Modules/DNSHealth/1.1.2/MailProviders/Microsoft365.json
@@ -1,6 +1,6 @@
{
"Name": "Microsoft 365",
- "MxMatch": "mail.protection.outlook.com|mx.microsoft",
+ "MxMatch": "mail.protection.outlook.com|mx.microsoft|mail.eo.outlook.com",
"SpfInclude": "spf.protection.outlook.com",
"Selectors": ["selector1", "selector2"],
"MinimumSelectorPass": 1,
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/Mimecast.json b/Modules/DNSHealth/1.1.2/MailProviders/Mimecast.json
similarity index 100%
rename from Modules/DNSHealth/1.1.0/MailProviders/Mimecast.json
rename to Modules/DNSHealth/1.1.2/MailProviders/Mimecast.json
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/Null.json b/Modules/DNSHealth/1.1.2/MailProviders/Null.json
similarity index 100%
rename from Modules/DNSHealth/1.1.0/MailProviders/Null.json
rename to Modules/DNSHealth/1.1.2/MailProviders/Null.json
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/Proofpoint.json b/Modules/DNSHealth/1.1.2/MailProviders/Proofpoint.json
similarity index 100%
rename from Modules/DNSHealth/1.1.0/MailProviders/Proofpoint.json
rename to Modules/DNSHealth/1.1.2/MailProviders/Proofpoint.json
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/Reflexion.json b/Modules/DNSHealth/1.1.2/MailProviders/Reflexion.json
similarity index 100%
rename from Modules/DNSHealth/1.1.0/MailProviders/Reflexion.json
rename to Modules/DNSHealth/1.1.2/MailProviders/Reflexion.json
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/Sophos.json b/Modules/DNSHealth/1.1.2/MailProviders/Sophos.json
similarity index 100%
rename from Modules/DNSHealth/1.1.0/MailProviders/Sophos.json
rename to Modules/DNSHealth/1.1.2/MailProviders/Sophos.json
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/SpamTitan.json b/Modules/DNSHealth/1.1.2/MailProviders/SpamTitan.json
similarity index 100%
rename from Modules/DNSHealth/1.1.0/MailProviders/SpamTitan.json
rename to Modules/DNSHealth/1.1.2/MailProviders/SpamTitan.json
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/SymantecCloud.json b/Modules/DNSHealth/1.1.2/MailProviders/SymantecCloud.json
similarity index 100%
rename from Modules/DNSHealth/1.1.0/MailProviders/SymantecCloud.json
rename to Modules/DNSHealth/1.1.2/MailProviders/SymantecCloud.json
diff --git a/Modules/DNSHealth/1.1.0/MailProviders/_template.json b/Modules/DNSHealth/1.1.2/MailProviders/_template.json
similarity index 100%
rename from Modules/DNSHealth/1.1.0/MailProviders/_template.json
rename to Modules/DNSHealth/1.1.2/MailProviders/_template.json
diff --git a/Modules/DNSHealth/1.1.0/PSGetModuleInfo.xml b/Modules/DNSHealth/1.1.2/PSGetModuleInfo.xml
similarity index 79%
rename from Modules/DNSHealth/1.1.0/PSGetModuleInfo.xml
rename to Modules/DNSHealth/1.1.2/PSGetModuleInfo.xml
index e64f6f169372..85f904f8990e 100644
--- a/Modules/DNSHealth/1.1.0/PSGetModuleInfo.xml
+++ b/Modules/DNSHealth/1.1.2/PSGetModuleInfo.xml
@@ -1,144 +1,144 @@
-
-
-
- Microsoft.PowerShell.Commands.PSRepositoryItemInfo
- System.Management.Automation.PSCustomObject
- System.Object
-
-
- DNSHealth
- 1.1.0
- Module
- CIPP DNS Health Check Module
- John Duprey
- johnduprey
- 2023 John Duprey
- 2025-05-27T23:15:27-04:00
-
-
-
- https://github.com/johnduprey/DNSHealth
-
-
-
- System.Object[]
- System.Array
- System.Object
-
-
- PSModule
-
-
-
-
- System.Collections.Hashtable
- System.Object
-
-
-
- Command
-
-
-
- Read-DmarcPolicy
- Read-MtaStsPolicy
- Read-DkimRecord
- Read-MtaStsRecord
- Read-MXRecord
- Read-NSRecord
- Read-SPFRecord
- Read-TlsRptRecord
- Read-WhoisRecord
- Resolve-DnsHttpsQuery
- Set-DnsResolver
- Test-DNSSEC
- Test-HttpsCertificate
- Test-MtaSts
-
-
-
-
- Function
-
-
-
- Read-DmarcPolicy
- Read-MtaStsPolicy
- Read-DkimRecord
- Read-MtaStsRecord
- Read-MXRecord
- Read-NSRecord
- Read-SPFRecord
- Read-TlsRptRecord
- Read-WhoisRecord
- Resolve-DnsHttpsQuery
- Set-DnsResolver
- Test-DNSSEC
- Test-HttpsCertificate
- Test-MtaSts
-
-
-
-
- Workflow
-
-
-
-
-
-
- RoleCapability
-
-
-
- DscResource
-
-
-
- Cmdlet
-
-
-
-
-
-
-
-
-
-
- https://www.powershellgallery.com/api/v2
- PSGallery
- NuGet
-
-
- System.Management.Automation.PSCustomObject
- System.Object
-
-
- 2023 John Duprey
- CIPP DNS Health Check Module
- False
- True
- True
- 0
- 290
- 28670
- 5/27/2025 11:15:27 PM -04:00
- 5/27/2025 11:15:27 PM -04:00
- 5/27/2025 11:15:27 PM -04:00
- PSModule PSFunction_Read-DmarcPolicy PSCommand_Read-DmarcPolicy PSFunction_Read-MtaStsPolicy PSCommand_Read-MtaStsPolicy PSFunction_Read-DkimRecord PSCommand_Read-DkimRecord PSFunction_Read-MtaStsRecord PSCommand_Read-MtaStsRecord PSFunction_Read-MXRecord PSCommand_Read-MXRecord PSFunction_Read-NSRecord PSCommand_Read-NSRecord PSFunction_Read-SPFRecord PSCommand_Read-SPFRecord PSFunction_Read-TlsRptRecord PSCommand_Read-TlsRptRecord PSFunction_Read-WhoisRecord PSCommand_Read-WhoisRecord PSFunction_Resolve-DnsHttpsQuery PSCommand_Resolve-DnsHttpsQuery PSFunction_Set-DnsResolver PSCommand_Set-DnsResolver PSFunction_Test-DNSSEC PSCommand_Test-DNSSEC PSFunction_Test-HttpsCertificate PSCommand_Test-HttpsCertificate PSFunction_Test-MtaSts PSCommand_Test-MtaSts PSIncludes_Function
- False
- 2025-05-27T23:15:27Z
- 1.1.0
- John Duprey
- false
- Module
- DNSHealth.nuspec|MailProviders\Google.json|MailProviders\BarracudaESS.json|MailProviders\_template.json|MailProviders\Null.json|MailProviders\HornetSecurity.json|DNSHealth.psm1|MailProviders\Intermedia.json|MailProviders\Proofpoint.json|MailProviders\Reflexion.json|DNSHealth.psd1|MailProviders\SpamTitan.json|MailProviders\Mimecast.json|MailProviders\Sophos.json|MailProviders\Microsoft365.json|MailProviders\AppRiver.json|MailProviders\SymantecCloud.json
- a300d2b0-d468-46d1-88a3-e442a76b655b
- 7.0
-
-
- C:\GitHub\CIPP Workspace\CIPP-API\Modules\DNSHealth\1.1.0
-
-
-
+
+
+
+ Microsoft.PowerShell.Commands.PSRepositoryItemInfo
+ System.Management.Automation.PSCustomObject
+ System.Object
+
+
+ DNSHealth
+ 1.1.2
+ Module
+ CIPP DNS Health Check Module
+ John Duprey
+ johnduprey
+ 2023 John Duprey
+ 2026-01-29T15:52:42-05:00
+
+
+
+ https://github.com/johnduprey/DNSHealth
+
+
+
+ System.Object[]
+ System.Array
+ System.Object
+
+
+ PSModule
+
+
+
+
+ System.Collections.Hashtable
+ System.Object
+
+
+
+ Command
+
+
+
+ Read-DmarcPolicy
+ Read-MtaStsPolicy
+ Read-DkimRecord
+ Read-MtaStsRecord
+ Read-MXRecord
+ Read-NSRecord
+ Read-SPFRecord
+ Read-TlsRptRecord
+ Read-WhoisRecord
+ Resolve-DnsHttpsQuery
+ Set-DnsResolver
+ Test-DNSSEC
+ Test-HttpsCertificate
+ Test-MtaSts
+
+
+
+
+ Workflow
+
+
+
+
+
+
+ RoleCapability
+
+
+
+ DscResource
+
+
+
+ Cmdlet
+
+
+
+ Function
+
+
+
+ Read-DmarcPolicy
+ Read-MtaStsPolicy
+ Read-DkimRecord
+ Read-MtaStsRecord
+ Read-MXRecord
+ Read-NSRecord
+ Read-SPFRecord
+ Read-TlsRptRecord
+ Read-WhoisRecord
+ Resolve-DnsHttpsQuery
+ Set-DnsResolver
+ Test-DNSSEC
+ Test-HttpsCertificate
+ Test-MtaSts
+
+
+
+
+
+
+
+
+
+
+
+ https://www.powershellgallery.com/api/v2
+ PSGallery
+ NuGet
+
+
+ System.Management.Automation.PSCustomObject
+ System.Object
+
+
+ 2023 John Duprey
+ CIPP DNS Health Check Module
+ False
+ True
+ True
+ 0
+ 392
+ 28878
+ 1/29/2026 3:52:42 PM -05:00
+ 1/29/2026 3:52:42 PM -05:00
+ 1/29/2026 3:52:42 PM -05:00
+ PSModule PSFunction_Read-DmarcPolicy PSCommand_Read-DmarcPolicy PSFunction_Read-MtaStsPolicy PSCommand_Read-MtaStsPolicy PSFunction_Read-DkimRecord PSCommand_Read-DkimRecord PSFunction_Read-MtaStsRecord PSCommand_Read-MtaStsRecord PSFunction_Read-MXRecord PSCommand_Read-MXRecord PSFunction_Read-NSRecord PSCommand_Read-NSRecord PSFunction_Read-SPFRecord PSCommand_Read-SPFRecord PSFunction_Read-TlsRptRecord PSCommand_Read-TlsRptRecord PSFunction_Read-WhoisRecord PSCommand_Read-WhoisRecord PSFunction_Resolve-DnsHttpsQuery PSCommand_Resolve-DnsHttpsQuery PSFunction_Set-DnsResolver PSCommand_Set-DnsResolver PSFunction_Test-DNSSEC PSCommand_Test-DNSSEC PSFunction_Test-HttpsCertificate PSCommand_Test-HttpsCertificate PSFunction_Test-MtaSts PSCommand_Test-MtaSts PSIncludes_Function
+ False
+ 2026-01-29T15:52:42Z
+ 1.1.2
+ John Duprey
+ false
+ Module
+ DNSHealth.nuspec|DNSHealth.psm1|MailProviders\SpamTitan.json|MailProviders\Proofpoint.json|MailProviders\Sophos.json|DNSHealth.psd1|MailProviders\Google.json|MailProviders\BarracudaESS.json|MailProviders\AppRiver.json|MailProviders\SymantecCloud.json|MailProviders\Microsoft365.json|MailProviders\HornetSecurity.json|MailProviders\_template.json|MailProviders\Mimecast.json|MailProviders\Intermedia.json|MailProviders\Null.json|MailProviders\Reflexion.json
+ a300d2b0-d468-46d1-88a3-e442a76b655b
+ 7.0
+
+
+ /Users/johnduprey/Documents/GitHub/CIPP Workspace/CIPP-API/Modules/DNSHealth/1.1.2
+
+
+
diff --git a/CIPPTimers.json b/Resources/CIPPTimers.json
similarity index 100%
rename from CIPPTimers.json
rename to Resources/CIPPTimers.json
diff --git a/CommunityRepos.json b/Resources/CommunityRepos.json
similarity index 100%
rename from CommunityRepos.json
rename to Resources/CommunityRepos.json
diff --git a/ConversionTable.csv b/Resources/ConversionTable.csv
similarity index 100%
rename from ConversionTable.csv
rename to Resources/ConversionTable.csv
diff --git a/TemplateEmail.html b/Resources/TemplateEmail.html
similarity index 100%
rename from TemplateEmail.html
rename to Resources/TemplateEmail.html
diff --git a/intuneCollection.json b/Resources/intuneCollection.json
similarity index 100%
rename from intuneCollection.json
rename to Resources/intuneCollection.json
diff --git a/words.txt b/Resources/words.txt
similarity index 100%
rename from words.txt
rename to Resources/words.txt
diff --git a/ExampleReportTemplate.ps1 b/Tools/ExampleReportTemplate.ps1
similarity index 100%
rename from ExampleReportTemplate.ps1
rename to Tools/ExampleReportTemplate.ps1
diff --git a/Test-AllZTNATests.ps1 b/Tools/Test-AllZTNATests.ps1
similarity index 100%
rename from Test-AllZTNATests.ps1
rename to Tools/Test-AllZTNATests.ps1
diff --git a/Tools/Update-LicenseSKUFiles.ps1 b/Tools/Update-LicenseSKUFiles.ps1
index e21817d57f18..2bcc8d8c1910 100644
--- a/Tools/Update-LicenseSKUFiles.ps1
+++ b/Tools/Update-LicenseSKUFiles.ps1
@@ -54,5 +54,28 @@ foreach ($File in $LicenseJSONFiles) {
Write-Host "Updated $($File.FullName) with new license SKU data." -ForegroundColor Green
}
+# Sync ExcludeSkuList.JSON names with the authoritative license data
+Set-Location $PSScriptRoot
+$ExcludeSkuListPath = Join-Path $PSScriptRoot '..\Config\ExcludeSkuList.JSON'
+if (Test-Path $ExcludeSkuListPath) {
+ Write-Host 'Syncing ExcludeSkuList.JSON product names...' -ForegroundColor Yellow
+ $GuidToName = @{}
+ foreach ($license in $LicenseData) {
+ if (-not $GuidToName.ContainsKey($license.GUID)) {
+ $GuidToName[$license.GUID] = $license.Product_Display_Name
+ }
+ }
+ $ExcludeSkuList = Get-Content -Path $ExcludeSkuListPath -Encoding utf8 | ConvertFrom-Json
+ $updatedCount = 0
+ foreach ($entry in $ExcludeSkuList) {
+ if ($GuidToName.ContainsKey($entry.GUID) -and $entry.Product_Display_Name -cne $GuidToName[$entry.GUID]) {
+ $entry.Product_Display_Name = $GuidToName[$entry.GUID]
+ $updatedCount++
+ }
+ }
+ $ExcludeSkuList | ConvertTo-Json -Depth 100 | Set-Content -Path $ExcludeSkuListPath -Encoding utf8
+ Write-Host "Updated $updatedCount product names in ExcludeSkuList.JSON." -ForegroundColor Green
+}
+
# Clean up the temporary license SKU CSV file
Remove-Item -Path $TempLicenseDataFile -Force
diff --git a/host.json b/host.json
index ff513b12d0ea..aa8f581b4e1f 100644
--- a/host.json
+++ b/host.json
@@ -16,7 +16,7 @@
"distributedTracingEnabled": false,
"version": "None"
},
- "defaultVersion": "10.0.5",
+ "defaultVersion": "10.0.6",
"versionMatchStrategy": "Strict",
"versionFailureStrategy": "Fail"
}
diff --git a/profile.ps1 b/profile.ps1
index d26d55f264bd..3925ffbf3f9d 100644
--- a/profile.ps1
+++ b/profile.ps1
@@ -8,7 +8,6 @@ if ($env:APPLICATIONINSIGHTS_CONNECTION_STRING -or $env:APPINSIGHTS_INSTRUMENTAT
$hasAppInsights = $true
}
if ($hasAppInsights) {
- Set-Location -Path $PSScriptRoot
$SwAppInsights = [System.Diagnostics.Stopwatch]::StartNew()
try {
$AppInsightsDllPath = Join-Path $PSScriptRoot 'Shared\AppInsights\Microsoft.ApplicationInsights.dll'
@@ -97,6 +96,7 @@ $CurrentVersion = (Get-Content -Path (Join-Path $PSScriptRoot 'version_latest.tx
$Table = Get-CippTable -tablename 'Version'
Write-Information "Function App: $($env:WEBSITE_SITE_NAME) | API Version: $CurrentVersion | PS Version: $($PSVersionTable.PSVersion)"
$global:CippVersion = $CurrentVersion
+$ENV:CurrentVersion = $CurrentVersion
$LastStartup = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'Version' and RowKey eq '$($env:WEBSITE_SITE_NAME)'"
if (!$LastStartup -or $CurrentVersion -ne $LastStartup.Version) {
diff --git a/version_latest.txt b/version_latest.txt
index 2681b301aa6c..80f86ac0c358 100644
--- a/version_latest.txt
+++ b/version_latest.txt
@@ -1 +1 @@
-10.0.5
+10.0.6