Skip to content

Commit aadb760

Browse files
author
Vadim
committed
Add examples: group lifecycle policies, call records, org settings, online meetings
1 parent f1055f0 commit aadb760

4 files changed

Lines changed: 349 additions & 0 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""
2+
Group lifecycle policies — set group expiration, manage renewal,
3+
and enable auto-renewal for Microsoft 365 groups.
4+
5+
M365 group expiration is a core governance feature. Without it,
6+
groups accumulate and never get cleaned up. This example shows:
7+
- List existing lifecycle policies
8+
- Add groups to a policy
9+
- Remove groups from a policy
10+
- Renew a group (keep alive)
11+
12+
Requires delegated permission ``Group.ReadWrite.All``.
13+
14+
https://learn.microsoft.com/en-us/graph/api/resources/grouplifecyclepolicy
15+
"""
16+
17+
import sys
18+
19+
from office365.graph_client import GraphClient
20+
from tests import test_client_id, test_client_secret, test_tenant
21+
22+
23+
def main():
24+
client = GraphClient(tenant=test_tenant).with_client_secret(test_client_id, test_client_secret)
25+
26+
# -- Step 1: list existing lifecycle policies --
27+
policies = client.group_lifecycle_policies.get().execute_query()
28+
print(f"Group lifecycle policies: {len(policies)}\n")
29+
30+
for p in policies:
31+
lifetime = p.group_lifetime_in_days or "?"
32+
notification = p.alternate_notification_emails or "(none)"
33+
managed = p.managed_group_types or []
34+
print(f" Lifetime: {lifetime:>3} days "
35+
f"notification: {notification:45s} "
36+
f"managed types: {managed}")
37+
38+
# -- Step 2: find groups not covered by any policy --
39+
groups = (
40+
client.groups.get()
41+
.filter("resourceProvisioningOptions/Any(x:x eq 'Team') or mailEnabled eq true")
42+
.select(["id", "displayName", "mail"])
43+
.top(999)
44+
.execute_query()
45+
)
46+
47+
covered = set()
48+
for p in policies:
49+
gid = p.properties.get("groupId", "")
50+
if gid:
51+
covered.add(gid)
52+
elif hasattr(p, "properties") and p.properties.get("groupIds"):
53+
for gid in p.properties["groupIds"]:
54+
covered.add(gid)
55+
56+
uncovered = [g for g in groups if g.id not in covered]
57+
print(f"\nM365 groups: {len(groups)}")
58+
print(f"Uncovered by lifecycle policy: {len(uncovered)}")
59+
60+
if uncovered:
61+
print("\nFirst 5 groups without expiration:")
62+
for g in uncovered[:5]:
63+
print(f" {g.display_name:40s} {g.mail or '?'}")
64+
65+
# -- Step 3: create a lifecycle policy (commented) --
66+
# policy = client.group_lifecycle_policies.add(
67+
# groupLifetimeInDays=180,
68+
# managedGroupTypes="All",
69+
# alternateNotificationEmails="admin@contoso.com",
70+
# ).execute_query()
71+
# print(f"\n✓ Lifecycle policy created")
72+
73+
# -- Step 4: add a group to the policy --
74+
if policies and uncovered:
75+
policy = policies[0]
76+
group_id = uncovered[0].id
77+
policy.add_group(group_id=group_id).execute_query()
78+
print(f"\n✓ Added {uncovered[0].display_name} to lifecycle policy.")
79+
80+
# -- Step 5: renew a group (keep alive another period) --
81+
if policies:
82+
# Pick any group ID that was already managed
83+
sample = next((p.properties.get("groupId", "") for p in policies if p.properties.get("groupId")), None)
84+
if sample:
85+
policies[0].renew_group(group_id=sample).execute_query()
86+
print(f"✓ Renewed group {sample[:20]}.")
87+
88+
# -- Cleanup: remove a group from policy --
89+
# policy.remove_group(group_id=group_id).execute_query()
90+
# print("✓ Removed group from policy.")
91+
92+
print("\nDone.")
93+
94+
95+
if __name__ == "__main__":
96+
main()
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""
2+
Organization settings — read and update tenant-wide configuration.
3+
4+
Covers the most practical tenant settings:
5+
- Read organization profile (name, technical contacts, privacy)
6+
- List organization branding (sign-in page, logo, background)
7+
- Read partner tenant relationships (permissions, delegations)
8+
9+
Requires delegated permission ``Organization.Read.All``
10+
(``Organization.ReadWrite.All`` for updates).
11+
12+
https://learn.microsoft.com/en-us/graph/api/resources/organization
13+
"""
14+
15+
import sys
16+
17+
from office365.graph_client import GraphClient
18+
from tests import test_client_id, test_client_secret, test_tenant
19+
20+
21+
def main():
22+
client = GraphClient(tenant=test_tenant).with_client_secret(test_client_id, test_client_secret)
23+
24+
# -- Step 1: read organization profile --
25+
orgs = client.organization.get().execute_query()
26+
if not orgs:
27+
print("(no organization data)")
28+
return
29+
30+
org = orgs[0]
31+
print("Organization details:\n")
32+
print(f" Display name: {org.display_name or '?'}")
33+
print(f" Tenant ID: {org.id or '?'}")
34+
print(f" City: {org.city or '?'}")
35+
print(f" Country/region: {org.country or '?'}")
36+
print(f" Postal code: {org.postal_code or '?'}")
37+
print(f" State: {org.state or '?'}")
38+
print(f" Street: {org.street or '?'}")
39+
print(f" Tenant type: {org.tenant_type or '?'}")
40+
41+
if org.verified_domains:
42+
print(f"\n Verified domains ({len(org.verified_domains)}):")
43+
for d in org.verified_domains:
44+
is_default = d.properties.get("isDefault", False)
45+
is_initial = d.properties.get("isInitial", False)
46+
print(f" {d.properties.get('name', '?'):35s} "
47+
f"{'default ' if is_default else ''}"
48+
f"{'initial' if is_initial else ''}")
49+
50+
# -- Step 2: notification contacts --
51+
print(f"\n Technical notification mails: {org.technical_notification_mails or '(none)'}")
52+
print(f" Security/compliance notification: {org.security_compliance_notification_mails or '(none)'}")
53+
print(f" Marketing notification: {org.marketing_notification_emails or '(none)'}")
54+
print(f" Privacy contact: {org.privacy_notification_emails or '(none)'}")
55+
print(f" Privacy profile URL: {org.privacy_profile_url or '(none)'}")
56+
57+
# -- Step 3: list organization branding --
58+
try:
59+
branding = org.branding.get().execute_query()
60+
print(f"\n Branding:")
61+
print(f" Locale: {branding.locale or '?'}")
62+
print(f" Background color: {branding.background_color or '?'}")
63+
print(f" Sign-in page text: {(branding.sign_in_page_text or '')[:60]}")
64+
print(f" Username hint: {branding.username_hint or '?'}")
65+
print(f" Hide keep me signed in: {branding.hide_keep_me_signed_in}")
66+
print(f" Custom logo: {'Yes' if branding.logo_id else 'No'}")
67+
except Exception as e:
68+
print(f"\n Branding: {e}")
69+
70+
# -- Step 4: partner tenant relationships --
71+
try:
72+
partners = org.partner_tenant_relationships.get().execute_query()
73+
print(f"\n Partner tenant relationships: {len(partners)}")
74+
for pt in partners:
75+
print(f" {pt.properties.get('partnerTenantId', '?'):40s} "
76+
f"status={pt.properties.get('status', '?')}")
77+
except Exception:
78+
print(f"\n (partner relationships not available)")
79+
80+
81+
if __name__ == "__main__":
82+
main()

