Skip to content

Commit 10b3033

Browse files
committed
Added implementation for late bound properties
1 parent 2bb827a commit 10b3033

File tree

4 files changed

+126
-41
lines changed

4 files changed

+126
-41
lines changed

config/ModuleMetadata.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@
2727
"versions": {
2828
"authentication": {
2929
"prerelease": "",
30-
"version": "2.27.0"
30+
"version": "2.27.1"
3131
},
3232
"beta": {
3333
"prerelease": "",
34-
"version": "2.27.0"
34+
"version": "2.27.1"
3535
},
3636
"v1.0": {
3737
"prerelease": "",
38-
"version": "2.27.0"
38+
"version": "2.27.1"
3939
}
4040
}
4141
}

config/ModulesMapping.jsonc

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,44 @@
11
{
22
"Applications": "^applicationTemplates\\.|^applications\\.|^servicePrincipals\\.|^onPremisesPublishingProfiles\\.|^users.appRoleAssignment$|^groups.appRoleAssignment$",
3-
"Bookings": "^bookingBusinesses\\.|^bookingCurrencies\\.|^solutions.booking.*.Actions$|^solutions.bookingBusiness$|^solutions.bookingCurrency$|^solutions.virtualEventsRoot$|^solutions.booking.*.Functions$|^solutions.solutionsRoot$",
4-
"BusinessScenario": "^solutions.businessScenario$|^solutions.BusinessScenario.*.Actions$|^solutions.BusinessScenario.*.Functions$",
5-
"BackupRestore": "^solutions.backupRestoreRoot$|^solutions.backupRestore.*.Actions$|^solutions.backupRestore.*.Functions$",
6-
"Calendar": "^places\\.|^users.calendar$|^users.calendarGroup$|^users.event$|^groups.calendar$|^groups.event$",
7-
"ChangeNotifications": "^subscriptions\\.",
8-
"CloudCommunications": "^users.onlineMeeting$|^users.presence$|^communications\\.",
9-
"Compliance": "^compliance\\.|^privacy.subjectRightsRequest$",
10-
"CrossDeviceExperiences": "^users.userActivity$|^users.device$",
11-
"Devices.CloudPrint": "^print\\.",
12-
"Devices.CorporateManagement": "^deviceAppManagement\\.|^officeConfiguration\\.|^users.mobileAppIntentAndState$|^users.mobileAppTroubleshootingEvent$|^users.windowsInformationProtectionDeviceRegistration$|^users.managedAppRegistration$|^users.managedDevice$|^users.deviceManagementTroubleshootingEvent$|^users.deviceEnrollmentConfiguration$",
13-
"Devices.ServiceAnnouncement": "^admin.serviceAnnouncement$|^admin.*.Actions$|^admin.*.Functions$",
14-
"DeviceManagement": "^deviceManagement.(deviceCompliancePolicy.*|deviceManagementConfigurationPolicy.*|deviceManagementCompliancePolicy.*|deviceManagementConfigurationSettingDefinition.*|deviceConfiguration.*|managedDevice.*|managementCondition.*|microsoftTunnel.*|windowsInformationProtection.*|deviceManagement|deviceManagement(DerivedCredentialSettings|Intent|ResourceAccessProfileBase|Script|SettingCategory|SettingDefinition|Template|TroubleshootingEvent)|androidForWork(AppConfigurationSchema|Settings)|androidManagedStore(AccountEnterpriseSettings|AppConfigurationSchema)|deviceAndAppManagementAssignmentFilter|deviceCategory|advancedThreatProtectionOnboardingStateSummary|dataSharingConsent|detectedApp|deviceHealthScript|deviceShellScript|embeddedSIMActivationCodePool|groupPolicyConfiguration|macOSSoftwareUpdateAccountSummary|mobileAppTroubleshootingEvent|notificationMessageTemplate|remoteActionAudit|softwareUpdateStatusSummary|windowsMalwareInformation|windowsQualityUpdateProfile)$|^admin.edge$|^deviceManagement.monitoring$|^users.ListCloudPCs$",
15-
"DeviceManagement.Administration": "^deviceManagement.(virtualEndpoint.*|.*Partner.*|.*Certificate.*|.*role.*|deviceManagement(DomainJoinConnector|ExchangeConnector|ExchangeOnPremisesPolicy)|groupPolicy(Category|Definition|DefinitionFile|MigrationReport|ObjectFile|UploadedDefinitionFile)|auditEvent|cartToClassAssociation|comanagementEligibleDevice|deviceAndAppManagementRoleAssignment|intuneBrandingProfile|iosUpdateDeviceStatus|mobileThreatDefenseConnector|ndesConnector|resourceOperation|restrictedAppsViolation|termsAndConditions)",
16-
"DeviceManagement.Enrollment": "^deviceManagement.(.*Enrollment.*|.*Autopilot.*|.*depOnboarding.*|importedDeviceIdentity|onPremisesConditionalAccessSettings|windowsFeatureUpdateProfile)$|^roleManagement.roleManagement$|^roleManagement.rbacApplicationMultiple$|^roleManagement.unifiedRbacApplication$",
17-
"DeviceManagement.Functions": "^deviceManagement.*.Functions$",
18-
"DirectoryObjects": "^directoryObjects\\.|^directory.publicKeyInfrastructureRoot$",
19-
"Education": "^education\\.",
20-
"Files": "^drives\\.|^shares\\.|^users.drive$|^groups.drive$",
21-
"Financials": "^financials\\.",
22-
"Groups": "^groups.group$|^groups.directoryObject$|^groups.conversation$|^groups.endpoint$|^groups.extension$|^groups.groupLifecyclePolicy$|^groups.resourceSpecificPermissionGrant$|^groups.profilePhoto$|^groups.conversationThread$|^groupLifecyclePolicies\\.|^users.group$|^groups.directorySetting$|^groups.*.Actions$|^groups.*.Functions$|^groupSettings\\.|^groups.groupSetting$|^groupSettingTemplates\\.",
23-
"Identity.DirectoryManagement": "^administrativeUnits\\.|^contacts\\.|^devices\\.|^domains\\.|^directoryRoles\\.|^directoryRoleTemplates\\.|^directorySettingTemplates\\.|^settings\\.|^subscribedSkus\\.|^contracts\\.|^directory\\.|^users.scopedRoleMembership$|^organization.organization$|^organization.organizationalBranding$|^organization.organizationSettings$|^organization.*.Actions$|^organization.extension$|^tenantRelationships.*.Actions$|^tenantRelationships.*.Functions$|admin.peopleAdminSettings$|^organization\\.partnerInformation$",
24-
"Identity.Governance": "^accessReviews\\.|^businessFlowTemplates\\.|^programs\\.|^programControls\\.|^programControlTypes\\.|^privilegedRoles\\.|^privilegedRoleAssignments\\.|^privilegedRoleAssignmentRequests\\.|^privilegedApproval\\.|^privilegedOperationEvents\\.|^privilegedAccess\\.|^agreements\\.|^users.agreementAcceptance$|^identityGovernance\\.|^roleManagement.rbacApplication$|^roleManagement.*.Functions$|roleManagement.*.Actions$",
3+
// "Bookings": "^bookingBusinesses\\.|^bookingCurrencies\\.|^solutions.booking.*.Actions$|^solutions.bookingBusiness$|^solutions.bookingCurrency$|^solutions.virtualEventsRoot$|^solutions.booking.*.Functions$|^solutions.solutionsRoot$",
4+
// "BusinessScenario": "^solutions.businessScenario$|^solutions.BusinessScenario.*.Actions$|^solutions.BusinessScenario.*.Functions$",
5+
// "BackupRestore": "^solutions.backupRestoreRoot$|^solutions.backupRestore.*.Actions$|^solutions.backupRestore.*.Functions$",
6+
// "Calendar": "^places\\.|^users.calendar$|^users.calendarGroup$|^users.event$|^groups.calendar$|^groups.event$",
7+
// "ChangeNotifications": "^subscriptions\\.",
8+
// "CloudCommunications": "^users.onlineMeeting$|^users.presence$|^communications\\.",
9+
// "Compliance": "^compliance\\.|^privacy.subjectRightsRequest$",
10+
// "CrossDeviceExperiences": "^users.userActivity$|^users.device$",
11+
// "Devices.CloudPrint": "^print\\.",
12+
// "Devices.CorporateManagement": "^deviceAppManagement\\.|^officeConfiguration\\.|^users.mobileAppIntentAndState$|^users.mobileAppTroubleshootingEvent$|^users.windowsInformationProtectionDeviceRegistration$|^users.managedAppRegistration$|^users.managedDevice$|^users.deviceManagementTroubleshootingEvent$|^users.deviceEnrollmentConfiguration$",
13+
// "Devices.ServiceAnnouncement": "^admin.serviceAnnouncement$|^admin.*.Actions$|^admin.*.Functions$",
14+
// "DeviceManagement": "^deviceManagement.(deviceCompliancePolicy.*|deviceManagementConfigurationPolicy.*|deviceManagementCompliancePolicy.*|deviceManagementConfigurationSettingDefinition.*|deviceConfiguration.*|managedDevice.*|managementCondition.*|microsoftTunnel.*|windowsInformationProtection.*|deviceManagement|deviceManagement(DerivedCredentialSettings|Intent|ResourceAccessProfileBase|Script|SettingCategory|SettingDefinition|Template|TroubleshootingEvent)|androidForWork(AppConfigurationSchema|Settings)|androidManagedStore(AccountEnterpriseSettings|AppConfigurationSchema)|deviceAndAppManagementAssignmentFilter|deviceCategory|advancedThreatProtectionOnboardingStateSummary|dataSharingConsent|detectedApp|deviceHealthScript|deviceShellScript|embeddedSIMActivationCodePool|groupPolicyConfiguration|macOSSoftwareUpdateAccountSummary|mobileAppTroubleshootingEvent|notificationMessageTemplate|remoteActionAudit|softwareUpdateStatusSummary|windowsMalwareInformation|windowsQualityUpdateProfile)$|^admin.edge$|^deviceManagement.monitoring$|^users.ListCloudPCs$",
15+
// "DeviceManagement.Administration": "^deviceManagement.(virtualEndpoint.*|.*Partner.*|.*Certificate.*|.*role.*|deviceManagement(DomainJoinConnector|ExchangeConnector|ExchangeOnPremisesPolicy)|groupPolicy(Category|Definition|DefinitionFile|MigrationReport|ObjectFile|UploadedDefinitionFile)|auditEvent|cartToClassAssociation|comanagementEligibleDevice|deviceAndAppManagementRoleAssignment|intuneBrandingProfile|iosUpdateDeviceStatus|mobileThreatDefenseConnector|ndesConnector|resourceOperation|restrictedAppsViolation|termsAndConditions)",
16+
// "DeviceManagement.Enrollment": "^deviceManagement.(.*Enrollment.*|.*Autopilot.*|.*depOnboarding.*|importedDeviceIdentity|onPremisesConditionalAccessSettings|windowsFeatureUpdateProfile)$|^roleManagement.roleManagement$|^roleManagement.rbacApplicationMultiple$|^roleManagement.unifiedRbacApplication$",
17+
// "DeviceManagement.Functions": "^deviceManagement.*.Functions$",
18+
// "DirectoryObjects": "^directoryObjects\\.|^directory.publicKeyInfrastructureRoot$",
19+
// "Education": "^education\\.",
20+
// "Files": "^drives\\.|^shares\\.|^users.drive$|^groups.drive$",
21+
// "Financials": "^financials\\.",
22+
// "Groups": "^groups.group$|^groups.directoryObject$|^groups.conversation$|^groups.endpoint$|^groups.extension$|^groups.groupLifecyclePolicy$|^groups.resourceSpecificPermissionGrant$|^groups.profilePhoto$|^groups.conversationThread$|^groupLifecyclePolicies\\.|^users.group$|^groups.directorySetting$|^groups.*.Actions$|^groups.*.Functions$|^groupSettings\\.|^groups.groupSetting$|^groupSettingTemplates\\.",
23+
// "Identity.DirectoryManagement": "^administrativeUnits\\.|^contacts\\.|^devices\\.|^domains\\.|^directoryRoles\\.|^directoryRoleTemplates\\.|^directorySettingTemplates\\.|^settings\\.|^subscribedSkus\\.|^contracts\\.|^directory\\.|^users.scopedRoleMembership$|^organization.organization$|^organization.organizationalBranding$|^organization.organizationSettings$|^organization.*.Actions$|^organization.extension$|^tenantRelationships.*.Actions$|^tenantRelationships.*.Functions$|admin.peopleAdminSettings$|^organization\\.partnerInformation$",
24+
// "Identity.Governance": "^accessReviews\\.|^businessFlowTemplates\\.|^programs\\.|^programControls\\.|^programControlTypes\\.|^privilegedRoles\\.|^privilegedRoleAssignments\\.|^privilegedRoleAssignmentRequests\\.|^privilegedApproval\\.|^privilegedOperationEvents\\.|^privilegedAccess\\.|^agreements\\.|^users.agreementAcceptance$|^identityGovernance\\.|^roleManagement.rbacApplication$|^roleManagement.*.Functions$|roleManagement.*.Actions$",
2525
"Identity.SignIns": "^organization.certificateBasedAuthConfiguration$|^invitations\\.|^identityProviders\\.|^oauth2PermissionGrants\\.|^identityProtection\\.|^dataPolicyOperations\\.|^identity\\.|^trustFramework\\.|^informationProtection\\.|^policies\\.|^users.authentication$|^users.informationProtection$|^tenantRelationships.multiTenantOrganization$|^policies.deviceRegistrationPolicy$|^policies.deviceRegistrationPolicy$",
26-
"Identity.Partner": "^tenantRelationships.delegatedAdminRelationship$|^tenantRelationships.delegatedAdminCustomer$",
27-
"Mail": "^users.inferenceClassification$|^users.mailFolder$|^users.message$",
28-
"ManagedTenants": "^tenantRelationships.managedTenant$",
29-
"NetworkAccess": "^networkAccess\\.",
30-
"Notes": "^users.onenote$|^groups.onenote$|^sites.onenote$",
31-
"People": "^users.person$|^users.profile$|^users.officeGraphInsights$|^users.userAnalytics$",
32-
"PersonalContacts": "^users.contactFolder$|^users.contact$",
33-
"Planner": "^planner\\.|^users.plannerUser$|^groups.plannerGroup$",
34-
"Reports": "^reports\\.|^auditLogs\\.|^deviceManagement.deviceManagementReports$|^admin.adminReportSetting",
35-
"SchemaExtensions": "^schemaExtensions\\.",
36-
"Search": "^search\\.|^external\\.",
37-
"Security": "^security\\.|^users.security$",
38-
"Sites": "^sites.baseSitePage$|^sites.site$|^sites.itemAnalytics$|^sites.columnDefinition$|^sites.contentType$|^sites.drive$|^sites.list$|^sites.sitePage$|^sites.permission$|^sites.store$|^users.site$|^groups.site$|^sites.*.Functions$|^sites.*.Actions$|^sites.richLongRunningOperation$|^termStore.sets.ListChildren$|^admin.sharepoint$",
39-
"Teams": "^teams\\.|^chats\\.|^users.chat$|^appCatalogs.teamsApp$|^users.userTeamwork$|^teamwork\\.|^users.team$|^groups.team$",
26+
// "Identity.Partner": "^tenantRelationships.delegatedAdminRelationship$|^tenantRelationships.delegatedAdminCustomer$",
27+
// "Mail": "^users.inferenceClassification$|^users.mailFolder$|^users.message$",
28+
// "ManagedTenants": "^tenantRelationships.managedTenant$",
29+
// "NetworkAccess": "^networkAccess\\.",
30+
// "Notes": "^users.onenote$|^groups.onenote$|^sites.onenote$",
31+
// "People": "^users.person$|^users.profile$|^users.officeGraphInsights$|^users.userAnalytics$",
32+
// "PersonalContacts": "^users.contactFolder$|^users.contact$",
33+
// "Planner": "^planner\\.|^users.plannerUser$|^groups.plannerGroup$",
34+
// "Reports": "^reports\\.|^auditLogs\\.|^deviceManagement.deviceManagementReports$|^admin.adminReportSetting",
35+
// "SchemaExtensions": "^schemaExtensions\\.",
36+
// "Search": "^search\\.|^external\\.",
37+
// "Security": "^security\\.|^users.security$",
38+
// "Sites": "^sites.baseSitePage$|^sites.site$|^sites.itemAnalytics$|^sites.columnDefinition$|^sites.contentType$|^sites.drive$|^sites.list$|^sites.sitePage$|^sites.permission$|^sites.store$|^users.site$|^groups.site$|^sites.*.Functions$|^sites.*.Actions$|^sites.richLongRunningOperation$|^termStore.sets.ListChildren$|^admin.sharepoint$",
39+
// "Teams": "^teams\\.|^chats\\.|^users.chat$|^appCatalogs.teamsApp$|^users.userTeamwork$|^teamwork\\.|^users.team$|^groups.team$",
4040
"Users": "^users.user$|^users.directoryObject$|^users.licenseDetails$|^users.mailboxSettings|^users.notification$|^users.outlookUser$|^users.profilePhoto$|^users.userSettings$|^users.extension$|^users.oAuth2PermissionGrant$|^users.todo$|^users.itemInsights$|^users.servicePrincipal$",
4141
"Users.Actions": "^users.*.Actions$",
42-
"Users.Functions": "^users.*.Functions$",
43-
"WindowsUpdates": "^admin.adminWindows$"
42+
// "Users.Functions": "^users.*.Functions$",
43+
// "WindowsUpdates": "^admin.adminWindows$"
4444
}

