diff --git a/docs/docs/cmd/outlook/calendar/calendar-get.mdx b/docs/docs/cmd/outlook/calendar/calendar-get.mdx
new file mode 100644
index 00000000000..c85be236418
--- /dev/null
+++ b/docs/docs/cmd/outlook/calendar/calendar-get.mdx
@@ -0,0 +1,165 @@
+import Global from '../../_global.mdx';
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+# outlook calendar get
+
+Retrieves the calendar of a user or a group.
+
+## Usage
+
+```sh
+m365 outlook calendar get [options]
+```
+
+## Options
+
+```md definition-list
+`-i, --id [id]`
+: ID of the calendar. Specify either `id` or `name`, but not both.
+
+`-n, --name [name]`
+: Name of the calendar. Specify either `id` or `name`, but not both.
+
+`--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.
+
+`--calendarGroupId [calendarGroupId]`
+: ID of the calendar group. Specify either `calendarGroupId` or `calendarGroupName`, but not both.
+
+`--calendarGroupName [calendarGroupName]`
+: Name of the calendar group. Specify either `calendarGroupId` or `calendarGroupName`, but not both.
+```
+
+
+
+## Permissions
+
+
+
+
+ | Resource | Permissions |
+ |-----------------|--------------------|
+ | Microsoft Graph | Calendar.ReadBasic |
+
+
+
+
+ | Resource | Permissions |
+ |-----------------|--------------------|
+ | Microsoft Graph | Calendar.ReadBasic |
+
+
+
+
+## Examples
+
+Get the calendar for the current signed-in user by id.
+
+```sh
+m365 outlook calendar get --userId "@meId" --id "AAMkAGI2TGuLAAA="
+```
+
+Get the calendar from a specific calendar group for the current signed-in user by name.
+
+```sh
+m365 outlook calendar get --userId "@meId" --calendarGroupName "Colleague calendars" --name "Calendar"
+```
+
+Get the calendar from a specific calendar group for a specific user by name.
+
+```sh
+m365 outlook calendar get --userId b743445a-112c-4fda-9afd-05943f9c7b36 --calendarGroupId "AAMkADIxYjJiYmIzLTFmNjYtNGNhMy0YOkcEEh3vhfAAAGgdFjAAA=" --name "Calendar"
+```
+
+## Response
+
+
+
+
+ ```json
+ {
+ "id": "AQMkAGRAAAA==",
+ "name": "Calendar",
+ "color": "lightOrange",
+ "hexColor": "#f7630c",
+ "groupClassId": "0006f0b7-0000-0000-c000-000000000046",
+ "isDefaultCalendar": true,
+ "changeKey": "fJKVL07sbkmIfHqjbDnRgQAACWzbtQ==",
+ "canShare": true,
+ "canViewPrivateItems": true,
+ "canEdit": true,
+ "allowedOnlineMeetingProviders": [
+ "teamsForBusiness"
+ ],
+ "defaultOnlineMeetingProvider": "teamsForBusiness",
+ "isTallyingResponses": true,
+ "isRemovable": false,
+ "owner": {
+ "name": "John Doe",
+ "address": "john.doe@contoso.com"
+ }
+ }
+ ```
+
+
+
+
+ ```text
+ allowedOnlineMeetingProviders: ["teamsForBusiness"]
+ canEdit : true
+ canShare : true
+ canViewPrivateItems : true
+ changeKey : fJKVL07sbkmIfHqjbDnRgQAACWzbtQ==
+ color : lightOrange
+ defaultOnlineMeetingProvider : teamsForBusiness
+ groupClassId : 0006f0b7-0000-0000-c000-000000000046
+ hexColor : #f7630c
+ id : AQMkAGRAAAA==
+ isDefaultCalendar : true
+ isRemovable : false
+ isTallyingResponses : true
+ name : Calendar
+ owner : {"name":"John Doe","address":"john.doe@contoso.com"}
+ ```
+
+
+
+
+ ```csv
+ id,name,color,hexColor,groupClassId,isDefaultCalendar,changeKey,canShare,canViewPrivateItems,canEdit,defaultOnlineMeetingProvider,isTallyingResponses,isRemovable
+ AQMkAGRAAAA==,Calendar,lightOrange,#f7630c,0006f0b7-0000-0000-c000-000000000046,1,fJKVL07sbkmIfHqjbDnRgQAACWzbtQ==,1,1,1,teamsForBusiness,1,0
+ ```
+
+
+
+
+ ```md
+ # outlook calendar get --name "Calendar" --userName "john.doe@contoso.com"
+
+ Date: 2/9/2026
+
+ ## Calendar (AQMkAGRAAAA==)
+
+ Property | Value
+ ---------|-------
+ id | AQMkAGRAAAA==
+ name | Calendar
+ color | lightOrange
+ hexColor | #f7630c
+ groupClassId | 0006f0b7-0000-0000-c000-000000000046
+ isDefaultCalendar | true
+ changeKey | fJKVL07sbkmIfHqjbDnRgQAACWzbtQ==
+ canShare | true
+ canViewPrivateItems | true
+ canEdit | true
+ defaultOnlineMeetingProvider | teamsForBusiness
+ isTallyingResponses | true
+ isRemovable | false
+ ```
+
+
+
\ No newline at end of file
diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts
index 55e13dca27c..83ad7bf4803 100644
--- a/docs/src/config/sidebars.ts
+++ b/docs/src/config/sidebars.ts
@@ -1302,6 +1302,15 @@ const sidebars: SidebarsConfig = {
},
{
'Outlook (outlook)': [
+ {
+ calendar: [
+ {
+ type: 'doc',
+ label: 'calendar get',
+ id: 'cmd/outlook/calendar/calendar-get'
+ }
+ ],
+ },
{
calendargroup: [
{
diff --git a/src/m365/outlook/commands.ts b/src/m365/outlook/commands.ts
index 79e8dc9f0d6..ced7dded59b 100644
--- a/src/m365/outlook/commands.ts
+++ b/src/m365/outlook/commands.ts
@@ -1,6 +1,7 @@
const prefix: string = 'outlook';
export default {
+ CALENDAR_GET: `${prefix} calendar get`,
CALENDARGROUP_LIST: `${prefix} calendargroup list`,
MAIL_SEARCHFOLDER_ADD: `${prefix} mail searchfolder add`,
MAIL_SEND: `${prefix} mail send`,
diff --git a/src/m365/outlook/commands/calendar/calendar-get.spec.ts b/src/m365/outlook/commands/calendar/calendar-get.spec.ts
new file mode 100644
index 00000000000..aab8db8bf81
--- /dev/null
+++ b/src/m365/outlook/commands/calendar/calendar-get.spec.ts
@@ -0,0 +1,227 @@
+import assert from 'assert';
+import sinon from 'sinon';
+import auth from '../../../../Auth.js';
+import { cli } from '../../../../cli/cli.js';
+import { CommandInfo } from '../../../../cli/CommandInfo.js';
+import { Logger } from '../../../../cli/Logger.js';
+import { CommandError } from '../../../../Command.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 './calendar-get.js';
+import { calendarGroup } from '../../../../utils/calendarGroup.js';
+import { formatting } from '../../../../utils/formatting.js';
+
+describe(commands.CALENDAR_GET, () => {
+ const userId = 'ae0e8388-cd70-427f-9503-c57498ee3337';
+ const userName = 'john.doe@contoso.com';
+ const calendarId = 'AAMkADJmMVAAA=';
+ const calendarName = 'Volunteer';
+ const calendarGroupId = 'AQMkADJmMVAAA=';
+ const calendarGroupName = 'My Calendars';
+ const response = {
+ "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"
+ }
+ };
+
+ let log: any[];
+ let logger: Logger;
+ let loggerLogSpy: sinon.SinonSpy;
+ let commandInfo: CommandInfo;
+ 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;
+ 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
+ ]);
+ });
+
+ after(() => {
+ sinon.restore();
+ auth.connection.active = false;
+ });
+
+ it('has correct name', () => {
+ assert.strictEqual(command.name, commands.CALENDAR_GET);
+ });
+
+ it('has a description', () => {
+ assert.notStrictEqual(command.description, null);
+ });
+
+ it('fails validation if neither id nor name is specified', () => {
+ const actual = commandOptionsSchema.safeParse({
+ userId: userId
+ });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('fails validation if both id and name is specified', () => {
+ const actual = commandOptionsSchema.safeParse({
+ id: calendarId,
+ name: calendarName,
+ userId: userId
+ });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('fails validation if userId is not a valid GUID', () => {
+ const actual = commandOptionsSchema.safeParse({
+ id: calendarId,
+ userId: 'foo'
+ });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('fails validation if userName is not a valid user principal name', () => {
+ const actual = commandOptionsSchema.safeParse({
+ id: calendarId,
+ userName: 'foo'
+ });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('fails validation if both userId and userName is specified', () => {
+ const actual = commandOptionsSchema.safeParse({
+ id: calendarId,
+ userId: userId,
+ userName: userName
+ });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('fails validation if both calendarGroupId and calendarGroupName is specified', () => {
+ const actual = commandOptionsSchema.safeParse({
+ id: calendarId,
+ userId: userId,
+ calendarGroupId: calendarGroupId,
+ calendarGroupName: calendarGroupName
+ });
+ assert.notStrictEqual(actual.success, true);
+ });
+
+ it('correctly retrieves a calendar by id for a user specified by id', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendars/${calendarId}`) {
+ return response;
+ }
+
+ throw 'Invalid request';
+ });
+
+ const parsedSchema = commandOptionsSchema.safeParse({
+ id: calendarId,
+ userId: userId,
+ verbose: true
+ });
+ await command.action(logger, { options: parsedSchema.data! });
+ assert(loggerLogSpy.calledOnceWithExactly(response));
+ });
+
+ it('correctly retrieves a calendar by id for a user specified by name from a calendar group specified by name', async () => {
+ sinon.stub(calendarGroup, 'getUserCalendarGroupByName').withArgs(userName, calendarGroupName, 'id').resolves({ id: calendarGroupId });
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userName}')/calendarGroups/${calendarGroupId}/calendars/${calendarId}`) {
+ return response;
+ }
+
+ throw 'Invalid request';
+ });
+
+ const parsedSchema = commandOptionsSchema.safeParse({
+ id: calendarId,
+ userName: userName,
+ calendarGroupName: calendarGroupName,
+ verbose: true
+ });
+ await command.action(logger, { options: parsedSchema.data! });
+ assert(loggerLogSpy.calledOnceWithExactly(response));
+ });
+
+ it('correctly retrieves a calendar by name for a user specified by name from a calendar group specified by id', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userName}')/calendarGroups/${calendarGroupId}/calendars?$filter=name eq '${formatting.encodeQueryParameter(calendarName)}'`) {
+ return {
+ value: [response]
+ };
+ }
+
+ throw 'Invalid request';
+ });
+
+ const parsedSchema = commandOptionsSchema.safeParse({
+ name: calendarName,
+ userName: userName,
+ calendarGroupId: calendarGroupId,
+ verbose: true
+ });
+ await command.action(logger, { options: parsedSchema.data! });
+ assert(loggerLogSpy.calledOnceWithExactly(response));
+ });
+
+ it('handles error when calendar was not found', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendars/${calendarId}`) {
+ throw {
+ error:
+ {
+ code: 'Request_ResourceNotFound',
+ message: `Resource '${calendarId}' does not exist or one of its queried reference-property objects are not present.`
+ }
+ };
+ }
+ throw `Invalid request`;
+ });
+
+ await assert.rejects(
+ command.action(logger, { options: { id: calendarId, userId: userId } }),
+ new CommandError(`Resource '${calendarId}' does not exist or one of its queried reference-property objects are not present.`)
+ );
+ });
+});
diff --git a/src/m365/outlook/commands/calendar/calendar-get.ts b/src/m365/outlook/commands/calendar/calendar-get.ts
new file mode 100644
index 00000000000..afcf127d5bf
--- /dev/null
+++ b/src/m365/outlook/commands/calendar/calendar-get.ts
@@ -0,0 +1,89 @@
+import { Calendar } from '@microsoft/microsoft-graph-types';
+import { Logger } from '../../../../cli/Logger.js';
+import GraphCommand from '../../../base/GraphCommand.js';
+import commands from '../../commands.js';
+import { z } from 'zod';
+import { globalOptionsZod } from '../../../../Command.js';
+import { validation } from '../../../../utils/validation.js';
+import { calendarGroup } from '../../../../utils/calendarGroup.js';
+import { calendar } from '../../../../utils/calendar.js';
+
+export const options = z.strictObject({
+ ...globalOptionsZod.shape,
+ id: z.string().alias('i').optional(),
+ name: z.string().alias('n').optional(),
+ userId: z.string()
+ .refine(userId => validation.isValidGuid(userId), {
+ error: e => `'${e.input}' is not a valid GUID.`
+ }).optional(),
+ userName: z.string()
+ .refine(userName => validation.isValidUserPrincipalName(userName), {
+ error: e => `'${e.input}' is not a valid UPN.`
+ }).optional(),
+ calendarGroupId: z.string().optional(),
+ calendarGroupName: z.string().optional()
+});
+
+declare type Options = z.infer;
+
+interface CommandArgs {
+ options: Options;
+}
+
+class OutlookCalendarGetCommand extends GraphCommand {
+ public get name(): string {
+ return commands.CALENDAR_GET;
+ }
+
+ public get description(): string {
+ return 'Retrieves the calendar of a user or a group';
+ }
+
+ public get schema(): z.ZodType | undefined {
+ return options;
+ }
+
+ public getRefinedSchema(schema: typeof options): z.ZodObject | undefined {
+ return schema
+ .refine(options => [options.id, options.name].filter(x => x !== undefined).length === 1, {
+ error: 'Specify either id or name, but not both'
+ })
+ .refine(options => !(options.userId && options.userName), {
+ error: 'Specify either userId or userName, but not both'
+ })
+ .refine(options => !(options.calendarGroupId && options.calendarGroupName), {
+ error: 'Specify either calendarGroupId or calendarGroupName, but not both'
+ });
+ }
+
+ public async commandAction(logger: Logger, args: CommandArgs): Promise {
+ if (this.verbose) {
+ await logger.logToStderr('Getting calendar...');
+ }
+
+ try {
+ const userIdentifier = args.options.userId ?? args.options.userName;
+ let calendarGroupId = args.options.calendarGroupId;
+
+ if (args.options.calendarGroupName) {
+ const group = await calendarGroup.getUserCalendarGroupByName(userIdentifier!, args.options.calendarGroupName, 'id');
+ calendarGroupId = group.id;
+ }
+
+ let result: Calendar | undefined;
+ if (args.options.id) {
+ result = await calendar.getUserCalendarById(userIdentifier!, args.options.id, calendarGroupId);
+ }
+ else {
+ result = await calendar.getUserCalendarByName(userIdentifier!, args.options.name!, calendarGroupId);
+ }
+
+ await logger.log(result);
+ }
+ catch (err: any) {
+ this.handleRejectedODataJsonPromise(err);
+ }
+ }
+}
+
+export default new OutlookCalendarGetCommand();
diff --git a/src/utils/calendar.spec.ts b/src/utils/calendar.spec.ts
new file mode 100644
index 00000000000..c5ae1dda848
--- /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);
+ });
+});
\ No newline at end of file
diff --git a/src/utils/calendar.ts b/src/utils/calendar.ts
new file mode 100644
index 00000000000..1ba3687927f
--- /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];
+ }
+};
diff --git a/src/utils/calendarGroup.spec.ts b/src/utils/calendarGroup.spec.ts
new file mode 100644
index 00000000000..c4d6b14540d
--- /dev/null
+++ b/src/utils/calendarGroup.spec.ts
@@ -0,0 +1,131 @@
+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 { calendarGroup } from './calendarGroup.js';
+import { formatting } from './formatting.js';
+import { settingsNames } from '../settingsNames.js';
+
+describe('utils/calendarGroup', () => {
+ const userId = '729827e3-9c14-49f7-bb1b-9608f156bbb8';
+ const groupName = 'My Calendars';
+ const invalidGroupName = 'M Calnedar';
+ const calendarGroupResponse = {
+ "name": "My Calendars",
+ "classId": "0006f0b7-0000-0000-c000-000000000046",
+ "changeKey": "NreqLYgxdE2DpHBBId74XwAAAAAGZw==",
+ "id": "AQMkADIxYjJiYgEzLTFmN_F8AAAIBBgAA_F8AAAJjIQAAAA=="
+ };
+ const anotherCalendarGroupResponse = {
+ "name": "My Calendars",
+ "classId": "0006f0b7-0000-0000-c000-000000000047",
+ "changeKey": "MreqLYgxdE2DpHBBId74XwAAAAAGZw==",
+ "id": "AQMkADIxYjJiYgEzLTFmN_F8AAAIBBgAA_F8AAAJjIQBBB=="
+ };
+ const calendarGroupLimitedResponse = {
+ "name": "My Calendars",
+ "id": "AQMkADIxYjJiYgEzLTFmN_F8AAAIBBgAA_F8AAAJjIQAAAA=="
+ };
+
+ afterEach(() => {
+ sinonUtil.restore([
+ request.get,
+ cli.getSettingWithDefaultValue,
+ cli.handleMultipleResultsFound
+ ]);
+ });
+
+ it('correctly get single calendar group by name using getUserCalendarGroupByName', async () => {
+ sinon.stub(request, 'get').callsFake(async opts => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendarGroups?$filter=name eq '${formatting.encodeQueryParameter(groupName)}'`) {
+ return {
+ value: [
+ calendarGroupResponse
+ ]
+ };
+ }
+
+ throw 'Invalid Request';
+ });
+
+ const actual = await calendarGroup.getUserCalendarGroupByName(userId, groupName);
+ assert.deepStrictEqual(actual, calendarGroupResponse);
+ });
+
+ it('correctly get single calendar group by name using getUserCalendarGroupByName with specified properties', async () => {
+ sinon.stub(request, 'get').callsFake(async opts => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendarGroups?$filter=name eq '${formatting.encodeQueryParameter(groupName)}'&$select=id,name`) {
+ return {
+ value: [
+ calendarGroupLimitedResponse
+ ]
+ };
+ }
+
+ throw 'Invalid Request';
+ });
+
+ const actual = await calendarGroup.getUserCalendarGroupByName(userId, groupName, 'id,name');
+ assert.deepStrictEqual(actual, calendarGroupLimitedResponse);
+ });
+
+ it('handles selecting single calendar group when multiple calendar groups with the specified name found using getUserCalendarGroupByName 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}')/calendarGroups?$filter=name eq '${formatting.encodeQueryParameter(groupName)}'`) {
+ return {
+ value: [
+ calendarGroupResponse,
+ anotherCalendarGroupResponse
+ ]
+ };
+ }
+
+ throw 'Invalid Request';
+ });
+
+ sinon.stub(cli, 'handleMultipleResultsFound').resolves(calendarGroupResponse);
+
+ const actual = await calendarGroup.getUserCalendarGroupByName(userId, groupName);
+ assert.deepStrictEqual(actual, calendarGroupResponse);
+ });
+
+ it('throws error message when no calendar group was found using getUserCalendarGroupByName', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/users('${userId}')/calendarGroups?$filter=name eq '${formatting.encodeQueryParameter(invalidGroupName)}'`) {
+ return { value: [] };
+ }
+
+ throw 'Invalid Request';
+ });
+
+ await assert.rejects(calendarGroup.getUserCalendarGroupByName(userId, invalidGroupName),
+ new Error(`The specified calendar group '${invalidGroupName}' does not exist.`));
+ });
+
+ it('throws error message when multiple calendar groups were found using getUserCalendarGroupByName', 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}')/calendarGroups?$filter=name eq '${formatting.encodeQueryParameter(groupName)}'`) {
+ return {
+ value: [
+ calendarGroupResponse,
+ anotherCalendarGroupResponse
+ ]
+ };
+ }
+
+ return 'Invalid Request';
+ });
+
+ await assert.rejects(calendarGroup.getUserCalendarGroupByName(userId, groupName),
+ Error(`Multiple calendar groups with name '${groupName}' found. Found: ${calendarGroupResponse.id}, ${anotherCalendarGroupResponse.id}.`));
+ });
+});
\ No newline at end of file
diff --git a/src/utils/calendarGroup.ts b/src/utils/calendarGroup.ts
new file mode 100644
index 00000000000..1f34a8c0132
--- /dev/null
+++ b/src/utils/calendarGroup.ts
@@ -0,0 +1,28 @@
+import { CalendarGroup } from '@microsoft/microsoft-graph-types';
+import { odata } from './odata.js';
+import { formatting } from './formatting.js';
+import { cli } from '../cli/cli.js';
+
+export const calendarGroup = {
+ async getUserCalendarGroupByName(userId: string, displayName: string, properties?: string): Promise {
+ let url = `https://graph.microsoft.com/v1.0/users('${userId}')/calendarGroups?$filter=name eq '${formatting.encodeQueryParameter(displayName)}'`;
+
+ if (properties) {
+ url += `&$select=${properties}`;
+ }
+
+ const calendarGroups = await odata.getAllItems(url);
+
+ if (calendarGroups.length === 0) {
+ throw new Error(`The specified calendar group '${displayName}' does not exist.`);
+ }
+
+ if (calendarGroups.length > 1) {
+ const resultAsKeyValuePair = formatting.convertArrayToHashTable('id', calendarGroups);
+ const selectedCalendarGroup = await cli.handleMultipleResultsFound(`Multiple calendar groups with name '${displayName}' found.`, resultAsKeyValuePair);
+ return selectedCalendarGroup;
+ }
+
+ return calendarGroups[0];
+ }
+};