Skip to main content

add-office365-users

Adds Office 365 Users by delegating to `/add-data-source` with `api-id=shared_office365users` and action mode. Use for user profiles, managers, direct reports, people search, and profile photos.

Source facts

Repository
microsoft/managed-apps
Last source activity
September 23, 2026 at 17:51
Detected SKILL.md language
English
Stars
24
Forks
8

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
add-office365-users
description
Adds Office 365 Users by delegating to `/add-data-source` with `api-id=shared_office365users` and action mode. Use for user profiles, managers, direct reports, people search, and profile photos.
user-invocable
true
allowed-tools
Read, AskUserQuestion, Skill
model
sonnet
**📋 Shared Instructions: [shared-instructions.md](${CLAUDE_PLUGIN_ROOT}/shared/shared-instructions.md)** — Cross-cutting concerns. # Add Office 365 Users (Wrapper) This skill is a thin wrapper. Use `/add-data-source` as the single implementation path. ## Delegation contract Invoke `/add-data-source` with: - `api-id`: `shared_office365users` - `mode`: `action` --- # Office 365 Users: Method Selection Guide After the connector is added, import the generated service: ```typescript import { Office365UsersService } from '../../generated/services/Office365UsersService' ``` Use the V2 methods for profiles and reporting relationships: | Scenario | Method | Result | | --- | --- | --- | | Current signed-in user | `MyProfile_V2($select?)` | One `GraphUser_V1` | | Specific user by ID or UPN | `UserProfile_V2(id, $select?)` | One `GraphUser_V1` | | User's manager | `Manager_V2(id, $select?)` | One `GraphUser_V1` | | Direct reports | `DirectReports_V2(id, $select?, $top?)` | Profiles in `result.data.value` | | Search people | `SearchUserV2(searchTerm, top?, ...)` | Search response in `result.data.value` | | Profile photo | `UserPhotoMetadata(userId)`, then `UserPhoto_V2(id)` | Metadata, then binary image data | `DirectReports_V2` requires the target manager's Microsoft Entra directory ID or user principal name (UPN). For the signed-in user, call `MyProfile_V2('id,userPrincipalName')`, then pass the returned `id` or fall back to `userPrincipalName`. For a known person, pass their directory ID or UPN directly. ## Profile and direct-report pattern Request only the fields the UI needs: ```typescript const fields = [ 'id', 'displayName', 'jobTitle', 'department', 'mail', 'userPrincipalName', 'officeLocation', ].join(',') const myProfile = await Office365UsersService.MyProfile_V2( 'id,userPrincipalName', ) if (!myProfile.success) { throw new Error(myProfile.error?.message ?? 'Failed to resolve current user') } const managerId = myProfile.data?.id ?? myProfile.data?.userPrincipalName if (!managerId) { throw new Error('Current user profile did not include an ID or UPN') } const result = await Office365UsersService.DirectReports_V2( managerId, fields, ) if (!result.success) { throw new Error(result.error?.message ?? 'Failed to load direct reports') } const people = result.data?.value ?? [] ``` An empty `value` array is valid and means the user has no direct reports. ## Profile photos: binary response and CSP-safe rendering Call `UserPhotoMetadata` before downloading the photo. `HasPhoto: false` is a valid no-photo state; render initials or another accessible fallback. Image responses are a special runtime contract: the generated `UserPhoto_V2` signature may declare `IOperationResult<string>`, but the Managed Apps SDK decodes an `image/*` response into a `Uint8Array`. Do not interpolate that byte array directly into a base64 URL, and do not use a `blob:` URL—the deployed App Player content security policy may block `blob:` under `img-src`. Convert the bytes to a `data:` URL: ```typescript function bytesToBase64(bytes: Uint8Array) { const chunkSize = 0x8000 let binary = '' for (let offset = 0; offset < bytes.length; offset += chunkSize) { binary += String.fromCharCode( ...bytes.subarray(offset, Math.min(offset + chunkSize, bytes.length)), ) } return btoa(binary) } function toImageDataUrl(value: unknown, contentType = 'image/jpeg') { if (value instanceof Uint8Array) { return `data:${contentType};base64,${bytesToBase64(value)}` } if (value instanceof ArrayBuffer) { return `data:${contentType};base64,${bytesToBase64(new Uint8Array(value))}` } if (typeof value === 'string') { if (/^data:image\/[a-z0-9.+-]+;base64,/i.test(value)) { return value } if (/^https?:/i.test(value)) { throw new Error( 'Profile photo returned an HTTP URL instead of binary image data.', ) } return `data:${contentType};base64,${value}` } throw new Error('The profile photo response used an unsupported format.') } ``` `UserPhoto_V2` has a binary response contract and does not return an external image URL. An HTTP value indicates an unexpected response shape, so the helper rejects it rather than passing a potentially CSP-blocked or authenticated URL to `<img src>`. Complete photo flow: ```typescript const metadata = await Office365UsersService.UserPhotoMetadata(userId) if (!metadata.success) { throw new Error(metadata.error?.message ?? 'Failed to check profile photo') } if (!metadata.data?.HasPhoto) { return undefined } const photo = await Office365UsersService.UserPhoto_V2(userId) if (!photo.success) { throw new Error(photo.error?.message ?? 'Failed to load profile photo') } return photo.data ? toImageDataUrl(photo.data, metadata.data.ContentType) : undefined ``` Always fetch photos through the generated connector service. Do not call Microsoft Graph directly and do not assign the authenticated connector runtime URL to `<img src>`.
View on GitHub