src/readme.graph.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,9 @@ directive:
495495
}
496496
});
497497
}
498+
499+
$ = $.replace(/await this\.Client\.(\w+)\(\s*Headers\s*,\s*([^,\s]+)[^;]+;/g,'await Microsoft.Graph.PowerShell.ModelExtensions.ModelExtensions.EnsurePropertiesAreReady($2,failOnExplicitNulls: false);\nawait this.Client.$1(Headers, $2, on2Xx, onDefault, this, Pipeline);')
500+
498501
499502
return $;
500503
}

tools/Custom/ModelExtensions.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using System.Linq;
3+
using System.Reflection;
4+
using System.Threading.Tasks;
5+
using NamespacePrefixPlaceholder.PowerShell.Runtime;
6+
7+
namespace NamespacePrefixPlaceholder.PowerShell.ModelExtensions
8+
{
9+
public static class ModelExtensions
10+
{
11+
/// <summary>
12+
/// Ensures that all properties marked as set on the model have meaningful values.
13+
/// </summary>
14+
/// <param name="model">The model object (must implement IsPropertySet(string)).</param>
15+
/// <param name="failOnExplicitNulls">If true, properties explicitly set to null will be considered invalid (strict mode).</param>
16+
/// <param name="retries">Number of retries for late-bound properties.</param>
17+
/// <param name="delayMs">Delay (ms) between retries.</param>
18+
public static async Task EnsurePropertiesAreReady(
19+
this object model,
20+
bool failOnExplicitNulls = false,
21+
int retries = 3,
22+
int delayMs = 100)
23+
{
24+
if (model == null)
25+
throw new ArgumentNullException(nameof(model));
26+
27+
// Ensure the model supports IsPropertySet(string)
28+
var isPropertySetMethod = model.GetType().GetMethod("IsPropertySet", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
29+
if (isPropertySetMethod == null)
30+
throw new InvalidOperationException($"{model.GetType().Name} does not implement IsPropertySet(string)");
31+
32+
var props = model.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
33+
34+
for (int attempt = 0; attempt <= retries; attempt++)
35+
{
36+
var unready = props
37+
.Where(p => IsUnready(model, p, isPropertySetMethod, failOnExplicitNulls))
38+
.ToList();
39+
40+
if (!unready.Any())
41+
return; // Ready!
42+
43+
if (attempt < retries)
44+
await Task.Delay(delayMs);
45+
}
46+
47+
// Final check before throwing
48+
var stillUnready = props
49+
.Where(p => IsUnready(model, p, isPropertySetMethod, failOnExplicitNulls))
50+
.Select(p => p.Name)
51+
.ToList();
52+
53+
if (stillUnready.Any())
54+
{
55+
throw new InvalidOperationException(
56+
$"Model '{model.GetType().Name}' has properties marked as set but not initialized properly: {string.Join(", ", stillUnready)}"
57+
);
58+
}
59+
}
60+
61+
private static bool IsUnready(object model, PropertyInfo prop, MethodInfo isPropertySetMethod, bool failOnExplicitNulls)
62+
{
63+
bool isSet = (bool)isPropertySetMethod.Invoke(model, new object[] { prop.Name });
64+
if (!isSet) return false; // not marked as set, skip
65+
66+
object value = prop.GetValue(model);
67+
68+
if (value == null)
69+
return failOnExplicitNulls; // null is OK in relaxed mode, fail in strict
70+
71+
return IsDefault(value);
72+
}
73+
74+
private static bool IsDefault(object value)
75+
{
76+
Type type = value.GetType();
77+
if (!type.IsValueType) return false;
78+
object defaultValue = Activator.CreateInstance(type);
79+
return value.Equals(defaultValue);
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)