examples/teams/call_records.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"""
2+
Call records — Teams call quality analytics across the tenant.
3+
4+
Rich data model: call → sessions → segments → endpoints.
5+
Useful for:
6+
- Identifying poor-quality calls (jitter, packet loss, latency)
7+
- Auditing direct routing call failures
8+
- Reporting on call modalities (audio, video, screen share)
9+
10+
Requires application permission ``CallRecords.Read.All``.
11+
12+
https://learn.microsoft.com/en-us/graph/api/resources/callrecords-api-overview
13+
"""
14+
15+
import sys
16+
17+
from office365.graph_client import GraphClient
18+
from tests import test_client_id, test_client_secret, test_tenant
19+
20+
21+
def main():
22+
client = GraphClient(tenant=test_tenant).with_client_secret(test_client_id, test_client_secret)
23+
24+
# -- Step 1: list recent call records --
25+
records = client.communications.call_records.top(20).get().execute_query()
26+
print(f"Call records: {len(records)}\n")
27+
28+
if not records:
29+
print("(No call records — may need CallRecords.Read.All permission or recent calls)")
30+
return
31+
32+
for r in records:
33+
r_id = r.id or "?"
34+
call_type = r.properties.get("type", "?")
35+
modality = r.properties.get("modalities", [])
36+
start = r.start_date_time
37+
if hasattr(start, "strftime"):
38+
start = start.strftime("%Y-%m-%d %H:%M")
39+
end = r.end_date_time
40+
if hasattr(end, "strftime"):
41+
end = end.strftime("%Y-%m-%d %H:%M")
42+
43+
user_count = r.properties.get("participants_count", "?")
44+
organizer = r.properties.get("organizer", {})
45+
org_upn = "?"
46+
if isinstance(organizer, dict):
47+
u = organizer.get("user", {})
48+
org_upn = u.get("userPrincipalName", "?") if isinstance(u, dict) else "?"
49+
50+
failure = r.properties.get("failureInfo", {})
51+
failure_phase = failure.get("stage", "") if isinstance(failure, dict) else ""
52+
53+
print(f" {r_id[:20]:20s} type={call_type:15s} modality={str(modality[:3]):10s} "
54+
f"users={user_count} organizer={org_upn[:25]:25s}")
55+
print(f" start={start} end={end} failure={failure_phase or 'none'}")
56+
57+
# -- Step 2: drill into sessions for the most recent call --
58+
if records:
59+
print()
60+
recent = records[0]
61+
print(f"Drilling into call {recent.id[:20]}...")
62+
63+
sessions = recent.sessions.get().execute_query()
64+
print(f" Sessions: {len(sessions)}")
65+
66+
for s in sessions:
67+
s_id = s.id[:15] if s.id else "?"
68+
callee = s.properties.get("callee", {}).get("user", {}).get("userPrincipalName", "?")
69+
caller = s.properties.get("caller", {}).get("user", {}).get("userPrincipalName", "?")
70+
modality = s.properties.get("modalities", [])
71+
print(f" session {s_id} caller={str(caller)[:25]:25s} "
72+
f"callee={str(callee)[:25]:25s} modality={str(modality[:3]):10s}")
73+
74+
# Segments within this session
75+
segments = s.segments.get().execute_query()
76+
for seg in segments:
77+
seg_id = seg.properties.get("id", "?")[:10]
78+
print(f" segment {seg_id}")
79+
80+
# Endpoint quality data
81+
endpoints = seg.properties.get("endpoints", [])
82+
for ep in endpoints:
83+
name = ep.get("name", "?")
84+
ep_type = ep.get("@odata.type", "?")
85+
print(f" endpoint={name} type={str(ep_type).split('.')[-1][:20]}")
86+
87+
88+
if __name__ == "__main__":
89+
main()

