/// <summary> /// Creates a child log scope. /// </summary> /// <param name="scopeName">The child scope name.</param> /// <returns>Newly-created scope.</returns> public BackgroundLogScope CreateChildScope(string scopeName) { var now = DateTime.UtcNow; int scopeSequence; lock (this.syncLock) { if (this.completed.HasValue) { throw new InvalidOperationException($"Log scope \"{this.Name}\" has alrady been completed."); } scopeSequence = this.ExecutionLogger.GetNextScopeSequence(); var scope = new BackgroundLogScope(this.ExecutionLogger, scopeName ?? string.Empty, Task.Run(createScopeInternalAsync)); this.childScopes.Add(scope); return(scope); } async Task <int?> createScopeInternalAsync() { int?parentScopeId = await this.ScopeId.ConfigureAwait(false); await RompDb.CreateLogScopeAsync(this.ExecutionId, scopeSequence, parentScopeId, scopeName ?? string.Empty, now); return(scopeSequence); } }
public static PackageSpecifier FromArgs(ArgList args) { var packageName = args.PopCommand(); if (string.IsNullOrEmpty(packageName)) { return(null); } var inst = new PackageSpecifier(); args.ProcessOptions(inst.ParseOption); // set source to default if not already specified if (inst.Source == null && !string.IsNullOrWhiteSpace(RompConfig.DefaultSource)) { var match = RompDb.GetPackageSources() .FirstOrDefault(s => string.Equals(s.Name, RompConfig.DefaultSource, StringComparison.OrdinalIgnoreCase)); if (match != null) { if (string.IsNullOrEmpty(match.UserName) || match.Password == null) { inst.Source = new UniversalFeedEndpoint(match.FeedUrl, true); } else { inst.Source = new UniversalFeedEndpoint(new Uri(match.FeedUrl), match.UserName, match.Password); } } } if (packageName.EndsWith(".upack", StringComparison.OrdinalIgnoreCase)) { if (inst.Source != null) { throw new RompException("--source cannot be specified if <packageName> refers to a file."); } if (inst.PackageVersion != null) { throw new RompException("--version cannot be specified if <packageName> refers to a file."); } inst.FileName = packageName; } else { try { inst.PackageId = UniversalPackageId.Parse(packageName); } catch (Exception ex) { throw new RompException("Invalid package name: " + packageName, ex); } } return(inst); }
public async Task ExecuteAsync() { var startTime = DateTime.UtcNow; this.ExecutionId = RompDb.CreateExecution(startTime, Domains.ExecutionStatus.Normal, Domains.ExecutionRunState.Executing, this.Simulation); this.DefaultExternalContext = new RompExecutionContext(this); this.Executer = new ExecuterThread(new AnonymousBlockStatement(this.plan), this); var result = ExecutionStatus.Error; try { await RompSessionVariable.ExpandValuesAsync(this.DefaultExternalContext); var targetDir = RompSessionVariable.GetSessionVariable(new RuntimeVariableName("TargetDirectory", RuntimeValueType.Scalar))?.GetValue().AsString(); if (string.IsNullOrWhiteSpace(targetDir) || !PathEx.IsPathRooted(targetDir)) { this.Executer.RootLogScope.Log(LogLevel.Error, "Invalid value for $TargetDirectory."); result = ExecutionStatus.Error; } else { PackageInstaller.TargetDirectory = targetDir; DirectoryEx.Create(targetDir); result = await this.Executer.ExecuteAsync(); } } catch (Exception ex) { if (!(ex is ExecutionFailureException)) { Logger.Log(MessageLevel.Error, "Unhandled exception in executer: " + ex.ToString()); } result = ExecutionStatus.Error; } finally { try { this.CleanTempDirectory(); } catch { } await this.RootExecutionLog.CompleteAllAsync(); RompDb.CompleteExecution( executionId: this.ExecutionId.Value, endDate: DateTime.UtcNow, statusCode: result >= ExecutionStatus.Error ? Domains.ExecutionStatus.Error : result >= ExecutionStatus.Warning ? Domains.ExecutionStatus.Warning : Domains.ExecutionStatus.Normal ); } }
private bool ParseOption(ArgOption o) { if (string.Equals("source", o.Key, StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(o.Value)) { throw new RompException("Expected source name or URL after --source="); } if (o.Value.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || o.Value.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { this.Source = new UniversalFeedEndpoint(o.Value, true); } else { var source = RompDb.GetPackageSources() .FirstOrDefault(s => string.Equals(s.Name, o.Value, StringComparison.OrdinalIgnoreCase)); if (source == null) { throw new RompException($"Package source \"{o.Value}\" not found."); } if (string.IsNullOrEmpty(source.UserName) || source.Password == null) { this.Source = new UniversalFeedEndpoint(source.FeedUrl, true); } else { this.Source = new UniversalFeedEndpoint(new Uri(source.FeedUrl), source.UserName, source.Password); } } return(true); } else if (string.Equals("version", o.Key, StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(o.Value)) { throw new RompException("Expected package version after --version="); } try { this.PackageVersion = UniversalPackageVersion.Parse(o.Value); } catch (Exception ex) { throw new RompException("Invalid version: " + o.Value, ex); } return(true); } return(false); }
/// <summary> /// Completes the log scope with the specified time. /// </summary> /// <param name="endTime">Log scope end time.</param> public Task CompleteScopeAsync(DateTime endTime) { lock (this.syncLock) { if (this.completeLogScopeTask != null) { return(this.completeLogScopeTask); } this.completed = endTime; this.completeLogScopeTask = innerCompleteAsync(); } return(innerCompleteAsync()); async Task innerCompleteAsync() { var tasks = new List <Task>(this.childScopes.Count + 1); var writeTask = this.writeLogMessagesTask; if (writeTask != null) { tasks.Add(writeTask); } if (this.childScopes.Count > 0) { foreach (var s in this.childScopes) { tasks.Add(s.CompleteScopeAsync(endTime)); } } await Task.WhenAll(tasks).ConfigureAwait(false); foreach (var s in this.childScopes) { if (s.maxLevel > this.maxLevel) { this.maxLevel = s.maxLevel; } } await RompDb.CompleteLogScopeAsync(this.ExecutionId, this.ScopeId.Result.Value, endTime); } }
private void MessagesWritten(Task task) { lock (this.syncLock) { if (this.entries.Count > 0) { var entries = this.entries; var writeTask = Task.Run(() => RompDb.WriteLogMessagesAsync(this.ExecutionId, (int)this.ScopeId.Result, entries)); this.writeLogMessagesTask = writeTask.ContinueWith(this.MessagesWritten); this.entries = new List <LogEntry>(); } else { this.writeLogMessagesTask = null; } } }
public static ResourceCredentials TryGetCredentials(this IHasCredentials obj) { if (obj == null) { throw new ArgumentNullException(nameof(obj)); } var name = CredentialName.TryParse(obj.CredentialName); if (name == null) { return(null); } var credentialInfo = RompDb.GetCredentialsByName(name.TypeName, name.Name); if (credentialInfo == null) { return(null); } return(Factory.CreateResourceCredentials(credentialInfo)); }
/// <summary> /// Posts a log message to be written to the database as soon as possible. /// </summary> /// <param name="level">The message level.</param> /// <param name="message">The message text.</param> public void WriteMessage(MessageLevel level, string message) { var now = DateTime.UtcNow; lock (this.syncLock) { if (this.completed.HasValue) { throw new InvalidOperationException($"Log scope {this.Name} has already been completed."); } if (this.maxLevel < (int)level) { this.maxLevel = (int)level; } // scope has not been created yet or waiting on messages to be written if (!this.ScopeId.IsCompleted || this.writeLogMessagesTask != null) { this.entries.Add(new LogEntry(this.ExecutionLogger.GetNextLogEntrySequence(), level, message)); if (this.writeLogMessagesTask == null) { this.writeLogMessagesTask = this.ScopeId.ContinueWith(this.MessagesWritten); } } // scope is created and nothing is being written else { this.writeLogMessagesTask = Task.Run(() => RompDb.WriteLogMessagesAsync( executionId: this.ExecutionId, scopeSequence: this.ScopeId.Result.Value, new[] { new LogEntry(this.ExecutionLogger.GetNextLogEntrySequence(), level, message) } )).ContinueWith(this.MessagesWritten); } } }
private static async Task RunPlanAsync(ScopedStatementBlock script, bool simulate) { if (simulate) { Console.WriteLine("Running as simulation"); } Console.WriteLine(); var executer = new RompExecutionEnvironment(script, simulate); if (!Console.IsOutputRedirected) { RompConsoleMessenger.ShowProgress = true; using (var done = new CancellationTokenSource()) { Task consoleTask = null; try { consoleTask = Task.Run(() => RompConsoleMessenger.MonitorStatus(executer, done.Token)); await executer.ExecuteAsync(); } finally { done.Cancel(); try { if (consoleTask != null) { await consoleTask; } } catch { } } } } else { RompConsoleMessenger.ShowProgress = false; await executer.ExecuteAsync(); } var exec = RompDb.GetExecutions() .FirstOrDefault(e => e.ExecutionId == executer.ExecutionId); if (exec != null) { var logs = RompDb.GetExecutionLogs(exec.ExecutionId); foreach (var log in logs) { log.WriteErrors(Console.Out); } if (exec.StatusCode == Domains.ExecutionStatus.Normal) { RompConsoleMessenger.WriteDirect($"Job #{executer.ExecutionId} completed successfully.", ConsoleColor.White); } else if (exec.StatusCode == Domains.ExecutionStatus.Warning) { RompConsoleMessenger.WriteDirect($"Job #{executer.ExecutionId} completed with warnings.", ConsoleColor.Yellow); } else { RompConsoleMessenger.WriteDirect($"Job #{executer.ExecutionId} encountered an error.", ConsoleColor.Red); throw new RompException("Job execution failed."); } Console.WriteLine(); } else { throw new RompException("Execution could not be created."); } }
public override IEnumerable <SDK.CredentialsInfo> GetCredentials() => RompDb.GetCredentials().Select(c => new SDK.CredentialsInfo(c.CredentialType_Name, c.Credential_Name, c.Configuration_Xml, null, null));
private async Task <int?> CreateRootScopeAsync(int sequence) { await Task.Run(() => RompDb.CreateLogScopeAsync(this.ExecutionId, sequence, null, string.Empty, DateTime.UtcNow)); return(sequence); }
private static void Credentials(ArgList args) { var command = args.PopCommand()?.ToLowerInvariant(); switch (command) { case "list": list(); break; case "display": display(); break; case "store": store(); break; case "delete": delete(); break; default: Console.WriteLine("Usage:"); Console.WriteLine("romp credentials list"); Console.WriteLine("romp credentials display <name> [--show-hidden]"); Console.WriteLine("romp credentials store <name>"); Console.WriteLine("romp credentials delete <name>"); break; } void list() { foreach (var c in RompDb.GetCredentials()) { Console.WriteLine(c.CredentialType_Name + "::" + c.Credential_Name); } } void display() { var(type, name) = parseQualifiedName(); var creds = RompDb.GetCredentialsByName(type, name); if (creds == null) { throw new RompException($"Credentials {type}::{name} not found."); } bool showHidden = false; args.ProcessOptions( o => { if (string.Equals(o.Key, "show-hidden", StringComparison.OrdinalIgnoreCase)) { showHidden = true; return(true); } else { return(false); } } ); args.ThrowIfAnyRemaining(); var instance = (ResourceCredentials)Persistence.DeserializeFromPersistedObjectXml(creds.Configuration_Xml); Console.WriteLine($"Name: {creds.CredentialType_Name}::{creds.Credential_Name}"); foreach (var prop in Persistence.GetPersistentProperties(instance.GetType(), false)) { var alias = prop.GetCustomAttribute <ScriptAliasAttribute>()?.Alias; if (alias != null) // only show items with ScriptAlias { var propName = prop.GetCustomAttribute <DisplayNameAttribute>()?.DisplayName ?? alias; bool hidden = prop.GetCustomAttribute <PersistentAttribute>().Encrypted; var value = prop.GetValue(instance); if (value is SecureString secure) { value = AH.Unprotect(secure); } if (hidden && !showHidden) { value = "(hidden)"; } if (value == null) { value = "(not specified)"; } Console.WriteLine(propName + ": " + value); } } } void store() { var n = parseQualifiedName(); var type = (from c in ExtensionsManager.GetComponentsByBaseClass <ResourceCredentials>() let a = c.ComponentType.GetCustomAttribute <ScriptAliasAttribute>() where string.Equals(a?.Alias, n.type, StringComparison.OrdinalIgnoreCase) || string.Equals(c.ComponentType.Name, n.type, StringComparison.OrdinalIgnoreCase) orderby string.Equals(a?.Alias, n.type, StringComparison.OrdinalIgnoreCase) descending select c.ComponentType).FirstOrDefault(); if (type == null) { throw new RompException($"Unknown credentials type \"{n.type}\". Are you missing an extension?"); } var credentials = (ResourceCredentials)Activator.CreateInstance(type); if (!Console.IsInputRedirected) { foreach (var property in Persistence.GetPersistentProperties(type, true)) { Again: Console.Write((property.GetCustomAttribute <DisplayNameAttribute>()?.DisplayName ?? property.Name) + ": "); string value; if (property.GetCustomAttribute <PersistentAttribute>()?.Encrypted == true || property.PropertyType == typeof(SecureString)) { value = ReadSensitive(); } else { value = Console.ReadLine(); } if (!string.IsNullOrEmpty(value)) { if (property.PropertyType == typeof(string)) { property.SetValue(credentials, value); } else if (property.PropertyType == typeof(SecureString)) { property.SetValue(credentials, AH.CreateSecureString(value)); } else { try { var convertedValue = Convert.ChangeType(value, Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType); property.SetValue(credentials, convertedValue); } catch (Exception ex) { Console.Error.WriteLine("Invalid value: " + ex.Message); goto Again; } } } } } else { throw new RompException("Credentials must be stored interactively."); } RompDb.CreateOrUpdateCredentials(n.name, credentials, true); Console.WriteLine("Credentials stored."); } void delete() { var(type, name) = parseQualifiedName(); RompDb.DeleteCredentials(type, name); Console.WriteLine("Credentials deleted."); } (string type, string name) parseQualifiedName() { var qualifiedName = args.PopCommand(); if (string.IsNullOrEmpty(qualifiedName)) { throw new RompException("Expected credentials name."); } var parts = qualifiedName.Split(new[] { "::" }, StringSplitOptions.None); if (parts.Length != 2 || string.IsNullOrWhiteSpace(parts[0]) || string.IsNullOrWhiteSpace(parts[1])) { throw new RompException("Invalid credentials name specification."); } return(parts[0], parts[1]); } }
private static void Jobs(ArgList args) { var command = args.PopCommand()?.ToLowerInvariant(); switch (command) { case "list": list(); break; case "logs": logs(); break; case "purge": purge(); break; default: Console.WriteLine("Usage:"); Console.WriteLine("romp jobs list"); Console.WriteLine("romp jobs logs [jobId]"); Console.WriteLine("romp jobs purge <days>"); break; } void list() { args.ThrowIfAnyRemaining(); var jobs = RompDb.GetExecutions() .OrderByDescending(j => j.StartDate); Console.WriteLine("Jobs:"); bool any = false; foreach (var job in jobs) { any = true; var text = $" {job.ExecutionId} {job.StartDate.LocalDateTime} {Domains.ExecutionStatus.GetName(job.StatusCode)}"; if (job.StatusCode == Domains.ExecutionStatus.Error) { RompConsoleMessenger.WriteDirect(text, ConsoleColor.Red); } else if (job.StatusCode == Domains.ExecutionStatus.Warning) { RompConsoleMessenger.WriteDirect(text, ConsoleColor.Yellow); } else { RompConsoleMessenger.WriteDirect(text); } } if (!any) { Console.WriteLine(" (none)"); } } void logs() { int?jobId = null; var jobIdText = args.PopCommand(); if (!string.IsNullOrEmpty(jobIdText)) { jobId = AH.ParseInt(jobIdText); if (jobId == null) { throw new RompException("Invalid job ID."); } } args.ThrowIfAnyRemaining(); if (jobId == null) { var latest = RompDb.GetExecutions().OrderByDescending(j => j.ExecutionId).FirstOrDefault(); jobId = latest?.ExecutionId; } if (jobId != null) { foreach (var log in RompDb.GetExecutionLogs(jobId.Value)) { log.WriteText(0, Console.Out); } } } void purge() { var daysText = args.PopCommand(); if (string.IsNullOrEmpty(daysText)) { throw new RompException("Usage: romp logs purge <days>"); } if (!int.TryParse(daysText, out int days) || days < 0) { throw new RompException("Must specify a nonnegative integer for \"days\" argument."); } var now = DateTimeOffset.Now; var executions = RompDb.GetExecutions() .Where(e => (int)now.Subtract(e.StartDate).TotalDays >= days) .ToList(); Console.WriteLine($"Purging logs for jobs older than {days} days."); foreach (var exec in executions) { Console.WriteLine($"Purging job #{exec.ExecutionId} ({exec.StartDate.LocalDateTime})..."); RompDb.DeleteExecution(exec.ExecutionId); } Console.WriteLine($"Purged {executions.Count} jobs."); Console.WriteLine(); } }
private static void Sources(ArgList args) { var command = args.PopCommand()?.ToLowerInvariant(); switch (command) { case "list": list(); break; case "display": display(); break; case "create": create(); break; case "delete": delete(); break; case "default": defaultCommand(); break; default: Console.WriteLine("Usage:"); Console.WriteLine("romp sources list"); Console.WriteLine("romp sources display <name> [--show-hidden]"); Console.WriteLine("romp sources create <name> <url>"); Console.WriteLine("romp sources delete <name>"); break; } void list() { bool any = false; Console.WriteLine("Package sources:"); foreach (var s in RompDb.GetPackageSources()) { any = true; var url = s.FeedUrl; if (!string.IsNullOrEmpty(s.UserName)) { url = s.UserName + "@" + url; } Console.WriteLine(" " + s.Name + ": " + url); } if (!any) { Console.WriteLine(" (none)"); } } void display() { var name = args.PopCommand(); if (string.IsNullOrEmpty(name)) { throw new RompException("Expected source name."); } var source = RompDb.GetPackageSources() .FirstOrDefault(s => string.Equals(s.Name, name, StringComparison.OrdinalIgnoreCase)); if (source == null) { throw new RompException($"Source {name} not found."); } bool showHidden = false; args.ProcessOptions( o => { if (string.Equals(o.Key, "show-hidden", StringComparison.OrdinalIgnoreCase)) { showHidden = true; return(true); } else { return(false); } } ); args.ThrowIfAnyRemaining(); Console.WriteLine("Name: " + source.Name); Console.WriteLine("Url: " + source.FeedUrl); Console.WriteLine("User: "******"(not specified)"); if (showHidden) { Console.WriteLine("Password: "******"(not specified)"); } } void create() { var name = args.PopCommand(); var url = args.PopCommand(); if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(url)) { throw new RompException("Usage: romp sources create <name> <url>"); } Uri uri; try { uri = new Uri(url); } catch (Exception ex) { throw new RompException("Invalid URL: " + ex.Message, ex); } string userName = null; SecureString password = null; if (!string.IsNullOrEmpty(uri.UserInfo)) { var parts = uri.UserInfo.Split(new[] { ':' }, 2); userName = parts[0]; password = AH.CreateSecureString(parts.Length > 1 ? parts[1] : string.Empty); } var sanitizedUrl = new UriBuilder(uri) { UserName = null, Password = null }; RompDb.CreateOrUpdatePackageSource(name, sanitizedUrl.ToString(), userName, password); Console.WriteLine("Package source stored."); } void delete() { var name = args.PopCommand(); if (string.IsNullOrEmpty(name)) { throw new RompException("Expected source name."); } RompDb.DeletePackageSource(name); Console.WriteLine("Package source deleted."); if (string.Equals(RompConfig.DefaultSource, name, StringComparison.OrdinalIgnoreCase)) { RompConfig.DeleteValue("default-source"); } } void defaultCommand() { var name = args.PopCommand(); if (string.IsNullOrEmpty(name)) { throw new RompException("Expected source name."); } if (!RompDb.GetPackageSources().Any(s => string.Equals(s.Name, name, StringComparison.OrdinalIgnoreCase))) { throw new RompException($"No source named {name} has been configured."); } RompConfig.SetValue("default-source", name); Console.WriteLine($"Default source set to {name}."); } }
public static async Task <int> Main(string[] args) { try { RompConsoleMessenger.WriteDirect("romp " + typeof(Program).Assembly.GetName().Version.ToString(3), ConsoleColor.White); var argList = new ArgList(args); RompConfig.Initialize(argList); RompSdkConfig.Initialize(); // this is a hack due to the weird way the extensions manager works Directory.CreateDirectory(RompConfig.ExtensionsPath); ExtensionsManager.SetEnvironmentConfiguration(RompConfig.ExtensionsPath, RompConfig.ExtensionsTempPath, AppDomain.CurrentDomain.BaseDirectory); RompDb.Initialize(); GlobalRompPlanValidator.Initialize(); Logger.AddMessenger(new RompConsoleMessenger()); RompConsoleMessenger.MinimumLevel = RompConfig.LogLevel; var command = argList.PopCommand()?.ToLowerInvariant(); switch (command) { case "install": await Install(argList); break; case "uninstall": await Uninstall(argList); break; case "validate": Validate(argList); break; case "inspect": await Inspect(argList); break; case "pack": Pack(argList); break; case "sources": Sources(argList); break; case "jobs": Jobs(argList); break; case "credentials": Credentials(argList); break; case "config": Config(argList); break; case "packages": await Packages(argList); break; case "about": About(argList); break; default: WriteUsage(); break; } WaitForEnter(); return(0); } catch (RompException ex) { Console.Error.WriteLine(ex.Message); WaitForEnter(); return(-1); } finally { RompDb.Cleanup(); } }
private static async Task Install(ArgList args) { var spec = PackageSpecifier.FromArgs(args); if (spec == null) { throw new RompException("Usage: romp install <package-file-or-name> [--version=<version-number>] [--source=<name-or-feed-url>] [--force] [-Vvar=value...]"); } Console.WriteLine("Package: " + spec); Console.WriteLine(); await ExtensionsManager.WaitForInitializationAsync(); bool simulate = false; bool force = false; var vars = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); using (var package = await spec.FetchPackageAsync(args, default)) { args.ProcessOptions(parseOption); if (!force) { var registeredPackage = await GetRegisteredPackageAsync(spec.PackageId); if (registeredPackage != null) { Console.WriteLine("Package is already installed. Use --force to install anyway."); return; } } foreach (var var in vars) { RompSessionVariable.SetSessionVariable(var.Key, var.Value); } var packageInfo = RompPackInfo.Load(package); if (packageInfo.WriteScriptErrors()) { throw new RompException("Error compiling install script."); } foreach (var var in packageInfo.Variables) { if (var.Value.Value != null && !vars.ContainsKey(var.Key)) { RompSessionVariable.SetSessionVariable(var.Key, var.Value.Value.Value); } } foreach (var var in packageInfo.Variables) { // should also validate/coerce type here if (var.Value.Required && var.Value.Value == null && !vars.ContainsKey(var.Key)) { if (Console.IsOutputRedirected) { throw new RompException("Missing required variable: " + var.Key); } Console.WriteLine($"Variable \"{var.Key}\" is required."); if (!string.IsNullOrWhiteSpace(var.Value.Description)) { Console.WriteLine("Description: " + var.Value.Description); } string value; do { // should not assume type to be scalar Console.Write(new RuntimeVariableName(var.Key, RuntimeValueType.Scalar) + ": "); if (var.Value.Sensitive) { value = ReadSensitive(); } else { value = Console.ReadLine(); } }while (string.IsNullOrEmpty(value)); RompSessionVariable.SetSessionVariable(var.Key, value); } } bool credentialsMissing = false; foreach (var creds in packageInfo.Credentials.Values) { if (RompDb.GetCredentialsByName(creds.Type, creds.Name) == null) { credentialsMissing = true; var text = "Credentials required: " + creds.FullName; if (!string.IsNullOrWhiteSpace(creds.Description)) { text += " (" + creds.Description + ")"; } RompConsoleMessenger.WriteDirect(text, ConsoleColor.Red); } } if (credentialsMissing) { throw new RompException("Use \"romp credentials store\" to create missing credentials."); } await PackageInstaller.RunAsync(package, "install.otter", simulate); using (var registry = PackageRegistry.GetRegistry(RompConfig.UserMode)) { await registry.LockAsync(); await registry.RegisterPackageAsync( new RegisteredPackage { Group = package.Group, Name = package.Name, Version = package.Version.ToString(), InstallationDate = DateTimeOffset.Now.ToString("o"), InstalledBy = Environment.UserName, InstalledUsing = "Romp", InstallPath = PackageInstaller.TargetDirectory } ); await registry.UnlockAsync(); } } bool parseOption(ArgOption o) { switch (o.Key.ToLowerInvariant()) { case "force": force = true; return(true); } if (o.Key.StartsWith("V") && o.Key.Length > 1) { vars[o.Key.Substring(1)] = o.Value ?? string.Empty; return(true); } return(false); } }