├── .gitattributes ├── .github └── workflows │ └── build-pipeline-01.yml ├── .gitignore ├── Greg.Xrm.Command.Core.TestSuite ├── Commands │ ├── Auth │ │ ├── CreateCommandExecutorTests.cs │ │ └── SelectCommandTest.cs │ ├── Column │ │ ├── CreateCommandExecutorTests.cs │ │ └── DeleteCommandExecutorTests.cs │ ├── Relationship │ │ └── CreatePolyCommandExecutorTests.cs │ ├── Solution │ │ ├── GetPublisherListCommandExecutorTest.cs │ │ └── GetPublisherListCommandTest.cs │ ├── Table │ │ ├── CreateCommandTests.cs │ │ ├── DeleteCommandExecutorTest.cs │ │ ├── DeleteCommandTest.cs │ │ └── ExportMetadataCommandTest.cs │ ├── UnifiedRouting │ │ ├── GetAgentStatusCommandExecutorTest.cs │ │ ├── GetAgentStatusCommandTest.cs │ │ ├── GetQueueStatusCommandExecutorTest.cs │ │ └── GetQueueStatusCommandTest.cs │ ├── Utility.cs │ └── WebResources │ │ └── PushLogic │ │ ├── FolderResolverTest.cs │ │ ├── Utils.cs │ │ └── WebResourcesFileResolverTest.cs ├── ExtensionsTest.cs ├── GlobalUsings.cs ├── Greg.Xrm.Command.Core.TestSuite.csproj └── Parsing │ └── CommandLineParserTest.cs ├── Greg.Xrm.Command.Core ├── Commands │ ├── Auth │ │ ├── CreateCommand.cs │ │ ├── CreateCommandExecutor.cs │ │ ├── DeleteCommand.cs │ │ ├── DeleteCommandExecutor.cs │ │ ├── Help.cs │ │ ├── ListCommand.cs │ │ ├── ListCommandExecutor.cs │ │ ├── PingCommand.cs │ │ ├── PingCommandExecutor.cs │ │ ├── RenameCommand.cs │ │ ├── RenameCommandExecutor.cs │ │ ├── SelectCommand.cs │ │ └── SelectCommandExecutor.cs │ ├── Column │ │ ├── Builders │ │ │ ├── AttributeMetadataBuilderBase.cs │ │ │ ├── AttributeMetadataBuilderBoolean.cs │ │ │ ├── AttributeMetadataBuilderDateTime.cs │ │ │ ├── AttributeMetadataBuilderDecimal.cs │ │ │ ├── AttributeMetadataBuilderFactory.cs │ │ │ ├── AttributeMetadataBuilderInteger.cs │ │ │ ├── AttributeMetadataBuilderMemo.cs │ │ │ ├── AttributeMetadataBuilderMoney.cs │ │ │ ├── AttributeMetadataBuilderNumericBase.cs │ │ │ ├── AttributeMetadataBuilderPicklist.cs │ │ │ ├── AttributeMetadataBuilderString.cs │ │ │ ├── IAttributeMetadataBuilder.cs │ │ │ └── IAttributeMetadataBuilderFactory.cs │ │ ├── CreateCommand.cs │ │ ├── CreateCommandExecutor.cs │ │ ├── CreateCommandResult.cs │ │ ├── DeleteCommand.cs │ │ ├── DeleteCommandExecutor.cs │ │ ├── ExportMetadataCommand.cs │ │ ├── ExportMetadataCommandExecutor.cs │ │ ├── GetDependenciesCommand.cs │ │ ├── GetDependenciesCommandExecutor.cs │ │ ├── Help.cs │ │ ├── SetSeedCommand.cs │ │ └── SetSeedCommandExecutor.cs │ ├── Forms │ │ ├── CleanCommand.cs │ │ ├── CleanCommandExecutor.cs │ │ ├── Help.cs │ │ └── Model │ │ │ ├── Form.cs │ │ │ ├── FormType.cs │ │ │ └── IFormRepository.cs │ ├── Help │ │ ├── HelpCommand.cs │ │ ├── HelpCommandExecutor.cs │ │ ├── HelpGeneratorForVerb.cs │ │ ├── HelpGeneratorGeneric.cs │ │ └── HelpGeneratorInMarkdown.cs │ ├── History │ │ ├── ClearCommand.cs │ │ ├── ClearCommandExecutor.cs │ │ ├── GetCommand.cs │ │ ├── GetCommandExecutor.cs │ │ ├── Help.cs │ │ ├── SetLengthCommand.cs │ │ └── SetLengthCommandExecutor.cs │ ├── Plugin │ │ ├── Help.cs │ │ ├── InstallCommand.cs │ │ ├── InstallCommandExecutor.cs │ │ ├── ListCommand.cs │ │ ├── ListCommandExecutor.cs │ │ ├── UninstallCommand.cs │ │ └── UninstallCommandExecutor.cs │ ├── Projects │ │ ├── Help.cs │ │ ├── InfoProjectCommand.cs │ │ ├── InfoProjectCommandExecutor.cs │ │ ├── InitProjectCommand.cs │ │ ├── InitProjectCommandExecutor.cs │ │ ├── ResumeProjectCommand.cs │ │ ├── ResumeProjectCommandExecutor.cs │ │ ├── SuspendProjectCommand.cs │ │ └── SuspendProjectCommandExecutor.cs │ ├── Relationship │ │ ├── AddPolyCommand.cs │ │ ├── AddPolyCommandExecutor.cs │ │ ├── CreateN1Command.cs │ │ ├── CreateN1CommandExecutor.cs │ │ ├── CreateNNCommand.cs │ │ ├── CreateNNCommandExecutor.cs │ │ ├── CreateNNExplicitStrategy.cs │ │ ├── CreateNNImplicitStrategy.cs │ │ ├── CreatePolyCommand.cs │ │ ├── CreatePolyCommandExecutor.cs │ │ ├── DeleteCommand.cs │ │ ├── DeleteCommandExecutor.cs │ │ ├── Help1.cs │ │ ├── Help2.cs │ │ ├── Help3.cs │ │ ├── ICreateNNStrategy.cs │ │ ├── RemovePolyCommand.cs │ │ └── RemovePolyCommandExecutor.cs │ ├── Settings │ │ ├── CreateCommand.cs │ │ ├── CreateCommandExecutor.cs │ │ ├── ExportCommand.cs │ │ ├── ExportCommandExecutor.cs │ │ ├── Help.cs │ │ ├── ImportCommand.cs │ │ ├── ImportCommandExecutor.cs │ │ ├── Imports │ │ │ ├── IImportAction.cs │ │ │ ├── IImportStrategy.cs │ │ │ ├── IImportStrategyFactory.cs │ │ │ ├── ImportActionSetAppValue.cs │ │ │ ├── ImportActionSetDefaultValue.cs │ │ │ ├── ImportActionSetEnvironmentValue.cs │ │ │ ├── ImportStrategyFactory.cs │ │ │ ├── ImportStrategyFromExcel.cs │ │ │ └── ImportStrategyFromJson.cs │ │ ├── Model │ │ │ ├── AppSetting.cs │ │ │ ├── IAppSettingRepository.cs │ │ │ ├── IOrganizationSettingRepository.cs │ │ │ ├── ISettingDefinitionRepository.cs │ │ │ ├── JsonSettingDefinition.cs │ │ │ ├── OrganizationSetting.cs │ │ │ ├── SettingDefinition.cs │ │ │ ├── SettingDefinitionDataType.cs │ │ │ ├── SettingDefinitionOverridableLevel.cs │ │ │ └── SettingDefinitionReleaseLevel.cs │ │ ├── SetValueCommand.cs │ │ ├── SetValueCommandExecutor.cs │ │ ├── UpdateCommand.cs │ │ └── UpdateCommandExecutor.cs │ ├── Solution │ │ ├── ComponentGetDependenciesCommand.cs │ │ ├── ComponentGetDependenciesCommandExecutor.cs │ │ ├── CreateCommand.cs │ │ ├── CreateCommandExecutor.cs │ │ ├── CreateCommandResult.cs │ │ ├── DeleteCommand.cs │ │ ├── DeleteCommandExecutor.cs │ │ ├── GetDefaultCommand.cs │ │ ├── GetDefaultCommandExecutor.cs │ │ ├── GetPublisherListCommand.cs │ │ ├── GetPublisherListExecutor.cs │ │ ├── Help1.cs │ │ ├── Help2.cs │ │ ├── SetDefaultCommand.cs │ │ └── SetDefaultCommandExecutor.cs │ ├── Table │ │ ├── Builders │ │ │ ├── AttributeMetadataScriptBuilderBase.cs │ │ │ ├── AttributeMetadataScriptBuilderBoolean.cs │ │ │ ├── AttributeMetadataScriptBuilderDateTime.cs │ │ │ ├── AttributeMetadataScriptBuilderDecimal.cs │ │ │ ├── AttributeMetadataScriptBuilderFactory.cs │ │ │ ├── AttributeMetadataScriptBuilderInteger.cs │ │ │ ├── AttributeMetadataScriptBuilderMemo.cs │ │ │ ├── AttributeMetadataScriptBuilderMoney.cs │ │ │ ├── AttributeMetadataScriptBuilderPicklist.cs │ │ │ ├── AttributeMetadataScriptBuilderString.cs │ │ │ ├── CommandArgsConstants.cs │ │ │ ├── IAttributeMetadataScriptBuilder.cs │ │ │ ├── IAttributeMetadataScriptBuilderFactory.cs │ │ │ └── TableMetadataScriptBuilder.cs │ │ ├── CreateCommand.cs │ │ ├── CreateCommandExecutor.cs │ │ ├── DeleteCommand.cs │ │ ├── DeleteCommandExecutor.cs │ │ ├── ExportMetadata │ │ │ ├── DataverseColumn.cs │ │ │ ├── ExcelMetadataSheetWriterColumns.cs │ │ │ ├── ExcelMetadataSheetWriterTable.cs │ │ │ ├── ExportMetadataCommand.cs │ │ │ ├── ExportMetadataCommandExecutor.cs │ │ │ ├── ExportMetadataStrategyExcel.cs │ │ │ ├── ExportMetadataStrategyFactory.cs │ │ │ ├── ExportMetadataStrategyJson.cs │ │ │ ├── IExcelMetadataSheetWriter.cs │ │ │ ├── IExportMetadataStrategy.cs │ │ │ └── IExportMetadataStrategyFactory.cs │ │ ├── Help.cs │ │ ├── Migration │ │ │ ├── IMigrationAction.cs │ │ │ ├── MigrationActionFullTable.cs │ │ │ ├── MigrationActionLog.cs │ │ │ ├── MigrationActionTableWithoutColumn.cs │ │ │ ├── MigrationActionUpdateTableColumn.cs │ │ │ ├── MigrationStrategyBuilder.cs │ │ │ ├── MigrationStrategyResult.cs │ │ │ ├── MissingTableCache.cs │ │ │ ├── SecurityTables.cs │ │ │ ├── TableGraphBuilder.cs │ │ │ └── TableModel.cs │ │ ├── ScriptCommand.cs │ │ ├── ScriptCommandExecutor.cs │ │ ├── TableDefineMigrationStrategyCommand.cs │ │ ├── TableDefineMigrationStrategyCommandExecutor.cs │ │ ├── TablePrintMermaidCommand.cs │ │ └── TablePrintMermaidCommandExecutor.cs │ ├── UnifiedRouting │ │ ├── GetAgentStatusCommand.cs │ │ ├── GetAgentStatusCommandExecutor.cs │ │ ├── GetQueueStatusCommand.cs │ │ ├── GetQueueStatusCommandExecutor.cs │ │ ├── Help.cs │ │ ├── Model │ │ │ ├── AgentStatus.cs │ │ │ ├── AgentStatusHistory.cs │ │ │ ├── Presence.cs │ │ │ ├── Queue.cs │ │ │ └── Systemuser.cs │ │ └── Repository │ │ │ └── AgentStatusHistoryRepository.cs │ ├── Views │ │ ├── CloneCommand.cs │ │ ├── CloneCommandExecutor.cs │ │ ├── DeleteCommand.cs │ │ ├── DeleteCommandExecutor.cs │ │ ├── GetCommand.cs │ │ ├── GetCommandExecutor.cs │ │ ├── Help.cs │ │ ├── ListCommand.cs │ │ ├── ListCommandExecutor.cs │ │ ├── Model │ │ │ ├── IViewRetrieverService.cs │ │ │ ├── Replicator.cs │ │ │ ├── ViewRetrieverService.cs │ │ │ └── XmlNodeListEmpty.cs │ │ ├── RenameCommand.cs │ │ ├── RenameCommandExecutor.cs │ │ ├── ReplicateCommand.cs │ │ └── ReplicateCommandExecutor.cs │ └── WebResources │ │ ├── AddReferenceCommand.cs │ │ ├── AddReferenceCommandExecutor.cs │ │ ├── ApplyIconsCommand.cs │ │ ├── ApplyIconsCommandExecutor.cs │ │ ├── ApplyIconsRules │ │ ├── IIconFinder.cs │ │ ├── IconFinder.cs │ │ ├── RuleToMatchImagesFolder.cs │ │ ├── RuleToMatchNameEnding.cs │ │ ├── RuleToMatchTableName.cs │ │ └── RuleToMatchTableNameWithPrefix.cs │ │ ├── Help.cs │ │ ├── InitCommand.cs │ │ ├── InitCommandExecutor.cs │ │ ├── JsCreateCommand.cs │ │ ├── JsCreateCommandExecutor.cs │ │ ├── JsResetTemplateCommand.cs │ │ ├── JsResetTemplateCommandExecutor.cs │ │ ├── JsSetTemplateCommand.cs │ │ ├── JsSetTemplateCommandExecutor.cs │ │ ├── ProjectFile │ │ ├── IWebResourceProjectFileRepository.cs │ │ ├── ProjectFileV1.cs │ │ └── WebResourceProjectFileRepository.cs │ │ ├── PushCommand.cs │ │ ├── PushCommandExecutor.cs │ │ ├── PushLogic │ │ ├── FolderResolver.cs │ │ ├── IFolderResolver.cs │ │ ├── IPublishXmlBuilder.cs │ │ ├── IWebResourceFilesResolver.cs │ │ ├── PublishXmlBuilder.cs │ │ ├── WebResourceFile.cs │ │ ├── WebResourceFilesResolver.cs │ │ └── WebResourceFolders.cs │ │ ├── SetEnvImageCommand.cs │ │ ├── SetEnvImageCommandExecutor.cs │ │ └── Templates │ │ ├── IJsTemplateManager.cs │ │ └── JsTemplateManager.cs ├── CommonExtensions.cs ├── ComponentType.cs ├── Greg.Xrm.Command.Core.csproj ├── IoCModule.cs ├── Model │ ├── DataverseColumnAttribute.cs │ ├── Dependency.cs │ ├── DependencyList.cs │ ├── IDependencyRepository.cs │ ├── IProcessTriggerRepository.cs │ ├── ISavedQueryRepository.cs │ ├── ISolutionRepository.cs │ ├── ITemporarySolution.cs │ ├── IUserQueryRepository.cs │ ├── IWebResourceRepository.cs │ ├── IWorkflowRepository.cs │ ├── ProcessTrigger.cs │ ├── SavedQuery.cs │ ├── Solution.cs │ ├── SolutionZipArchive.cs │ ├── TableView.cs │ ├── TemporarySolution.cs │ ├── UserQuery.cs │ ├── WebResource.cs │ ├── WebResourceType.cs │ └── Workflow.cs ├── Parsing │ ├── CommandParser.cs │ ├── CommandRegistry.cs │ ├── CommandRunArgs.cs │ ├── CommandTree.cs │ ├── ICommandLineArguments.cs │ ├── ICommandParser.cs │ └── ICommandRegistry.cs ├── Properties │ ├── Resources.Designer.cs │ └── Resources.resx ├── README.md └── Services │ ├── AttributeDeletion │ ├── AttributeDeletionException.cs │ ├── AttributeDeletionService.cs │ ├── AttributeDeletionStrategyBase.cs │ ├── AttributeDeletionStrategyForCharts.cs │ ├── AttributeDeletionStrategyForForms.cs │ ├── AttributeDeletionStrategyForMappings.cs │ ├── AttributeDeletionStrategyForPluginStepImages.cs │ ├── AttributeDeletionStrategyForPluginSteps.cs │ ├── AttributeDeletionStrategyForRelationships.cs │ ├── AttributeDeletionStrategyForViews.cs │ ├── AttributeDeletionStrategyForWorkflows.cs │ ├── IAttributeDeletionService.cs │ └── IAttributeDeletionStrategy.cs │ ├── CommandHistory │ ├── CommandHistory.cs │ ├── HistoryTracker.cs │ └── IHistoryTracker.cs │ ├── ComponentResolvers │ ├── ComponentResolverFactory.cs │ ├── IComponentResolver.cs │ ├── IComponentResolverFactory.cs │ ├── ResolverByQuery.cs │ └── ResolverForSystemForms.cs │ ├── Connection │ ├── OrganizationServiceRepository.cs │ ├── TokenCache.cs │ └── TokenDefinition.cs │ ├── ExcelExtensions.cs │ ├── FolderTree.cs │ ├── Graphs │ ├── ConsistencyException.cs │ ├── Cycle.cs │ ├── DirectedArc.cs │ ├── DirectedGraph.cs │ ├── DirectedNode.cs │ ├── IDirectedArc.cs │ ├── IDirectedNode.cs │ ├── IKey.cs │ └── INodeContent.cs │ ├── IStorage.cs │ ├── Output │ ├── OutputToConsole.cs │ └── OutputToMemory.cs │ ├── Plugin │ └── DependencyUtility.cs │ ├── Pluralization │ ├── PluralizationFactory.cs │ ├── PluralizationStrategy1033.cs │ ├── PluralizationStrategy1040.cs │ └── PluralizationStrategyIdentity.cs │ ├── Project │ ├── IPacxProjectRepository.cs │ ├── PacxProject.cs │ ├── PacxProjectDefinition.cs │ └── PacxProjectRepository.cs │ ├── Settings │ └── SettingsRepository.cs │ ├── StaticReflection.cs │ └── Storage.cs ├── Greg.Xrm.Command.Interfaces ├── CommandException.cs ├── CommandResult.cs ├── Extensions.cs ├── Greg.Xrm.Command.Interfaces.csproj ├── ICommandExecutor.cs ├── Logo_80.png ├── Model │ ├── CloneSettings.cs │ ├── EntityWrapper.cs │ └── IEntityWrapperInternal.cs ├── Parsing │ ├── Attributes │ │ ├── AliasAttribute.cs │ │ ├── CommandAttribute.cs │ │ └── OptionAttribute.cs │ ├── CommandDefinition.cs │ ├── ICanProvideUsageExample.cs │ ├── ICommandTree.cs │ ├── INamespaceHelper.cs │ ├── IReadOnlyCommandRegistry.cs │ ├── NamespaceHelper.cs │ ├── NamespaceHelperBase.cs │ ├── OptionDefinition.cs │ └── VerbNode.cs ├── README.md └── Services │ ├── Connection │ ├── ConnectionSetting.cs │ └── IOrganizationServiceRepository.cs │ ├── Encryption │ └── AesEncryption.cs │ ├── MarkdownWriter.cs │ ├── Output │ └── IOutput.cs │ ├── Pluralization │ ├── IPluralizationFactory.cs │ └── IPluralizationStrategy.cs │ └── Settings │ └── ISettingsRepository.cs ├── Greg.Xrm.Command.sln ├── Greg.Xrm.Command ├── AutoUpdater.cs ├── Bootstrapper.cs ├── CommandExecutorFactory.cs ├── CommandLineArguments.cs ├── Extensions.cs ├── Greg.Xrm.Command.csproj ├── ICommandExecutorFactory.cs ├── Program.cs ├── Properties │ ├── Resources.Designer.cs │ └── Resources.resx └── README.md ├── LICENSE.txt ├── Logo_80.png ├── README.md ├── ava_child.json ├── icon128.png └── sample └── SamplePlugin ├── SampleCommand.cs ├── SampleCommandExecutor.cs └── SamplePlugin.csproj /Greg.Xrm.Command.Core.TestSuite/Commands/Auth/CreateCommandExecutorTests.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services; 2 | using Greg.Xrm.Command.Services.Connection; 3 | using Greg.Xrm.Command.Services.Project; 4 | using Greg.Xrm.Command.Services.Settings; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Greg.Xrm.Command.Commands.Auth 8 | { 9 | [TestClass] 10 | public class CreateCommandExecutorTests 11 | { 12 | [TestMethod] 13 | [TestCategory("Integration")] 14 | public void Integration_Execute01() 15 | { 16 | var storage = new Storage(); 17 | var output = new OutputToMemory(); 18 | var settingsRepository = new SettingsRepository(storage); 19 | var pacxProjectRepository = new PacxProjectRepository(Mock.Of>()); 20 | var repository = new OrganizationServiceRepository(settingsRepository, pacxProjectRepository); 21 | var executor = new CreateCommandExecutor(repository, output); 22 | 23 | var command = new CreateCommand 24 | { 25 | Name = "test", 26 | ConnectionString = "" 27 | }; 28 | 29 | var task = executor.ExecuteAsync(command, CancellationToken.None); 30 | 31 | task.Wait(); 32 | 33 | Assert.IsNotNull(output.ToString()); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/Auth/SelectCommandTest.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Commands.Auth; 2 | 3 | namespace Greg.Xrm.Command.Commands.Delete 4 | { 5 | [TestClass] 6 | public class SelectCommandTest 7 | { 8 | [TestMethod] 9 | public void ParseWithLongNameShouldWork() 10 | { 11 | var command = Utility.TestParseCommand("auth", "select", "--name", "Conn1"); 12 | Assert.AreEqual("Conn1", command.Name); 13 | } 14 | 15 | 16 | [TestMethod] 17 | public void ParseWithShortNameShouldWork() 18 | { 19 | var command = Utility.TestParseCommand("auth", "select", "-n", "Conn1"); 20 | Assert.AreEqual("Conn1", command.Name); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/Column/CreateCommandExecutorTests.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Commands.Column.Builders; 2 | using Greg.Xrm.Command.Services; 3 | using Greg.Xrm.Command.Services.Connection; 4 | using Greg.Xrm.Command.Services.Project; 5 | using Greg.Xrm.Command.Services.Settings; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Xrm.Sdk.Metadata; 8 | 9 | namespace Greg.Xrm.Command.Commands.Column 10 | { 11 | [TestClass] 12 | public class CreateCommandExecutorTests 13 | { 14 | [TestMethod] 15 | [TestCategory("Integration")] 16 | public void Integration_CreateGlobalOptionSetField() 17 | { 18 | var storage = new Storage(); 19 | var output = new OutputToMemory(); 20 | var settingsRepository = new SettingsRepository(storage); 21 | var pacxProjectRepository = new PacxProjectRepository(Mock.Of>()); 22 | var repository = new OrganizationServiceRepository(settingsRepository, pacxProjectRepository); 23 | 24 | var attributeMetadataBuilderFactory = new AttributeMetadataBuilderFactory(); 25 | 26 | var command = new CreateCommand 27 | { 28 | EntityName = "ava_pippo", 29 | AttributeType = AttributeTypeCode.Picklist, 30 | SchemaName = "ava_test", 31 | DisplayName = "Test", 32 | GlobalOptionSetName = "ava_riccardo", //"emailserverprofile_authenticationprotocol", 33 | SolutionName = "master" 34 | }; 35 | 36 | 37 | var executor = new CreateCommandExecutor(output, repository, attributeMetadataBuilderFactory); 38 | 39 | executor.ExecuteAsync(command, CancellationToken.None).Wait(); 40 | 41 | Assert.IsNotNull(output.ToString()); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/Relationship/CreatePolyCommandExecutorTests.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services; 2 | using Greg.Xrm.Command.Services.Connection; 3 | using Greg.Xrm.Command.Services.Project; 4 | using Greg.Xrm.Command.Services.Settings; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Greg.Xrm.Command.Commands.Relationship 8 | { 9 | [TestClass] 10 | public class CreatePolyCommandExecutorTests 11 | { 12 | [TestMethod] 13 | [TestCategory("Integration")] 14 | public void Integration_CreateGlobalOptionSetField() 15 | { 16 | var storage = new Storage(); 17 | var output = new OutputToMemory(); 18 | var settingsRepository = new SettingsRepository(storage); 19 | var pacxProjectRepository = new PacxProjectRepository(Mock.Of>()); 20 | var repository = new OrganizationServiceRepository(settingsRepository, pacxProjectRepository); 21 | 22 | 23 | var command = new CreatePolyCommand 24 | { 25 | ChildTable = "ava_fundedemployee", 26 | Parents = "ava_solutionarea,ava_practice,ava_clientbusinessgroup,ava_crossstructure", 27 | LookupAttributeDisplayName = "Funded By", 28 | RelationshipNameSuffix = "poly", 29 | SolutionName = "cop_solutioning" 30 | }; 31 | 32 | 33 | var executor = new CreatePolyCommandExecutor(output, repository); 34 | 35 | var task = executor.ExecuteAsync(command, CancellationToken.None); 36 | Assert.IsNotNull(task); 37 | 38 | task.Wait(); 39 | 40 | Assert.IsFalse(task.IsFaulted, task.Exception!.Message); 41 | 42 | var result = task.Result; 43 | 44 | Assert.IsTrue(result.IsSuccess, result.ErrorMessage); 45 | Assert.IsNull(result.Exception, result.Exception?.Message); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/Solution/GetPublisherListCommandExecutorTest.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services; 2 | using Greg.Xrm.Command.Services.Connection; 3 | using Greg.Xrm.Command.Services.Project; 4 | using Greg.Xrm.Command.Services.Settings; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Greg.Xrm.Command.Commands.Solution 8 | { 9 | [TestClass] 10 | public class GetPublisherListCommandExecutorTest 11 | { 12 | [TestMethod] 13 | [TestCategory("Integration")] 14 | public async Task TestQuery() 15 | { 16 | var storage = new Storage(); 17 | var output = new OutputToConsole(); 18 | var settingsRepository = new SettingsRepository(storage); 19 | var pacxProjectRepository = new PacxProjectRepository(Mock.Of>()); 20 | var repository = new OrganizationServiceRepository(settingsRepository, pacxProjectRepository); 21 | 22 | 23 | var executor = new GetPublisherListExecutor(output, repository); 24 | 25 | var result = await executor.ExecuteAsync(new GetPublisherListCommand 26 | { 27 | Verbose = true 28 | }, new CancellationToken()); 29 | 30 | Assert.IsNotNull(result); 31 | Assert.IsTrue(result.IsSuccess, result.ErrorMessage); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/Solution/GetPublisherListCommandTest.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Solution 2 | { 3 | [TestClass] 4 | public class GetPublisherListCommandTest 5 | { 6 | [TestMethod] 7 | public void ParseWithLongNameShouldWork() 8 | { 9 | var command = Utility.TestParseCommand("solution", "getPublisherList", "--verbose"); 10 | 11 | 12 | Assert.IsTrue(command.Verbose); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/Table/CreateCommandTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | 3 | namespace Greg.Xrm.Command.Commands.Table 4 | { 5 | [TestClass] 6 | public class CreateCommandTests 7 | { 8 | [TestMethod] 9 | public void ShortNamesShouldBeResolvedProperly() 10 | { 11 | var command = Utility.TestParseCommand("create", "table", "-n", "Table1", "-s", "master"); 12 | 13 | Assert.AreEqual("Table1", command.DisplayName); 14 | Assert.IsNull(command.DisplayCollectionName); 15 | Assert.IsNull(command.Description); 16 | Assert.IsNull(command.SchemaName); 17 | Assert.AreEqual("master", command.SolutionName); 18 | Assert.AreEqual(OwnershipTypes.UserOwned, command.Ownership); 19 | Assert.IsFalse(command.IsActivity); 20 | Assert.IsFalse(command.IsAvailableOffline); 21 | Assert.IsFalse(command.IsValidForQueue); 22 | Assert.IsFalse(command.HasNotes); 23 | Assert.IsFalse(command.HasFeedback); 24 | Assert.IsTrue(command.IsAuditEnabled); 25 | Assert.IsNull(command.PrimaryAttributeSchemaName); 26 | Assert.IsNull(command.PrimaryAttributeDisplayName); 27 | Assert.IsNull(command.PrimaryAttributeDescription); 28 | Assert.IsNull(command.PrimaryAttributeAutoNumber); 29 | Assert.IsNull(command.PrimaryAttributeMaxLength); 30 | Assert.IsNull(command.PrimaryAttributeRequiredLevel); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/Table/DeleteCommandExecutorTest.cs: -------------------------------------------------------------------------------- 1 |  2 | using Greg.Xrm.Command.Services.Connection; 3 | using Microsoft.PowerPlatform.Dataverse.Client; 4 | using Microsoft.Xrm.Sdk; 5 | using Microsoft.Xrm.Sdk.Messages; 6 | 7 | namespace Greg.Xrm.Command.Commands.Table 8 | { 9 | [TestClass] 10 | public class DeleteCommandExecutorTest 11 | { 12 | [TestMethod] 13 | public void Test1() 14 | { 15 | var tableName = "table"; 16 | OrganizationRequest? requestToServer = null; 17 | 18 | var output = new OutputToMemory(); 19 | 20 | var organizationServiceRepository = new Mock(); 21 | var organizationService = new Mock(); 22 | organizationService.Setup(x => x.ExecuteAsync(It.IsAny())) 23 | .Callback(x => requestToServer = x) 24 | .ReturnsAsync(new DeleteEntityResponse()); 25 | 26 | organizationServiceRepository 27 | .Setup(organizationServiceRepository => organizationServiceRepository.GetCurrentConnectionAsync()) 28 | .ReturnsAsync(organizationService.Object); 29 | 30 | var executor = new DeleteCommandExecutor(output, organizationServiceRepository.Object); 31 | 32 | executor.ExecuteAsync(new DeleteCommand 33 | { 34 | SchemaName = tableName 35 | }, new CancellationToken()).Wait(); 36 | 37 | organizationServiceRepository.Verify(x => x.GetCurrentConnectionAsync(), Times.Once); 38 | organizationService.Verify(x => x.ExecuteAsync(It.IsAny()), Times.Once); 39 | 40 | Assert.IsNotNull(requestToServer); 41 | Assert.IsTrue(requestToServer is DeleteEntityRequest); 42 | 43 | var r = (DeleteEntityRequest)requestToServer; 44 | Assert.IsNotNull(r.LogicalName); 45 | Assert.AreEqual(tableName, r.LogicalName); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/Table/DeleteCommandTest.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Table 2 | { 3 | [TestClass] 4 | public class DeleteCommandTest 5 | { 6 | [TestMethod] 7 | public void ParseWithLongNameShouldWork() 8 | { 9 | var command = Utility.TestParseCommand("delete", "table", "--name", "Table1"); 10 | Assert.AreEqual("Table1", command.SchemaName); 11 | } 12 | 13 | 14 | [TestMethod] 15 | public void ParseWithShortNameShouldWork() 16 | { 17 | var command = Utility.TestParseCommand("delete", "table", "-n", "Table1"); 18 | Assert.AreEqual("Table1", command.SchemaName); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/Table/ExportMetadataCommandTest.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Commands.Table.ExportMetadata; 2 | using Greg.Xrm.Command.Services; 3 | using Greg.Xrm.Command.Services.Connection; 4 | using Greg.Xrm.Command.Services.Project; 5 | using Greg.Xrm.Command.Services.Settings; 6 | using Microsoft.Extensions.Logging; 7 | using OfficeOpenXml; 8 | using System.Diagnostics; 9 | 10 | namespace Greg.Xrm.Command.Commands.Table 11 | { 12 | [TestClass] 13 | public class ExportMetadataCommandTest 14 | { 15 | [TestMethod] 16 | [TestCategory("Integration")] 17 | public void Integration_ExecuteExportExcel() 18 | { 19 | ExcelPackage.LicenseContext = LicenseContext.NonCommercial; 20 | 21 | var storage = new Storage(); 22 | var output = new OutputToMemory(); 23 | var settingsRepository = new SettingsRepository(storage); 24 | var pacxProjectRepository = new PacxProjectRepository(Mock.Of>()); 25 | var repository = new OrganizationServiceRepository(settingsRepository, pacxProjectRepository); 26 | 27 | var exportMetadataStrategyFactory = new ExportMetadataStrategyFactory(output); 28 | 29 | var command = new ExportMetadataCommand 30 | { 31 | Format = ExportMetadataFormat.Excel, 32 | OutputFilePath = @"C:\temp\", 33 | TableSchemaName = "ava_practice", 34 | AutoOpenFile = true, 35 | }; 36 | 37 | 38 | var executor = new ExportMetadataCommandExecutor(output, repository, exportMetadataStrategyFactory); 39 | 40 | executor.ExecuteAsync(command, CancellationToken.None).Wait(); 41 | 42 | 43 | var outputText = output.ToString(); 44 | 45 | Debug.WriteLine(outputText); 46 | 47 | Assert.IsNotNull(outputText); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/UnifiedRouting/GetAgentStatusCommandExecutorTest.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services; 2 | using Greg.Xrm.Command.Services.Connection; 3 | using Greg.Xrm.Command.Services.Project; 4 | using Greg.Xrm.Command.Services.Settings; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Greg.Xrm.Command.Commands.UnifiedRouting 8 | { 9 | [TestClass] 10 | public class GetAgentStatusCommandExecutorTest 11 | { 12 | [TestMethod] 13 | [TestCategory("Integration")] 14 | public async Task TestQuery() 15 | { 16 | var agentPrimaryEmail = "francesco.catino@avanade.com"; 17 | var storage = new Storage(); 18 | var output = new OutputToConsole(); 19 | var settingsRepository = new SettingsRepository(storage); 20 | var pacxProjectRepository = new PacxProjectRepository(Mock.Of>()); 21 | var repository = new OrganizationServiceRepository(settingsRepository, pacxProjectRepository); 22 | 23 | 24 | var executor = new GetAgentStatusCommandExecutor(output, repository); 25 | 26 | var result = await executor.ExecuteAsync(new GetAgentStatusCommand 27 | { 28 | AgentPrimaryEmail = agentPrimaryEmail, 29 | DateTimeFilter = "28/11/2023 11:00" 30 | }, new CancellationToken()); 31 | 32 | Assert.IsNotNull(result); 33 | Assert.IsTrue(result.IsSuccess, result.ErrorMessage); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/UnifiedRouting/GetAgentStatusCommandTest.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.UnifiedRouting 2 | { 3 | [TestClass] 4 | public class GetAgentStatusCommandTest 5 | { 6 | [TestMethod] 7 | public void ParseWithLongNameShouldWork() 8 | { 9 | var command = Utility.TestParseCommand("unifiedrouting", "agentStatus", "--agentPrimaryEmail", "francesco.catino@avanade.com"); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/UnifiedRouting/GetQueueStatusCommandExecutorTest.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services; 2 | using Greg.Xrm.Command.Services.Connection; 3 | using Greg.Xrm.Command.Services.Project; 4 | using Greg.Xrm.Command.Services.Settings; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Greg.Xrm.Command.Commands.UnifiedRouting 8 | { 9 | [TestClass] 10 | public class GetQueueStatusCommandExecutorTest 11 | { 12 | [TestMethod] 13 | [TestCategory("Integration")] 14 | public async Task TestQuery() 15 | { 16 | var queue = "QUEUENAME"; 17 | var storage = new Storage(); 18 | var output = new OutputToConsole(); 19 | var settingsRepository = new SettingsRepository(storage); 20 | var pacxProjectRepository = new PacxProjectRepository(Mock.Of>()); 21 | var repository = new OrganizationServiceRepository(settingsRepository, pacxProjectRepository); 22 | 23 | 24 | var executor = new GetQueueStatusCommandExecutor(output, repository); 25 | 26 | var result = await executor.ExecuteAsync(new GetQueueStatusCommand 27 | { 28 | Queue = queue, 29 | DateTimeFilter = "28/11/2023 11:00" 30 | }, new CancellationToken()); 31 | 32 | Assert.IsNotNull(result); 33 | Assert.IsTrue(result.IsSuccess, result.ErrorMessage); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/UnifiedRouting/GetQueueStatusCommandTest.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.UnifiedRouting 2 | { 3 | [TestClass] 4 | public class GetQueueStatusCommandTest 5 | { 6 | [TestMethod] 7 | public void ParseWithLongNameShouldWork() 8 | { 9 | var command = Utility.TestParseCommand("unifiedrouting", "queueStatus", "--queue", "QUEUENAME"); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/Utility.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Commands.Auth; 2 | using Greg.Xrm.Command.Parsing; 3 | using Greg.Xrm.Command.Services; 4 | using Microsoft.Extensions.Logging.Abstractions; 5 | 6 | namespace Greg.Xrm.Command.Commands 7 | { 8 | static class Utility 9 | { 10 | public static TCommand TestParseCommand(params string[] args) 11 | { 12 | var log = NullLogger.Instance; 13 | var output = new OutputToMemory(); 14 | var storage = new Storage(); 15 | 16 | var registry = new CommandRegistry(log, output, storage); 17 | registry.InitializeFromAssembly(typeof(ListCommand).Assembly); 18 | 19 | var parser = new CommandParser(new OutputToMemory(), registry); 20 | 21 | var (parseResult, _) = parser.Parse(args); 22 | 23 | Assert.IsNotNull(parseResult, $"Parsing of arguments <{Concatenate(args)}> returned no command"); 24 | Assert.AreEqual(typeof(TCommand), parseResult.GetType(), $"On arguments <{Concatenate(args)}> the expected command type is '{typeof(TCommand).FullName}', actual is '{parseResult.GetType().FullName}'"); 25 | 26 | var command = (TCommand)parseResult; 27 | return command; 28 | } 29 | 30 | 31 | public static string Concatenate(string[] args) 32 | { 33 | return string.Join(" ", args.Select(x => WrapInQuotes(x))); 34 | } 35 | 36 | public static string WrapInQuotes(string x) 37 | { 38 | if (string.IsNullOrWhiteSpace(x)) return string.Empty; 39 | if (x.Contains(' ')) return $"\"{x}\""; 40 | return x; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Commands/WebResources/PushLogic/Utils.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.WebResources.PushLogic 2 | { 3 | public static class Utils 4 | { 5 | public static string CreateTempFolder() 6 | { 7 | var temp = Path.Combine(Path.GetTempPath(), "PACX-" + DateTime.Now.Ticks); 8 | CreateFolder(temp); 9 | return temp; 10 | } 11 | public static string CreateLocalTempFolder() 12 | { 13 | var temp = Path.Combine(Environment.CurrentDirectory, "PACX-" + DateTime.Now.Ticks); 14 | CreateFolder(temp); 15 | return temp; 16 | } 17 | 18 | public static void CreateFolder(string root, string? path = null) 19 | { 20 | if (path != null) 21 | { 22 | root = Path.Combine(root, path); 23 | } 24 | 25 | Directory.CreateDirectory(root); 26 | } 27 | 28 | public static void CreateFile(string root, string filePath, string? content = "") 29 | { 30 | var fullPath = Path.Combine(root, filePath); 31 | Directory.CreateDirectory(Path.GetDirectoryName(fullPath) ?? string.Empty); 32 | File.WriteAllText(fullPath, content); 33 | } 34 | 35 | public static void DeleteFolder(string root, string ? path = null) 36 | { 37 | if (path != null) 38 | { 39 | root = Path.Combine(root, path); 40 | } 41 | 42 | Directory.Delete(root, true); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/ExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | 3 | namespace Greg.Xrm.Command 4 | { 5 | [TestClass] 6 | public class ExtensionsTest 7 | { 8 | [TestMethod] 9 | public void SplitNameInPartsByCapitalLettersShouldWork() 10 | { 11 | Assert.AreEqual("Is BPF Entity", "IsBPFEntity".SplitNameInPartsByCapitalLetters()); 12 | Assert.AreEqual("asdasdasd", "asdasdasd".SplitNameInPartsByCapitalLetters()); 13 | Assert.AreEqual("asdas dasd", "asdas dasd".SplitNameInPartsByCapitalLetters()); 14 | Assert.AreEqual("asdas da Sd", "asdas daSd".SplitNameInPartsByCapitalLetters()); 15 | } 16 | 17 | 18 | [TestMethod] 19 | public void GenerateKeyIv() 20 | { 21 | var key = RandomNumberGenerator.GetBytes(32); 22 | var keyString = Convert.ToBase64String(key); 23 | 24 | var iv = RandomNumberGenerator.GetBytes(16); 25 | var ivString = Convert.ToBase64String(iv); 26 | 27 | Assert.IsNotNull(keyString); 28 | Assert.IsNotNull(ivString); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | global using Greg.Xrm.Command.Services.Output; 3 | global using Moq; 4 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Greg.Xrm.Command.Core.TestSuite.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | Greg.Xrm.Command 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core.TestSuite/Parsing/CommandLineParserTest.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Commands.Auth; 2 | using Greg.Xrm.Command.Services; 3 | using Microsoft.ApplicationInsights; 4 | using Microsoft.Extensions.Logging.Abstractions; 5 | 6 | namespace Greg.Xrm.Command.Parsing 7 | { 8 | [TestClass] 9 | public class CommandLineParserTest 10 | { 11 | [TestMethod] 12 | public void AuthListShouldBeResolvedProperly() 13 | { 14 | var log = NullLogger.Instance; 15 | var output = new OutputToMemory(); 16 | var storage = new Storage(); 17 | 18 | var registry = new CommandRegistry(log, output, storage); 19 | registry.InitializeFromAssembly(typeof(ListCommand).Assembly); 20 | 21 | var parser = new CommandParser(new OutputToMemory(), registry); 22 | 23 | var command = parser.Parse("auth", "list"); 24 | 25 | Assert.IsNotNull(command); 26 | Assert.AreEqual(typeof(ListCommand), command.GetType()); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Auth/CreateCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Auth 4 | { 5 | [Command("auth", "create", HelpText = "Create and store authentication profiles on this computer. Can be also used to update an existing authentication profile.")] 6 | public class CreateCommand 7 | { 8 | [Option("name", "n", HelpText = "The name you want to give to this authentication profile (maximum 30 characters).")] 9 | [Required] 10 | public string? Name { get; set; } 11 | 12 | [Option("conn", "cs", HelpText = "The [connection string](https://learn.microsoft.com/en-us/power-apps/developer/data-platform/xrm-tooling/use-connection-strings-xrm-tooling-connect) that will be used to connect to the dataverse.")] 13 | [Required] 14 | public string? ConnectionString { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Auth/CreateCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Connection; 2 | using Greg.Xrm.Command.Services.Output; 3 | using Microsoft.PowerPlatform.Dataverse.Client.Utils; 4 | 5 | namespace Greg.Xrm.Command.Commands.Auth 6 | { 7 | public class CreateCommandExecutor : ICommandExecutor 8 | { 9 | private readonly IOrganizationServiceRepository organizationServiceRepository; 10 | private readonly IOutput output; 11 | 12 | public CreateCommandExecutor( 13 | IOrganizationServiceRepository organizationServiceRepository, 14 | IOutput output) 15 | { 16 | this.organizationServiceRepository = organizationServiceRepository; 17 | this.output = output; 18 | } 19 | 20 | public async Task ExecuteAsync(CreateCommand command, CancellationToken cancellationToken) 21 | { 22 | if (string.IsNullOrWhiteSpace(command.Name)) 23 | { 24 | throw new CommandException(CommandException.CommandRequiredArgumentNotProvided, "You must specify the name to be given to the authentication profile"); 25 | } 26 | if (string.IsNullOrWhiteSpace(command.ConnectionString)) 27 | { 28 | throw new CommandException(CommandException.CommandRequiredArgumentNotProvided, "You must specify the connectionString to be given to the authentication profile"); 29 | } 30 | 31 | try 32 | { 33 | await organizationServiceRepository.SetConnectionAsync(command.Name, command.ConnectionString); 34 | return CommandResult.Success(); 35 | } 36 | catch(DataverseConnectionException ex) 37 | { 38 | output.WriteLine(ex.Message, ConsoleColor.Red); 39 | if (ex.InnerException != null) 40 | { 41 | output.WriteLine(" " + ex.InnerException.Message, ConsoleColor.Red); 42 | } 43 | return CommandResult.Fail(ex.Message, ex); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Auth/DeleteCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Auth 4 | { 5 | [Command("auth", "delete", HelpText = "Deletes an authentication profile from the store.")] 6 | public class DeleteCommand 7 | { 8 | [Option("name", "n", HelpText = "The name of the authentication profile to delete.")] 9 | [Required] 10 | public string Name { get; set; } = string.Empty; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Auth/DeleteCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Connection; 2 | using Greg.Xrm.Command.Services.Output; 3 | 4 | namespace Greg.Xrm.Command.Commands.Auth 5 | { 6 | public class DeleteCommandExecutor : ICommandExecutor 7 | { 8 | private readonly IOrganizationServiceRepository organizationServiceRepository; 9 | private readonly IOutput output; 10 | 11 | public DeleteCommandExecutor( 12 | IOrganizationServiceRepository organizationServiceRepository, 13 | IOutput output) 14 | { 15 | this.organizationServiceRepository = organizationServiceRepository; 16 | this.output = output; 17 | } 18 | 19 | public async Task ExecuteAsync(DeleteCommand command, CancellationToken cancellationToken) 20 | { 21 | if (string.IsNullOrWhiteSpace(command.Name)) 22 | { 23 | throw new CommandException(CommandException.CommandRequiredArgumentNotProvided, "You must specify the name of the authentication profile to delete"); 24 | } 25 | 26 | 27 | await organizationServiceRepository.DeleteConnectionAsync(command.Name); 28 | output.WriteLine($"Authentication profile '{command.Name}' deleted"); 29 | return CommandResult.Success(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Auth/Help.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Auth 4 | { 5 | public class Help : NamespaceHelperBase 6 | { 7 | public Help() : base("Manage how you authenticate to Dataverse environments", "auth") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Auth/ListCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Auth 4 | { 5 | [Command("auth", "list", HelpText = "List all the authentication profiles stored on this computer")] 6 | public class ListCommand 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Auth/PingCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Auth 4 | { 5 | [Command("auth", "ping", HelpText = "Tests the connection to the Dataverse environment currently selected")] 6 | [Alias("ping")] 7 | public class PingCommand 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Auth/RenameCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Auth 4 | { 5 | [Command("auth", "rename", HelpText = "Renames an authentication profile")] 6 | public class RenameCommand 7 | { 8 | [Option("old", "o", HelpText = "The new name of the authentication profile")] 9 | [Required] 10 | public string OldName { get; set; } = string.Empty; 11 | 12 | [Option("new", "n", HelpText = "The new name of the authentication profile")] 13 | [Required] 14 | public string NewName { get; set; } = string.Empty; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Auth/RenameCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Connection; 2 | using Greg.Xrm.Command.Services.Output; 3 | 4 | namespace Greg.Xrm.Command.Commands.Auth 5 | { 6 | public class RenameCommandExecutor : ICommandExecutor 7 | { 8 | private readonly IOrganizationServiceRepository organizationServiceRepository; 9 | private readonly IOutput output; 10 | 11 | public RenameCommandExecutor( 12 | IOrganizationServiceRepository organizationServiceRepository, 13 | IOutput output) 14 | { 15 | this.organizationServiceRepository = organizationServiceRepository; 16 | this.output = output; 17 | } 18 | public async Task ExecuteAsync(RenameCommand command, CancellationToken cancellationToken) 19 | { 20 | await organizationServiceRepository.RenameConnectionAsync(command.OldName, command.NewName); 21 | 22 | this.output.WriteLine($"Authentication profile '{command.OldName}' renamed in '{command.NewName}'. Type 'pacx auth list' to check the updated name."); 23 | return CommandResult.Success(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Auth/SelectCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Auth 4 | { 5 | [Command("auth", "select", HelpText = "Selects a connection to use for the next commands")] 6 | public class SelectCommand 7 | { 8 | [Option("name", "n", HelpText = "The name of the authentication profile to set as default.")] 9 | [Required] 10 | public string? Name { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Auth/SelectCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Connection; 2 | using Greg.Xrm.Command.Services.Output; 3 | 4 | namespace Greg.Xrm.Command.Commands.Auth 5 | { 6 | public class SelectCommandExecutor : ICommandExecutor 7 | { 8 | private readonly IOutput output; 9 | private readonly IOrganizationServiceRepository repository; 10 | 11 | public SelectCommandExecutor(IOutput output, IOrganizationServiceRepository repository) 12 | { 13 | this.output = output; 14 | this.repository = repository; 15 | } 16 | 17 | public async Task ExecuteAsync(SelectCommand command, CancellationToken cancellationToken) 18 | { 19 | if (string.IsNullOrWhiteSpace(command.Name)) 20 | { 21 | throw new CommandException(CommandException.CommandRequiredArgumentNotProvided, "The name of the authentication profile to set as default is required."); 22 | } 23 | 24 | var connections = await this.repository.GetAllConnectionDefinitionsAsync(); 25 | 26 | if (!connections.Exists(command.Name)) 27 | { 28 | return CommandResult.Fail("Invalid connection name: " + command.Name + Environment.NewLine + "use 'auth list' to see the list of available connections."); 29 | } 30 | 31 | if (connections.CurrentConnectionStringKey == command.Name) 32 | { 33 | this.output.Write("Connection '").Write(command.Name).WriteLine("' is already set as default."); 34 | return CommandResult.Success(); 35 | } 36 | 37 | await this.repository.SetDefaultAsync(command.Name); 38 | this.output.Write("Connection '").Write(command.Name, ConsoleColor.Yellow).WriteLine("' set as default."); 39 | return CommandResult.Success(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/Builders/AttributeMetadataBuilderBoolean.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | using Microsoft.Xrm.Sdk; 3 | using Microsoft.Xrm.Sdk.Metadata; 4 | 5 | namespace Greg.Xrm.Command.Commands.Column.Builders 6 | { 7 | internal class AttributeMetadataBuilderBoolean : AttributeMetadataBuilderBase 8 | { 9 | public override Task CreateFromAsync(IOrganizationServiceAsync2 crm, CreateCommand command, int languageCode, string publisherPrefix, int customizationOptionValuePrefix) 10 | { 11 | var attribute = new BooleanAttributeMetadata(); 12 | SetCommonProperties(attribute, command, languageCode, publisherPrefix); 13 | 14 | // Set extended properties 15 | attribute.OptionSet = new BooleanOptionSetMetadata( 16 | new OptionMetadata(new Label(command.TrueLabel, languageCode), 1), 17 | new OptionMetadata(new Label(command.FalseLabel, languageCode), 0) 18 | ); 19 | 20 | return Task.FromResult((AttributeMetadata)attribute); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/Builders/AttributeMetadataBuilderDateTime.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | using Microsoft.Xrm.Sdk.Metadata; 3 | 4 | namespace Greg.Xrm.Command.Commands.Column.Builders 5 | { 6 | internal class AttributeMetadataBuilderDateTime : AttributeMetadataBuilderBase 7 | { 8 | public override Task CreateFromAsync(IOrganizationServiceAsync2 crm, CreateCommand command, int languageCode, string publisherPrefix, int customizationOptionValuePrefix) 9 | { 10 | var attribute = new DateTimeAttributeMetadata(); 11 | SetCommonProperties(attribute, command, languageCode, publisherPrefix); 12 | 13 | attribute.DateTimeBehavior = GetBehavior(command.DateTimeBehavior); 14 | attribute.Format = command.DateTimeFormat; 15 | attribute.ImeMode = command.ImeMode; 16 | 17 | return Task.FromResult((AttributeMetadata)attribute); 18 | } 19 | 20 | private static DateTimeBehavior GetBehavior(DateTimeBehavior1 dateTimeBehavior) 21 | { 22 | return dateTimeBehavior switch 23 | { 24 | DateTimeBehavior1.UserLocal => DateTimeBehavior.UserLocal, 25 | DateTimeBehavior1.TimeZoneIndependent => DateTimeBehavior.TimeZoneIndependent, 26 | DateTimeBehavior1.DateOnly => DateTimeBehavior.DateOnly, 27 | _ => throw new NotSupportedException($"DateTimeBehavior {dateTimeBehavior} is not supported."), 28 | }; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/Builders/AttributeMetadataBuilderDecimal.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | using Microsoft.Xrm.Sdk.Metadata; 3 | 4 | namespace Greg.Xrm.Command.Commands.Column.Builders 5 | { 6 | internal class AttributeMetadataBuilderDecimal : AttributeMetadataBuilderNumericBase 7 | { 8 | public override Task CreateFromAsync(IOrganizationServiceAsync2 crm, CreateCommand command, int languageCode, string publisherPrefix, int customizationOptionValuePrefix) 9 | { 10 | var attribute = new DecimalAttributeMetadata(); 11 | SetCommonProperties(attribute, command, languageCode, publisherPrefix); 12 | 13 | // Set extended properties 14 | attribute.MinValue = Convert.ToDecimal(GetDoubleValue(command.MinValue, Limit.Min)); 15 | attribute.MaxValue = Convert.ToDecimal(GetDoubleValue(command.MaxValue, Limit.Max)); 16 | 17 | attribute.Precision = command.Precision; //1; 18 | attribute.ImeMode = command.ImeMode; // ImeMode.Disabled; 19 | 20 | return Task.FromResult((AttributeMetadata)attribute); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/Builders/AttributeMetadataBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | 3 | namespace Greg.Xrm.Command.Commands.Column.Builders 4 | { 5 | public class AttributeMetadataBuilderFactory : IAttributeMetadataBuilderFactory 6 | { 7 | private readonly Dictionary> cache = new(); 8 | 9 | public AttributeMetadataBuilderFactory() 10 | { 11 | cache.Add(AttributeTypeCode.String, () => new AttributeMetadataBuilderString()); 12 | cache.Add(AttributeTypeCode.Integer, () => new AttributeMetadataBuilderInteger()); 13 | cache.Add(AttributeTypeCode.Decimal, () => new AttributeMetadataBuilderDecimal()); 14 | cache.Add(AttributeTypeCode.Boolean, () => new AttributeMetadataBuilderBoolean()); 15 | cache.Add(AttributeTypeCode.Picklist, () => new AttributeMetadataBuilderPicklist()); 16 | cache.Add(AttributeTypeCode.Money, () => new AttributeMetadataBuilderMoney()); 17 | cache.Add(AttributeTypeCode.Memo, () => new AttributeMetadataBuilderMemo()); 18 | cache.Add(AttributeTypeCode.DateTime, () => new AttributeMetadataBuilderDateTime()); 19 | } 20 | 21 | public IAttributeMetadataBuilder CreateFor(AttributeTypeCode attributeType) 22 | { 23 | if (!cache.TryGetValue(attributeType, out var factory)) 24 | throw new CommandException(CommandException.CommandInvalidArgumentValue, $"The attribute type '{attributeType}' is not supported yet"); 25 | 26 | return factory(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/Builders/AttributeMetadataBuilderInteger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | using Microsoft.Xrm.Sdk.Metadata; 3 | 4 | namespace Greg.Xrm.Command.Commands.Column.Builders 5 | { 6 | internal class AttributeMetadataBuilderInteger : AttributeMetadataBuilderNumericBase 7 | { 8 | 9 | public override Task CreateFromAsync(IOrganizationServiceAsync2 crm, CreateCommand command, int languageCode, string publisherPrefix, int customizationOptionValuePrefix) 10 | { 11 | var attribute = new IntegerAttributeMetadata(); 12 | SetCommonProperties(attribute, command, languageCode, publisherPrefix); 13 | 14 | attribute.MinValue = GetIntValue(command.MinValue, Limit.Min); 15 | attribute.MaxValue = GetIntValue(command.MaxValue, Limit.Max); 16 | attribute.Format = command.IntegerFormat; 17 | 18 | return Task.FromResult((AttributeMetadata)attribute); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/Builders/AttributeMetadataBuilderMoney.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | using Microsoft.Xrm.Sdk.Metadata; 3 | 4 | namespace Greg.Xrm.Command.Commands.Column.Builders 5 | { 6 | internal class AttributeMetadataBuilderMoney : AttributeMetadataBuilderNumericBase 7 | { 8 | 9 | public override Task CreateFromAsync(IOrganizationServiceAsync2 crm, CreateCommand command, int languageCode, string publisherPrefix, int customizationOptionValuePrefix) 10 | { 11 | var attribute = new MoneyAttributeMetadata(); 12 | SetCommonProperties(attribute, command, languageCode, publisherPrefix); 13 | 14 | // Set extended properties 15 | attribute.MinValue = GetDoubleValue(command.MinValue, Limit.Min); 16 | attribute.MaxValue = GetDoubleValue(command.MaxValue, Limit.Max); 17 | 18 | attribute.Precision = command.Precision; //1; 19 | attribute.PrecisionSource = command.PrecisionSource; // default 2; 20 | attribute.ImeMode = command.ImeMode; // ImeMode.Disabled; 21 | 22 | if (attribute.PrecisionSource == 0 && attribute.Precision is null) 23 | { 24 | throw new CommandException(CommandException.CommandRequiredArgumentNotProvided, $"The attribute 'Precision' must be specified when PrecisionSource = 0"); 25 | } 26 | 27 | 28 | return Task.FromResult((AttributeMetadata)attribute); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/Builders/AttributeMetadataBuilderString.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | using Microsoft.Xrm.Sdk.Metadata; 3 | 4 | namespace Greg.Xrm.Command.Commands.Column.Builders 5 | { 6 | 7 | public class AttributeMetadataBuilderString : AttributeMetadataBuilderBase 8 | { 9 | public override Task CreateFromAsync( 10 | IOrganizationServiceAsync2 crm, 11 | CreateCommand command, 12 | int languageCode, 13 | string publisherPrefix, 14 | int customizationOptionValuePrefix) 15 | { 16 | var attribute = new StringAttributeMetadata(); 17 | SetCommonProperties(attribute, command, languageCode, publisherPrefix); 18 | 19 | attribute.MaxLength = GetMaxLength(command.MaxLength); 20 | attribute.Format = command.StringFormat; 21 | attribute.AutoNumberFormat = command.AutoNumber; 22 | 23 | return Task.FromResult((AttributeMetadata)attribute); 24 | } 25 | 26 | 27 | 28 | public static int GetMaxLength(int? maxLength) 29 | { 30 | if (maxLength == null) return 100; 31 | if (maxLength <= 0) throw new CommandException(CommandException.CommandInvalidArgumentValue, $"The max length must be a positive number"); 32 | return maxLength.Value; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/Builders/IAttributeMetadataBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | using Microsoft.Xrm.Sdk.Metadata; 3 | 4 | namespace Greg.Xrm.Command.Commands.Column.Builders 5 | { 6 | public interface IAttributeMetadataBuilder 7 | { 8 | Task CreateFromAsync( 9 | IOrganizationServiceAsync2 crm, 10 | CreateCommand command, 11 | int languageCode, 12 | string publisherPrefix, 13 | int customizationOptionValuePrefix); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/Builders/IAttributeMetadataBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | 3 | namespace Greg.Xrm.Command.Commands.Column.Builders 4 | { 5 | public interface IAttributeMetadataBuilderFactory 6 | { 7 | IAttributeMetadataBuilder CreateFor(AttributeTypeCode attributeType); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/CreateCommandResult.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Column 2 | { 3 | public class CreateCommandResult : CommandResult 4 | { 5 | public CreateCommandResult(Guid attributeId) : base(true) 6 | { 7 | this["Attribute ID"] = attributeId; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/DeleteCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace Greg.Xrm.Command.Commands.Column 5 | { 6 | [Command("column", "delete", HelpText = "Deletes a column from a given Dataverse table.")] 7 | [Alias("delete", "column")] 8 | public class DeleteCommand 9 | { 10 | [Option("table", "t", HelpText = "The schema name of the table that contains the column to delete.")] 11 | [Required] 12 | public string? TableName { get; set; } 13 | 14 | [Option("schemaName", "sn", HelpText = "The schema name of the column to delete.")] 15 | [Required] 16 | public string? SchemaName { get; set; } 17 | 18 | [Option("force", "f", HelpText = "(preview) If specified, tries to force the deletion removing the column dependencies (thanks @daryllabar)", DefaultValue = false)] 19 | public bool Force { get; set; } = false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/ExportMetadataCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Column 4 | { 5 | [Command("column", "exportMetadata", HelpText = "Exports the metadata definition of a given column (for documentation purpose)")] 6 | public class ExportMetadataCommand 7 | { 8 | [Option("table", "t", "The name of the table containing the column to export")] 9 | [Required] 10 | public string TableSchemaName { get; set; } = string.Empty; 11 | 12 | [Option("column", "c", "The name of the column to export")] 13 | [Required] 14 | public string ColumnSchemaName { get; set; } = string.Empty; 15 | 16 | [Option("output", "o", "The name of the folder that will contain the file with the exported metadata. (default: current folder)")] 17 | public string OutputFilePath { get; set; } = string.Empty; 18 | 19 | [Option("run", "r", "Automatically opens the file containing the exported metadata after export.", false)] 20 | public bool AutoOpenFile { get; set; } = false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/GetDependenciesCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Column 4 | { 5 | [Command("column", "getDependencies", HelpText = "Retrieves the list of solution components that depend from a given column")] 6 | [Alias("column", "getdeps")] 7 | [Alias("column", "get-dependencies")] 8 | public class GetDependenciesCommand 9 | { 10 | [Option("table", "t", HelpText = "The name of the table containing the column to retrieve the dependencies for")] 11 | [Required] 12 | public string? TableName { get; set; } 13 | 14 | [Option("column", "c", HelpText = "The name of the column to retrieve the dependencies for")] 15 | [Required] 16 | public string? ColumnName { get; set; } 17 | 18 | 19 | [Option("forDelete", "d", HelpText = "Specifies whether to retrieve the dependencies for delete or not", DefaultValue = true)] 20 | public bool ForDelete { get; set; } = true; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/Help.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Column 4 | { 5 | public class Help : NamespaceHelperBase 6 | { 7 | public Help() : base("Execute manipulations on Dataverse columns", "column") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Column/SetSeedCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Column 4 | { 5 | [Command("column", "seed", HelpText = "Sets the seed for a given column. Is a proxy for [SetAutoNumberSeed Action](https://learn.microsoft.com/en-us/power-apps/developer/data-platform/webapi/reference/setautonumberseed?view=dataverse-latest)")] 6 | [Alias("column", "set-seed")] 7 | [Alias("column", "setSeed")] 8 | public class SetSeedCommand 9 | { 10 | [Option("table", "t", HelpText = "The logical name of the table.")] 11 | [Required] 12 | public string TableName { get; set; } = string.Empty; 13 | 14 | [Option("column", "c", HelpText = "The logical name of the column.")] 15 | [Required] 16 | public string ColumnName { get; set; } = string.Empty; 17 | 18 | [Option("seed", "s", HelpText = "The seed value to set.")] 19 | [Required] 20 | public int Seed { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Forms/Help.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Forms 4 | { 5 | public class Help : NamespaceHelperBase 6 | { 7 | public Help() : base("Commands to streamline form manipulation", "forms") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Forms/Model/FormType.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Forms.Model 2 | { 3 | public enum FormType 4 | { 5 | Dashboard = 0, 6 | AppointmentBook = 1, 7 | Main = 2, 8 | MiniCampaignBO = 3, 9 | Preview = 4, 10 | MobileExpress = 5, 11 | QuickView = 6, 12 | QuickCreate = 7, 13 | Dialog = 8, 14 | TaskFlow = 9, 15 | InteractionCentricDashboard = 10, 16 | Card = 11, 17 | MainInteractiveExperience = 12, 18 | ContextualDashboard = 13, 19 | Other = 100, 20 | MainBackup = 101, 21 | AppointmentBookBackup = 102, 22 | PowerBIDashboard = 103, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Forms/Model/IFormRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | 3 | namespace Greg.Xrm.Command.Commands.Forms.Model 4 | { 5 | public interface IFormRepository 6 | { 7 | Task> GetMainFormByTableNameAsync(IOrganizationServiceAsync2 crm, string tableName); 8 | } 9 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Help/HelpCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Help 4 | { 5 | public class HelpCommand 6 | { 7 | public HelpCommand(VerbNode lastMatchingVerb) 8 | { 9 | this.ExportHelp = false; 10 | this.ExportHelpPath = string.Empty; 11 | this.LastMatchingVerb = lastMatchingVerb; 12 | } 13 | 14 | 15 | public HelpCommand(CommandDefinition commandDefinition) 16 | { 17 | this.CommandDefinition = commandDefinition; 18 | this.ExportHelp = false; 19 | this.ExportHelpPath = string.Empty; 20 | this.LastMatchingVerb = null; 21 | } 22 | 23 | public HelpCommand(IReadOnlyList commandDefinitionList, IReadOnlyList commandTree, IReadOnlyDictionary options) 24 | { 25 | this.CommandList = commandDefinitionList; 26 | this.CommandTree = commandTree; 27 | this.ExportHelp = options.ContainsKey("--export"); 28 | this.ExportHelpPath = this.ExportHelp ? options["--export"] : string.Empty; 29 | this.LastMatchingVerb = null; 30 | } 31 | 32 | 33 | 34 | 35 | 36 | public CommandDefinition? CommandDefinition { get; } 37 | 38 | public bool ExportHelp { get; } 39 | 40 | public string ExportHelpPath { get; } 41 | 42 | public IReadOnlyList CommandList { get; } = new List(); 43 | 44 | public IReadOnlyList CommandTree { get; } = new List(); 45 | 46 | public VerbNode? LastMatchingVerb { get; } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/History/ClearCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.History 2 | { 3 | [Command("history", "clear", HelpText = "Clears the command history")] 4 | [Alias("clear-history")] 5 | [Alias("clear", "history")] 6 | public class ClearCommand 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/History/ClearCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.CommandHistory; 2 | using Greg.Xrm.Command.Services.Output; 3 | 4 | namespace Greg.Xrm.Command.Commands.History 5 | { 6 | public class ClearCommandExecutor : ICommandExecutor 7 | { 8 | private readonly IOutput output; 9 | private readonly IHistoryTracker historyTracker; 10 | 11 | public ClearCommandExecutor(IOutput output, IHistoryTracker historyTracker) 12 | { 13 | this.output = output ?? throw new ArgumentNullException(nameof(output)); 14 | this.historyTracker = historyTracker ?? throw new ArgumentNullException(nameof(historyTracker)); 15 | } 16 | 17 | 18 | public async Task ExecuteAsync(ClearCommand command, CancellationToken cancellationToken) 19 | { 20 | this.output.Write("Cleaning up command history..."); 21 | 22 | await this.historyTracker.ClearAsync(); 23 | 24 | this.output.WriteLine("Done", ConsoleColor.Green); 25 | 26 | return CommandResult.Success(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/History/GetCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.History 4 | { 5 | [Command("history", "get", HelpText = "Get the list of commands executed in the past")] 6 | [Alias("get-history")] 7 | [Alias("get", "history")] 8 | public class GetCommand 9 | { 10 | [Option("length", "l", HelpText = "The number of commands to retrieve. If not specified, retrieves the whole command list.")] 11 | [Range(1, 10000)] 12 | public int? Length { get; set; } 13 | 14 | [Option("file", "f", HelpText = "If you want to save the command list to a specific file, specify the name of the file.")] 15 | public string? File { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/History/Help.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.History 4 | { 5 | public class Help : NamespaceHelperBase 6 | { 7 | public Help() : base("Provides access to the command history", "history") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/History/SetLengthCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.History 4 | { 5 | [Command("history", "setLength", HelpText = "Allows to specify the length of the command history that will be persisted.")] 6 | [Alias("history", "set-length")] 7 | [Alias("history", "len")] 8 | public class SetLengthCommand 9 | { 10 | [Option("length", "l", HelpText = "The maximum number of commands to keep in history.")] 11 | [Required] 12 | [Range(1, 100, ErrorMessage = "The length must be between 1 and 10000")] 13 | public int Length { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/History/SetLengthCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.CommandHistory; 2 | using Greg.Xrm.Command.Services.Output; 3 | 4 | namespace Greg.Xrm.Command.Commands.History 5 | { 6 | public class SetLengthCommandExecutor : ICommandExecutor 7 | { 8 | private readonly IOutput output; 9 | private readonly IHistoryTracker historyTracker; 10 | 11 | public SetLengthCommandExecutor(IOutput output, IHistoryTracker historyTracker) 12 | { 13 | this.output = output ?? throw new ArgumentNullException(nameof(output)); 14 | this.historyTracker = historyTracker ?? throw new ArgumentNullException(nameof(historyTracker)); 15 | } 16 | 17 | 18 | public async Task ExecuteAsync(SetLengthCommand command, CancellationToken cancellationToken) 19 | { 20 | this.output.Write("Setting command history max length to ").Write(command.Length).Write("..."); 21 | 22 | await this.historyTracker.SetMaxLengthAsync(command.Length); 23 | 24 | this.output.WriteLine("Done", ConsoleColor.Green); 25 | return CommandResult.Success(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Plugin/Help.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Plugin 4 | { 5 | public class Help : NamespaceHelperBase 6 | { 7 | public Help() : base("Allows adding, listing, updating and removing PACX plugins", "plugin") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Plugin/ListCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Plugin 2 | { 3 | [Command("plugin", "list", HelpText = "Lists all the installed plugins.")] 4 | public class ListCommand 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Plugin/UninstallCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace Greg.Xrm.Command.Commands.Plugin 5 | { 6 | [Command("plugin", "uninstall", HelpText = "Uninstalls a PACX plugin.")] 7 | [Alias("plugin", "remove")] 8 | [Alias("plugin", "delete")] 9 | [Alias("uninstall", "plugin")] 10 | [Alias("delete", "plugin")] 11 | [Alias("remove", "plugin")] 12 | public class UninstallCommand 13 | { 14 | [Option("name", "n", HelpText = "The unique name of the NuGet package containing the plugin to uninstall.")] 15 | [Required] 16 | public string Name { get; set; } = string.Empty; 17 | 18 | public void WriteUsageExamples(MarkdownWriter writer) 19 | { 20 | throw new NotImplementedException(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Plugin/UninstallCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services; 2 | using Greg.Xrm.Command.Services.Output; 3 | 4 | namespace Greg.Xrm.Command.Commands.Plugin 5 | { 6 | public class UninstallCommandExecutor : ICommandExecutor 7 | { 8 | private readonly IOutput output; 9 | private readonly IStorage storage; 10 | 11 | public UninstallCommandExecutor(IOutput output, IStorage storage) 12 | { 13 | this.output = output; 14 | this.storage = storage; 15 | } 16 | 17 | 18 | public Task ExecuteAsync(UninstallCommand command, CancellationToken cancellationToken) 19 | { 20 | var storageFolder = this.storage.GetOrCreateStorageFolder(); 21 | var pluginRootFolder = storageFolder.CreateSubdirectory("Plugins"); 22 | 23 | var pluginFolder = pluginRootFolder.GetDirectories(command.Name).FirstOrDefault(); 24 | 25 | if (pluginFolder == null) 26 | { 27 | return Task.FromResult(CommandResult.Fail($"Plugin {command.Name} not found.")); 28 | } 29 | 30 | try 31 | { 32 | this.output.Write($"Deleting plugin <{pluginFolder.Name}>..."); 33 | 34 | File.WriteAllText(Path.Combine(pluginFolder.FullName, ".delete"), "Plugin deleted on " + DateTime.Now.ToLongDateString()); 35 | 36 | this.output.WriteLine("Done", ConsoleColor.Green); 37 | 38 | return Task.FromResult(CommandResult.Success()); 39 | } 40 | catch(Exception ex) 41 | { 42 | this.output.WriteLine("Failed", ConsoleColor.Red); 43 | return Task.FromResult(CommandResult.Fail(ex.Message)); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Projects/Help.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Projects 4 | { 5 | public class Help : NamespaceHelperBase 6 | { 7 | public Help() : base("Commands to create and work with PACX projects.", "project") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Projects/InfoProjectCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Projects 2 | { 3 | [Command("project", "info", HelpText = "If the current folder is under a PACX project folder, shows the details of the current project")] 4 | [Alias("project", "get")] 5 | public class InfoProjectCommand 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Projects/InfoProjectCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Output; 2 | using Greg.Xrm.Command.Services.Project; 3 | 4 | namespace Greg.Xrm.Command.Commands.Projects 5 | { 6 | public class InfoProjectCommandExecutor( 7 | IOutput output, 8 | IPacxProjectRepository pacxProjectRepository 9 | ) 10 | : ICommandExecutor 11 | { 12 | 13 | 14 | public async Task ExecuteAsync(InfoProjectCommand command, CancellationToken cancellationToken) 15 | { 16 | var project = await pacxProjectRepository.GetCurrentProjectAsync(); 17 | if (project == null) 18 | { 19 | output.WriteLine("The current folder does not belongs to a PACX project."); 20 | return CommandResult.Success(); 21 | } 22 | 23 | output.Write($" Version : ").WriteLine(project.Version, ConsoleColor.Cyan); 24 | output.Write($" Auth. Profile: ").WriteLine(project.AuthProfileName, ConsoleColor.Cyan); 25 | output.Write($" Def. Solution: ").WriteLine(project.SolutionName, ConsoleColor.Cyan); 26 | output.Write($" Suspended : ").WriteLine(project.IsSuspended, ConsoleColor.Cyan); 27 | 28 | return CommandResult.Success(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Projects/ResumeProjectCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Projects 2 | { 3 | [Command("project", "resume", HelpText = "When called, enables the current project. All the subsequent commands will use the project's auth profile and solution.")] 4 | public class ResumeProjectCommand 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Projects/ResumeProjectCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Output; 2 | using Greg.Xrm.Command.Services.Project; 3 | 4 | namespace Greg.Xrm.Command.Commands.Projects 5 | { 6 | public class ResumeProjectCommandExecutor( 7 | IOutput output, 8 | IPacxProjectRepository pacxProjectRepository 9 | ) 10 | : ICommandExecutor 11 | { 12 | 13 | 14 | public async Task ExecuteAsync(ResumeProjectCommand command, CancellationToken cancellationToken) 15 | { 16 | var project = await pacxProjectRepository.GetCurrentProjectAsync(); 17 | if (project == null) 18 | { 19 | return CommandResult.Fail("The current folder does not belongs to a PACX project."); 20 | } 21 | 22 | if (!project.IsSuspended) 23 | { 24 | output.WriteLine("The project is already enabled, nothing to do."); 25 | return CommandResult.Success(); 26 | } 27 | 28 | project.IsSuspended = false; 29 | await pacxProjectRepository.SaveAsync(project, Environment.CurrentDirectory, cancellationToken); 30 | 31 | output.WriteLine("Project resumed!", ConsoleColor.Green); 32 | return CommandResult.Success(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Projects/SuspendProjectCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Projects 2 | { 3 | [Command("project", "suspend", HelpText = "When called, disables the current project. All the subsequent commands will fall back to the default auth profile and solution.")] 4 | public class SuspendProjectCommand 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Projects/SuspendProjectCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Output; 2 | using Greg.Xrm.Command.Services.Project; 3 | 4 | namespace Greg.Xrm.Command.Commands.Projects 5 | { 6 | public class SuspendProjectCommandExecutor( 7 | IOutput output, 8 | IPacxProjectRepository pacxProjectRepository 9 | ) 10 | : ICommandExecutor 11 | { 12 | 13 | 14 | public async Task ExecuteAsync(SuspendProjectCommand command, CancellationToken cancellationToken) 15 | { 16 | var project = await pacxProjectRepository.GetCurrentProjectAsync(); 17 | if (project == null) 18 | { 19 | return CommandResult.Fail("The current folder does not belongs to a PACX project."); 20 | } 21 | 22 | if (project.IsSuspended) 23 | { 24 | output.WriteLine("The project is already suspended, nothing to do."); 25 | return CommandResult.Success(); 26 | } 27 | 28 | project.IsSuspended = true; 29 | await pacxProjectRepository.SaveAsync(project, Environment.CurrentDirectory, cancellationToken); 30 | 31 | output.WriteLine("Project suspended!", ConsoleColor.Green); 32 | return CommandResult.Success(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Relationship/DeleteCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Relationship 4 | { 5 | [Command("rel", "delete", HelpText = "Deletes a relationship")] 6 | public class DeleteCommand 7 | { 8 | [Option("name", "n", HelpText = "The schema name of the relationship")] 9 | [Required] 10 | public string Name { get; set; } = string.Empty; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Relationship/DeleteCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Connection; 2 | using Greg.Xrm.Command.Services.Output; 3 | using Microsoft.Xrm.Sdk; 4 | using Microsoft.Xrm.Sdk.Messages; 5 | using System.ServiceModel; 6 | 7 | namespace Greg.Xrm.Command.Commands.Relationship 8 | { 9 | public class DeleteCommandExecutor : ICommandExecutor 10 | { 11 | private readonly IOutput output; 12 | private readonly IOrganizationServiceRepository organizationServiceRepository; 13 | 14 | public DeleteCommandExecutor(IOutput output, IOrganizationServiceRepository organizationServiceRepository) 15 | { 16 | this.output = output; 17 | this.organizationServiceRepository = organizationServiceRepository; 18 | } 19 | 20 | 21 | 22 | public async Task ExecuteAsync(DeleteCommand command, CancellationToken cancellationToken) 23 | { 24 | this.output.Write($"Connecting to the current dataverse environment..."); 25 | var crm = await this.organizationServiceRepository.GetCurrentConnectionAsync(); 26 | this.output.WriteLine("Done", ConsoleColor.Green); 27 | 28 | try 29 | { 30 | output.Write($"Deleting relationship {command.Name}..."); 31 | 32 | var request = new DeleteRelationshipRequest 33 | { 34 | Name = command.Name 35 | }; 36 | 37 | await crm.ExecuteAsync(request); 38 | 39 | 40 | this.output.WriteLine("Done", ConsoleColor.Green); 41 | 42 | return CommandResult.Success(); 43 | } 44 | catch (FaultException ex) 45 | { 46 | return CommandResult.Fail(ex.Message, ex); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Relationship/Help1.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Relationship 4 | { 5 | public class Help1 : NamespaceHelperBase 6 | { 7 | public Help1() : base("Executes operations on Dataverse relationships between tables", "rel") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Relationship/Help2.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Relationship 4 | { 5 | public class Help2 : NamespaceHelperBase 6 | { 7 | public Help2() : base("Creates a new relationship between Dataverse tables", "rel", "create") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Relationship/Help3.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Relationship 4 | { 5 | public class Help3 : NamespaceHelperBase 6 | { 7 | public Help3() : base("Manage polymorphic relationships (create, add parent table, remove parent table, ...)", "rel", "poly") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Relationship/ICreateNNStrategy.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Relationship 2 | { 3 | public interface ICreateNNStrategy 4 | { 5 | Task CreateAsync(CreateNNCommand command, string currentSolutionName, int defaultLanguageCode, string publisherPrefix); 6 | } 7 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Relationship/RemovePolyCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Relationship 4 | { 5 | [Command("rel", "poly", "remove", HelpText = "Removes a parent from an existing many-to-one **polymorphic** relationship between Dataverse tables")] 6 | [Alias("rel", "poli", "remove")] 7 | [Alias("rel", "poly", "rem")] 8 | [Alias("rel", "poli", "rem")] 9 | public class RemovePolyCommand 10 | { 11 | 12 | [Option("child", "c", "The child table (N side of the relationship)")] 13 | [Required] 14 | public string ChildTable { get; set; } = string.Empty; 15 | 16 | 17 | [Required] 18 | [Option("lookup", "l", "The lookup column that represent the relationship to update.")] 19 | public string LookupColumnName { get; set; } = string.Empty; 20 | 21 | 22 | [Option("parent", "p", "The parent table to add to the relationship")] 23 | [Required] 24 | public string ParentTable { get; set; } = string.Empty; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Help.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Settings 4 | { 5 | public class Help : NamespaceHelperBase 6 | { 7 | public Help() : base("Commands to manipulate dataverse settings (https://learn.microsoft.com/en-us/power-apps/maker/data-platform/create-edit-configure-settings)", "settings") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Imports/IImportAction.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Settings.Imports 2 | { 3 | public interface IImportAction 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Imports/IImportStrategy.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Settings.Imports 2 | { 3 | public interface IImportStrategy 4 | { 5 | Task> ImportAsync(CancellationToken cancellationToken); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Imports/IImportStrategyFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Settings.Imports 2 | { 3 | public interface IImportStrategyFactory 4 | { 5 | Task CreateAsync(Stream stream, CancellationToken cancellationToken); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Imports/ImportActionSetAppValue.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Settings.Imports 2 | { 3 | public class ImportActionSetAppValue : IImportAction 4 | { 5 | private readonly string uniqueName; 6 | private readonly string appName; 7 | private readonly string value; 8 | 9 | public ImportActionSetAppValue(string uniqueName, string appName, string value) 10 | { 11 | this.uniqueName = uniqueName; 12 | this.appName = appName; 13 | this.value = value; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Imports/ImportActionSetDefaultValue.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Settings.Imports 2 | { 3 | public class ImportActionSetDefaultValue : IImportAction 4 | { 5 | private readonly string uniqueName; 6 | private readonly string value; 7 | 8 | public ImportActionSetDefaultValue(string uniqueName, string value) 9 | { 10 | this.uniqueName = uniqueName; 11 | this.value = value; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Imports/ImportActionSetEnvironmentValue.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Settings.Imports 2 | { 3 | public class ImportActionSetEnvironmentValue : IImportAction 4 | { 5 | private readonly string uniqueName; 6 | private readonly string value; 7 | 8 | public ImportActionSetEnvironmentValue(string uniqueName, string value) 9 | { 10 | this.uniqueName = uniqueName; 11 | this.value = value; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Imports/ImportStrategyFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Settings.Imports 2 | { 3 | public class ImportStrategyFactory : IImportStrategyFactory 4 | { 5 | public async Task CreateAsync(Stream stream, CancellationToken cancellationToken) 6 | { 7 | var format = await DetectFormatAsync(stream, cancellationToken); 8 | stream.Position = 0; 9 | 10 | switch (format) 11 | { 12 | case Format.Json: 13 | return new ImportStrategyFromJson(stream); 14 | case Format.Excel: 15 | return new ImportStrategyFromExcel(stream); 16 | default: 17 | throw new NotSupportedException($"The format {format} is not supported"); 18 | } 19 | } 20 | private static async Task DetectFormatAsync(Stream stream, CancellationToken cancellationToken) 21 | { 22 | var buffer = new byte[1]; 23 | var read = await stream.ReadAsync(buffer, cancellationToken); 24 | if (read == 0) 25 | { 26 | return Format.Text; 27 | } 28 | 29 | var firstChar = buffer[0]; 30 | if (firstChar == (byte)'[') 31 | { 32 | return Format.Json; 33 | } 34 | 35 | return Format.Excel; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Imports/ImportStrategyFromExcel.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Greg.Xrm.Command.Commands.Settings.Imports 4 | { 5 | public class ImportStrategyFromExcel : IImportStrategy 6 | { 7 | private Stream stream; 8 | 9 | public ImportStrategyFromExcel(Stream stream) 10 | { 11 | this.stream = stream; 12 | } 13 | 14 | public Task> ImportAsync(CancellationToken cancellationToken) 15 | { 16 | throw new NotImplementedException(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Model/IAppSettingRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | 3 | namespace Greg.Xrm.Command.Commands.Settings.Model 4 | { 5 | public interface IAppSettingRepository 6 | { 7 | Task> GetByDefinitionsAsync(IOrganizationServiceAsync2 crm, IReadOnlyList settingDefinitionList); 8 | } 9 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Model/IOrganizationSettingRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | 3 | namespace Greg.Xrm.Command.Commands.Settings.Model 4 | { 5 | public interface IOrganizationSettingRepository 6 | { 7 | Task> GetByDefinitionsAsync(IOrganizationServiceAsync2 crm, IReadOnlyList settingDefinitionList); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Model/ISettingDefinitionRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | 3 | namespace Greg.Xrm.Command.Commands.Settings.Model 4 | { 5 | public interface ISettingDefinitionRepository 6 | { 7 | Task> GetAllAsync(IOrganizationServiceAsync2 crm, Guid? solutionId, bool onlyVisible); 8 | Task GetByUniqueNameAsync(IOrganizationServiceAsync2 crm, string uniqueName); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Model/SettingDefinitionDataType.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Settings.Model 2 | { 3 | public enum SettingDefinitionDataType 4 | { 5 | Number = 0, 6 | String = 1, 7 | Boolean = 2 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Model/SettingDefinitionOverridableLevel.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Settings.Model 2 | { 3 | public enum SettingDefinitionOverridableLevel 4 | { 5 | AppAndOrganization = 0, 6 | Organization = 1, 7 | App = 2, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Settings/Model/SettingDefinitionReleaseLevel.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Settings.Model 2 | { 3 | public enum SettingDefinitionReleaseLevel 4 | { 5 | GA = 0, 6 | Preview = 2 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Solution/ComponentGetDependenciesCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace Greg.Xrm.Command.Commands.Solution 5 | { 6 | [Command("solution", "component", "getDependencies", HelpText = "Retrieves the list of solution components that depend from a given component")] 7 | [Alias("solution", "component", "getdeps")] 8 | [Alias("solution", "component", "get-dependencies")] 9 | public class ComponentGetDependenciesCommand 10 | { 11 | [Option("componentId", "id", HelpText = "The GUID of the component to retrieve the dependencies for")] 12 | [Required] 13 | public Guid ComponentId { get; set; } = Guid.Empty; 14 | 15 | [Option("type", "t", HelpText = "The type of the component to retrieve the dependencies for")] 16 | [Required] 17 | public ComponentType? ComponentType { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Solution/CreateCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace Greg.Xrm.Command.Commands.Solution 5 | { 6 | [Command("solution", "create", HelpText = "Creates a new unmanaged solution in the current Dataverse environment,\nalso creating the publisher, if needed.")] 7 | public class CreateCommand 8 | { 9 | [Option("name", "n", HelpText = "The display name of the solution to create")] 10 | [Required] 11 | public string? DisplayName { get; set; } 12 | 13 | [Option("uniqueName", "un", HelpText = "The unique name of the solution to create. If not specified, is deducted from the display name")] 14 | public string? UniqueName { get; set; } 15 | 16 | [Option("publisherUniqueName", "pun", HelpText = "The unique name of the publisher to create. If not specified, is deducted from the friendly name or customization prefix")] 17 | public string? PublisherUniqueName { get; set; } 18 | 19 | [Option("publisherFriendlyName", "puf", HelpText = "The friendly name of the publisher to create. If not specified, is deducted from the unique name or customization prefix")] 20 | public string? PublisherFriendlyName { get; set; } 21 | 22 | [Option("publisherPrefix", "pp", HelpText = "The customization prefix of the publisher to create. If not specified, is deducted from the unique name.")] 23 | public string? PublisherCustomizationPrefix { get; set; } 24 | 25 | [Option("publisherOptionSetPrefix", "pop", HelpText = "The option set prefix of the publisher to create (5 digit number).")] 26 | public int? PublisherOptionSetPrefix { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Solution/CreateCommandResult.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Solution 2 | { 3 | public class CreateCommandResult : CommandResult 4 | { 5 | public CreateCommandResult(Guid solutionId, Guid publisherId) : base(true) 6 | { 7 | this["Solution Id"] = solutionId; 8 | this["Publisher Id"] = publisherId; 9 | } 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Solution/DeleteCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Solution 4 | { 5 | [Command("solution", "delete", HelpText = "Deletes a solution from the current Dataverse environment")] 6 | public class DeleteCommand 7 | { 8 | [Option("uniqueName", "un", HelpText = "The unique name of the solution to delete.")] 9 | [Required] 10 | public string? SolutionUniqueName { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Solution/GetDefaultCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Solution 2 | { 3 | [Command("solution", "getDefault", HelpText = "Gets the default solution for the current environment, if it has been set via `solution setDefault`")] 4 | public class GetDefaultCommand 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Solution/GetDefaultCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Connection; 2 | using Greg.Xrm.Command.Services.Output; 3 | 4 | namespace Greg.Xrm.Command.Commands.Solution 5 | { 6 | public class GetDefaultCommandExecutor : ICommandExecutor 7 | { 8 | private readonly IOutput output; 9 | private readonly IOrganizationServiceRepository organizationServiceRepository; 10 | 11 | public GetDefaultCommandExecutor( 12 | IOutput output, 13 | IOrganizationServiceRepository organizationServiceRepository) 14 | { 15 | this.output = output; 16 | this.organizationServiceRepository = organizationServiceRepository; 17 | } 18 | 19 | 20 | 21 | public async Task ExecuteAsync(GetDefaultCommand command, CancellationToken cancellationToken) 22 | { 23 | 24 | try 25 | { 26 | var connections = await this.organizationServiceRepository.GetAllConnectionDefinitionsAsync(); 27 | 28 | var currentConnection = connections.CurrentConnectionStringKey; 29 | if (string.IsNullOrWhiteSpace(currentConnection)) 30 | { 31 | return CommandResult.Fail("No default connection selected. Please use 'pacx auth select' to select a default connection."); 32 | } 33 | 34 | if (connections.DefaultSolutions.TryGetValue(currentConnection, out var defaultSolutionName)) 35 | { 36 | this.output.Write("Default solution is: '").Write(defaultSolutionName, ConsoleColor.Yellow).Write("'").WriteLine(); 37 | return CommandResult.Success(); 38 | } 39 | 40 | this.output.WriteLine("No default solution set for the current connection. Current connection is " + currentConnection, ConsoleColor.Yellow); 41 | return CommandResult.Success(); 42 | } 43 | catch (Exception ex) 44 | { 45 | return CommandResult.Fail("Error while getting default solution: " + ex.Message, ex); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Solution/GetPublisherListCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Solution 2 | { 3 | [Command("solution", "getPublisherList", HelpText = "Lists the available publishers in current Dataverse environment. It displays unique name, friendly name and prefix.")] 4 | public class GetPublisherListCommand 5 | { 6 | [Option("verbose", "v", HelpText = "Add optionset prefix, created on, created by and description details.", DefaultValue = false)] 7 | public bool Verbose { get; set; } = false; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Solution/Help1.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Solution 4 | { 5 | public class Help1 : NamespaceHelperBase 6 | { 7 | public Help1() : base("Manages how you work with Dataverse solutions", "solution") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Solution/Help2.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Solution 4 | { 5 | public class Help2 : NamespaceHelperBase 6 | { 7 | public Help2() : base("Actions applicable to Dataverse solution components", "solution", "component") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Solution/SetDefaultCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | using Greg.Xrm.Command.Services; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace Greg.Xrm.Command.Commands.Solution 6 | { 7 | [Command("solution", "setDefault", HelpText = "Sets the default solution for the current environment")] 8 | public class SetDefaultCommand : ICanProvideUsageExample 9 | { 10 | [Option("name", "un", HelpText = "The unique name of the solution to set as default")] 11 | [Required] 12 | public string? SolutionUniqueName { get; set; } 13 | 14 | 15 | 16 | public void WriteUsageExamples(MarkdownWriter writer) 17 | { 18 | writer.Write("The default solution is the solution that will be used by commands such as "); 19 | writer.WriteCode("pacx table create"); 20 | writer.Write(" or "); 21 | writer.WriteCode("pacx column create"); 22 | writer.Write(" to store the applied customizations, when a solution uniquename is not been indicated in the proper command arguments."); 23 | writer.WriteLine(); 24 | writer.WriteLine(); 25 | writer.WriteParagraph("Example:"); 26 | writer.WriteCodeBlock("pacx solution setDefault -n my_solution_uniquename", "Command"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Builders/AttributeMetadataScriptBuilderBoolean.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Greg.Xrm.Command.Commands.Table.Builders 9 | { 10 | public class AttributeMetadataScriptBuilderBoolean : AttributeMetadataScriptBuilderBase 11 | { 12 | public override string GetColumnScript(AttributeMetadata attributeMetadata) 13 | { 14 | 15 | var sb = new StringBuilder(GetCommonColumns(attributeMetadata)); 16 | var attribute = (BooleanAttributeMetadata)attributeMetadata; 17 | 18 | sb.Append(CreatePropertyAttribute(attribute.OptionSet.TrueOption, CommandArgsConstants.TRUE_LABEL)); 19 | sb.Append(CreatePropertyAttribute(attribute.OptionSet.FalseOption, CommandArgsConstants.FALSE_LABEL)); 20 | 21 | return sb.ToString(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Builders/AttributeMetadataScriptBuilderDateTime.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | using Microsoft.Xrm.Sdk.Metadata; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Greg.Xrm.Command.Commands.Table.Builders 10 | { 11 | public class AttributeMetadataScriptBuilderDateTime : AttributeMetadataScriptBuilderBase 12 | { 13 | public override string GetColumnScript(AttributeMetadata attributeMetadata) 14 | { 15 | 16 | var sb = new StringBuilder(GetCommonColumns(attributeMetadata)); 17 | var attribute = (DateTimeAttributeMetadata)attributeMetadata; 18 | 19 | sb.Append(CreatePropertyAttribute(attribute.DateTimeBehavior.Value, CommandArgsConstants.DATETIME_BEHAVIOR)); 20 | if(attribute.Format.HasValue) 21 | sb.Append(CreatePropertyAttribute(((DateTimeFormat)attribute.Format).ToString(), CommandArgsConstants.DATETIME_FORMAT)); 22 | if(attribute.ImeMode.HasValue) 23 | sb.Append(CreatePropertyAttribute(((ImeMode)attribute.ImeMode).ToString(), CommandArgsConstants.IME_MODE)); 24 | 25 | return sb.ToString(); 26 | } 27 | 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Builders/AttributeMetadataScriptBuilderDecimal.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | using System.Text; 3 | 4 | namespace Greg.Xrm.Command.Commands.Table.Builders 5 | { 6 | internal class AttributeMetadataScriptBuilderDecimal : AttributeMetadataScriptBuilderBase 7 | { 8 | public override string GetColumnScript(AttributeMetadata attributeMetadata) 9 | { 10 | var sb = new StringBuilder(GetCommonColumns(attributeMetadata)); 11 | var attribute = (DecimalAttributeMetadata)attributeMetadata; 12 | 13 | if (attribute.Precision.HasValue) 14 | sb.Append(CreatePropertyAttribute(attribute.Precision.Value, CommandArgsConstants.PRECISION)); 15 | if (attribute.ImeMode.HasValue) 16 | sb.Append(CreatePropertyAttribute((ImeMode)attribute.ImeMode.Value, CommandArgsConstants.IME_MODE)); 17 | if (attribute.MinValue.HasValue) 18 | sb.Append(CreatePropertyAttribute(attribute.MinValue.Value, CommandArgsConstants.MIN)); 19 | if (attribute.MaxValue.HasValue) 20 | sb.Append(CreatePropertyAttribute(attribute.MaxValue.Value, CommandArgsConstants.MAX)); 21 | 22 | return sb.ToString(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Builders/AttributeMetadataScriptBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Commands.Column.Builders; 2 | using Microsoft.Xrm.Sdk.Metadata; 3 | 4 | namespace Greg.Xrm.Command.Commands.Table.Builders 5 | { 6 | public class AttributeMetadataScriptBuilderFactory : IAttributeMetadataScriptBuilderFactory 7 | { 8 | 9 | private readonly Dictionary> cache = new(); 10 | 11 | public AttributeMetadataScriptBuilderFactory() 12 | { 13 | cache.Add(AttributeTypeCode.String, () => new AttributeMetadataScriptBuilderString()); 14 | cache.Add(AttributeTypeCode.Picklist, () => new AttributeMetadataScriptBuilderPicklist()); 15 | cache.Add(AttributeTypeCode.Integer, () => new AttributeMetadataScriptBuilderInteger()); 16 | cache.Add(AttributeTypeCode.Money, () => new AttributeMetadataScriptBuilderMoney()); 17 | cache.Add(AttributeTypeCode.Boolean, () => new AttributeMetadataScriptBuilderBoolean()); 18 | cache.Add(AttributeTypeCode.Decimal, () => new AttributeMetadataScriptBuilderDecimal()); 19 | cache.Add(AttributeTypeCode.Memo, () => new AttributeMetadataScriptBuilderMemo()); 20 | cache.Add(AttributeTypeCode.DateTime, () => new AttributeMetadataScriptBuilderDateTime()); 21 | cache.Add(AttributeTypeCode.Virtual, () => new AttributeMetadataScriptBuilderPicklist()); 22 | } 23 | 24 | public IAttributeMetadataScriptBuilder CreateFor(AttributeTypeCode attributeType) 25 | { 26 | if (!cache.TryGetValue(attributeType, out var factory)) 27 | throw new CommandException(CommandException.CommandInvalidArgumentValue, $"The attribute type '{attributeType}' is not supported yet"); 28 | 29 | return factory(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Builders/AttributeMetadataScriptBuilderInteger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Greg.Xrm.Command.Commands.Table.Builders 9 | { 10 | internal class AttributeMetadataScriptBuilderInteger : AttributeMetadataScriptBuilderBase 11 | { 12 | public override string GetColumnScript(AttributeMetadata attributeMetadata) 13 | { 14 | 15 | var sb = new StringBuilder(GetCommonColumns(attributeMetadata)); 16 | var attribute = (IntegerAttributeMetadata)attributeMetadata; 17 | 18 | //sb.Append(CreatePropertyAttribute("integer", CommandArgsConstants.TYPE)); 19 | if(attribute.Format.HasValue) 20 | sb.Append(CreatePropertyAttribute(((IntegerFormat)attribute.Format.Value).ToString(), CommandArgsConstants.INT_FORMAT)); 21 | if(attribute.MinValue.HasValue) 22 | sb.Append(CreatePropertyAttribute(attribute.MinValue.Value, CommandArgsConstants.MIN)); 23 | if(attribute.MaxValue.HasValue) 24 | sb.Append(CreatePropertyAttribute(attribute.MaxValue.Value, CommandArgsConstants.MAX)); 25 | 26 | return sb.ToString(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Builders/AttributeMetadataScriptBuilderMemo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | using System.Text; 3 | 4 | namespace Greg.Xrm.Command.Commands.Table.Builders 5 | { 6 | internal class AttributeMetadataScriptBuilderMemo : AttributeMetadataScriptBuilderBase 7 | { 8 | public override string GetColumnScript(AttributeMetadata attributeMetadata) 9 | { 10 | 11 | var sb = new StringBuilder(GetCommonColumns(attributeMetadata)); 12 | var attribute = (MemoAttributeMetadata)attributeMetadata; 13 | 14 | sb.Append(CreatePropertyAttribute(attribute.Format, CommandArgsConstants.STRING_FORMAT)); 15 | sb.Append(CreatePropertyAttribute(attribute.FormatName, CommandArgsConstants.MEMO_FORMAT)); 16 | sb.Append(CreatePropertyAttribute(attribute.MaxLength, CommandArgsConstants.MAX_LENGTH)); 17 | if(attribute.ImeMode.HasValue) 18 | sb.Append(CreatePropertyAttribute(attribute.ImeMode.Value.ToString(), CommandArgsConstants.IME_MODE)); 19 | 20 | return sb.ToString(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Builders/AttributeMetadataScriptBuilderMoney.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Greg.Xrm.Command.Commands.Table.Builders 9 | { 10 | internal class AttributeMetadataScriptBuilderMoney : AttributeMetadataScriptBuilderBase 11 | { 12 | public override string GetColumnScript(AttributeMetadata attributeMetadata) 13 | { 14 | 15 | var sb = new StringBuilder(GetCommonColumns(attributeMetadata)); 16 | var attribute = (MoneyAttributeMetadata)attributeMetadata; 17 | 18 | //sb.Append(CreatePropertyAttribute("money", CommandArgsConstants.TYPE)); 19 | if (attribute.MinValue.HasValue) 20 | sb.Append(CreatePropertyAttribute(attribute.MinValue.Value, CommandArgsConstants.MIN)); 21 | if (attribute.MaxValue.HasValue) 22 | sb.Append(CreatePropertyAttribute(attribute.MaxValue.Value, CommandArgsConstants.MAX)); 23 | if (attribute.Precision.HasValue) 24 | sb.Append(CreatePropertyAttribute(attribute.Precision.Value, CommandArgsConstants.PRECISION)); 25 | if (attribute.PrecisionSource.HasValue) 26 | sb.Append(CreatePropertyAttribute(attribute.PrecisionSource.Value, CommandArgsConstants.PRECISION_SOURCE)); 27 | if (attribute.ImeMode.HasValue) 28 | sb.Append(CreatePropertyAttribute(((ImeMode)attribute.ImeMode.Value).ToString(), CommandArgsConstants.IME_MODE)); 29 | 30 | return sb.ToString(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Builders/AttributeMetadataScriptBuilderPicklist.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | using System.Text; 3 | 4 | namespace Greg.Xrm.Command.Commands.Table.Builders 5 | { 6 | internal class AttributeMetadataScriptBuilderPicklist : AttributeMetadataScriptBuilderBase 7 | { 8 | public override string GetColumnScript(AttributeMetadata attributeMetadata) 9 | { 10 | //Check multiselect 11 | EnumAttributeMetadata attr = attributeMetadata.AttributeType == AttributeTypeCode.Picklist ? 12 | (PicklistAttributeMetadata)attributeMetadata : 13 | (MultiSelectPicklistAttributeMetadata)attributeMetadata; 14 | 15 | var sb = new StringBuilder(GetCommonColumns(attributeMetadata, AttributeTypeCode.Picklist.ToString())); 16 | 17 | sb.Append(CreatePropertyAttribute(attr.OptionSet.IsGlobal, CommandArgsConstants.GLOBAL_OPTIONSET_NAME)); 18 | sb.Append(CreatePropertyAttribute(string.Join(",", attr.OptionSet.Options.Select(x => x.Label.UserLocalizedLabel.Label).ToArray()), CommandArgsConstants.OPTIONS)); 19 | 20 | if (attr is MultiSelectPicklistAttributeMetadata) 21 | sb.Append(CreatePropertyAttribute(true, CommandArgsConstants.MULTISELECT)); 22 | 23 | return sb.ToString(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Builders/AttributeMetadataScriptBuilderString.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | using System.Text; 3 | 4 | namespace Greg.Xrm.Command.Commands.Table.Builders 5 | { 6 | public class AttributeMetadataScriptBuilderString : AttributeMetadataScriptBuilderBase 7 | { 8 | public override string GetColumnScript(AttributeMetadata attributeMetadata) 9 | { 10 | var sb = new StringBuilder(GetCommonColumns(attributeMetadata)); 11 | var attribute = (StringAttributeMetadata)attributeMetadata; 12 | 13 | //sb.Append(CreatePropertyAttribute("text", CommandArgsConstants.TYPE)); 14 | sb.Append(CreatePropertyAttribute(attribute.MaxLength, CommandArgsConstants.MAX_LENGTH)); 15 | sb.Append(CreatePropertyAttribute(attribute.Format, CommandArgsConstants.STRING_FORMAT)); 16 | sb.Append(CreatePropertyAttribute(attribute.AutoNumberFormat, CommandArgsConstants.AUTONUMBER)); 17 | 18 | return sb.ToString(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Builders/IAttributeMetadataScriptBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | using Microsoft.Xrm.Sdk.Metadata; 3 | 4 | namespace Greg.Xrm.Command.Commands.Table.Builders 5 | { 6 | public interface IAttributeMetadataScriptBuilder 7 | { 8 | 9 | string GetColumnScript(AttributeMetadata attributeMetadata); //, ScriptCommand command 10 | /* 11 | 12 | IOrganizationServiceAsync2 crm, 13 | CreateCommand command, 14 | int languageCode, 15 | string publisherPrefix, 16 | int customizationOptionValuePrefix 17 | */ 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Builders/IAttributeMetadataScriptBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | 3 | namespace Greg.Xrm.Command.Commands.Table.Builders 4 | { 5 | public interface IAttributeMetadataScriptBuilderFactory 6 | { 7 | IAttributeMetadataScriptBuilder CreateFor(AttributeTypeCode attributeType); 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/DeleteCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace Greg.Xrm.Command.Commands.Table 5 | { 6 | [Command("table", "delete", HelpText = "Deletes a table (if possible) from the current Dataverse environment")] 7 | [Alias("delete", "table")] 8 | public class DeleteCommand 9 | { 10 | [Option("name", "n", HelpText = "The schema name of the table to delete")] 11 | [Required] 12 | public string? SchemaName { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/DeleteCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Connection; 2 | using Greg.Xrm.Command.Services.Output; 3 | using Microsoft.Xrm.Sdk.Messages; 4 | 5 | namespace Greg.Xrm.Command.Commands.Table 6 | { 7 | public class DeleteCommandExecutor : ICommandExecutor 8 | { 9 | private readonly IOutput output; 10 | private readonly IOrganizationServiceRepository organizationServiceRepository; 11 | 12 | public DeleteCommandExecutor( 13 | IOutput output, 14 | IOrganizationServiceRepository organizationServiceRepository) 15 | { 16 | this.output = output; 17 | this.organizationServiceRepository = organizationServiceRepository; 18 | } 19 | 20 | 21 | 22 | public async Task ExecuteAsync(DeleteCommand command, CancellationToken cancellationToken) 23 | { 24 | this.output.Write($"Connecting to the current dataverse environment..."); 25 | var crm = await this.organizationServiceRepository.GetCurrentConnectionAsync(); 26 | this.output.WriteLine("Done", ConsoleColor.Green); 27 | 28 | 29 | try 30 | { 31 | output.Write("Deleting table ").Write(command.SchemaName, ConsoleColor.Yellow).Write("..."); 32 | 33 | var request = new DeleteEntityRequest 34 | { 35 | LogicalName = command.SchemaName 36 | }; 37 | 38 | await crm.ExecuteAsync(request); 39 | 40 | output.WriteLine(" Done", ConsoleColor.Green); 41 | return CommandResult.Success(); 42 | } 43 | catch (Exception ex) 44 | { 45 | return CommandResult.Fail(ex.Message, ex); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/ExportMetadata/ExportMetadataCommand.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace Greg.Xrm.Command.Commands.Table.ExportMetadata 5 | { 6 | [Command("table", "exportMetadata", HelpText = "Exports the metadata definition of a given table (for documentation purpose)")] 7 | public class ExportMetadataCommand 8 | { 9 | [Option("table", "t", "The name of the table containing the column to export.")] 10 | [Required] 11 | public string TableSchemaName { get; set; } = string.Empty; 12 | 13 | [Option("what", "w", "The level of details to export.", DefaultValue = EntityFilters.All)] 14 | public EntityFilters What { get; set; } = EntityFilters.All; 15 | 16 | [Option("output", "o", "The name of the folder that will contain the file with the exported metadata. (default: current folder)")] 17 | public string OutputFilePath { get; set; } = string.Empty; 18 | 19 | [Option("run", "r", "Automatically opens the file containing the exported metadata after export.", false)] 20 | public bool AutoOpenFile { get; set; } = false; 21 | 22 | [Option("format", "f", "The format of the exported metadata file.", DefaultValue = ExportMetadataFormat.Json)] 23 | public ExportMetadataFormat Format { get; set; } = ExportMetadataFormat.Json; 24 | } 25 | 26 | 27 | public enum ExportMetadataFormat 28 | { 29 | Json, 30 | Excel 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/ExportMetadata/ExportMetadataStrategyExcel.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Output; 2 | using Microsoft.Xrm.Sdk.Metadata; 3 | using OfficeOpenXml; 4 | 5 | namespace Greg.Xrm.Command.Commands.Table.ExportMetadata 6 | { 7 | public class ExportMetadataStrategyExcel : IExportMetadataStrategy 8 | { 9 | private readonly IOutput output; 10 | private readonly List sheetWriterList = new(); 11 | 12 | public ExportMetadataStrategyExcel(IOutput output) 13 | { 14 | this.output = output; 15 | this.sheetWriterList.Add(new ExcelMetadataSheetWriterTable()); 16 | this.sheetWriterList.Add(new ExcelMetadataSheetWriterColumns()); 17 | } 18 | 19 | 20 | public async Task ExportAsync(EntityMetadata entityMetadata, string outputFolder) 21 | { 22 | var fileName = $"{entityMetadata.SchemaName}.xlsx"; 23 | var filePath = Path.Combine(outputFolder, fileName); 24 | 25 | 26 | 27 | try 28 | { 29 | using var package = new ExcelPackage(); 30 | 31 | foreach (var writer in this.sheetWriterList) 32 | { 33 | writer.Write(package, entityMetadata); 34 | } 35 | 36 | await package.SaveAsAsync(filePath); 37 | return filePath; 38 | } 39 | catch (Exception ex) 40 | { 41 | output.WriteLine() 42 | .Write("Error while trying to write on the generated file: ", ConsoleColor.Red) 43 | .WriteLine(ex.Message, ConsoleColor.Red); 44 | 45 | return null; 46 | } 47 | 48 | } 49 | 50 | 51 | } 52 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/ExportMetadata/ExportMetadataStrategyFactory.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Output; 2 | 3 | namespace Greg.Xrm.Command.Commands.Table.ExportMetadata 4 | { 5 | public class ExportMetadataStrategyFactory : IExportMetadataStrategyFactory 6 | { 7 | private readonly IOutput output; 8 | 9 | public ExportMetadataStrategyFactory(IOutput output) 10 | { 11 | this.output = output ?? throw new ArgumentNullException(nameof(output)); 12 | } 13 | 14 | 15 | public IExportMetadataStrategy Create(ExportMetadataFormat format) 16 | { 17 | switch (format) 18 | { 19 | case ExportMetadataFormat.Json: 20 | return new ExportMetadataStrategyJson(output); 21 | case ExportMetadataFormat.Excel: 22 | return new ExportMetadataStrategyExcel(output); 23 | default: 24 | throw new CommandException(CommandException.CommandInvalidArgumentValue, $"The format '{format}' is not supported."); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/ExportMetadata/ExportMetadataStrategyJson.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Output; 2 | using Microsoft.Xrm.Sdk.Metadata; 3 | using Newtonsoft.Json; 4 | 5 | namespace Greg.Xrm.Command.Commands.Table.ExportMetadata 6 | { 7 | public class ExportMetadataStrategyJson : IExportMetadataStrategy 8 | { 9 | private readonly IOutput output; 10 | 11 | public ExportMetadataStrategyJson(IOutput output) 12 | { 13 | this.output = output; 14 | } 15 | 16 | 17 | public async Task ExportAsync(EntityMetadata entityMetadata, string outputFolder) 18 | { 19 | var text = JsonConvert.SerializeObject(entityMetadata, Formatting.Indented); 20 | 21 | 22 | var fileName = $"{entityMetadata.SchemaName}.json"; 23 | var filePath = Path.Combine(outputFolder, fileName); 24 | 25 | try 26 | { 27 | await File.WriteAllTextAsync(filePath, text); 28 | 29 | return filePath; 30 | } 31 | catch (Exception ex) 32 | { 33 | output.WriteLine() 34 | .Write("Error while trying to write on the generated file: ", ConsoleColor.Red) 35 | .WriteLine(ex.Message, ConsoleColor.Red); 36 | 37 | return null; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/ExportMetadata/IExcelMetadataSheetWriter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | using OfficeOpenXml; 3 | 4 | namespace Greg.Xrm.Command.Commands.Table.ExportMetadata 5 | { 6 | public interface IExcelMetadataSheetWriter 7 | { 8 | void Write(ExcelPackage package, EntityMetadata entityMetadata); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/ExportMetadata/IExportMetadataStrategy.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Metadata; 2 | 3 | namespace Greg.Xrm.Command.Commands.Table.ExportMetadata 4 | { 5 | public interface IExportMetadataStrategy 6 | { 7 | Task ExportAsync(EntityMetadata entityMetadata, string outputFolder); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/ExportMetadata/IExportMetadataStrategyFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Table.ExportMetadata 2 | { 3 | public interface IExportMetadataStrategyFactory 4 | { 5 | IExportMetadataStrategy Create(ExportMetadataFormat format); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Help.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Table 4 | { 5 | public class Help : NamespaceHelperBase 6 | { 7 | public Help() : base("Execute manipulations on Dataverse tables", "table") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Migration/IMigrationAction.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Table.Migration 2 | { 3 | public interface IMigrationAction 4 | { 5 | string TableName { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Migration/MigrationActionFullTable.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Table.Migration 2 | { 3 | public class MigrationActionFullTable : IMigrationAction 4 | { 5 | public MigrationActionFullTable(string? tableName) 6 | { 7 | if (string.IsNullOrWhiteSpace(tableName)) 8 | { 9 | throw new ArgumentNullException(nameof(tableName), $"'{nameof(tableName)}' cannot be null or empty."); 10 | } 11 | 12 | TableName = tableName.ToLowerInvariant(); 13 | } 14 | 15 | public string TableName { get; } 16 | 17 | public override string ToString() 18 | { 19 | return $"Full import on table <{TableName}>"; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Migration/MigrationActionLog.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Table.Migration 2 | { 3 | internal class MigrationActionLog : IMigrationAction 4 | { 5 | private readonly string message; 6 | 7 | public MigrationActionLog(string message) 8 | { 9 | this.message = message; 10 | } 11 | 12 | public string TableName => string.Empty; 13 | 14 | public override string ToString() 15 | { 16 | return message; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Migration/MigrationActionTableWithoutColumn.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Table.Migration 2 | { 3 | public class MigrationActionTableWithoutColumn : IMigrationAction 4 | { 5 | public MigrationActionTableWithoutColumn(string? tableName, string columnName) 6 | { 7 | if (string.IsNullOrEmpty(tableName)) 8 | { 9 | throw new ArgumentException($"'{nameof(tableName)}' cannot be null or empty.", nameof(tableName)); 10 | } 11 | 12 | if (string.IsNullOrEmpty(columnName)) 13 | { 14 | throw new ArgumentException($"'{nameof(columnName)}' cannot be null or empty.", nameof(columnName)); 15 | } 16 | 17 | TableName = tableName; 18 | ColumnName = columnName; 19 | } 20 | 21 | public string TableName { get; } 22 | 23 | public string ColumnName { get; } 24 | 25 | public string[] GetRelatedTableNames() 26 | { 27 | return ColumnName 28 | .Split(",") 29 | .Select(x => x.Trim()) 30 | .Select(x => 31 | { 32 | var startIndex = x.IndexOf("(") + 1; 33 | var len = x.IndexOf(")") - startIndex; 34 | return x.Substring(startIndex, len); 35 | }) 36 | .Distinct() 37 | .ToArray(); 38 | } 39 | 40 | 41 | public override string ToString() 42 | { 43 | return $"Import table <{TableName}> without column(s) <{ColumnName}>"; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Migration/MigrationActionUpdateTableColumn.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Table.Migration 2 | { 3 | public class MigrationActionUpdateTableColumn : IMigrationAction 4 | { 5 | public MigrationActionUpdateTableColumn(string? tableName, string columnName) 6 | { 7 | if (string.IsNullOrEmpty(tableName)) 8 | { 9 | throw new ArgumentException($"'{nameof(tableName)}' cannot be null or empty.", nameof(tableName)); 10 | } 11 | 12 | if (string.IsNullOrEmpty(columnName)) 13 | { 14 | throw new ArgumentException($"'{nameof(columnName)}' cannot be null or empty.", nameof(columnName)); 15 | } 16 | 17 | TableName = tableName; 18 | ColumnName = columnName; 19 | } 20 | 21 | public string TableName { get; } 22 | 23 | public string ColumnName { get; } 24 | 25 | public string[] GetRelatedTableNames() 26 | { 27 | return ColumnName 28 | .Split(",") 29 | .Select(x => x.Trim()) 30 | .Select(x => 31 | { 32 | var startIndex = x.IndexOf("(") + 1; 33 | var len = x.IndexOf(")") - startIndex; 34 | return x.Substring(startIndex, len); 35 | }) 36 | .Distinct() 37 | .ToArray(); 38 | } 39 | 40 | 41 | public override string ToString() 42 | { 43 | return $"Update table <{TableName}> to set column(s) <{ColumnName}>"; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Migration/MigrationStrategyResult.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Table.Migration 2 | { 3 | public class MigrationStrategyResult 4 | { 5 | private MigrationStrategyResult(string errorMessage) 6 | { 7 | ErrorMessage = errorMessage; 8 | } 9 | 10 | 11 | public MigrationStrategyResult() 12 | { 13 | ErrorMessage = string.Empty; 14 | } 15 | 16 | public static MigrationStrategyResult Error(string errorMessage) => new MigrationStrategyResult(errorMessage); 17 | 18 | 19 | public bool HasError => !string.IsNullOrWhiteSpace(ErrorMessage); 20 | public string ErrorMessage { get; private set; } 21 | 22 | public List MigrationActions { get; } = new List(); 23 | 24 | public MigrationStrategyResult Add(string? tableName) 25 | { 26 | return Add(new MigrationActionFullTable(tableName)); 27 | } 28 | 29 | public MigrationStrategyResult Add(IMigrationAction migrationAction) 30 | { 31 | MigrationActions.Add(migrationAction); 32 | return this; 33 | } 34 | 35 | internal void SetError(string errorMessage) 36 | { 37 | ErrorMessage = errorMessage; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Migration/MissingTableCache.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace Greg.Xrm.Command.Commands.Table.Migration 4 | { 5 | public class MissingTableCache 6 | { 7 | private readonly Dictionary> cache = new(); 8 | 9 | public void Add(string tableName, string referencedByName) 10 | { 11 | if (!cache.ContainsKey(tableName)) 12 | { 13 | cache[tableName] = new List(); 14 | } 15 | 16 | if (cache[tableName].Contains(referencedByName)) 17 | { 18 | return; 19 | } 20 | 21 | cache[tableName].Add(referencedByName); 22 | } 23 | 24 | 25 | public bool HasMissingTables => cache.Count > 0; 26 | 27 | public override string ToString() 28 | { 29 | var sb = new StringBuilder(); 30 | sb.AppendLine("The following tables are missing from the list:"); 31 | foreach (var item in cache) 32 | { 33 | sb.AppendLine($" - {item.Key} is referenced by {string.Join(", ", item.Value.Order())}"); 34 | } 35 | 36 | return sb.ToString(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Migration/SecurityTables.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Table.Migration 2 | { 3 | static class SecurityTables 4 | { 5 | public static string[] SecurityTableNames { get; } = new[] { "systemuser", "businessunit", "team", "organization", "fieldsecurityprofile", "position" }; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/Migration/TableModel.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services.Graphs; 2 | 3 | namespace Greg.Xrm.Command.Commands.Table.Migration; 4 | 5 | public class TableModel : INodeContent 6 | { 7 | private readonly string name; 8 | 9 | public TableModel(string name) 10 | { 11 | this.name = name.ToLowerInvariant(); 12 | } 13 | 14 | public object Key => name; 15 | 16 | 17 | public override string ToString() 18 | { 19 | return name; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/ScriptCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Table 4 | { 5 | [Command("table", "script", HelpText = "Creates a new script for a table to be copied somewhere else")] 6 | [Alias("script", "table")] 7 | public class ScriptCommand 8 | { 9 | [Option("schemaName", "sn")] 10 | public string? SchemaName { get; set; } 11 | 12 | [Option("output", "o", "The name of the folder that will contain the file with the exported metadata. (default: current folder)")] 13 | public string OutputFilePath { get; set; } = string.Empty; 14 | 15 | [Option("run", "r", "Automatically opens the file containing the exported metadata after export.", false)] 16 | public bool AutoOpenFile { get; set; } = false; 17 | 18 | [Option("includeTable", "it", "Includes into the resulting file the script command to create table.", false)] 19 | public bool IncludeTable { get; set; } = false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Table/TablePrintMermaidCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.Table 2 | { 3 | [Command("table", "print", HelpText = "Returns the Mermaid (https://mermaid.js.org/) classDiagram representation of the set of tables contained in a given solution")] 4 | public class TablePrintMermaidCommand 5 | { 6 | [Option("solution", "s", HelpText = "The name of the solution containing the entities to export. If not specified, the default solution is used instead.")] 7 | public string? SolutionName { get; set; } 8 | 9 | [Option("include-security-tables", "ist", HelpText = "If false, the security tables (organization, systemuser, businessunit, team, position, fieldsecurityprofile) are not taken consideration in the export.", DefaultValue = false)] 10 | public bool IncludeSecurityTables { get; set; } = false; 11 | 12 | [Option("skip-missing-tables", "skip", HelpText = "If true, the command will not fail if some tables are missing in the solution. The missing tables will be skipped.", DefaultValue = false)] 13 | public bool SkipMissingTables { get; set; } = false; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/UnifiedRouting/GetAgentStatusCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | using Greg.Xrm.Command.Services; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace Greg.Xrm.Command.Commands.UnifiedRouting 6 | { 7 | [Command("unifiedrouting", "agentStatus", HelpText = "Get the agent status with the primary email/domain name provided. Optionally, you can specify a date in order to get agent status at that time. It uses the Dataverse environment selected using `pacx auth select`")] 8 | [Alias("ur","status")] 9 | public class GetAgentStatusCommand : ICanProvideUsageExample 10 | { 11 | [Option("agentPrimaryEmail", "a", "Agent primary email (or domain name) used to perform the query.")] 12 | [Required] 13 | public string? AgentPrimaryEmail { get; set; } 14 | 15 | [Option("dateTime", "t", "Date and time (local time) used to perform the query. Format dd/MM/yyyy HH:mm.")] 16 | public string? DateTimeFilter { get; set; } 17 | 18 | public void WriteUsageExamples(MarkdownWriter writer) 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/UnifiedRouting/GetQueueStatusCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | using Greg.Xrm.Command.Services; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace Greg.Xrm.Command.Commands.UnifiedRouting 6 | { 7 | [Command("unifiedrouting", "queueStatus", HelpText = "List the agents in the queue provided. Optionally, you can specify a date in order to list agents status at that time. It uses the Dataverse environment selected using `pacx auth select`")] 8 | [Alias("ur","queueStatus")] 9 | public class GetQueueStatusCommand : ICanProvideUsageExample 10 | { 11 | [Option("queue", "q", "Queue name used to perform the query.")] 12 | [Required] 13 | public string? Queue { get; set; } 14 | 15 | [Option("dateTime", "t", "Date and time (local time) used to perform the query. Format dd/MM/yyyy HH:mm.")] 16 | public string? DateTimeFilter { get; set; } 17 | 18 | public void WriteUsageExamples(MarkdownWriter writer) 19 | { 20 | 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/UnifiedRouting/Help.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.UnifiedRouting 4 | { 5 | public class Help : NamespaceHelperBase 6 | { 7 | public Help() : base("Execute manipulations on unified routing settings", "unifiedrouting") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/UnifiedRouting/Model/AgentStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.UnifiedRouting.Model 2 | { 3 | class AgentStatus 4 | { 5 | public string? UserEmail { get; set; } 6 | public string? UserFullName { get; set; } 7 | public string? Status { get; set; } 8 | public int? StatusCode { get; set; } 9 | public DateTime? DateStart { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/UnifiedRouting/Model/AgentStatusHistory.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.UnifiedRouting.Model 2 | { 3 | #pragma warning disable IDE1006 // Naming Styles 4 | public static class msdyn_agentstatushistory 5 | { 6 | public static string msdyn_agentstatushistoryid => "msdyn_agentstatushistoryid"; 7 | public static string createdon => "createdon"; 8 | public static string msdyn_starttime => "msdyn_starttime"; 9 | public static string msdyn_presenceid => "msdyn_presenceid"; 10 | public static string msdyn_endtime => "msdyn_endtime"; 11 | public static string msdyn_availablecapacity => "msdyn_availablecapacity"; 12 | public static string msdyn_agentid => "msdyn_agentid"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/UnifiedRouting/Model/Presence.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.UnifiedRouting.Model 2 | { 3 | #pragma warning disable IDE1006 // Naming Styles 4 | public static class msdyn_presence 5 | { 6 | public static string msdyn_presenceid => "msdyn_presenceid"; 7 | public static string msdyn_presencestatustext => "msdyn_presencestatustext"; 8 | public static string msdyn_basepresencestatus => "msdyn_basepresencestatus"; 9 | 10 | 11 | public enum AgentStatuses 12 | { 13 | Available = 192360000, 14 | Busy = 192360001, 15 | BusyDND = 192360002, 16 | Away = 192360003 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/UnifiedRouting/Model/Queue.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.UnifiedRouting.Model 2 | { 3 | #pragma warning disable IDE1006 // Naming Styles 4 | #pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language. 5 | #pragma warning disable S101 // Types should be named in PascalCase 6 | public static class queue 7 | { 8 | public static string queueid => "queueid"; 9 | public static string name => "name"; 10 | } 11 | 12 | public static class queuemembership 13 | { 14 | public static string queueid => "queueid"; 15 | public static string systemuserid => "systemuserid"; 16 | 17 | } 18 | #pragma warning restore S101 // Types should be named in PascalCase 19 | #pragma warning restore CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language. 20 | #pragma warning restore IDE1006 // Naming Styles 21 | } 22 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/UnifiedRouting/Model/Systemuser.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.UnifiedRouting.Model 2 | { 3 | #pragma warning disable IDE1006 // Naming Styles 4 | #pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language. 5 | #pragma warning disable S101 // Types should be named in PascalCase 6 | public static class systemuser 7 | { 8 | public static string systemuserid => "systemuserid"; 9 | public static string internalemailaddress => "internalemailaddress"; 10 | public static string fullname => "fullname"; 11 | public static string domainname => "domainname"; 12 | } 13 | #pragma warning restore S101 // Types should be named in PascalCase 14 | #pragma warning restore CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language. 15 | #pragma warning restore IDE1006 // Naming Styles 16 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Views/CloneCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Views 4 | { 5 | [Command("view", "clone", HelpText = "Creates a copy of a given view.")] 6 | public class CloneCommand 7 | { 8 | [Option("old", "o", HelpText = "The display name of the view to clone.")] 9 | [Required] 10 | public string OldName { get; set; } = string.Empty; 11 | 12 | [Option("new", "n", HelpText = "The new name of the view. If not provided, a suffix will be set by default.")] 13 | public string NewName { get; set; } = string.Empty; 14 | 15 | 16 | [Option("table", "t", HelpText = "The name of the table that contains the view. Required only if the view name is not unique in the system.")] 17 | public string? TableName { get; set; } 18 | 19 | 20 | [Option("type", "q", HelpText = "The type of query.", DefaultValue = QueryType1.SavedQuery)] 21 | public QueryType1 QueryType { get; set; } = QueryType1.SavedQuery; 22 | 23 | 24 | [Option("clean", "c", HelpText = "Indicates that during the clone operation, all the filters applied on the previous view must be removed from the new view.")] 25 | public bool Clean { get; set; } = false; 26 | 27 | 28 | [Option("solution", "s", HelpText = "Specifies the name of the solution that will contain the view after the creation. If not specified, the default solution for the current environment is used.")] 29 | public string? SolutionName { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Views/DeleteCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Views 4 | { 5 | [Command("view", "delete", HelpText = "Deletes a given view")] 6 | public class DeleteCommand 7 | { 8 | [Option("name", "n", HelpText = "The display name of the view to delete.")] 9 | [Required] 10 | public string ViewName { get; set; } = string.Empty; 11 | 12 | [Option("table", "t", HelpText = "The name of the table that contains the view. Required only if the view name is not unique in the system.")] 13 | public string? TableName { get; set; } 14 | 15 | [Option("type", "q", HelpText = "The type of query.", DefaultValue = QueryType1.SavedQuery)] 16 | public QueryType1 QueryType { get; set; } = QueryType1.SavedQuery; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Views/GetCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Views 4 | { 5 | [Command("view", "get", HelpText = "Retrieves the definition (fetchxml and layoutxml) of a given view")] 6 | public class GetCommand 7 | { 8 | [Option("name", "n", HelpText = "The display name of the view to retrieve.")] 9 | [Required] 10 | public string ViewName { get; set; } = string.Empty; 11 | 12 | [Option("table", "t", HelpText = "The name of the table that contains the view. Required only if the view name is not unique in the system.")] 13 | public string? TableName { get; set; } 14 | 15 | [Option("type", "q", HelpText = "The type of query.", DefaultValue = QueryType1.SavedQuery)] 16 | public QueryType1 QueryType { get; set; } = QueryType1.SavedQuery; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Views/GetCommandExecutor.cs: -------------------------------------------------------------------------------- 1 |  2 | using Greg.Xrm.Command.Commands.Views.Model; 3 | using Greg.Xrm.Command.Services.Connection; 4 | using Greg.Xrm.Command.Services.Output; 5 | using System.Xml.Linq; 6 | 7 | namespace Greg.Xrm.Command.Commands.Views 8 | { 9 | public class GetCommandExecutor( 10 | IOrganizationServiceRepository organizationServiceRepository, 11 | IOutput output, 12 | IViewRetrieverService viewRetriever 13 | ) 14 | : ICommandExecutor 15 | { 16 | public async Task ExecuteAsync(GetCommand command, CancellationToken cancellationToken) 17 | { 18 | output.Write($"Connecting to the current dataverse environment..."); 19 | var crm = await organizationServiceRepository.GetCurrentConnectionAsync(); 20 | output.WriteLine("Done", ConsoleColor.Green); 21 | 22 | var (result, view) = await viewRetriever.GetByNameAsync(crm, command.QueryType, command.ViewName, command.TableName); 23 | if (view == null) return result; 24 | 25 | 26 | if (!string.IsNullOrWhiteSpace(view.layoutxml)) { 27 | 28 | output.WriteLine() 29 | .WriteLine("--- LayoutXml ---------------------------------------------") 30 | .WriteLine(); 31 | try 32 | { 33 | output.WriteLine(XDocument.Parse(view.layoutxml)); 34 | } 35 | catch(Exception ex) 36 | { 37 | output.WriteLine(ex.ToString()); 38 | 39 | output.WriteLine(view.layoutxml, ConsoleColor.Red); 40 | } 41 | } 42 | 43 | if (!string.IsNullOrWhiteSpace(view.fetchxml)) 44 | { 45 | output.WriteLine() 46 | .WriteLine("--- FetchXml ----------------------------------------------") 47 | .WriteLine(); 48 | try 49 | { 50 | output.WriteLine(XDocument.Parse(view.fetchxml)); 51 | } 52 | catch 53 | { 54 | output.WriteLine(view.fetchxml, ConsoleColor.Red); 55 | } 56 | } 57 | 58 | return CommandResult.Success(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Views/Help.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.Views 4 | { 5 | public class Help : NamespaceHelperBase 6 | { 7 | public Help() : base("Execute manipulations on Dataverse views", "view") 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Views/ListCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Views 4 | { 5 | [Command("view", "list", HelpText = "List all the views available for a given Dataverse table")] 6 | public class ListCommand 7 | { 8 | [Option("table", "t", HelpText = "The table name for which to list the views")] 9 | [Required] 10 | public string TableName { get; set; } = string.Empty; 11 | 12 | 13 | [Option("type", "q", HelpText = "The type of query to list.", DefaultValue = QueryType.SavedQuery)] 14 | public QueryType QueryType { get; set; } = QueryType.SavedQuery; 15 | } 16 | 17 | 18 | public enum QueryType 19 | { 20 | SavedQuery, 21 | UserQuery, 22 | Both 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Views/Model/IViewRetrieverService.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Model; 2 | using Microsoft.PowerPlatform.Dataverse.Client; 3 | 4 | namespace Greg.Xrm.Command.Commands.Views.Model 5 | { 6 | public interface IViewRetrieverService { 7 | Task<(CommandResult, TableView?)> GetByNameAsync( 8 | IOrganizationServiceAsync2 crm, 9 | QueryType1 queryType, 10 | string viewName, 11 | string? tableName); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Views/Model/XmlNodeListEmpty.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Xml; 3 | 4 | namespace Greg.Xrm.Command.Commands.Views.Model 5 | { 6 | class XmlNodeListEmpty : XmlNodeList 7 | { 8 | public static XmlNodeListEmpty Instance { get; } = new XmlNodeListEmpty(); 9 | 10 | 11 | private XmlNodeListEmpty() 12 | { 13 | 14 | } 15 | 16 | public override int Count => 0; 17 | 18 | public override IEnumerator GetEnumerator() 19 | { 20 | return new List().GetEnumerator(); 21 | } 22 | 23 | public override XmlNode? Item(int index) 24 | { 25 | return null; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/Views/RenameCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Greg.Xrm.Command.Commands.Views 4 | { 5 | [Command("view", "rename", HelpText = "Changes the name of a given view")] 6 | public class RenameCommand 7 | { 8 | [Option("old", "o", HelpText = "The display name of the view to rename.")] 9 | [Required] 10 | public string OldName { get; set; } = string.Empty; 11 | 12 | 13 | [Option("new", "n", HelpText = "The new name of the view.")] 14 | [Required] 15 | public string NewName { get; set; } = string.Empty; 16 | 17 | 18 | [Option("table", "t", HelpText = "The name of the table that contains the view. Required only if the view name is not unique in the system.")] 19 | public string? TableName { get; set; } 20 | 21 | [Option("type", "q", HelpText = "The type of query.", DefaultValue = QueryType1.SavedQuery)] 22 | public QueryType1 QueryType { get; set; } = QueryType1.SavedQuery; 23 | } 24 | 25 | 26 | 27 | 28 | public enum QueryType1 29 | { 30 | SavedQuery, 31 | UserQuery 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/AddReferenceCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | using Greg.Xrm.Command.Services; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace Greg.Xrm.Command.Commands.WebResources 6 | { 7 | [Command("webresources", "addReference", HelpText = "Adds an external web resource to the current webresource project **as a reference**, without copying it locally.")] 8 | [Alias("webresources", "add-reference")] 9 | [Alias("wr", "add-reference")] 10 | [Alias("wr", "add-ref")] 11 | [Alias("wr", "addRef")] 12 | public class AddReferenceCommand : ICanProvideUsageExample 13 | { 14 | [Option("source", "src", HelpText = "The absolute or relative URL of the web resource to add as reference to the current project.")] 15 | [Required] 16 | public string Source { get; set; } = string.Empty; 17 | 18 | [Option("target", "tgt", HelpText = "The target URL of the webresource, relative to the root of the dataverse WebResources. It must include the publisher prefix.")] 19 | [Required] 20 | public string Target { get; set; } = string.Empty; 21 | 22 | [Option("path", HelpText = "The folder containing the .wr.pacx project where the reference should be added. If not specified, the command will find it recoursing up from the current folder.")] 23 | public string? Path { get; set; } 24 | 25 | [Option("solution", "s", HelpText = "The name of the solution that will contain the WebResources. If empty, the default solution for the current environment is used as default")] 26 | public string? SolutionName { get; set; } 27 | 28 | 29 | 30 | 31 | public void WriteUsageExamples(MarkdownWriter writer) 32 | { 33 | 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/ApplyIconsRules/IIconFinder.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.WebResources.ApplyIconsRules 2 | { 3 | public interface IIconFinder 4 | { 5 | string? Find(IReadOnlyCollection icons, string tableName, string publisherPrefix); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/ApplyIconsRules/IconFinder.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.WebResources.ApplyIconsRules 2 | { 3 | public class IconFinder : IIconFinder 4 | { 5 | private readonly List rules = new(); 6 | 7 | public IconFinder() 8 | { 9 | this.rules.Add(new RuleToMatchTableName()); 10 | this.rules.Add(new RuleToMatchTableNameWithPrefix()); 11 | this.rules.Add(new RuleToMatchImagesFolder()); 12 | this.rules.Add(new RuleToMatchNameEnding()); 13 | } 14 | 15 | 16 | public string? Find(IReadOnlyCollection icons, string tableName, string publisherPrefix) 17 | { 18 | foreach (var rule in this.rules) 19 | { 20 | var icon = rule.Find(icons, tableName, publisherPrefix); 21 | if (icon != null) return icon; 22 | } 23 | return null; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/ApplyIconsRules/RuleToMatchImagesFolder.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.WebResources.ApplyIconsRules 2 | { 3 | class RuleToMatchImagesFolder : IIconFinder 4 | { 5 | public string? Find(IReadOnlyCollection icons, string tableName, string publisherPrefix) 6 | { 7 | if (icons == null) throw new ArgumentNullException(nameof(icons)); 8 | if (tableName == null) throw new ArgumentNullException(nameof(tableName)); 9 | if (publisherPrefix == null) throw new ArgumentNullException(nameof(publisherPrefix)); 10 | 11 | var icon = icons.FirstOrDefault(x => string.Equals(x, $"{publisherPrefix}_/images/{tableName}.svg", StringComparison.OrdinalIgnoreCase)); 12 | return icon; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/ApplyIconsRules/RuleToMatchNameEnding.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.WebResources.ApplyIconsRules 2 | { 3 | class RuleToMatchNameEnding : IIconFinder 4 | { 5 | public string? Find(IReadOnlyCollection icons, string tableName, string publisherPrefix) 6 | { 7 | if (icons == null) throw new ArgumentNullException(nameof(icons)); 8 | if (tableName == null) throw new ArgumentNullException(nameof(tableName)); 9 | if (publisherPrefix == null) throw new ArgumentNullException(nameof(publisherPrefix)); 10 | 11 | var icon = icons.FirstOrDefault(x => x.EndsWith($"/{tableName}.svg", StringComparison.OrdinalIgnoreCase)); 12 | return icon; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/ApplyIconsRules/RuleToMatchTableName.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.WebResources.ApplyIconsRules 2 | { 3 | class RuleToMatchTableName : IIconFinder 4 | { 5 | public string? Find(IReadOnlyCollection icons, string tableName, string publisherPrefix) 6 | { 7 | if (icons == null) throw new ArgumentNullException(nameof(icons)); 8 | if (tableName == null) throw new ArgumentNullException(nameof(tableName)); 9 | if (publisherPrefix == null) throw new ArgumentNullException(nameof(publisherPrefix)); 10 | 11 | var icon = icons.FirstOrDefault(x => string.Equals(x, $"{tableName}.svg", StringComparison.OrdinalIgnoreCase)); 12 | return icon; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/ApplyIconsRules/RuleToMatchTableNameWithPrefix.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.WebResources.ApplyIconsRules 2 | { 3 | class RuleToMatchTableNameWithPrefix : IIconFinder 4 | { 5 | public string? Find(IReadOnlyCollection icons, string tableName, string publisherPrefix) 6 | { 7 | if (icons == null) throw new ArgumentNullException(nameof(icons)); 8 | if (tableName == null) throw new ArgumentNullException(nameof(tableName)); 9 | if (publisherPrefix == null) throw new ArgumentNullException(nameof(publisherPrefix)); 10 | 11 | var icon = icons.FirstOrDefault(x => string.Equals(x, $"{publisherPrefix}_{tableName}.svg", StringComparison.OrdinalIgnoreCase)); 12 | return icon; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/Help.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command.Commands.WebResources 4 | { 5 | public class Help : NamespaceHelperBase 6 | { 7 | public Help() : base("Commands to work with webresources", "webresources") 8 | { 9 | } 10 | } 11 | 12 | 13 | public class Help2 : NamespaceHelperBase 14 | { 15 | public Help2() : base("(Preview) Commands that can be used to create webresource files starting from templates", "webresources", "create") 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/JsCreateCommand.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Greg.Xrm.Command.Commands.WebResources 3 | { 4 | [Command("webresources", "js", "create", HelpText = "(Preview) Creates a new Javascript webresource from a template")] 5 | [Alias("wr", "js", "create")] 6 | [Alias("wr", "create", "js")] 7 | [Alias("webresources", "create", "js")] 8 | public class JsCreateCommand 9 | { 10 | [Option("for", "f", HelpText = "Indicates if the JS web resource to create is for a form, a ribbon command, or other", DefaultValue = JavascriptWebResourceType.Form)] 11 | public JavascriptWebResourceType Type { get; set; } = JavascriptWebResourceType.Form; 12 | 13 | [Option("table", "t", HelpText = "Name of the table related to the JS. Mandatory for form JS. Optional for Ribbon JS (if not specified, is assumed as a global ribbon command). Must not be specified for Other JS.")] 14 | public string? TableName { get; set; } 15 | 16 | [Option("namespace", "ns", HelpText = "Namespace for the generated webresources. If not specified, the **uniquename** of the default solution publisher will be used.")] 17 | public string? Namespace { get; set; } 18 | 19 | [Option("solution", "s", HelpText = "The name of the solution that will contain the creted WebResource. Is used to deduct the namespace, if not explicitly set.")] 20 | public string? SolutionName { get; set; } 21 | } 22 | 23 | 24 | 25 | 26 | public enum JavascriptWebResourceType 27 | { 28 | Form, 29 | Ribbon, 30 | Other 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/JsResetTemplateCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Commands.WebResources.Templates; 2 | using Greg.Xrm.Command.Services.Output; 3 | 4 | namespace Greg.Xrm.Command.Commands.WebResources 5 | { 6 | public class JsResetTemplateCommandExecutor : ICommandExecutor 7 | { 8 | private readonly IOutput output; 9 | private readonly IJsTemplateManager jsTemplateManager; 10 | 11 | public JsResetTemplateCommandExecutor( 12 | IOutput output, 13 | IJsTemplateManager jsTemplateManager) 14 | { 15 | this.output = output; 16 | this.jsTemplateManager = jsTemplateManager; 17 | } 18 | 19 | public async Task ExecuteAsync(JsResetTemplateCommand command, CancellationToken cancellationToken) 20 | { 21 | this.output.Write("Resetting default template..."); 22 | var global = command.Type == JavascriptWebResourceType.Ribbon && !command.ForTable; 23 | await this.jsTemplateManager.ResetTemplateForAsync(command.Type, global); 24 | this.output.WriteLine("Done", ConsoleColor.Green); 25 | 26 | return CommandResult.Success(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/JsSetTemplateCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Commands.WebResources.Templates; 2 | using Greg.Xrm.Command.Services.Output; 3 | 4 | namespace Greg.Xrm.Command.Commands.WebResources 5 | { 6 | public class JsSetTemplateCommandExecutor : ICommandExecutor 7 | { 8 | private readonly IOutput output; 9 | private readonly IJsTemplateManager jsTemplateManager; 10 | 11 | public JsSetTemplateCommandExecutor( 12 | IOutput output, 13 | IJsTemplateManager jsTemplateManager) 14 | { 15 | this.output = output; 16 | this.jsTemplateManager = jsTemplateManager; 17 | } 18 | 19 | 20 | public async Task ExecuteAsync(JsSetTemplateCommand command, CancellationToken cancellationToken) 21 | { 22 | string templateContent; 23 | try 24 | { 25 | this.output.Write("Reading template contents..."); 26 | templateContent = await File.ReadAllTextAsync(command.FileName, cancellationToken); 27 | this.output.WriteLine("Done", ConsoleColor.Green); 28 | } 29 | catch(Exception ex) 30 | { 31 | this.output.WriteLine("ERROR", ConsoleColor.Red); 32 | return CommandResult.Fail("Error while trying to read the template file contents: " + ex.Message, ex); 33 | } 34 | 35 | this.output.Write("Updating default template..."); 36 | var global = command.Type == JavascriptWebResourceType.Ribbon && !command.ForTable; 37 | await this.jsTemplateManager.SetTemplateForAsync(command.Type, global, templateContent); 38 | this.output.WriteLine("Done", ConsoleColor.Green); 39 | 40 | return CommandResult.Success(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/ProjectFile/IWebResourceProjectFileRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.WebResources.ProjectFile 2 | { 3 | public interface IWebResourceProjectFileRepository 4 | { 5 | Task<(bool, ProjectFileV1)> TryReadAsync(DirectoryInfo folder); 6 | Task SaveAsync(DirectoryInfo folder, string publisherPrefix, ProjectFileV1? projectFile = null); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/ProjectFile/ProjectFileV1.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Model; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Converters; 4 | 5 | namespace Greg.Xrm.Command.Commands.WebResources.ProjectFile 6 | { 7 | public class ProjectFileV1 8 | { 9 | public string Version { get; protected set; } = "1"; 10 | 11 | 12 | public List ExternalReferences { get; protected set; } = []; 13 | 14 | public bool ContainsExternalReference(WebResourceMap map) 15 | { 16 | if (map == null) return false; 17 | 18 | return ExternalReferences.Any(x => string.Equals(x.Source, map.Source, StringComparison.OrdinalIgnoreCase)) 19 | || ExternalReferences.Any(x => string.Equals( x.Target, map.Target, StringComparison.OrdinalIgnoreCase)); 20 | } 21 | } 22 | 23 | 24 | public class WebResourceMap 25 | { 26 | public WebResourceMap(string source, string target, WebResourceType type) 27 | { 28 | Source = source; 29 | Target = target; 30 | Type = type; 31 | } 32 | protected WebResourceMap() 33 | { 34 | Source = string.Empty; 35 | Target = string.Empty; 36 | Type = WebResourceType.Data; 37 | } 38 | 39 | 40 | public string Source { get; protected set; } 41 | 42 | public string Target { get; protected set; } 43 | 44 | [JsonConverter(typeof(StringEnumConverter))] 45 | public WebResourceType Type { get; protected set; } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/PushLogic/IFolderResolver.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.WebResources.PushLogic 2 | { 3 | public interface IFolderResolver 4 | { 5 | WebResourceFolders ResolveFrom(string? path, string publisherPrefix); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/PushLogic/IPublishXmlBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Crm.Sdk.Messages; 2 | 3 | namespace Greg.Xrm.Command.Commands.WebResources.PushLogic 4 | { 5 | public interface IPublishXmlBuilder 6 | { 7 | void Clear(); 8 | 9 | void AddWebResource(Guid id); 10 | void AddTable(string tableName); 11 | 12 | PublishXmlRequest? Build(); 13 | } 14 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/PushLogic/IWebResourceFilesResolver.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.WebResources.PushLogic 2 | { 3 | public interface IWebResourceFilesResolver 4 | { 5 | IReadOnlyList ResolveFiles(WebResourceFolders folders); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/PushLogic/WebResourceFile.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Model; 2 | 3 | namespace Greg.Xrm.Command.Commands.WebResources.PushLogic 4 | { 5 | public class WebResourceFile 6 | { 7 | public WebResourceFile(string localPath, string rootFolder, WebResourceType type) 8 | { 9 | this.LocalPath = localPath; 10 | this.RemotePath = localPath.Substring(rootFolder.Length).TrimStart(Path.DirectorySeparatorChar).Replace("\\", "/"); 11 | this.Type = type; 12 | } 13 | 14 | public string LocalPath { get; } 15 | public string RemotePath { get; } 16 | public WebResourceType Type { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/PushLogic/WebResourceFolders.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.WebResources.PushLogic 2 | { 3 | public record WebResourceFolders(string ProjectRootPath, string RequestedPath, string PublisherPrefix); 4 | } 5 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Commands/WebResources/Templates/IJsTemplateManager.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Commands.WebResources.Templates 2 | { 3 | public interface IJsTemplateManager 4 | { 5 | Task GetTemplateForAsync(JavascriptWebResourceType type, bool global); 6 | 7 | Task SetTemplateForAsync(JavascriptWebResourceType type, bool global, string template); 8 | 9 | Task ResetTemplateForAsync(JavascriptWebResourceType type, bool global); 10 | } 11 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/CommonExtensions.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Model; 2 | using Microsoft.Xrm.Sdk; 3 | 4 | namespace Greg.Xrm.Command 5 | { 6 | public static class CommonExtensions 7 | { 8 | 9 | /// 10 | /// Clones a specific entity (excepts the standard attributes - createdon, ... - and the forbidden passed via parameter) 11 | /// 12 | /// The entity to clone 13 | /// The attributes to not to clone 14 | /// A new entity, cloned from the previous one 15 | public static Entity Clone(this Entity original, params string[] forbiddenAttributes) 16 | { 17 | var clone = new Entity(original.LogicalName); 18 | foreach (var attribute in original.Attributes) 19 | { 20 | if (!CloneSettings.IsForbidden(original, attribute.Key, forbiddenAttributes)) 21 | clone[attribute.Key] = attribute.Value; 22 | } 23 | return clone; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Model/DataverseColumnAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Greg.Xrm.Command.Model 4 | { 5 | [AttributeUsage(AttributeTargets.Property, AllowMultiple =false, Inherited =true)] 6 | public class DataverseColumnAttribute : Attribute 7 | { 8 | 9 | 10 | public static string[] GetFromClass() where T : class 11 | { 12 | return GetFromClass(typeof(T)); 13 | } 14 | 15 | public static string[] GetFromClass(Type type) 16 | { 17 | return type.GetProperties().Where(p => p.GetCustomAttribute() != null).Select(p => p.Name).ToArray(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Model/IDependencyRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | 3 | namespace Greg.Xrm.Command.Model 4 | { 5 | public interface IDependencyRepository 6 | { 7 | Task GetDependenciesAsync(IOrganizationServiceAsync2 crm, ComponentType componentType, Guid componentId, bool? forDelete = false); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Model/IProcessTriggerRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | 3 | namespace Greg.Xrm.Command.Model 4 | { 5 | public interface IProcessTriggerRepository 6 | { 7 | Task> GetByWorkflowIdAsync(IOrganizationServiceAsync2 crm, Guid workflowId); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Model/ISavedQueryRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | 3 | namespace Greg.Xrm.Command.Model 4 | { 5 | public interface ISavedQueryRepository 6 | { 7 | Task> GetByIdAsync(IOrganizationServiceAsync2 crm, IEnumerable ids); 8 | Task> GetContainingAsync(IOrganizationServiceAsync2 crm, string tableName, string columnName); 9 | Task> GetByTableNameAsync(IOrganizationServiceAsync2 crm, string tableName); 10 | Task> GetByNameAsync(IOrganizationServiceAsync2 crm, string viewName); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Model/ISolutionRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | using Microsoft.Xrm.Sdk; 3 | 4 | namespace Greg.Xrm.Command.Model 5 | { 6 | public interface ISolutionRepository 7 | { 8 | Task GetByUniqueNameAsync(IOrganizationServiceAsync2 crm, string uniqueName); 9 | 10 | Task CreateTemporarySolutionAsync(IOrganizationServiceAsync2 crm, EntityReference publisherRef); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Model/ITemporarySolution.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Model 2 | { 3 | /// 4 | /// Represents a temporary solution used to store components during a command execution. 5 | /// It should get removed when the command execution is completed. 6 | /// Implements the IDisposable pattern to be used with "using" construct. 7 | /// 8 | public interface ITemporarySolution : IDisposable 9 | { 10 | /// 11 | /// Adds a component to the solution. 12 | /// 13 | /// The unique identifier of the component to add to the solution. 14 | /// The type of component 15 | /// A task 16 | Task AddComponentAsync(Guid componentId, ComponentType componentType); 17 | 18 | 19 | /// 20 | /// Deletes the current solution 21 | /// 22 | Task DeleteAsync(); 23 | 24 | /// 25 | /// Deletes the current solution 26 | /// 27 | void Delete(); 28 | 29 | 30 | /// 31 | /// Downloads the solution as a zip file. 32 | /// 33 | /// 34 | Task DownloadAsync(); 35 | 36 | /// 37 | /// Uploads a new version of a given solution. 38 | /// 39 | /// The zip file to upload 40 | /// The name of the table to which the form belongs 41 | /// 42 | Task UploadAndPublishAsync(byte[] zipFile, params string[] tableNames); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Model/IUserQueryRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | 3 | namespace Greg.Xrm.Command.Model 4 | { 5 | public interface IUserQueryRepository 6 | { 7 | Task> GetByIdAsync(IOrganizationServiceAsync2 crm, IEnumerable ids); 8 | Task> GetContainingAsync(IOrganizationServiceAsync2 crm, string tableName, string columnName); 9 | Task> GetByTableNameAsync(IOrganizationServiceAsync2 crm, string tableName); 10 | Task> GetByNameAsync(IOrganizationServiceAsync2 crm, string viewName); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Model/IWebResourceRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | 3 | namespace Greg.Xrm.Command.Model 4 | { 5 | public interface IWebResourceRepository 6 | { 7 | Task> GetByNameAsync(IOrganizationServiceAsync2 crm, string[] fileNames, bool fetchContent = false); 8 | Task> GetBySolutionAsync(IOrganizationServiceAsync2 crm, string solutionUniqueName, bool fetchContent = false); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Model/IWorkflowRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | 3 | namespace Greg.Xrm.Command.Model 4 | { 5 | #pragma warning restore IDE1006 // Naming Styles 6 | 7 | 8 | public interface IWorkflowRepository 9 | { 10 | Task> GetByIdsAsync(IOrganizationServiceAsync2 crm, IEnumerable ids); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Model/ProcessTrigger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | using Microsoft.Xrm.Sdk; 3 | using Microsoft.Xrm.Sdk.Query; 4 | 5 | namespace Greg.Xrm.Command.Model 6 | { 7 | public class ProcessTrigger : EntityWrapper 8 | { 9 | public ProcessTrigger(Entity entity) : base(entity) 10 | { 11 | } 12 | 13 | public string? controlname => Get(); 14 | 15 | 16 | public class Repository : IProcessTriggerRepository 17 | { 18 | public async Task> GetByWorkflowIdAsync(IOrganizationServiceAsync2 crm, Guid workflowId) 19 | { 20 | var query = new QueryExpression("processtrigger"); 21 | query.ColumnSet.AddColumns(nameof(controlname)); 22 | query.Criteria.AddCondition("processid", ConditionOperator.Equal, workflowId); 23 | query.NoLock = true; 24 | 25 | var result = await crm.RetrieveMultipleAsync(query); 26 | 27 | return result.Entities.Select(x => new ProcessTrigger(x)).ToArray(); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Model/SavedQuery.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | 3 | namespace Greg.Xrm.Command.Model 4 | { 5 | public class SavedQuery : TableView 6 | { 7 | public SavedQuery(Entity entity) : base(entity) 8 | { 9 | } 10 | 11 | public SavedQuery() : base("savedquery") { } 12 | 13 | 14 | public class Repository : Repository, ISavedQueryRepository 15 | { 16 | public Repository() : base("savedquery", e => new SavedQuery(e)) 17 | { 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Model/UserQuery.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | 3 | namespace Greg.Xrm.Command.Model 4 | { 5 | public class UserQuery : TableView 6 | { 7 | public UserQuery(Entity entity) : base(entity) 8 | { 9 | } 10 | 11 | public UserQuery() : base("userquery") { } 12 | 13 | public class Repository : TableView.Repository, IUserQueryRepository 14 | { 15 | public Repository() : base("userquery", e => new UserQuery(e)) 16 | { 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Model/WebResourceType.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Model 2 | { 3 | public enum WebResourceType 4 | { 5 | WebPage = 1, 6 | StyleSheet = 2, 7 | Script = 3, 8 | Data = 4, 9 | ImagePng = 5, 10 | ImageJpg = 6, 11 | ImageGif = 7, 12 | Silverlight = 8, 13 | Xsl = 9, 14 | Icon = 10, 15 | ImageSvg = 11, 16 | Resx = 12, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Parsing/CommandTree.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Parsing 2 | { 3 | public class CommandTree : List, ICommandTree 4 | { 5 | public VerbNode? FindNode(IReadOnlyList verbs) 6 | { 7 | var node = this.Find(_ => string.Equals(_.Verb, verbs[0], StringComparison.OrdinalIgnoreCase)); 8 | if (node == null) return null; 9 | 10 | for (int i = 1; i < verbs.Count; i++) 11 | { 12 | var child = node.Children.Find(_ => string.Equals(_.Verb, verbs[i], StringComparison.OrdinalIgnoreCase)); 13 | if (child == null) return node; 14 | 15 | node = child; 16 | } 17 | 18 | return node; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Parsing/ICommandLineArguments.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Parsing 2 | { 3 | public interface ICommandLineArguments : IReadOnlyList 4 | { 5 | bool Remove(string arg); 6 | void RemoveAt(int index); 7 | 8 | int IndexOf(string arg); 9 | } 10 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Parsing/ICommandParser.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Parsing 2 | { 3 | /// 4 | /// Creates a command object starting from a set of arguments 5 | /// 6 | public interface ICommandParser 7 | { 8 | /// 9 | /// Checks if there is a command matching the arguments provided. 10 | /// If found, returns the command object filled with options. 11 | /// If not found, or any error has been encountered, returns an help command. 12 | /// 13 | /// The arguments that will be used to match the command. 14 | /// 15 | /// A command object, or an help command if no command has been found or an error happened during parsing. 16 | /// 17 | (object, CommandRunArgs) Parse(params string[] args); 18 | 19 | 20 | /// 21 | /// Checks if there is a command matching the arguments provided. 22 | /// If found, returns the command object filled with options. 23 | /// If not found, or any error has been encountered, returns an help command. 24 | /// 25 | /// The arguments that will be used to match the command. 26 | /// 27 | /// A command object, or an help command if no command has been found or an error happened during parsing. 28 | /// 29 | (object, CommandRunArgs) Parse(IEnumerable args); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Parsing/ICommandRegistry.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Greg.Xrm.Command.Parsing 4 | { 5 | /// 6 | /// Registry that holds all the command definitions 7 | /// 8 | public interface ICommandRegistry : IReadOnlyCommandRegistry 9 | { 10 | /// 11 | /// 12 | /// 13 | /// 14 | void InitializeFromAssembly(Assembly assembly); 15 | 16 | void ScanPluginsFolder(ICommandLineArguments args); 17 | 18 | /// 19 | /// Returns the executor type for the given command type 20 | /// 21 | /// The type of the command 22 | /// 23 | Type? GetExecutorTypeFor(Type commandType); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/README.md: -------------------------------------------------------------------------------- 1 | # Greg.Xrm.Command.Core 2 | 3 | Engine behind the [Greg.Xrm.Command](https://github.com/neronotte/Greg.Xrm.Command) command line tool. 4 | Can be used to import Greg.Xrm.Command business logic in any application. 5 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/AttributeDeletion/AttributeDeletionException.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.AttributeDeletion 2 | { 3 | public class AttributeDeletionException : Exception 4 | { 5 | public AttributeDeletionException() 6 | { 7 | } 8 | 9 | public AttributeDeletionException(string? message) : base(message) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/AttributeDeletion/AttributeDeletionService.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Model; 2 | using Greg.Xrm.Command.Services.Output; 3 | using Microsoft.Crm.Sdk.Messages; 4 | using Microsoft.PowerPlatform.Dataverse.Client; 5 | using Microsoft.Xrm.Sdk.Metadata; 6 | 7 | namespace Greg.Xrm.Command.Services.AttributeDeletion 8 | { 9 | public class AttributeDeletionService( 10 | IOutput output, 11 | IEnumerable strategies) 12 | : IAttributeDeletionService 13 | { 14 | public async Task DeleteAttributeAsync(IOrganizationServiceAsync2 crm, AttributeMetadata attribute, DependencyList dependencies, bool? simulation = false) 15 | { 16 | foreach(var strategy in strategies) 17 | { 18 | try 19 | { 20 | await strategy.HandleAsync(crm, attribute, dependencies); 21 | } 22 | catch(AttributeDeletionException ex) 23 | { 24 | output.WriteLine($"Error while trying to remove a dependency on {strategy.GetType().Name}: " + ex.Message, ConsoleColor.Red); 25 | } 26 | 27 | } 28 | 29 | await PublishAll(crm); 30 | } 31 | 32 | 33 | private async Task PublishAll(IOrganizationServiceAsync2 crm) 34 | { 35 | output.Write("Publishing all..."); 36 | await crm.ExecuteAsync(new PublishAllXmlRequest()); 37 | output.WriteLine("Done", ConsoleColor.Green); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/AttributeDeletion/AttributeDeletionStrategyForCharts.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Model; 2 | using Greg.Xrm.Command.Services.Output; 3 | using Microsoft.PowerPlatform.Dataverse.Client; 4 | using Microsoft.Xrm.Sdk.Metadata; 5 | 6 | namespace Greg.Xrm.Command.Services.AttributeDeletion 7 | { 8 | public class AttributeDeletionStrategyForCharts(IOutput output) 9 | : AttributeDeletionStrategyBase 10 | { 11 | public override ComponentType ComponentType => ComponentType.SavedQueryVisualization; 12 | 13 | 14 | protected override async Task HandleInternalAsync( 15 | IOrganizationServiceAsync2 crm, 16 | AttributeMetadata attribute, 17 | IReadOnlyList dependencies) 18 | { 19 | var result = await RetrieveDataAsync(crm, dependencies, "savedqueryvisualization", "savedqueryvisualizationid", "name", "datadescription"); 20 | 21 | var i = 0; 22 | foreach (var e in result) 23 | { 24 | ++i; 25 | 26 | output.Write($"Updating savedqueryvisualization {i}/{result.Count} {e.GetAttributeValue("name")}..."); 27 | e["datadescription"] = RemoveFieldFromFetchXml(e.GetAttributeValue("datadescription"), attribute.LogicalName); 28 | await crm.UpdateAsync(e); 29 | output.WriteLine("Done", ConsoleColor.Green); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/AttributeDeletion/AttributeDeletionStrategyForPluginStepImages.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Model; 2 | using Greg.Xrm.Command.Services.Output; 3 | using Microsoft.PowerPlatform.Dataverse.Client; 4 | using Microsoft.Xrm.Sdk.Metadata; 5 | 6 | namespace Greg.Xrm.Command.Services.AttributeDeletion 7 | { 8 | public class AttributeDeletionStrategyForPluginStepImages( 9 | IOutput output 10 | ) 11 | : AttributeDeletionStrategyBase 12 | { 13 | public override ComponentType ComponentType => ComponentType.SDKMessageProcessingStepImage; 14 | 15 | 16 | protected override async Task HandleInternalAsync( 17 | IOrganizationServiceAsync2 crm, 18 | AttributeMetadata attribute, 19 | IReadOnlyList dependencies) 20 | { 21 | var result = await RetrieveDataAsync(crm, dependencies, "sdkmessageprocessingstepimage", "sdkmessageprocessingstepimageid", "name", "attributes1"); 22 | 23 | var i = 0; 24 | foreach (var e in result) 25 | { 26 | ++i; 27 | output.Write($"Updating sdkmessageprocessingstepimage {i}/{result.Count} {e.GetAttributeValue("name")}..."); 28 | 29 | e["attributes1"] = RemoveValueFromCsvValues(e.GetAttributeValue("attributes1"), attribute.LogicalName); 30 | await crm.UpdateAsync(e); 31 | output.WriteLine("Done", ConsoleColor.Green); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/AttributeDeletion/AttributeDeletionStrategyForPluginSteps.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Model; 2 | using Greg.Xrm.Command.Services.Output; 3 | using Microsoft.PowerPlatform.Dataverse.Client; 4 | using Microsoft.Xrm.Sdk.Metadata; 5 | 6 | namespace Greg.Xrm.Command.Services.AttributeDeletion 7 | { 8 | public class AttributeDeletionStrategyForPluginSteps( 9 | IOutput output 10 | ) 11 | : AttributeDeletionStrategyBase 12 | { 13 | public override ComponentType ComponentType => ComponentType.SDKMessageProcessingStep; 14 | 15 | 16 | protected override async Task HandleInternalAsync( 17 | IOrganizationServiceAsync2 crm, 18 | AttributeMetadata attribute, 19 | IReadOnlyList dependencies) 20 | { 21 | var result = await RetrieveDataAsync(crm, dependencies, "sdkmessageprocessingstep", "sdkmessageprocessingstepid", "name", "filteringattributes"); 22 | 23 | var i = 0; 24 | foreach (var e in result) 25 | { 26 | ++i; 27 | output.Write($"Updating sdkmessageprocessingstep {i}/{result.Count} {e.GetAttributeValue("name")}..."); 28 | e["filteringattributes"] = RemoveValueFromCsvValues(e.GetAttributeValue("filteringattributes"), attribute.LogicalName); 29 | await crm.UpdateAsync(e); 30 | output.WriteLine("Done", ConsoleColor.Green); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/AttributeDeletion/AttributeDeletionStrategyForViews.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Model; 2 | using Greg.Xrm.Command.Services.Output; 3 | using Microsoft.PowerPlatform.Dataverse.Client; 4 | using Microsoft.Xrm.Sdk.Metadata; 5 | 6 | namespace Greg.Xrm.Command.Services.AttributeDeletion 7 | { 8 | public class AttributeDeletionStrategyForViews( 9 | IOutput output, 10 | ISavedQueryRepository savedQueryRepository, 11 | IUserQueryRepository userQueryRepository 12 | ) 13 | : AttributeDeletionStrategyBase 14 | { 15 | public override ComponentType ComponentType => ComponentType.SavedQuery; 16 | 17 | 18 | protected override async Task HandleInternalAsync( 19 | IOrganizationServiceAsync2 crm, 20 | AttributeMetadata attribute, 21 | IReadOnlyList dependencies) 22 | { 23 | var viewList = new List(); 24 | 25 | var savedQueries = await savedQueryRepository.GetByIdAsync(crm, dependencies.Select(x => x.dependentcomponentobjectid)); 26 | viewList.AddRange(savedQueries); 27 | 28 | var userQueries = await userQueryRepository.GetContainingAsync(crm, attribute.EntityLogicalName, attribute.SchemaName); 29 | viewList.AddRange(userQueries); 30 | 31 | var i = 0; 32 | foreach (var e in viewList) 33 | { 34 | ++i; 35 | 36 | output.Write($"Updating {e.EntityName} {i}/{viewList.Count} {e.name}..."); 37 | 38 | e.fetchxml = RemoveFieldFromFetchXml(e.fetchxml, attribute.LogicalName); 39 | 40 | if (e.layoutxml is not null) 41 | { 42 | e.layoutxml = RemoveFieldFromFetchXml(e.layoutxml, attribute.LogicalName); 43 | } 44 | 45 | await e.SaveOrUpdateAsync(crm); 46 | output.WriteLine("Done", ConsoleColor.Green); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/AttributeDeletion/IAttributeDeletionService.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Model; 2 | using Microsoft.PowerPlatform.Dataverse.Client; 3 | using Microsoft.Xrm.Sdk.Metadata; 4 | 5 | namespace Greg.Xrm.Command.Services.AttributeDeletion 6 | { 7 | public interface IAttributeDeletionService 8 | { 9 | Task DeleteAttributeAsync(IOrganizationServiceAsync2 crm, AttributeMetadata attribute, DependencyList dependencies, bool? simulation = false); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/AttributeDeletion/IAttributeDeletionStrategy.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Model; 2 | using Microsoft.PowerPlatform.Dataverse.Client; 3 | using Microsoft.Xrm.Sdk.Metadata; 4 | 5 | namespace Greg.Xrm.Command.Services.AttributeDeletion 6 | { 7 | public interface IAttributeDeletionStrategy 8 | { 9 | Task HandleAsync( 10 | IOrganizationServiceAsync2 crm, 11 | AttributeMetadata attribute, 12 | DependencyList dependencies); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/CommandHistory/CommandHistory.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.CommandHistory 2 | { 3 | class CommandHistory 4 | { 5 | public int MaxSize { get; set; } = 1000; 6 | 7 | public List Commands { get; set; } = new List(); 8 | 9 | public void Add(params string[] command) 10 | { 11 | var commandText = string.Join(" ", command.Select(x => EncloseInQuotesIfContainsSpace(x))); 12 | 13 | this.Commands.Add(commandText); 14 | if (this.Commands.Count > this.MaxSize) 15 | { 16 | this.Commands.RemoveAt(0); 17 | } 18 | } 19 | 20 | private static string EncloseInQuotesIfContainsSpace(string command) 21 | { 22 | if (command.Contains(' ')) 23 | { 24 | return $"\"{command}\""; 25 | } 26 | return command; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/CommandHistory/IHistoryTracker.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.CommandHistory 2 | { 3 | public interface IHistoryTracker 4 | { 5 | Task SetMaxLengthAsync(int maxLength); 6 | 7 | 8 | Task AddAsync(params string[] command); 9 | 10 | Task> GetLastAsync(int? count); 11 | 12 | Task ClearAsync(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/ComponentResolvers/IComponentResolver.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.ComponentResolvers 2 | { 3 | public interface IComponentResolver 4 | { 5 | Task> GetNamesAsync(IReadOnlyList componentIdSet); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/ComponentResolvers/IComponentResolverFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.ComponentResolvers 2 | { 3 | public interface IComponentResolverFactory 4 | { 5 | IComponentResolver? GetComponentResolverFor(ComponentType componentType); 6 | } 7 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/ComponentResolvers/ResolverByQuery.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Microsoft.PowerPlatform.Dataverse.Client; 3 | using Microsoft.Xrm.Sdk; 4 | using Microsoft.Xrm.Sdk.Query; 5 | using System.ServiceModel; 6 | 7 | namespace Greg.Xrm.Command.Services.ComponentResolvers 8 | { 9 | public class ResolverByQuery : IComponentResolver 10 | { 11 | private readonly IOrganizationServiceAsync2 crm; 12 | private readonly ILogger log; 13 | private readonly string table; 14 | private readonly string nameColumn; 15 | private readonly string? tableIdColumn; 16 | 17 | public ResolverByQuery(IOrganizationServiceAsync2 crm, ILogger log, string table, string nameColumn, string? tableIdColumn = null) 18 | { 19 | this.crm = crm; 20 | this.log = log; 21 | this.table = table; 22 | this.nameColumn = nameColumn; 23 | this.tableIdColumn = tableIdColumn ?? $"{table}id"; 24 | } 25 | 26 | public async Task> GetNamesAsync(IReadOnlyList componentIdSet) 27 | { 28 | Dictionary dict; 29 | try 30 | { 31 | var query = new QueryExpression(this.table); 32 | query.ColumnSet.AddColumns(this.nameColumn); 33 | query.Criteria.AddCondition(this.tableIdColumn, ConditionOperator.In, componentIdSet.Cast().ToArray()); 34 | query.NoLock = true; 35 | 36 | var result = await this.crm.RetrieveMultipleAsync(query); 37 | 38 | dict = result.Entities.ToDictionary(x => x.Id, x => x.GetAttributeValue(this.nameColumn)); 39 | } 40 | catch(FaultExceptionex) 41 | { 42 | this.log.LogError(ex, "Error while retrieving {table} records: {message}", this.table, ex.Message); 43 | 44 | dict = new(); 45 | } 46 | 47 | foreach (var id in componentIdSet) 48 | { 49 | if (!dict.ContainsKey(id)) 50 | { 51 | dict.Add(id, $"[Missing: {id}]"); 52 | } 53 | } 54 | return dict; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Connection/TokenDefinition.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Greg.Xrm.Command.Services.Connection 4 | { 5 | public class TokenDefinition 6 | { 7 | public TokenDefinition(Uri serviceUri, string accessToken) 8 | { 9 | if (serviceUri is null) 10 | { 11 | throw new ArgumentNullException(nameof(serviceUri)); 12 | } 13 | 14 | if (string.IsNullOrEmpty(accessToken)) 15 | { 16 | throw new ArgumentException($"'{nameof(accessToken)}' cannot be null or empty.", nameof(accessToken)); 17 | } 18 | 19 | this.ServiceUri = serviceUri; 20 | this.AccessToken = accessToken; 21 | } 22 | 23 | public TokenDefinition() 24 | { 25 | } 26 | 27 | public Uri? ServiceUri { get; set; } 28 | public string? AccessToken { get; set; } 29 | 30 | [JsonIgnore] 31 | public bool IsValid => this.ServiceUri != null && !string.IsNullOrEmpty(this.AccessToken); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/FolderTree.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services 2 | { 3 | public static class FolderTree 4 | { 5 | public static DirectoryInfo CreateFolderTree(DirectoryInfo root, string[] folderTree) 6 | { 7 | if (folderTree == null || folderTree.Length == 0) return root; 8 | 9 | var current = root; 10 | for (var i = 0; i < folderTree.Length; i++) 11 | { 12 | 13 | var folderName = folderTree[i]; 14 | var folder = current.GetDirectories(folderName).FirstOrDefault(); 15 | if (folder == null) 16 | { 17 | folder = current.CreateSubdirectory(folderName); 18 | } 19 | 20 | current = folder; 21 | } 22 | return current; 23 | } 24 | 25 | 26 | public static DirectoryInfo? RecurseBackFolderContainingFile(string fileName, DirectoryInfo? startDirectory = null) 27 | { 28 | if (startDirectory == null) 29 | { 30 | startDirectory = new DirectoryInfo(Environment.CurrentDirectory); 31 | } 32 | 33 | var current = startDirectory; 34 | do 35 | { 36 | var file = current.GetFiles(fileName).FirstOrDefault(); 37 | if (file != null) 38 | { 39 | return current; 40 | } 41 | 42 | current = current.Parent; 43 | } 44 | while (current != null); 45 | 46 | return null; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Graphs/ConsistencyException.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.Graphs 2 | { 3 | public class ConsistencyException : Exception 4 | { 5 | public ConsistencyException() { } 6 | public ConsistencyException(string message) : base(message) { } 7 | public ConsistencyException(string message, Exception inner) : base(message, inner) { } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Graphs/IDirectedArc.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.Graphs 2 | { 3 | public interface IDirectedArc 4 | where T : INodeContent 5 | 6 | { 7 | /// 8 | /// Gets the starting node of the arc 9 | /// 10 | IDirectedNode From { get; } 11 | 12 | /// 13 | /// Gets the final node of the arc 14 | /// 15 | IDirectedNode To { get; } 16 | 17 | /// 18 | /// Gets the additional information associated with the arc, with the given key 19 | /// 20 | /// The type of the info 21 | /// The key of the info to return 22 | /// The additional information associated with the arc, with the given key 23 | T1? GetAdditionalInfo(string key); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Graphs/IDirectedNode.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.Graphs 2 | { 3 | public interface IDirectedNode 4 | where T : INodeContent 5 | { 6 | /// 7 | /// Gets the content of the node 8 | /// 9 | T Content { get; } 10 | 11 | /// 12 | /// Gets the arcs that end into this node 13 | /// 14 | IReadOnlyList> InboundArcs { get; } 15 | 16 | /// 17 | /// Gets the arcs that start from this node 18 | /// 19 | IReadOnlyList> OutboundArcs { get; } 20 | 21 | 22 | /// 23 | /// Indicates whether the node has a self-loop 24 | /// 25 | bool HasAutoCycle { get; } 26 | 27 | 28 | /// 29 | /// Returns the autocycle, if any 30 | /// 31 | IDirectedArc? AutoCycle { get; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Graphs/IKey.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.Graphs 2 | { 3 | public interface IKey : IEquatable 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Graphs/INodeContent.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.Graphs 2 | { 3 | public interface INodeContent 4 | { 5 | object Key { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/IStorage.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services 2 | { 3 | public interface IStorage 4 | { 5 | DirectoryInfo GetOrCreateStorageFolder(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Pluralization/PluralizationFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.Pluralization 2 | { 3 | public class PluralizationFactory : IPluralizationFactory 4 | { 5 | private readonly Dictionary cache = new Dictionary(); 6 | 7 | 8 | public IPluralizationStrategy CreateFor(int languageCode) 9 | { 10 | if (cache.TryGetValue(languageCode, out var strategy)) 11 | return strategy; 12 | 13 | strategy = CreateStrategy(languageCode); 14 | cache.Add(languageCode, strategy); 15 | return strategy; 16 | } 17 | 18 | private static IPluralizationStrategy CreateStrategy(int languageCode) 19 | { 20 | if (languageCode == 1040) return new PluralizationStrategy1040(); 21 | if (languageCode == 1033) return new PluralizationStrategy1033(); 22 | 23 | return new PluralizationStrategyIdentity(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Pluralization/PluralizationStrategy1033.cs: -------------------------------------------------------------------------------- 1 | using Pluralize.NET; 2 | 3 | namespace Greg.Xrm.Command.Services.Pluralization 4 | { 5 | public class PluralizationStrategy1033 : IPluralizationStrategy 6 | { 7 | private readonly IPluralize service; 8 | 9 | public PluralizationStrategy1033() 10 | { 11 | service = new Pluralizer(); 12 | } 13 | 14 | 15 | public Task GetPluralForAsync(string word) 16 | { 17 | return Task.FromResult(service.Pluralize(word)); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Pluralization/PluralizationStrategy1040.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.Pluralization 2 | { 3 | public class PluralizationStrategy1040 : IPluralizationStrategy 4 | { 5 | public Task GetPluralForAsync(string word) 6 | { 7 | throw new NotImplementedException(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Pluralization/PluralizationStrategyIdentity.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.Pluralization 2 | { 3 | public class PluralizationStrategyIdentity : IPluralizationStrategy 4 | { 5 | public Task GetPluralForAsync(string word) 6 | { 7 | return Task.FromResult(word); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Project/IPacxProjectRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.Project 2 | { 3 | public interface IPacxProjectRepository 4 | { 5 | Task GetCurrentProjectAsync(); 6 | 7 | Task SaveAsync(PacxProjectDefinition projectDefinition, string folder, CancellationToken cancellationToken); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Project/PacxProject.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.Project 2 | { 3 | public abstract class PacxProject 4 | { 5 | public const string FileName = ".pacxproj"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Project/PacxProjectDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.Project 2 | { 3 | /// 4 | /// Represents a pacx project 5 | /// 6 | public class PacxProjectDefinition : PacxProject 7 | { 8 | public string Version { get; set; } = "1.0"; 9 | public bool IsSuspended { get; set; } = false; 10 | 11 | public string AuthProfileName { get; set; } = string.Empty; 12 | 13 | public string SolutionName { get; set; } = string.Empty; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Core/Services/Storage.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services 2 | { 3 | public class Storage : IStorage 4 | { 5 | private DirectoryInfo? storageFolder = null; 6 | 7 | public DirectoryInfo GetOrCreateStorageFolder() 8 | { 9 | if (this.storageFolder != null) 10 | return this.storageFolder; 11 | 12 | var folderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); 13 | if (!Directory.Exists(folderPath)) 14 | { 15 | Directory.CreateDirectory(folderPath); 16 | } 17 | 18 | folderPath = Path.Combine(folderPath, "Greg.Xrm.Command"); 19 | if (!Directory.Exists(folderPath)) 20 | { 21 | Directory.CreateDirectory(folderPath); 22 | } 23 | 24 | 25 | 26 | this.storageFolder = new DirectoryInfo(folderPath); 27 | return this.storageFolder; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/CommandException.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace Greg.Xrm.Command 4 | { 5 | [DebuggerDisplay("{Message}")] 6 | public class CommandException : Exception 7 | { 8 | public CommandException(int errorCode, string message) : base(message) 9 | { 10 | ErrorCode = errorCode; 11 | } 12 | 13 | public CommandException(int errorCode, string message, Exception innerException) : base(message, innerException) 14 | { 15 | ErrorCode = errorCode; 16 | } 17 | 18 | 19 | public int ErrorCode { get; } 20 | 21 | 22 | 23 | 24 | 25 | public const int ConnectionNotSet = 100001; 26 | public const int ConnectionInvalid = 100002; 27 | 28 | public const int CommandRequiredArgumentNotProvided = 200001; 29 | public const int CommandInvalidArgumentType = 200002; 30 | public const int CommandInvalidArgumentValue = 200003; 31 | public const int CommandCannotBeCreated = 200004; 32 | 33 | public const int DuplicateCommand = 300001; 34 | public const int DuplicateOption = 300002; 35 | 36 | public const int XrmError = 900001; 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/CommandResult.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command 2 | { 3 | public class CommandResult : Dictionary 4 | { 5 | protected CommandResult(bool isSuccess, string? errorMessage = null, Exception? exception = null) 6 | { 7 | this.IsSuccess = isSuccess; 8 | this.ErrorMessage = errorMessage ?? string.Empty; 9 | this.Exception = exception; 10 | } 11 | 12 | 13 | public bool IsSuccess { get; protected set; } 14 | public string ErrorMessage { get; protected set; } 15 | public Exception? Exception { get; protected set; } 16 | 17 | 18 | 19 | public static CommandResult Success() 20 | { 21 | return new CommandResult(true); 22 | } 23 | 24 | public static CommandResult Fail(string message, Exception? exception = null) 25 | { 26 | return new CommandResult(false, message, exception); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Greg.Xrm.Command.Interfaces.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | Greg.Xrm.Command 8 | True 9 | PACX - Interfaces 10 | _neronotte 11 | _neronotte 12 | Greg.Xrm.Command - Interfaces 13 | Copyright @ _neronotte 14 | https://github.com/neronotte/Greg.Xrm.Command 15 | Logo_80.png 16 | README.md 17 | https://github.com/neronotte/Greg.Xrm.Command 18 | pacx dataverse sdk pac cli 19 | 1.2024.10.151 20 | 1.1.1 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | True 32 | \ 33 | 34 | 35 | True 36 | \ 37 | PreserveNewest 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/ICommandExecutor.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command 2 | { 3 | public interface ICommandExecutor 4 | { 5 | Task ExecuteAsync(T command, CancellationToken cancellationToken); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Logo_80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neronotte/Greg.Xrm.Command/0a5c8b05ffe83e4cf86d6fcda1a4b7f653ab8ea0/Greg.Xrm.Command.Interfaces/Logo_80.png -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Model/CloneSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | 3 | namespace Greg.Xrm.Command.Model 4 | { 5 | /// 6 | /// Settings that can be used to configure the clonation feature 7 | /// 8 | public static class CloneSettings 9 | { 10 | private static readonly List ForbiddenAttributes = new(); 11 | 12 | static CloneSettings() 13 | { 14 | ForbiddenAttributes.AddRange(new[] 15 | { 16 | "statecode", 17 | "statuscode", 18 | "ownerid", 19 | "owningbusinessunit", 20 | "owningteam", 21 | "owninguser", 22 | "createdon", 23 | "createdby", 24 | "modifiedon", 25 | "modifiedby" 26 | }); 27 | } 28 | 29 | 30 | /// 31 | /// Indicates whether a property is forbidden or not for the clone. 32 | /// 33 | /// The entity that contains the property to clone 34 | /// The name of the property to clone 35 | /// Forbidden attributes 36 | /// 37 | public static bool IsForbidden(Entity original, string propertyName, string[] otherForbiddenAttributes) 38 | { 39 | otherForbiddenAttributes ??= Array.Empty(); 40 | 41 | if (string.Equals(propertyName, original.LogicalName + "id", StringComparison.OrdinalIgnoreCase)) return true; 42 | if (ForbiddenAttributes.Exists(x => string.Equals(x, propertyName, StringComparison.OrdinalIgnoreCase))) return true; 43 | #pragma warning disable S6605 // Collection-specific "Exists" method should be used instead of the "Any" extension 44 | return otherForbiddenAttributes.Any(x => string.Equals(x, propertyName, StringComparison.OrdinalIgnoreCase)); 45 | #pragma warning restore S6605 // Collection-specific "Exists" method should be used instead of the "Any" extension 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Model/IEntityWrapperInternal.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | 3 | namespace Greg.Xrm.Command.Model 4 | { 5 | /// 6 | /// USE WITH EXTREME CAUTION 7 | /// Interface that can be used to access internal members of the EntityWrapper class. 8 | /// 9 | /// Usage of this interface ***breaks the encapsulation principle of OOP***, 10 | /// should be used only when there is no other option 11 | /// 12 | public interface IEntityWrapperInternal 13 | { 14 | /// 15 | /// Returns the target entity on the entity wrapper. 16 | /// 17 | /// The target entity. 18 | Entity GetTarget(); 19 | 20 | 21 | 22 | /// 23 | /// Returns the pre-image used by the entity wrapper. 24 | /// 25 | /// The pre-image entity. 26 | Entity GetPreImage(); 27 | 28 | 29 | 30 | /// 31 | /// Returns the merge between pre-image and target. 32 | /// 33 | /// The post-image entity. 34 | Entity GetPostImage(); 35 | 36 | 37 | /// 38 | /// Allows users to override the Id of the current record. 39 | /// 40 | /// The new GUID for the current record. 41 | void SetId(Guid newId); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Parsing/Attributes/AliasAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command 2 | { 3 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] 4 | public class AliasAttribute : Attribute 5 | { 6 | public AliasAttribute(params string[] verbs) 7 | { 8 | this.Verbs = verbs ?? Array.Empty(); 9 | this.ExpandedVerbs = string.Join(" ", this.Verbs); 10 | } 11 | 12 | /// 13 | /// Gets the list of verbs that can be used to invoke the command 14 | /// 15 | public string[] Verbs { get; } 16 | 17 | /// 18 | /// Gets the list of verbs that can be used to invoke the command, as a single string 19 | /// 20 | public string ExpandedVerbs { get; } 21 | 22 | 23 | public override string ToString() 24 | { 25 | return ExpandedVerbs; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Parsing/Attributes/CommandAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command 2 | { 3 | /// 4 | /// Defines a new command 5 | /// 6 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] 7 | public class CommandAttribute : Attribute 8 | { 9 | /// 10 | /// Creates a new instance of the class 11 | /// 12 | /// The versm that can be used to invoke the command 13 | public CommandAttribute(params string[] verbs) 14 | { 15 | this.Verbs = verbs ?? Array.Empty(); 16 | } 17 | 18 | /// 19 | /// Gets the list of verbs that can be used to invoke the command 20 | /// 21 | public string[] Verbs { get; } 22 | 23 | /// 24 | /// Gets or sets the help text for the command 25 | /// 26 | public string? HelpText { get; set; } 27 | 28 | /// 29 | /// Indicates if the command must be hidden from the help list 30 | /// 31 | public bool Hidden { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Parsing/Attributes/OptionAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command 2 | { 3 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] 4 | public class OptionAttribute : Attribute 5 | { 6 | public OptionAttribute(string longName) 7 | { 8 | this.LongName = longName; 9 | this.ShortName = null; 10 | this.HelpText = null; 11 | this.DefaultValue = null; 12 | this.SuppressValuesHelp = false; 13 | } 14 | public OptionAttribute(string longName, string shortName) 15 | { 16 | this.LongName = longName; 17 | this.ShortName = shortName; 18 | this.HelpText = null; 19 | this.DefaultValue = null; 20 | this.SuppressValuesHelp = false; 21 | } 22 | 23 | public OptionAttribute(string longName, string shortName, string helpText, object? defaultValue = null) 24 | { 25 | this.LongName = longName; 26 | this.ShortName = shortName; 27 | this.HelpText = helpText; 28 | this.DefaultValue = defaultValue; 29 | this.SuppressValuesHelp = false; 30 | } 31 | 32 | public string LongName { get; } 33 | public string? ShortName { get; set; } 34 | public string? HelpText { get; set; } 35 | public object? DefaultValue { get; set; } 36 | 37 | public bool SuppressValuesHelp { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Parsing/ICanProvideUsageExample.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services; 2 | 3 | namespace Greg.Xrm.Command.Parsing 4 | { 5 | public interface ICanProvideUsageExample 6 | { 7 | void WriteUsageExamples(MarkdownWriter writer); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Parsing/ICommandTree.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Parsing 2 | { 3 | public interface ICommandTree : IReadOnlyList 4 | { 5 | VerbNode? FindNode(IReadOnlyList verbs); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Parsing/INamespaceHelper.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services; 2 | 3 | namespace Greg.Xrm.Command.Parsing 4 | { 5 | public interface INamespaceHelper 6 | { 7 | public string[] Verbs { get; } 8 | 9 | string GetHelp(); 10 | void WriteHelp(MarkdownWriter writer); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Parsing/IReadOnlyCommandRegistry.cs: -------------------------------------------------------------------------------- 1 | using Autofac.Core; 2 | 3 | namespace Greg.Xrm.Command.Parsing 4 | { 5 | public interface IReadOnlyCommandRegistry 6 | { 7 | ICommandTree Tree { get; } 8 | 9 | 10 | IReadOnlyList Commands { get; } 11 | 12 | 13 | IReadOnlyList Modules { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Parsing/NamespaceHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Parsing 2 | { 3 | public static class NamespaceHelper 4 | { 5 | public static INamespaceHelper Empty { get; } = new NamespaceHelperEmpty(); 6 | 7 | class NamespaceHelperEmpty : NamespaceHelperBase 8 | { 9 | public NamespaceHelperEmpty() : base(string.Empty, Array.Empty()) 10 | { 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Parsing/NamespaceHelperBase.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Services; 2 | 3 | namespace Greg.Xrm.Command.Parsing 4 | { 5 | public abstract class NamespaceHelperBase : INamespaceHelper 6 | { 7 | private readonly string help; 8 | 9 | protected NamespaceHelperBase(string help, params string[] verbs) 10 | { 11 | this.help = help; 12 | this.Verbs = verbs; 13 | } 14 | 15 | public string[] Verbs { get; } 16 | 17 | public string GetHelp() 18 | { 19 | return this.help; 20 | } 21 | 22 | public virtual void WriteHelp(MarkdownWriter writer) 23 | { 24 | writer.WriteParagraph(this.GetHelp()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Parsing/OptionDefinition.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Greg.Xrm.Command.Parsing 4 | { 5 | public class OptionDefinition 6 | { 7 | public OptionDefinition(PropertyInfo property, OptionAttribute option, bool isRequired) 8 | { 9 | this.Property = property; 10 | this.Option = option; 11 | this.IsRequired = isRequired; 12 | } 13 | 14 | public PropertyInfo Property { get; } 15 | public OptionAttribute Option { get; } 16 | 17 | public bool IsRequired { get; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This NuGet Package contains the interfaces that are required to implement a command for [PACX](https://github.com/neronotte/Greg.Xrm.Command). -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Services/Connection/IOrganizationServiceRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.PowerPlatform.Dataverse.Client; 2 | 3 | namespace Greg.Xrm.Command.Services.Connection 4 | { 5 | public interface IOrganizationServiceRepository 6 | { 7 | string GetTokenCachePath(); 8 | 9 | Task GetAllConnectionDefinitionsAsync(); 10 | 11 | Task GetCurrentConnectionAsync(); 12 | Task GetConnectionByName(string connectionName); 13 | 14 | Task GetEnvironmentFromConnectioStringAsync(string connectionName); 15 | 16 | Task SetConnectionAsync(string name, string connectionString); 17 | Task DeleteConnectionAsync(string name); 18 | 19 | Task SetDefaultAsync(string name); 20 | Task SetDefaultSolutionAsync(string uniqueName); 21 | Task GetCurrentDefaultSolutionAsync(); 22 | Task RenameConnectionAsync(string oldName, string newName); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Services/Encryption/AesEncryption.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Text; 3 | 4 | namespace Greg.Xrm.Command.Services.Encryption 5 | { 6 | public class AesEncryption 7 | { 8 | public static string Encrypt(string plaintext, byte[] key, byte[] iv) 9 | { 10 | using Aes aesAlg = Aes.Create(); 11 | aesAlg.Key = key; 12 | aesAlg.IV = iv; 13 | 14 | ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); 15 | byte[] encryptedBytes; 16 | using (var msEncrypt = new System.IO.MemoryStream()) 17 | { 18 | using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) 19 | { 20 | byte[] plainBytes = Encoding.UTF8.GetBytes(plaintext); 21 | csEncrypt.Write(plainBytes, 0, plainBytes.Length); 22 | } 23 | encryptedBytes = msEncrypt.ToArray(); 24 | } 25 | return Convert.ToBase64String(encryptedBytes); 26 | } 27 | 28 | 29 | public static string Decrypt(string ciphertext, byte[] key, byte[] iv) 30 | { 31 | using Aes aesAlg = Aes.Create(); 32 | 33 | aesAlg.Key = key; 34 | aesAlg.IV = iv; 35 | ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); 36 | byte[] decryptedBytes; 37 | 38 | using var msDecrypt = new MemoryStream(Convert.FromBase64String(ciphertext)); 39 | using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read); 40 | using var msPlain = new MemoryStream(); 41 | 42 | csDecrypt.CopyTo(msPlain); 43 | decryptedBytes = msPlain.ToArray(); 44 | 45 | 46 | return Encoding.UTF8.GetString(decryptedBytes); 47 | 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Services/Output/IOutput.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Greg.Xrm.Command.Services.Output 3 | { 4 | public interface IOutput 5 | { 6 | IOutput Write(object? text); 7 | IOutput Write(object? text, ConsoleColor color); 8 | IOutput WriteLine(); 9 | IOutput WriteLine(object? text); 10 | IOutput WriteLine(object? text, ConsoleColor color); 11 | IOutput WriteTable(IReadOnlyList collection, Func rowHeaders, Func rowData, Func? colorPicker = null); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Services/Pluralization/IPluralizationFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.Pluralization 2 | { 3 | public interface IPluralizationFactory 4 | { 5 | IPluralizationStrategy CreateFor(int languageCode); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Services/Pluralization/IPluralizationStrategy.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace Greg.Xrm.Command.Services.Pluralization 4 | { 5 | public interface IPluralizationStrategy 6 | { 7 | Task GetPluralForAsync(string word); 8 | } 9 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command.Interfaces/Services/Settings/ISettingsRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command.Services.Settings 2 | { 3 | public interface ISettingsRepository 4 | { 5 | Task GetAsync(string key); 6 | Task SetAsync(string key, T value); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Greg.Xrm.Command/CommandLineArguments.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command.Parsing; 2 | 3 | namespace Greg.Xrm.Command 4 | { 5 | public class CommandLineArguments : List, ICommandLineArguments 6 | { 7 | public CommandLineArguments(string[] args) : base(args) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /Greg.Xrm.Command/Extensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System.Reflection; 3 | 4 | namespace Greg.Xrm.Command 5 | { 6 | public static class Extensions 7 | { 8 | public static void RegisterCommandExecutors(this IServiceCollection services, Assembly assembly) 9 | { 10 | var genericCommandExecutorType = typeof(ICommandExecutor<>); 11 | #pragma warning disable S6605 // Collection-specific "Exists" method should be used instead of the "Any" extension 12 | assembly 13 | .GetTypes() 14 | .Where(t => !t.IsAbstract && !t.IsInterface) 15 | .Where(t => t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericCommandExecutorType)) 16 | .ToList() 17 | .ForEach(t =>{ 18 | var specificCommandExecutorType = t.GetInterfaces().First(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericCommandExecutorType); 19 | services.AddTransient(specificCommandExecutorType, t); 20 | }); 21 | #pragma warning restore S6605 // Collection-specific "Exists" method should be used instead of the "Any" extension 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Greg.Xrm.Command/ICommandExecutorFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Greg.Xrm.Command 2 | { 3 | public interface ICommandExecutorFactory : IDisposable 4 | { 5 | object? CreateFor(Type commandType); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Greg.Xrm.Command/README.md: -------------------------------------------------------------------------------- 1 | # Greg.Xrm.Command ⁓ aka PACX 2 | 3 | **Command line utility belt for Dataverse**. 4 | 5 | Provides a set of useful commands to perform various operations on Dataverse, operations not yet supported by the Power Platform CLI, such as dependency checks and datamodel manipulation. 6 | Is a tool from the community, for the community. Fully open source. 7 | 8 | Feel free to contribute to the project, or to suggest new commands. 9 | 10 | [Official GitHub repository](https://github.com/neronotte/Greg.Xrm.Command) 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Logo_80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neronotte/Greg.Xrm.Command/0a5c8b05ffe83e4cf86d6fcda1a4b7f653ab8ea0/Logo_80.png -------------------------------------------------------------------------------- /icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neronotte/Greg.Xrm.Command/0a5c8b05ffe83e4cf86d6fcda1a4b7f653ab8ea0/icon128.png -------------------------------------------------------------------------------- /sample/SamplePlugin/SampleCommand.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command; 2 | 3 | namespace SamplePlugin 4 | { 5 | [Command("sample", HelpText = "Sample plugin that does a simple echo")] 6 | public class SampleCommand 7 | { 8 | [Option("echo", HelpText = "Echo message")] 9 | public string? Echo { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/SamplePlugin/SampleCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using Greg.Xrm.Command; 2 | using Greg.Xrm.Command.Services.Output; 3 | 4 | namespace SamplePlugin 5 | { 6 | public class SampleCommandExecutor : ICommandExecutor 7 | { 8 | private readonly IOutput output; 9 | 10 | public SampleCommandExecutor(IOutput output) 11 | { 12 | this.output = output; 13 | } 14 | 15 | public Task ExecuteAsync(SampleCommand command, CancellationToken cancellationToken) 16 | { 17 | this.output.WriteLine($"Echo: {command.Echo ?? "-"}"); 18 | return Task.FromResult(CommandResult.Success()); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample/SamplePlugin/SamplePlugin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------