public void execute(String[] args) {

		int ix = 0;

		String configFilePath = args.length != 0 && !args[0].startsWith("-") ? args[ix++]
				: System.getProperty(CONFIG_FILE_PROPERTY);

		System.out.println("server.conf: " + configFilePath);

		File serverRootDir = getServerDirFromConfig(configFilePath);
		if (serverRootDir == null || (args.length - ix) == 0) {
			displayUsage("");
			System.exit(-1);
			return;
		}

		try {
			serverRootDir = serverRootDir.getCanonicalFile();
		}
		catch (IOException e1) {
			System.err.println("Failed to resolve server directory: " + serverRootDir);
			System.exit(-1);
		}

		System.out.println("Using server directory: " + serverRootDir);

		File userFile = new File(serverRootDir, UserManager.USER_PASSWORD_FILE);
		if (!serverRootDir.isDirectory() || !userFile.isFile()) {
			System.err.println("Invalid Ghidra server directory!");
			System.exit(-1);
		}

		File cmdDir = CommandProcessor.getCommandDir(serverRootDir);
		if (!cmdDir.isDirectory() || !cmdDir.canWrite()) {
			System.err.println("Insufficient privilege or server not started!");
			System.exit(-1);
		}

		// Process command line
		boolean listRepositories = false;
		boolean listAllUserPermissions = false;
		Set<String> listUsernameSet = new HashSet<>();
		boolean listUsers = false;
		boolean migrationConfirmed = false;
		boolean migrationAbort = false;
		ArrayList<String> cmdList = new ArrayList<>();
		int cmdLen = 1;
		for (; ix < args.length; ix += cmdLen) {
			boolean queueCmd = true;
			String pwdHash = null;
			switch (args[ix]) {
				case CommandProcessor.ADD_USER_COMMAND:  // add user
					cmdLen = 2;
					validateSID(args, ix + 1);
					if (hasOptionalArg(args, ix + 2, CommandProcessor.PASSWORD_OPTION)) {
						++cmdLen;
						pwdHash = promptForPasswordAndGetSaltedHash(args[ix + 1]);
					}
					break;
				case CommandProcessor.REMOVE_USER_COMMAND: // remove user
					cmdLen = 2;
					validateSID(args, ix + 1);
					break;
				case CommandProcessor.RESET_USER_COMMAND: // reset user
					cmdLen = 2;
					validateSID(args, ix + 1);
					if (hasOptionalArg(args, ix + 2, CommandProcessor.PASSWORD_OPTION)) {
						++cmdLen;
						pwdHash = promptForPasswordAndGetSaltedHash(args[ix + 1]);
					}
					break;
				case CommandProcessor.SET_USER_DN_COMMAND: // set/add user with DN for PKI
					cmdLen = 3;
					validateSID(args, ix + 1);
					validateDN(args, ix + 2);
					break;
				case CommandProcessor.GRANT_USER_COMMAND: // grant repository permission
					cmdLen = 4;
					validateSID(args, ix + 1);
					validatePermission(args, ix + 2);
					validateRepositoryName(args, ix + 3, serverRootDir);
					break;
				case CommandProcessor.REVOKE_USER_COMMAND: // revoke repository access
					cmdLen = 3;
					validateSID(args, ix + 1);
					validateRepositoryName(args, ix + 2, serverRootDir);
					break;
				case LIST_COMMAND: // list repositories;
					queueCmd = false;
					listRepositories = true;
					boolean hasUsernames = false;
					while ((ix + 1) < args.length && !args[ix + 1].startsWith("-")) {
						String sid = args[++ix]; // consume next arg as user sid
						validateSID(sid);
						listUsernameSet.add(sid);
						hasUsernames = true;
					}
					if (!hasUsernames && (ix + 1) < args.length && "--users".equals(args[ix + 1])) {
						listAllUserPermissions = true;
						++ix;
					}
					break;
				case USERS_COMMAND: // list users (also affects listRepositories)
					queueCmd = false;
					listUsers = true;
					listUsernameSet.clear();
					break;
				case MIGRATE_ALL_COMMAND: // list repositories;
					queueCmd = false;
					if (!migrationConfirmed && !confirmMigration()) {
						migrationAbort = true;
					}
					migrationConfirmed = true;
					if (!migrationAbort) {
						RepositoryManager.markAllRepositoriesForIndexMigration(serverRootDir);
					}
					break;
				case MIGRATE_COMMAND: // list repositories
					queueCmd = false;
					if (ix == (args.length - 1)) {
						System.err.println(
							"Missing " + MIGRATE_COMMAND + " repository name argument");
					}
					else {
						String repositoryName = args[ix + 1];
						if (!migrationConfirmed && !confirmMigration()) {
							migrationAbort = true;
						}
						migrationConfirmed = true;
						if (!migrationAbort) {
							Repository.markRepositoryForIndexMigration(serverRootDir,
								repositoryName,
								false);
						}
					}
					break;
				default:
					displayUsage("Invalid usage!");
					System.exit(-1);
			}
			if (queueCmd) {
				addCommand(cmdList, args, ix, cmdLen, pwdHash);
			}
		}

		if (cmdList.size() != 0) {
			try {
				CommandProcessor.writeCommands(cmdList, cmdDir);
			}
			catch (IOException e) {
				System.err.println("Failed to queue commands: " + e.toString());
				System.exit(-1);
			}
			System.out.println("Command queued.");
		}

		if (listUsers) {
			UserManager.listUsers(serverRootDir);
		}
		if (listRepositories) {
			if (listUsernameSet.isEmpty()) {
				RepositoryManager.listRepositories(serverRootDir, listAllUserPermissions);
			}
			else {
				RepositoryManager.listRepositories(serverRootDir, listUsernameSet);
			}
		}
		System.out.println();
	}
Main method for processing ServerAdmin command line arguments.
The following properties may be set:
<pre>
  UserAdmin.invocation - identifies the name of the application used when displaying usage text.
  UserAdmin.config - identifies the config file instead of passing on command line.
</pre>
@param args command line arguments
Ghidra/Features/GhidraServer/src/main/java/ghidra/server/ServerAdmin.java