diff --git a/docs/docs/cmd/outlook/event/event-list.mdx b/docs/docs/cmd/outlook/event/event-list.mdx
new file mode 100644
index 00000000000..f45013e7171
--- /dev/null
+++ b/docs/docs/cmd/outlook/event/event-list.mdx
@@ -0,0 +1,245 @@
+import Global from '../../_global.mdx';
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+# event list
+
+Retrieves a list of events from a specific calendar of a user.
+
+## Usage
+
+```sh
+m365 outlook event list [options]
+```
+
+## Options
+
+```md definition-list
+`--userId [userId]`
+: ID of the user. Specify either `userId` or `userName`, but not both.
+
+`--userName [userName]`
+: UPN of the user. Specify either `userId` or `userName`, but not both.
+
+`--calendarId [calendarId]`
+: ID of the calendar. Specify either `calendarId` or `calendarName`, but not both.
+
+`--calendarName [calendarName]`
+: Name of the calendar. Specify either `calendarId` or `calendarName`, but not both.
+
+`--startDateTime [startDateTime]`
+: Time indicating the inclusive start of a time range when the event starts.
+
+`--endDateTime [endDateTime]`
+: Time indicating the exclusive end of a time range when the event starts.
+
+`--timeZone [timeZone]`
+: The time zone for the event start and end times.
+
+`--properties [properties]`
+: Comma-separated list of properties to retrieve.
+
+`--filter [filter]`
+: OData filter to apply when retrieving the events.
+```
+
+
+
+## Remarks
+
+:::info
+
+When you specify a value for `timeZone`, consider the options of the [time zone list](https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones?view=windows-11#time-zones), or [additional time zone list](https://learn.microsoft.com/en-us/graph/api/resources/datetimetimezone?view=graph-rest-1.0#additional-time-zones)
+
+:::
+
+## Permissions
+
+
+
+
+ | Resource | Permissions |
+ |-----------------|-------------------------------------|
+ | Microsoft Graph | Calendars.ReadBasic, Calendars.Read |
+
+
+
+
+ | Resource | Permissions |
+ |-----------------|-------------------------------------|
+ | Microsoft Graph | Calendars.ReadBasic, Calendars.Read |
+
+
+
+
+
+## Examples
+
+List all events for the current signed-in user from a calendar specified by id.
+
+```sh
+m365 outlook event list --userId "@meId" --calendarId "AAMkAGRkZ"
+```
+
+List all events for the current signed-in user from a calendar specified by id and return event times in Pacific Standard Time time zone.
+
+```sh
+m365 outlook event list --userId "@meId" --calendarId "AAMkAGRkZ" --timeZone 'Pacific Standard Time'
+```
+
+List only id, subject, start time and end time of all events for a specific user and specific calendar
+
+```sh
+m365 outlook event list --userName "john.doe@contoso.com" --calendarName "Calendar" --properties "id,subject,start,end"
+```
+
+Filter events for the current signed-in user
+
+```sh
+m365 outlook event list --userId "@meId" --calendarId "AAMkAGRkZ" --filter "contains(subject, 'contoso')"
+```
+
+List all events from specific date range
+
+```sh
+m365 outlook event list --userId "@meId" --calendarId "AAMkAGRkZ" --startDateTime '2026-01-01' --endDateTime '2026-01-31'
+```
+
+## Response
+
+
+
+
+ ```json
+ [
+ {
+ "id": "AQMkAGRlM2Y5YTkzLWI2NzAtNDczOS05YWMyLTJhZGY2MGExMGU0MgBGAAADSG3wPE27kUeySjmT5eRT8QcAfJKVL07AC6GQ5pgAAAA==",
+ "createdDateTime": "2026-03-29T13:57:47.9194633Z",
+ "lastModifiedDateTime": "2026-03-29T13:59:48.6329479Z",
+ "changeKey": "fJKVL07sbkmIfHqjbDnRgQAC54IeWA==",
+ "categories": [],
+ "transactionId": "localevent:c95ac848-7295-ad3e-ee1e-f3832b10bf3e",
+ "originalStartTimeZone": "Greenwich Standard Time",
+ "originalEndTimeZone": "Greenwich Standard Time",
+ "iCalUId": "040000008200E00074C5B7101A82E008000000006B71750684BFDC01000000000000000010000000872F2916501A8442A7DB64D2E460E3D9",
+ "uid": "040000008200E00074C5B7101A82E008000000006B71750684BFDC01000000000000000010000000872F2916501A8442A7DB64D2E460E3D9",
+ "reminderMinutesBeforeStart": 15,
+ "isReminderOn": true,
+ "hasAttachments": false,
+ "subject": "Retro",
+ "bodyPreview": "Retrospective",
+ "importance": "normal",
+ "sensitivity": "normal",
+ "isAllDay": false,
+ "isCancelled": false,
+ "isOrganizer": true,
+ "responseRequested": true,
+ "seriesMasterId": null,
+ "showAs": "busy",
+ "type": "singleInstance",
+ "webLink": "https://outlook.office365.com/owa/?itemid=AQMkAGRlM2Y%3D%3D&exvsurl=1&path=/calendar/item",
+ "onlineMeetingUrl": null,
+ "isOnlineMeeting": false,
+ "onlineMeetingProvider": "unknown",
+ "allowNewTimeProposals": true,
+ "occurrenceId": null,
+ "isDraft": false,
+ "hideAttendees": false,
+ "responseStatus": {
+ "response": "organizer",
+ "time": "0001-01-01T00:00:00Z"
+ },
+ "body": {
+ "contentType": "html",
+ "content": "\r\\\n\r\\\n\r\\\n\r\\\n\r\\\n\r\\\nRetrospective
\r\\\n\r\\\n\r\\\n"
+ },
+ "start": {
+ "dateTime": "2026-03-29T16:00:00.0000000",
+ "timeZone": "UTC"
+ },
+ "end": {
+ "dateTime": "2026-03-29T18:00:00.0000000",
+ "timeZone": "UTC"
+ },
+ "location": {
+ "displayName": "",
+ "locationType": "default",
+ "uniqueIdType": "unknown",
+ "address": {},
+ "coordinates": {}
+ },
+ "locations": [],
+ "recurrence": null,
+ "attendees": [],
+ "organizer": {
+ "emailAddress": {
+ "name": "John Doe",
+ "address": "john.doe@contoso.com"
+ }
+ },
+ "onlineMeeting": null
+ }
+ ]
+ ```
+
+
+
+
+ ```text
+ id subject
+ -------------------------------------------------------------------------------------------------------------------------------------------------------- -------
+ AQMkAGRlM2Y5YTkzLWI2NzAtNDczOS05YWMyLTJhZGY2MGExMGU0MgBGAAADSG3wPE27kUeySjmT5eRT8QcAfJKVL07AC6GQ5pgAAAA== Retro
+ ```
+
+
+
+
+ ```csv
+ id,createdDateTime,lastModifiedDateTime,changeKey,transactionId,originalStartTimeZone,originalEndTimeZone,iCalUId,uid,reminderMinutesBeforeStart,isReminderOn,hasAttachments,subject,bodyPreview,importance,sensitivity,isAllDay,isCancelled,isOrganizer,responseRequested,seriesMasterId,showAs,type,webLink,onlineMeetingUrl,isOnlineMeeting,onlineMeetingProvider,allowNewTimeProposals,occurrenceId,isDraft,hideAttendees,recurrence,onlineMeeting
+ AQMkAGRlM2Y5YTkzLWI2NzAtNDczOS05YWMyLTJhZGY2MGExMGU0MgBGAAADSG3wPE27kUeySjmT5eRT8QcAfJKVL07AC6GQ5pgAAAA==,2026-03-29T13:57:47.9194633Z,2026-03-29T13:59:48.6329479Z,fJKVL07sbkmIfHqjbDnRgQAC54IeWA==,localevent:c95ac848-7295-ad3e-ee1e-f3832b10bf3e,Greenwich Standard Time,Greenwich Standard Time,040000008200E00074C5B7101A82E008000000006B71750684BFDC01000000000000000010000000872F2916501A8442A7DB64D2E460E3D9,040000008200E00074C5B7101A82E008000000006B71750684BFDC01000000000000000010000000872F2916501A8442A7DB64D2E460E3D9,15,1,0,Retro,Retrospective,normal,normal,0,0,1,1,,busy,singleInstance,https://outlook.office365.com/owa/?itemid=AQMkAGRlM2Y=1&path=/calendar/item,,0,unknown,1,,0,0,,
+ ```
+
+
+
+
+ ```md
+ # outlook event list --debug "false" --verbose "false" --userId "893f9116-e024-4bc6-8e98-54c245129485" --startDateTime "2026-03-29" --endDateTime "2026-03-31"
+
+ Date: 3/29/2026
+
+ ## AQMkAGRlM2Y5YTkzLWI2NzAtNDczOS05YWMyLTJhZGY2MGExMGU0MgBGAAADSG3wPE27kUeySjmT5eRT8QcAfJKVL07AC6GQ5pgAAAA==
+
+ Property | Value
+ ---------|-------
+ id | AQMkAGRlM2Y5YTkzLWI2NzAtNDczOS05YWMyLTJhZGY2MGExMGU0MgBGAAADSG3wPE27kUeySjmT5eRT8QcAfJKVL07AC6GQ5pgAAAA==
+ createdDateTime | 2026-03-29T13:57:47.9194633Z
+ lastModifiedDateTime | 2026-03-29T13:59:48.6329479Z
+ changeKey | fJKVL07sbkmIfHqjbDnRgQAC54IeWA==
+ transactionId | localevent:c95ac848-7295-ad3e-ee1e-f3832b10bf3e
+ originalStartTimeZone | Greenwich Standard Time
+ originalEndTimeZone | Greenwich Standard Time
+ iCalUId | 040000008200E00074C5B7101A82E008000000006B71750684BFDC01000000000000000010000000872F2916501A8442A7DB64D2E460E3D9
+ uid | 040000008200E00074C5B7101A82E008000000006B71750684BFDC01000000000000000010000000872F2916501A8442A7DB64D2E460E3D9
+ reminderMinutesBeforeStart | 15
+ isReminderOn | true
+ hasAttachments | false
+ subject | Retro
+ bodyPreview | Retrospective
+ importance | normal
+ sensitivity | normal
+ isAllDay | false
+ isCancelled | false
+ isOrganizer | true
+ responseRequested | true
+ showAs | busy
+ type | singleInstance
+ webLink | https://outlook.office365.com/owa/?itemid=AQMkAGRlM2Y5YTkzLWI2NzAtNDczOS05YWMyLTJhZGY2MGExMGU0MgBGAAADSG3wPE27kUeySjmT5eRT8QcAfJKVL07sbkmIfHqjbDnRgQAAAgENAAAAfJKVL07sbkmIfHqjbDnRgQAC6GQ5pgAAAA%3D%3D&exvsurl=1&path=/calendar/item
+ isOnlineMeeting | false
+ onlineMeetingProvider | unknown
+ allowNewTimeProposals | true
+ isDraft | false
+ hideAttendees | false
+ ```
+
+
+
diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts
index 55e13dca27c..633678cdc37 100644
--- a/docs/src/config/sidebars.ts
+++ b/docs/src/config/sidebars.ts
@@ -1311,6 +1311,15 @@ const sidebars: SidebarsConfig = {
}
]
},
+ {
+ event: [
+ {
+ type: 'doc',
+ label: 'event list',
+ id: 'cmd/outlook/event/event-list'
+ }
+ ]
+ },
{
mail: [
{
diff --git a/src/m365/outlook/commands.ts b/src/m365/outlook/commands.ts
index 79e8dc9f0d6..e21d2d43f13 100644
--- a/src/m365/outlook/commands.ts
+++ b/src/m365/outlook/commands.ts
@@ -2,6 +2,7 @@ const prefix: string = 'outlook';
export default {
CALENDARGROUP_LIST: `${prefix} calendargroup list`,
+ EVENT_LIST: `${prefix} event list`,
MAIL_SEARCHFOLDER_ADD: `${prefix} mail searchfolder add`,
MAIL_SEND: `${prefix} mail send`,
MAILBOX_SETTINGS_GET: `${prefix} mailbox settings get`,
diff --git a/src/m365/outlook/commands/event/event-list.spec.ts b/src/m365/outlook/commands/event/event-list.spec.ts
new file mode 100644
index 00000000000..1bf9d6560a4
--- /dev/null
+++ b/src/m365/outlook/commands/event/event-list.spec.ts
@@ -0,0 +1,416 @@
+import assert from 'assert';
+import sinon from 'sinon';
+import auth from '../../../../Auth.js';
+import { CommandError } from '../../../../Command.js';
+import { CommandInfo } from '../../../../cli/CommandInfo.js';
+import { Logger } from '../../../../cli/Logger.js';
+import { cli } from '../../../../cli/cli.js';
+import request from '../../../../request.js';
+import { telemetry } from '../../../../telemetry.js';
+import { pid } from '../../../../utils/pid.js';
+import { session } from '../../../../utils/session.js';
+import { sinonUtil } from '../../../../utils/sinonUtil.js';
+import commands from '../../commands.js';
+import command, { options } from './event-list.js';
+import { calendar } from '../../../../utils/calendar.js';
+
+describe(commands.EVENT_LIST, () => {
+ const userId = 'b743445a-112c-4fda-9afd-05943f9c7b36';
+ const userName = 'john.doe@contoso.com';
+ const calendarId = 'AAMkAGI2AAATZQAAA=';
+ const calendarName = 'My Calendar';
+
+ const eventsResponse = [
+ {
+ "id": "AQMkAGRlM2Y5YTkzLWI2NzAtNDczOS05YWMyLTJhZGY2MGExMGU0MgBGAAADSG3wPE27kUeySjmT5eRT8QcAfJKVL07sbkmIfHqjbDnRgQAAAgENAAAAfJKVL07sbkmIfHqjbDnRgQAC6GQ5pgAAAA==",
+ "createdDateTime": "2026-03-29T13:57:47.9194633Z",
+ "lastModifiedDateTime": "2026-03-29T13:59:48.6329479Z",
+ "changeKey": "fJKVL07sbkmIfHqjbDnRgQAC54IeWA==",
+ "categories": [],
+ "transactionId": "localevent:c95ac848-7295-ad3e-ee1e-f3832b10bf3e",
+ "originalStartTimeZone": "Greenwich Standard Time",
+ "originalEndTimeZone": "Greenwich Standard Time",
+ "iCalUId": "040000008200E00074C5B7101A82E008000000006B71750684BFDC01000000000000000010000000872F2916501A8442A7DB64D2E460E3D9",
+ "uid": "040000008200E00074C5B7101A82E008000000006B71750684BFDC01000000000000000010000000872F2916501A8442A7DB64D2E460E3D9",
+ "reminderMinutesBeforeStart": 15,
+ "isReminderOn": true,
+ "hasAttachments": false,
+ "subject": "Pub",
+ "bodyPreview": "sdfsdfsdfsdfdsfsdfsdfsd",
+ "importance": "normal",
+ "sensitivity": "normal",
+ "isAllDay": false,
+ "isCancelled": false,
+ "isOrganizer": true,
+ "responseRequested": true,
+ "seriesMasterId": null,
+ "showAs": "busy",
+ "type": "singleInstance",
+ "webLink": "https://outlook.office365.com/owa/?itemid=AQMkAGRlM2Y5YTkzLWI2NzAtNDczOS05YWMyLTJhZGY2MGExMGU0MgBGAAADSG3wPE27kUeySjmT5eRT8QcAfJKVL07sbkmIfHqjbDnRgQAAAgENAAAAfJKVL07sbkmIfHqjbDnRgQAC6GQ5pgAAAA%3D%3D&exvsurl=1&path=/calendar/item",
+ "onlineMeetingUrl": null,
+ "isOnlineMeeting": false,
+ "onlineMeetingProvider": "unknown",
+ "allowNewTimeProposals": true,
+ "occurrenceId": null,
+ "isDraft": false,
+ "hideAttendees": false,
+ "responseStatus": {
+ "response": "organizer",
+ "time": "0001-01-01T00:00:00Z"
+ },
+ "body": {
+ "contentType": "html",
+ "content": "\r\\\n
\r\\\n\r\\\n\r\\\n\r\\\n\r\\\nsdfsdfsdfsdfdsfsdfsdfsd
\r\\\n\r\\\n\r\\\n"
+ },
+ "start": {
+ "dateTime": "2026-03-29T16:00:00.0000000",
+ "timeZone": "UTC"
+ },
+ "end": {
+ "dateTime": "2026-03-29T18:00:00.0000000",
+ "timeZone": "UTC"
+ },
+ "location": {
+ "displayName": "",
+ "locationType": "default",
+ "uniqueIdType": "unknown",
+ "address": {},
+ "coordinates": {}
+ },
+ "locations": [],
+ "recurrence": null,
+ "attendees": [],
+ "organizer": {
+ "emailAddress": {
+ "name": "Martin Macháček",
+ "address": "MartinMachacek@4wrvkx.onmicrosoft.com"
+ }
+ },
+ "onlineMeeting": null
+ },
+ {
+ "id": "AQMkAGRlM2Y5YTkzLWI2NzAtNDczOS05YWMyLTJhZGY2MGExMGU0MgBGAAADSG3wPE27kUeySjmT5eRT8QcAfJKVL07sbkmIfHqjbDnRgQAAAgENAAAAfJKVL07sbkmIfHqjbDnRgQAC6GQ5pQAAAA==",
+ "createdDateTime": "2026-03-29T13:57:18.3565941Z",
+ "lastModifiedDateTime": "2026-03-29T13:57:19.5423408Z",
+ "changeKey": "fJKVL07sbkmIfHqjbDnRgQAC54IdjA==",
+ "categories": [],
+ "transactionId": "localevent:0209423d-9958-b2db-5fcc-39360518b2b8",
+ "originalStartTimeZone": "Greenwich Standard Time",
+ "originalEndTimeZone": "Greenwich Standard Time",
+ "iCalUId": "040000008200E00074C5B7101A82E00800000000C699D6F483BFDC0100000000000000001000000035C1CD3344304A40ACDF54500FE2F871",
+ "uid": "040000008200E00074C5B7101A82E00800000000C699D6F483BFDC0100000000000000001000000035C1CD3344304A40ACDF54500FE2F871",
+ "reminderMinutesBeforeStart": 15,
+ "isReminderOn": true,
+ "hasAttachments": false,
+ "subject": "Testik",
+ "bodyPreview": "",
+ "importance": "normal",
+ "sensitivity": "normal",
+ "isAllDay": false,
+ "isCancelled": false,
+ "isOrganizer": true,
+ "responseRequested": true,
+ "seriesMasterId": null,
+ "showAs": "busy",
+ "type": "singleInstance",
+ "webLink": "https://outlook.office365.com/owa/?itemid=AQMkAGRlM2Y5YTkzLWI2NzAtNDczOS05YWMyLTJhZGY2MGExMGU0MgBGAAADSG3wPE27kUeySjmT5eRT8QcAfJKVL07sbkmIfHqjbDnRgQAAAgENAAAAfJKVL07sbkmIfHqjbDnRgQAC6GQ5pQAAAA%3D%3D&exvsurl=1&path=/calendar/item",
+ "onlineMeetingUrl": null,
+ "isOnlineMeeting": false,
+ "onlineMeetingProvider": "unknown",
+ "allowNewTimeProposals": true,
+ "occurrenceId": null,
+ "isDraft": false,
+ "hideAttendees": false,
+ "responseStatus": {
+ "response": "organizer",
+ "time": "0001-01-01T00:00:00Z"
+ },
+ "body": {
+ "contentType": "html",
+ "content": ""
+ },
+ "start": {
+ "dateTime": "2026-03-30T14:30:00.0000000",
+ "timeZone": "UTC"
+ },
+ "end": {
+ "dateTime": "2026-03-30T15:00:00.0000000",
+ "timeZone": "UTC"
+ },
+ "location": {
+ "displayName": "",
+ "locationType": "default",
+ "uniqueIdType": "unknown",
+ "address": {},
+ "coordinates": {}
+ },
+ "locations": [],
+ "recurrence": null,
+ "attendees": [],
+ "organizer": {
+ "emailAddress": {
+ "name": "Martin Macháček",
+ "address": "MartinMachacek@4wrvkx.onmicrosoft.com"
+ }
+ },
+ "onlineMeeting": null
+ }
+ ];
+
+ let log: string[];
+ let logger: Logger;
+ let commandInfo: CommandInfo;
+ let loggerLogSpy: sinon.SinonSpy;
+ let commandOptionsSchema: typeof options;
+
+ before(() => {
+ sinon.stub(auth, 'restoreAuth').resolves();
+ sinon.stub(telemetry, 'trackEvent').resolves();
+ sinon.stub(pid, 'getProcessName').returns('');
+ sinon.stub(session, 'getId').returns('');
+ auth.connection.active = true;
+ if (!auth.connection.accessTokens[auth.defaultResource]) {
+ auth.connection.accessTokens[auth.defaultResource] = {
+ expiresOn: 'abc',
+ accessToken: 'abc'
+ };
+ }
+ commandInfo = cli.getCommandInfo(command);
+ commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;
+ });
+
+ beforeEach(() => {
+ log = [];
+ logger = {
+ log: async (msg: string) => {
+ log.push(msg);
+ },
+ logRaw: async (msg: string) => {
+ log.push(msg);
+ },
+ logToStderr: async (msg: string) => {
+ log.push(msg);
+ }
+ };
+ loggerLogSpy = sinon.spy(logger, 'log');
+ });
+
+ afterEach(() => {
+ sinonUtil.restore([
+ request.get,
+ calendar.getUserCalendarByName
+ ]);
+ });
+
+ after(() => {
+ sinon.restore();
+ auth.connection.active = false;
+ });
+
+ it('has correct name', () => {
+ assert.strictEqual(command.name, commands.EVENT_LIST);
+ });
+
+ it('has a description', () => {
+ assert.notStrictEqual(command.description, null);
+ });
+
+ it('defines correct properties for the default output', () => {
+ assert.deepStrictEqual(command.defaultProperties(), ['id', 'subject']);
+ });
+
+ it('passes validation with userId', () => {
+ const actual = commandOptionsSchema.safeParse({ userId });
+ assert.strictEqual(actual.success, true);
+ });
+
+ it('passes validation with userName', () => {
+ const actual = commandOptionsSchema.safeParse({ userName });
+ assert.strictEqual(actual.success, true);
+ });
+
+ it('fails validation if both userId and userName are specified', () => {
+ const actual = commandOptionsSchema.safeParse({ userId, userName });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('fails validation if neither userId nor userName is specified', () => {
+ const actual = commandOptionsSchema.safeParse({
+ id: calendarId
+ });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('fails validation if userId is not a valid GUID', () => {
+ const actual = commandOptionsSchema.safeParse({ userId: 'foo' });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('fails validation if userName is not a valid UPN', () => {
+ const actual = commandOptionsSchema.safeParse({ userName: 'foo' });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('fails validation if both calendarId and calendarName are specified', () => {
+ const actual = commandOptionsSchema.safeParse({ calendarId, calendarName });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('fails validation with unknown options', () => {
+ const actual = commandOptionsSchema.safeParse({ unknownOption: 'value' });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('fails validation if startDateTime is not a valid ISO date-time', () => {
+ const actual = commandOptionsSchema.safeParse({ startDateTime: 'foo' });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('fails validation if endDateTime is not a valid ISO date-time', () => {
+ const actual = commandOptionsSchema.safeParse({ endDateTime: 'foo' });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('retrieves events for the user specified by id', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/events`) {
+ return {
+ value: eventsResponse
+ };
+ }
+
+ throw 'Invalid request';
+ });
+
+ await command.action(logger, { options: commandOptionsSchema.parse({ userId: userId, verbose: true }) });
+ assert(loggerLogSpy.calledOnceWith(eventsResponse));
+ });
+
+ it('retrieves filtered events in specific time zone for the user specified by UPN from a calendar specified by id', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userName}')/calendars/${calendarId}/events?$filter=contains(subject, 'contoso')`) {
+ return {
+ value: eventsResponse
+ };
+ }
+
+ throw 'Invalid request';
+ });
+
+ await command.action(logger, {
+ options: commandOptionsSchema.parse({
+ userName: userName,
+ calendarId: calendarId,
+ timeZone: 'Pacific Standard Time',
+ filter: "contains(subject, 'contoso')",
+ verbose: true
+ })
+ });
+ assert(loggerLogSpy.calledOnceWith(eventsResponse));
+ });
+
+ it('retrieves filtered events since the specified date for the user specified by UPN from a calendar specified by id', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userName}')/calendars/${calendarId}/events?$filter=contains(subject, 'contoso') and start/dateTime ge '2026-03-29T00:00:00Z'`) {
+ return {
+ value: eventsResponse
+ };
+ }
+
+ throw 'Invalid request';
+ });
+
+ await command.action(logger, {
+ options: commandOptionsSchema.parse({
+ userName: userName,
+ calendarId: calendarId,
+ startDateTime: '2026-03-29T00:00:00Z',
+ filter: "contains(subject, 'contoso')",
+ verbose: true
+ })
+ });
+ assert(loggerLogSpy.calledOnceWith(eventsResponse));
+ });
+
+ it('retrieves limited properties of events since the specified date for the user specified by id from a calendar specified by name', async () => {
+ sinon.stub(calendar, 'getUserCalendarByName').resolves({ id: calendarId });
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendars/${calendarId}/events?$select=id,subject,start,end&$filter=start/dateTime ge '2026-03-29T00:00:00Z'`) {
+ return {
+ value: eventsResponse
+ };
+ }
+
+ throw 'Invalid request';
+ });
+
+ await command.action(logger, {
+ options: commandOptionsSchema.parse({
+ userId: userId,
+ calendarName: calendarName,
+ startDateTime: '2026-03-29T00:00:00Z',
+ properties: 'id,subject,start,end',
+ verbose: true
+ })
+ });
+ assert(loggerLogSpy.calledOnceWith(eventsResponse));
+ });
+
+ it('retrieves limited properties of events till the specified date for the user specified by id from a calendar specified by name', async () => {
+ sinon.stub(calendar, 'getUserCalendarByName').resolves({ id: calendarId });
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendars/${calendarId}/events?$select=id,subject,start,end&$filter=start/dateTime lt '2026-03-31T00:00:00Z'`) {
+ return {
+ value: eventsResponse
+ };
+ }
+
+ throw 'Invalid request';
+ });
+
+ await command.action(logger, {
+ options: commandOptionsSchema.parse({
+ userId: userId,
+ calendarName: calendarName,
+ endDateTime: '2026-03-31T00:00:00Z',
+ properties: 'id,subject,start,end',
+ verbose: true
+ })
+ });
+ assert(loggerLogSpy.calledOnceWith(eventsResponse));
+ });
+
+ it('retrieves events in specific date range for the user specified by id', async () => {
+ sinon.stub(calendar, 'getUserCalendarById').resolves({ id: calendarId });
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/events?$expand=attachments($select=id)&$filter=start/dateTime ge '2026-03-29T00:00:00Z' and start/dateTime lt '2026-03-31T00:00:00Z'`) {
+ return {
+ value: eventsResponse
+ };
+ }
+
+ throw 'Invalid request';
+ });
+
+ await command.action(logger, {
+ options: commandOptionsSchema.parse({
+ userId: userId,
+ startDateTime: '2026-03-29T00:00:00Z',
+ endDateTime: '2026-03-31T00:00:00Z',
+ properties: 'attachments/id',
+ verbose: true
+ })
+ });
+ assert(loggerLogSpy.calledOnceWith(eventsResponse));
+ });
+
+ it('correctly handles API OData error', async () => {
+ const errorMessage = 'Something went wrong';
+ sinon.stub(request, 'get').rejects({ error: { error: { message: errorMessage } } });
+
+ await assert.rejects(
+ command.action(logger, { options: commandOptionsSchema.parse({ userId: userId }) }),
+ new CommandError(errorMessage)
+ );
+ });
+});
\ No newline at end of file
diff --git a/src/m365/outlook/commands/event/event-list.ts b/src/m365/outlook/commands/event/event-list.ts
new file mode 100644
index 00000000000..5892f82cd3e
--- /dev/null
+++ b/src/m365/outlook/commands/event/event-list.ts
@@ -0,0 +1,141 @@
+import { Event } from '@microsoft/microsoft-graph-types';
+import { z } from 'zod';
+import { globalOptionsZod } from '../../../../Command.js';
+import GraphCommand from '../../../base/GraphCommand.js';
+import { Logger } from '../../../../cli/Logger.js';
+import commands from '../../commands.js';
+import { validation } from '../../../../utils/validation.js';
+import { odata } from '../../../../utils/odata.js';
+import { CliRequestOptions } from '../../../../request.js';
+import { calendar } from '../../../../utils/calendar.js';
+
+export const options = z.strictObject({
+ ...globalOptionsZod.shape,
+ userId: z.string().refine(id => validation.isValidGuid(id), {
+ error: e => `'${e.input}' is not a valid GUID.`
+ }).optional(),
+ userName: z.string().refine(name => validation.isValidUserPrincipalName(name), {
+ error: e => `'${e.input}' is not a valid UPN.`
+ }).optional(),
+ calendarId: z.string().optional(),
+ calendarName: z.string().optional(),
+ startDateTime: z.string().refine(date => validation.isValidISODateTime(date), {
+ error: e => `'${e.input}' is not a valid ISO date-time.`
+ }).optional(),
+ endDateTime: z.string().refine(date => validation.isValidISODateTime(date), {
+ error: e => `'${e.input}' is not a valid ISO date-time.`
+ }).optional(),
+ timeZone: z.string().optional(),
+ properties: z.string().optional(),
+ filter: z.string().optional()
+});
+
+declare type Options = z.infer;
+
+interface CommandArgs {
+ options: Options;
+}
+
+class OutlookEventListCommand extends GraphCommand {
+ public get name(): string {
+ return commands.EVENT_LIST;
+ }
+
+ public get description(): string {
+ return 'Retrieves a list of events from a specific calendar of a user.';
+ }
+
+ public get schema(): z.ZodType | undefined {
+ return options;
+ }
+
+ public getRefinedSchema(schema: typeof options): z.ZodObject | undefined {
+ return schema
+ .refine(options => [options.userId, options.userName].filter(x => x !== undefined).length === 1, {
+ error: 'Specify either userId or userName, but not both'
+ })
+ .refine(options => !(options.calendarId && options.calendarName), {
+ error: 'Specify either calendarId or calendarName, but not both.'
+ });
+ }
+
+ public defaultProperties(): string[] | undefined {
+ return ['id', 'subject'];
+ }
+
+ public async commandAction(logger: Logger, args: CommandArgs): Promise {
+ try {
+ if (this.verbose) {
+ await logger.logToStderr('Getting a list of the events...');
+ }
+
+ let events;
+ const endpoint = await this.getRequestUrl(args.options);
+ if (args.options.timeZone) {
+ const requestOptions: CliRequestOptions = {
+ url: endpoint,
+ headers: {
+ accept: 'application/json;odata.metadata=none',
+ Prefer: `outlook.timezone="${args.options.timeZone}"`
+ },
+ responseType: 'json'
+ };
+
+ events = await odata.getAllItems(requestOptions);
+ }
+ else {
+ events = await odata.getAllItems(endpoint);
+ }
+
+ await logger.log(events);
+ }
+ catch (err: any) {
+ this.handleRejectedODataJsonPromise(err);
+ }
+ }
+
+ private async getRequestUrl(options: Options): Promise {
+ const queryParameters: string[] = [];
+
+ if (options.properties) {
+ const allProperties = options.properties.split(',');
+ const selectProperties = allProperties.filter(prop => !prop.includes('/'));
+ const expandProperties = allProperties.filter(prop => prop.includes('/'));
+
+ if (selectProperties.length > 0) {
+ queryParameters.push(`$select=${selectProperties}`);
+ }
+
+ if (expandProperties.length > 0) {
+ const fieldExpands = expandProperties.map(p => `${p.split('/')[0]}($select=${p.split('/')[1]})`);
+ queryParameters.push(`$expand=${fieldExpands.join(',')}`);
+ }
+ }
+
+ if (options.filter || options.startDateTime || options.endDateTime) {
+ let filter = options.filter || '';
+ if (options.startDateTime) {
+ filter += `${filter ? ' and ' : ''}start/dateTime ge '${options.startDateTime}'`;
+ }
+ if (options.endDateTime) {
+ filter += `${filter ? ' and ' : ''}start/dateTime lt '${options.endDateTime}'`;
+ }
+ queryParameters.push(`$filter=${filter}`);
+ }
+
+ const queryString = queryParameters.length > 0
+ ? `?${queryParameters.join('&')}`
+ : '';
+
+ const userIdentifier = options.userId ?? options.userName;
+ let calendarId = options.calendarId;
+ if (options.calendarName) {
+ calendarId = (await calendar.getUserCalendarByName(userIdentifier!, options.calendarName))!.id;
+ }
+ return calendarId
+ ? `${this.resource}/v1.0/users('${userIdentifier}')/calendars/${calendarId}/events${queryString}`
+ : `${this.resource}/v1.0/users('${userIdentifier}')/events${queryString}`;
+ }
+}
+
+export default new OutlookEventListCommand();
\ No newline at end of file
diff --git a/src/utils/calendar.spec.ts b/src/utils/calendar.spec.ts
new file mode 100644
index 00000000000..fed0380396c
--- /dev/null
+++ b/src/utils/calendar.spec.ts
@@ -0,0 +1,189 @@
+import assert from 'assert';
+import sinon from 'sinon';
+import { cli } from '../cli/cli.js';
+import request from '../request.js';
+import { sinonUtil } from './sinonUtil.js';
+import { calendar } from './calendar.js';
+import { formatting } from './formatting.js';
+import { settingsNames } from '../settingsNames.js';
+
+describe('utils/calendar', () => {
+ const userId = '729827e3-9c14-49f7-bb1b-9608f156bbb8';
+ const calendarId = 'AAMkAGI2TGuLAAA';
+ const calendarName = 'My Calendar';
+ const invalidCalendarName = 'M Calnedar';
+ const calendarGroupId = 'AQMkADIxYjJiYgEzLTFmN_F8AAAIBBgAA_F8AAAJjIQAAAA==';
+ const calendarResponse = {
+ "id": "AAMkAGI2TGuLAAA=",
+ "name": "Calendar",
+ "color": "auto",
+ "isDefaultCalendar": true,
+ "changeKey": "nfZyf7VcrEKLNoU37KWlkQAAA0x0+w==",
+ "canShare": true,
+ "canViewPrivateItems": true,
+ "hexColor": "",
+ "canEdit": true,
+ "allowedOnlineMeetingProviders": [
+ "teamsForBusiness"
+ ],
+ "defaultOnlineMeetingProvider": "teamsForBusiness",
+ "isTallyingResponses": true,
+ "isRemovable": false,
+ "owner": {
+ "name": "John Doe",
+ "address": "john.doe@contoso.com"
+ }
+ };
+ const anotherCalendarResponse = {
+ "id": "AAMkAGI2TGuLBBB=",
+ "name": "Vacation",
+ "color": "auto",
+ "isDefaultCalendar": false,
+ "changeKey": "abcdf7VcrEKLNoU37KWlkQAAA0x0+w==",
+ "canShare": false,
+ "canViewPrivateItems": true,
+ "hexColor": "",
+ "canEdit": true,
+ "allowedOnlineMeetingProviders": [
+ ],
+ "defaultOnlineMeetingProvider": "none",
+ "isTallyingResponses": true,
+ "isRemovable": false,
+ "owner": {
+ "name": "John Doe",
+ "address": "john.doe@contoso.com"
+ }
+ };
+ const calendarLimitedResponse = {
+ "id": "AAMkAGI2TGuLAAA=",
+ "name": "Calendar",
+ "color": "auto"
+ };
+
+ afterEach(() => {
+ sinonUtil.restore([
+ request.get,
+ cli.getSettingWithDefaultValue,
+ cli.handleMultipleResultsFound
+ ]);
+ });
+
+ it('correctly get single calendar by name using getUserCalendarByName', async () => {
+ sinon.stub(request, 'get').callsFake(async opts => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendars?$filter=name eq '${formatting.encodeQueryParameter(calendarName)}'`) {
+ return {
+ value: [
+ calendarResponse
+ ]
+ };
+ }
+
+ throw 'Invalid Request';
+ });
+
+ const actual = await calendar.getUserCalendarByName(userId, calendarName);
+ assert.deepStrictEqual(actual, calendarResponse);
+ });
+
+ it('correctly get single calendar by name from a calendar group using getUserCalendarByName with specified properties', async () => {
+ sinon.stub(request, 'get').callsFake(async opts => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendarGroups/${calendarGroupId}/calendars?$filter=name eq '${formatting.encodeQueryParameter(calendarName)}'&$select=id,name`) {
+ return {
+ value: [
+ calendarLimitedResponse
+ ]
+ };
+ }
+
+ throw 'Invalid Request';
+ });
+
+ const actual = await calendar.getUserCalendarByName(userId, calendarName, calendarGroupId, 'id,name');
+ assert.deepStrictEqual(actual, calendarLimitedResponse);
+ });
+
+ it('handles selecting single calendar when multiple calendars with the specified name found using getUserCalendarByName and cli is set to prompt', async () => {
+ sinon.stub(request, 'get').callsFake(async opts => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendars?$filter=name eq '${formatting.encodeQueryParameter(calendarName)}'`) {
+ return {
+ value: [
+ calendarResponse,
+ anotherCalendarResponse
+ ]
+ };
+ }
+
+ throw 'Invalid Request';
+ });
+
+ sinon.stub(cli, 'handleMultipleResultsFound').resolves(calendarResponse);
+
+ const actual = await calendar.getUserCalendarByName(userId, calendarName);
+ assert.deepStrictEqual(actual, calendarResponse);
+ });
+
+ it('throws error message when no calendar was found using getUserCalendarByName', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendars?$filter=name eq '${formatting.encodeQueryParameter(invalidCalendarName)}'`) {
+ return { value: [] };
+ }
+
+ throw 'Invalid Request';
+ });
+
+ await assert.rejects(calendar.getUserCalendarByName(userId, invalidCalendarName),
+ new Error(`The specified calendar '${invalidCalendarName}' does not exist.`));
+ });
+
+ it('throws error message when multiple calendars were found using getUserCalendarByName', async () => {
+ sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => {
+ if (settingName === settingsNames.prompt) {
+ return false;
+ }
+
+ return defaultValue;
+ });
+
+ sinon.stub(request, 'get').callsFake(async opts => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendars?$filter=name eq '${formatting.encodeQueryParameter(calendarName)}'`) {
+ return {
+ value: [
+ calendarResponse,
+ anotherCalendarResponse
+ ]
+ };
+ }
+
+ return 'Invalid Request';
+ });
+
+ await assert.rejects(calendar.getUserCalendarByName(userId, calendarName),
+ Error(`Multiple calendars with name '${calendarName}' found. Found: ${calendarResponse.id}, ${anotherCalendarResponse.id}.`));
+ });
+
+ it('correctly get single calendar by id using getUserCalendarById', async () => {
+ sinon.stub(request, 'get').callsFake(async opts => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendars/${calendarId}`) {
+ return calendarResponse;
+ }
+
+ throw 'Invalid Request';
+ });
+
+ const actual = await calendar.getUserCalendarById(userId, calendarId);
+ assert.deepStrictEqual(actual, calendarResponse);
+ });
+
+ it('correctly get single calendar by id from a calendar group using getUserCalendarById with specified properties', async () => {
+ sinon.stub(request, 'get').callsFake(async opts => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendarGroups/${calendarGroupId}/calendars/${calendarId}?$select=id,displayName`) {
+ return calendarLimitedResponse;
+ }
+
+ throw 'Invalid Request';
+ });
+
+ const actual = await calendar.getUserCalendarById(userId, calendarId, calendarGroupId, 'id,displayName');
+ assert.deepStrictEqual(actual, calendarLimitedResponse);
+ });
+});
diff --git a/src/utils/calendar.ts b/src/utils/calendar.ts
new file mode 100644
index 00000000000..b01d81c6cd7
--- /dev/null
+++ b/src/utils/calendar.ts
@@ -0,0 +1,47 @@
+import { Calendar } from '@microsoft/microsoft-graph-types';
+import { odata } from './odata.js';
+import { formatting } from './formatting.js';
+import { cli } from '../cli/cli.js';
+import request, { CliRequestOptions } from '../request.js';
+
+export const calendar = {
+ async getUserCalendarById(userId: string, calendarId: string, calendarGroupId?: string, properties?: string): Promise {
+ let url = `https://graph.microsoft.com/v1.0/users('${userId}')/${calendarGroupId ? `calendarGroups/${calendarGroupId}/` : ''}calendars/${calendarId}`;
+
+ if (properties) {
+ url += `?$select=${properties}`;
+ }
+
+ const requestOptions: CliRequestOptions = {
+ url: url,
+ headers: {
+ accept: 'application/json;odata.metadata=none'
+ },
+ responseType: 'json'
+ };
+
+ return await request.get(requestOptions);
+ },
+
+ async getUserCalendarByName(userId: string, name: string, calendarGroupId?: string, properties?: string): Promise {
+ let url = `https://graph.microsoft.com/v1.0/users('${userId}')/${calendarGroupId ? `calendarGroups/${calendarGroupId}/` : ''}calendars?$filter=name eq '${formatting.encodeQueryParameter(name)}'`;
+
+ if (properties) {
+ url += `&$select=${properties}`;
+ }
+
+ const calendars = await odata.getAllItems(url);
+
+ if (calendars.length === 0) {
+ throw new Error(`The specified calendar '${name}' does not exist.`);
+ }
+
+ if (calendars.length > 1) {
+ const resultAsKeyValuePair = formatting.convertArrayToHashTable('id', calendars);
+ const selectedCalendar = await cli.handleMultipleResultsFound(`Multiple calendars with name '${name}' found.`, resultAsKeyValuePair);
+ return selectedCalendar;
+ }
+
+ return calendars[0];
+ }
+};