@@ -71,15 +71,18 @@ private static Command CreateBlueprintCleanupCommand(
7171 new [ ] { "--verbose" , "-v" } ,
7272 description : "Enable verbose logging" ) ;
7373
74+ var endpointOnlyOption = new Option < bool > (
75+ new [ ] { "--endpoint-only" } ,
76+ description : "Delete only the messaging endpoint, keep the blueprint application" ) ;
77+
7478 command . AddOption ( configOption ) ;
7579 command . AddOption ( verboseOption ) ;
80+ command . AddOption ( endpointOnlyOption ) ;
7681
77- command . SetHandler ( async ( configFile , verbose ) =>
82+ command . SetHandler ( async ( configFile , verbose , endpointOnly ) =>
7883 {
7984 try
8085 {
81- logger . LogInformation ( "Starting blueprint cleanup..." ) ;
82-
8386 var config = await LoadConfigAsync ( configFile , logger , configService ) ;
8487 if ( config == null ) return ;
8588
@@ -89,8 +92,18 @@ private static Command CreateBlueprintCleanupCommand(
8992 agentBlueprintService . CustomClientAppId = config . ClientAppId ;
9093 }
9194
95+ // If endpoint-only mode, only delete the messaging endpoint
96+ if ( endpointOnly )
97+ {
98+ await ExecuteEndpointOnlyCleanupAsync ( logger , config , botConfigurator ) ;
99+ return ;
100+ }
101+
102+ // Full blueprint cleanup (original behavior)
103+ logger . LogInformation ( "Starting blueprint cleanup..." ) ;
104+
92105 // Check if there's actually a blueprint to clean up
93- if ( string . IsNullOrEmpty ( config . AgentBlueprintId ) )
106+ if ( string . IsNullOrWhiteSpace ( config . AgentBlueprintId ) )
94107 {
95108 logger . LogInformation ( "No blueprint application found to clean up" ) ;
96109 return ;
@@ -127,27 +140,10 @@ private static Command CreateBlueprintCleanupCommand(
127140 // Blueprint deleted successfully
128141 logger . LogInformation ( "Agent blueprint application deleted successfully" ) ;
129142
130- // Handle endpoint deletion if needed
131- if ( ! string . IsNullOrEmpty ( config . BotName ) )
143+ // Handle endpoint deletion if needed using shared helper
144+ if ( ! await DeleteMessagingEndpointAsync ( logger , config , botConfigurator ) )
132145 {
133- // Delete messaging endpoint
134- logger . LogInformation ( "Deleting messaging endpoint registration..." ) ;
135- var endpointName = EndpointHelper . GetEndpointName ( config . BotName ) ;
136-
137- var endpointDeleted = await botConfigurator . DeleteEndpointWithAgentBlueprintAsync (
138- endpointName ,
139- config . Location ,
140- config . AgentBlueprintId ) ;
141-
142- if ( ! endpointDeleted )
143- {
144- logger . LogWarning ( "Failed to delete blueprint messaging endpoint" ) ;
145- return ;
146- }
147- }
148- else
149- {
150- logger . LogInformation ( "No blueprint messaging endpoint found in configuration" ) ;
146+ return ;
151147 }
152148
153149 // Clear configuration after successful blueprint deletion
@@ -167,7 +163,7 @@ private static Command CreateBlueprintCleanupCommand(
167163 {
168164 logger . LogError ( ex , "Blueprint cleanup failed" ) ;
169165 }
170- } , configOption , verboseOption ) ;
166+ } , configOption , verboseOption , endpointOnlyOption ) ;
171167
172168 return command ;
173169 }
@@ -207,7 +203,7 @@ private static Command CreateAzureCleanupCommand(
207203 logger . LogInformation ( "=========================" ) ;
208204 logger . LogInformation ( " Web App: {WebAppName}" , config . WebAppName ) ;
209205 logger . LogInformation ( " App Service Plan: {PlanName}" , config . AppServicePlanName ) ;
210- if ( ! string . IsNullOrEmpty ( config . BotId ) )
206+ if ( ! string . IsNullOrWhiteSpace ( config . BotId ) )
211207 logger . LogInformation ( " Azure Bot: {BotId}" , config . BotId ) ;
212208 logger . LogInformation ( " Resource Group: {ResourceGroup}" , config . ResourceGroup ) ;
213209 logger . LogInformation ( "" ) ;
@@ -308,9 +304,9 @@ private static Command CreateInstanceCleanupCommand(
308304 logger . LogInformation ( "============================" ) ;
309305 logger . LogInformation ( "Will delete the following resources:" ) ;
310306
311- if ( ! string . IsNullOrEmpty ( config . AgenticAppId ) )
307+ if ( ! string . IsNullOrWhiteSpace ( config . AgenticAppId ) )
312308 logger . LogInformation ( " Agent Identity Application: {IdentityId}" , config . AgenticAppId ) ;
313- if ( ! string . IsNullOrEmpty ( config . AgenticUserId ) )
309+ if ( ! string . IsNullOrWhiteSpace ( config . AgenticUserId ) )
314310 logger . LogInformation ( " Agent User: {UserId}" , config . AgenticUserId ) ;
315311 logger . LogInformation ( " Generated configuration file" ) ;
316312 logger . LogInformation ( "" ) ;
@@ -324,15 +320,15 @@ private static Command CreateInstanceCleanupCommand(
324320 }
325321
326322 // Delete agent identity application
327- if ( ! string . IsNullOrEmpty ( config . AgenticAppId ) )
323+ if ( ! string . IsNullOrWhiteSpace ( config . AgenticAppId ) )
328324 {
329325 logger . LogInformation ( "Deleting agent identity application..." ) ;
330326 await executor . ExecuteAsync ( "az" , $ "ad app delete --id { config . AgenticAppId } ", null , true , false , CancellationToken . None ) ;
331327 logger . LogInformation ( "Agent identity application deleted" ) ;
332328 }
333329
334330 // Delete agent user
335- if ( ! string . IsNullOrEmpty ( config . AgenticUserId ) )
331+ if ( ! string . IsNullOrWhiteSpace ( config . AgenticUserId ) )
336332 {
337333 logger . LogInformation ( "Deleting agent user..." ) ;
338334 await executor . ExecuteAsync ( "az" , $ "ad user delete --id { config . AgenticUserId } ", null , true , false , CancellationToken . None ) ;
@@ -420,19 +416,19 @@ private static async Task ExecuteAllCleanupAsync(
420416 logger . LogInformation ( "Complete Cleanup Preview:" ) ;
421417 logger . LogInformation ( "============================" ) ;
422418 logger . LogInformation ( "WARNING: ALL RESOURCES WILL BE DELETED:" ) ;
423- if ( ! string . IsNullOrEmpty ( config . AgentBlueprintId ) )
419+ if ( ! string . IsNullOrWhiteSpace ( config . AgentBlueprintId ) )
424420 logger . LogInformation ( " Blueprint Application: {BlueprintId}" , config . AgentBlueprintId ) ;
425- if ( ! string . IsNullOrEmpty ( config . AgenticAppId ) )
421+ if ( ! string . IsNullOrWhiteSpace ( config . AgenticAppId ) )
426422 logger . LogInformation ( " Agent Identity Application: {IdentityId}" , config . AgenticAppId ) ;
427- if ( ! string . IsNullOrEmpty ( config . AgenticUserId ) )
423+ if ( ! string . IsNullOrWhiteSpace ( config . AgenticUserId ) )
428424 logger . LogInformation ( " Agent User: {UserId}" , config . AgenticUserId ) ;
429- if ( ! string . IsNullOrEmpty ( config . WebAppName ) )
425+ if ( ! string . IsNullOrWhiteSpace ( config . WebAppName ) )
430426 logger . LogInformation ( " Web App: {WebAppName}" , config . WebAppName ) ;
431- if ( ! string . IsNullOrEmpty ( config . AppServicePlanName ) )
427+ if ( ! string . IsNullOrWhiteSpace ( config . AppServicePlanName ) )
432428 logger . LogInformation ( " App Service Plan: {PlanName}" , config . AppServicePlanName ) ;
433- if ( ! string . IsNullOrEmpty ( config . BotName ) )
429+ if ( ! string . IsNullOrWhiteSpace ( config . BotName ) )
434430 logger . LogInformation ( " Azure Messaging Endpoint: {BotName}" , config . BotName ) ;
435- if ( ! string . IsNullOrEmpty ( config . Location ) )
431+ if ( ! string . IsNullOrWhiteSpace ( config . Location ) )
436432 logger . LogInformation ( " Location: {Location}" , config . Location ) ;
437433 logger . LogInformation ( " Generated configuration file" ) ;
438434 logger . LogInformation ( "" ) ;
@@ -452,7 +448,7 @@ private static async Task ExecuteAllCleanupAsync(
452448 logger . LogInformation ( "Starting complete cleanup..." ) ;
453449
454450 // 1. Delete agent blueprint application
455- if ( ! string . IsNullOrEmpty ( config . AgentBlueprintId ) )
451+ if ( ! string . IsNullOrWhiteSpace ( config . AgentBlueprintId ) )
456452 {
457453 logger . LogInformation ( "Deleting agent blueprint application..." ) ;
458454 var deleted = await agentBlueprintService . DeleteAgentBlueprintAsync (
@@ -472,7 +468,7 @@ private static async Task ExecuteAllCleanupAsync(
472468 }
473469
474470 // 2. Delete agent identity application
475- if ( ! string . IsNullOrEmpty ( config . AgenticAppId ) )
471+ if ( ! string . IsNullOrWhiteSpace ( config . AgenticAppId ) )
476472 {
477473 logger . LogInformation ( "Deleting agent identity application..." ) ;
478474
@@ -493,45 +489,25 @@ private static async Task ExecuteAllCleanupAsync(
493489 }
494490
495491 // 3. Delete agent user
496- if ( ! string . IsNullOrEmpty ( config . AgenticUserId ) )
492+ if ( ! string . IsNullOrWhiteSpace ( config . AgenticUserId ) )
497493 {
498494 logger . LogInformation ( "Deleting agent user..." ) ;
499495 await executor . ExecuteAsync ( "az" , $ "ad user delete --id { config . AgenticUserId } ", null , true , false , CancellationToken . None ) ;
500496 logger . LogInformation ( "Agent user deleted" ) ;
501497 }
502498
503- // 4. Delete bot messaging endpoint
504- if ( ! string . IsNullOrEmpty ( config . BotName ) )
499+ // 4. Delete bot messaging endpoint using shared helper
500+ if ( ! string . IsNullOrWhiteSpace ( config . BotName ) )
505501 {
506- logger . LogInformation ( "Deleting messaging endpoint registration..." ) ;
507- if ( string . IsNullOrEmpty ( config . AgentBlueprintId ) )
502+ var endpointDeleted = await DeleteMessagingEndpointAsync ( logger , config , botConfigurator ) ;
503+ if ( ! endpointDeleted )
508504 {
509- logger . LogError ( "Agent Blueprint ID not found. Agent Blueprint ID is required for deleting endpoint registration." ) ;
510505 hasFailures = true ;
511506 }
512- else
513- {
514- var endpointName = EndpointHelper . GetEndpointName ( config . BotName ) ;
515-
516- var endpointDeleted = await botConfigurator . DeleteEndpointWithAgentBlueprintAsync (
517- endpointName ,
518- config . Location ,
519- config . AgentBlueprintId ) ;
520-
521- if ( endpointDeleted )
522- {
523- logger . LogInformation ( "Messaging endpoint deleted successfully" ) ;
524- }
525- else
526- {
527- logger . LogWarning ( "Failed to delete messaging endpoint" ) ;
528- hasFailures = true ;
529- }
530- }
531507 }
532508
533509 // 5. Delete Azure resources (Web App and App Service Plan)
534- if ( ! string . IsNullOrEmpty ( config . WebAppName ) && ! string . IsNullOrEmpty ( config . ResourceGroup ) )
510+ if ( ! string . IsNullOrWhiteSpace ( config . WebAppName ) && ! string . IsNullOrWhiteSpace ( config . ResourceGroup ) )
535511 {
536512 logger . LogInformation ( "Deleting Azure resources..." ) ;
537513
@@ -562,7 +538,7 @@ private static async Task ExecuteAllCleanupAsync(
562538 }
563539
564540 // Delete App Service Plan after web app is gone (with retry for conflicts)
565- if ( ! string . IsNullOrEmpty ( config . AppServicePlanName ) )
541+ if ( ! string . IsNullOrWhiteSpace ( config . AppServicePlanName ) )
566542 {
567543 logger . LogInformation ( "Deleting App Service Plan: {PlanName}..." , config . AppServicePlanName ) ;
568544
@@ -663,6 +639,107 @@ private static async Task ExecuteAllCleanupAsync(
663639 }
664640 }
665641
642+ /// <summary>
643+ /// Shared helper method to delete a messaging endpoint.
644+ /// Validates configuration, gets endpoint name, and calls the bot configurator to delete.
645+ /// </summary>
646+ /// <param name="logger">Logger instance for diagnostic messages</param>
647+ /// <param name="config">Configuration containing endpoint and blueprint information</param>
648+ /// <param name="botConfigurator">Bot configurator service for endpoint operations</param>
649+ /// <returns>True if endpoint was deleted successfully; false otherwise</returns>
650+ private static async Task < bool > DeleteMessagingEndpointAsync (
651+ ILogger < CleanupCommand > logger ,
652+ Agent365Config config ,
653+ IBotConfigurator botConfigurator )
654+ {
655+ // Check if there's actually an endpoint to clean up
656+ if ( string . IsNullOrWhiteSpace ( config . BotName ) )
657+ {
658+ logger . LogInformation ( "No messaging endpoint found in configuration" ) ;
659+ return true ; // No endpoint to delete = success
660+ }
661+
662+ // Check if blueprint ID exists (required for endpoint deletion)
663+ if ( string . IsNullOrWhiteSpace ( config . AgentBlueprintId ) )
664+ {
665+ logger . LogError ( "Agent Blueprint ID not found. Agent Blueprint ID is required for deleting endpoint registration." ) ;
666+ return false ;
667+ }
668+
669+ logger . LogInformation ( "Deleting messaging endpoint registration..." ) ;
670+ var endpointName = EndpointHelper . GetEndpointName ( config . BotName ) ;
671+
672+ var endpointDeleted = await botConfigurator . DeleteEndpointWithAgentBlueprintAsync (
673+ endpointName ,
674+ config . Location ,
675+ config . AgentBlueprintId ) ;
676+
677+ if ( endpointDeleted )
678+ {
679+ logger . LogInformation ( "Messaging endpoint deleted successfully" ) ;
680+ return true ;
681+ }
682+ else
683+ {
684+ logger . LogWarning ( "Failed to delete messaging endpoint" ) ;
685+ return false ;
686+ }
687+ }
688+
689+ /// <summary>
690+ /// Executes endpoint-only cleanup - deletes the messaging endpoint while preserving the blueprint application
691+ /// </summary>
692+ private static async Task ExecuteEndpointOnlyCleanupAsync (
693+ ILogger < CleanupCommand > logger ,
694+ Agent365Config config ,
695+ IBotConfigurator botConfigurator )
696+ {
697+ logger . LogInformation ( "Starting endpoint-only cleanup..." ) ;
698+
699+ // Check if there's actually an endpoint to clean up
700+ if ( string . IsNullOrWhiteSpace ( config . BotName ) )
701+ {
702+ logger . LogInformation ( "No messaging endpoint found to clean up" ) ;
703+ return ;
704+ }
705+
706+ // Check if blueprint ID exists (required for endpoint deletion)
707+ if ( string . IsNullOrWhiteSpace ( config . AgentBlueprintId ) )
708+ {
709+ logger . LogError ( "Agent Blueprint ID not found. Blueprint ID is required for endpoint deletion." ) ;
710+ logger . LogInformation ( "Please ensure blueprint is configured before attempting endpoint cleanup." ) ;
711+ return ;
712+ }
713+
714+ logger . LogInformation ( "" ) ;
715+ logger . LogInformation ( "Endpoint Cleanup Preview:" ) ;
716+ logger . LogInformation ( "============================" ) ;
717+ logger . LogInformation ( "Will delete messaging endpoint:" ) ;
718+ logger . LogInformation ( " Endpoint Name: {BotName}" , config . BotName ) ;
719+ logger . LogInformation ( " Location: {Location}" , config . Location ) ;
720+ logger . LogInformation ( "" ) ;
721+
722+ Console . Write ( "Continue with endpoint cleanup? (y/N): " ) ;
723+ var response = Console . ReadLine ( ) ? . Trim ( ) . ToLowerInvariant ( ) ;
724+ if ( response != "y" && response != "yes" )
725+ {
726+ logger . LogInformation ( "Cleanup cancelled by user" ) ;
727+ return ;
728+ }
729+
730+ // Use shared helper to delete the endpoint
731+ var deleted = await DeleteMessagingEndpointAsync ( logger , config , botConfigurator ) ;
732+
733+ if ( ! deleted )
734+ {
735+ return ;
736+ }
737+
738+ logger . LogInformation ( "" ) ;
739+ logger . LogInformation ( "Endpoint cleanup completed successfully!" ) ;
740+ logger . LogInformation ( "" ) ;
741+ }
742+
666743 private static async Task < Agent365Config ? > LoadConfigAsync (
667744 FileInfo ? configFile ,
668745 ILogger < CleanupCommand > logger ,
0 commit comments