examples/teams/online_meetings.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""
2+
Online meetings — create Teams meetings with join links for
3+
scheduling automation.
4+
5+
Creates a Teams meeting accessible via a generated join URL.
6+
Supports meeting settings (lobby, chat, recording), custom
7+
start/end time, and participant configuration.
8+
9+
Useful for:
10+
- Automated scheduling bots
11+
- Meeting room booking systems
12+
- CRM integrations that create Teams meetings
13+
14+
Requires delegated permission ``OnlineMeetings.ReadWrite.All``.
15+
16+
https://learn.microsoft.com/en-us/graph/api/resources/onlinemeeting
17+
"""
18+
19+
import sys
20+
from datetime import datetime, timedelta, timezone
21+
22+
from office365.graph_client import GraphClient
23+
from tests import test_client_id, test_client_secret, test_tenant
24+
25+
26+
def main():
27+
client = GraphClient(tenant=test_tenant).with_client_secret(test_client_id, test_client_secret)
28+
29+
# -- Step 1: create a Teams meeting --
30+
start = datetime.now(timezone.utc) + timedelta(days=1, hours=9)
31+
end = start + timedelta(hours=1)
32+
33+
meeting = client.me.online_meetings.create(
34+
startDateTime=start.isoformat(),
35+
endDateTime=end.isoformat(),
36+
subject="Project sync — SDK demo",
37+
description="Weekly sync with demo team.",
38+
).execute_query()
39+
40+
print(f"Meeting created:\n")
41+
print(f" Subject: {meeting.subject}")
42+
print(f" Join link: {meeting.join_web_url}")
43+
print(f" Start: {meeting.start_date_time.strftime('%Y-%m-%d %H:%M') if meeting.start_date_time else '?'}")
44+
print(f" End: {meeting.end_date_time.strftime('%Y-%m-%d %H:%M') if meeting.end_date_time else '?'}")
45+
46+
# -- Step 2: create with lobby and chat settings --
47+
meeting2 = client.me.online_meetings.create(
48+
startDateTime=(start + timedelta(days=1)).isoformat(),
49+
endDateTime=(end + timedelta(days=1)).isoformat(),
50+
subject="All-hands with lobby control",
51+
description="Lobby bypass disabled for external participants.",
52+
lobbyBypassSettings={
53+
"scope": "organization",
54+
"isDialInBypassEnabled": False,
55+
},
56+
allowMeetingChat="enabled",
57+
allowTranscription=True,
58+
).execute_query()
59+
60+
print(f"\nMeeting with lobby control:\n")
61+
print(f" Subject: {meeting2.subject}")
62+
print(f" Join link: {meeting2.join_web_url}")
63+
print(f" Chat: {meeting2.allow_meeting_chat}")
64+
print(f" Transcript: {meeting2.allow_transcription}")
65+
66+
# -- Step 3: get meeting details by join web URL --
67+
fetched = client.me.online_meetings.get_by_join_web_url(
68+
meeting2.join_web_url
69+
).execute_query()
70+
print(f"\nFetched by join URL: {fetched.subject}")
71+
72+
# -- Step 4: list recent meetings (last 30 days) --
73+
since = (datetime.now(timezone.utc) - timedelta(days=30)).isoformat()
74+
recent = client.me.online_meetings.filter(f"startDateTime ge {since}").top(10).get().execute_query()
75+
print(f"\nRecent meetings: {len(recent)}")
76+
for m in recent:
77+
start_str = m.start_date_time.strftime("%m-%d") if m.start_date_time else "?"
78+
print(f" {start_str} {m.subject[:45]:45s} {m.join_web_url}")
79+
80+
81+
if __name__ == "__main__":
82+
main()

0 commit comments

Comments
 (0)