diff --git a/bundles/com.espressif.idf.ui/OSGI-INF/l10n/bundle.properties b/bundles/com.espressif.idf.ui/OSGI-INF/l10n/bundle.properties
index d98fdafb4..d2cf44af9 100644
--- a/bundles/com.espressif.idf.ui/OSGI-INF/l10n/bundle.properties
+++ b/bundles/com.espressif.idf.ui/OSGI-INF/l10n/bundle.properties
@@ -68,4 +68,5 @@ command.name.PartitionTableEditor = ESP-IDF: Partition Table Editor
command.label.nsvTableEditor = ESP-IDF: NVS Table Editor
command.name.nvsTableEditor = ESP-IDF: NVS Table Editor
command.tooltip.nvsTableEditor = NVS Editor can help you to easily edit NVS CSV, generate encrypted and non-encrypted partitions through GUI, without interacting directly with the csv files.
-build_hints.name = Build Hints
\ No newline at end of file
+build_hints.name = Build Hints
+command.label.SbomCommandLabel = ESP-IDF: SBOM Tool
\ No newline at end of file
diff --git a/bundles/com.espressif.idf.ui/icons/Software_bill_of_materials_(SBOM).png b/bundles/com.espressif.idf.ui/icons/Software_bill_of_materials_(SBOM).png
new file mode 100644
index 000000000..a46564888
Binary files /dev/null and b/bundles/com.espressif.idf.ui/icons/Software_bill_of_materials_(SBOM).png differ
diff --git a/bundles/com.espressif.idf.ui/plugin.xml b/bundles/com.espressif.idf.ui/plugin.xml
index a288f248c..fadc285bb 100644
--- a/bundles/com.espressif.idf.ui/plugin.xml
+++ b/bundles/com.espressif.idf.ui/plugin.xml
@@ -428,6 +428,22 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/dialogs/SbomCommandDialog.java b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/dialogs/SbomCommandDialog.java
new file mode 100644
index 000000000..677fa0393
--- /dev/null
+++ b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/dialogs/SbomCommandDialog.java
@@ -0,0 +1,455 @@
+package com.espressif.idf.ui.dialogs;
+
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.TitleAreaDialog;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.console.IHyperlink;
+import org.eclipse.ui.console.IPatternMatchListener;
+import org.eclipse.ui.console.MessageConsoleStream;
+import org.eclipse.ui.console.PatternMatchEvent;
+import org.eclipse.ui.console.TextConsole;
+import org.eclipse.ui.ide.IDE;
+
+import com.espressif.idf.core.IDFCorePlugin;
+import com.espressif.idf.core.ProcessBuilderFactory;
+import com.espressif.idf.core.logging.Logger;
+import com.espressif.idf.core.util.IDFUtil;
+import com.espressif.idf.core.util.StringUtil;
+import com.espressif.idf.ui.IDFConsole;
+import com.espressif.idf.ui.update.Messages;
+
+public class SbomCommandDialog extends TitleAreaDialog
+{
+
+ private static final String PATH_REGEX = "(.+)"; //$NON-NLS-1$
+ private static final String DEFAULT_OUTPUT_FILE_NAME = "sbom.txt"; //$NON-NLS-1$
+ private static final String ESP_IDF_SBOM_COMMAND_NAME = "esp_idf_sbom"; //$NON-NLS-1$
+ protected static final String[] EXTENSIONS = { "*.json" }; //$NON-NLS-1$
+ private MessageConsoleStream console;
+ private Button saveOutputToFileCheckBoxButton;
+ private Text outputFileText;
+ private IProject selectedProject;
+ private Text projectDescriptionPathText;
+ private String projectDescription;
+ private boolean saveOutputFileStatus;
+ private String outputFilePath;
+
+ public SbomCommandDialog(Shell parentShell)
+ {
+ super(parentShell);
+ }
+
+ @Override
+ public void create()
+ {
+ super.create();
+ getShell().setText(Messages.SbomCommandDialog_SbomTitle);
+ setTitle(Messages.SbomCommandDialog_SbomTitle);
+ setMessage(Messages.SbomCommandDialog_SbomInfoMsg);
+ setDefaults();
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent)
+ {
+ Composite area = (Composite) super.createDialogArea(parent);
+
+ Composite container = new Composite(area, SWT.NONE);
+ container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ GridLayout layout = new GridLayout(3, false);
+ container.setLayout(layout);
+
+ saveOutputToFileCheckBoxButton = new Button(container, SWT.CHECK);
+ saveOutputToFileCheckBoxButton.setText(Messages.SbomCommandDialog_RedirectOutputCheckBoxLbl);
+ saveOutputToFileCheckBoxButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1));
+
+ Label projectDescriptionPathLbl = new Label(container, SWT.NONE);
+ projectDescriptionPathLbl.setText(Messages.SbomCommandDialog_ProjectDescriptionPathLbl);
+ projectDescriptionPathText = new Text(container, SWT.SINGLE | SWT.BORDER | SWT.H_SCROLL);
+ projectDescriptionPathText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ Button projectDescriptionBtn = new Button(container, SWT.PUSH);
+ projectDescriptionBtn.setLayoutData(new GridData(SWT.RIGHT, SWT.FILL, false, false, 1, 1));
+ projectDescriptionBtn.setText(Messages.SbomCommandDialog_BrowseBtnTxt);
+ projectDescriptionBtn.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ FileDialog fileSelectionDialog = new FileDialog(getParentShell());
+ fileSelectionDialog.setFilterExtensions(EXTENSIONS);
+ fileSelectionDialog.setFilterPath(buildProjectDescriptionPath());
+ String selectedFilePath = fileSelectionDialog.open();
+
+ if (selectedFilePath != null && !selectedFilePath.isEmpty())
+ {
+ projectDescriptionPathText.setText(selectedFilePath);
+ }
+ super.widgetSelected(e);
+ }
+ });
+
+ Label outputFileLbl = new Label(container, SWT.NONE);
+ outputFileLbl.setText(Messages.SbomCommandDialog_OutputFilePathLbl);
+ outputFileText = new Text(container, SWT.NONE);
+ outputFileText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ Button outputFileBrowseBtn = new Button(container, SWT.PUSH);
+ outputFileBrowseBtn.setText(Messages.SbomCommandDialog_BrowseBtnTxt);
+ outputFileBrowseBtn.addSelectionListener(new SelectionAdapter()
+ {
+ @Override
+ public void widgetSelected(SelectionEvent e)
+ {
+ FileDialog fileSelectionDialog = new FileDialog(getParentShell());
+ fileSelectionDialog.setFilterPath(buildProjectDescriptionPath());
+ String selectedFilePath = fileSelectionDialog.open();
+
+ if (selectedFilePath != null && !selectedFilePath.isEmpty())
+ {
+ outputFileText.setText(selectedFilePath);
+ }
+ super.widgetSelected(e);
+ }
+ });
+ saveOutputToFileCheckBoxButton.addListener(SWT.Selection, e -> {
+ outputFileLbl.setVisible(saveOutputToFileCheckBoxButton.getSelection());
+ outputFileText.setVisible(saveOutputToFileCheckBoxButton.getSelection());
+ outputFileBrowseBtn.setVisible(saveOutputToFileCheckBoxButton.getSelection());
+ container.requestLayout();
+ });
+
+ return super.createDialogArea(parent);
+ }
+
+ protected String runCommand(List arguments, Path workDir, Map env)
+ {
+ String exportCmdOp = StringUtil.EMPTY;
+ ProcessBuilderFactory processRunner = new ProcessBuilderFactory();
+ try
+ {
+ IStatus status = processRunner.runInBackground(arguments, workDir, env);
+ if (status == null)
+ {
+ IStatus errorStatus = IDFCorePlugin.errorStatus(Messages.SbomCommandDialog_StatusCantBeNullErrorMsg,
+ null);
+ Logger.log(IDFCorePlugin.getPlugin(), errorStatus);
+ return errorStatus.getMessage();
+ }
+
+ exportCmdOp = status.getMessage();
+ Logger.log(exportCmdOp);
+ }
+ catch (IOException e)
+ {
+ Logger.log(e);
+ }
+ return exportCmdOp;
+ }
+
+ @Override
+ protected void okPressed()
+ {
+ console = new IDFConsole().getConsoleStream(Messages.IDFToolsHandler_ToolsManagerConsole, null, false);
+ Job refreshJob = new Job(Messages.SbomCommandDialog_RefreshProjectJob)
+ {
+
+ protected IStatus run(IProgressMonitor monitor)
+ {
+ try
+ {
+ selectedProject.refreshLocal(IResource.DEPTH_INFINITE, null);
+ }
+ catch (CoreException e)
+ {
+ Logger.log(e);
+ }
+ return Status.OK_STATUS;
+ }
+ };
+ Job espIdfSbomJob = new Job(Messages.SbomCommandDialog_EspIdfSbomJobName)
+ {
+
+ protected IStatus run(IProgressMonitor monitor)
+ {
+ if (!getEspIdfSbomInstalledStatus())
+ {
+ installEspIdfSbom();
+ }
+ runEspIdfSbomCommand();
+ try
+ {
+ refreshJob.join();
+ }
+ catch (InterruptedException e)
+ {
+ Logger.log(e);
+ }
+ return Status.OK_STATUS;
+ }
+ };
+
+ projectDescription = projectDescriptionPathText.getText();
+ saveOutputFileStatus = saveOutputToFileCheckBoxButton.getSelection();
+ outputFilePath = outputFileText.getText();
+ espIdfSbomJob.schedule();
+
+ super.okPressed();
+ }
+
+ @Override
+ protected boolean isResizable()
+ {
+ return true;
+ }
+
+ private void setDefaults()
+ {
+ ISelection selection = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService()
+ .getSelection();
+ if (selection instanceof IStructuredSelection)
+ {
+ Object element = ((IStructuredSelection) selection).getFirstElement();
+
+ if (element instanceof IResource)
+ {
+ selectedProject = ((IResource) element).getProject();
+ }
+ }
+ projectDescriptionPathText.setText(buildProjectDescriptionPath());
+ if (!Files.isRegularFile(Paths.get(projectDescriptionPathText.getText())))
+ {
+ setMessage(Messages.SbomCommandDialog_ProjectDescDoesntExistDefaultErrorMsg);
+ getButton(IDialogConstants.OK_ID).setEnabled(false);
+ }
+ outputFileText.setText(String.join(FileSystems.getDefault().getSeparator(),
+ Paths.get(selectedProject.getLocationURI()).toString(), DEFAULT_OUTPUT_FILE_NAME));
+
+ saveOutputToFileCheckBoxButton.setSelection(false);
+ saveOutputToFileCheckBoxButton.notifyListeners(SWT.Selection, null);
+
+ outputFileText.addListener(SWT.Modify, e -> getButton(IDialogConstants.OK_ID).setEnabled(validateInput()));
+ projectDescriptionPathText.addListener(SWT.Modify,
+ e -> getButton(IDialogConstants.OK_ID).setEnabled(validateInput()));
+ }
+
+ private String buildProjectDescriptionPath()
+ {
+ return String.join(FileSystems.getDefault().getSeparator(),
+ Paths.get(selectedProject.getLocationURI()).toString(), "build", //$NON-NLS-1$
+ "project_description.json"); //$NON-NLS-1$
+ }
+
+ private void runEspIdfSbomCommand()
+ {
+ Map environment = new HashMap<>(System.getenv());
+ List arguments = new ArrayList<>();
+ final String pythonEnvPath = IDFUtil.getIDFPythonEnvPath();
+ arguments.add(pythonEnvPath);
+ arguments.add("-m"); //$NON-NLS-1$
+ arguments.add(ESP_IDF_SBOM_COMMAND_NAME);
+ arguments.add("create"); //$NON-NLS-1$
+ arguments.add(projectDescription);
+ if (saveOutputFileStatus)
+ {
+ arguments.add("--output-file"); //$NON-NLS-1$
+ arguments.add(outputFilePath);
+ }
+ String cmdOutput = runCommand(arguments, null, environment);
+ cmdOutput = cmdOutput.isEmpty() && saveOutputFileStatus
+ ? String.format(Messages.SbomCommandDialog_ConsoleRedirectedOutputFormatString, outputFilePath)
+ : cmdOutput;
+ console.getConsole().addPatternMatchListener(getPatternMatchListener());
+ console.println(cmdOutput);
+
+ }
+
+ private IPatternMatchListener getPatternMatchListener()
+ {
+ return new IPatternMatchListener()
+ {
+
+ public void matchFound(PatternMatchEvent event)
+ {
+
+ try
+ {
+ IHyperlink hepHyperlink = createHyperlinkWhichOpensFileInEditor();
+ // calculating the right offset and length to highlight only the output path
+ console.getConsole().addHyperlink(hepHyperlink, getPattern().length() - PATH_REGEX.length(),
+ event.getLength() - getPattern().length() + PATH_REGEX.length());
+ }
+ catch (BadLocationException e)
+ {
+ Logger.log(e);
+ }
+ }
+
+ private IHyperlink createHyperlinkWhichOpensFileInEditor()
+ {
+ return new IHyperlink()
+ {
+ public void linkActivated()
+ {
+ IFileStore fileStore = EFS.getLocalFileSystem().getStore(new Path(outputFilePath));
+ if (!fileStore.fetchInfo().isDirectory() && fileStore.fetchInfo().exists())
+ {
+ IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+ try
+ {
+ IDE.openEditorOnFileStore(page, fileStore);
+ }
+ catch (PartInitException e)
+ {
+ Logger.log(e);
+ }
+ }
+
+ }
+
+ public void linkEntered()
+ {
+ }
+
+ public void linkExited()
+ {
+ }
+ };
+ }
+
+ public void disconnect()
+ {
+ }
+
+ public void connect(TextConsole console)
+ {
+ }
+
+ public String getPattern()
+ {
+ return String.format(Messages.SbomCommandDialog_ConsoleRedirectedOutputFormatString, PATH_REGEX);
+ }
+
+ public String getLineQualifier()
+ {
+ return null;
+ }
+
+ public int getCompilerFlags()
+ {
+ return 0;
+ }
+ };
+ }
+
+ private boolean validateInput()
+ {
+ boolean validateStatus = true;
+ java.nio.file.Path projectDescriptionPath = null;
+ try
+ {
+ projectDescriptionPath = Paths.get(projectDescriptionPathText.getText());
+ }
+ catch (InvalidPathException e)
+ {
+ validateStatus = false;
+ setErrorMessage(Messages.SbomCommandDialog_InvalidProjectDescPathErrorMsg);
+ }
+ if (projectDescriptionPath != null && !Files.isRegularFile(projectDescriptionPath))
+ {
+ validateStatus = false;
+ setErrorMessage(Messages.SbomCommandDialog_ProjectDescDoesntExistsErrorMsg);
+ }
+
+ java.nio.file.Path outputFilePath = null;
+ try
+ {
+ outputFilePath = Paths.get(outputFileText.getText());
+ }
+ catch (InvalidPathException e)
+ {
+ validateStatus = false;
+ setErrorMessage(Messages.SbomCommandDialog_InvalidOutputFilePathErrorMsg);
+ }
+ if (outputFilePath != null && saveOutputToFileCheckBoxButton.getSelection()
+ && checkIfFileIsNotWritable(outputFilePath))
+ {
+ validateStatus = false;
+ setErrorMessage(Messages.SbomCommandDialog_OutputFileNotWritabbleErrorMsg);
+ }
+
+ if (validateStatus)
+ {
+ setErrorMessage(null);
+ }
+ return validateStatus;
+ }
+
+ private boolean checkIfFileIsNotWritable(java.nio.file.Path pathToFile)
+ {
+ return Files.exists(pathToFile) && !Files.isWritable(pathToFile);
+ }
+
+ private void installEspIdfSbom()
+ {
+ Map environment = new HashMap<>(System.getenv());
+ List arguments = new ArrayList<>();
+ final String pythonEnvPath = IDFUtil.getIDFPythonEnvPath();
+ arguments.add(pythonEnvPath);
+ arguments.add("-m"); //$NON-NLS-1$
+ arguments.add("pip"); //$NON-NLS-1$
+ arguments.add("install"); //$NON-NLS-1$
+ arguments.add(ESP_IDF_SBOM_COMMAND_NAME);
+ String cmdOutput = runCommand(arguments, null, environment);
+ console.println(cmdOutput);
+
+ }
+
+ private boolean getEspIdfSbomInstalledStatus()
+ {
+ Map environment = new HashMap<>(System.getenv());
+ List arguments = new ArrayList<>();
+ final String pythonEnvPath = IDFUtil.getIDFPythonEnvPath();
+ arguments.add(pythonEnvPath);
+ arguments.add("-m"); //$NON-NLS-1$
+ arguments.add("pip"); //$NON-NLS-1$
+ arguments.add("list"); //$NON-NLS-1$
+ String cmdOutput = runCommand(arguments, null, environment);
+ return cmdOutput.contains("esp-idf-sbom"); //$NON-NLS-1$
+ }
+}
diff --git a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/handlers/SbomCommandHandler.java b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/handlers/SbomCommandHandler.java
new file mode 100644
index 000000000..1c5b00826
--- /dev/null
+++ b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/handlers/SbomCommandHandler.java
@@ -0,0 +1,23 @@
+package com.espressif.idf.ui.handlers;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.PlatformUI;
+
+import com.espressif.idf.ui.dialogs.SbomCommandDialog;
+
+public class SbomCommandHandler extends AbstractHandler
+{
+
+ public Object execute(ExecutionEvent event) throws ExecutionException
+ {
+ Shell activeShell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
+ SbomCommandDialog flashDialog = new SbomCommandDialog(activeShell);
+ flashDialog.create();
+ flashDialog.open();
+ return null;
+ }
+
+}
diff --git a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/Messages.java b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/Messages.java
index 09f8502c6..ce52527eb 100644
--- a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/Messages.java
+++ b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/Messages.java
@@ -51,7 +51,23 @@ public class Messages extends NLS
public static String PythonIdfEnvMsg;
public static String MissingIdfPathMsg;
public static String NotFoundMsg;
-
+
+ public static String SbomCommandDialog_BrowseBtnTxt;
+ public static String SbomCommandDialog_ConsoleRedirectedOutputFormatString;
+ public static String SbomCommandDialog_EspIdfSbomJobName;
+ public static String SbomCommandDialog_InvalidProjectDescPathErrorMsg;
+ public static String SbomCommandDialog_InvalidOutputFilePathErrorMsg;
+ public static String SbomCommandDialog_OutputFileNotWritabbleErrorMsg;
+ public static String SbomCommandDialog_OutputFilePathLbl;
+ public static String SbomCommandDialog_ProjectDescDoesntExistDefaultErrorMsg;
+ public static String SbomCommandDialog_ProjectDescDoesntExistsErrorMsg;
+ public static String SbomCommandDialog_ProjectDescriptionPathLbl;
+ public static String SbomCommandDialog_RedirectOutputCheckBoxLbl;
+ public static String SbomCommandDialog_RefreshProjectJob;
+ public static String SbomCommandDialog_SbomInfoMsg;
+ public static String SbomCommandDialog_SbomTitle;
+ public static String SbomCommandDialog_StatusCantBeNullErrorMsg;
+
static
{
// initialize resource bundle
diff --git a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/messages.properties b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/messages.properties
index 94c6ac3de..4ffa881ee 100644
--- a/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/messages.properties
+++ b/bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/update/messages.properties
@@ -44,3 +44,18 @@ EclipseMsg=Eclipse Version:
PythonIdfEnvMsg=Python set for IDF_PYTHON_ENV:
MissingIdfPathMsg=ESP-IDF version cannot be checked. IDF_PATH or IDF_PYTHON_ENV_PATH are not set.
NotFoundMsg=
+SbomCommandDialog_BrowseBtnTxt=Browse
+SbomCommandDialog_ConsoleRedirectedOutputFormatString=The output was redirected to the file: %s
+SbomCommandDialog_EspIdfSbomJobName=Running esp-idf-sbom command...
+SbomCommandDialog_InvalidProjectDescPathErrorMsg=The path to project_description.json is invalid
+SbomCommandDialog_InvalidOutputFilePathErrorMsg=The path to the output file is invalid
+SbomCommandDialog_OutputFileNotWritabbleErrorMsg=The selected output file is not writable
+SbomCommandDialog_OutputFilePathLbl=Output File Path:
+SbomCommandDialog_ProjectDescDoesntExistDefaultErrorMsg=Provided project_description.json doesn't exist. Build the project first.
+SbomCommandDialog_ProjectDescDoesntExistsErrorMsg=Provided project_description.json doesn't exist.
+SbomCommandDialog_ProjectDescriptionPathLbl=Project Description Path:
+SbomCommandDialog_RedirectOutputCheckBoxLbl=Redirect output to the file
+SbomCommandDialog_RefreshProjectJob=Refreshing the project
+SbomCommandDialog_SbomInfoMsg=Provide path to the project description file and click OK. The result of the command will be displayed on the console
+SbomCommandDialog_SbomTitle=Software Bill of Materials Tool
+SbomCommandDialog_StatusCantBeNullErrorMsg=Operation status cannot be null. Please ensure the operation was executed correctly.