diff --git a/docs/docs/cmd/viva/engage/engage-community-remove.mdx b/docs/docs/cmd/viva/engage/engage-community-remove.mdx
new file mode 100644
index 00000000000..e4f0aeb5b05
--- /dev/null
+++ b/docs/docs/cmd/viva/engage/engage-community-remove.mdx
@@ -0,0 +1,52 @@
+import Global from '/docs/cmd/_global.mdx';
+
+# viva engage community remove
+
+Removes a Viva Engage community
+
+## Usage
+
+```sh
+m365 viva engage community remove [options]
+```
+
+## Options
+
+```md definition-list
+`-i, --id [id]`
+: The id of the community. Specify either `id` or `displayName` but not both.
+
+`-n, --displayName [displayName]`
+: The name of the community. Specify either `id` or `displayName` but not both.
+
+`-f, --force`
+: Don't prompt for confirmation.
+```
+
+
+
+## Remarks
+
+:::info
+
+When the Viva Engage community is removed, all the associated Microsoft 365 content, including the M365 group, the document library, OneNote notebook, and Planner plans is deleted.
+
+:::
+
+## Examples
+
+Remove a community specified by id without prompting
+
+```sh
+m365 viva engage community remove --id eyJfdHlwZSI6Ikdyb3VwIiwiaWQiOiI0NzY5MTM1ODIwOSJ9 --force
+```
+
+Remove a community specified by name and prompt for confirmation
+
+```sh
+m365 viva engage community remove --displayName 'Software Engineers'
+```
+
+## Response
+
+The command won't return a response on success
\ No newline at end of file
diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts
index e0b1e70e70e..7c407aa9601 100644
--- a/docs/src/config/sidebars.ts
+++ b/docs/src/config/sidebars.ts
@@ -4406,6 +4406,11 @@ const sidebars: SidebarsConfig = {
label: 'engage community get',
id: 'cmd/viva/engage/engage-community-get'
},
+ {
+ type: 'doc',
+ label: 'engage community remove',
+ id: 'cmd/viva/engage/engage-community-remove'
+ },
{
type: 'doc',
label: 'engage group list',
diff --git a/src/m365/viva/commands.ts b/src/m365/viva/commands.ts
index 6c8b6348937..ca688fa44f5 100644
--- a/src/m365/viva/commands.ts
+++ b/src/m365/viva/commands.ts
@@ -4,6 +4,7 @@ export default {
CONNECTIONS_APP_CREATE: `${prefix} connections app create`,
ENGAGE_COMMUNITY_ADD: `${prefix} engage community add`,
ENGAGE_COMMUNITY_GET: `${prefix} engage community get`,
+ ENGAGE_COMMUNITY_REMOVE: `${prefix} engage community remove`,
ENGAGE_GROUP_LIST: `${prefix} engage group list`,
ENGAGE_GROUP_USER_ADD: `${prefix} engage group user add`,
ENGAGE_GROUP_USER_REMOVE: `${prefix} engage group user remove`,
diff --git a/src/m365/viva/commands/engage/Community.ts b/src/m365/viva/commands/engage/Community.ts
new file mode 100644
index 00000000000..ecf30fbfb9c
--- /dev/null
+++ b/src/m365/viva/commands/engage/Community.ts
@@ -0,0 +1,6 @@
+export interface Community {
+ id: string;
+ displayName: string;
+ description: string;
+ privacy: string;
+}
\ No newline at end of file
diff --git a/src/m365/viva/commands/engage/engage-community-remove.spec.ts b/src/m365/viva/commands/engage/engage-community-remove.spec.ts
new file mode 100644
index 00000000000..5968308b04a
--- /dev/null
+++ b/src/m365/viva/commands/engage/engage-community-remove.spec.ts
@@ -0,0 +1,141 @@
+import assert from 'assert';
+import sinon from 'sinon';
+import auth from '../../../../Auth.js';
+import commands from '../../commands.js';
+import request from '../../../../request.js';
+import { telemetry } from '../../../../telemetry.js';
+import { Logger } from '../../../../cli/Logger.js';
+import { CommandError } from '../../../../Command.js';
+import { pid } from '../../../../utils/pid.js';
+import { session } from '../../../../utils/session.js';
+import { sinonUtil } from '../../../../utils/sinonUtil.js';
+import { cli } from '../../../../cli/cli.js';
+import command from './engage-community-remove.js';
+import { vivaEngage } from '../../../../utils/vivaEngage.js';
+
+describe(commands.ENGAGE_COMMUNITY_REMOVE, () => {
+ const communityId = 'eyJfdHlwZSI6Ikdyb3VwIiwiaWQiOiI0NzY5MTM1ODIwOSJ9';
+ const displayName = 'Software Engineers';
+
+ let log: string[];
+ let logger: Logger;
+ let promptIssued: boolean;
+
+ before(() => {
+ sinon.stub(auth, 'restoreAuth').resolves();
+ sinon.stub(telemetry, 'trackEvent').returns();
+ sinon.stub(pid, 'getProcessName').returns('');
+ sinon.stub(session, 'getId').returns('');
+ auth.connection.active = true;
+ });
+
+ beforeEach(() => {
+ log = [];
+ logger = {
+ log: async (msg: string) => {
+ log.push(msg);
+ },
+ logRaw: async (msg: string) => {
+ log.push(msg);
+ },
+ logToStderr: async (msg: string) => {
+ log.push(msg);
+ }
+ };
+ sinon.stub(cli, 'promptForConfirmation').callsFake(() => {
+ promptIssued = true;
+ return Promise.resolve(false);
+ });
+
+ promptIssued = false;
+ });
+
+ afterEach(() => {
+ sinonUtil.restore([
+ request.delete,
+ vivaEngage.getCommunityIdByDisplayName,
+ cli.promptForConfirmation
+ ]);
+ });
+
+ after(() => {
+ sinon.restore();
+ auth.connection.active = false;
+ });
+
+ it('has correct name', () => {
+ assert.strictEqual(command.name, commands.ENGAGE_COMMUNITY_REMOVE);
+ });
+
+ it('has a description', () => {
+ assert.notStrictEqual(command.description, null);
+ });
+
+ it('prompts before removing the community when confirm option not passed', async () => {
+ await command.action(logger, { options: { id: communityId } });
+
+ assert(promptIssued);
+ });
+
+ it('aborts removing the community when prompt not confirmed', async () => {
+ const deleteSpy = sinon.stub(request, 'delete').resolves();
+
+ await command.action(logger, { options: { id: communityId } });
+ assert(deleteSpy.notCalled);
+ });
+
+ it('removes the community specified by id without prompting for confirmation', async () => {
+ const deleteRequestStub = sinon.stub(request, 'delete').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/employeeExperience/communities/${communityId}`) {
+ return;
+ }
+
+ throw 'Invalid request';
+ });
+
+ await command.action(logger, { options: { id: communityId, force: true, verbose: true } });
+ assert(deleteRequestStub.called);
+ });
+
+ it('removes the community specified by displayName while prompting for confirmation', async () => {
+ sinon.stub(vivaEngage, 'getCommunityIdByDisplayName').resolves(communityId);
+
+ const deleteRequestStub = sinon.stub(request, 'delete').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/employeeExperience/communities/${communityId}`) {
+ return;
+ }
+
+ throw 'Invalid request';
+ });
+
+ sinonUtil.restore(cli.promptForConfirmation);
+ sinon.stub(cli, 'promptForConfirmation').resolves(true);
+
+ await command.action(logger, { options: { displayName: displayName } });
+ assert(deleteRequestStub.called);
+ });
+
+ it('throws an error when the community specified by id cannot be found', async () => {
+ const error = {
+ error: {
+ code: 'notFound',
+ message: 'Not found.',
+ innerError: {
+ date: '2024-08-30T06:25:04',
+ 'request-id': '186480bb-73a7-4164-8a10-b05f45a94a4f',
+ 'client-request-id': '186480bb-73a7-4164-8a10-b05f45a94a4f'
+ }
+ }
+ };
+ sinon.stub(request, 'delete').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/employeeExperience/communities/${communityId}`) {
+ throw error;
+ }
+
+ throw 'Invalid request';
+ });
+
+ await assert.rejects(command.action(logger, { options: { id: communityId, force: true } }),
+ new CommandError(error.error.message));
+ });
+});
\ No newline at end of file
diff --git a/src/m365/viva/commands/engage/engage-community-remove.ts b/src/m365/viva/commands/engage/engage-community-remove.ts
new file mode 100644
index 00000000000..88a4d0f6532
--- /dev/null
+++ b/src/m365/viva/commands/engage/engage-community-remove.ts
@@ -0,0 +1,113 @@
+import GlobalOptions from '../../../../GlobalOptions.js';
+import { Logger } from '../../../../cli/Logger.js';
+import { cli } from '../../../../cli/cli.js';
+import request, { CliRequestOptions } from '../../../../request.js';
+import { vivaEngage } from '../../../../utils/vivaEngage.js';
+import GraphCommand from '../../../base/GraphCommand.js';
+import commands from '../../commands.js';
+
+interface CommandArgs {
+ options: Options;
+}
+
+interface Options extends GlobalOptions {
+ id?: string;
+ displayName?: string;
+ force?: boolean
+}
+
+class VivaEngageCommunityRemoveCommand extends GraphCommand {
+ public get name(): string {
+ return commands.ENGAGE_COMMUNITY_REMOVE;
+ }
+ public get description(): string {
+ return 'Removes a community';
+ }
+
+ constructor() {
+ super();
+
+ this.#initTelemetry();
+ this.#initOptions();
+ this.#initOptionSets();
+ this.#initTypes();
+ }
+
+ #initTelemetry(): void {
+ this.telemetry.push((args: CommandArgs) => {
+ Object.assign(this.telemetryProperties, {
+ id: args.options.id !== 'undefined',
+ displayName: args.options.displayName !== 'undefined',
+ force: !!args.options.force
+ });
+ });
+ }
+
+ #initOptions(): void {
+ this.options.unshift(
+ {
+ option: '-i, --id [id]'
+ },
+ {
+ option: '-n, --displayName [displayName]'
+ },
+ {
+ option: '-f, --force'
+ }
+ );
+ }
+
+ #initOptionSets(): void {
+ this.optionSets.push(
+ {
+ options: ['id', 'displayName']
+ }
+ );
+ }
+
+ #initTypes(): void {
+ this.types.string.push('id', 'displayName');
+ }
+
+ public async commandAction(logger: Logger, args: CommandArgs): Promise {
+
+ const removeCommunity = async (): Promise => {
+ try {
+ let communityId = args.options.id;
+
+ if (args.options.displayName) {
+ communityId = await vivaEngage.getCommunityIdByDisplayName(args.options.displayName);
+ }
+
+ if (args.options.verbose) {
+ await logger.logToStderr(`Removing Viva Engage community with ID ${communityId}...`);
+ }
+
+ const requestOptions: CliRequestOptions = {
+ url: `${this.resource}/v1.0/employeeExperience/communities/${communityId}`,
+ headers: {
+ accept: 'application/json;odata.metadata=none'
+ }
+ };
+
+ await request.delete(requestOptions);
+ }
+ catch (err: any) {
+ this.handleRejectedODataJsonPromise(err);
+ }
+ };
+
+ if (args.options.force) {
+ await removeCommunity();
+ }
+ else {
+ const result = await cli.promptForConfirmation({ message: `Are you sure you want to remove Viva Engage community '${args.options.id || args.options.displayName}'?` });
+
+ if (result) {
+ await removeCommunity();
+ }
+ }
+ }
+}
+
+export default new VivaEngageCommunityRemoveCommand();
\ No newline at end of file
diff --git a/src/utils/vivaEngage.spec.ts b/src/utils/vivaEngage.spec.ts
new file mode 100644
index 00000000000..e372d61f405
--- /dev/null
+++ b/src/utils/vivaEngage.spec.ts
@@ -0,0 +1,108 @@
+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 { vivaEngage } from './vivaEngage.js';
+import { formatting } from './formatting.js';
+import { settingsNames } from '../settingsNames.js';
+
+describe('utils/vivaEngage', () => {
+ const displayName = 'All Company';
+ const invalidDisplayName = 'All Compayn';
+ const communityResponse = {
+ "id": "eyJfdHlwZSI6Ikdyb3VwIiwiaWQiOiI0NzY5MTM1ODIwOSJ9",
+ "description": "This is the default group for everyone in the network",
+ "displayName": "All Company",
+ "privacy": "Public"
+ };
+ const anotherCommunityResponse = {
+ "id": "eyJfdHlwZ0NzY5SIwiIiSJ9IwO6IaWQiOIMTM1ODikdyb3Vw",
+ "description": "Test only",
+ "displayName": "All Company",
+ "privacy": "Private"
+ };
+
+ afterEach(() => {
+ sinonUtil.restore([
+ request.get,
+ cli.getSettingWithDefaultValue,
+ cli.handleMultipleResultsFound
+ ]);
+ });
+
+ it('correctly get single community id by name using getCommunityIdByDisplayName', async () => {
+ sinon.stub(request, 'get').callsFake(async opts => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/employeeExperience/communities?$filter=displayName eq '${formatting.encodeQueryParameter(displayName)}'`) {
+ return {
+ value: [
+ communityResponse
+ ]
+ };
+ }
+
+ return 'Invalid Request';
+ });
+
+ const actual = await vivaEngage.getCommunityIdByDisplayName(displayName);
+ assert.deepStrictEqual(actual, 'eyJfdHlwZSI6Ikdyb3VwIiwiaWQiOiI0NzY5MTM1ODIwOSJ9');
+ });
+
+ it('handles selecting single community when multiple communities with the specified name found using getCommunityIdByDisplayName and cli is set to prompt', async () => {
+ sinon.stub(request, 'get').callsFake(async opts => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/employeeExperience/communities?$filter=displayName eq '${formatting.encodeQueryParameter(displayName)}'`) {
+ return {
+ value: [
+ communityResponse,
+ anotherCommunityResponse
+ ]
+ };
+ }
+
+ return 'Invalid Request';
+ });
+
+ sinon.stub(cli, 'handleMultipleResultsFound').resolves(communityResponse);
+
+ const actual = await vivaEngage.getCommunityIdByDisplayName(displayName);
+ assert.deepStrictEqual(actual, 'eyJfdHlwZSI6Ikdyb3VwIiwiaWQiOiI0NzY5MTM1ODIwOSJ9');
+ });
+
+ it('throws error message when no community was found using getCommunityIdByDisplayName', async () => {
+ sinon.stub(request, 'get').callsFake(async (opts) => {
+ if (opts.url === `https://graph.microsoft.com/v1.0/employeeExperience/communities?$filter=displayName eq '${formatting.encodeQueryParameter(invalidDisplayName)}'`) {
+ return { value: [] };
+ }
+
+ throw 'Invalid Request';
+ });
+
+ await assert.rejects(vivaEngage.getCommunityIdByDisplayName(invalidDisplayName)), Error(`The specified Viva Engage community '${invalidDisplayName}' does not exist.`);
+ });
+
+ it('throws error message when multiple communities were found using getCommunityIdByDisplayName', 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/employeeExperience/communities?$filter=displayName eq '${formatting.encodeQueryParameter(displayName)}'`) {
+ return {
+ value: [
+ communityResponse,
+ anotherCommunityResponse
+ ]
+ };
+ }
+
+ return 'Invalid Request';
+ });
+
+ await assert.rejects(vivaEngage.getCommunityIdByDisplayName(displayName),
+ Error(`Multiple Viva Engage communities with name '${displayName}' found. Found: ${communityResponse.id}, ${anotherCommunityResponse.id}.`));
+ });
+});
\ No newline at end of file
diff --git a/src/utils/vivaEngage.ts b/src/utils/vivaEngage.ts
new file mode 100644
index 00000000000..35bc9461529
--- /dev/null
+++ b/src/utils/vivaEngage.ts
@@ -0,0 +1,28 @@
+import { cli } from '../cli/cli.js';
+import { Community } from '../m365/viva/commands/engage/Community.js';
+import { formatting } from './formatting.js';
+import { odata } from './odata.js';
+
+export const vivaEngage = {
+ /**
+ * Get Viva Engage community ID by display name.
+ * @param displayName Community display name.
+ * @returns The ID of the Viva Engage community.
+ * @throws Error when the community was not found.
+ */
+ async getCommunityIdByDisplayName(displayName: string): Promise {
+ const communities = await odata.getAllItems(`https://graph.microsoft.com/v1.0/employeeExperience/communities?$filter=displayName eq '${formatting.encodeQueryParameter(displayName)}'`);
+
+ if (communities.length === 0) {
+ throw `The specified Viva Engage community '${displayName}' does not exist.`;
+ }
+
+ if (communities.length > 1) {
+ const resultAsKeyValuePair = formatting.convertArrayToHashTable('id', communities);
+ const selectedCommunity = await cli.handleMultipleResultsFound(`Multiple Viva Engage communities with name '${displayName}' found.`, resultAsKeyValuePair);
+ return selectedCommunity.id;
+ }
+
+ return communities[0].id;
+ }
+};
\ No newline at end of file